Left join when there are lots of matched rows from right table - sql

I have two tables.
Product(id, name)
LineItem(id, product_id, order_id)
Order(id, state)
Order can have many products. One product can belong to many orders at the same time.
I would like to select Products, which don't have orders with specific statuses(i.e. 1, 2).
My query is
SELECT products.id, products.price
FROM "products"
LEFT OUTER JOIN line_items ON line_items.product_id = products.id
LEFT OUTER JOIN orders ON orders.id = line_items.order_id AND orders.status IN (1, 2)
WHERE (products.price > 0) AND (orders.id IS NULL) AND "products"."id" = $1
GROUP BY products.id, products.price [["id", 11]]
11 is an id of a product, that should not appear to the result, but it does.

I would like to select Products, which don't have orders with specific statuses(i.e. 1, 2).
SELECT * FROM products p -- I would like to select Products
WHERE NOT EXISTS( -- , which don't have
SELECT *
FROM orders o -- orders
JOIN line_items li ON li.order_id = o.id
WHERE li.product_id = p.id
AND o.status IN (1,2) -- with specific statuses(i.e. 1, 2).
);

select p.id, p.name
from products p
join lineitem l on l.product_id = p.id
join `order` o on l.order_id = o.id
group by p.id, p.name
having sum(case when o.state in (1,2) then 1 else 0 end) = 0

The idea is to start with the products table and use left join to find orders with 1 or 2. If they don't exist, then you want the product:
select p.id, p.name
from product p left join
lineitem li
on li.product_id = p.id left join
orders o -- a better name for the table
on li.order_id = o.id and
o.state in (1, 2)
where o.id is null
group by p.id, p.name;

Related

Selections through SQL

I am attempting to create a list of all orders that contain six specific products. The current query I'm using is:
SELECT o.order_date,
o.price AS revenue,
o.cost,
f.id AS fileid,
o.id AS orderid,
o.fk_order_status_id,
o.date_started,
o.date_completed,
p.description AS product,
pp.description AS subproduct
FROM orders o
JOIN file f
f.id = o.fk_file_id
JOIN product p
ON p.id = o.fk_product_id
JOIN product_product pp
ON pp.fk_product_id = o.fk_product_id
WHERE o.fk_product_id IN ('66','8','6','21','11')
Which pulls all orders that have ANY of those products. What I need to narrow down the results are orders that have those products altogether, not only one or two of the products. We are attempting to get counts of a Work Flow that we have recently implemented
You can use aggregation and having:
SELECT o.order_date, o.price AS revenue, o.cost,
o.id AS orderid, o.fk_order_status_id, o.date_started, o.date_completed
FROM orders o JOIN
file f
ON f.id = o.fk_file_id JOIN
product p
ON p.id = o.fk_product_id JOIn
product_product pp
ON pp.fk_product_id = o.fk_product_id
WHERE o.fk_product_id IN (66, 8, 6, 21, 11)
GROUP BY o.order_date, o.price, o.cost,
o.id, o.fk_order_status_id, o.date_started, o.date_completed
HAVING COUNT(DISTINCT o.fk_product_id) = 5; -- number of products in the list

How to list products that belong to more than 3 private categories

What's the SQL to find the list of products that belong to more than 3 private categories.
I tried this:
SELECT
products.*
FROM
products
INNER JOIN
product_categories
ON
products.product_id = product_categories.product_id
INNER JOIN
categories
ON
product_categories.category_id = categories.category_id
WHERE
categories.is_private = 1
GROUP BY
categories.category_id
HAVING
COUNT(categories.category_id) > 3
Thanks!
Your query would be correct if you aggregated by products.product_id -- well, depending on the database, you might need to include other columns in the SELECT. But it is valid SQL assuming that product_id is unique in that table.
If you only want the product ids you don't need the products table:
select pc.product_id
from product_categories pc join
categories c
on pc.category_id = c.category_id
where is_private = 1
group by pc.product_id
having count(*) > 3;
you need every product witch .... so you should do group by on productID.also you can add other columns of products that you need in both 'select' and 'group by'
SELECT
products.product_id,products.name
FROM
products
INNER JOIN
product_categories
ON
products.product_id = product_categories.product_id
INNER JOIN
categories
ON
product_categories.category_id = categories.category_id
WHERE
categories.is_private = 1
GROUP BY
products.product_id,products.name
HAVING
COUNT (categories.category_id) > 3
To get all columns of products:
SELECT products.*
FROM products p
WHERE product_id in (SELECT pc.product_id
FROM product_categories pc
INNER JOIN categories c ON pc.category_id = c.category_id
WHERE c.is_private = 1
GROUP BY c.category_id
HAVING COUNT(c.category_id) > 3)
Check this out!
select * from products where product_id in
(select pc.product_id from product_categories inner join categories c on
pc.category_id=c.category_id where c.is_private=1
group by c.category_id having count(c.category_id)>3)

How do we find which customers placed orders with items made in USA refer to image

Which customers placed orders with items made inside the USA?
SELECT DISTINCT, WHERE, Temporary Table, Subquery
tables to refer
I would use exists with a correlated subquery that follows the relationships like customer > order > order_item > product > supplier and filters on US suppliers:
select c.*
from customer c
where exists (
select 1
from order o
inner join order_item oi on oi.order_id = o.id
inner join product p on p.id = oi.product_id
inner join supplier s on s.id = p.supplier_id
where o.customer_id = c.id and s.country = 'USA'
)

