How can I sum cost items, grouped by invoice? - sql

I have two SQL Server tables below:
Invoice
InvoiceId Amount [Date]
1 10 2015-05-28 21:47:50.000
2 20 2015-05-28 21:47:50.000
3 25 2015-05-28 23:25:50.000
InvoiceItem
Id InvoiceId Cost
1 1 8
2 1 3
3 1 7
4 2 15
5 2 17
6 3 20
7 3 22
Now I want to JOIN these two tables ON InvoiceId and retrieve the following:
COUNT of DISTINCT InvoiceId from Invoice table AS [Count]
SUM of Amount from Invoice table AS Amount
SUM of Cost from InvoiceItem table AS Cost
HOUR part of [Date]
and GROUP them BY HOUR part of [Date].
Desired Output wil be:
[Count] Amount Cost HourOfDay
2 30 50 22
1 25 42 23
How can I do this?

one approach is to use a derived table:
SELECT CAST([Date] AS DATE) AS [Date],
DATEPART(HOUR,i.[Date]) AS HourOfDay,
COUNT(i.InvoiceId) AS NumberOfInvoices,
SUM(i.Amount) AS Amount,
SUM(it.Cost) AS Cost
FROM invoice i
INNER JOIN
(SELECT InvoiceId, SUM(Cost) AS Cost
FROM invoiceitem
GROUP BY InvoiceId) it ON i.InvoiceId = it.InvoiceId
GROUP BY [Date],DATEPART(HOUR,i.[Date])
or a CTE (Common Table Expression)
WITH InvoiceCosts (InvoiceId, Cost)
AS
(
SELECT InvoiceId, SUM(Cost) AS Cost
FROM invoiceitem
GROUP BY InvoiceId
)
SELECT CAST([Date] AS DATE) AS [Date],
DATEPART(HOUR,i.[Date]) AS HourOfDay,
COUNT(i.InvoiceId) AS NumberOfInvoices,
SUM(i.Amount) AS Amount,
SUM(ic.Cost) AS Cost
FROM invoice i
INNER JOIN
InvoiceCosts ic ON i.InvoiceId = ic.InvoiceId
GROUP BY [Date],DATEPART(HOUR,i.[Date])

SELECT COUNT (DISTINCT inv.InvoiceId) [Count],
SUM (Amount) Amount,
SUM (Cost) Cost,
datepart(HOUR, inv.[Date]) HourOfDay
FROM Invoice inv
INNER JOIN InvoiceItem itm
ON inv.InvoiceId = itm.InvoiceId
GROUP BY datepart(HOUR, inv.[Date]);

Related

Find the second top selling product in terms of sales and quantity

There are two Tables - orders and item_line
orders
order_id
created_at
total_amount
123
2022-11-11 13:40:50
450.00
124
2022-10-30 00:40:50
1500.00
item_line
order_id
product_id
product_name
quantity
unit_price
123
a1b
milo
4
100.00
123
c2d
coke
5
10.00
124
c2d
coke
150
10.00
The question is:
Find the second top selling product in terms of sales and quantity in the current year sold between 6PM to 9PM.
My Take on This is -
SELECT * FROM (
SELECT i.product_name,
SUM(o.total_amount)sales,
SUM(i.quantity)total_qty,
ROW_NUMBER() OVER (ORDER BY SUM(o.total_amount) DESC,SUM(i.quantity)total_qty DESC) AS rn
FROM item_line i
WHERE o.created_at BETWEEN 18:00:00 AND 21:00:00
JOIN orders o on o.order_id = i.order_id
GROUP BY i.product_name ) temp
WHERE rn = 2;
But it's not correct. What wrong I am doing?
SELECT * FROM (
SELECT i.product_name,SUM(o.total_amount)AS 'Net Sales',
ROW_NUMBER() OVER(ORDER BY SUM(o.total_amount) DESC) AS rn
FROM item_line i
JOIN orders o on o.order_id = i.order_id
WHERE DATEPART(HOUR,o.created_at) BETWEEN 18 AND 21
GROUP BY i.product_name) temp
WHERE rn =2;
-- In terms of total quantity
SELECT * FROM (
SELECT i.product_name,SUM(i.quantity)AS 'Total Quantity',
ROW_NUMBER() OVER(ORDER BY SUM(i.quantity) DESC) AS rn
FROM item_line i
JOIN orders o on o.order_id = i.order_id
WHERE DATEPART(HOUR,o.created_at) BETWEEN 18 AND 21
GROUP BY i.product_name) temp
WHERE rn =2;
select o.order_id, sum(quantity), total_amount from orders [o]
inner join item_line[i] on o.order_id = i.order_id
group by o.order_id, total_amount order by total_amount desc, sum(quantity) desc
OFFSET 1 ROWS
FETCH NEXT 1 ROWS ONLY;
you can add target date time in filter

