T-SQL query to summarize sales for ALL YEARS: with total per month per year, and cumulative monthly amounts - sql

I need to create a Sales Report that shows all years sales per month, and cumulative sales.
The database table is simple:
Transactions
(
ID INT,
TransactionDate DATETIME,
SalesAmount MONEY
)
I want the results to look similar to ExcelSheet below (I am showing only 2017/2018 amounts, but actual query needs to return results for all available years according to TransactionDate)

This is aggregation and a cumulative sum:
select year(TransactionDate), month(TransactionDate),
sum(SalesAmount),
sum(sum(SalesAmount)) over (partition by year(TransactionDate) order by min(TransactionDate))
from Transactions
group by year(TransactionDate), month(TransactionDate)
order by year(TransactionDate), month(TransactionDate);

Try it:
With Q
as
(
Select DatePart(yyyy,TransactionDate) 'Year',DatePart(m,TransactionDate) 'Month', sum(SalesAmount) 'Sales'
From Transactions
Group by DatePart(yyyy,TransactionDate),DatePart(m,TransactionDate)
)
Select q.Year,q.Month,q.sales,( Select sum(q1.Sales)
From Q q1
Where q1.Year=q.Year
And q1.Month <= q.Month
) 'Cumulative Sale'
From Q q
Order by q.Year,q.Month

Try this:
with cte as
(
select year(TransactionDate) as Year, month(TransactionDate) as Month, SalesAmount
)
select a.Year, a.Month, a.SalesAmount, sum(b.SalesAmount) as cumulativeSalesAmount
from Transactions a inner join Transactions b on a.STORE_ID = b.STORE_ID and a.Year = b.Year and a.Month >= b.Month
group by a.Year, a.Month
order by 1, 2

Related

SQLite Getting multiple results with LIMIT 1

I have the following problem.
Part of a task is to determine the visitor(s) with the most money spent between 2000 and 2020.
It just looks like this.
SELECT UserEMail FROM Visitor
JOIN Ticket ON Visitor.UserEMail = Ticket.VisitorUserEMail
where Ticket.Date> date('2000-01-01') AND Ticket.Date < date ('2020-12-31')
Group by Ticket.VisitorUserEMail
order by SUM(Price) DESC;
Is it possible to output more than one person if both have spent the same amount?
Use rank():
SELECT VisitorUserEMail
FROM (SELECT VisitorUserEMail, SUM(PRICE) as sum_price,
RANK() OVER (ORDER BY SUM(Price) DESC) as seqnum
FROM Ticket t
WHERE t.Date >= date('2000-01-01') AND Ticket.Date <= date('2021-01-01')
GROUP BY t.VisitorUserEMail
) t
WHERE seqnum = 1;
Note: You don't need the JOIN, assuming that ticket buyers are actually visitors. If that assumption is not true, then use the JOIN.
Use a CTE that returns all the total prices for each email and with NOT EXISTS select the rows with the top total price:
WITH cte AS (
SELECT VisitorUserEMail, SUM(Price) SumPrice
FROM Ticket
WHERE Date >= '2000-01-01' AND Date <= '2020-12-31'
GROUP BY VisitorUserEMail
)
SELECT c.VisitorUserEMail
FROM cte c
WHERE NOT EXISTS (
SELECT 1 FROM cte
WHERE SumPrice > c.SumPrice
)
or:
WITH cte AS (
SELECT VisitorUserEMail, SUM(Price) SumPrice
FROM Ticket
WHERE Date >= '2000-01-01' AND Date <= '2020-12-31'
GROUP BY VisitorUserEMail
)
SELECT VisitorUserEMail
FROM cte
WHERE SumPrice = (SELECT MAX(SumPrice) FROM cte)
Note that you don't need the function date() because the result of date('2000-01-01') is '2000-01-01'.
Also I think that the conditions in the WHERE clause should include the =, right?

Group by join date + add month in where sql

