Cumulative total for quarters - sql

I currently have a query which produces data for each quarter, what I would like is for the total to be a running total. So quarter 2's figures will be added on to quarter 1's and so on until the end of the fiscal year, where the process will then start again.
select datepart(year,date) as Year,
CASE
WHEN MONTH(date) BETWEEN 1 AND 3 THEN convert(char(4), YEAR(date) - 1) + 'Q4'
WHEN MONTH(date) BETWEEN 4 AND 6 THEN convert(char(4), YEAR(date) - 0) + 'Q1'
WHEN MONTH(date) BETWEEN 7 AND 9 THEN convert(char(4), YEAR(date) - 0) + 'Q2'
WHEN MONTH(date) BETWEEN 10 AND 12 THEN convert(char(4), YEAR(date) - 0) + 'Q3'
END AS Quarter,
Data1,
Data2,
FROM TABLE
GROUP BY datepart(year,date),
CASE
WHEN MONTH(date) BETWEEN 1 AND 3 THEN convert(char(4), YEAR(date) - 1) + 'Q4'
WHEN MONTH(date) BETWEEN 4 AND 6 THEN convert(char(4), YEAR(date) - 0) + 'Q1'
WHEN MONTH(date) BETWEEN 7 AND 9 THEN convert(char(4), YEAR(date) - 0) + 'Q2'
WHEN MONTH(date) BETWEEN 10 AND 12 THEN convert(char(4), YEAR(date) - 0) + 'Q3'
END
This is what I currently get
year quarter data1 data2
2016 Q1 10 4
2016 Q2 5 6
2016 Q3 4 2
2017 Q4 1 1
I would like the output to look like this
year quarter data1 data2
2016 Q1 10 4
2016 Q2 15 10
2016 Q3 19 12
2017 Q4 20 13
Thanks for any help

You can wrap your query in a CTE and use CROSS APPLY in order to calculate the cumulative sum:
;WITH CTE AS (
-- your query
)
SELECT t1.year, t1.quarter, t1.data1, t3.cumulative_sum
FROM CTE AS t1
CROSS APPLY (
SELECT SUM(data2) AS cumulative_sum
FROM CTE AS t2
WHERE t2.year <= t1.year AND t2.quarter <= t1.quarter) AS t3

First, your query can be simplified to:
SELECT datepart(year, date) as Year,
datename(year, date) + 'Q' + datename(quarter, dateadd(month, -3, date)) as quarter,
SUM(Data1) as Data1, SUM(Data2) as Data2
FROM TABLE
GROUP BY datepart(year, date) as Year,
datename(year, date) + 'Q' + datename(quarter, dateadd(month, -3, date)) as quarter
ORDER BY quarter;
Note the use of datename(). This returns a string rather than a number, which is easier for string concatenation. In addition, this simplifies the logic just by subtracting 3 months to get the quarter.
In SQL Server 2012+, you can use the built in functionality for cumulative sum. In SQL Server 2008, one method uses apply:
WITH t as (
SELECT datepart(year, date) as Year,
datename(year, date) + 'Q' + datename(quarter, dateadd(month, -3, date)) as quarter,
SUM(Data1) as Data1, SUM(Data2) as Data2
FROM TABLE
GROUP BY datepart(year, date) as Year,
datename(year, date) + 'Q' + datename(quarter, dateadd(month, -3, date)) as quarter
)
SELECT t.*, tt.data2
FROM t OUTER APPLY
(SELECT SUM(data1) as data2
FROM t t2
WHERE t2.quarter <= t.quarter
) tt;

Related

Grouping several years' data by week

