Left outer join with only first row - sql

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

Related

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 to add TOP 1 in query with left join in views?

I have 3 same product in ID=42, with 3 different images. I want to take the first image from the product ID, I try adding "TOP 1", error
This is my query
CREATE OR REPLACE VIEW UserOrdersView
AS
SELECT
u.[User_ID],
p.Product_Name,
p.Price,
o.Order_Price,
o.Order_ID,
i.[Image]
FROM Product p
LEFT JOIN Orders o ON o.Product_ID = p.Product_ID
INNER JOIN Users u ON u.[User_ID]= o.[User_ID]
LEFT JOIN Product_Images i ON i.Product_ID = p.Product_ID
WHERE o.[User_ID] = 42
You need to use OUTER APPLY to get top 1 image data from Product_image table based on Product ID.
Please check this Real life example, when to use OUTER / CROSS APPLY in SQL stackoverflow link for more knowledge.
Please check below updated view code for your answer.
CREATE OR REPLACE VIEW UserOrdersView
AS
BEGIN
SELECT
u.[User_ID],
p.Product_Name,
p.Price,
o.Order_Price,
o.Order_ID,
i.[Image]
FROM Product p
INNER JOIN Users u ON u.[User_ID]= o.[User_ID]
LEFT JOIN Orders o ON o.Product_ID = p.Product_ID
OUTER APPLY
(
SELECT TOP 1
T2.[Image]
FROM Product_Images T2
WHERE T2.Product_ID = p.Product_ID
) i
WHERE o.[User_ID] = 42
END
GO
WITH cte as (
SELECT
u.[User_ID],
p.Product_Name,
p.Price,
o.Order_Price,
o.Order_ID,
i.[Image],
ROW_NUMBER() OVER (PARTITION BY i.[Image] ORDER BY p.Product_Name) AS rn
FROM Product p
LEFT JOIN Orders o ON o.Product_ID = p.Product_ID
INNER JOIN Users u ON u.[User_ID]= o.[User_ID]
LEFT JOIN Product_Images i ON i.Product_ID = p.Product_ID
)
SELECT [User_ID],Product_Name,Price,Order_Price,Order_ID,[Image] FROM cte
WHERE rn=1
Put your all query inside a CTE with a new column that you will use to filter the results.
This new column is produced with ROW_NUMBER() function partitioned by Product_Name

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

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;

I want to delete all products from a table(products) where a product is only represented in one category

Like the title says, i would like to delete all products in categoryID 102, but not if they are ALSO represented in any other categoryid.
Pseudo: Delete x where categoryid = 102, and x is not in any other category.
I gotta select from 3 tables: Product, Category, Product_Category_Mapping
This is what i have now:
select p.Name
from Product p, Category c, Product_Category_Mapping pcm
where c.Id = 102 and pcm.CategoryId = c.Id and pcm.ProductId = p.Id
I bet this is pretty simple. I might need a subselect?
This is an example of a set-within-a-sets query. A good way to handle this is with group by and having. Here is an example for two products:
select p.Name
from Product_Category_Mapping pcm join
Product p
on pcm.ProductId = p.Id
where pcm.id in (102, 103)
group by p.Name
having count(distinct pcm.id) = 2;
You don't specify which DBMS you're using, but this is pretty plain vanilla SQL:
SELECT p.Id AS ProductId, p.Name AS ProductName
FROM Product p INNER JOIN Product_Category_Mapping pcm
ON p.Id = pcm.ProductId
WHERE pcm.CategoryId = 102
AND NOT EXISTS ( SELECT 1 FROM Product_Category_Mapping pcm2
WHERE pcm.ProductId = p.Id
AND pcm2.CategoryId <> pcm.CategoryId )
Incidentally, joining to Category is unnecessary since the CategoryId is already present in the Product_Category_Mapping table. To delete, just use the above query with an IN:
DELETE FROM Product
WHERE Id IN (
SELECT p.Id AS ProductId
FROM Product p INNER JOIN Product_Category_Mapping pcm
ON p.Id = pcm.ProductId
WHERE pcm.CategoryId = 102
AND NOT EXISTS ( SELECT 1 FROM Product_Category_Mapping pcm2
WHERE pcm.ProductId = p.Id
AND pcm2.CategoryId <> pcm.CategoryId )
)
or you might do the following instead:
DELETE FROM Product p
WHERE EXISTS ( SELECT 1 FROM Product_Category_Mapping pcm
WHERE pcm.ProductId = p.Id
AND pcm.CategoryId = 102 )
AND NOT EXISTS ( SELECT 1 FROM Product_Category_Mapping pcm2
WHERE pcm2.ProductId = p.Id
AND pcm2.CategoryId <> 102 )

