How to select rows that matches "multiple rows condition" in mysql - mysql

So I created a sql fiddle to explain my problem much clearer:
http://sqlfiddle.com/#!9/f35416
As you can see I have 3 tables and 1 of them links the 2 others.
table name: tags
---------------------
id | tag | value
---------------------
1 | color | green
2 | color | yellow
3 | color | red
4 | category | dress
5 | category | car
table name: product_tags_link
---------------------
product_id | tag_id
---------------------
1 | 1
1 | 5
2 | 1
3 | 2
3 | 5
4 | 4
5 | 4
5 | 1
table name: products
---------------------
id | name
---------------------
1 | green car
2 | yellow only
3 | yellow car
4 | dress only
5 | green dress
How can I make it so If I can get whatever product that have a "color" "green" and "category" "car"?
I tried doing:
select `ptl`.`product_id`
from `product_tags_link` as `ptl`
inner join `tags` on `tags`.`id` = `ptl`.`tag_id`
where ((`tags`.`tag` = "green") or (`tags`.`value` = "car"))
but it will return other product that is green OR car. changing it to and will not return anything as well.
I'm hoping to receive is product_id: 1 which have both color:green and category:car

Join all 3 tables, group by product and set the condition in the HAVING clause:
select p.id, p.name
from products p
inner join product_tags_link l on l.product_id = p.id
inner join tags t on t.id = l.tag_id
where (t.tag = 'category' and t.value = 'car')
or (t.tag = 'color' and t.value = 'green')
group by p.id, p.name
having count(distinct t.tag) = 2
Or:
select p.id, p.name
from products p
inner join product_tags_link l on l.product_id = p.id
inner join tags t on t.id = l.tag_id
where (t.tag, t.value) in (('category', 'car'), ('color', 'green'))
group by p.id, p.name
having count(distinct t.tag) = 2
See the demo.
Results:
> id | name
> -: | :---
> 1 | test

I would omit the joining table and do a simple join as follows:
SELECT
p.id AS product_id
FROM
products p
LEFT JOIN
tags t ON p.id = t.id
WHERE
t.value = 'green'
AND p.name LIKE '%car%'

Related

MySQL select from two table based on multiple conditions in same table

I'm trying to build a filter to quickly find the right product based on some specifications. But I can't get the MySQL to work. Been gooogling for a while now but can't find a similar question. I hope you can help me.
This is the products table
--------------------
| id | name |
--------------------
| 1 | Product 1 |
| 2 | Product 2 |
| 3 | Product 3 |
--------------------
This is the relation table for the specifications
--------------------------------
| id | specs_id | prod_id |
--------------------------------
| 1 | 1 | 1 |
| 2 | 5 | 1 |
| 3 | 6 | 2 |
| 4 | 9 | 3 |
| 5 | 11 | 2 |
---------------------------------
This is the MySQL how I want it to work.
$sql = "SELECT p.id, p.name
FROM products p
JOIN specs s ON p.id = s.prod_id
WHERE s.specs_id = 1
AND s.specs_id = 5
AND s.specs_id = 7
GROUP BY p.id";
This example will give no result
$sql = "SELECT p.id, p.name
FROM products p
JOIN specs s ON p.id = s.prod_id
WHERE s.specs_id = 1
AND s.specs_id = 5
GROUP BY p.id";
This will return product with ID 1
Item_id does not exist in your table. You also used AND insted of OR, thus no entry could match. None can have the specs_id 2,5, and 7 at the same time.
SELECT p.id, p.name
FROM products p
JOIN specs s
ON p.id = s.prod_id
WHERE s.specs_id = 5
OR s.specs_id = 2
OR s.specs_id = 7
GROUP BY p.id;
Maybe OR or IN is what you are looking for:
SELECT DISTINCT p.id, p.name
FROM products p
JOIN specs s ON p.id = s.prod_id
WHERE s.specs_id IN (1,5);
or
SELECT DISTINCT p.id, p.name
FROM products p
JOIN specs s ON p.id = s.prod_id
WHERE s.specs_id=1 OR s.specs_id=5;
Also, use DISTINCT instead of GROUP BY if you do not have aggregate functions.
You can do like first filter specs table with required specs_id and then make join that result with products table.
select p.id, p.name from
(select * from products p ) p
join (select * from specs where specs_id in (1,5,7)) s
on p.id = s.prod_id
group by p.id

MySQL: left join only one row and sum

