SQL Query number of units in each category - sql

Need help with an SQL Server query to get below results.
An SQL query to report how many units in each category have been ordered on each day of the week
This is current syntax
SELECT TOP 3 ProductID , ProductQty
FROM OrderDetails
ORDER BY ProductQty DESC;
Here is the image from the database
Any help is much appreciated.
Thank you

We can get your required result by dividing logic into two parts.
Firstly, get the product total count from order details
Secondly, we can assing category-wise rank to products with highest to lowest and pull the first 3 products from each category.
;WITH CTE_Data AS (
SELECT PD.PrdCategory AS PrdCategory, OD.ProductID AS ProductID, SUM(ProductQty) AS ProductQty
FROM OrderDetails OD (NOLOCK)
INNER JOIN ProductDetails PD (NOLOCK) ON OD.ProductID = PD.PrdId
GROUP BY PD.PrdCategory, ProductID
)
, CTE_Data2 AS(
SELECT PrdCategory, ProductID, ProductQty, ROW_NUMBER() OVER(PARTITION BY PrdCategory, ProductID ORDER BY ProductQty DESC) AS RowNo
FROM CTE_Data
)
SELECT ProductID, ProductQty
FROM CTE_Data2
WHERE RowNo IN (1,2,3)

Related

Retrieve top selling products

I would like to receive top 5 selling products in quantity in an order from NorthWind database.
The database has a bunch of tables like Order, OrderDetails, Customers, etc. I was suggested to use Orders Details table below:
Now, I tried the following:
WITH cte AS (
SELECT
OrderID,
Quantity,
ProductID,
ROW_NUMBER() OVER(PARTITION BY OrderID ORDER BY OrderID) as row_num
FROM [Order Details]
)
SELECT *
FROM cte
WHERE row_num IN (SELECT row_num FROM cte WHERE row_num <=10)
ORDER BY OrderID;
Thought this retrieves 10 rows now for each order, they are not ordered based on sold quantities and top sold products are not retrieved properly as for some orders the top sold was beyond the first top 10 rows based on row number I got with ROW_NUMBER() function in SQL.
Edit: For example, if I have 10 orders each with 20 products, then I want top 5 each each product, so the result table should have 50 rows total.
After your edits:
WITH cte AS (
SELECT
OrderID,
Quantity,
ProductID,
ROW_NUMBER() OVER(PARTITION BY OrderID ORDER BY Quantity DESC) as row_num
FROM [Order Details]
)
SELECT *
FROM cte
WHERE row_num <= 5
ORDER BY OrderID;
You should do a
SELECT DISTINCT productid FROM OrderDetails ORDER BY quantity GROUP BY productId LIMIT 5
At least this is the mysql syntax.

Query to return amount of orders of a specific Product ID before and after the current order