Better way of doing multiple sub-selects?

Pretty standard setup with a table of products, categories and products in categories, but I was wondering if there is a better/more effective way to get the several top categories for a product (I don't really want to bring them back as separate rows so an inner join isn't viable).
The SQL I have currently is:
SELECT p.*,
(SELECT TOP 1 category_name FROM categories c INNER JOIN products_and_categories pc ON pc.category_id = c.category_id WHERE p.product_id = pc.product_id AND category_parent = 100 ORDER BY sort) AS cat_1,
(SELECT TOP 1 category_name FROM categories c INNER JOIN products_and_categories pc ON pc.category_id = c.category_id WHERE p.product_id = pc.product_id AND category_parent = 200 ORDER BY sort) AS cat_2,
(SELECT TOP 1 category_name FROM categories c INNER JOIN products_and_categories pc ON pc.category_id = c.category_id WHERE p.product_id = pc.product_id AND category_parent = 500 ORDER BY sort) AS cat_3,
(SELECT TOP 1 category_name FROM (SELECT TOP 2 c.* FROM categories c INNER JOIN products_and_categories pc ON pc.category_id = c.category_id WHERE p.product_id = pc.product_id AND category_parent = 500 ORDER BY sort) c1 ORDER BY sort DESC) AS cat_4,
(SELECT TOP 1 category_name FROM categories c INNER JOIN products_and_categories pc ON pc.category_id = c.category_id WHERE p.product_id = pc.product_id AND category_parent = 50 ORDER BY sort) AS cat_5,
(SELECT TOP 1 category_name FROM (SELECT TOP 2 c.* FROM categories c INNER JOIN products_and_categories pc ON pc.category_id = c.category_id WHERE p.product_id = pc.product_id AND category_parent = 50 ORDER BY sort) c1 ORDER BY sort DESC) AS cat_6
FROM products AS p
I'm happy enough doing it this way for a couple of columns, but was wondering if there was any other way to do it? Either by joining on a PIVOT table or something else I've not thought of or if I should just accept doing it this way.
There are a couple of other restrictions (which may or may not be important):
Some of the sub queries may not bring back any results
Some of the rows I require the top 2 categories (as can be seen in the above code)
Since your correlated subqueries only have simple equals conditions you can move these to a join, then rather than using TOP 1 you can use ROW_NUMBER() to sort your categories, then only select the top 1 for each product_ID/Parent_category combination, or in the case of two of your fields, select the the second category.
I think this would work for you:
WITH ProductCategories AS
( SELECT pc.Product_ID,
c.category_name,
category_parent,
RowNum = ROW_NUMBER() OVER(PARTITION BY pc.Product_ID, category_parent ORDER BY Sort)
FROM categories c
INNER JOIN products_and_categories pc
ON pc.category_id = c.category_id
), MaxProductCategories AS
( SELECT Product_ID,
[Cat_1] = MAX(CASE WHEN RowNum = 1 AND category_parent = 100 THEN category_name END),
[Cat_2] = MAX(CASE WHEN RowNum = 1 AND category_parent = 200 THEN category_name END),
[Cat_3] = MAX(CASE WHEN RowNum = 1 AND category_parent = 500 THEN category_name END),
[Cat_4] = MAX(CASE WHEN RowNum = 2 AND category_parent = 500 THEN category_name END),
[Cat_5] = MAX(CASE WHEN RowNum = 1 AND category_parent = 50 THEN category_name END),
[Cat_6] = MAX(CASE WHEN RowNum = 2 AND category_parent = 50 THEN category_name END)
FROM ProductCategories
WHERE RowNum IN (1, 2)
AND category_parent IN (50, 100, 200, 500)
GROUP BY Product_ID
)
SELECT p.*,
mpc.Cat_1,
mpc.Cat_2,
mpc.Cat_3,
mpc.Cat_4,
mpc.Cat_5,
mpc.Cat_6
FROM products p
LEFT JOIN MaxProductCategories mpc
ON mpc.Product_ID = p.Product_ID;