Top 3 rows per country - sql

I've produced a report of "Which and how many of Products been sold in each country". I'm using Northwind database, SQL Server 2012.
The following is the code:
SELECT
o.ShipCountry AS 'Country',od.ProductID,
p.ProductName, p.UnitPrice,
SUM(od.Quantity) AS 'Number of Units Sold'
FROM
Products p
INNER JOIN
[Order Details] od ON od.ProductID = p.ProductID
INNER JOIN
Orders o ON o.OrderID = od.OrderID
GROUP BY
p.ProductName, od.ProductID, p.UnitPrice, o.ShipCountry
ORDER BY
o.ShipCountry, 'Number of Units Sold' DESC
The result shows over 900 rows, each country has about 10 to 20 rows:
But I want to take it up a notch, and now I want to produce "Top 3 products sold per country"
So I tried ROW_NUMBER() OVER (PARTITION BY but I'm clumsy at using Row_NUMBER()
The below is my wrong code:
WITH CTE AS
(
SELECT
o.ShipCountry AS 'Country',od.ProductID,
p.ProductName, p.UnitPrice,
SUM(od.Quantity) AS 'Number of Units Sold',
ROW_NUMBER() OVER (PARTITION BY o.ShipCountry ORDER BY ('Number of Units Sold') DESC) AS 'Number of Units Sold'
FROM
Products p
INNER JOIN
[Order Details] od ON od.ProductID = p.ProductID
INNER JOIN
Orders o ON o.OrderID = od.OrderID)
SELECT
'Country', ProductID,
ProductName, UnitPrice, 'Number of Units Sold'
FROM
CTE
WHERE
'Number of Units Sold' < 4
GROUP BY
p.ProductName, od.ProductID, p.UnitPrice, o.ShipCountry
ORDER BY
o.ShipCountry DESC

Try this:
WITH CTE AS
(
SELECT
o.ShipCountry, od.ProductID,
p.ProductName, p.UnitPrice,
SUM(od.Quantity) AS UnitsSold,
RowNum = ROW_NUMBER() OVER (PARTITION BY o.ShipCountry ORDER BY SUM(od.Quantity) DESC)
FROM
Products p
INNER JOIN
[Order Details] od ON od.ProductID = p.ProductID
INNER JOIN
Orders o ON o.OrderID = od.OrderID
GROUP BY
p.ProductName, od.ProductID, p.UnitPrice, o.ShipCountry
)
SELECT *
FROM CTE
WHERE CTE.RowNum <= 3
Basically, in the CTE, you define the columns you want - word of caution: don't use column names with spaces and stuff like that! Makes for a nice presentation on screen, but really hard to use in a query!
Then you add the ROW_NUMBER() that will number each entry for each country starting at 1.
And finally, you select from the CTE, and you take only those rows with a RowNum <= 3 ==> the TOP 3 for each country.

;with CTE as(
SELECT o.ShipCountry AS 'Country',
od.ProductID,
p.ProductName,
p.UnitPrice,
SUM(od.Quantity) AS 'Number of Units Sold'
FROM Products p
INNER JOIN [Order Details] od
ON od.ProductID=p.ProductID
INNER JOIN Orders o
ON o.OrderID=od.OrderID
GROUP BY p.ProductName, od.ProductID, p.UnitPrice, o.ShipCountry
)
,CTE2 as
( Select
CTE.Country,
CTE.ProductID,
CTE.ProductName,
CTE.UnitPrice,
CTE.[Number of Units Sold],
ROW_NUMBER() OVER (PARTITION BY CTE.Country
ORDER BY CTE.[Number of Units Sold] DESC) AS rownum
from CTE
)
select CTE2.Country,
CTE2.ProductID,
CTE2.ProductName,
CTE2.UnitPrice,
CTE2.[Number of Units Sold]
FROM CTE2
WHERE CTE2.rownum<4
ORDER BY CTE2.Country, CTE2.[Number of Units Sold] DESC

Try this :
SELECT Country, ProductID,
ProductName,UnitPrice,Number_of_Units_Sold
FROM
(
SELECT o.ShipCountry AS Country, od.ProductID as ProductID,
p.ProductName as ProductName, p.UnitPrice as UnitPrice,
SUM(od.Quantity) AS Number_of_Units_Sold,
ROW_NUMBER() OVER (PARTITION BY o.ShipCountry ORDER BY (SUM(od.Quantity)) DESC) AS MYRANK
FROM Products p
INNER JOIN [OrderDetails] od
ON od.ProductID=p.ProductID
INNER JOIN Orders o
ON o.OrderID=od.OrderID
GROUP BY o.ShipCountry, p.ProductName, od.ProductID, p.UnitPrice, o.ShipCountry
) tmp
where MYRANK <= 3
ORDER BY Country, Number_of_Units_Sold DESC

Related

SQL And Northwind

I have a query where i can find all the products, all the customers that bought each product and the quantity.
select OD.ProductID, OD.Quantity, O.CustomerID
from dbo.[Order Details] OD inner join dbo.Orders O on OD.OrderID = O.OrderID
Order by OD.ProductID ASC, OD.Quantity DESC
But what i need is to now which customer bought the most of each product. How can I do it?
You can use top 1 with ties with rank window function:
select top 1 with ties OD.ProductID,
OD.Quantity,
O.CustomerID
from dbo.[Order Details] OD
inner join dbo.Orders O on OD.OrderID = O.OrderID
order by rank() over (
partition by OD.ProductID order by OD.Quantity desc
);
The above will return multiple rows per productId if there are multiple customers with max quantity ordered for that product.
If you want to get only one row, you can use row_number:
select top 1 with ties OD.ProductID,
OD.Quantity,
O.CustomerID
from dbo.[Order Details] OD
inner join dbo.Orders O on OD.OrderID = O.OrderID
order by row_number() over (
partition by OD.ProductID order by OD.Quantity desc
);
You can also do this without top:
select *
from (
select OD.ProductID,
OD.Quantity,
O.CustomerID,
row_number() over (
partition by OD.ProductID order by OD.Quantity desc
) as rn
from dbo.[Order Details] OD
inner join dbo.Orders O on OD.OrderID = O.OrderID
) t
where rn = 1;

Postgresql returning the most popular genre of product per customer

I have a query that is supposed to return a list of customers with the most popular product type for each customer. I have have a query that sums up each product purchased in all given product types and lists them in descending order per customer
SELECT c.customer_name as cname, ptr.product_type as pop_gen, sum(od.quantity) as li
FROM product_type_ref as ptr
INNER JOIN product as p
on p.product_type_ref_id = ptr.product_type_ref_id
INNER JOIN order_detail as od
on od.product_id = p.product_id
INNER JOIN order as o
on o.order_id = od.order_id
INNER JOIN customer as c
on c.customer_id = o.customer_id
GROUP BY cname, pop_gen
ORDER BY cname, li DESC
which returns this data:
'andy','Drama',1000
'andy','Action',250
'andy','Comedy',100
'bebe','Drama',250
'bebe','Action',100
'bebe','Comedy',25
'buster','Action',825
'buster','Comedy',768
'buster','Drama',721
'buster','Romance',100
'ron','Romance',50
'ron','Comedy',10
how could i return this:
andy, Drama
bebe, Drama
buster, Action
ron, Romance
In Postgres, you can just use distinct on:
SELECT DISTINCT ON (c.customer_name) c.customer_name as cname,
ptr.product_type as pop_gen, sum(od.quantity) as li
FROM product_type_ref as ptr
INNER JOIN product as p
on p.product_type_ref_id = ptr.product_type_ref_id
INNER JOIN order_detail as od
on od.product_id = p.product_id
INNER JOIN order as o
on o.order_id = od.order_id
INNER JOIN customer as c
on c.customer_id = o.customer_id
GROUP BY cname, pop_gen
ORDER BY cname, li DESC;
Classic greatest-n-per-group. One possible solution is to use ROW_NUMBER():
WITH
CTE
AS
(
SELECT
c.customer_name as cname, ptr.product_type as pop_gen, sum(od.quantity) as li
,ROW_NUMBER() OVER(PARTITION BY c.customer_name ORDER BY sum(od.quantity) DESC) AS rn
FROM
product_type_ref as ptr
INNER JOIN product as p on p.product_type_ref_id = ptr.product_type_ref_id
INNER JOIN order_detail as od on od.product_id = p.product_id
INNER JOIN order as o on o.order_id = od.order_id
INNER JOIN customer as c on c.customer_id = o.customer_id
GROUP BY
cname, pop_gen
)
SELECT
cname, pop_gen, li
FROM CTE
WHERE rn = 1
ORDER BY cname;
Add ROW_NUMBER()
SELECT *
FROM (
SELECT c.customer_name as cname,
ptr.product_type as pop_gen,
sum(od.quantity) as li,
ROW_NUMBER() OVER (PARTITION BY c.customer_name
ORDER BY sum(od.quantity) DESC) as rn
......
) as T
WHERE T.rn = 1

Select maximum value of one table depending on 2 other tables

I have 3 tables
Orders (orderID, CustomerID)
Orderlines (orderID, ProdID)
Products (ProdID, CategoryID)!
I want to find the customerID which has the most different "CategoryID" in one order!
To get you there, start with the basic query to get your info:
SELECT o.customer_id
,l.orderid
,COUNT(DISTINCT categoryid) category_cnt
FROM orders o
JOIN orderlines l on l.orderid = o.orderid
JOIN products p ON l.prodid = p.prodid
GROUP BY l.customner_id, l.orderid
order by COUNT(DISTINCT categoryid) desc;
Once you see that this works out, we will add an analytic to this to show you the rank() function
SELECT o.customer_id
,l.orderid
,COUNT(DISTINCT categoryid) category_cnt
, rank() over (order by COUNT(DISTINCT categoryid) desc) as count_rank
FROM orders o
JOIN orderlines l on l.orderid = o.orderid
JOIN products p ON l.prodid = p.prodid
GROUP BY l.customner_id, l.orderid
order by COUNT(DISTINCT categoryid) desc;
Following so far? OK, so now we just need to push this down into a sub-query to get the record(s) ranked #1 (in case more than one customer order matches the top count)
SELECT customer_id, order_id, category_cnt
FROM (
SELECT o.customer_id
,l.orderid
,COUNT(DISTINCT categoryid) category_cnt
, rank() over (order by COUNT(DISTINCT categoryid) desc) as count_rank
FROM orders o
JOIN orderlines l on l.orderid = o.orderid
JOIN products p ON l.prodid = p.prodid
GROUP BY l.customner_id, l.orderid)
WHERE count_rank = 1;
Try;
with data_a as ( --distinct CategoryID cnt
select
o.orderID,
o.customerID,
count(DISTINCT p.CategoryID) cnt
from Orders o
join Orderlines ol.orderID = o.orderID
join Products p on p.ProdID = ol.ProdID
group by o.orderID, o.customerID
),
data as ( --get all count rnk
select
orderID,
customerID,
rank() over (partition by orderID, customerID order by cnt desc) rnk
from data_a
)
select
orderID,
customerID
from data
where rnk = 1
Step by step: Count distinct categories per order first. Then rank your orders, so that the orders with the most categories get rank #1. Then find customers for all orders ranked #1.
select distinct cutomerid
from orders
where orderid in
(
select orderid
from
(
select orderid, rank() over (order by category_count desc) as rnk
from
(
select ol.orderid, count(distinct p.distinctcategroyid) as category_count
from orderlines ol
join products p on p.prodid = ol.prodid
group by ol.orderid
) counted
) ranked
where rnk = 1
);
Something like that i guess
SELECT o.customerID, t.category_cnt
FROM (SELECT l.orderid, COUNT(DISTINCT categoryid) category_cnt
FROM orderlines l
JOIN products p ON l.prodid = p.prodid
GROUP BY l.orderid
ORDER BY category_cnt DESC) t
JOIN orders o ON o.orderid = t.orderid
WHERE rownum < 2

SQL Server : How to Select Sum Amount Spent for the Most Expensive Item by a Customer - Northwind DB

Actually question tells all; Lots of customers has many orders with for many items; I'm trying to display the total amount spent for the most expensive item ordered by that customers through the all orders given by that customer. I'm using Northwind DB and tables like Customers, Orders, Order Details, Products. I've the query below, I've tried to limit it by an aggregate function but SQL does not allow it on where clause. Any help?
select
p.ProductName,
c.ContactName,
od.ProductID,
MAX(od.UnitPrice)
SUM(od.UnitPrice*od.Quantity) as Total
from
Customers c
join
Orders o ON c.CustomerID = o.CustomerID
join
[Order Details] od on od.OrderID = o.OrderID
join
Products p on od.ProductID = p.ProductID
where
c.CustomerID in
group by
c.ContactName, p.ProductName, od.Quantity, od.ProductID
order by
MAX(od.UnitPrice) desc
I think the easiest way to solve this is by using a window function to get the highest priced product. The following query uses row_number() for this purpose:
select p.ProductName, c.ContactName, od.ProductID,
MAX(od.UnitPrice)
SUM(od.UnitPrice*od.Quantity) as Total
from Customers c join
(select od.*, o.CustomerId,
row_number() over (partition by o.CustomerId
order by od.UnitPrice desc) as seqnum
from [Order Details] od join
Orders o
on od.OrderId = o.OrderId
) od
on od.CustomerId = c.CustomerId and seqnum = 1 join
Products p
on od.ProductID = p.ProductID
group by c.ContactName, p.ProductName, od.ProductID
order by MAX(od.UnitPrice) desc;
Note that the joins have been rearranged a bit. You need the customer id to define the highest priced product in the subquery, so the subquery has the join to orders. You don't need the join in the outer query.

SELECT TOP record for each year

I am trying to recap on my sql skill, now I am trying to run a simple query on northwinddb to show me the top customer for each year, but as soon as I use the TOP function only 1 record gets display no matter on what I partition by, This is my T-SQL code
SELECT DISTINCT TOP 1 C.CompanyName
, YEAR(O.OrderDate) AS Year
, SUM(Quantity) OVER(PARTITION BY C.CompanyName, YEAR(O.OrderDate)) AS Total
FROM Customers C JOIN Orders O
ON C.CustomerID = O.CustomerID JOIN [Order Details] OD
ON O.OrderID = OD.OrderID
You can do this bit more compactly in SQL Server 2008 as follows:
select top (1) with ties
C.CompanyName,
Year(O.OrderDate) as Yr,
sum(OD.Quantity) as Total
from Orders as O
join Customers as C on C.CustomerID = O.CustomerID
join "Order Details" as OD on OD.OrderID = O.OrderID
group by C.CompanyName, Year(O.OrderDate)
order by
row_number() over (
partition by Year(O.OrderDate)
order by sum(OD.Quantity) desc
);
Thank you for the help. I found a way that allows me to change the number of top customers i want to see for each year. By using Sub queries and Row_Number
SELECT CompanyName
,yr
,Total
FROM(SELECT CompanyName
, yr
, Total
, ROW_NUMBER() OVER(PARTITION BY yr ORDER BY yr, Total DESC) AS RowNumber
FROM(SELECT DISTINCT CompanyName
, YEAR(O.OrderDate) AS yr
, SUM(OD.Quantity) OVER(PARTITION BY CompanyName
, YEAR(O.OrderDate)) As Total
FROM Customers C JOIN Orders O
ON C.CustomerID = O.CustomerID JOIN [Order Details] OD
ON O.OrderID = OD.OrderID) Tr)Tr2
Where RowNumber <= 1
Three steps: first sum quantities grouped by company and year, then order the results by quantity, then filter first row by group only.
; WITH sums as (
SELECT C.Companyname, YEAR(O.OrderDate) AS Year, sum (Quantity) Total
FROM Customers C JOIN Orders O
ON C.CustomerID = O.CustomerID JOIN [Order Details] OD
ON O.OrderID = OD.OrderID
group by C.Companyname, YEAR(O.OrderDate)
)
with ordering as (
select Companyname, Year, Total,
row_number() over (partition by CompanyName, Year order by Total desc) rownum
from sums
)
select *
from ordering
where rownum = 1