Mysql group by with multiple columns with inner joins - mysql

I have 3 tables, products, productscategories and categories.
The productscategories is a many to many table with only id numbers of the other two tables.
The result without the group by looks like this:
id | Url | Category
-------------------------------------
1 | http://example.com/12 | hat
2 | http://example.com/12 | shoe
3 | http://example.com/13 | hat
4 | http://example.com/13 | jacket
5 | http://example.com/14 | hat
6 | http://example.com/14 | socks
Now I want to exclude every row with the same url if it contains any of the choosen categories, in this case jacket and shoe.
The unwanted result looks like this:
id | Url | Category
-------------------------------------
1 | http://example.com/12 | hat
3 | http://example.com/13 | hat
5 | http://example.com/14 | hat
Because url with id 13 includes jacket I don't want it there. Same goes for url with 14 which includes shoe.
This accur because I have multiple categories and multiple urls that are not aware of each other.
The sql for the above:
SELECT * FROM products
JOIN productscategories ON products.id = productscategories.product_id
JOIN categories ON categories.id = productscategories.category_id
WHERE categories.slug NOT IN (
'shoe',
'jacket',
)
GROUP BY products.image_url
The wanted result:
id | Url | Category
-------------------------------------
5 | http://example.com/14 | hat
How can I make an sql query that makes url aware of the category, like above?

SELECT * FROM products
JOIN productscategories ON products.id = productscategories.product_id
JOIN categories ON categories.id = productscategories.category_id
GROUP BY products.image_url
HAVING sum( categories.slug IN('shoe','jacket') )=0
categories.slug IN('shoe','jacket') - return 1 if category in set or 0 if not. sum() - return count of shoe/jacket in group. HAVING filter group with shoe/jacket in it.

Suggestion 1: WHERE NOT EXISTS
SELECT * FROM products
JOIN productscategories ON products.id = productscategories.product_id
JOIN categories ON categories.id = productscategories.category_id
WHERE NOT EXISTS(
SELECT 1
FROM products p2
JOIN productscategories pc2 ON p2.id = pc2.product_id
JOIN categories c2 ON c2.id = pc2.category_id
WHERE c2.slug IN ('shoe','jacket')
AND p2.url = products.url
)
Suggestion 2: OUTER JOIN
SELECT * FROM products
JOIN productscategories ON products.id = productscategories.product_id
JOIN categories ON categories.id = productscategories.category_id
LEFT OUTER JOIN products p2 ON products.url = p2.url
LEFT OUTER JOIN productscategories pc3 ON p2.id = pc2.product_id
LEFT OUTER JOIN categories c2 ON c2.id = pc2.category_id AND c2.slug IN ('shoe','jacket')
WHERE c2.id IS NULL

Try this:
SELECT * FROM (
SELECT * FROM products
JOIN productscategories ON products.id = productscategories.product_id
JOIN categories ON categories.id = productscategories.category_id
) AS A
LEFT JOIN
(
SELECT Url FROM products
JOIN productscategories ON products.id = productscategories.product_id
JOIN categories ON categories.id = productscategories.category_id
WHERE Categories.slug IN ('jacket', 'shoe')
GROUP BY url
) B ON B.url = A.url
WHERE B.url IS NULL

Related

php - Check same column twice in mysqli query

