SQL Recursion, Retrieve Orders based on Product and parent Product - sql

I would like an SQL query that returns 'Orders' based on the associated 'Product', the 'Product.DepositId' has to equal an exact integer value (for example Product.DepositId = 1).
If the associated 'Product.DepositId' is null, then the query needs to go up the Product ladder get the parent 'Product' using 'Product.ParentId' and so on.
A 'Product' parent hierarchy can go 'N' tiers/layers. (for example Child -> ChildParent -> ChildParent -> Final Parent)
Multiple 'Product' children can be associated to the same Parent
Only the top most parent 'Product' will have a DepositId. So if the 'Product.ParentId' is null then the 'Product.DepositId' will not be null
An 'Order' can be associated with a child 'Product' or with a parent 'Product'. (Parents can have orders as well.)
For example (to make the example simple I used integer ids instead of uniqueidentifier)
Products
Id ParentId DepositId
1 NULL 10
2 NULL 20
3 1 NULL
4 2 NULL
5 1 NULL
6 3 NULL
Orders
Id ProductId
1001 1
1002 2
1003 3
1004 4
1005 5
1006 6
Expected Result Orders with DepositId = 10
OrderId ProductId
1001 1 --Because Product 1 DepositId = 10
1003 3 --Because Product 3 is child of Product 1
1005 5 --Because Product 5 is child of Product 1
1006 6 --Because Product 6 is child of Product 3 which in
turn is a child of Product 1

This calls for a recursive CTE.
;with rcte_products as (
select Id as ProductId, ParentId
, DepositId
, 0 as Lvl
, Id as BaseId
from Products
where DepositId = 10
union all
select t.Id, t.ParentId
, c.DepositId
, c.Lvl+1
, c.BaseId
from rcte_products c
join Products t on t.ParentId = c.ProductId
)
select
o.Id as OrderId
, o.ProductId
from rcte_products c
join Orders o
on o.ProductId = c.ProductId
order by o.Id;
OrderId
ProductId
1001
1
1003
3
1005
5
1006
6
Demo on db<>fiddle here

Related

SQL get SUM, COUNT, max, min and names of invoice

I have the following tables:
invoice
id
customerId
date
1
3
2020-10-10
2
NULL
2020-09-10
3
1
2020-10-15
product
id
name
price
1
car
10
2
pen
5
3
laptop
6
4
table
2
customer
id
name
1
a
2
b
3
c
4
d
invoceProduct
id
invoiceid
productid
1
1
1
3
1
2
4
1
4
5
2
2
6
2
3
I need to get the following for each invoice:
invoiceID, date, customerName, Total, Quantity(count of items), max product name and price, min product name and price in each invoice.
I wrote query and get most of values but I cannot get the name of products.
This is my sql query
WITH cte_Products(InvoiceId, date, CustomerName, ProductName, price) AS
(
select
i.id as InvoiceId,
i.date as date,
CASE
when c.name IS NULL THEN 'NoName'
Else c.name
End AS CustomerName,
p.name as ProductName,
p.price as price
from invoceProduct ip
join product p on p.id = ip.productID
join invoice i on i.id = ip.invoiceId
left join customer c on i.customerId = c.id
)
select
cte.InvoiceId,
cte.date,
cte.CustomerName,
SUM(cte.price) as Total,
count(*) AS ItemsQuantity,
MAX(cte.price) AS MostExpensiveItem,
MIN(cte.price) AS CheapestItem
from
cte_Products cte
group by cte.InvoiceId, cte.date, cte.CustomerName;
I got this result
InvoiceId
date
CustomerName
Total
ItemsQuantity
MostExpensiveItem
CheapestItem
1
2020-10-10
c
17
3
10
2
2
2020-09-10
NoName
11
2
6
5
I need to add the product name with product price under MostExpensiveItem and CheapestItem.

SQL return rows based on column value condition