I have a table with two columns: OrderId and ProductId, and I need a query which will show these two columns and add another two columns, one which will tell me how many orders of this specific ProductId came before this order (orderIds are created in ascending order), and the other column which will tell me how many orders of this specific item came after this order.
So, for example, this should be the result:
OrderId
ProductId
OrdersBefore
OrdersAfter
245
PR-987
0
2
246
GH-764
0
1
247
NV-102
0
0
248
PR-987
1
1
249
PR-987
2
0
250
GH-764
1
0
I've been fiddling around with adding the table again as a join, tried it as a subquery, and all kinds of other possible methods but it either didn't work at all, went on endlessly or returned wrong results. So I'm basically stuck, and I'd appreciate any help or guidance.
Here are two methods I tried (others I don't remember, being that I tried so many iterations of so many methods), both never finish querying:
1.
SELECT OrderId
, ProductId
, (
SELECT COUNT(*)
FROM Orders o2
WHERE o2.ID < Orders.ID
AND ProductID = ProductID
)
OrdersBefore
, (
SELECT COUNT(*)
FROM Orders o2
WHERE o2.ID > Orders.ID
AND ProductID = ProductID
)
OrdersAfter
FROM Orders
SELECT o.OrderId
, o.ProductId
, COUNT(DISTINCT before.ID) OrdersBefore
, COUNT(DISTINCT after.ID) OrdersAfter
FROM Orders o
LEFT JOIN Orders before ON before.ProductId = o.ProductId
AND before.OrderId< o.OrderId
LEFT JOIN Orders after ON after.ProductId = o.ProductId
AND after.OrderId> o.OrderId
GROUP BY o.OrderId
, o.ProductId
You can use row_number(). Assuming that orderid is how you define the ordering of the orders:
select t.*,
row_number() over (partition by productid order by orderid) - 1 as orders_before,
row_number() over (partition by productid order by orderid desc) - 1 as orders_after
from t;
You can use a windowed COUNT OVER
SELECT
OrderId,
ProductId,
OrdersBefore = COUNT(*) OVER (PARTITION BY t.ProductId ORDER BY t.OrderId ROWS UNBOUNDED PRECEDING),
OrdersAfter = COUNT(*) OVER (PARTITION BY t.ProductId ORDER BY t.OrderId ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM YourTable t

Select top 10 products sold in each year

I have two tables :
Sales
columns: (Sales_id, Date , Customer_id, Product_id, Purchase_amount):
Product
columns: ( Product_id, Product_Name, Brand_id,Brand_name)
I have to write a query to find the top 10 products sold every year. The query I have right now is :
WITH PH AS
(SELECT P.Product_Name, LEFT(S.Date,4) "SYEAR", COUNT(S.Product_id) "Product Count"
FROM Sales S LEFT JOIN Product P
ON S.Product_Id=P.Product_Id
GROUP BY P.Product_Name, LEFT(S.Date,4)
SELECT P.Product_Name, "SYEAR", "Product_Count"
FROM (SELECT P.Product_Name, "SYEAR", "Product_Count",
RANK OVER (PARTITION BY "SYEAR" ORDER BY "Product_Count" DESC) "TEMP"
)
WHERE "TEMP"<=10
This doesn't seem like the most optimized query. Can you please help me with that? Can there be an alternate version to obtain the required result?
Notes
The main reason for the repetition of the code is to enable grouping by the year. There's no field for the year in the given table.
The date format is: YYYYMMDD (example: 20200630)
Any help will be appreciated. Thanks in advance
You can combine the window functions with the aggregation:
SELECT PY.*
FROM (SELECT P.Product_Name, LEFT(S.Date,4) AS YEAR, COUNT(*) AS CNT,
RANK() OVER (PARTITION BY LEFT(S.Date, 4) ORDER BY COUNT(*) DESC) AS SEQNUM
FROM Sales S LEFT JOIN
Product P
ON S.Product_Id = P.Product_Id
GROUP BY P.Product_Name, LEFT(S.Date, 4)
) PY
WHERE SEQNUM <= 10;
From a performance perspective, this probably generates an execution plan very similar to your query. It is however simpler to follow.

Fixing Nested aggregated function

I am trying to display the productid for the product that has been sold the most (i.e, that has been sold in the highest quantity)
I have tried multiple different versions of code but every time it says cannot nest aggregated operations
SELECT productid
FROM soldvia
GROUP BY productid
WHERE productid IN (SELECT MAX(SUM(noofitems)) FROM soldvia GROUP BY productid);
I expect the output to be
PRODUCTID
3x3
4x4
You can't nest aggregations.
Use ORDER BY with TOP :
SELECT TOP 1 productid
FROM soldvia
GROUP BY productid
ORDER BY SUM(noofitems) DESC
Please try below query for your exact answer.
select productid, sum(noofitems) as max_sold,
convert(varchar,productid) +' x '+ convert(varchar,sum(noofitems)) as
output_sold from soldvia group by productid order by sum(noofitems) desc
Output will be
ProductId NoOfItemSold Output_Sold
1 7 1x7
2 4 2x4
3 1 3x1
In Teradata, you can use the qualify clause:
SELECT productid
FROM soldvia
GROUP BY productid
QUALIFY ROW_NUMBER() OVER (ORDER BY COUNT(*) DESC) = 1;
This is handy. You can get duplicates by changing ROW_NUMBER() to RANK(). Actually, RANK() is more consistent with the code in your question.
The answer by #forpas is probably the way to go but this one is a little closer to yours:
SELECT productid
FROM soldvia
GROUP BY productid
HAVING SUM(noofitems) = (
SELECT MAX(items)
FROM (
SELECT SUM(noofitems) AS items
FROM soldvia
GROUP BY productid
) x
)

Trying to Find MAX value of a SUM query in SQL

I have 2 tables:
Product(ProductID, ProductName, ProductPrice, VendorID, CategoryID)
SoldVia(ProductID, TID, NoOfItems)
I need to display the productID for the product that has been sold in the highest quantity. I can easily come up with the list sorted in ascending order with this query:
SELECT distinct productid, sum(noofitems)
From soldvia
Group By productid
Order By sum(noofitems) DESC
By question is, how do I only show the top value of the list, using the MAX function? I can't use LIMIT or TOP for this assignment, but whenever I use MAX, I run into various issues with aggregates.
After I'm done with that, how do I show the product name for the best selling product?
Thank you!
Give this a try:
SELECT prd.ProductId
FROM Product prd
INNER JOIN SoldVia sld ON prd.ProductId = sld.ProductId
WHERE prd.NoOfItems = (SELECT MAX(NoOfItems) FROM SoldVia) -- Check for item that has max # items sold
This will return the items with the highest aggregate value of NoOfItems
Update
I didn't know you were on Teradata. That makes life much much easier :)
SELECT ProductName
FROM Product prd
INNER JOIN (
SELECT ProductId, SUM(NoOfItems) AS TotalItemsSold
FROM SoldVia
GROUP BY ProductId
QUALIFY RANK() OVER(ORDER BY TotalItemsSold DESC) = 1 -- Only return ProductId(s) with largest TotalItemsSold value (includes ties)
) agg ON prd.ProductId = agg.ProductId -- Get aggregate # items sold (if any)
This will only return rows if there are matching rows in both tables.
This is a little more simple, but I think this still should work for you
select productid, max(itemsum)
from
(SELECT productid, sum(noofitems) as itemsum
FROM soldvia
group by productid)
;
Based on #ravioli's answer, without a subselect.
From logic I would prefer the subselect (early reducing the number of rows), but the explain shows, that 1 more step is used with the subselect. I expect it to be different for larger number of rows.
select
S.ProductID
, P.ProductName
, sum(NoOfItems) as TotalItemsSold
from SoldVia as S
inner join Product as P
on S.ProductID = P.ProductID
group by S.ProductID, P.ProductName
QUALIFY RANK() OVER(ORDER BY TotalItemsSold DESC) = 1 -- Only return ProductId(s) with largest TotalItemsSold
;