It's possible left join only one row without sub query?
I need to get product statistics and some of products have multiple groups.
Therefore, the amount of products is incorrect.
SELECT COUNT(p.id) AS total_product, SUM(p.price) AS total_price
FROM product p
LEFT JOIN attribute_group a ON
a.product_id = p.id
WHERE p.created_at >= "2018-01-01" AND (a.id = 1 OR a.id = 2)
GROUP BY p.id
LIMIT 0, 30;
product
id | price
1 | 100
2 | 150
3 | 250
attribute_group
id | product_id | title
1 | 1 | a1
2 | 1 | a2
3 | 2 | a3
4 | 3 | a4
Should be:
1| 100
But i get:
2 | 200
You appear to want all products or the counts/sum of them that have attributes of both 1 and 2. Here is one method:
SELECT COUNT(*) as num_products, SUM(p.price) as total_price
FROM product p
WHERE p.created_at >= '2018-01-01' AND
EXISTS (SELECT 1
FROM attribute_group ag
WHERE ag.product_id = p.id AND ag.id = 1
) AND
EXISTS (SELECT 1
FROM attribute_group ag
WHERE ag.product_id = p.id AND ag.id = 2
);

Mysql: Select rows from a join table where 'in' and 'not in' criteria are used

I have 3 tables like below:
Table media:
+------------------------+
| media_id | media_name |
+------------------------+
| 1 | item1 |
| 2 | item2 |
| 3 | item3 |
+------------------------+
Join Table mediatag:
+--------------------+
| media_id | tag_id |
+--------------------+
| 1 | 1 |
| 1 | 2 |
| 2 | 1 |
| 3 | 1 |
| 3 | 3 |
+--------------------+
Table tag:
+--------------------+
| tag_id | tag_name |
+--------------------+
| 1 | blue |
| 2 | red |
| 3 | white |
| 4 | green |
+--------------------+
I wish retrieve all medias that have 'blue' and 'white' tags but without medias that have 'red' tag.
So in my example, the result must be: item2, item3
I tried this query but obviously the item1 is displayed:
SELECT m.media_id, media_name FROM media AS m
INNER JOIN mediatag AS mag ON m.media_id = mag.media_id
WHERE tag_id = '1' OR tag_id = '3' AND tag_id !='2';
how to do this?
Group your data and select only those groups having the conditions you mention
SELECT m.media_id, m.media_name
FROM media AS m
INNER JOIN mediatag AS mag ON m.media_id = mag.media_id
GROUP BY m.media_id, m.media_name
HAVING sum(tag_id in (1,3)) > 0
AND sum(tag_id = 2) = 0
From your desired result, it seems like you want that to actually be blue OR white without red. You can use similar logic but change it to use an OR:
SELECT m.media_id, m.media_name
FROM media AS m
INNER JOIN mediatag AS mt
ON m.media_id = mt.media_id
GROUP BY m.media_id, m.media_name
HAVING (sum(mt.tag_id = 1) > 0 OR sum(mt.tag_id = 3) > 0)
AND sum(mt.tag_id = 2) = 0;
See this demo.
If you didn't want to use the conditional logic in the HAVING clause, you could also write this as a NOT EXISTS query and get the same result:
SELECT DISTINCT m.media_id, m.media_name
FROM media AS m
INNER JOIN mediatag AS mt
ON m.media_id = mt.media_id
WHERE mt.tag_id in (1, 3)
and not exists (SELECT 1
FROM mediatag mt2
WHERE m.media_id = mt2.media_id
and mt2.tag_id = 2);
See another demo.
I would do a left outer join on a sub select of media that matches the rows you want to exclude, then in the where say media_id IS NULL
SELECT *
FROM media AS a
INNER JOIN mediatag AS b ON a.media_id = b.media_id
INNER JOIN tag c ON b.tag_id = c.tag_id AND c.tag_name = 'blue'
LEFT OUTER JOIN (
SELECT a.media_id
FROM media AS a
INNER JOIN mediatag AS b ON a.media_id = b.media_id
INNER JOIN tag c ON b.tag_id = c.tag_id AND c.tag_name = 'red'
) d ON a.media_id = d.media_id
WHERE d.media_id IS NULL;

How to select items on table based on 3 relations?

