SQL Server : calculate monthly total sales incl empty months - sql

I'm trying to calculate the total sales of a product in a month, but I would like it to include any "empty" months (with no sales) and only select the latest 12 months.
This is my code so far.
declare
#ProductNo int
set #ProductNo = 1234
SELECT
YEAR(o.OrderDate) as 'Year', MONTH(o.OrderDate) as 'Month', sum(Amount) as 'Units sold',[ProductNo]
FROM [OrderLine] ol
inner join [Order] o on ol.OrderNo = o.OrderNo
where ProductNo = #ProductNo
Group by ProductNo, YEAR(o.OrderDate), Month(o.OrderDate)
Order by ProductNo, YEAR(o.OrderDate), Month(o.OrderDate)
This returns
Year Month Units sold
2011 6 2
2011 10 1
2011 11 1
2012 2 1
But I would like it to return.
Year Month Units sold
2011 4 0
2011 5 0
2011 6 2
2011 7 0
2011 8 0
2011 9 0
2011 10 1
2011 11 1
2011 12 0
2012 1 0
2012 2 2
2012 3 0
I'm using SQL Server 2008 R2 Sp1

I've done before I know that you have calendar table. I've used master.dbo.spt_values to generate last twelve consecutive months (including current).
declare #ProductNo int
set #ProductNo = 1234
select MONTH(d.date), YEAR(d.date), isnull(t.amnt, 0) as [Units sold] from (
SELECT
YEAR(o.OrderDate) as 'Year',
MONTH(o.OrderDate) as 'Month',
sum(Amount) as amnt,
[ProductNo]
FROM [OrderLine] ol
inner join [Order] o on ol.OrderNo = o.OrderNo
where ProductNo = #ProductNo
group by ProductNo, YEAR(o.OrderDate), Month(o.OrderDate)
) t
right join (
select dateadd(mm, -number, getdate()) as date
from master.dbo.spt_values
where type = 'p' and number < 12
) d on year(d.date) = t.[year] and month(d.date) = t.[month]
order by YEAR(d.date), MONTH(d.date)

Try:
;with CTE as
(select 0 months_ago union all
select months_ago - 1 months_ago from CTE where months_ago > -11),
month_list as
(select dateadd(MONTH,
months_ago,
dateadd(DAY,
1-datepart(DAY,getdate()),
cast(GETDATE() as DATE))) month_start
from cte)
SELECT YEAR(ml.start_date) as 'Year',
MONTH(ml.start_date) as 'Month',
sum(Amount) as 'Units sold',[ProductNo]
FROM month_list ml
left join [Order] o
on o.OrderDate >= ml.start_date and
o.OrderDate < dateadd(MONTH, 1, ml.start_date)
left join [OrderLine] ol
on ol.OrderNo = o.OrderNo and ProductNo = #ProductNo
Group by ProductNo, YEAR(ml.start_date), Month(ml.start_date)
Order by ProductNo, YEAR(ml.start_date), Month(ml.start_date)

Related

In SQL, is there a way to show all dates even if the date doesn't have data points?

