How to SUM over month columns in past year best way? - sql

I have following columns from Jan to Dec:
Year - Jan - Feb - Mar - (etc.) - Dec
---- --- --- --- ---
2015 25 32 102 12
2016 30 40 50 60
How to effectively do SUM over past year? Let's say from GETDATE(), If today is 16.08.2017, I want SUM from 16.08.2016 (from august 2016 till august 2017).
I have following code:
select sum(val)
from t cross apply
(values (t.year, t.Jan, 1),
(t.year, t.Feb, 2),
. . .
) v(yyyy, val, mon)
where yyyy * 100 + mon >= (year(getdate()) - 1) * 100 + month(getdate());
which works, but is there any way to do it without cross apply? (for instance: just where clause)

how about something like below which uses UNPIVOT notation.
select sum(val)
from
(select * from t )s
unpivot
( val for month in ( Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec))up
where
cast(month+ cast(year as varchar(20)) as date)
between dateadd(m,-12,getdate()) and getdate()
Live Demo

Assuming all your column month names are 3 letters, you could also use dynamic SQL. Something like the following should work.
DECLARE #CurrentDate DATE = CAST(GETDATE() AS DATE)
DECLARE #Year INT = DATEPART(YEAR, #CurrentDate)
DECLARE #PrevYear INT = #Year - 1
DECLARE #Month CHAR(3)
SET #Month = convert(char(3), #CurrentDate, 0)
BEGIN
EXEC('SELECT SUM(t.'+#Month+') FROM t WHERE Year >= ' + #PrevYear + ' AND Year <= ' + #Year)
END

Related

Subtract N from weeknum of current year

We have table WeeklyResults that looks like this:
Year WeekNum Value
2021 47 11.0
2021 48 14.0
2021 49 12.0
2021 50 17.0
2021 51 11.8
2021 52 11.3
2021 53 11.1
2022 01 11.5
2022 02 11.5
2022 03 81.5
We have a report with two parameters: Date and WeekNum. The report needs to show the last 6 weeks based on the weeknumber selected.
The issue is that, if user selects Week 2 of 2022, how can I subtract 6 weeks so that I get weeks 50, 51, 52, 53 of 2021 and weeks 1, 2 from 2022?
So, if the user selects 2022 and Week 02, it would show the last 6 weeks based on Weeknum 2 of year 2022 (wk50 to Wk02). If user selects 2021 and 52, it would show wk47-52.
You can use a little < and <= logic along side TOP and ORDER BY to achieve this:
DECLARE #Year int = 2022,
#WeekNum int = 3; --Note, if you are storing WeekNum as a (var)char,
--your leading zeros imply you are, then define the
--variable as a char(2).
SELECT TOP (6)
[Year],
WeekNum,
[Value]
FROM dbo.YourTable
WHERE ([Year] = #Year AND WeekNum <= #WeekNum)
OR [Year] < #Year
ORDER BY [Year] DESC,
WeekNum DESC;
Try this:
DECLARE #Year int = 2022
, #WeekNum varchar(02) = '02'
;
WITH FinalTable AS
(
SELECT TOP 6 *
FROM WeeklyResults
ORDER BY LTRIM(Year) + WeekNum DESC
)
SELECT *
FROM FinalTable
ORDER BY Year, WeekNum
Another option that doesn't involve an ORDER BY. Using DATEPART you can determine the final week of the prior year and subtract the number of weeks to get the records needed.
You may need to adjust what day is the first day of the week for your count. See this post for more info on that.
DECLARE
#Year INT = 2022
, #WeekNum INT = 3;
SELECT
*
FROM
WeeklyResults
WHERE
Year = #Year
AND WeekNum <= #WeekNum
OR
(
#WeekNum < 6
AND Year = #Year - 1
AND WeekNum > DATEPART (WEEK, CONCAT ('12/31/', #Year - 1)) - #WeekNum
);

How to get financial quarters date by current date

The quarters described as below:
APRIL - JUNE - Q1
JULY - SEPT - Q2
OCT - DEC - Q3
JAN - MARCH - Q4
For ex:
Date = '2018-12-24' -- Where this date is under third quarter
So Expected should be [Get all quarter < current date's quarter]
---------------------------
Quarters
---------------------------
01 Jul 2018 - 30 Sep 2018
01 Apr 2018 - 30 Jun 2018
---------------------------
If the
Date = '2019-01-24' -- Where this date is under fourth quarter
Expected result:
---------------------------
Quarters
---------------------------
01 Oct 2018 - 30 Dec 2018
01 Jul 2018 - 30 Sep 2018
01 Apr 2018 - 30 Jun 2018
---------------------------
The query that I have tried:
DECLARE #dt DATETIME = '2019-01-24 18:15:59.517'
DECLARE #FirstDayOfQuarter DATETIME
DECLARE #LastDayOfQuarter DATETIME
SET #FirstDayOfQuarter = (SELECT Dateadd(qq, Datediff(qq, 0, #dt) - 1, 0))
SET #LastDayOfQuarter = (SELECT Dateadd(dd, -1, Dateadd(qq, Datediff(qq, 0, #dt)
, 0)))
DECLARE #year INT
SET #year = Datepart(year, #dt)
DECLARE #currQ NVARCHAR(max)
SET #currQ = (SELECT CONVERT(VARCHAR(20), #FirstDayOfQuarter, 106)
+ Space(1) + '-' + Space(1)
+ CONVERT(VARCHAR(20), #LastDayOfQuarter, 106))
SELECT CONVERT(NVARCHAR(20), Dateadd(m, 3*number, CONVERT(DATE, CONVERT(VARCHAR(
5),
#year)+'-1-1')), 106)
+ Space(1) + '-' + Space(1)
+ CONVERT(NVARCHAR(20), Dateadd(d, -1, Dateadd(m, 3*number+3, CONVERT(
DATE,
CONVERT(VARCHAR(5), #year)+'-1-1'))), 106) AS Quarter,
number,
CASE
WHEN #dt BETWEEN Dateadd(m, 3 * number, CONVERT(DATE, CONVERT(VARCHAR(5
),
#year) +
'-1-1')) AND
Dateadd(d, -1, Dateadd(m, 3 * number + 3,
CONVERT(DATE, CONVERT(VARCHAR(5)
,
#year
) + '-1-1'))) THEN 1
ELSE 0
END AS isCurrentQuarter
INTO #allquarters
FROM master..spt_values
WHERE type = 'p'
AND number BETWEEN 1 AND 4
SELECT TOP 1 number,
CASE
WHEN #dt BETWEEN Dateadd(m, 3 * number,
CONVERT(DATE, CONVERT(VARCHAR(5),
#year) +
'-1-1')) AND
Dateadd(d, -1, Dateadd(m, 3 * number + 3,
CONVERT(DATE, CONVERT(
VARCHAR(5),
#year
) + '-1-1'))) THEN 1
ELSE 0
END AS isCurrentQuarter
INTO #currentquarter
FROM master..spt_values
WHERE type = 'p'
AND number BETWEEN 1 AND 4
ORDER BY iscurrentquarter DESC,
number ASC
SELECT quarter
FROM #allquarters
WHERE number < (SELECT number
FROM #currentquarter)
ORDER BY number DESC
DROP TABLE #allquarters
DROP TABLE #currentquarter
It is working for GETDATE() but when I put the date as 2019-01-24 18:15:59.517 then it gives me empty result
EDIT:
If the
Date = '2019-05-24' -- Where this date is under first quarter of next year
Expected result:
---------------------------
Quarters
---------------------------
01 Jan 2019 - 30 March 2019
01 Oct 2018 - 30 Dec 2018
01 Jul 2018 - 30 Sep 2018
01 Apr 2018 - 30 Jun 2018
---------------------------
If the
Date = '2018-05-24' -- Where this date is under the first quarter of current year
Expected result:
---------------------------
Quarters
---------------------------
01 Jan 2018 - 30 March 2018
01 Oct 2017 - 30 Dec 2017
01 Jul 2017 - 30 Sep 2017
01 Apr 2017 - 30 Jun 2017
---------------------------
I believe this does what you want:
with dte as (
select cast('2019-07-24' as date) as dte
)
select dte,
(convert(varchar(255), dateadd(month, v.n, datefromparts(year(dte), ((month(dte) - 1) / 3) * 3 + 1, 1)), 106) + ' - ' +
convert(varchar(255), dateadd(day, -1, dateadd(month, v.n + 3, datefromparts(year(dte), ((month(dte) - 1) / 3) * 3 + 1, 1))), 106)
) as string
from dte cross apply
(values (0), (-3), (-6), (-9)) v(n)
where month(dte) < 4 or month(dte) >= 13 + v.n
order by v.n;
Here is a db<>fiddle.
Use the formula (n + 5) % 12 + 3 to convert month numbers 1, 2, ..., 12 to 9, 10, ..., 14, 3, ..., 8 which is the number of months you need to subtract from a given date. In SQL Server 2008 you would translate it as:
DATEADD(MONTH, DATEDIFF(MONTH, 0, inputdate) - ((MONTH(inputdate) + 5) % 12 + 3), 0)
The converts 2018-04-xx to 2017-04-01 and 2018-07-xx to 2018-04-01. Adding 3, 6 and 9 months is trivial.
DB Fiddle

SQL - Create a temp table or CTE of first day of the month and month names

I need to create a temp table or common table expression based on 2 paremters in a SQL Server 2012 environment
#calYear
#currentYear
so if #calYear = 5 and #currentYear='2014'
I would like to generate a temp table of 5 years starting from current year with 4 columns like
YearDesc MonthName MonthNum FirstDayOfMonth
2014 Jan 1 1/1/2014
2014 Feb 2 2/1/2014
2014 Mar 3 3/1/2014
...
...
...
2018 Oct 10 10/1/2018
2018 Nov 11 11/1/2018
2018 Dec 12 12/1/2018
Is it possible to do a Do While loop efficently? How would I account for the month names?
I'm using a really cumbersome Do While loop to iterate all the months of the year then iterate all the years.
One way using a recursive cte:
declare #calYear int = 5, #currentYear char(4) = '2014'
;with cte (dt) as (
select DATEFROMPARTS(#currentyear,1,1) dt
union all
select dateadd(month,1,dt)
from cte where dt < dateadd(year,#calyear,DATEFROMPARTS(#currentyear,1,1))
)
select year(dt) YearDesc, datename(month, dt) MonthName, month(dt) MonthNum, dt FirstDayOfMonth
from cte
order by dt
Or using a numbers table: (in this case master..spt_values)
declare #calYear int = 5, #currentYear char(4) = '2014'
;with cte2 (dt) as (
select dateadd(month,number,DATEFROMPARTS(#currentyear,1,1)) dt
from master..spt_values where type = 'p'
and number <= 12*#calYear
)
select year(dt) YearDesc, datename(month, dt) MonthName, month(dt) MonthNum, dt FirstDayOfMonth
from cte2
order by dt

How to iterate last six month(including current) loop without any custom table?

I want output like follow:
Currently I am using a table. But I don't want to use this with any table(EXCEPT SYSTEM TABLES). Is it possible?
Query(Using a table):
DECLARE #End_date DATETIME
SET #End_Date = DATEADD(month, -6, GETDATE())
SELECT DISTINCT MONTH(S.ACDATE) AS Mon,CONVERT(CHAR(4),S.ACDATE) AS Month_Name,
(YEAR(S.ACDATE) % 100) AS Year_No
FROM SALES as S
WHERE S.ACDATE < DATEADD(month,MONTH(getdate()),
DATEADD(year,YEAR(getdate())-1900,0))
AND S.ACDATE >= DATEADD(month,MONTH(#End_Date)-1,
DATEADD(year,YEAR(#End_Date)-1900,0))
DECLARE #dt DATE = CONVERT(DATE, '05/03/2013', 101)
SELECT MONTH (dt) AS Mon, LEFT (DATENAME (mm, dt), 3) AS Month_Name, YEAR (dt) % 1000 AS Year_No
FROM (
SELECT DATEADD (mm, -diff, #dt) dt
FROM (VALUES(1),(2),(3),(4),(5),(6))t(diff)
) t
Result set is:
Mon Month_Name Year_No
----------- ---------- -----------
4 Apr 13
3 Mar 13
2 Feb 13
1 Jan 13
12 Dec 12
11 Nov 12

SQL days available using a not exists on current financial year

I have a table called 'holiday table' which basically contains dates for all days where employees will not be in work (e.g bank holidays etc)
The below query is basically looking at the current financial year and working out how many days are available firstly by month, and then using the unuion all cummulatively, (e.g April-May, April-June) I dont need one for April though as I can use the non-cumulative for this.
See query:
DECLARE #StartDate DATETIME,
#EndDate DATETIME
--available days
--current – start of this financial year
SELECT #StartDate = (select
case when month(getdate()) >= 4 then
convert(datetime, cast(year(getdate()) as varchar) + '-4-1')
else
convert(datetime, cast(year(getdate())-1 as varchar) + '-4-1')
end),
--current – end of this financial year
#EndDate = (select
case when month(getdate()) < 4 then
convert(datetime, cast(year(getdate()) as varchar) + '-3-31')
else
convert(datetime, cast(year(getdate())+1 as varchar) + '-3-31')
end)
CREATE TABLE #data
(
firstday DATETIME NOT NULL PRIMARY KEY,
workingdays INT NOT NULL
);
WITH dayscte ([Date])
AS (SELECT #StartDate
UNION ALL
SELECT Dateadd(DAY, 1, [Date])
FROM dayscte
WHERE [Date] <= #Enddate)
INSERT INTO #data
SELECT MIN([Date]),
COUNT(*) [Day]
FROM dayscte
LEFT JOIN dbo.Holiday_Table
ON [Date] BETWEEN dbo.Holiday_Table.sch_cal_d AND dbo.Holiday_Table.sch_cal_ed
where
NOT EXISTS (
SELECT sch_id,sch_cal_d,sch_cal_ed FROM dbo.Holiday_Table WHERE
sch_id ='66291100Ks'
AND
[date] <= sch_cal_d
AND
[date] >= sch_cal_ed
)
AND Datename(weekday, [Date]) NOT IN ( 'Saturday', 'Sunday' )
GROUP BY Datepart(MONTH, [Date]),
Datepart(YEAR, [Date])
OPTION (MAXRECURSION 366)
DECLARE #Date DATETIME
SET #Date = (SELECT MIN(firstday)
FROM #data)
SELECT Period,
workingdays [Days_Available_Minus_Holidays] ,
year (firstday) AS [Year]
FROM (SELECT Datename(MONTH, firstday) [Period],
workingdays,
0 [SortField],
firstday
FROM #data
UNION
SELECT Datename(MONTH, #Date) + '-' + Datename(MONTH, firstday),
(SELECT SUM(workingdays)
FROM #data b
WHERE b.firstday <= a.firstday ) [WorkingDays],
1 [SortField],
firstday
FROM #data a
WHERE
firstday > #Date) data
ORDER BY sortfield,
firstday
DROP TABLE #data
GO
The results for this are as follows:
Period Days_Available_Minus_Holidays Year
April 19 2012
May 22 2012
June 19 2012
July 22 2012
August 22 2012
September 20 2012
October 23 2012
November 22 2012
December 19 2012
January 23 2013
February 20 2013
March 21 2013
April 1 2013
April-May 41 2012
April-June 60 2012
April-July 82 2012
April-August 104 2012
April-September 124 2012
April-October 147 2012
April-November 169 2012
April-December 188 2012
April-January 211 2013
April-February 231 2013
April-March 252 2013
April-April 253 2013
My problem is when I get to the cumulative, it does another 'April' and then at the bottom it does an 'April-April' I do not need a cumulative for April as it is only one month do basically I dont want the first or last cumulative values as April is covered by the non-cumulatives, or if the second 'April' must stay, then it should not read '1' as days available, by be the same as the non-cumulative, which is 19 as this is how many days are actually available.
Try removing the equals in your WITH clause
Change WHERE [Date] <= #Enddate to WHERE [Date] < #Enddate
It seems your adding a day to the date before the WHERE clause therefore it is overstepping by a day.