Get top n occurences based on related table value - sql

I have a table Orders (Id, OrderDate, CreatorId) and a table OrderLines (Id, OrderId, OwnerIdentity, ProductId, Amount)
Scenario is as follows: Someone opens up an Order and other users can then place their product orders on that order. Those users are the OwnerId of OrderLines.
I need to retrieve the top 3 latest orders that a user has placed an order on and display all of his orders placed, to give him an insight in his personal recent orders.
So my end result would be something like
OrderId | ProductId | Amount
----------------------------
1 | 1 | 2
1 | 7 | 1
1 | 2 | 5
4 | 4 | 3
4 | 1 | 2
8 | 4 | 1
8 | 9 | 2

Select o.Id as OrderId, ol.ProductId, ol.Amount from Orders o
inner join OrderLines ol
on o.Id = ol.OrderId where o.Id in
(Select top 3 OrderId from Orders where OwnerId = #OwnerId)
Order By o.OrderDate desc
You can add date time column to OrderLines table to query latest personal orders and then update the code by moving "order by OrderDate desc" section to sub select query.

select * from
(
select OrderId, ProductId, Amount
row_number() over (partition by OrderID order by Orders.OrderDate) as rn
from OrderLines
join Orders
on OrderLines.OrderId = Orders.Id
where OwnerIdentity = x
) lskdfj
where rn <= 3

Try the below query:
SELECT OL.OrderId, OL.ProductID, OL.Amount
FROM OrderLines OL WHERE OL.OrderId IN
(
SELECT TOP 3 O.OrderID FROM orders O LEFT JOIN OrderLines OL2
ON OL2.orderId=O.OrderID
WHERE OL2.OwnerIdentity =...
ORDER BY O.OrderDate DESC
) AND WHERE OL.OwnerIdentity =...

;WITH cte AS (
SELECT ol.OrderId, ol.ProductId, ol.Amount,
ROW_NUMBER()OVER (PARTITION BY ol.OrderId ORDER BY o.OrderDate DESC) rn
FROM OrderLines ol
JOIN Orders o ON ol.OrderId = o.Id
WHERE OwnerIdentity = #OwnerId
)
SELECT OrderId, ProductId, Amount
FROM cte
WHERE rn <= 3

Related

SQL JOIN omits some fields

I have the following tables:
Product_T with columns:
ProductID,
ProductDescription
OrderLine_T with columns:
OrderID,
ProductID,
OrderedQuantity
Order_T with columns:
OrderID,
CustomerID,
Customer_T with columns:
CustomerID,
CustomerName
I want to list the product ID and description, along with the customer ID and name for the customer who has bought the most of that product and also show the total quantity ordered by that customer.
I came up with following query, to list the max quantity product per order:
SELECT o1.OrderID, o1.ProductID, SUM(o1.OrderedQuantity) AS A
FROM OrderLine_T o1
GROUP BY
o1.ProductID,
o1.OrderID
HAVING SUM(o1.OrderedQuantity) = (
SELECT MAX(s.d)
FROM (
SELECT
o1.OrderID,
o1.ProductID,
SUM(o1.OrderedQuantity) AS d
FROM OrderLine_T o1
GROUP BY
o1.ProductID,
o1.OrderID
) s
WHERE o1.ProductID = s.ProductID
)
And that gave me a correct output of:
50 20 1
48 17 5
32 14 10
59 13 2
1 10 9
2 8 2
69 7 4
4 6 3
32 5 10
55 4 2
2 3 12
1 2 18
26 1 5
But then, when I tried joining it with other tables, so I could select CustomerName and CustomerID, like so:
SELECT
o1.ProductID,
s.CustomerName,
s.CustomerID,
SUM(o1.OrderedQuantity) AS A
FROM OrderLine_T o1
INNER JOIN (
SELECT
c1.CustomerName,
c1.CustomerID,
p1.ProductID
FROM Product_T p1
INNER JOIN OrderLine_T o3 ON p1.ProductID = o3.ProductID
INNER JOIN Order_T o2 ON o3.OrderID = o2.OrderID
INNER JOIN Customer_T c1 ON o2.CustomerID = c1.CustomerID
) s ON s.ProductID = o1.ProductID
GROUP BY
o1.ProductID,
s.CustomerName,
s.CustomerID
HAVING SUM(o1.OrderedQuantity) = (
SELECT MAX(s.d)
FROM (
SELECT
o1.OrderID,
o1.ProductID,
SUM(o1.OrderedQuantity) AS d
FROM OrderLine_T o1
GROUP BY
o1.ProductID,
o1.OrderID
) s
WHERE o1.ProductID = s.ProductID
) ;
The output shrunk to:
17 Contemporary Casuals 1 5
8 Home Furnishings 3 2
7 Eastern Furniture 4 4
10 Eastern Furniture 4 9
20 Dunkins Furniture 8 1
13 Ikards 13 2
Why could that be?
It seems you should be using window functions here, such as ROW_NUMBER, along with conditional aggregation
SELECT
o.ProductID,
p.Description,
CustomerID = MAX(CASE WHEN o.rn = 1 THEN c.CustomerID END),
CustomerName = MAX(CASE WHEN o.rn = 1 THEN c.CustomerName END),
SUM(CASE WHEN o.rn = 1 THEN o.TotalQty END) AS QtyForTopCustomer
SUM(o.TotalQty) AS TotalQty
FROM (
SELECT
o.ProductID,
o.CustomerID,
TotalQty = SUM(oi.OrderedQuantity),
rn = ROW_NUMBER() OVER (PARTITION BY oi.ProductId ORDER BY SUM(oi.OrderedQuantity) DESC)
FROM OrderLine_T ol
INNER JOIN Order_T o ON o.OrderID = ol.OrderID
GROUP BY
o.ProductID,
o.CustomerID
) o
INNER JOIN Customer_T c ON c.CustomerID = o.CustomerID
INNER JOIN Product_T p ON p.ProductID = ol.ProductID
GROUP BY
o.ProductID,
p.Description;
If you only wanted the data for that one customer, you could remove the conditional aggregation and just filter by row-number
SELECT
o.ProductID,
p.Description,
o.CustomerID,
o.CustomerName,
o.TotalQty
FROM (
SELECT
p.ProductID,
p.Description,
o.CustomerID,
TotalQty = SUM(oi.OrderedQuantity),
rn = ROW_NUMBER() OVER (PARTITION BY oi.ProductId ORDER BY SUM(oi.OrderedQuantity) DESC)
FROM OrderLine_T ol
INNER JOIN Order_T o ON o.OrderID = ol.OrderID
GROUP BY
p.ProductID,
p.Description,
o.CustomerID
) o
INNER JOIN Customer_T c ON c.CustomerID = o.CustomerID
INNER JOIN Product_T p ON p.ProductID = ol.ProductID
WHERE o.rn = 1;

Get ID using join based on most recent date

I have 2 tables:
Orders
order_id total
1 5
Invoices
order_id invoice_id ship_date
1 a 1/1/2020
1 b 2/2/2020
I need to get the earliest ship date and the invoice_id of the latest date. So the query should return the following:
order_id total latest_invoice_id earliest_ship_date
1 5 b 1/1/2020
Here's my query so far:
SELECT
order_id,
total,
earliest_ship_date,
latest_invoice_id
FROM Orders o
INNER JOIN (SELECT
order_id,
min(ship_date) as earliest_ship_date,
max(invoice_id) as latest_invoice_id
FROM Invoices
GROUP BY order_id) i ON o.order_id = i.order_id
Of course this doesn't work because all I do is get the highest invoice_id using alphabetical order. How can I get the invoice ID of the latest ship date in this case?
You can use window functions and conditional aggregation. I find that a lateral join is handy here:
select o.*, i.*
from orders o
cross apply (
select
max(case when rn = 1 then invoice_id end) latest_invoice_id,
min(ship_date) earliest_ship_date
from (
select i.*,
row_number() over(partition by order_id order by ship_date desc) rn
from invoices i
where i.order_id = o.order_id
) i
) i
You can go for ranking and then arrive at the result
;WITH CTE_Invoices AS
(SELECT o.Order_Id,O.total, i.invoice_id,i.ship_date
ROW_NUMBER() OVER(PARTITION BY Order_Id ORDER BY ship_date DESC) as rnk_latestInvoice,
ROW_NUMBER() OVER(PARTITION BY Order_Id ORDER BY ship_date) AS rnk_shipdate
FROM Orders as o
INNER JOIN Invoices as i
ON i.Order_Id = o.Order_ID)
select Order_Id, total , MAX(CASE WHEN rnk_latestInvoice = 1 THEN invoice_id END) as Latest_Invoice_id,
MAX(CASE WHEN rnk_shipdate = 1 THEN ship_date END) as earliest_shipdate
FROM CTE_Invoices
GROUP BY Order_Id, total

Selecting the MIN(date) and the MAX(date) with the MAX(date) Freight Values

I need the MIN(Orderdate) and MAX(Orderdate) with the MAX(Orderdate) Freight values.
I've tried using a CTE and I am struggling to eliminate duplicate rows for the CustomerID
USE Northwind
GO
WITH CTE AS (
SELECT a.customerID,
MAX(b.OrderDate) AS LastOrder,
MIN(b.OrderDate) AS FirstOrder
FROM Orders AS b
INNER JOIN Customers AS a
ON b.CustomerID = a.CustomerID
GROUP BY a.CustomerID
)
SELECT CTE.customerID, CTE.FirstOrder, d.OrderDate as LastOrder, d.Freight
FROM CTE
INNER JOIN Orders as d
On CTE.CustomerID = d.CustomerID
GROUP BY CTE.CustomerID, CTE.FirstOrder, d.Freight, d.OrderDate
HAVING d.OrderDate = MAX(d.OrderDate)
I am trying to get these results which should display 89 records.
CustomerID FirstOrder LastOrder Freight
| ALFKI | 1997-08-25 | 1998-04-09 | 1.21 |
| ANATR | 1996-09-18 | 1998-03-04 | 39.92 |
| ANTON | 1996-11-27 | 1998-01-28 | 58.43 |
| AROUT | 1996-11-15 | 1998-04-10 | 33.80 |
| BERGS | 1996-08-12 | 1998-03-04 | 151.52 |
Just to Keep it simple and in line with the question. The Subquery CTE already has the customerID and the LastOrder(Max Order Date). Joining the LastOrder column in CTE to the Order tables OrderDate will give the expected results.
and d.OrderDate = CTE.LastOrder
Full Query:
WITH CTE AS (
SELECT a.customerID,
MAX(b.OrderDate) AS LastOrder,
MIN(b.OrderDate) AS FirstOrder
FROM Orders AS b
INNER JOIN Customers AS a
ON b.CustomerID = a.CustomerID
GROUP BY a.CustomerID
)
SELECT CTE.customerID, CTE.FirstOrder, d.OrderDate as LastOrder, d.Freight
FROM CTE
INNER JOIN Orders as d
On CTE.CustomerID = d.CustomerID
and d.OrderDate = CTE.LastOrder
Just use conditional aggregation:
SELECT o.customerID,
MAX(o.OrderDate) AS LastOrder,
MIN(o.OrderDate) AS FirstOrder,
MAX(CASE WHEN seqnum = 1 THEN o.freight END) as lastFreight
FROM (SELECT o.*,
ROW_NUMBER() OVER (PARTITION BY o.customerID ORDER BY o.OrderDate DESC) as seqnum
FROM Orders o
) o
GROUP BY o.CustomerID;
Note that you do not appear to need the Customer table. All the information you need is in Orders (I doubt you are using Customer to remove rows.)

Group By / Having correlated subquery

I am trying to solve this query, I'm really lost
least one order in which the quantity they ordered was greater than
the average quantity of all other orders.
My tables are:
Customer
Cust_ID | CustName | Region | Phone
Orders
Ordernum | Cust_ID | Item_ID | Quantity
Vendor
Vendor_ID | Item_ID | Costs | Region
stock
Item_ID | Description | Price | On_hand
Database schema is:
Customer (Cust_ID, CustName, Region, Phone)
Orders (Ordernum, Cust_ID, Item_ID, Quantity) Foreign key Cust_ID
references Customer Not Null, On Delete Restrict
Foreign key Item_ID references Stock Not Null, On Delete Restrict
Stock (Item_ID, Description, Price, On_hand)
Vendor (Vendor_ID, Item_ID, Cost, Region)
Foreign key Item_ID references Stock Not null, On Delete Restrict
Here is what I have tried so far. What am I doing wrong?
select custname, phone, count(distinct(item_id)), sum(quantity)
from customer c, orders o
Where c.cust_id= o.cust_id and count (o.ordernum) >= 2
group by cust_id
having sum (quantity) >
(select avg(quantity)
from orders o2
where o.item_id != o2.item_id)
order by custname;
I re-wrote my code and this is what I came up with. I'm really lost:
select c1.custname, phone, count(distinct(o.item_id)), sum(quantity) as quantity
from customer c1, orders o
Where c1.cust_id= o.cust_id
group by c1.cust_id, custname, phone, quantity
Having 2>=
(select count(o1.item_id)
From orders o1
where c1.cust_ID = o1.cust_ID)
AND sum(quantity) >
(select AVG(quantity)
from orders o2
Where c1.cust_ID != o2.cust_ID AND o.Item_ID = o2.Item_ID)
order by custname;
Try this query
select a.cust_id, count(distinct Item_ID) as itemOrderd,
sum(b.quantity) as sum
from
customer a
inner join
orders b
on a.cust_id=b.cust_id
group by a.cust_id
having count(distinct ordernum) > 1
and max(b.quantity) > avg(b.quantity)
Fiddle
| CUST_ID | ITEMORDERD | SUM |
|---------|------------|-----|
| 1 | 2 | 61 |
I think it will be:
SELECT MAX(c.CustName) as CustName,
MAX(c.Phone) as Phone,
COUNT(DISTINCT o.Item_ID) as CountOfDistinctItems,
SUM(o.Quantity) as SumOfQuantity
FROM Customer as c
JOIN Orders as o on c.Cust_ID=o.Cust_ID
JOIN (SELECT Ordernum, SUM(Quantity) as Order_q_sum
FROM Orders
GROUP BY Ordernum) as oq
ON o.Ordernum=oq.Ordernum
GROUP BY c.Cust_ID
HAVING COUNT(DISTINCT o.Ordernum)>=2
AND
MAX(oq.Order_q_sum)>
(
SELECT AVG(SumQuantity) FROM
(SELECT SUM(Quantity) SumQuantity FROM Orders GROUP BY OrderNum) as t1
)
HOPE THIS CLOSER TO YOUR NEEDS
SELECT x.custname,
x.phone,
Sum(x.itemcnt),
Sum(x.quantity)
FROM (SELECT c.custname,
c.phone,
1 AS itemCnt,
Sum(o.quantity),
Isnull((SELECT Avg(y.quantity)
FROM orders y
WHERE y.cust_id = o.cust_id), 0.00) AvgQty
FROM customer c
LEFT OUTER JOIN orders o
ON c.cust_id = o.cust_id
GROUP BY c.custname,
c.phone,
o.item_id) x
GROUP BY x.custname,
x.phone
HAVING Sum(x.itemcnt) > 1
AND Sum(x.quantity) > x.avgqty

Get orders where order lines meet certain requirements

I have the following simplified tables:
tblOrders
orderID date
---------------------
1 2013-10-04
2 2013-10-05
3 2013-10-06
tblOrderLines
lineID orderID ProductCategory
--------------------------------------
1 1 10
2 1 3
3 1 10
4 2 3
5 3 3
6 3 10
7 3 10
I want to select records from tblOrders ONLY if any order line has ProductCategory = 10. So, if none of the lines of a particular order has ProductCategory = 10, then do not return that order.
How would I do that?
This should do:
SELECT *
FROM tblOrders O
WHERE EXISTS(SELECT 1 FROM tblOrderLines
WHERE ProductCategory = 10
AND OrderID = O.OrderID)
You can use exists for this
Select o.*
From tblOrders o
Where exists (
Select 1
From tblOrderLines ol
Where ol.ProductCategory = 10
And ol.OrderId = o.OrderId
)
try this
SELECT DISTINCT orderId
FROM tblOrders t1
INNER JOIN tblOrderLines t2 ON t1.orderId = t2.orderId
WHERE t2.ProductCategory = 10
Try this:
SELECT *
FROM tblOrders O
JOIN tblOrderLines L
ON O.orderID = L.orderID
WHERE L.OrderID in (SELECT orderID FROM tblOrderLines WHERE ProductCategory = 10)