I am using Covid world data, which tracks number of new_cases every day. I have data from 2020-01-01 to present day. This is my current query:
SELECT MIN(date) as week, datepart(iso_week, date) week_num, sum(new_cases) as [Total Cases]
FROM covid_data_cleaned
GROUP BY DATEPART(iso_week, date), DATEPART(year, date)
ORDER BY MIN(date) DESC
Which gives me pretty much what I want except for when the year changes:
week week_num Total Cases
2022-01-10 2 20803473
2022-01-03 1 17217248
**2022-01-01 52 2115780**
**2021-12-27 52 7971560**
2021-12-20 51 5561762
I want to figure out the workaround to combining the '52' values together. Alternatively, my dataset has 112 weeks; can I assign the values 1-112 to the whole dataset rather than 1-52 for each year?
You could aggregate on a correction via a CASE WHEN for the iso year.
SELECT
min(weekstart) as [week]
, [iso_week] as weeknum
, sum(new_cases) as [Total Cases]
FROM
(
SELECT
min([date]) as weekstart
, datepart(iso_week, [date]) as [iso_week]
, case
when month(min([date])) = 1
and datepart(iso_week, [date]) >= 52
then year([date]) - 1
when month(min([date])) = 12
and datepart(iso_week, [date]) = 1
then year([date]) + 1
else year([date])
end as iso_year
, sum(new_cases) as new_cases
FROM covid_data_cleaned
GROUP BY year([date]), datepart(iso_week, [date])
) q
GROUP BY iso_year, [iso_week]
ORDER BY [week] DESC;
Instead of iso week, you can use week.
DECLARE #table table(datev date, new_Cases int)
INSERT INTO #table values
('2022-01-01',123),('2022-01-09',432),('2022-01-03',123),('2021-12-27',234);
SELECT MIN(datev) as week, datepart(week, datev) week_num, sum(new_cases) as [Total Cases]
FROM #table
GROUP BY DATEPART(week, datev), DATEPART(year, datev)
ORDER BY MIN(datev) DESC
week
week_num
Total Cases
2022-01-09
3
432
2022-01-03
2
123
2022-01-01
1
123
2021-12-27
53
234

My count CTE returning blanks, how can I get it return as 0?

