I'm trying to write query to a MYSQL database that queries for products that have a relationship with all selected categories
Products table
product_id | product_name
---------------------------------
1 | product name one
2 | product name two
3 | product three
Category table
category_id | category_name
1 | category one
2 | category two
3 | category three
Category_relationship table
product_id | category_id
--------------------------
1 | 1
1 | 2
1 | 3
2 | 1
2 | 2
3 | 3
So for example:
category ID's 1 and 3 are selected
Only product_id to be returned would be '1'
Using 2 JOINs
SELECT
p.*
FROM products p
JOIN category_relationship r1
ON r1.product_id = p.product_id
JOIN category_relationship r2
ON r2.product_id = p.product_id
WHERE r1.category_id = 1
AND r2.category_id = 3
Using GROUP BY
SELECT
p.*
FROM products p
JOIN category_relationship r
ON r.product_id = p.product_id
WHERE r.category_id IN (1,3)
GROUP BY p.product_id
HAVING COUNT(*) = 2 <-- number of category ids
It depends on your tables size and data distribution but I'd guess the first query to be faster.
But if you have lists of various sizes to check (with 3, 4, ... category ids), the first query has to be built dynamically while the second can be easily adjusted.
Join it twice:
select p.*
from products p
join category_relationship c1 on c1.product_id = p.product_id
and c1.category_id = 1
join category_relationship c2 on c2.product_id = p.product_id
and c2.category_id = 3
Try this:
SELECT DISTINCT product_id FROM category_relationship WHERE category_id IN (1, 3)
For example:
SELECT *
FROM Products P, Category_relationship CP
WHERE P.product_id = CP.product_id
AND CP.category_id IN (1,3)
SELECT p.product_id, p.product_name
FROM products p LEFT JOIN category_relationship cp ON p.product_id = cp.product_id
GROUP BY p.product_id, p.product_name
HAVING COUNT(cp.category_id) = (SELECT COUNT(*) FROM categories)
Related
So I have table with products
id | product_name
1 | Product 1
2 | Product 2
3 | Product 3
... table with atributtes:
id | attribute
1 | big
2 | orange
3 | expensive
and table with products and their attributes
id | product_id | attribute_id
1 | 1 | 1
2 | 1 | 2
3 | 2 | 3
4 | 3 | 2
and what I want is to filter big, orange products.. in this case: Product 1
Something like:
SELECT product_name
FROM products as a
JOIN products_attributes as b ON a.id=b.product_id
WHERE b.attribute_id = 1 OR b.attribute_id=2
will not work as it returns Product 3 as well..
This doesn't work too, of course:
SELECT product_name
FROM products as a
JOIN products_attributes as b ON a.id=b.product_id
WHERE b.attribute_id = 1 AND b.attribute_id=2
Please help :)
You need to add atributtes table into your SELECT statement and
filter by attribute IN ( 'big','orange' )
GROUPing with HAVING clause should be added to satisfy the both conditions at the same time
SELECT p.product_name
FROM products as p
JOIN products_attributes as pa
ON p.id = pa.product_id
JOIN attributes a
ON a.id = pa.attribute_id
WHERE a.attribute IN ( 'big','orange' )
GROUP BY p.product_name
HAVING COUNT(DISTINCT a.attribute) = 2
using IN rather than OR operator is more straightforward to use .
Demo
In order to find products that have multiple attributes, first we need to join the attributes table with itself:
SELECT pa1.product_id FROM products_attributes as pa1
JOIN products_attributes as pa2 ON pa1.product_id = pa2.product_id
AND pa1.attribute_id = 1 AND pa2.attribute_id = 2
This will output 1, which is the correct id. Note that it doesn't matter which of the two tables you will select the product_id from, since you're joining it with itself and both will contain it.
Now all we need is the name, so we're going to join the products table as well and change our selection:
SELECT p.product_name FROM products p
JOIN products_attributes as pa1 ON pa1.product_id = p.id
JOIN products_attributes as pa2 ON pa1.product_id = pa2.product_id
AND pa1.attribute_id = 1 AND pa2.attribute_id = 2
This should output "Product1".
I am trying to count the number of results in a column after joining a table and I am having a hard time getting my query to work.
In the end result, I need to get a table with the product id, product name and the number of medias for each product.
This is what I have so far:
SQL
select
p.id,
p.name,
count(distinct mp.media_id)
from products as p
left join medias_products as mp
on mp.product_id = p.id
group by mp.media_id
order by p.id
These are the tables:
Medias
Id | client id
------ | ---------
1 | 1
2 | 2
Products
id | name | client_id
------ | -------- | ---------
1 | product1 | 1
2 | product2 | 2
medias_products
product_id | media_id
---------- | --------
1 | 2
2 | 1
Client
id | name
------ | -----
1 | Peter
2 | John
In addition, I'd like to find another query that would give me the results filtered by an specific client id.
Can someone please shed some light and share the knowledge.
Thanks in advance.
Try this:
select
p.id,
min(p.name) as name,
count(distinct mp.media_id) as medias
from products as p
left join medias_products as mp
on mp.product_id = p.id
group by p.id
order by p.id
For your second query:
select
p.id,
min(p.name) as name,
count(distinct mp.media_id) as medias
from products as p
inner join medias_products as mp
on mp.product_id = p.id
inner join medias as m
on m.id = mp.media_id
inner join clients as c
on c.id = m.client_id
where c.id = <your client's id>
group by p.id
order by p.id
Shouldn't you GROUP BY p.id instead of mp.media_id? That is
select
p.id,
p.name,
count(distinct mp.media_id)
from
products as p
left join medias_products as mp
on p.id = mp.product_id
group by p.id
order by p.id
I'm not sure I phrased the question correctly, so feel free to correct me. Here are the tables with their data:
product category category_product
------- -------- ----------------
id_product id_category active id_category id_product
1 1 1 1 1
2 2 1 2 1
3 3 0 1 2
4 0 2 2
3 2
3 3
4 3
I need to select only those products, which have all categories as inactive.
For example:
Product 1 is good, since it belongs to active categories (1, 2).
Product 2 is good, since it has at least one active category (1, 2; 3 - inactive)
Product 3 must be selected, since all its categories are inactive (3, 4).
I have the following query, which is obviously incorrect, since it selects both products: 2 and 3:
SELECT p.id_product
FROM product p
JOIN category_product cp
ON p.id_product = cp.id_product
JOIN category c
ON c.id_category = cp.id_category
WHERE
c.active = 0;
Here is the SQL Fiddle: http://sqlfiddle.com/#!2/909dd/2/0
How can I solve this?
This way you can select product without active category.
SELECT p.id_product
FROM product p
WHERE NOT EXISTS
(SELECT * FROM
category_product cp
INNER JOIN category c ON c.id_category = cp.id_category
WHERE p.id_product = cp.id_product AND c.active = 1);
SQL Fiddle
Consider the following:
SELECT p.*
, COUNT(*)
, SUM(c.active = 1) active
, SUM(c.active = 0) inactive
FROM product p
JOIN category_product cp
ON cp.id_product = p.id_product
JOIN category c
ON c.id_category = cp.id_category
GROUP
BY p.id_product;
+------------+----------+--------+----------+
| id_product | COUNT(*) | active | inactive |
+------------+----------+--------+----------+
| 1 | 2 | 2 | 0 |
| 2 | 3 | 2 | 1 |
| 3 | 2 | 0 | 2 |
+------------+----------+--------+----------+
http://sqlfiddle.com/#!2/909dd/55
The last part of this problem has been left as an exercise for the reader
This is what i get while trying...apologies if anything is wrong
set #count:=0;
select a.id_product,a.times from
(SELECT count(p.id_product)times, p.id_product, c.active,
if(c.active!=0, #count:=#count+1, #count:=0) x
From category_product cp
join product p
on (p.id_product = cp.id_product)
join category c
on(c.id_category = cp.id_category )
group by id_product )a
where a.x=0;
Here is a simplified version of my data:
products:
+----+-----------+
| id | name |
+----+-----------+
| 1 | Product X |
| 2 | Product Y |
| 3 | Product Z |
+----+-----------+
categories:
+----+---------------+
| id | name |
+----+---------------+
| 1 | Hotel |
| 2 | Accommodation |
+----+---------------+
category_product
+----+------------+-------------+
| id | product_id | category_id |
+----+------------+-------------+
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 1 |
| 4 | 3 | 2 |
+----+------------+-------------+
How do I construct an efficient query that will only retrieve products that have both categories "Hotel" and "Accommodation" related (eg. Product X)?
I first tried a join approach
SELECT *
FROM products p
JOIN category_product cp
ON p.id = cp.product_id
WHERE cp.category_id = 1 OR cp.category_id = 2
^ This doesn't work because it doesn't contrain the query to containing both.
I have found an approach using sub-queries that works... but I've been warned against sub-queries for performance reasons:
SELECT *
FROM products p
WHERE
(
SELECT id
FROM category_product
WHERE product_id = p.id
AND category_id = 1
)
AND
(
SELECT id
FROM category_product
WHERE product_id = p.id
AND category_id = 2
)
Are there any better solutions (or how about alternatives)? I've considered de-normalizing categories to an extra column on products but would ideally like to avoid that. Hoping for a magic bullet solution!
UPDATE
I've run some of the (great) solutions provided in the answers:
My data is 235 000 category_product rows and 58 000 products and obviously benchmarks are always dependent on environment and indexes etc.
"Relational division" #podiluska
2 categories: 2826 rows ~ 20ms
5 categories: 46 rows ~ 25-30 ms
8 categories: 1 rows ~ 25-30 ms
"Where exists" #Tim Schmelter
2 categories: 2826 rows ~ 5-7ms
5 categories: 46 rows ~ 30 ms
8 categories: 1 rows ~ 300 ms
One can see the results start to diverge with having a greater number of categories thrown in. I'll look at using "relational division" as it provides consistent results but implementation might cause me to look at "where exists" too (long format http://pastebin.com/6NRX0QbJ)
SELECT p.*
FROM products p
inner join
(
select product_ID
from category_product
where category_id in (1,2)
group by product_id
having count(distinct category_id)=2
) pc
on p.id = pc.product_id
This technique is called "relational division"
select *
from products p
where
(
select
count(distinct cp.category_id)
from category_product as cp
where
cp.product_id = p.id and
cp.category_id in (1, 2)
) = 2
or you can use exists
select *
from products p
where
exists
(
select
count(distinct cp.category_id)
from category_product as cp
where
cp.product_id = p.id and
cp.category_id in (1, 2)
having count(distinct cp.category_id) = 2
)
I would use EXISTS:
SELECT P.* FROM Products P
WHERE EXISTS
(
SELECT 1 FROM category_product cp
WHERE cp.product_id = p.id
AND category_id = 1
)
AND EXISTS
(
SELECT 1 FROM category_product cp
WHERE cp.product_id = p.id
AND category_id = 2
)
SELECT categories.name,products.name
FROM
category_product,category,product
where
category_product.product_id=product.id
and
category_product.category_id=category.id
and
(
select count(1) from category_product
where
category_product.categoty_id=1
or
category_product.categoty_id=2
group by product_id having count(1)=2
)
SELECT p.id
FROM products p
JOIN category_product cp
ON p.id = cp.product_id
WHERE cp.category_id IN (1,2)
GROUP BY p.id
HAVING COUNT(DISTINCT cp.category_id) = 2
Hello I have a table structure like this
products_id | model_num | master_model_num
1 | cth001 | 0
2 | cth002 | 0
3 | cth003 | cth001
4 | cth004 | cth001
5 | cth005 | 0
6 | cth006 | cth002
My Problem
I will provide the products_id to the table and it will get all product ids whoes master_model_num is equal to the model_num of the given products_id
I have tried following query but it doen't generate the result that I want
SELECT p.products_id
FROM products p,products pp
WHERE p.products_id=pp.products_id
AND p.products_model=pp.products_master_model
AND p.products_id='1'
SELECT pp.products_id
FROM products p
INNER JOIN products pp
ON p.model_num = pp.master_model_num
WHERE p.products_id = '1'
Wouldn't
SELECT products_id
FROM products
WHERE master_model_num = (SELECT model_num
FROM products
WHERE products_id = 1)
make more sense in this case? By having AND p.products_id='1' on the end of your query, you're guaranteeing that you'll only get one record back.
Try this
SELECT
p.products_id
FROM
products p
INNER JOIN
products pp
ON
pp.products_master_model = p.products_model
SELECT p.products_id FROM products p,products pp where p.model_num=pp.master_model_num and p.products_id='1'