MySQL Grouping SubQuery Optimization - mysql

I have a table of Categories, and a table of Products. Products have a category_id, and also a maker_id.
I am attempting to return a table of Category Names, along with a Binary of whether or not the category contains any products that belong to a given $maker_id (as defined in PHP code).
My current method counts how many matching products are in each category, but I'm assuming there is a faster way since I only need a Yes/No. Current code:
SELECT
c.name,
SUM(CASE WHEN p.maker_id = '{$maker_id}' THEN 1 ELSE 0 END) AS already_used
FROM categories c
LEFT JOIN products p ON p.category_id = c.id
GROUP BY c.id
I'm reading up on using EXISTS, but all the examples I've found are using it in the WHERE clause. Thanks!

You can try this:
SELECT c.name,COUNT(1) AS already_used, SUM(IF(p.status = 'ready', 1, 0)) AS ready_count
FROM categories c
LEFT JOIN products p
ON (p.category_id = c.id)
WHERE p.maker_id = '{$maker_id}'
GROUP BY c.id;

Related

Product groups in US that has no sale in SQL

I have written the following two queries for the below requirement. Please let me know which method is correct or both methods are wrong? Thanks a lot
There were two tables -
'Orders' with - order_id(PK), item id, quantity, order_date [Transactional Table]
'Catalog' with-item id, product group, location [Dimension Table]
They asked to write a SQL code that will return the product groups of US that has no sale in any unit(i.e all the item id from an individual product group has no sale).
1st Method:
with cte as
(
select c.*,o.order_id,
case when o.order_id is not null then 1 else 0 end sale_ind
from Catalog c
left join Orders o
on c.item_id = o.item_id
and c.location = 'US'
)
select product_group
from cte
group by product_group having sum(sale_ind) = 0
2nd Method:
select c.*
from Catalog c
where c.location='US'
and item_id not in (
select item_id
from Orders)
They asked to write a SQL code that will return the product groups of US that has no sale in any unit(i.e all the item id from an individual product group has no sale).
I would tend to go with not exists for this:
select distinct c.product_group
from catalog c
where c.location = 'US' and
not exists (select 1
from orders o
where o.item_id = c.item_id
);
That said, both your queries look okay, but the first is correct. The second is returning all catalog records not all product_groups. As for the second, I would discourage you from ever using not in with a subquery. No rows are returned if item_id returned by the subquery is ever NULL.
SELECT DISTINCT c.product_group
FROM Catalog c
LEFT OUTER JOIN Orders o
on c.item_id = o.item_id
WHERE c.location='US'
AND o.item_id is null
Left join: because you want catalog records (left side) even if there are no order records (right side). The second part of the WHERE clause filters out instances where there are orders.
You can’t use an inner join as that would return only records where the Catalog record had corresponding orders, which is not what you want

Mysql (doctrine) - count inner join having count > X

I have SQL to count products with specific properties. I am using it in the products filter. SQL is very long, but here is the primary part:
SELECT COUNT(products.id) as products_count, property_items.description, property_items.id as id
FROM property_items
INNER JOIN product_properties ON property_items.id = product_properties.property_item_id
INNER JOIN products ON product_properties.product_id
INNER JOIN product_properties pp ON products.id = pp.product_id AND (pp.property_item_id IN ($ids))
GROUP BY property_items.id
HAVING COUNT(pp.id) >= $countIds
This works perfectly when I have only the one element in $ids, but when i choose one more, the result is bad. It looks like the sql returns count of all products with any property from $ids, but I need to count only products that contains all properties.
First get all available properties. On each property join products that contains this property and go back to all properties of this product to check, if product contains already checked properties too. Or it is bad idea? I need to keep primary table (FROM table) as property_items.
I need to get result in this format:
=============================
id|description|products_count
=============================
1 |lorem ipsum|10
-----------------------------
2 |dolore sit |2
Thanks for any idea.
Try to use SELECT COUNT (DISTINCT products.id) as cnt
You can get the product ids that have all the properties by doing:
SELECT pp.property_id
FROM property_items pi INNER JOIN
product_properties pp
ON pi.id = pp.property_item_id INNER JOIN
products p
ON pp.product_id = p.id
WHERE pp.property_item_id IN ($ids)
GROUP BY pp.property_id
HAVING COUNT(DISTINCT pp.property_item_id) = $countIds -- has all of them
Note that I rationalized the joins. I think your simplification of the query wasn't quite right. I also added table aliases, so the query is easier to write and to read.
If you want the count of such products, use a subquery:
SELECT COUNT(*)
FROM (SELECT pp.property_id
FROM property_items pi INNER JOIN
product_properties pp
ON pi.id = pp.property_item_id INNER JOIN
products p
ON pp.product_id = p.id
WHERE find_in_set(pp.property_item_id, $ids)
GROUP BY pp.property_id
HAVING COUNT(DISTINCT pp.property_item_id) = $countIds -- has all of them
) ;
Your problem is probably because of this line:
WHERE pp.property_item_id IN ($ids)
If you are passing $ids as a comma-separated string, then your query will not work. Note the replacement above.

How to select resource with where condition enforcing having two relations in joined table

