how to write this self join query in mysql - mysql

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'

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

SQL Filter products from one table basing on attributes from second one

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".

MySQL left join with NULL values

I'm certain this has been asked and answered already but don't know how exactly the question should be.
I have two tables
ID | name
=========
1 | foo
2 | bar
3 | lou
4 | sue
and a meta table:
p_ID | key | value
===================
1 | poo | 1
2 | zoo | 'whatever'
3 | clu | 423
4 | poo | 1
I like to get all entries from the first table which doesn'T have a poo value assigned:
ID | name
=========
2 | bar
3 | lou
My approach was
SELECT *
FROM table AS p
LEFT JOIN meta AS m
ON m.p_id = p.ID
WHERE m.key = 'poo'
AND m.value IS NULL
but this returns an empty result
You have to move the m.key = 'poo' expression into the ON-clause. Everything that is in the WHERE-clause MUST be present, even in a LEFT JOIN.
SELECT *
FROM table AS p
LEFT
JOIN meta AS m
ON m.p_id = p.ID
AND m.key = 'poo'
WHERE m.value IS NULL
I think you should want this;)
SELECT *
FROM table AS p
LEFT JOIN meta AS m
ON m.p_id = p.ID
AND m.key = 'poo'
WHERE m.value IS NULL
When you use m.key = 'poo' in WHERE clause, this will be computed as INNER JOIN, so you only get records with m.key = 'poo', not all rows in table.
You can use INNER JOIN instead.
SELECT *
FROM `table` T
INNER JOIN meta M ON M.p_id = T.ID
WHERE M.key <> 'poo'

How to mysql distinct one of many count fields when GROUP BY doesn't work?

I've a database with products, manufactors and categories of this products and information about name of manufactors, products and if those products have images.
But in this database are duplicated products with different IDs. Only thing I can identify them is their name.
Now I've a query where I want see how many products per manufactorer and per category were in my database and count also the number of products with images.
-----------------------------------------------------
| manufactor | category | products | productsImages |
|------------|----------|----------|----------------|
| manu-1 | cat-1 | 5 | 3 |
|------------|----------|----------|----------------|
| manu-1 | cat-2 | 15 | 8 |
|------------|----------|----------|----------------|
| manu-2 | cat-1 | 11 | 0 |
|------------|----------|----------|----------------|
| manu-3 | cat-2 | 5 | 4 |
|------------|----------|----------|----------------|
| manu-3 | cat-3 | 9 | 4 |
|------------|----------|----------|----------------|
My approach looks like:
SELECT m.`name` AS manufactor,
pg.`name` AS category,
COUNT(p.`name`) AS products,
COUNT(pi.`idImage`) AS productsImages
FROM `product` AS p
LEFT JOIN `product_image` AS pi ON pi.`idProduct` = p.`id`
INNER JOIN `manufacturer` AS m ON m.`id` = p.`idManufacturer`
INNER JOIN `product_groupname` AS pg ON pg.`id` = p.`idProductGroup`
GROUP BY p.`idManufacturer`, p.`idProductGroup`
ORDER BY m.`name`, p.`idProductGroup`;
I can't group by p.name because then I'd get a result row for each product.
Do COUNT(DISTINCT(p.name)) didn't help either.
So any suggestions or will I have do to subqueries?
If you want to count the distinct names where pi.`idImage` IS NOT NULL you can use the following:
SELECT m.`name` AS manufactor,
pg.`name` AS category,
COUNT(DISTINCT p.`name`) AS products,
COUNT(DISTINCT CASE
WHEN pi.`idImage` IS NOT NULL THEN p.`name`
ELSE NULL
END) AS productsImages
FROM `product` AS p
LEFT JOIN `product_image` AS pi ON pi.`idProduct` = p.`id`
INNER JOIN `manufacturer` AS m ON m.`id` = p.`idManufacturer`
INNER JOIN `product_groupname` AS pg ON pg.`id` = p.`idProductGroup`
GROUP BY p.`idManufacturer`, p.`idProductGroup`
ORDER BY m.`name`, p.`idProductGroup`;
See comments on question - distinct on p.name is the solution.
SELECT m.`name` AS manufactor,
pg.`name` AS category,
COUNT(DISTINCT(p.`name`)) AS products,
COUNT(pi.`idImage`) AS productsImages
FROM `product` AS p
LEFT JOIN `product_image` AS pi ON pi.`idProduct` = p.`id`
INNER JOIN `manufacturer` AS m ON m.`id` = p.`idManufacturer`
INNER JOIN `product_groupname` AS pg ON pg.`id` = p.`idProductGroup`
GROUP BY p.`idManufacturer`, p.`idProductGroup`
ORDER BY m.`name`, p.`idProductGroup`;

MySQL query to determine records that are related to category A and B

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