Number of Customer Purchases in Their First Month

I have a list of customer orders. I can easily calculate the month and year of first purchase for each customer (e.g. customer 1 had their first purchase in Sept 2021, customer 2 had their first purchase in Oct 2021, etc.). What I want to add is an additional column that counts the number of purchases a customer made in their first month.
Existing data table (Orders):
OrderId
CustomerId
OrderDate
1
1
9/15/2021
2
1
10/15/2021
3
1
11/1/2021
4
2
10/1/2021
5
2
10/6/2021
6
2
10/7/2021
7
2
11/9/2021
8
3
11/15/2021
Desired output:
CustomerId
FirstOrderMonth
FirstOrderYear
FirstMonthPurchaseCount
1
9
2021
1
2
10
2021
3
3
11
2021
1
I was thinking something like this for the first three columns:
SELECT o.CustomerId,
MONTH(MIN(o.OrderDate)) as FirstOrderMonth,
YEAR(MIN(o.OrderDate)) as FirstOrderYear
FROM Orders o
GROUP BY o.CustomerId
I am not sure how to approach the final column and was hoping for some help.
Aggregate by the customer's id, the year and the month of the order and use window functions to get the year and month of the 1st order and the count of that 1st month:
SELECT DISTINCT CustomerId,
FIRST_VALUE(MONTH(OrderDate)) OVER (PARTITION BY CustomerId ORDER BY YEAR(OrderDate), MONTH(OrderDate)) FirstOrderMonth,
MIN(YEAR(OrderDate)) OVER (PARTITION BY CustomerId) FirstOrderYear,
FIRST_VALUE(COUNT(*)) OVER (PARTITION BY CustomerId ORDER BY YEAR(OrderDate), MONTH(OrderDate)) FirstMonthPurchaseCount
FROM Orders
GROUP BY CustomerId, YEAR(OrderDate), MONTH(OrderDate);
See the demo.
You may use the RANK() function to identify the first month purchases for each user as the following:
Select D.CustomerId, MONTH(OrderDate) FirstOrderMonth,
YEAR(OrderDate) FirstOrderYear, COUNT(*) FirstMonthPurchaseCount
From
(
Select *, RANK() Over (Partition By CustomerId Order By YEAR(OrderDate), MONTH(OrderDate)) rnk
From table_name
) D
Where D.rnk = 1
Group By D.CustomerId, MONTH(OrderDate), YEAR(OrderDate)
See a demo.
If you want to find second, third ... month purchases, you may use the DENSE_RANK() function instead of RANK() and change the value in the where clause to the required month order.
select CustomerId
,min(month(OrderDate)) as FirstOrderMonth
,min(year(OrderDate)) as FirstOrderYear
,count(first_month_flag) as FirstMonthPurchaseCount
from (select *
,case when month(OrderDate) = month(min(OrderDate) over(partition by CustomerId)) then 1 end as first_month_flag
from Orders) Orders
group by CustomerId
CustomerId
FirstOrderMonth
FirstOrderYear
FirstMonthPurchaseCount
1
9
2021
1
2
10
2021
3
3
11
2021
1
Fiddle