CTE created to count the number of days left from today's date to end of current month. So my report for today (30 March 2021) did not count tomorrow's date 31 March 2021.
declare #DespatchTo Date = '03-30-2021'
WITH mycte AS
(
SELECT CAST(Convert(date,getdate()) AS DATETIME) DateValue
UNION ALL
SELECT DateValue + 1
FROM mycte
WHERE DateValue < DATEADD(d, -1, DATEADD(m, DATEDIFF(m, 0, #DespatchTo) + 1, 0)) --03-31-2021
)
SELECT SUN.Count as SunCount, SAT.Count as SatCount, WK.Count as WeekCount
FROM
(SELECT count(*) as Count
FROM mycte
WHERE DatePart("w",DateValue) = 1
group by DatePart("w",DateValue))
As SUN,
(SELECT count(*) as Count
FROM mycte
WHERE DatePart("w",DateValue) = 7
group by DatePart("w",DateValue))
As SAT,
(SELECT distinct SUM(COUNT(*)) OVER() AS Count
FROM mycte
WHERE DatePart("w",DateValue) > 1 AND DatePart("w",DateValue) < 7
group by DatePart("w",DateValue))
As WK
Which returns blank/null results. How can I return as 0?
here is what you need to do:
;WITH mycte AS (
SELECT GETDATE() DateValue
UNION ALL
SELECT DateValue + 1
FROM mycte
WHERE DateValue < EOMONTH(GETDATE())
)
select
count(case when datepart(dw, DateValue) = 1 then 1 end) SUN
, count(case when datepart(dw, DateValue) = 7 then 1 end) SAT
, count(case when datepart(dw, DateValue) between 2 and 6 then 1 end) WK
from mycte
if you want to exclude today, you can adjust cte :
;WITH mycte AS (
SELECT GETDATE() + 1 DateValue
WHERE GETDATE() <> EOMONTH(GETDATE())
UNION ALL
SELECT DateValue + 1
FROM mycte
WHERE DateValue < EOMONTH(GETDATE())
)
select
count(case when datepart(dw, DateValue) = 1 then 1 end) SUN
, count(case when datepart(dw, DateValue) = 7 then 1 end) SAT
, count(case when datepart(dw, DateValue) between 2 and 6 then 1 end) WK
from mycte

Displaying start date of week in SQL

I have a SQL query that to return the number of items per week. I have a query that returns so far this:
Number of Items | Week Number
-------------------------------
100 | 18
80 | 19
120 | 20
And would like to return the following:
Number of Items | Week Beginning
-------------------------------
100 | 1st May 2017
80 | 8th May 2017
120 | 15th May 2017
What I have so far is:
SELECT COUNT(*) AS 'Number of Items', DATEPART(WEEK, Date) FROM table
where DATEPART(Year, Date) = '2017' and DATEPART(MONTH, Date) = 5
group by DATEPART(WEEK, Date)
You are talking about the 1st day of the current week:
example: select FORMAT(dateadd(ww,datediff(ww,0,getdate()),0),'dd MMM yyyy')--if you are using SQL 2012+
answer:
SELECT COUNT(*) AS 'Number of Items', FORMAT(dateadd(ww,datediff(ww,0,date_column),0),'dd MMM yyyy')
FROM table
where DATEPART(Year, Date) = '2017' and DATEPART(MONTH, Date) = 5
group by DATEPART(WEEK, Date)
As you need Monday to be the first day of the week
select DATEPART(WEEK, MyDate),DATEADD(DAY,1,(DATEADD(DAY, 1-DATEPART(WEEKDAY, MyDate), MyDate)))
from (
select '5/3/2017' MyDate
union all select '5/10/2017'
union all select '5/14/2017')A
SELECT DATEADD(DAY, DATEDIFF(DAY, 0, Date) /7*7, 0) AS StartDateOfWeek
check this if it solves
DECLARE #WeekNum INT
, #YearNum char(4);
SELECT #WeekNum = 20
, #YearNum = 2017
-- once you have the #WeekNum and #YearNum set, the following calculates the date range.
SELECT DATEADD(wk, DATEDIFF(wk, 6, '1/1/' + #YearNum) + (#WeekNum-1), 6) AS StartOfWeek;
SELECT DATEADD(wk, DATEDIFF(wk, 5, '1/1/' + #YearNum) + (#WeekNum-1), 5) AS EndOfWeek;
thanks to http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=185440

How to get sum by MonthYear in sql?

I have a table like this
id Date Amount
1 2016-09-29 09:25:37.000 25.13
2 2016-08-01 17:20:39.000 598.00
3 2016-09-29 09:24:47.000 15.60
4 2016-07-28 17:50:11.000 61.80
5 2016-07-28 17:53:56.000 31.40
6 2016-07-22 10:40:27.000 74.16
I'm trying to get two columns like this,
MonthYear Total
Sep 2016 40.73
Aug 2016 598.00
Jul 2016 167.36
But, I want to get most recent year and month to top.
Try this sql,
SELECT CONVERT(CHAR(4), Date, 100) + CONVERT(CHAR(4), Date, 120)
AS MonthYear, SUM(Amount)
AS Total,
CAST(CONVERT(varchar(4), Date, 120) AS int)
AS Year, DATEPART(m, Date) As Month
FROM your_table
GROUP BY CONVERT(CHAR(4), Date, 100) + CONVERT(CHAR(4), Date,120),CAST(CONVERT(varchar(4), Date, 120) AS int), DATEPART(m, Date)
ORDER BY Year DESC, Month DESC
Just an other perspective.
Query
SELECT t.[MonthYear], SUM(t.[Amount]) AS [Total] FROM(
SELECT RIGHT((CONVERT(VARCHAR(11), [Date], 106)), 8) as [MonthYear], [Amount]
FROM [your_table_name]
)t
GROUP BY t.[MonthYear];
Try below
SELECT Datename(MONTH, [Date]) month_name,
Year([Date]) year,
Sum([Amount]),
Month([date]) month_no
FROM #Table1
GROUP BY Datename(MONTH, [Date]),
Year([Date]),
Month([date])
ORDER BY Month([date]) DESC,
Year([Date]),
Datename(MONTH, [Date])

tsql join with dateparts in subqueries

I have two queries with subqueries for dateparts which I like to join.
SELECT DateMonth, DateYear, Datestring,
MAX(CouponTotalCount) NoOfCouponsViewed
FROM (
SELECT *, DATEPART(MONTH, DateInsert) DateMonth, DATEPART(YEAR, DateInsert) DateYear,
CONVERT(CHAR(4), DateInsert, 100) + CONVERT(CHAR(4), DateInsert, 120) Datestring
FROM FlurryCouponViewed
) sub
where couponID=249
GROUP BY DateMonth, DateYear, Datestring
SELECT DateMonth, DateYear, Datestring,
MAX(CouponTotalCount) NoOfCouponsRedeemed
FROM (
SELECT *, DATEPART(MONTH, DateInsert) DateMonth, DATEPART(YEAR, DateInsert) DateYear,
CONVERT(CHAR(4), DateInsert, 100) + CONVERT(CHAR(4), DateInsert, 120) Datestring
FROM FlurryCouponRedeemed
) sub
where couponID=249
GROUP BY DateMonth, DateYear, Datestring
The output is of the two queries is:
DateMonth DateYear Datestring NoOfCouponsViewed
----------- ----------- ---------- -----------------
2 2012 Feb 2012 5
3 2012 Mar 2012 12
4 2012 Apr 2012 25
5 2012 May 2012 25
DateMonth DateYear Datestring NoOfCouponsRedeemed
----------- ----------- ---------- -------------------
2 2012 Feb 2012 3
3 2012 Mar 2012 4
4 2012 Apr 2012 5
5 2012 May 2012 11
What I like to achive is two have one joined query giving me:
DateMonth DateYear Datestring NoOfCouponsViewed NoOfCouponsRedeemed
----------- ----------- ---------- ----------------- -------------------
2 2012 Feb 2012 5 3
3 2012 Mar 2012 12 4
4 2012 Apr 2012 25 5
5 2012 May 2012 25 11
How can I do this ?
Make a inner join between the two queries and it should work:
SELECT sub.DateMonth, sub.DateYear, sub.Datestring,
MAX(sub.CouponTotalCount) NoOfCouponsViewed,
MAX(sub2.CouponTotalCount) NoOfCouponsViewed
FROM (
SELECT *, DATEPART(MONTH, DateInsert) DateMonth, DATEPART(YEAR, DateInsert) DateYear,
CONVERT(CHAR(4), DateInsert, 100) + CONVERT(CHAR(4), DateInsert, 120) Datestring
FROM FlurryCouponViewed
) sub
INNER JOIN
( SELECT *, DATEPART(MONTH, DateInsert) DateMonth, DATEPART(YEAR, DateInsert) DateYear,
CONVERT(CHAR(4), DateInsert, 100) + CONVERT(CHAR(4), DateInsert, 120) Datestring
FROM FlurryCouponRedeemed
) sub2 on sub.DateMonth = sub2.DateMonth and sub.DateYear = sub2.DateYear and sub.Datestring = sub2.Datestring
where sub.couponID=249 and sub2.couponID=249
GROUP BY sub.DateMonth, sub.DateYear, sub.Datestring
or a UNION:
SELECT u.DateMonth, u.DateYear, u.Datestring, MAX(u.NoOfCouponsViewed), MAX(u.NoOfCouponsRedeemed)
FROM (
SELECT DateMonth, DateYear, Datestring,
MAX(CouponTotalCount) NoOfCouponsViewed, 0 AS NoOfCouponsRedeemed
FROM (
SELECT *, DATEPART(MONTH, DateInsert) DateMonth, DATEPART(YEAR, DateInsert) DateYear,
CONVERT(CHAR(4), DateInsert, 100) + CONVERT(CHAR(4), DateInsert, 120) Datestring
FROM FlurryCouponViewed
) sub
where couponID=249
GROUP BY DateMonth, DateYear, Datestring
UNION
SELECT DateMonth, DateYear, Datestring, 0 AS NoOfCouponsViewed,
MAX(CouponTotalCount) NoOfCouponsRedeemed
FROM (
SELECT *, DATEPART(MONTH, DateInsert) DateMonth, DATEPART(YEAR, DateInsert) DateYear,
CONVERT(CHAR(4), DateInsert, 100) + CONVERT(CHAR(4), DateInsert, 120) Datestring
FROM FlurryCouponRedeemed
) sub
where couponID=249
GROUP BY DateMonth, DateYear, Datestring
) u
GROUP BY u.DateMonth, u.DateYear, u.Datestring
I would use UNION rather than JOIN
SELECT MonthInserted,
LEFT(DATENAME(MONTH, Datestring), 3) + ' ' + DATENAME(YEAR, MonthInserted) AS DateString
MAX(Viewed) NoOfCouponsViewed,
MAX(Redeemed) NoOfCouponsRedeemed
FROM ( SELECT CouponID,
DATEADD(MONTH, DATEDIFF(MONTH, 0, DateInsert)) [MonthInserted],
CouponTotalCount AS Viewed,
0 AS Redeemed
FROM FlurryCouponViewed
UNION ALL
SELECT CouponID,
DATEADD(MONTH, DATEDIFF(MONTH, 0, DateInsert)),
0,
CouponTotalCount
FROM FlurryCouponRedeemed
) sub
WHERE couponID = 249
GROUP BY MonthInserted
I think a UNION would perform better than the JOIN in the accepted answer as the MAX implies that there are multiple rows per month, and since month is the only common field so it will end up cross joining queries (i.e. if there are 1000 coupons viewed in june 2012 and 500 are redeemed the cross join means you would be selecting the max from 50,000 rows instead of 1500). I am not certain of the schema and logic so this may not be possible, but if there are dates in FlurryCouponRedeemed that are not in FlurryCouponViewed then these will not show.
I also like to keep dates as dates for as long as possible to help the optimiser do it's job, this is why I've replaced DATEPART(YEAR... & DATEPART(MONTH... AND CONVERT(VARCHAR(4), DateInsert... with DATEADD(MONTH, DATEDIFF(MONTH, 0, DateInsert))