I have 3 tables in my php based system.
Those tables are product, category, product_categories.
Product
pid | product_name | price
1 | Nike T-Shirt | 23
Category
cid | category_name
1 | Men
2 | Women
Product_categories
pcid | cid | pid
1 | 1 | 1
2 | 2 | 1
That means, 1 product may be in both multiple categories.
Now I am developing the product search section with filter.
If a user select both categories, all the products in selected categories should display.
Example : If a user select both Men & Women, Nike T-Shirt should be displayed.
The query I used:
select p.*
from products p
left join product_categories pc on pc.pid=p.pid
WHERE pc.cid ='1' AND pc.cid = '2'
But it not returning correct products.
Where is the error?
You want conditions that span over multiple rows, which suggests aggregation. You can join the tables, group by product, and use the having clause for filtering:
select p.id, p.product_name, p.price
from product p
inner join product_category pc on pc.pid = p.pid
inner join category c on c.cid = pc.cid
where c.category_name in ('Men', 'Women')
group by p.pid, p.product_name, p.price
having max(c.category_name = 'Men') = 1 and max(c.category_name = 'Women') = 1
If you can filter by category id rather than by category name, then you need one less join:
select p.id, p.product_name, p.price
from product p
inner join product_category pc on pc.pid = p.pid
where pc.cid in (1, 2)
group by p.pid, p.product_name, p.price
having max(pc.cid= 1) = 1 and max(pc.cid = 2) = 1
Here are few alternatives:
-- All products that are at lest in one of the desired categories
select * from product where product.pid = any (select product_category.pid from product_category where cid = 1 or cid = 2);
-- Products that are in both categories
select * from product where product.pid = any (
select product_category.pid from product_category where cid = 1 or cid = 2 group by pid having count(cid) = 2
);
-- Products with additional information "in how many desired categories they are"
-- You can order by it and/or filter on it
with product_category_matches as (
select pid, count(*) as category_count from product_category where cid in (1, 2) group by pid
)
select *, product_category_matches.category_count from
product inner join product_category_matches on product.pid = product_category_matches.pid
where product_category_matches.category_count > 0 -- Product must be at least in one desired category
order by product_category_matches.category_count desc

Mysql pivot join fails

I have following tables
Tags
id | tag_name | slug |
1 | tag1 |tag1
2 | tag1 |tag1
products
id | proudct_name
1|product1
2|product2
product_tags
id | product_id | tag_id
1|1||1
2|1|2
3|2|1
i need retrieve only those product which belongs to both tag1 and tag2
select * from `products` INNER JOIN product_tags ON products.id=product_tags.product_id
INNER JOIN tags ON tags.id=product_tags.tag_id WHERE product_tags.tag_id=1 AND product_tags.tag_id=2
But my query return empty result
This will look the product that in both tables:
select * from `products` a where exists(select 1 from product_tags b where b.tag_id = 1 and a.product_id = b.product_id) and exists(select 1 from product_tags b where b.tag_id = 2 and a.product_id = b.product_id)
You can do what you want with inner join, which appears to be what you are trying. You just need to inner join twice:
select p.*
from products p join
product_tags pt1
on pt1.product_id = p.id and pt1.tag_id = 1 join
product_tags pt2
on pt2.product_id = p.id and pt2.tag_id = 2;
This might be easier to code in Laravel.
Your version doesn't work because the tag cannot be both "1" and "2" at the same time -- it can have different rows with those values but only one value per row.
Also, the tags table is not needed for the query.

MySQL a left join and a few inner joins in a same query

I have few tables namely.
categories
items
restaurant_items
restaurants
new_sponsoreds
regions
If I briefly explain these tables,
categories is food categories. eg:- Cake
item is like a subcategory for food. eg:- Chocolate Cake.
restaurant_items is an instance of item which belongs to a particular restaurant. eg:- Chocolate cake in ABC restaurant.
restaurants is as it names implies just a restaurant and regions is an area where the restaurant located at.
MOST IMPORTANT ONE new_sponsoreds is where restaurants promote their food items as advertisements.
runday | category_id | restaurant_id
2018-12-5 | 23 | 45
This new_sponsoreds table indicates, We need to run advertisements on restaurant_items where category_id is 23 in restaurant's id is 45.
MY USE CASE
Get all the restaurant_items from a given region and then filter those
restaurant_items by a category and finally right outer join this results with new_sponsoreds.
So that a food app user can get restaurant_items from a region (NEW YORK) and filter it by a category (CAKES).
This query is for get that restaurant_items.
SELECT restaurant_items.* FROM test2.restaurant_items
inner join items on restaurant_items.item_id = items.Item_id
inner join categories on items.category_id = categories.id
inner join restaurants on restaurant_items.restaurant_id = restaurants.Restaurant_id
where restaurants.region_id = 7 and categories.id = 33
This is works fine. But when I try to join this results with the new_sponsoreds table like this,
SELECT restaurant_items.*,new_sponsoreds.* FROM test2.restaurant_items
inner join items on restaurant_items.item_id = items.Item_id
inner join categories on items.category_id = categories.id
inner join restaurants on restaurant_items.restaurant_id = restaurants.Restaurant_id
left outer join new_sponsoreds on categories.id = new_sponsoreds.category_id
where restaurants.region_id = 7 and categories.id = 33
and new_sponsoreds.runday = current_date()
This query returns empty set of rows.
But what I expect was this query will gives me all the restaurant_items filter from a region and a category_id and if a restaurant promote a category in today, this also will include the results.
id |restaurant_id | Item_id | runday | category_id | restaurant_id
1 |12 | 23 |2018-12-04 | 25 | 12
2 |12 | 45 |null | null | null
3 |15 | 23 |null | null | null
This is my excepted results table where restaurant_id and Item_id from restaurant_items and others from new_sponsoreds
In here first record match the left outer join because restuarant_id 12 is promoting there category_id 25 in 2018-12-04.
Second record doesn't matches even if it belongs to the same restaurant, it's category_id is not equals to the 25.
Third record has the same category_id as the first record. But it doesn't match the join because it belongs to a different restaurant.
This is my expected results. But I'm getting nothing after I added the left join part to my query. Why this could happen?
SELECT restaurant_items.*,new_sponsoreds.* FROM test2.restaurant_items
inner join items on restaurant_items.item_id = items.Item_id
inner join categories on items.category_id = categories.id
inner join restaurants on restaurant_items.restaurant_id = restaurants.Restaurant_id
left outer join new_sponsoreds on categories.id = new_sponsoreds.category_id
and new_sponsoreds.runday = current_date() and new_sponsoreds.restaurant_id = restaurants.Restaurant_id
where restaurants.region_id = 7 and categories.id = 33
As the comment suggested I move the lat line up one and added this part to the left outer join
and new_sponsoreds.restaurant_id = restaurants.Restaurant_id