MS SQL | How to query a filtered column (WHERE) with non filtered data

I have a problem solving an MS SQL query.
in summary, the query should get the date column as two columns, year and month, the count of other columns, the sum of total of a column, and a filtered sum column.
what I struggled with was adding the filtered sum column.
a sample data, Test:
customerID, 1,2,3,4...
InvoiceID, 1234551, 1234552...
ProductID, A, B, C...
Date, Datetime
Income, int
customerID
InvoiceID
ProductID
Date
Income
1
1234551
A
01/01/2015
300
2
1234552
B
02/01/2016
300
I have a solution, but I am sure there is a more simple solution.
WITH CTE_1 AS
(
SELECT Date,
COUNT(DISTINCT Test.customerID) AS customers,
COUNT(Test.InvoiceID) AS Invoices,
COUNT(Test.ProductID) AS Products,
Sum(Income) AS Total_Income,
ISNULL((SELECT Sum(Income) AS Income_A FROM Test ts WHERE ProductID = 'A' AND ts.Date = Test.Date),0) AS Total_Income_A
FROM Test
GROUP BY Test.Date
)
SELECT YEAR(Date) AS Year,
MONTH(Date) AS Month,
Sum(customers) AS customers,
Sum(Invoices) AS Invoices,
Sum(Products) AS Products,
Sum(Total_Income) AS Total_Income,
Sum(Total_Income_A) AS Total_Income_A
FROM CTE_1
GROUP BY YEAR(Date), MONTH(Date)
ORDER BY YEAR(Date), MONTH(Date)
to produce:
Year, 2015, 2016...
Month, 1, 2, ...
customers, int
Invoices, int
Products, int
Total_Income, int
Total_Income_A, int
Year
Month
customers
Invoices
Products
Total_Income
Total_Income_A
2015
1
3
4
4
1600
600
2015
2
1
1
1
1200
0
Thanks!
Nir
You can directly apply a Conditional Aggregation such as
SELECT YEAR(Date) AS Year,
MONTH(Date) AS Month,
COUNT(DISTINCT customerID) AS customers,
COUNT(DISTINCT InvoiceID) AS Invoices,
COUNT(ProductID) AS Products,
SUM(Income) AS Total_Income,
ISNULL(SUM(CASE WHEN ProductID = 'A' THEN Income END),0) AS Total_Income_A
FROM Test
GROUP BY YEAR(Date), MONTH(Date)
ORDER BY YEAR(Date), MONTH(Date)
Demo

SQL | How to sum over partition group of 3 items?

