How to select items on table based on 3 relations? - mysql

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.

Related

Mysql left join with limit returning join record for one row

How do i join a table using limit?
I have the below query but it doesn't work as expected.
Am using left join to select only one row from table, but it only select one record as expected for the first row while it returns null on others
Even when they have file saved in TABLE_USER_FILES.
TABLE_USERS
uid | u_name
----|---------
p1 | Peter
j1 | John
f1 | Foo
b1 | Bar
TABLE_USER_POST
pid | p_name | p_uid
----|---------|--------
xp1 | PHP | p1
xp2 | SQL | p1
xp3 | JS | j1
xp4 | CSS | b1
TABLE_USER_FILES
fid | f_uid | f_url | f_path
----|--------|---------|----------
fa1 | p1 | ax.png | gallery
fb2 | p1 | bc.png | gallery
bc3 | j1 | cc.png | gallery
fd4 | f1 | cx.png | gallery
fe5 | j1 | qd.png | gallery
Query
SELECT post.*, user.u_name, files.f_url
FROM TABLE_USER_POST post
INNER JOIN TABLE_USERS user
ON user.uid = post.p_uid
LEFT JOIN (
SELECT f_url, f_uid
FROM TABLE_USER_FILES
WHERE f_path = "gallery"
ORDER BY fid DESC
LIMIT 1
) files
ON files.f_uid = post.p_uid
ORDER BY post.pid DESC
LIMIT 0, 20
Expected result
pid | p_name | p_uid | u_name | f_url
----|---------|--------|---------|---------
xp1 | PHP | p1 | Peter | bc.png
xp2 | SQL | p1 | Peter | bc.png
xp3 | JS | j1 | John | qd.png
xp4 | CSS | b1 | Bar | NULL
There are many solutions here. For example, LATERAL in MySQL 8.0.14+
SELECT post.*, user.u_name, files.f_url
FROM TABLE_USER_POST post
INNER JOIN TABLE_USERS user
ON user.uid = post.p_uid
LEFT OUTER JOIN LATERAL (
SELECT f_url, f_uid
FROM TABLE_USER_FILES tuf
WHERE f_path = "gallery"
AND tuf.f_uid = post.p_uid
ORDER BY fid DESC
LIMIT 1
) files ON true
ORDER BY post.pid DESC
LIMIT 0, 20
If only one column from TABLE_USER_FILES is needed, then the query in the SELECT clause:
SELECT post.*, user.u_name,
( SELECT f_url
FROM TABLE_USER_FILES tuf
WHERE f_path = "gallery"
AND tuf.f_uid = post.p_uid
ORDER BY fid DESC
LIMIT 1
) AS f_url
FROM TABLE_USER_POST post
INNER JOIN TABLE_USERS user
ON user.uid = post.p_uid
ORDER BY post.pid DESC
LIMIT 0, 20
db<>fiddle
Please try this instead.
SELECT post.*, user.u_name, files.f_url
FROM TABLE_USER_POSTS post
LEFT JOIN TABLE_USER_FILES files
ON files.f_uid = post.p_uid
AND files.fid = (SELECT MAX(fid) FROM TABLE_USER_FILES WHERE f_uid = files.f_uid)
INNER JOIN TABLE_USERS user
ON user.uid = post.p_uid
ORDER BY post.pid DESC;
Thank you!

How to select rows that matches "multiple rows condition" in 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%'

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;

Slow query, should i index or other solutions?