SQL -- Counting with nested subquery

I have a collection of tables in a relational database
products
categories
orders
line_items
customers
Products has a many-to-many relationship with categories (join table categories_products) and also has and belongs to many orders through line_items, which is a join table for products and orders with an id. A customer also has many orders.
I'm trying to put together some SQL that will give me this sort of response:
customer_id | customer_first_name | category_id | category_name | number_purchased
-----------------------------------
1 |Jack | 1 | Electronics | 15
2 |Jill | 1 | Electronics | 2
2 |Jill | 2 | Hiking | 3
This is the giant hunk of SQL I've been trying to use to get these values:
SELECT
DISTINCT customers.id AS customer_id,
customers.first_name AS customer_first_name,
categories.id AS category_id,
categories.name AS category_name,
(
SELECT count(li.id) FROM line_items li
INNER JOIN orders o ON li.order_id = o.id
INNER JOIN products p ON li.product_id = p.id
INNER JOIN categories_products cp ON cp.product_id = p.id
WHERE
o.customer_id = customer_id
AND o.status = 3
AND cp.category_id = category_id
) AS number_purchased
FROM orders
LEFT JOIN customers ON orders.customer_id = customers.id
LEFT JOIN line_items li ON li.order_id = orders.id
LEFT JOIN products ON products.id = li.product_id
LEFT JOIN categories_products catpr ON catpr.product_id = products.id
LEFT JOIN categories ON catpr.category_id = categories.id
Only the count itself is wrong. Instead of getting the number of line items a customer has bought in a specific category, I'm instead getting a count for all LineItems that have been part of a completed order.
How can I get the count to correctly represent the number of line_items purchased by a specific customer within a category?
NOTE: in the SQL text, o.status = 3 is using an enum to indicate that an Order is "complete."
I think your inner join with categories_products is screwing this up. You should set up a fiddle, like #Strawberry suggested, or try this:
SELECT
DISTINCT customers.id AS customer_id,
customers.first_name AS customer_first_name,
categories.id AS category_id,
categories.name AS category_name,
(
SELECT count(li.id) FROM line_items li
INNER JOIN orders o ON li.order_id = o.id
INNER JOIN products p ON li.product_id = p.id
WHERE
o.customer_id = customer_id
AND o.status = 3
) AS number_purchased
FROM orders
LEFT JOIN customers ON orders.customer_id = customers.id
LEFT JOIN line_items li ON li.order_id = orders.id
LEFT JOIN products ON products.id = li.product_id
LEFT JOIN categories_products catpr ON catpr.product_id = products.id
LEFT JOIN categories ON catpr.category_id = categories.id
If you wanted to correct your count, I would advise using a GROUP BY clause in the subquery. If you GROUP BY orders then you will only get the specific order which you retrieved when looking that the user id was correct. I would encourage you to take a look at mistakes in other part of your SQL code to clean up this hulking query. For example, make sure you want to be using distinct and that you actually want to be using left joins versus inner joins, both of which could seriously mess with the performance of your program.

