T-SQL query to summarize total per month per year, and cumulative amounts to date - sql

I have a database table that captures every Sales Transaction:
Transactions
(
ID INT,
TransactionDate DATETIME,
SalesAmount MONEY
)
I want to write a T-SQL query which returns a report (snapshot sample below). First column it lists the month, next column Total-Sales per month within year, and last column cumulative amount of that year up to this month. Only for year of 2018.
Any thoughts or solutions? Thank you.

Try this:
;with cte as
(
Select
YEAR(TransactionDate) as [Year],
MONTH(TransactionDate) as [Month],
SUM (SalesAmount) as [MonthlySales],
DATEPART(m, TransactionDate) as [MonthNumber]
from Transactions
group by YEAR(TransactionDate), MONTH(TransactionDate)
)
select
a.[Month], a.MonthlySales as [MonthlySales 2018], SUM(b.MonthlySales) as [Cumulative 2018]
from cte a inner join cte b on a.MonthNumber >= b.MonthNumber
WHERE (a.[Year]) = 2018 AND (b.[Year]) = 2018
group by a.[Month], a.MonthlySales
ORDER by a.[Month]

Try this one:
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,( 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

You would use aggregation and window functions:
select datename(month, transaction_date) as mon,
sum(salesAmount) as monthly_sales,
sum(salesAumount) over (order by min(transaction_date)) as running_amount
from transactions t
where t.transaction_date >= '2018-01-01' and
t.transaction_date < '2019-01-01'
group by datename(month, transaction_date)
order by min(transaction_date);

Related

Using CTE to create pivot table

I've a table:
Task:
Create a pivot table using CTE.
Count the orders placed for each month for several years: from 2011 to 2013. The final table should include four fields: invoice_month, year_2011, year_2012, year_2013. The month field must store the month as a number between 1 and 12.
If no orders were placed in any month, the number of that month should still be included in the table.
I was able to solve this task with this query:
WITH year11
AS (
SELECT EXTRACT(MONTH FROM invoice.invoice_date::TIMESTAMP) AS invoice_month
,COUNT(*) AS orders
FROM invoice
WHERE EXTRACT(YEAR FROM invoice.invoice_date::TIMESTAMP) = 2011
GROUP BY invoice_month
)
,year12
AS (
SELECT EXTRACT(MONTH FROM invoice.invoice_date::TIMESTAMP) AS invoice_month
,COUNT(*) AS orders
FROM invoice
WHERE EXTRACT(YEAR FROM invoice.invoice_date::TIMESTAMP) = 2012
GROUP BY invoice_month
)
,year13
AS (
SELECT EXTRACT(MONTH FROM invoice.invoice_date::TIMESTAMP) AS invoice_month
,COUNT(*) AS orders
FROM invoice
WHERE EXTRACT(YEAR FROM invoice.invoice_date::TIMESTAMP) = 2013
GROUP BY invoice_month
)
SELECT year11.invoice_month
,year11.orders AS year_2011
,year12.orders AS year_2012
,year13.orders AS year_2013
FROM year11
INNER JOIN year12 ON year11.invoice_month = year12.invoice_month
INNER JOIN year13 ON year11.invoice_month = year13.invoice_month
But this request looks too big (or not?).
What can I improve (should I?)using CTE in my query?
Other tools to solve this task fast and beautiful?
I find using filtered aggregation a lot easier to generate pivot tables:
SELECT extract(month from inv.invoice_date) AS invoice_month
COUNT(*) filter (where extract(year from inv.invoice_date) = 2011) AS orders_2011,
COUNT(*) filter (where extract(year from inv.invoice_date) = 2012) AS orders_2012,
COUNT(*) filter (where extract(year from inv.invoice_date) = 2013) AS orders_2013
FROM invoice inv
WHERE inv.invoice_date >= date '2011-01-01'
AND inv.invoice_date < date '2014-01-01'
GROUP BY invoice_month

Calculating Year over Year Growth using PostgreSQL

I'm using a SQL dataset called Superstore and I want to figure out how to calculate year over year sales growth as a percentage. Here's the code I already have:
SELECT
EXTRACT(year FROM order_date) AS order_year,
SUM(sales) AS total_sales,
FROM orders
WHERE order_date BETWEEN date '2016-01-01' and date '2019-12-01'
GROUP BY 1
ORDER BY 1
Any help would be welcomed. Thank you.
I would aggregate in a subquery, then use a window function:
SELECT *
FROM (SELECT total_sales / lag(total_sales) OVER (ORDER BY order_year) * 100.0,
year
FROM (SELECT CAST (EXTRACT(year FROM order_date) AS integer) AS order_year,
SUM(sales) AS total_sales
FROM orders
GROUP BY 1) AS subq
) AS subq2
WHERE year = 2021;

CTE Rolling 3 Mo Avg

output for all 3 queries
working on an assigment, below is the ask, she has directed us to use a CTE
Write SQL query code used to explore the database tables and write a query that retrieves finance amounts from "FactFinance" in the "AdventureWorksDW2016CTP3" database and returns those amounts, organized by month, and showing a 3-month rolling average
SELECT DateKey,
month(date) as [Month],
year(date) as [Year],
SUM ( ALL Amount) OVER (PARTITION BY Date ORDER BY Date ASC) AS Amount
FROM FactFinance
SELECT
YEAR(Date) AS Year,
MONTH(Date) AS Month,
SUM(Amount) AS Amount
FROM FactFinance
GROUP BY YEAR(Date), MONTH(Date)
ORDER BY Year, Month;
WITH CTE AS (
SELECT
DateKey AS Month,
AVG(Amount) AS AvgAmt
from FactFinance
group by DateKey
)
SELECT
Month,
AvgAmt
FROM CTE
GO
oUTPUT for last query Needing 3 month rolling average
First, you should know the right way to answer this. Assuming you have data for all three months, then:
SELECT YEAR(Date) AS Year,
MONTH(Date) AS Month,
SUM(Amount) AS Amount,
AVG(SUM(Amount)) OVER (ORDER BY MIN(DATE)
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) as rolling_3month_avg
FROM FactFinance
GROUP BY YEAR(Date), MONTH(Date)
ORDER BY Year, Month;
If I were told to use a CTE for this, I might be tempted to do:
WITH unnecessary_cte as (
SELECT YEAR(Date) AS Year,
MONTH(Date) AS Month,
SUM(Amount) AS Amount,
AVG(SUM(Amount)) OVER (ORDER BY MIN(DATE)
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) as rolling_3month_avg
FROM FactFinance
GROUP BY YEAR(Date), MONTH(Date)
)
SELECT *
FROM unnecessary_cte
ORDER BY YEAR, MONTH;
However, we can try to read your instructor's mind and speculate that she wants you to write something like this:
WITH ym as (
SELECT YEAR(Date) AS Year,
MONTH(Date) AS Month,
SUM(Amount) AS Amount
FROM FactFinance
GROUP BY YEAR(Date), MONTH(Date)
)
SELECT ym.*,
(SELECT AVG(Amount)
FROM ym ym2
WHERE 12 * ym2.year + ym2.month
BETWEEN 12 * ym.year + ym.month - 2 AND
12 * ym.year + ym.month
) as rolling_3month_avg
FROM ym
ORDER BY YEAR, MONTH;

Count the number of rows each month in SQL Server

I'm using SQL Server. I've a following table Orders:
Orders (Id, ItemId, CustomerId, Quantity, OrderDateTime)
I want to count the number of orders each month. I've written 2 of the following query.
Query #1:
SELECT
MONTH(OrderDateTime) AS MonthCol,
YEAR(OrderDateTime) AS YearCol,
COUNT(id) AS OrderCount
FROM
Orders
WHERE
OrderDateTime >= '2000' AND OrderDateTime <= '2018'
GROUP BY
YEAR(OrderDateTime), MONTH(OrderDateTime)
ORDER BY
YearCol, MonthCol
Query #2:
SELECT
DATEPART(mm, OrderDateTime) AS Month,
COUNT(*) AS OrderCount
FROM
Orders
WHERE
OrderDateTime >= '2000' AND OrderDateTime <= '2018'
GROUP BY
DATEPART(mm, OrderDateTime)
Issue with both queries is that I'm not getting the columns with 0 orders. How will I get it?
SQL will not give you data about months and year which do not exist as rows. To get 0 order rows you'd need to right join the results with a calendar table containing all needed months and years or you can also use a tally table.
Select T.MonthCol, T.YearCol,OrderCount= COALESCE(OrderCount,0)
from
(
SELECT MONTH(OrderDateTime) AS MonthCol, YEAR(OrderDateTime) AS YearCol, count(id) AS OrderCount
FROM Orders
WHERE OrderDateTime >= '2000' AND OrderDateTime <= '2018'
GROUP BY YEAR(OrderDateTime), MONTH(OrderDateTime)
ORDER BY YearCol, MonthCol)
P
RIGHT JOIN
(
select * from
( values (2000),(2001),(2002),(2003),(2004),(2005),(2006),(2007),(2008))v(YearCol)
cross join
( values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12))u(MonthCol)
)T
on P.MonthCol=T.MonthCol
and P.YearCol=T.YearCol
I would be inclined to use a recursive CTE for this -- this gives pretty easy flexibility on the range you want:
with dates as (
select cast('2000-01-01' as date) dte
union all
select dateadd(month, 1, dte)
from dates
where dte < '2018-12-01'
)
select year(OrderDateTime) AS year,
month(OrderDateTime) AS month,
count(o.id) as OrderCount
from dates left join
orders o
on d.OrderDateTime >= dates.dte and
d.OrderDateTime < dateadd(month, 1, dates.dte)
group by year(OrderDateTime), month(OrderDateTime)
order by year(OrderDateTime), month(OrderDateTime)
option (maxrecursion 0);
Notes:
This uses the JOIN to do the filtering. This makes it safer to change the range that you are looking for.
I find the year() and month() functions to be more convenient datepart().
When using date parts, spell them out. Why waste brain power trying to remember if mm really means months or minutes?
I added an order by. Presumably you want the results in chronological order.