I have this very slow query, it counts the product that has certain specifications, is the solution indexing? or other solutions?
select count(DISTINCT if(ps10.specification in ('Meisje'),p.products_id,NULL)) as count1 ,count(DISTINCT if(ps10.specification in ('Jongen'),p.products_id,NULL)) as count2 ,count(DISTINCT if(ps10.specification in ('Unisex'),p.products_id,NULL)) as count3 from (products p)
join (products_to_categories p2c)
on (p.products_id = p2c.products_id)
left join (specials s)
on (p.products_id = s.products_id)
left join (products_attributes pa)
on (p.products_id = pa.products_id)
left join (products_options_values pv)
on (pa.options_values_id = pv.products_options_values_id)
left join (products_stock ps)
on (p.products_id=ps.products_id and pv.products_options_values_id = ps.products_options_values_id2)
INNER JOIN products_specifications ps10 ON p.products_id = ps10.products_id INNER JOIN products_specifications ps17 ON p.products_id = ps17.products_id where p.products_status = '1' and ps.products_stock_quantity>0 and p2c.categories_id in (2,54,60,82,109,115,116,118,53,58,104,55,101,75,56,64,66,67,68,69,70,71,84,103,114,80,92,99,93,94,95,97,106) AND ps10.specifications_id = '10'
AND ps10.language_id = '1'
AND ps17.specification in ('Babyslofjes'
) AND ps17.specifications_id = '17'
AND ps17.language_id = '1'
explain this query gives me this results:
+----+-------------+-------+--------+-------------------------------------+-------------------------------------+---------+------------------------------------------+-------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+--------+-------------------------------------+-------------------------------------+---------+------------------------------------------+-------+--------------------------+
| 1 | SIMPLE | ps | ALL | idx_products_stock_attributes | NULL | NULL | NULL | 16216 | Using where |
| 1 | SIMPLE | p | eq_ref | PRIMARY | PRIMARY | 4 | kikleding.ps.products_id | 1 | Using where |
| 1 | SIMPLE | s | ref | idx_specials_products_id | idx_specials_products_id | 4 | kikleding.p.products_id | 1 | Using index |
| 1 | SIMPLE | p2c | ref | PRIMARY | PRIMARY | 4 | kikleding.ps.products_id | 1 | Using where; Using index |
| 1 | SIMPLE | pv | ref | PRIMARY | PRIMARY | 4 | kikleding.ps.products_options_values_id2 | 1 | Using where; Using index |
| 1 | SIMPLE | ps10 | ref | products_id | products_id | 12 | kikleding.p.products_id,const,const | 1 | Using where |
| 1 | SIMPLE | ps17 | ref | products_id | products_id | 12 | kikleding.ps.products_id,const,const | 1 | Using where |
| 1 | SIMPLE | pa | ref | idx_products_attributes_products_id | idx_products_attributes_products_id | 4 | kikleding.p2c.products_id | 6 | Using where |
+----+-------------+-------+--------+-------------------------------------+-------------------------------------+---------+------------------------------------------+-------+--------------------------+
Changed the left joins to inner joins like this:
select count(DISTINCT if(ps10.specification in ('Meisje'),p.products_id,NULL)) as count1 ,count(DISTINCT if(ps10.specification in ('Jongen'),p.products_id,NULL)) as count2 ,count(DISTINCT if(ps10.specification in ('Unisex'),p.products_id,NULL)) as count3 from (products p)
inner join (products_to_categories p2c)
on (p.products_id = p2c.products_id)
left join (specials s)
on (p.products_id = s.products_id)
inner join (products_attributes pa)
on (p.products_id = pa.products_id)
inner join (products_options_values pv)
on (pa.options_values_id = pv.products_options_values_id)
inner join (products_stock ps)
on (p.products_id=ps.products_id and pv.products_options_values_id = ps.products_options_values_id2)
INNER JOIN products_specifications ps10 ON p.products_id = ps10.products_id INNER JOIN products_specifications ps17 ON p.products_id = ps17.products_id where p.products_status = '1' and ps.products_stock_quantity>0 and p2c.categories_id in (2,54,60,82,109,115,116,118,53,58,104,55,101,75,56,64,66,67,68,69,70,71,84,103,114,80,92,99,93,94,95,97,106) AND ps10.specifications_id = '10'
AND ps10.language_id = '1'
AND ps17.specification in ('Babyslofjes'
) AND ps17.specifications_id = '17'
AND ps17.language_id = '1'
I Indexed the ps.products_id
It's little bit faster, thank you for the comments, but the query is still very slow
As obvious use p.products_id more so first index that attribute in table Products. Then pv.products_options_values_id and also try to index other attributes you use in Inner Join. Also try to convert where conditions to be used with in Join conditions expecially for Inner join
I would go for a slightly modified query to put the conditions into the join from the where parts, and from the sample I'd think you could get rid of the Specials table as well.
select
count(distinct if(ps10.specification in ('Meisje'), p.products_id, null)) as count1,
count(distinct if(ps10.specification in ('Jongen'), p.products_id, null)) as count2,
count(distinct if(ps10.specification in ('Unisex'), p.products_id, null)) as count3
from (products p)
inner join (products_to_categories p2c)
on (p.products_id = p2c.products_id)
inner join (products_attributes pa)
on (p.products_id = pa.products_id)
inner join (products_options_values pv)
on (pa.options_values_id = pv.products_options_values_id)
inner join (products_stock ps)
on (p.products_id=ps.products_id and pv.products_options_values_id = ps.products_options_values_id2 and ps.products_stock_quantity > 0)
inner join products_specifications ps10
ON p.products_id = ps10.products_id and ps10.language_id = '1' and ps10.specifications_id = '10'
inner join products_specifications ps17
ON p.products_id = ps17.products_id and ps17.language_id = '1' and ps17.specifications_id = '17'
where p.products_status = '1'
and p2c.categories_id in (2,54,60,82,109,115,116,118,53,58,104,55,101,75,56,64,66,67,68,69,70,71,84,103,114,80,92,99,93,94,95,97,106)
and ps17.specification in ('Babyslofjes')
As for the indexes, I'd check the following to be available:
products / products_id (most probably is)
products_to_categories / products_id+categories_id (most probably also)
products_attributes / products_id+options_values_id
products_options_values / products_options_values_id
products_specifications / products_id+language_id+specifications_id
From the table names I suspect this being an OS/XTcommerce database, I'll try to get my hands on one in a few hours and give a more detailed opinion. I just don't remember products_stock and products_specifications, those are both tables, not views, right?