I have the following table:
PERSON
ID
Name
date_created
date_left
What I want is a list of all months and the amount of users joined and the amount of users that left.
I already have the following query: it returns the amount of new users that joined in the month that I pass:
select MONTH(date_created) 'Month', YEAR(date_created) 'Year', count(*) as 'New Users'
from person p
where YEAR(date_created) = 2018 and MONTH( p.date_created) = 5
group by MONTH(date_created), YEAR(date_created)
It returns what I want:
How would I edit this to include a year report and add the column 'Users left' next to the 'new users' one?
My result would be:
MONTH YEAR NEW USERS USERS LEFT
1 2019 10 5
I would "unpivot" the data using cross apply:
select v.[year], v.[month], sum(v.iscreated) as num_created,
sum(v.isleft) as num_left
from person p cross apply
(values (year(p.date_created), month(p.date_created), 1, 0),
(year(p.date_left), month(p.date_left), 0, 1)
) v([year], [month], iscreated, isleft)
group by v.[year], v.[month]
order by v.[year], v.[month];
The straight-forward approach is probably to full outer join all entries and all leaves. SQL Server makes this a bit awkward by not featuring USING, so we must use ON and COALESCE on month and year instead.
select
coalesce(pin.year, pout.year) as year,
coalesce(pin.month, pout.month) as month,
coalesce(pin.cnt, 0) as count_in,
coalesce(pout.cnt, 0) as count_out
from
(
select year(date_created) as year, month(date_created) as month, count(*) as cnt
from person
group by year(date_created), month(date_created)
) pin
full outer join
(
select year(date_left) as year, month(date_left) as month, count(*) as cnt
from person
group by month(date_left), year(date_left)
) pout on pout.year = pin.year and pout.month = pin.month
order by year, month;
Maybe you could do it with a SubSelect? Tried it right now with ORACLE Syntax, I'm not sure if it works in SQL-Server.
SELECT * FROM
(
select MONTH(date_created) 'Month_C', YEAR(date_created) 'Year_C', count(*) as 'New Users'
from person p
where YEAR(date_created) = 2018 and MONTH( p.date_created) = 5
group by MONTH(date_created), YEAR(date_created)
)created_user,
(
select MONTH(date_left) 'Month_L', YEAR(date_left) 'Year_L', count(*) as 'New Users'
from person p
where YEAR(date_left) = 2018 and MONTH( p.date_left) = 5
group by MONTH(date_left), YEAR(date_left)
) left_user
where created_user.Year_C = left_user.Year_L
and created_user.Month_C = left_user.Month_L

SQL YTD and last year YTD on complete data

I need to calculate YTD and last year YTD on a table [SQL Server 2012]. Below is the query I tried. Its getting doubled and tripled for some cases.
SELECT SUM(A.RevisionNumber)YTD,SUM(P.RevisionNumber)LY_YTD,B.OrderDateM,B.OrderDateY
FROM
(select MONTH(OrderDate)OrderDateM,YEAR(OrderDate)OrderDateY from sales.SalesOrderHeader B
group by MONTH(OrderDate),YEAR(OrderDate))B
LEFT JOIN
(select SUM(RevisionNumber)RevisionNumber,MONTH(OrderDate)OrderDateM,YEAR(OrderDate)OrderDateY
from sales.SalesOrderHeader
group by MONTH(OrderDate),YEAR(OrderDate))A
ON A.OrderDateM<=B.OrderDateM AND A.OrderDateY=B.OrderDateY
LEFT JOIN
(select SUM(RevisionNumber)RevisionNumber,MONTH(OrderDate)OrderDateM,YEAR(OrderDate)OrderDateY
from sales.SalesOrderHeader
group by MONTH(OrderDate),YEAR(OrderDate))P
ON P.OrderDateM<=B.OrderDateM AND P.OrderDateY=B.OrderDateY-1
GROUP BY B.OrderDateM,B.OrderDateY
ORDER BY B.OrderDateY,B.OrderDateM
You can use windowing function as below:
;With cte as (
Select Sum(RevisionNumber) As SM_RevisionNumber, Month(OrderDate) as OrderM,
Year(OrderDate) as OrderY
From Sales.SalesOrderHeader
Group by Month(OrderDate), Year(OrderDate)
), cte2 as (
Select YTD = Sum(SM_RevisionNumber) over (partition by OrderY order by OrderM),
OrderM, OrderY, RowN = Row_Number() over(order by OrderY, OrderM)
from cte
)
Select YTD, LY_YTD = lag(YTD, 12, null) over(Order by RowN), OrderM, ORderY
from cte2
But this solution assumes we have atleast one entry for each month and year.

display only specific rows in a column with a group by

I'm somewhat new to Oracle SQL and can't figure this out. I want to display the rows with the high value in the third column. Here is my table i'm working with:
theyear custseg sales
2010 Corporate 573637.62
2010 Home Office 515314.98
2010 Small Biz 390361.94
2010 Consumer 383825.67
2011 Corporate 731208
2011 Home Office 521274.34
2011 Consumer 390967.03
2011 Small Biz 273264.81
2012 Corporate 823861.38
2012 Consumer 480082.9
2012 Home Office 478106.93
I want the highest value grouped by year. If I do a group by with just the year I get the answer somewhat, but I can't include/display customer segment (ugh). It just displays the year and the max sales. When I include the customer segment it gives me that table, which displays all the sales - not what i'm looking for. I simply want the rows that contain the MAX sales given the year (theyear) AND the customer segment (custseg). For what it's worth here is the code I used to create the above:
select theyear, custseg, max(totalsales) sales from (
select custseg, extract(year from ordshipdate) theyear, sum(ordsales) TotalSales from customers, orderdet
where customers.custid = orderdet.custid
group by custseg, extract(year from ordshipdate)
order by sum(ordsales) desc)
group by theyear, custseg
order by theyear, max(totalsales) desc;
Assuming all fields are in the customer table as described in the question, the following query would do what you want:
select c.theyear, c.custseg, c.sales
from
customer c inner join
(
select theyear, max(sales) as max_sales_in_year
from customer
group by theyear
) maxvalues
on (
c.year = maxvalues.theyear and
c.sales = maxvalues.max_sales_in_year
);
Swap the inner join with a right outer join if you do not plan to settle ties arbitrarily.
I would use ROW_NUMBER():
SELECT theyear, custseg, totalsales FROM
(
select theyear, custseg, totalsales,
ROW_NUMBER OVER(PARTITION BY theyear ORDER BY totalsales DESC) rn
from
(
select custseg, extract(year from ordshipdate) theyear, sum(ordsales) TotalSales
from customers, orderdet
where customers.custid = orderdet.custid
group by custseg, extract(year from ordshipdate)
) a
) b
WHERE rn = 1;
BTW, the query above will look more readable when using CTE:
WITH a AS(
select custseg, extract(year from ordshipdate) theyear, sum(ordsales) TotalSales
from customers, orderdet
where customers.custid = orderdet.custid
group by custseg, extract(year from ordshipdate)),
b AS (
select theyear, custseg, totalsales,
ROW_NUMBER OVER(PARTITION BY theyear ORDER BY totalsales DESC) rn
FROM a)
SELECT theyear, custseg, totalsales
FROM b;

