I have 3 tables product, filter, product_filter - Many to Many relationship
I'm trying to filter products like this:
SELECT p.* FROM product p
INNER JOIN product_filter pf ON pf.product_id = p.id
WHERE (pf.filter_id = 1 OR pf.filter_id = 2) AND pf.filter_id = 3
The problem is that there are products that have all the filters(1,2,3) but i get 0 results
Please help :)
For a given row in the cross product there is only one filter_id. You don't have 1 and 3 in the same row in product_filter.
To get that effect try a double join along these lines :-
SELECT p.* FROM product p
INNER JOIN product_filter pf1 ON pf1.product_id = p.id
INNER JOIN product_filter pf2 ON pf2.product_id = p.id
WHERE (pf1.filter_id = 1 OR pf1.filter_id = 2) AND pf2.filter_id = 3
Related
So I created a sql fiddle to explain my problem much clearer:
http://sqlfiddle.com/#!9/3122282/1
As you can see I have 3 tables and 1 of them links the 2 others.
I want to make it so if I say "give me the products that is (color green OR red) and PET (dog)"?
I tried doing:
select `ptl`.`product_id`
from `tags` inner join `tags` as `ptl`
on `tags`.`id` = `ptl`.`tag_id`
where ((`tags`.`tag` = "color" and `tags.value` in ("green", "red"))
or (`tags`.`tag` = "pet" and `tags.value` in ("dog")))
having count(distinct `ptl.tag_id`) = 2
// 2 in that case is the number of tag "category".
but this doesn't seem to work. since having is just checking the count, it will also return the products with 2 color tags without any pet.
You can join the 3 tables, group by product and set the conditions in the HAVING clause:
SELECT p.id, p.name
FROM products p
INNER JOIN product_tags_link pt ON pt.product_id = p.id
INNER JOIN tags t ON pt.tag_id = t.id
GROUP BY p.id, p.name
HAVING SUM(t.tag = 'color' AND t.value IN ('green', 'red')) > 0
AND SUM(t.tag = 'pet' AND t.value IN ('dog')) > 0
See the demo.
You are not joining tags table with product_tags_link and products.
Take this query as a base and add the conditions on the where clause
select *
from products p
inner join product_tags_link ptl on ptl.product_id = p.id
inner join tags t on t.id = ptl.tag_id
where CONDITIONS
a CONDITIONS that can be taken as example
p.id = 1 and t.tag = 'color' and t.value = 'green'
I have following database structure to store product options.
Now i have problem to filter out products that match only given options. First i did WHERE option_id IN (array of options), but that would give me products that match any of the options and that is not solution. User wants to filter out only products with given material, color, and size for instance. And if i do WHERE option_id = 4 AND option_id = 6 for instance i get nothing.
Here is my query:
SELECT DISTINCT p.id AS id,
...
FROM products p
LEFT JOIN product_categories pc ON p.id = pc.product_id
LEFT JOIN product_images pi ON p.id = pi.product_id
LEFT JOIN product_options po ON p.id = po.product_id
WHERE p.product_active = 1
AND po.option_id = 1 // only to get the idea
GROUP BY id
ORDER BY id DESC
LIMIT 0,
12
Just to mention it is PHP application , where user select options from select element with or without multiple attribute.
How to acomplish this?
You can use having:
SELECT p.id AS id, ...
FROM products p JOIN
product_categories pc
ON p.id = pc.product_id LEFT JOIN
product_images pi
ON p.id = pi.product_id JOIN
product_options po
ON p.id = po.product_id
WHERE p.product_active = 1 AND
po.option_id IN (4, 6)
GROUP BY p.id
HAVING COUNT(DISTINCT po.option_id) = 2
ORDER BY p.id DESC
LIMIT 0, 12;
The HAVING clause is specifying that a given id has two matching options. Because of the WHERE clause, these are the only two options that you care about.
I didn't change your approach (you didn't supply the complete query), but you are doing joins along different dimensions -- categories, images, and options. This creates a Cartesian product for each product, and that is often not the best approach to such a query.
There is no need for LEFT JOIN in the solution.
SELECT DISTINCT p.id AS id
FROM products p
JOIN product_options po ON p.id = po.product_id
WHERE p.product_active = 1
AND po.option_id IN (1, 2, 3)
GROUP BY p.id
HAVING COUNT(po.option_id) = 3
My solution keep only tables necessary to find the products with specified options.
In the case you want products having exactly this options and no others you can use NOT EXISTS:
SELECT DISTINCT p.id AS id
FROM products p
JOIN product_options po ON p.id = po.product_id
WHERE p.product_active = 1 AND
po.option_id IN (1, 2, 3) and
NOT EXISTS (
SELECT 1
FROM product_options po2
WHERE p.id = po2.product_id and po2.option_id NOT IN (1, 2, 3)
)
GROUP BY p.id
HAVING COUNT(po.option_id) = 3
If you want to select products accoding to the other conditions (like product categories and so on) then use IN in the WHERE clause. This approach avoids generating duplicate po.option_id and the outer query will still work correctly even without DISTINCT in COUNT.
SELECT DISTINCT p.id AS id
FROM products p
JOIN product_options po ON p.id = po.product_id
WHERE p.product_active = 1 AND
po.option_id IN (1, 2, 3) AND
-- use the following IN predicate to select products with specific features without introducing duplicates in your query
p.id IN (
select product_id FROM product_categories WHERE <your_condition>
)
GROUP BY p.id
HAVING COUNT(po.option_id) = 3
You select products with image lists. Something like:
select products.*, group_concat(product_images.id)
Additionally there may be options the product must all meet. This is criteria that belongs in the WHERE clause.
select
p.*,
(select group_concat(image) from product_images i where i.product_id = p.id) as images
from products p
where product_active = 1
and id in
(
select product_id
from product_options
where option_id in (1,3,55,97)
group by product_id
having count(*) = 4 -- four options in this example
);
Thanks guys, i've managed to return exactly what i wanted.
Now i just have problem with pagination query for the filtered products.
Final search query:
SELECT DISTINCT p.id AS id,
main_price,
promotion_price,
NEW,
sale,
recommended,
COUNT(pi.filename) AS image_count,
GROUP_CONCAT(DISTINCT pi.filename
ORDER BY pi.main_image DESC, pi.id ASC) AS images,
name_sr,
uri_sr,
description_sr
FROM products p
LEFT JOIN product_categories pc ON p.id = pc.product_id
LEFT JOIN product_images pi ON p.id = pi.product_id
LEFT JOIN product_options po ON p.id = po.product_id
WHERE p.product_active = 1
AND po.option_id IN(1)
AND p.main_price BETWEEN 5250.00 AND 14000.00
GROUP BY id
HAVING COUNT(DISTINCT po.option_id) = 1
ORDER BY id DESC
LIMIT 0,
12
Pagination query is something like this i modified it accorgin to new filter query:
SELECT COUNT(DISTINCT p.id) AS number
FROM products p
LEFT JOIN product_categories pc ON p.id = pc.product_id
LEFT JOIN product_images pi ON p.id = pi.product_id
LEFT JOIN product_options po ON p.id = po.product_id
WHERE p.product_active = 1
AND po.option_id IN(1)
AND p.main_price BETWEEN 5250.00 AND 14000.00
GROUP BY(p.id)
HAVING COUNT(DISTINCT po.option_id) = 1
If i leave out DISTINCT in SELECT COUNT i don't get filtered pagination, if i set DISTINCT i get number of rows that corespond to pagination. I suppose i could add another count(*) to all of this with subquery, but not sure if that is way to go and if there is more efficient and elegant way to do this.
I have two tables 'shops' and 'products'. Shop has many products and product belongs to one shop. Moreover product belongs to the only one category. I have id's of 3 categories (for example 1,2,3). How can I get all shops having products which belongs to all 3 categories?
I tried
SELECT distinct s.*
from shops s
left join products p on p.shop_id = s.id
where p.category_id in (1,2,3)
but this returns shops with products which belongs to the category 1 OR 2 OR 3 But I want the products which belongs to the all 3 categories like 1 AND 2 AND 3, so every shop have to have at least 3 products
You could check the s.id having count(distinct p.category_id) = 3
SELECT s.id
from shops s
inner join products p on p.shop_id = s.id
where p.category_id in (1,2,3)
group by s.id
having count(distinct p.category_id) = 3
SELECT s.*
from shops s
join products p
on p.shop_id = s.id
where p.category_id in (1,2,3)
Group
by s.id
Having count(distinct p.category_id) = 3
Or something like that
Try this?
SELECT DISTINCT A.ID FROM SHOPS A, PRODUCTS B
WHERE A.ID = B.SHOP_ID AND EXISTS (SELECT COUNT(1) FROM PRODUCTS C, PRODUCTS D, PRODUCTS E WHERE B.ID = C.ID AND C.ID = D.ID AND D.ID = E.ID AND C.CATEGORY_ID = 1 AND D.CATEGORY_ID = 2 AND E.CATEGORY_ID = 3)
My T-SQL is rusty and this assumes I got the relations correct such that Shops 1 - M Products and, I'm unsure if Category is an entity but Category 1 - M Product. Also, just for future work, I've found it helps when your entities are names singular nouns but that's just my preference. Good luck.
Use group by will return by each shop
SELECT distinct s.*
from shops s
left join products p on p.shop_id = s.id
where p.category_id in (1,2,3)
group by s.id
having count(distinct p.category_id) = 3
I have four tables products, product_histories, vendor_invoices and invoices
This is the query I have developed
SELECT p.product_id, product_name, vendor_name FROM products AS p
INNER JOIN product_histories AS ph ON p.product_id = ph.product_id
CASE
WHEN ph.history_type = "P" THEN
LEFT JOIN vendor_invoices AS vi ON link_id = vi.vi_id
WHEN ph.history_type = "S" THEN
LEFT JOIN invoices AS i ON i.invoice_id = link_id
END
ORDER BY ph_id ASC
What I want that if ph.history_type is P then is should join vendor_invoices and if it is S then it should join invoices. But it says there is a syntax error.
Can anyone help me out with it? Or could show a better way to achieve this problem.
After 2 days of searching and trying similar questions, it's got to the point where I need to ask the question!
I have the following database structure (simplified)..
mt_product | mt_sku | mt_price
========== | ====== | ========
id | brand_id | mpn | id | product_id | retailer_id | sku | id | sku_id | price | date
For instance...
* A can of Coca-Cola is ONE product.
* It can be sold in many different retailers, who will all have a SKU for it.
* This SKU will have a price, which can change day-by-day.
I want to list the total number of prices for the product.
To list this I currently have the following query which nearly works...
SELECT
p.id AS pid, p.title AS p_title, p.cat, p.mpn,
b.id AS bid, b.name AS brand,
(SELECT COUNT(s.id) FROM mt_sku AS s WHERE s.pid = p.id) AS num_sku,
(SELECT COUNT(gbp.id) FROM mt_price AS gbp INNER JOIN mt_sku ON mt_sku.id = gbp.sid ) AS num_price
FROM mt_product AS p
INNER JOIN mt_brand b ON p.bid = b.id
INNER JOIN mt_sku s ON p.id = s.pid
num_sku returns as expected, however when I introduce the second sub query for num_price (and I have revised this many times) I either get...
* no duplications of the pid but the num_price is the total number of prices to SKUs, not the amount of prices for this product_id (as query above) eg1_img
* the correct number of num_price, but instead of totalling up the total num_price, the pid is duplicated in the table (as query below) - therefore as the pid is duplicated, this does not give me the result I want. I added DISTINCT as it helped an earlier version of the query, it now makes no difference. eg2_img
SELECT
DISTINCT(p.id) AS pid, p.title AS p_title, p.cat, p.mpn,
b.id AS bid, b.name AS brand,
(SELECT COUNT(s.id) FROM mt_sku AS s WHERE s.pid = p.id) AS num_sku,
(SELECT COUNT(gbp.id) FROM mt_price AS gbp WHERE s.id = gbp.sid) AS num_price
FROM mt_product AS p
INNER JOIN mt_brand b ON p.bid = b.id
INNER JOIN mt_sku s ON p.id = s.pid
I'm pretty sure the key to this is that
product can have multiple SKUs, of which a SKU has multiple price history.
Any help or ideas of the schema would be superb.
Try this:
SELECT
p.id AS pid, p.title AS p_title, p.cat, p.mpn,
b.id AS bid, b.name AS brand,
COUNT(DISTINCT s.id) AS num_sku,
COUNT(gbp.id) AS num_price
FROM mt_product AS p
INNER JOIN mt_brand b ON p.brand_id = b.id
INNER JOIN mt_sku s ON p.id = s.product_id
INNER JOIN mt_price gbp ON s.id = gbp.sku_id
GROUP BY b.id, p.id
The products that don't have SKUs defined will not appear in the result set. Use LEFT JOIN mt_sku to make them appear in the result set (having 0 for num_sku and num_price):
LEFT JOIN mt_sku s ON p.id = s.product_id
In both variants of the query, the products that do not have prices defined will not appear in the result set. Use LEFT JOIN mt_price to include them into the result set (having 0 for num_price):
LEFT JOIN mt_price gbp ON s.id = gbp.sku_id
Take a look at the MySQL documentation for JOINs, GROUP BY and GROUP BY aggregate functions.
If you want to list the total prices then you need correlations.
Your first count is fine, because it is correlated to the outer query. The second has no correlation, so that seems strange. The following fixes the num_price subquery:
SELECT p.id AS pid, p.title AS p_title, p.cat, p.mpn,
b.id AS bid, b.name AS brand,
(SELECT COUNT(s2.id) FROM mt_sku s2 WHERE s2.pid = p.id) AS num_sku,
(SELECT COUNT(gbp.id) FROM mt_price gbp WHERE s.id = gbp.sid ) AS num_price
FROM mt_product p INNER JOIN
mt_brand b
ON p.bid = b.id INNER JOIN
mt_sku s
ON p.id = s.pid;
I'm also not sure why you have all the joins in the outer query. I assume that a given product is going to have multiple rows, and you want the multiple rows to have the same num_sku and num_price values.