I have 3 tables:
Categories
| id | name
| 1 | Samsung
| 2 | Apple
Products
| id | category_id | name
| 1 | 1 | Galaxy S4
| 2 | 1 | Galaxy S3
| 3 | 1 | SHG-G600
| 4 | 3 | Lumia 920
Tags
| id | product_id | name | type
| 1 | 1 | smart-phone | phoneType
| 2 | 2 | smart-phone | phoneType
| 3 | 3 | normal-cell | phoneType
| 4 | 1 | red | phoneColor
I'm trying to find a way to select all Samsung devices which have 'smart-phone' as 'phoneType' and 'red' as 'phoneColor'.
So this what I did until now:
SELECT *
FROM `products`
INNER JOIN `product_tag` ON `product_tag`.`product_id` = `products`.`id`
INNER JOIN `tags` ON `tags`.`id` = `products`.`id`
WHERE (
`tags`.`type` = 'phoneType'
AND `tags`.`name` = 'smart-phone'
)
OR (
`tags`.`type` = 'phoneColor'
AND `tags`.`name` = 'red'
)
)
This did not work as is (without selecting category).
I also didn't know how to join categories and add where categories.id = 1.
You can do this by putting the logic in the having clause. For your example code:
SELECT p.*
FROM `products` p join
`product_tag` pt
ON pt.`product_id` = p.`id` join
`tags` t
ON t.`id` = p.`id`
group by p.id
having sum(t.`type` = 'caseMaterial' AND t.name = 'leather') > 0 and
sum(t.`type` = 'caseFor' AND t.`name` = 'iphone-5') > 0;
However, I'm not quite sure how this relates to the tables at the beginning of the question. Your code sample and data layout are not consistent.
I extended the solution of #Gordon Linoff by adding the category join.
SELECT p.*
FROM `products` p join
`categories` c
ON c.`id` = p.`category_id` join
`product_tag` pt
ON pt.`product_id` = p.`id` join
`tags` t
ON t.`id` = pt.`tag_id`
where c.id = 1
group by p.id
having sum(t.`type` = 'phoneType' AND t.name = 'smart-phone') > 0 and
sum(t.`type` = 'phoneColor' AND t.`name` = 'red') > 0
This is working now. Thanks to #Gordon Linoff.

Joining two tables with MySQL for aggregation?

I need to join tables to do aggregation. I suck at that. Here's my scenario:
CATEGORIES
CatID | CategoryName | Parent
1 | Cars | NULL
2 | Electronics | NULL
3 | DVD | 2
4 | Blu_ray | 2
5 | Shoes | NULL
So basically, topmost elements don't have parents. Then I have
PRODUCTS
ProdID | Prod Name | CatID
1 | DVD Player 1 | 3
2 | Blu-Ray Player | 3
3 | Nike | 5
4 | DVD Player 2 | 3
I want to end up with...
CATEGORIES
CatID | CategoryName | Parent | totalProds
1 | Cars | NULL | 0
2 | Electronics | NULL | 0
3 | DVD | 2 | 2
4 | Blu_ray | 2 | 1
5 | Shoes | NULL | 1
Any ideas?
Aren't you just asking for the total number of products per category?
SELECT CatID, CategoryName, Parent, COUNT(*) totalProds
FROM categories c
INNER JOIN products p ON p.CatID = c.CatID
GROUP BY CatId
See below query
SELECT
c.`CatID`,
c.`CategoryName`,
c.`Parent`,
COALESCE(COUNT(DISTINCT p.`ProdID`),0) AS totalProds
FROM `CATEGORIES` c
LEFT JOIN `PRODUCTS` p
ON p.`CatID` = c.`CatID`
ORDER BY c.`CatID`
LEFT JOIN to return a row for every category. COALESCE to just make sure a 0 is returned if appropriate.
Assuming you have a limited number of category levels, you can union each level at a time. Here's for 4 levels:
SELECT c1.CatID, c1.CategoryName, c1.Parent, COUNT(1) totalProds
FROM products p
INNER JOIN categories c1 ON c1.CatID = p.CatID
GROUP BY c1.CatID, c1.CategoryName, c1.Parent
UNION
SELECT c2.CatID, c2.CategoryName, c2.Parent, COUNT(1)
FROM products p
INNER JOIN categories c1 ON c1.CatID = p.CatID
INNER JOIN categories c2 ON c2.CatID = c1.Parent
GROUP BY c2.CatID, c2.CategoryName, c2.Parent
UNION
SELECT c3.CatID, c3.CategoryName, c3.Parent, COUNT(1)
FROM products p
INNER JOIN categories c1 ON c1.CatID = p.CatID
INNER JOIN categories c2 ON c2.CatID = c1.Parent
INNER JOIN categories c3 ON c3.CatID = c2.Parent
GROUP BY c3.CatID, c3.CategoryName, c3.Parent
UNION
SELECT c4.CatID, c4.CategoryName, c4.Parent, COUNT(1)
FROM products p
INNER JOIN categories c1 ON c1.CatID = p.CatID
INNER JOIN categories c2 ON c2.CatID = c1.Parent
INNER JOIN categories c3 ON c3.CatID = c2.Parent
INNER JOIN categories c4 ON c4.CatID = c3.Parent
GROUP BY c4.CatID, c4.CategoryName, c4.Parent
Hope you get the idea...