How to get a minimum value and the year it represents

I have a list of orders in a table. these all have a date against them. How do I write a query to return the minimum orders in a year and the associated year.
SELECT Max(YearCounts.[CountForYear]) AS [MinForYear]
FROM ( SELECT COUNT(PK) AS [CountForYear] FROM Orders
WHERE DATEPART( year , TransactionDate) > '2002'
GROUP BY DATEPART( Year, TransactionDate ) ) YearCounts
So I am looking for 98008 orders in 2003 as an example
Thanks for the answers, I checked them out, the sub query option executed fastest. I take on board the single quotes comments, thanks for the assistance.
Just use TOP 1
SELECT TOP 1 YearCounts.[CountForYear] AS [MinForYear],
YearCounts.[YEAR]
FROM ( SELECT COUNT(PK) AS [CountForYear],
DATEPART( year , TransactionDate) as [YEAR] FROM Orders
WHERE DATEPART( year , TransactionDate) > '2002'
GROUP BY DATEPART( Year, TransactionDate ) ) YearCounts
ORDER BY YearCounts.[CountForYear]
You can do this without a subquery, just by using top and order by:
SELECT TOP 1 DATEPART(year, TransactionDate), COUNT(PK) AS CountForYear
FROM Orders o
WHERE DATEPART(year , TransactionDate) > 2002
GROUP BY DATEPART(Year, TransactionDate )
ORDER BY COUNT(PK) DESC ;
You can also write this using the year function (which I personally find easier to read):
SELECT TOP 1 YEAR(TransactionDate), COUNT(PK) AS CountForYear
FROM Orders o
WHERE YEAR(TransactionDate) > 2002
GROUP BY YEAR(TransactionDate)
ORDER BY COUNT(PK) DESC ;
Your original query had single quotes around '2002'. This is unnecessary. You should express numeric constants without single quotes. Only use single quotes for string and date constants.