How to select resource with where condition enforcing having two relations in joined table.
For example lets say I have to tables resource and item where one resource can have many items and item can be assigned to many resources.
Now I need to select a resource which have two specific items ? how to do it in the simplest way possible ?
SELECT
r.name,
GROUP_CONCAT(DISTINCT CONCAT(i.name)) AS itemNames
FROM
resources r
LEFT JOIN resources_items ri ON ri.resourceId = r.id
LEFT JOIN items i ON i.id = ri.itemId
WHERE i.id= '1' AND i.id = '2'
GROUP BY r.id
Is this a good direction ?
Later on I'd like to select many resources based on many items. For example all resources that have id in given array or have an item with id 1 or 2
Another approach would be using IN() and COUNT()
SELECT
r.name,
GROUP_CONCAT(DISTINCT CONCAT(i.name)) AS itemNames
FROM
resources r
LEFT JOIN resources_items ri ON ri.resourceId = r.id
LEFT JOIN items i ON i.id = ri.itemId
WHERE i.id IN(1,2)
GROUP BY r.id
HAVING COUNT(DISTINCT i.id) = 2
For more element you just need to update your IN and the count need to be equal to the length of your given array
This approach is mysql specific which will use sum() to make the each filter is true like
SELECT
r.name,
GROUP_CONCAT(DISTINCT CONCAT(i.name)) AS itemNames
FROM
resources r
LEFT JOIN resources_items ri ON ri.resourceId = r.id
LEFT JOIN items i ON i.id = ri.itemId
WHERE i.id IN(1,2)
GROUP BY r.id
HAVING SUM(i.id = 1) > 1
AND SUM(i.id = 2) > 1
And repeat this SUM(i.id = #itemInput) clause according to your input items so as compare to first approach this is also a complex one like your which will involve no. of conditions or no. of joins. while in first approach you just need to count your inputs and match in having clause

Select distinct returning indistinct rows

Select distinct is returning indistinct rows. Why?
I want to return distinct shops.
Here is my sql statement:
SELECT
DISTINCT s.*, p.p_id
FROM
shop s
INNER JOIN product_shop ps on s.s_id = ps.s_id
INNER JOIN product p ON p.p_id = ps.p_id
WHERE
s.country = 'new zealand'
Here is the result:
The product (p.p_id) needs to not be distinct, as I want to return a list of shops that have a specific product. But the Shop needs to be distinct.
What am I doing wrong?
Returned rows are distinct. Distinct is applied to all returned row, not to single column. Yes, p_id is same for two rows. But if you compare all columns, there are differences between them.
If you want distinct shops - don't include in select columns from other tables, because it can cause duplicates as in your example.
Simply don't include p.p_id within your selection.
I.e.
SELECT DISTINCT
s.*
FROM shop s
....
Well, If you will look at your entire output, you can see the p_id(the last column) is different for each row. Distinct applies to the entire record, not just one column.
You can either drop the p_id from your select, or use group by and decide which one of the p_id you want, perhaps max? :
SELECT
s.*, max(p.p_id)
FROM
shop s
INNER JOIN product_shop ps on s.s_id = ps.s_id
INNER JOIN product p ON p.p_id = ps.p_id
WHERE
s.country = 'new zealand'
GROUP BY s.id

SQL Join involving 3 tables, how to?

SQL newbie here.
So we have 3 tables:
categories(cat_id,name);
products(prod_id,name);
relationships(prod_id,cat_id);
It is a one-to-many relationship.
So, given a category name say "Books". How do I find all the products that come under books?
As an example,
categories(1,Books);
categories(2,Phones);
products(302,Sherlock Holmes);
relationships(302,1);
You need to JOIN the three tables.
SELECT p.*
FROM relationships r
INNER JOIN products p
ON p.prod_id = r.prod_id
INNER JOIN categories c
ON c.cat_d = r.cat_id
WHERE c.name = 'Books'
You have to join tables on related columns and specify WHERE clause to select all records where category name = 'Books'
SELECT p.*
FROM categories c
JOIN relationships r ON c.cat_id = r.cat_id
JOIN products p ON r.prod_id = p.prod_id
WHERE c.name = 'Books' -- or specify parameter like #Books
In SQL you often join related tables and beginners tend to join, whatever the situation. I would not recommend this. In your case you want to select products. If you only want to show products data, select from products only. You want to select products that are in the category 'Books' (or for which exists an entry in category 'Books'). Hence use an IN or EXISTS clause in order to find them:
select * from products
where prod_id in
(
select prod_id
from relationships
where cat_id = (select cat_id from categories where name = 'Books')
);
Thus you get a well structured query that tells the reader easily how the tables are related and what data you are actually interested in. Later, with different tables and data to select, this may keep you from duplicate result rows that you must get rid of by using DISTINCT or from getting wrong aggregates (sums, counts, etc.), because of mistakenly considering records multifold.
try this:
select p.Prod_id,p.name
from products p inner join relationships r on
p.prod_id = r.prod_id
where r.cat_id = (select cat_id from categories where name = 'books')
or
select p.Prod_id,p.name
from products p inner join relationships r on
p.prod_id = r.prod_id inner join categories c on c.cat_id = r.cat_id
where c.name = 'books'