SQL Union query simplification

I don't know if this question is apropriate on this forum.
I have a huge query :
SELECT threshold.id, brand.id, COUNT(brand.id), threshold
FROM current_stock, article, product, brand, delivery, threshold
WHERE current_stock.article_id = article.id
AND article.product_product_code = product.product_code
AND product.brand_id = brand.id
AND article.delivery_id = delivery.id
AND delivery.store_id = 'E260'
AND threshold.brand_id = brand.id
GROUP BY brand.id
HAVING COUNT(brand.id) <= threshold
UNION
SELECT threshold.id, brand.id, 0, threshold
FROM current_stock, article, product, brand, delivery, threshold
WHERE threshold.store_id = 'E260'
AND threshold.brand_id NOT IN (
SELECT brand_id FROM current_stock, article, product, delivery
WHERE current_stock.article_id = article.id
AND article.product_product_code = product.product_code
AND article.delivery_id = delivery.id
AND delivery.store_id = 'E260')
And I think it's possible to do better but after a entire day of try I haven't found a better query giving the same result.
For clarify, I have a stock (with current_stock, article, product and delivery). I also have thresholds. what I want is to check for each thresholds if there is the given minimum amount of stock for the given brand.
My problem is that if there is 0 article of a brand, the first part of the query will not take care about the threshold on this brand. It's why I have added an uggly Union.
Someone have an idea for a better way to do this ?
EDIT
This what I have done after the reading of comments and answers :
SELECT t.id, b.id, t.threshold, count(b.id) stock
FROM threshold t
inner join brand b on b.id = t.brand_id
left join product p on p.brand_id = b.id
inner join article a on a.product_product_code = p.product_code
inner join delivery d on d.id = a.delivery_id
inner join current_stock cs on cs.article_id = a.id
WHERE
t.store_id = 'E260' AND
d.store_id = 'E260'
GROUP BY b.id
HAVING stock <= t.threshold
My problem is that it don't gives all threholds... only ones that have at least one 'current_stock'. I have perhaps don't understand how joins are working.
Here an example of threshold table :
| id | brand_id | threshold |
-----------------------------
| 1 | 86 | 1 |
| 2 | 28 | 1 |
| 3 | 12 | 1 |
What I want as result this :
# with 2 entries in 'current_stock' for the brand id 28, 1 for 12 and 0 for 86
| t.id | b.id | threshold | stock |
-----------------------------------
| 1 | 86 | 1 | 0 |
| 3 | 12 | 1 | 1 |
Guessing a few parts here since you've used implicit joins in your sample. An explicit version would look something like this (provided I guessed correctly for how you are joining the threshold table).
SELECT
t.id,
b.id,
COUNT(b.id),
t.threshold
FROM
current_stock c
inner join article a on a.id = c.article_id
inner join delivery d on d.id = a.delivery_id
inner join product p on p.product_code = a.product_product_code
inner join brand b on b.id = p.brand_id
inner join threshold t on t.brand_id = b.id
WHERE
d.store_id = 'E260'
GROUP BY b.id
HAVING COUNT(b.id) <= t.threshold
Now to get your results to include rows where there aren't any 'articles' you can start switching out the inner joins for left joins. However, you can't simply use left outer join article... in the example above, because the store_id in the WHERE clause will just turn it back into a pseudo inner join.
Instead, is there a different field you can join the delivery table on from current_stock?
EDIT - 07/29/15
I think you're close, you may just have one too many filters and you're counting from 'b' when the wanted outcome suggests you should be counting from 'cs' instead. Try this:
SELECT t.id, b.id, t.threshold, count(cs.id) stock
FROM
threshold t
inner join brand b on b.id = t.brand_id
inner join product p on p.brand_id = b.id
inner join article a on a.product_product_code = p.product_code
left outer join delivery d on d.store_id = t.store_id
left outer join current_stock cs on cs.article_id = a.id
WHERE
t.store_id = 'E260'
GROUP BY b.id
HAVING stock <= t.threshold