I'm trying to get the percent of a day's revenue for top 3 product categories but struggling with the percentage. I have already the revenue per product ranked 1 to 3 but cant wrap my head on how to get the percentage.
Any pointers will be appreciated.
SELECT * FROM (
SELECT date,
category_name,
revenue,
row_number() OVER(PARTITION BY DATE(date) ORDER BY revenue DESC) AS category_rank,
(revenue / (select sum(revenue) from a group by 1)) * 100 percentage AS percentage_of_daily_total -- this is the wrong one
FROM (
SELECT DATE(orders.order_timestamp) AS date,
products.product_cat AS category_name,
ROUND(SUM(payments.payment)) AS revenue
FROM table1.orders orders
JOIN table1.t_payments payments ON orders.order_id = payments.order_id
JOIN table1.t_items items ON orders.order_id = items.order_id
JOIN table1.t_products products ON items.product_id = products.product_id
GROUP BY 1 ,2) a) b
WHERE category_rank <= 3;
Sample data is as follow: date, category_name, revenue, category_rank
2016-10-05 jeans 20 1
2016-10-05 shirts 15 2
2016-10-05 shoes 10 3
2016-10-06 skirts 50 1
2016-10-06 sports_wear 30 2
2016-10-06 accesories 10 3
Desired outcome:date, category_name, revenue, category_rank, percentage_of_daily_total
2016-10-05 jeans 30 1 50
2016-10-05 shirts 20 2 33
2016-10-05 shoes 10 3 17
2016-10-06 skirts 20 1 50
2016-10-06 sports_wear 16 2 40
2016-10-06 accessories 4 3 10
Use CTEs
WITH a AS (
SELECT DATE(orders.order_timestamp) AS date,
products.product_cat AS category_name,
ROUND(SUM(payments.payment)) AS revenue
FROM table1.orders orders
JOIN table1.t_payments payments ON orders.order_id = payments.order_id
JOIN table1.t_items items ON orders.order_id = items.order_id
JOIN table1.t_products products ON items.product_id = products.product_id
GROUP BY 1 ,2
)
SELECT * FROM (
SELECT a.date,
a.category_name,
a.revenue,
row_number() OVER(PARTITION BY DATE(a.date) ORDER BY a.revenue DESC) AS category_rank,
(a.revenue / b.revenue_sum) * 100 percentage AS percentage_of_daily_total
FROM a
JOIN (SELECT date, sum(revenue) AS revenue_sum FROM a GROUP BY 1) AS b
ON a.date = b.date)
WHERE category_rank <= 3;
Your original query is very close. Calculate the percent in the OUTERMOST sql. So drop your percentage calc and then in the outer Select:
Select *, 100*revenue/(sum(revenue) Over (Partition by Date)) as percentage_of_daily_total
Remember that by the time you get to calculating the windowing functions the Where clause has already been executed and you're down to 3 records per day so any total will only be based on the top 3.

calculating percentage of sales profit in SQL

Product Group Product ID Sales Profit
A 6797 1,000 200
A 6745 500 90
B 1278 200 60
B 1245 1,500 350
C 7890 650 80
D 4587 350 50
Q1). Filter out product IDs that contribute to top 80% of the total profit of their respective group.
Not sure what rdbms you are using you can get the output in SQL server in this way. You can get profit for a group and use aggregate function to compare and filter the rows.
select 'A' as Product_group, 6797 as ProductID, 1000 as Sales , 200 as Profit into #temp1 union all
select 'A' as Product_group, 6745 as ProductID, 500 as Sales , 90 as Profit union all
select 'B' as Product_group, 1278 as ProductID, 200 as Sales , 60 as Profit union all
select 'B' as Product_group, 1245 as ProductID, 1500 as Sales , 350 as Profit union all
select 'C' as Product_group, 7890 as ProductID, 650 as Sales , 80 as Profit union all
select 'D' as Product_group, 4587 as ProductID, 350 as Sales , 50 as Profit
select t.Product_group, t.ProductID, sum(t.sales) totalsles, sum(t.profit) totalProfit, sum(Profit_grp) Groupprofit from #temp1 t
join (select Product_group, sum(sales) totalsles_group, sum(profit) Profit_grp from #temp1 t1 group by Product_group) t1 on t1.Product_group = t.Product_group
group by t.Product_group, t.ProductID
having sum(t.profit) *1.0/ sum(t1.Profit_grp) *1.0 >= 0.8
Output: I added group profit just to compare. You can remove the aggregate and add in group by if you would like
Product_group ProductID totalsles totalProfit Groupprofit
B 1245 1500 350 410
C 7890 650 80 80
D 4587 350 50 50
I think this may works out:
with CTE as(
select [Product Group], sum([Sales]) as Tolsum from Table
group by [Product Group]
select prod.*,
sum(prod.[Profit]/cte.[Tolsum]) over (Partition by prod.[Product Group] Order by prod.[Product ID]) as contribution
from CTE cte
inner join
Table prod
on
cte.[Product Group] = prod.[Product Group]
having
sum(prod.[Profit]/cte.[Tolsum]) over (Partition by prod.[Product Group] Order by prod.[Product ID]) < 0.8