join SQL query problem

I have following query which returns the product and the lowest sell price found with the quantity of that sell price. Everything works perfectly until I want to get a product that does not have any prices in the product_price table. How can I let return it the product data and NULLS for sellPrice and quantity?
SELECT p.*, MIN(pp.sellPrice) as sellPrice, pp.quantity FROM `product` as p
LEFT JOIN `product_price_group` as ppg ON ppg.productId = p.`id`
LEFT JOIN `product_price` as pp ON pp.priceGroupId = ppg.`id`
WHERE p.`id` = 1 AND p.`active` = 1
Output of an product that has a price available:
+----+--------------+--------+--------------+--------------+-----------+----------+
| id | name | active | sortSequence | creationDate | sellPrice | quantity |
+----+--------------+--------+--------------+--------------+-----------+----------+
| 1 | product_id_1 | 1 | 1 | 1287481220 | 22.00 | 10 |
+----+--------------+--------+--------------+--------------+-----------+----------+
Output of an product that does not have a pricing avaialble
+----+------+--------+--------------+--------------+-----------+----------+
| id | name | active | sortSequence | creationDate | sellPrice | quantity |
+----+------+--------+--------------+--------------+-----------+----------+
| NULL | NULL | NULL | NULL | NULL | NULL | NULL |
+----+------+--------+--------------+--------------+-----------+----------+
Desired output:
+----+--------------+--------+--------------+--------------+-----------+----------+
| id | name | active | sortSequence | creationDate | sellPrice | quantity |
+----+--------------+--------+--------------+--------------+-----------+----------+
| 2 | product_id_2 | 1 | 1 | 1287481220 | NULL | NULL |
+----+--------------+--------+--------------+--------------+-----------+----------+
Update
It seems that I was selecting oN product items that don't exist! Very stupid.
What about using LEFT OUTER JOIN for product_price table?
SELECT p.*, MIN(pp.sellPrice) as sellPrice, pp.quantity FROM `product` as p
LEFT JOIN `product_price_group` as ppg ON ppg.productId = p.`id`
LEFT OUTER JOIN `product_price` as pp ON pp.priceGroupId = ppg.`id`
WHERE p.`id` = 1 AND p.`active` = 1
Is this what you want?
UPDATE: Revision - Like others say, LEFT JOIN = LEFT (OUTER) JOIN so it will not help you in this case...
I MAY be incorrect, but my understanding of a LEFT JOIN has always been the table reference in the equality test as written in the SQL statement... which is in addition, how I write queries... Start with the table I'm expecting FIRST (left), joined to the OTHER (right) table second... Keep the join condition ALSO respective of that relationship...
select from x left join y where x.fld = y.fld
instead of
select from x left join y where y.fld = x.fld
So I would adjust your query as follows
SELECT
p.*,
MIN(pp.sellPrice) as sellPrice,
pp.quantity
FROM
product as p
LEFT JOIN product_price_group as ppg
ON p.id = ppg.productId
LEFT JOIN product_price as pp
ON ppg.id = pp.priceGroupId
WHERE
p.id = 1
AND p.active = 1
Additionally, you can wrap your min() and quantity with a IFNULL( field, 0 ) to prevent NULLS from showing but instead have actual zero values.