I have below tables
Order Table
OrderNo CategoryID SubCategoryID CountryID
100 7 1 3
200 8 2 5
300 7 2 4
400 2 6 2
Tracking Table
OrderNo CountryID TrackingTypeID
100 2 1
200 1 2
100 3 3
400 5 5
200 2 2
Reviewed Table
OrderNo
300
100
200
Expected Result
OrderNo SubCategoryID CategoryID CountryID CountryID
100 1 7 3 3
200 2 8 5 5
300 2 7 4 4
Each subCategory belongs to one Category.
Now I want to write a query for below requirements.
Returns only orders of CategoryID (7,8)
1.1 In case the order has SubCategoryID (1,4)
return only orders which has a record in Tracking table with
same CountryID as in Order table.
And that order should not have a TrackingTypeID of (5,6) in Tracking
table.
1.2 In case the order has SubCategoryID (2,6) then that order must
have a record in Reviewed table and that order should not have a
TrackingTypeID (5,6) in Tracking table
Exclude all orders which have SubCategoryID that not belong to CategoryID (7,8)
I wrote below script but the issue is that it's always run the second condition only.And no order which satisfies the first condition is being returned.
But if I run the script with the first condition only I get these orders. I don't know what I'm doing wrong here.
SELECT DISTINCT o.orderNo , o.subCategoryId , o.categoryId ,
o.countryId , Tracking.countryId
FROM [Order] o
JOIN Tracking ON o.orderNo = Tracking.orderNo
WHERE
(o.subCategoryId IN (1,4 ) AND o.countryId = Tracking.countryId AND
EXISTS (SELECT 1
FROM tracking t
WHERE t.orderNo = o.orderNo AND t.countryId = o.countryId)
AND NOT EXISTS ( SELECT 1
FROM tracking t
WHERE t.orderNo = o.orderNo
AND TrackingTypeID IN (5,6))
)
OR
(o.subCategoryId in (
SELECT id FROM subCatgory WHERE categoryId in (7,8)
AND ID NOT IN (1 , 4)
)
AND EXISTS (SELECT 1
FROM Reviewed r
WHERE r.orderNo = r.orderNo
)
AND NOT EXISTS ( SELECT 1
FROM tracking t
WHERE t.orderNo = o.orderNo
AND TrackingTypeID IN (5,6))
)

SQL query with one-to-many relationship with PostgreSQL