How to return the most ordered item for each month

I am trying to return the most ordered product per month, of the year 2007. I would like to see the name of the product, how many of them where ordered that month, and the month. I am using the AdventureWorks2012 database. I have tried a few different ways but each time multiple product orders are returned for the same month, instead of the one product that had the most order quantity that month. Sorry if this is not clear. I am trying to test myself so I make up my own questions and try to answer them. If anyone knows a site that have questions and answers like this so I can verify that would be super helpful! Thanks for any help. Here is the farthest I have been able to get with the query.
WITH Ord2007Sum
AS (SELECT sum(od.orderqty) AS sorder,
od.productid,
oh.orderdate,
od.SalesOrderID
FROM Sales.SalesOrderDetail AS od
INNER JOIN
sales.SalesOrderHeader AS oh
ON od.SalesOrderID = oh.SalesOrderID
WHERE year(oh.OrderDate) = 2007
GROUP BY ProductID, oh.OrderDate, od.SalesOrderID)
SELECT max(sorder),
s.productid,
month(h.orderdate) AS morder --, s.salesorderid
FROM Ord2007Sum AS s
INNER JOIN
sales.SalesOrderheader AS h
ON s.OrderDate = h.OrderDate
GROUP BY s.ProductID, month(h.orderdate)
ORDER BY morder;
Make a CTE that groups our products by month and creates a sum
;WITH OrderRows AS
(
SELECT
od.ProductId,
MONTH(oh.OrderDate) SalesMonth,
SUM(od.orderqty) OVER (PARTITION BY od.ProductId, MONTH(oh.OrderDate) ORDER BY oh.OrderDate) ProdMonthSum
FROM SalesOrderDetail AS od
INNER JOIN SalesOrderHeader AS oh
ON od.SalesOrderID = oh.SalesOrderID
WHERE year(oh.OrderDate) = 2007
),
Make a simple numbers table to break out each month of the year
Months AS
(
SELECT 1 AS MonthNum UNION SELECT 2 UNION SELECT 3 UNION SELECT 4
UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8
UNION SELECT 9 UNION SELECT 10 UNION SELECT 11 UNION SELECT 12
)
We query our months table against the data and select the top product for each month based on the sum
SELECT
m.MonthNum,
d.ProductID,
d.ProdMonthSum
FROM Months m
OUTER APPLY
(
SELECT TOP 1 r.ProductID, r.ProdMonthSum
FROM OrderRows r
WHERE r.SalesMonth = m.MonthNum
ORDER BY ProdMonthSum DESC
) d
Your group by statement should not include oh.OrderDate, od.SalesOrderID because this will aggregate your data to the incorrect level. You want the ProductID that was most commonly sold per month so the group by conditions become ProductID, datepart(mm,oh.OrderDate). As Andrew suggested the Row_Number function is useful in this case as it lets you create a key that is ordered by month and sorder and which resets each month. Finally in the outer query limits the results to the first instance (which is the highest quantity)for each month.
WITH Ord2007Sum
AS(
SELECT sum(od.orderqty) AS sorder,
od.productid,
datepart(mm,oh.OrderDate) AS 'Month'
row_number() over (partition by datepart(mm,oh.OrderDate)
Order by datepart(mm,oh.OrderDate)desc, sorder desc) row
FROM Sales.SalesOrderDetail AS od
INNER JOIN
sales.SalesOrderHeader AS oh
ON od.SalesOrderID = oh.SalesOrderID
WHERE datepart(yyyy,oh.OrderDate) = 2007
GROUP BY ProductID, datepart(mm,oh.OrderDate)
)
SELECT productid,
sorder,
[month]
FROM Ord2007Sum
WHERE row =1