I have a transaction table t as follows in MS SQL Management Studio:
If I run the following SQL to summarise the transaction:
Select
Format(Transaction_Date, 'MMM-yyyy') as 'Year/Month'
,Customer
,Count(Customer) as SalesCount
From t
Group by Format(Transaction_Date, 'MMM-yyyy'), Customer
Order by Customer, Format(Transaction_Date, 'MMM-yyyy')
I'll get:
However I was asked to add all the months for the year 2019 and if there's no transaction in a certain month then return 0 for the SalesCount column:
I tried to create a month table with all the months in 2019 and left join it with the transaction table, but it still returns the same result with no showing of the months without transactions.
Time table I created:
declare #StartDate date = '2019-01-01';
declare #EndDate date = '2020-01-01';
With cte as (
Select #StartDate AS myDate
Union All
Select Dateadd(Month,1,myDate)
From cte
Where Dateadd(Month,1,myDate) < #EndDate
)
,TimeTable as(
SELECT
year(myDate)
,Datename(Month,myDate)
,Format(myDate,'MMMM-yy') as 'Month-Year'
FROM cte
)
Select
tb.'Month-Year'
t.Format(Transaction_Date, 'MMM-yyyy') as Year/Month
,t.Customer
,t.Count(Customer) as SalesCount
From TimeTable tb
Left Join Transaction t on t.'Month/Year' = tb.'Month-Year'
Group by tb.'Month-Year', Format(Transaction_Date, 'MMM-yyyy'), Customer
Order by Customer, Format(Transaction_Date, 'MMM-yyyy')
Your help will be much appreciated!
You need to generate all the rows for the months. One method uses a recursive CTE. Then rest is then left join and aggregation.
Let me assume you are using SQL Server:
with months as (
select convert(date, '2019-01-01') as mon
union all
select dateadd(month, 1, mon)
from months
where mon < '2019-12-01'
)
select Format(m.mon, 'MMM-yyyy') as year_month,
c.Customer,
count(t.customer) as SalesCount
from months m cross join
(select distinct customer from t) c left join
t
on t.transaction_date >= m.mon and
t.transaction_date < dateadd(month, 1, mon) and
t.customer = c.customer
group by m.mon, c.customer
order by c.ustomer, c.mon ;
Note the other changes to the query:
Year/Month is not a valid column alias.
This orders the rows chronologically. That is usually (always?) preferred over alphabetic sorting of months.
You can use monthYear combination table and with distinct customer list, you can achieve this.
declare #table table(trandate date,customer char(1))
inSert into #table values
('2019-01-03','A'),
('2019-01-17','A'),
('2019-06-03','A'),
('2019-07-03','A'),
('2019-06-03','B'),
('2019-07-03','B');
;with monthYear AS
(
select * from
(
values
('Jan-19')
,('Feb-19')
,('Mar-19')
,('Apr-19')
,('May-19')
,('Jun-19')
,('Jul-19')
,('Aug-19')
,('Sep-19')
,('Oct-19')
,('Nov-19')
,('Dec-19')
) as t(mon)
)
SELECT my.mon,c.customer, isnull(t.salescount,0) as salescount FROM monthYear as my
CROSS JOIN (SELECT distinct customer from #table) as c
OUTER APPLY
(
select format(trandate,'MMM-yy') as MonthYear, count(customer) as salescount
from #table
where customer = c.customer
group by format(trandate,'MMM-yy')
having format(trandate,'MMM-yy') = my.mon
) as t
mon
customer
salescount
Jan-19
A
2
Feb-19
A
0
Mar-19
A
0
Apr-19
A
0
May-19
A
0
Jun-19
A
1
Jul-19
A
1
Aug-19
A
0
Sep-19
A
0
Oct-19
A
0
Nov-19
A
0
Dec-19
A
0
Jan-19
B
0
Feb-19
B
0
Mar-19
B
0
Apr-19
B
0
May-19
B
0
Jun-19
B
1
Jul-19
B
1
Aug-19
B
0
Sep-19
B
0
Oct-19
B
0
Nov-19
B
0
Dec-19
B
0

SQL: Group by specific time instead of year

I have the following query:
SELECT DATEPART(yyyy, ap.Date) AS 'Year', COUNT(p.Name2) AS 'Times entered', p.Name2 AS 'Name'
FROM Person p JOIN Price ap ON ap.PersonId = p.ID
GROUP BY p.Name2, DATEPART(yyyy, ap.Date)
ORDER BY DATEPART(yyyy, ap.Date) DESC , p.Name2 ASC
As result I have the following Data:
2018 50 Bob
2018 40 Fred
2017 10 Bob
2017 5 Fred
What I actually want is to not group by year, but to group by the period of between july 2017 and june 2018.
I want to group by period of July 2017 to June 2018.
How are we possible to set a timeframe as a group by function in SQL?
Something like this should work:
SELECT g.my AS 'Month-Year',
COUNT(p.Name2) AS 'Times entered',
p.Name2 AS 'Name'
FROM Person p
JOIN Price ap ON ap.PersonId = p.ID
CROSS APPLY
(
SELECT CONCAT(DATEPART(mm, ap.Date), '-', DATEPART(yyyy, ap.Date)) AS my
) AS g
WHERE ap.Date BETWEEN '2017-07-01' AND '2018-06-30'
GROUP BY p.Name2, g.my
ORDER BY g.my DESC , p.Name2 ASC

SQL: add missing months from different years

SQL SERVER
[CreatedOn] - DATETIME
I get this table:
Year Month Count
2009 7 1
2009 9 1
2010 1 2
2010 3 13
From query:
SELECT
YEAR ([CreatedOn]) AS 'Year',
MONTH ([CreatedOn]) AS 'Month',
COUNT ([CreatedOn]) AS 'Count'
FROM xxx
GROUP BY YEAR ([CreatedOn]), MONTH ([CreatedOn])
How can I get table like this (with missed months and Count 0):
Year Month Count
2009 7 1
2009 8 0
2009 9 1
2009 10 0
2009 11 0
2009 12 0
2010 1 2
2010 2 0
2010 3 13
Syntax says you are using MSSQL. Use Recursive CTE to generate the calender table then do a Left outer join with XXX table
DECLARE #maxdate DATE = (SELECT Max([CreatedOn])
FROM xxx);
WITH calender
AS (SELECT Min([CreatedOn]) dates,
FROM xxx
UNION ALL
SELECT Dateadd(mm, 1, dates)
FROM cte
WHERE dates < #maxdate)
SELECT Year(dates) [YEAR],
Month(dates) [month],
Count ([CreatedOn]) AS 'Count'
FROM calender a
LEFT OUTER JOIN xxx b
ON Year(dates) = Year ([CreatedOn])
AND Month(dates) = Month ([CreatedOn])
GROUP BY Year(dates),
Month(dates)
Note : Instead of Recursive CTE create a physical calender table
This will use a build in table to create the calendar:
;WITH limits as
(
SELECT min([CreatedOn]) mi, max([CreatedOn]) ma
FROM xxx
), months as(
SELECT
dateadd(mm, number, mi) m
FROM
master..spt_values v
JOIN
limits l
ON
number between 0 and datediff(mm, l.mi, l.ma)
WHERE
v.type = 'P'
)
SELECT
year(months.m) year,
month(months.m) month,
count(qry.[CreatedOn]) cnt
FROM
xxx qry
RIGHT JOIN
months
ON
months.m = dateadd(mm, datediff(mm, 0, qry.[CreatedOn]), 0)
GROUP BY
year(months.m),
month(months.m)

JOIN, GROUP BY AND SUM in single Query

Invoices Table
invoice_id invoice_date
------------ --------------
1 2013-11-27
2 2013-10-09
3 2013-09-12
Orders Table
order_id invoice_id product quantity total
--------- ---------- --------- --------- -------
1 1 Product 1 100 1000
2 1 Product 2 50 200
3 2 Product 1 40 400
4 3 Product 2 50 200
And i want a single sql query that produces following result
products Month 9 Total Month 10 Total Mont 11 Total
-------- ------------- -------------- -------------
Product 1 0 400 100
Product 2 200 0 200
I have tried the following sql query
SELECT orders.products, DATEPART(Year, invoices.invoice_date) Year, DATEPART(Month, invoices.invoice_date) Month, SUM(orders.total) [Total],
FROM invoices INNER JOIN orders ON invoices.invoice_id=orders.invoice_id
GROUP BY orders.products, DATEPART(Year, invoices.invoice_date), DATEPART(Month, invoices.invoice_date)
But it returns nothing. Is it possible to get this result with single query and what should i do for that ? Thanks
I think you want to use PIVOT here ...
Try this:
WITH tmp
AS
(
SELECT orders.products,
DATEPART(Year, invoices.invoice_date) Year,
DATEPART(Month, invoices.invoice_date) Month,
SUM(orders.total) [Total]
FROM invoices INNER JOIN orders ON invoices.invoice_id = orders.invoice_id
GROUP BY
orders.products,
DATEPART(Year, invoices.invoice_date),
DATEPART(Month, invoices.invoice_date)
)
SELECT products,
ISNULL([9],0) AS Nine, ISNULL([10],0) AS Ten, ISNULL([11],0) as Eleven
FROM tmp
PIVOT
(
SUM([Total])
FOR Month IN
( [9], [10], [11])
) as PVT;
You can edit it here: http://sqlfiddle.com/#!6/6f80f/6
You better use pivot for this. Just remember that you have to list every month explicitly in pivot..for..in clause.
select
*
into #invoices
from (
select 1 as invoice_id, '2013-11-27' as invoice_date union all
select 2,'2013-10-09' union all
select 3,'2013-09-12'
) x
select
*
into #orders
from (
select 1 as order_id,1 as invoice_id,'Product 1' as product,100 as quantity,1000 as total union all
select 2,1,'Product 2',50,200 union all
select 3,2,'Product 1',40,400 union all
select 4,3,'Product 2',50,200
) x
GO
select
Product,
[9], [10], [11]
from (
select
o.product, datepart([month],i.invoice_date) as mon,
o.total
from #invoices i
join #orders o
on i.invoice_id=o.invoice_id
) x
pivot (
sum(total) for mon in ([9],[10],[11])
) p
One way to do it is with Cases:
Select
O.product,
Sum(Case
When DATEPART(M, I.invoice_Date) = 9 Then O.total
Else 0
End) as Month9,
Sum(Case
When DATEPART(M, I.invoice_Date) = 10 Then O.total
Else 0
End) as Month10,
Sum(Case
When DATEPART(M, I.invoice_Date) = 11 Then O.total
Else 0
End) as Month11
From Invoice I
Left join Orders O on I.invoice_id = O.invoice_id
Group by O.product
There is another way using Pivots (depending on your version of SQL).
Select product, [9], [10], [11]
From
(
Select O.Product, O.total, DatePart(M, I.Invoice_Date) as [MonthNum]
From Invoice I
Left join Orders O on I.invoice_id = O.invoice_id
) P
PIVOT
(
Sum(P.total)
For [MonthNum] in ([9], [10], [11])
) as O

Grouping data with step down summation

I have a table with OrderDate,TotalAmount. I want to display month and TotalAmount of month with total amount of previous month to be added in next month.
e.g.
OrderDate TotalAmount
---------- -----------
13.01.1998--- 10
15.01.1998--- 11
01.02.1998--- 12
18.02.1998--- 10
12.03.1998--- 09
Output should be
Month TotalSum
------ --------
1--- 21
2--- 43
3--- 52
If your data would only be from a single calendar year, you could use
with g as
( select month(orderdate) as ordermonth,
sum( totalamount ) as sales
from orders
group by month(orderdate)
)
select m.ordermonth, sum(t.sales) as totalsales
from g as m
join g as t on m.ordermonth >= t.ordermonth
group by m.ordermonth
order by m.ordermonth
But if there is ANY chance that your data could have two years, then you need year in there as well, so construct your month to include year.
with g as
( select format(orderdate, 'yyyy-MM') as ordermonth,
sum( totalamount ) as sales
from orders
group by format(orderdate, 'yyyy-MM')
)
select m.ordermonth, sum(t.sales) as totalsales
from g as m
join g as t on m.ordermonth >= t.ordermonth
group by m.ordermonth
order by m.ordermonth