I'm trying to aggregate a list of cart items with a query that relates through a line_items table. I've abstracted a simple example of my use case:
my expected result would look like this:
cart_id cart_total shopper_id items payment_id
1 345 34 [{"name":"egg","price:"39"}] AS34gsdVSWET4
2 54 45 [{"name":"biscut","price:"5"},{"name":"apple","price:"15"}] JFHERHJE$Y#$H
given a schema and data like:
carts:
id cart_total shopper_id
1 39 34
2 20 45
line_items:
id cart_id item_name item_price
1 1 egg 39
2 2 biscut 5
3 2 apple 15
payment:
id cart_id payment_id
1 1 AS34gsdVSWET4
2 2 JFHERHJE$Y#$H
How to get all cart list and get a list of carts for particular shopperId?
Here is my solution using json_build_object:
SELECT c.id AS cart_id,
c.cart_total,
c.shopper_id,
json_agg(
json_build_object(
'item_name', i.item_name,
'item_price', i.item_price::text
)
) AS items,
p.payment_id
FROM carts AS c
JOIN line_items AS i ON i.cart_id = c.id
JOIN payment AS p ON p.cart_id = c.id
GROUP BY c.id, c.cart_total, c.shopper_id, p.payment_id;
Are you sure you want the price as string?

Get all the orders for ItemId 1 and 2 simultaneously

I have scenario below. I have a table Orders(OrderId, ItemId) and i want to get all the orders where ItemId 1 and 2 is coming simulataneoudsly
OrderId ItemId
1000 1
1001 2
1002 1
1002 2
1002 3
1003 1
1003 2
1004 4
1004 5
1005 3
1006 1
1006 3
as per above table's data and the ask ....i should get output OrderId 1002 and 1003.
Use COUNT and HAVING:
SELECT OrderId
FROM Orders
WHERE
ItemId IN(1, 2)
GROUP BY OrderId
HAVING COUNT(ItemId) = 2
If duplicate ItemId is allowed in an OrderId, use:
HAVING COUNT(DISTINCT ItemId) = 2
ONLINE DEMO
Another option, using nested queries, would be:
SELECT orderId
FROM ORDERS o
WHERE o.itemId = 1
AND EXISTS (SELECT 'X'
FROM ORDERS o1
WHERE o1.orderId = o.orderId
AND o1.itemId = 2);
ONLINE DEMO

tricky select statement

I have a table with categories, each category as an ID, a Name and a ParentID. The problem is that there are 3 levels, parent categories, sub categories and child categories.
I can extract parent categories with a simple SELECT and a WHERE ParentID IS NULL clause as such:
SELECT *
FROM Category
WHERE ParentID IS NULL
However, a WHERE ParentID IS NOT NULL clause will return both, sub categories as well as child categories.
I'm looking for a way to extract only sub categories, and only child categories.
Typically, for these sort of problems, it is better to use the Recursive Queries Using Common Table Expressions. Something like so:
;WITH CategoriesTree(CategoryID, CategoryName, ParentName, CategoryLevel)
AS
(
SELECT
c.ID,
c.Name,
CAST('No Parent' AS VARCHAR(50)) AS ParentName,
0 AS CategoryLevel
FROM #Categories c
WHERE c.ParentID IS NULL
UNION ALL
SELECT c.ID, c.Name, p.CategoryName, p.CategoryLevel + 1
FROM CategoriesTree p
INNER JOIN #Categories c ON c.ParentID = p.CategoryID
)
SELECT *
FROM CategoriesTree
Where CategoryLevel = some id;
SQL Fiddle Demo
This will give you:
CATEGORYID CATEGORYNAME PARENTNAME CATEGORYLEVEL
1 Root Cateogry No Parent 0
2 Sub Cateogry 1 Root Cateogry 1
3 Sub Cateogry 2 Root Cateogry 1
4 Sub Cateogry 3 Root Cateogry 1
8 sub Cateogry 1 of 3 Sub Cateogry 3 2
7 Sub Cateogry 1 of 2 Sub Cateogry 2 2
5 Sub Cateogry 1 of 1 Sub Cateogry 1 2
6 sub Cateogry 2 of 1 Sub Cateogry 1 2
How does this work?
Using this query, you can control what level of categories you want to select. For instance, for the sample data, I used in the previous demo, here is the categories tree:
1: RootCategory Category Level: 0
|
|
----------------------------
| | |
| | |
2: Sub1 3: Sub2 4: sub3 Category Level: 1
| | |
------------ | |
| | | |
| | | |
5: Sub1of1 6: Sub2of1 7: sub1of2 8: sub1of3 Category Level: 2
This query will give you this categories tree with the new generated CategoryLevel column.
Note that: In the sample data I used in the demo, there was only one parent category (the categories with parentid IS NULL). However, the query will for work fine in case there were a lot of parent categories. And this because of the anchor query of the CTE, which is:
SELECT
c.ID,
c.Name,
CAST('No Parent' AS VARCHAR(50)) AS ParentName,
0 AS CategoryLevel
FROM #Categories c
WHERE c.ParentID IS NULL;
Then, you can use the generated column CategoryLevel to select only the child categories of the level that you are interested in.
For example, if you need to select only the sub categories of the first sub categories of the root category, you can get these categories using the predicate CategoryLevel = 2:
;WITH CategoriesTree(CategoryID, CategoryName, ParentName, CategoryLevel)
AS
(
...
)
SELECT *
FROM CategoriesTree
WHERE CategoryLevel = 2;
This will give you:
CATEGORYID CATEGORYNAME PARENTNAME CATEGORYLEVEL
8 sub Cateogry 1 of 3 Sub Cateogry 3 2
7 Sub Cateogry 1 of 2 Sub Cateogry 2 2
5 Sub Cateogry 1 of 1 Sub Cateogry 1 2
6 sub Cateogry 2 of 1 Sub Cateogry 1 2
SQL Fiddle Demo
First level categories - you have it:
SELECT *
FROM Category
WHERE ParentID IS NULL
For the second level categories you can try:
SELECT * FROM Category
WHERE ParentID IN (SELECT ID FROM Category WHERE ParentID IS NULL).
For the third:
SELECT * FROM Category
WHERE ParentID IN (SELECT ID FROM Category WHERE ParentID IS NOT NULL)
(Not tested)
How about something like:
-- Root parents
select c.* from categories c where c.ParentID is null
-- Second level. Select where parentid is a root category.
select c.* from categories c
where c.ParentID in (select c1.ID from categories c1 where c1.ParentID is null);
-- Third level. Select where parentid is a second level category
with second_level_cats (ID) as (
select c.ID from categories c
where c.ParentID in (select c1.ID from categories c1 where c1.ParentID is null)
)
select c.* from categories c
where c.ParentID in (select l2.ID from second_level_cats l2)
May not be entirely optimal but it seems to work. If there's only a relatively low number of rows and you're only ever going to go to three levels then it should suffice.