SQL bestsellers query

I have a problem with finding bestselling books in each category. With attached code I can find only categories of needed books but how to find this books?
SELECT b.category_id, max(b.total_quantity) as max_quantity
FROM (
SELECT books.id, books.category_id, sum(order_items.quantity) as total_quantity
FROM order_items
INNER JOIN orders ON order_items.order_id = orders.id
INNER JOIN books on order_items.book_id = books.id
WHERE orders.status in (2, 3)
GROUP BY books.id
) as b
GROUP BY b.category_id
Use distinct on:
SELECT DISTINCT ON (b.id), b.*, SUM(oi.quantity) as total_quantity
FROM order_items oi JOIN
orders o
ON oi.order_id = o.id JOIN
books b
ON oi.book_id = b.id
WHERE o.status in (2, 3)
GROUP BY b.id
ORDER BY b.category_id, total_quantity DESC
You could filter in the having clause with a correlated, aggregate query that returns the top selling quantity for the related category:
select b.id, b.category_id, sum(oi.quantity) as total_quantity
from order_items oi
inner join orders o on oi.order_id = o.id and o.status in (2, 3)
inner join books b on oi.book_id = b.id
having sum(oi.quantity) = (
select sum(oi.quantity)
from order_items oi1
inner join orders o1 on oi1.order_id = o1.id and o1.status in (2, 3)
inner join books b1 on oi1.book_id = b1.id
where b1.category_id = b.category_id
group by b1.id
order by sum(oi.quantity) desc
limit 1
)
group by b.id, b.category_id
Or, if your RDBMS supports window functions (and allows mixing them with aggregation):
select id, category_id, total_quantity
from (
select
b.id,
b.category_id,
sum(oi.quantity) as total_quantity,
rank() over(partition by b.category_id order by sum(oi.quantity) desc) rn
from order_items oi
inner join orders o on oi.order_id = o.id and o.status in (2, 3)
inner join books b on oi.book_id = b.id
group by b.id, b.category_id
) t
where rn = 1
I would use a DENSE_RANK over the SUM.
If available in the RDBMS.
SELECT *
FROM
(
SELECT books.id, books.category_id
, SUM(orditm.quantity) AS total_quantity
, DENSE_RANK() OVER (PARTITION BY books.category_id ORDER BY SUM(order_items.quantity) DESC) AS Rnk
FROM order_items AS orditm
JOIN orders ON orditm.order_id = orders.id
JOIN books ON orditm.book_id = books.id
WHERE orders.status IN (2, 3)
GROUP BY books.id, books.category_id
) q
WHERE Rnk = 1
I guess you must have book_name column in your books table. So you can try below query -
SELECT b.book_name, b.category_id, max(b.total_quantity) as max_quantity
FROM (
SELECT books.id, books.book_name, books.category_id, sum(order_items.quantity) as total_quantity
FROM order_items
INNER JOIN orders ON order_items.order_id = orders.id
INNER JOIN books on order_items.book_id = books.id
WHERE orders.status in (2, 3)
GROUP BY books.id
) as b
GROUP BY b.category_id

Left outer join with only first row

I have a query something like
SELECT S.product_id, S.link, C.id AS category_id
FROM Products P
INNER JOIN SEO S ON S.product_id = P.id AND P.product_type = 1
LEFT OUTER JOIN Categories C ON c.product_id = P.id
WHERE P.active = 1
I works fine for me as long as each product has assigned to only one category. But if a product is assigned to many categories it returns all possible combinations.
Can I only select the first one and if a product don't have any category the link should still be returned with category_id = NULL
An easy way is to use outer apply, so as to have a correlated join, and make that a top 1 query. Thus you are able to access all columns of the category record in question. I'm adding a category name here as an example:
select s.product_id, s.link, c.id as category_id, c.name as category_name
from products p
inner join seo s on s.product_id = p.id
outer apply
(
select top 1 *
from categories cat
where cat.product_id = p.id
order by cat.id
) c
where p.active = 1
and p.product_type = 1;
You can use a GROUP BY to accomplish this along with an Aggregate function, most likely MIN or MAX.
Depending on which Category Id you prefer in your result you could select the minimum.
SELECT S.product_id, S.link, MIN(C.id) AS category_id
FROM Products P
INNER JOIN SEO S ON S.product_id = P.id AND P.product_type = 1
LEFT OUTER JOIN Categories C ON c.product_id = P.id
WHERE P.active = 1
GROUP BY S.product_id, S.link
Or the maximum.
SELECT S.product_id, S.link, MAX(C.id) AS category_id
FROM Products P
INNER JOIN SEO S ON S.product_id = P.id AND P.product_type = 1
LEFT OUTER JOIN Categories C ON c.product_id = P.id
WHERE P.active = 1
GROUP BY S.product_id, S.link
Alternate solution using subquery:
SELECT S.product_id, S.link,
(
SELECT C.id FROM Categories C WHERE C.product_id = P.id AND
ROW_NUMBER() OVER(ORDER BY /* your sort option goes here*/ ) = 1
) AS category_id
FROM Products P
INNER JOIN SEO S ON S.product_id = P.id AND P.product_type = 1
WHERE P.active = 1