PIVOT SQL Data & Fill In Blanks - sql

I'm attempting to transform this data:
ItemID MonthAsInt Month Year InvType Quantity
4643 4 April 2011 Shipment 10
4643 5 May 2011 Shipment 10
4643 7 July 2011 Shipment 10
4643 8 August 2011 Destroy 10
4643 11 November 2011 Shipment 25
4643 12 December 2011 Picking 1
Into this (basically, a 12 month snap shot):
February March April May June July August ...
Shipment 0 0 10 10 0 10 0
Picking 0 0 0 0 0 0 0
Destroy ...
I've messed with the PIVIOT method, but I haven't had much luck. At this point, all I have is the list of dates that I need between GETDATE() and GETDATE() - 12 months (retrieved with the query below):
DECLARE #BeginDate DATETIME
DECLARE #EndDate DATETIME
SET #BeginDate = GETDATE();
SET #EndDate = DATEADD(MONTH, -12, GETDATE());
WITH CTE_DatesTable
AS
(
SELECT #EndDate AS [Date]
UNION ALL
SELECT DATEADD(dd, 1, [date])
FROM CTE_DatesTable
WHERE DATEADD(dd, 1, [date]) <= #BeginDate
)
SELECT DISTINCT DATENAME(MONTH, [date]) + ' '
+ CAST(YEAR([date]) AS VARCHAR(4)) AS MonthYear,
YEAR([date]) AS YearAsInt,
MONTH([Date]) AS MonthAsInt
FROM CTE_DatesTable
ORDER BY YEAR([date]), MONTH([Date])
OPTION (MAXRECURSION 0)
See the query in action here.
Is what I'm trying to accomplish possible? Am I going in the right direction? Any help would be appreciated!

You can do this without pivot (the syntax for which I find daunting as well). Since you don't know the actual layout of columns beforehand, I think this is easiest with dynamic SQL. Given the following table/sample data:
USE tempdb;
GO
CREATE TABLE dbo.foo
(
ItemID INT,
MonthAsInt INT,
[Month] VARCHAR(12),
[Year] INT,
InvType VARCHAR(12),
Quantity INT
);
INSERT dbo.foo SELECT 4643,4 ,'April ',2011,'Shipment',10
UNION ALL SELECT 4643,5 ,'May ',2011,'Shipment',10
UNION ALL SELECT 4643,7 ,'July ',2011,'Shipment',10
UNION ALL SELECT 4643,8 ,'August ',2011,'Destroy ',10
UNION ALL SELECT 4643,11,'November',2011,'Shipment',25
UNION ALL SELECT 4643,12,'December',2011,'Picking ',1;
You can generate a list of months using a much simpler CTE, and build a dynamic SQL statement based off of that:
DECLARE #sql NVARCHAR(MAX) = N'';
;WITH n AS
(
SELECT TOP (12) d = DATEADD
(
MONTH,
-(ROW_NUMBER() OVER (ORDER BY [object_id]) - 1),
GETDATE()
)
FROM sys.objects
ORDER BY d DESC
)
SELECT #sql = #sql + N',' + CHAR(13) + CHAR(10) + DATENAME(MONTH, d)
+ ' = SUM(CASE WHEN [Year] = ' + CONVERT(VARCHAR(4), DATEPART(YEAR, d))
+ ' AND MonthAsInt = ' + CONVERT(VARCHAR(2), DATEPART(MONTH, d))
+ ' THEN Quantity ELSE 0 END)'
FROM n
ORDER BY d;
SELECT #sql = N'SELECT InvType' + #sql + '
FROM dbo.foo
GROUP BY InvType';
PRINT #sql;
-- EXEC sp_executesql #sql;
I put the PRINT there so you could test it out before running it. I wasn't sure if you needed 12 months or 13 months, you can just change the TOP (12) to TOP (13) if you want 13 months, or remove the -1 if you don't want the current month included.

If you need a dynamic pivot: see here
otherwise
This is how you do it will pivot. Of course, PIVOT requires you to know what your data will look like beforehand. If you need a dynamic pivot, then there are plenty of dynamic crosstab/pivot queries/sps that people have already written.
DECLARE #BeginDate DATETIME
DECLARE #EndDate DATETIME
SET #BeginDate = GETDATE();
SET #EndDate = DATEADD(MONTH, -12, GETDATE());
WITH CTE_DatesTable
AS
(
SELECT #EndDate AS [Date]
UNION ALL
SELECT DATEADD(dd, 1, [date])
FROM CTE_DatesTable
WHERE DATEADD(dd, 1, [date]) <= #BeginDate
)
SELECT DISTINCT DATENAME(MONTH, [date]) + ' ' + CAST(YEAR([date]) AS VARCHAR(4)) AS MonthYear,
YEAR([date]) AS YearAsInt,
MONTH([Date]) AS MonthAsInt,
case when month([date]) < 5 then 'Shipment' else 'Picking' end InvType,
floor(10 * RAND() * month([date])) Quantity
into #orig
FROM CTE_DatesTable
ORDER BY YEAR([date]), MONTH([Date])
OPTION (MAXRECURSION 0)
update #orig
set Quantity = null
where MonthYear = 'February 2011'
select * from #orig
select *
from
(
select isnull(Quantity, 0) Quantity, MonthYear from #orig
) SourceTbl
PIVOT
(
sum(Quantity)
for MonthYear in ([February 2011], [March 2011])
) PivotTbl
drop table #orig
Result:
February 2011 March 2011
0 29

Related

How to get the First day and last day of the month from given month number and year in SQL Server

I have a month number and year: month 2 and year 2022.
How can I get the first day of that month like 2022-02-01 and last day of month 2022-02-28?
I have seen many posts on getting first and last date of month based on given date or the current date, but I need it based on given month and year.
Thanks in advance
Here is my stored procedure:
ALTER PROCEDURE [dbo].[Rpt_ItemsSales_DayMonthYear_year]-- 2022
#year int = NULL
AS
;WITH months(MonthNumber) AS
(
SELECT 1
UNION ALL
SELECT MonthNumber + 1
FROM months
WHERE MonthNumber < 12
)
SELECT
SalesPos_Dtls.ItemName,
SUM(SalesPos_Dtls.Qty) AS SumQty,
SUM(SalesPos_Dtls.TotalPrice) AS SumTotal,
SalesPos_Dtls.ItemCode,
DATENAME(month, DATEADD(month, m.MonthNumber, 0) - 1) AS MonthName,
m.MonthNumber
FROM
months AS m
LEFT JOIN
SalesPos ON MONTH(SalesPos.StartDate) = m.MonthNumber
AND (status = 'IsPosted')
AND (#year = YEAR(salespos.startdate) OR #year IS NULL)
LEFT JOIN
[dbo].SalesPos_Dtls ON SalesPos.ID = SalesPos_Dtls.OrderId
GROUP BY
m.MonthNumber, dbo.SalesPos_Dtls.ItemName, dbo.SalesPos_Dtls.ItemCode
And this is as far as I got
ALTER PROCEDURE [dbo].[Rpt_ItemsSales_DayMonthYear_year] --2022,1
#year int = NULL,
#month int = NULL
AS
DECLARE #yearr int = #year
DECLARE #monthh int = #month
;WITH months(MonthNumber) AS
(
SELECT 1
UNION ALL
SELECT MonthNumber + 1
FROM months
WHERE MonthNumber < 12
)
SELECT
SalesPos_Dtls.ItemName,
SUM(SalesPos_Dtls.Qty) AS SumQty,
SUM(SalesPos_Dtls.TotalPrice) AS SumTotal,
SalesPos_Dtls.ItemCode,
DATENAME(month, DATEADD(month, m.MonthNumber, 0) - 1) AS MonthName,
m.MonthNumber,
DATEFROMPARTS (#yearr, #monthh, 1) AS MonthStart,
EOMONTH (DATEFROMPARTS (#yearr, #monthh, 1)) AS MonthEnd
FROM
months AS m
LEFT JOIN
SalesPos ON MONTH(SalesPos.StartDate) = m.MonthNumber
AND (status = 'IsPosted')
AND (#year = YEAR(salespos.startdate) OR #year IS NULL)
LEFT JOIN
[dbo].SalesPos_Dtls ON SalesPos.ID = SalesPos_Dtls.OrderId
WHERE
(MONTH(SalesPos.StartDate) = #month OR #month IS NULL)
GROUP BY
m.MonthNumber, dbo.SalesPos_Dtls.ItemName, dbo.SalesPos_Dtls.ItemCode
Screenshot with the sample data:
But I want my data this way :
هوت دوج لارج 3.0000 75.0000
76 January 1 2022-1-01 2022-1-31
هوت دوج ميديم 1.0000 20.0000 77 January 1 2022-1-01 2022-1-31
NULL NULL NULL NULL February 2 2022-2-01 2022-2-28
NULL NULL NULL NULL March 3 NULL 2022-3-01 2022-3-31
SET DATEFIRST 1
DECLARE #Month smallint= 3, #Year smallint = 2022;
/*First and Last day of month as DATE */
select DATEFROMPARTS(#Year, #MONTH, 1)FirstDayOfMonth , EOMONTH(DATEFROMPARTS(#Year, #MONTH, 1)) LastDayofMonth
/*First and Last Weekday of Month */
select DATEPART( dw, DATEFROMPARTS(#Year, #MONTH, 1) ) FirstWeekDayOfMonth , DATEPART(dw, (EOMONTH(DATEFROMPARTS(#Year, #MONTH, 1)))) LastWeekDayofMonth
i dont know if this is the right way to update the question but here is the solution that worked for me.
SELECT DATEADD(month, DATEDIFF(month, 0, #mydate), 0) AS StartOfMonth
SELECT EOMONTH(#mydate) as LastOFMonth
And thanks
edit:
this is the stored procedure. all i wanted is to have 2 columns infront of each item that have the firstday of the month and the last day witch this item was purchased.
i hope this and the stored procedure explain what i mean.
ALTER proc [dbo].[Rpt_ItemsSales_DayMonthYear_year]--Rpt_ItemsSales_DayMonthYear_year 2022
#year int=null
as
;WITH months(MonthNumber) AS
(
SELECT 1
UNION ALL
SELECT MonthNumber+1
FROM months
WHERE MonthNumber < 12
)
select SalesPos_Dtls.ItemName,sum(SalesPos_Dtls.Qty) as SumQty,sum(SalesPos_Dtls.TotalPrice) as SumTotal,SalesPos_Dtls.ItemCode,DateName( month , DateAdd( month , m.MonthNumber , 0 ) - 1 ) as MonthName,
m.MonthNumber, cast(DATEADD(month, DATEDIFF(month, 0, SalesPos.StartDate), 0) as date) AS StartOfMonth,EOMONTH(SalesPos.StartDate) as LastOFMonth
from months as m
left join SalesPos on month(SalesPos.StartDate) = m.MonthNumber and (status = 'IsPosted') and (#year = year(salespos.startdate) or #year is null)
left JOIN [dbo].SalesPos_Dtls on SalesPos.ID=SalesPos_Dtls.OrderId
group by m.MonthNumber,dbo.SalesPos_Dtls.ItemName,dbo.SalesPos_Dtls.ItemCode,cast(DATEADD(month, DATEDIFF(month, 0, SalesPos.StartDate), 0) as date),EOMONTH(SalesPos.StartDate)

SQL Query for getting nth Weekday date between two dates

My scenario is as below:
#StartDate = 13th of current month
#EndDate = 12th of next month.
I want to get all the date with the day-name for Mondays, Tuesdays, Wednesdays, Thursdays, Fridays, Saturdays and Sundays lying between the start and end date.
Try this:
declare #startDate datetime = '2016-01-13'
declare #endDate datetime = '2016-02-12'
;with dateRange as
(
select [Date] = dateadd(dd, 1, #startDate)
where dateadd(dd, 1, #startDate) < #endDate
union all
select dateadd(dd, 1, [Date])
from dateRange
where dateadd(dd, 1, [Date]) < #endDate
)
select [Date], datename(dw,[Date])
from dateRange
To count the number of each day as per your comment (this should be part of the question really), change the last part of James' answer to this:
select datename(dw,[Date]) as day_name, count([Date]) as number_days
from dateRange group by datename(dw,[Date]), datepart(DW,[Date])
order by datepart(DW,[Date]);
You can try something like this :
DECLARE #StartDate DATETIME
DECLARE #StartDateFixed DATETIME
DECLARE #EndDate DATETIME
DECLARE #NumberOfDays int
SET #StartDate = '2016/01/01'
SET #EndDate = '2016/01/02'
SET #NumberOfDays = DATEDIFF(DAY,#StartDate,#EndDate) + 1
SET #StartDateFixed = DATEADD(DD,-1,#StartDate)
SELECT WeekDay , COUNT(WeekDay)
FROM (
SELECT TOP (#NumberOfDays) WeekDay = DATENAME(DW , DATEADD(DAY,ROW_NUMBER() OVER(ORDER BY spt.name), #StartDateFixed))
FROM [master].[dbo].[spt_values] spt
) A
GROUP BY WeekDay
The output will be
WeekDay
------------------------------ -----------
Friday 1
Saturday 1
(2 row(s) affected)
In case if you need to get current and next date from date number specified such as 13 and 12
Current Month
DECLARE #cur_mont INT = (SELECT MONTH(GETDATE()))
Current Year
DECLARE #cur_year INT = (SELECT YEAR(GETDATE()))
Next Month
DECLARE #nxt_mont INT = (SELECT MONTH(DATEADD(month, 1, GETDATE())))
Next Month year (In case of December year change)
DECLARE #nxt_year INT = (SELECT YEAR(DATEADD(month, 1, GETDATE())))
Create start date
DECLARE #startDate DATETIME = (SELECT CAST(CAST(#cur_year AS varchar) + '-' + CAST(#cur_mont AS varchar) + '-' + CAST(13 AS varchar) AS DATETIME))
Create end date
DECLARE #endDate DATETIME = (SELECT CAST(CAST(#nxt_year AS varchar) + '-' + CAST(#nxt_mont AS varchar) + '-' + CAST(12 AS varchar) AS DATETIME))
Dates between start and end date
SELECT TOP (DATEDIFF(DAY, #startDate, #endDate) + 1)
DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY a.object_id) - 1, #startDate) AS Date,
DATENAME(DW, DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY a.object_id) - 1, #startDate)) AS Day
FROM sys.all_objects a CROSS JOIN sys.all_objects b;
DECLARE #dayStart int = 13, --The day of current month
#dayEnd int = 12, --The day of another month
#howManyMonth int = 1, --How many month to take
#dateStart date,
#dateEnd date
--Here we determine range of the dates
SELECT #dateStart = CONVERT (date,
CAST(DATEPART(Year,GETDATE()) as nvarchar(5))+ '-' +
CASE WHEN LEN(CAST(DATEPART(Month,GETDATE()) as nvarchar(5))) = 1
THEN '0'+CAST(DATEPART(Month,GETDATE()) as nvarchar(5))
ELSE CAST(DATEPART(Month,GETDATE()) as nvarchar(5)) END + '-' +
CAST (#dayStart as nvarchar(5))),
#dateEnd = CONVERT (date,
CAST(DATEPART(Year,DATEADD(Month,#howManyMonth,GETDATE())) as nvarchar(5))+ '-' +
CASE WHEN LEN(CAST(DATEPART(Month,DATEADD(Month,#howManyMonth,GETDATE())) as nvarchar(5))) = 1
THEN '0'+CAST(DATEPART(Month,DATEADD(Month,#howManyMonth,GETDATE())) as nvarchar(5))
ELSE CAST(DATEPART(Month,DATEADD(Month,#howManyMonth,GETDATE())) as nvarchar(5)) END + '-' +
CAST (#dayEnd as nvarchar(5)))
;WITH cte AS (
SELECT #dateStart as date_
UNION ALL
SELECT DATEADD(day,1,date_)
FROM cte
WHERE DATEADD(day,1,date_) <= #dateEnd
)
--Get results
SELECT DATENAME(WEEKDAY,date_) as [DayOfWeek],
COUNT(*) as [DaysCount]
FROM cte
GROUP BY DATEPART(WEEKDAY,date_),
DATENAME(WEEKDAY,date_)
ORDER BY DATEPART(WEEKDAY,date_)
OPTION (MAXRECURSION 0)
Output:
DayOfWeek DaysCount
----------- -----------
Sunday 4
Monday 4
Tuesday 4
Wednesday 5
Thursday 5
Friday 4
Saturday 4
(7 row(s) affected

Get all dates between two dates in SQL Server

How to get all the dates between two dates?
I have a variable #MAXDATE which is storing the maximum date from the table. Now I want to get the all dates between #Maxdate and GETDATE() and want to store these dates in a cursor.
So far I have done as follows:
;with GetDates As
(
select DATEADD(day,1,#maxDate) as TheDate
UNION ALL
select DATEADD(day,1, TheDate) from GetDates
where TheDate < GETDATE()
)
This is working perfectly but when I am trying to store these values in a cursor
SET #DateCurSor = CURSOR FOR
SELECT TheDate
FROM GetDates
Compilation Error
Incorrect syntax near the keyword 'SET'.
How to solve this?
My first suggestion would be use your calendar table, if you don't have one, then create one. They are very useful. Your query is then as simple as:
DECLARE #MinDate DATE = '20140101',
#MaxDate DATE = '20140106';
SELECT Date
FROM dbo.Calendar
WHERE Date >= #MinDate
AND Date < #MaxDate;
If you don't want to, or can't create a calendar table you can still do this on the fly without a recursive CTE:
DECLARE #MinDate DATE = '20140101',
#MaxDate DATE = '20140106';
SELECT TOP (DATEDIFF(DAY, #MinDate, #MaxDate) + 1)
Date = DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY a.object_id) - 1, #MinDate)
FROM sys.all_objects a
CROSS JOIN sys.all_objects b;
For further reading on this see:
Generate a set or sequence without loops – part 1
Generate a set or sequence without loops – part 2
Generate a set or sequence without loops – part 3
With regard to then using this sequence of dates in a cursor, I would really recommend you find another way. There is usually a set based alternative that will perform much better.
So with your data:
date | it_cd | qty
24-04-14 | i-1 | 10
26-04-14 | i-1 | 20
To get the quantity on 28-04-2014 (which I gather is your requirement), you don't actually need any of the above, you can simply use:
SELECT TOP 1 date, it_cd, qty
FROM T
WHERE it_cd = 'i-1'
AND Date <= '20140428'
ORDER BY Date DESC;
If you don't want it for a particular item:
SELECT date, it_cd, qty
FROM ( SELECT date,
it_cd,
qty,
RowNumber = ROW_NUMBER() OVER(PARTITION BY ic_id
ORDER BY date DESC)
FROM T
WHERE Date <= '20140428'
) T
WHERE RowNumber = 1;
You can use this script to find dates between two dates. Reference taken from this Article:
DECLARE #StartDateTime DATETIME
DECLARE #EndDateTime DATETIME
SET #StartDateTime = '2015-01-01'
SET #EndDateTime = '2015-01-12';
WITH DateRange(DateData) AS
(
SELECT #StartDateTime as Date
UNION ALL
SELECT DATEADD(d,1,DateData)
FROM DateRange
WHERE DateData < #EndDateTime
)
SELECT DateData
FROM DateRange
OPTION (MAXRECURSION 0)
GO
Just saying...here is a more simple approach to this:
declare #sdate date = '2017-06-25'
, #edate date = '2017-07-24';
with dates_CTE (date) as (
select #sdate
Union ALL
select DATEADD(day, 1, date)
from dates_CTE
where date < #edate
)
select *
from dates_CTE;
Easily create a Table Value Function that will return a table with all dates.
Input dates as string
You can customize the date in the the format you like '01/01/2017' or '01-01-2017' in string formats (103,126 ...)
Try this
CREATE FUNCTION [dbo].[DateRange_To_Table] ( #minDate_Str NVARCHAR(30), #maxDate_Str NVARCHAR(30))
RETURNS #Result TABLE(DateString NVARCHAR(30) NOT NULL, DateNameString NVARCHAR(30) NOT NULL)
AS
begin
DECLARE #minDate DATETIME, #maxDate DATETIME
SET #minDate = CONVERT(Datetime, #minDate_Str,103)
SET #maxDate = CONVERT(Datetime, #maxDate_Str,103)
INSERT INTO #Result(DateString, DateNameString )
SELECT CONVERT(NVARCHAR(10),#minDate,103), CONVERT(NVARCHAR(30),DATENAME(dw,#minDate))
WHILE #maxDate > #minDate
BEGIN
SET #minDate = (SELECT DATEADD(dd,1,#minDate))
INSERT INTO #Result(DateString, DateNameString )
SELECT CONVERT(NVARCHAR(10),#minDate,103), CONVERT(NVARCHAR(30),DATENAME(dw,#minDate))
END
return
end
To execute the function do this:
SELECT * FROM dbo.DateRange_To_Table ('01/01/2017','31/01/2017')
The output will be
01/01/2017 Sunday
02/01/2017 Monday
03/01/2017 Tuesday
04/01/2017 Wednesday
05/01/2017 Thursday
06/01/2017 Friday
07/01/2017 Saturday
08/01/2017 Sunday
09/01/2017 Monday
10/01/2017 Tuesday
11/01/2017 Wednesday
12/01/2017 Thursday
13/01/2017 Friday
14/01/2017 Saturday
15/01/2017 Sunday
16/01/2017 Monday
17/01/2017 Tuesday
18/01/2017 Wednesday
19/01/2017 Thursday
20/01/2017 Friday
21/01/2017 Saturday
22/01/2017 Sunday
23/01/2017 Monday
24/01/2017 Tuesday
25/01/2017 Wednesday
26/01/2017 Thursday
27/01/2017 Friday
28/01/2017 Saturday
29/01/2017 Sunday
30/01/2017 Monday
31/01/2017 Tuesday
This can be considered as bit tricky way as in my situation, I can't use a CTE table, so decided to join with sys.all_objects and then created row numbers and added that to start date till it reached the end date.
See the code below where I generated all dates in Jul 2018. Replace hard coded dates with your own variables (tested in SQL Server 2016):
select top (datediff(dd, '2018-06-30', '2018-07-31')) ROW_NUMBER()
over(order by a.name) as SiNo,
Dateadd(dd, ROW_NUMBER() over(order by a.name) , '2018-06-30') as Dt from sys.all_objects a
You can try this:
SET LANGUAGE SPANISH
DECLARE #startDate DATE = GETDATE() -- Your start date
DECLARE #endDate DATE = DATEADD(MONTH, 16, GETDATE()) -- Your end date
DECLARE #years INT = YEAR(#endDate) - YEAR(#startDate)
CREATE TABLE #TMP_YEARS (
[year] INT
)
-- Get all posible years between the start and end date
WHILE #years >= 0
BEGIN
INSERT INTO #TMP_YEARS
([year])
SELECT YEAR(#startDate) + #years
SET #years = #years - 1
END
;WITH [days]([day]) AS -- Posible days at a month
(
SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL -- days lower than 10
SELECT 10 UNION ALL SELECT 11 UNION ALL SELECT 12 UNION ALL SELECT 13 UNION ALL SELECT 14 UNION ALL SELECT 15 UNION ALL SELECT 16 UNION ALL SELECT 17 UNION ALL SELECT 18 UNION ALL SELECT 19 UNION ALL -- days lower than 20
SELECT 20 UNION ALL SELECT 21 UNION ALL SELECT 22 UNION ALL SELECT 23 UNION ALL SELECT 24 UNION ALL SELECT 25 UNION ALL SELECT 26 UNION ALL SELECT 27 UNION ALL SELECT 28 UNION ALL SELECT 29 UNION ALL -- days lower than 30
SELECT 30 UNION ALL SELECT 31 -- days higher 30
),
[months]([month]) AS -- All months at a year
(
SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL SELECT 12
)
SELECT CONVERT(VARCHAR, a.[year]) + '-' + REPLICATE('0', 2 - LEN(CONVERT(VARCHAR, n.[month]))) + CONVERT(VARCHAR, n.[month]) + '-' + REPLICATE('0', 2 - LEN(CONVERT(VARCHAR, d.[day]))) + CONVERT(VARCHAR, d.[day]) as [date]
FROM #TMP_YEARS a
CROSS JOIN [months] n -- Join all years with all months
INNER JOIN [days] d on DAY(EOMONTH(CONVERT(VARCHAR, a.[year]) + '-' + REPLICATE('0', 2 - LEN(CONVERT(VARCHAR, n.[month]))) + CONVERT(VARCHAR, n.[month]) + '-' + CONVERT(VARCHAR, DAY(EOMONTH(CAST(CONVERT(VARCHAR, a.[year]) + '-' + CONVERT(varchar, n.[month]) + '-15' AS DATE)))))) >= d.[day] AND -- The number of the day can't be higher than the last day of the current month and the current year
CONVERT(VARCHAR, a.[year]) + '-' + REPLICATE('0', 2 - LEN(CONVERT(VARCHAR, n.[month]))) + CONVERT(VARCHAR, n.[month]) + '-' + REPLICATE('0', 2 - LEN(CONVERT(VARCHAR, d.[day]))) + CONVERT(VARCHAR, d.[day]) <= ISNULL(#endDate, GETDATE()) AND -- The current date can't be higher than the end date
CONVERT(VARCHAR, a.[year]) + '-' + REPLICATE('0', 2 - LEN(CONVERT(VARCHAR, n.[month]))) + CONVERT(VARCHAR, n.[month]) + '-' + REPLICATE('0', 2 - LEN(CONVERT(VARCHAR, d.[day]))) + CONVERT(VARCHAR, d.[day]) >= ISNULL(#startDate, GETDATE()) -- The current date should be higher than the start date
ORDER BY a.[year] ASC, n.[month] ASC, d.[day] ASC
The output will be something like this, you can format the date as you like:
2019-01-24
2019-01-25
2019-01-26
2019-01-27
2019-01-28
2019-01-29
2019-01-30
2019-01-31
2019-02-01
2019-02-02
2019-02-03
2019-02-04
2019-02-05
2019-02-06
2019-02-07
2019-02-08
2019-02-09
...
create procedure [dbo].[p_display_dates](#startdate datetime,#enddate datetime)
as
begin
declare #mxdate datetime
declare #indate datetime
create table #daterange (dater datetime)
insert into #daterange values (#startdate)
set #mxdate = (select MAX(dater) from #daterange)
while #mxdate < #enddate
begin
set #indate = dateadd(day,1,#mxdate)
insert into #daterange values (#indate)
set #mxdate = (select MAX(dater) from #daterange)
end
select * from #daterange
end
I listed dates of 2 Weeks later. You can use variable #period OR function datediff(dd, #date_start, #date_end)
declare #period INT, #date_start datetime, #date_end datetime, #i int;
set #period = 14
set #date_start = convert(date,DATEADD(D, -#period, curent_timestamp))
set #date_end = convert(date,current_timestamp)
set #i = 1
create table #datesList(dts datetime)
insert into #datesList values (#date_start)
while #i <= #period
Begin
insert into #datesList values (dateadd(d,#i,#date_start))
set #i = #i + 1
end
select cast(dts as DATE) from #datesList
Drop Table #datesList
This is the method that I would use.
DECLARE
#DateFrom DATETIME = GETDATE(),
#DateTo DATETIME = DATEADD(HOUR, -1, GETDATE() + 2); -- Add 2 days and minus one hour
-- Dates spaced a day apart
WITH MyDates (MyDate)
AS (
SELECT #DateFrom
UNION ALL
SELECT DATEADD(DAY, 1, MyDate)
FROM MyDates
WHERE MyDate < #DateTo
)
SELECT
MyDates.MyDate
, CONVERT(DATE, MyDates.MyDate) AS [MyDate in DATE format]
FROM
MyDates;
Here is a similar example, but this time the dates are spaced one hour apart to further aid understanding of how the query works:
-- Alternative example with dates spaced an hour apart
WITH MyDates (MyDate)
AS (SELECT #DateFrom
UNION ALL
SELECT DATEADD(HOUR, 1, MyDate)
FROM MyDates
WHERE MyDate < #DateTo
)
SELECT
MyDates.MyDate
FROM
MyDates;
As you can see, the query is fast, accurate and versatile.
You can use SQL Server recursive CTE
DECLARE
#MinDate DATE = '2020-01-01',
#MaxDate DATE = '2020-02-01';
WITH Dates(day) AS
(
SELECT CAST(#MinDate as Date) as day
UNION ALL
SELECT CAST(DATEADD(day, 1, day) as Date) as day
FROM Dates
WHERE CAST(DATEADD(day, 1, day) as Date) < #MaxDate
)
SELECT* FROM dates;
declare #start_dt as date = '1/1/2021'; -- Date from which the calendar table will be created.
declare #end_dt as date = '1/1/2022'; -- Calendar table will be created up to this date (not including).
declare #dates as table (
date_id date primary key,
date_year smallint,
date_month tinyint,
date_day tinyint,
weekday_id tinyint,
weekday_nm varchar(10),
month_nm varchar(10),
day_of_year smallint,
quarter_id tinyint,
first_day_of_month date,
last_day_of_month date,
start_dts datetime,
end_dts datetime
)
while #start_dt < #end_dt
begin
insert into #dates(
date_id, date_year, date_month, date_day,
weekday_id, weekday_nm, month_nm, day_of_year, quarter_id,
first_day_of_month, last_day_of_month,
start_dts, end_dts
)
values(
#start_dt, year(#start_dt), month(#start_dt), day(#start_dt),
datepart(weekday, #start_dt), datename(weekday, #start_dt), datename(month, #start_dt), datepart(dayofyear, #start_dt), datepart(quarter, #start_dt),
dateadd(day,-(day(#start_dt)-1),#start_dt), dateadd(day,-(day(dateadd(month,1,#start_dt))),dateadd(month,1,#start_dt)),
cast(#start_dt as datetime), dateadd(second,-1,cast(dateadd(day, 1, #start_dt) as datetime))
)
set #start_dt = dateadd(day, 1, #start_dt)
end
-- sample of the data
select
top 50 *
--into master.dbo.DimDate
from #dates d
order by date_id
DECLARE #FirstDate DATE = '2018-01-01'
DECLARE #LastDate Date = '2018-12-31'
DECLARE #tbl TABLE(ID INT IDENTITY(1,1) PRIMARY KEY,CurrDate date)
INSERT #tbl VALUES( #FirstDate)
WHILE #FirstDate < #LastDate
BEGIN
SET #FirstDate = DATEADD( day,1, #FirstDate)
INSERT #tbl VALUES( #FirstDate)
END
INSERT #tbl VALUES( #LastDate)
SELECT * FROM #tbl

Summing and grouping the number of records in a month

I have the following table which has employees' absence:
RecordId EmpID ActivityCode DateFrom DateTo
---------------------------------------------------------------
666542 1511 AB 29/01/2011 02/02/2011
666986 1511 AB 11/11/2011 11/11/2011
666996 1511 EL 13/11/2011 17/11/2011
755485 1787 SL 01/11/2011 14/11/2011
758545 1787 SL 15/11/2011 03/12/2011
796956 1954 AB 09/11/2011 09/11/2011
799656 1367 AB 09/11/2011 09/11/2011
808845 1527 EL 16/11/2011 16/11/2011
823323 1527 EL 17/11/2011 17/11/2011
823669 1527 EL 18/11/2011 18/11/2011
899555 1123 AB 09/11/2011 09/12/2011
990990 1511 AB 12/11/2011 12/11/2011
Now I want a report generated by a stored proc to sum all the absence days for a specific absence code for each month in a given year, for example If i want to know the totals of absence in 2011 from the previous table I will get something similar to:
Month TotalDays
---------------------------------
JAN 2011 201
FEB 2011 36
MAR 2011 67
APR 2011 91
....
The stored proc will have two params (#Year INT, #AbsCode NVARCHAR(3)).
Please Note, Sometimes a record overlaps another month (like the first row in the example table) and that should be counted separately for each month. I have tried using loops but with no luck. I am so weak in TSQL.
UPDATE
Right now I am using a scalar value user function and a stored procedure to do the job, Its ugly and hard to trace. Here it is any way:
The User function:
ALTER FUNCTION [dbo].[GetActivityTotalDaysInMonth]
(
#ActivityCode CHAR(3)
,#Year INT
,#Month INT
)
RETURNS INT
AS
BEGIN
DECLARE #FirstDayOfMonth DATETIME
DECLARE #LastDayOfMonth DATETIME
SET #FirstDayOfMonth = CAST(CAST(#Year AS varchar) + '-' + CAST(#Month AS varchar) + '-' + CAST(1 AS varchar) AS DATETIME)
SET #LastDayOfMonth = DATEADD(s, -1, DATEADD(M, 1, #FirstDayOfMonth))
DECLARE #TotalDays INT
SELECT #TotalDays =
SUM(DATEDIFF(DAY,
(CASE WHEN ActivityDateFrom < #FirstDayOfMonth THEN #FirstDayOfMonth ELSE ActivityDateFrom END)
, (CASE WHEN ActivityDateTo > #LastDayOfMonth THEN #LastDayOfMonth ELSE ActivityDateTo END))+1)
FROM Activities
WHERE
ActivityCode=#ActivityCode
AND ((ActivityDateFrom < #FirstDayOfMonth AND ActivityDateTo >= #FirstDayOfMonth)
OR (ActivityDateFrom >= #FirstDayOfMonth AND ActivityDateTo <= #LastDayOfMonth)
OR (ActivityDateFrom <= #LastDayOfMonth AND ActivityDateTo > #LastDayOfMonth))
RETURN #TotalDays
END
Now, I call this function inside a loop in a stored procedure:
ALTER PROCEDURE GetAnnualActivityTotalDays
(
#ActivityCode CHAR(3)
,#Year INT
)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Stats TABLE
([Month] NVARCHAR(50), TotalDays INT)
DECLARE #MonthNo INT
DECLARE #Month DATETIME
SET #MonthNo = 1
WHILE #MonthNo <= 12
BEGIN
SET #Month = CAST(CAST(#Year AS varchar) + '-' + CAST(#MonthNo AS varchar) + '-' + CAST(1 AS varchar) AS DATETIME)
INSERT INTO #Stats ([Month], TotalDays)
SELECT UPPER(SUBSTRING(DATENAME(mm, #Month), 1, 3)) + ', ' + CAST(#Year AS NVARCHAR),
dbo.GetActivityTotalDaysInMonth(#ActivityCode
,#Year
,#MonthNo
,#Base)
SET #MonthNo = #MonthNo + 1
END
SELECT * FROM #Stats
END
As you can see, this is ugly code which I believe it can be done in an easier way.. Any suggestions?
You'd need to create a calendar table which will allow you to easily count the days for each month that the start and end dates encompass. For example recordid = 666542 has 3 days in January and 2 days in February. You'd be able to get that number by a query like
select calyear, calmonth, caldate
from calendar
join activities on calendar.caldate between activities.activitydatefrom and activities.activitydateto
where activitycode = 'AB'
If you wrap that in a common table expression you can perform aggregation queries afterwards on the CTE.
with mycte as (
select calyear, calmonth, caldate
from calendar
join activities on calendar.caldate between activities.activitydatefrom and activities.activitydateto
where activitycode = 'AB'
)
select calyear, calmonth, count(caldate)
from mycte
group by calyear, calmonth
order by calyear, calmonth
To generate the calendar table you can use code similar to
create table calendar (calyear, calmonth, caldate)
declare #numdays int --number of days to generate in the calendar
declare #datestart datetime --the date to begin from in the calendar
set #numdays = 365
set #datestart = 'jan 1 2011';
with num as (
select 0 number
union
select 1 number
union
select 2 number
union
select 3 number
union
select 4 number
union
select 5 number
union
select 6 number
union
select 7 number
union
select 8 number
union
select 9 number
),
numberlist as (
select ((hundreds.number * 100) + (tens.number * 10) + ones.number) n
from num hundreds
cross join num tens
cross join num ones
where ((hundreds.number * 100) + (tens.number * 10) + ones.number) < #numdays
)
insert into calendar (calyear, calmonth, caldate)
select
datepart(yy,dateadd(dd,n,#datestart)) calyear,
datepart(mm,dateadd(dd,n,#datestart)) calmonth,
dateadd(dd,n,#datestart)caldate
from numberlist

t-sql select get all Months within a range of years

I need a select to return Month and year Within a specified date range where I would input the start year and month and the select would return month and year from the date I input till today.
I know I can do this in a loop but I was wondering if it is possible to do this in a series selects?
Year Month
---- -----
2010 1
2010 2
2010 3
2010 4
2010 5
2010 6
2010 7
and so on.
Gosh folks... using a "counting recursive CTE" or "rCTE" is as bad or worse than using a loop. Please see the following article for why I say that.
http://www.sqlservercentral.com/articles/T-SQL/74118/
Here's one way to do it without any RBAR including the "hidden RBAR" of a counting rCTE.
--===== Declare and preset some obviously named variables
DECLARE #StartDate DATETIME,
#EndDate DATETIME
;
SELECT #StartDate = '2010-01-14', --We'll get the month for both of these
#EndDate = '2020-12-05' --dates and everything in between
;
WITH
cteDates AS
(--==== Creates a "Tally Table" structure for months to add to start date
-- calulated by the difference in months between the start and end date.
-- Then adds those numbers to the start of the month of the start date.
SELECT TOP (DATEDIFF(mm,#StartDate,#EndDate) + 1)
MonthDate = DATEADD(mm,DATEDIFF(mm,0,#StartDate)
+ (ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1),0)
FROM sys.all_columns ac1
CROSS JOIN sys.all_columns ac2
)
--===== Slice each "whole month" date into the desired display values.
SELECT [Year] = YEAR(MonthDate),
[Month] = MONTH(MonthDate)
FROM cteDates
;
I know this is an old question, but I'm mildly horrified at the complexity of some of the answers. Using a CTE is definitely the simplest way to go for selecting these values:
WITH months(dt) AS
(SELECT getdate() dt
UNION ALL
SELECT dateadd(month, -1, dt)
FROM months)
SELECT
top (datediff(month, '2017-07-01' /* start date */, getdate()) + 1)
YEAR(months.dt) yr, MONTH(months.dt) mnth
FROM months
OPTION (maxrecursion 0);
Just slap in whichever start date you'd like in place of the '2017-07-01' above and you're good to go with an efficient and easily-integrated solution.
Edit: Jeff Moden's answer quite effectively advocates against using rCTEs. However, in this case it appears to be a case of premature optimization - we're talking about 10's of records in all likelihood, and even if you span back to 1900 from today, it's still a minuscule hit. Using rCTEs to achieve code maintainability seems to be worth the trade if the expected result set is small.
You can use something like this: Link
To generate the equivalent of a numbers table using date ranges.
But could you please clarify your inputs and outputs?
Do you want to input a start date, for example, '2010-5-1' and end date, for example, '2010-8-1' and have it return every month between the two? Do you want to include the start month and end month, or exclude them?
Here's some code that I wrote that will quickly generate an inclusive result of every month between two dates.
--Inputs here:
DECLARE #StartDate datetime;
DECLARE #EndDate datetime;
SET #StartDate = '2010-1-5 5:00PM';
SET #EndDate = GETDATE();
--Procedure here:
WITH RecursiveRowGenerator (Row#, Iteration) AS (
SELECT 1, 1
UNION ALL
SELECT Row# + Iteration, Iteration * 2
FROM RecursiveRowGenerator
WHERE Iteration * 2 < CEILING(SQRT(DATEDIFF(MONTH, #StartDate, #EndDate)+1))
UNION ALL
SELECT Row# + (Iteration * 2), Iteration * 2
FROM RecursiveRowGenerator
WHERE Iteration * 2 < CEILING(SQRT(DATEDIFF(MONTH, #StartDate, #EndDate)+1))
)
, SqrtNRows AS (
SELECT *
FROM RecursiveRowGenerator
UNION ALL
SELECT 0, 0
)
SELECT TOP(DATEDIFF(MONTH, #StartDate, #EndDate)+1)
DATEADD(month, DATEDIFF(month, 0, #StartDate) + A.Row# * POWER(2,CEILING(LOG(SQRT(DATEDIFF(MONTH, #StartDate, #EndDate)+1))/LOG(2))) + B.Row#, 0) Row#
FROM SqrtNRows A, SqrtNRows B
ORDER BY A.Row#, B.Row#;
Code below generates the values for the range between 21 Jul 2013 and 15 Jan 2014.
I usually use it in SSRS reports for generating lookup values for the Month parameter.
declare
#from date = '20130721',
#to date = '20140115';
with m as (
select * from (values ('Jan', '01'), ('Feb', '02'),('Mar', '03'),('Apr', '04'),('May', '05'),('Jun', '06'),('Jul', '07'),('Aug', '08'),('Sep', '09'),('Oct', '10'),('Nov', '11'),('Dec', '12')) as t(v, c)),
y as (select cast(YEAR(getdate()) as nvarchar(4)) [v] union all select cast(YEAR(getdate())-1 as nvarchar(4)))
select m.v + ' ' + y.v [value_field], y.v + m.c [label_field]
from m
cross join y
where y.v + m.c between left(convert(nvarchar, #from, 112),6) and left(convert(nvarchar, #to, 112),6)
order by y.v + m.c desc
Results:
value_field label_field
---------------------------
Jan 2014 201401
Dec 2013 201312
Nov 2013 201311
Oct 2013 201310
Sep 2013 201309
Aug 2013 201308
Jul 2013 201307
you can do the following
SELECT DISTINCT YEAR(myDate) as [Year], MONTH(myDate) as [Month]
FROM myTable
WHERE <<appropriate criteria>>
ORDER BY [Year], [Month]
---Here is a version that gets the month end dates typically used for accounting purposes
DECLARE #StartDate datetime;
DECLARE #EndDate datetime;
SET #StartDate = '2010-1-1';
SET #EndDate = '2020-12-31';
--Procedure here:
WITH RecursiveRowGenerator (Row#, Iteration)
AS ( SELECT 1, 1
UNION ALL
SELECT Row# + Iteration, Iteration * 2
FROM RecursiveRowGenerator
WHERE Iteration * 2 < CEILING(SQRT(DATEDIFF(MONTH, #StartDate, #EndDate)+1))
UNION ALL SELECT Row# + (Iteration * 2), Iteration * 2
FROM RecursiveRowGenerator
WHERE Iteration * 2 < CEILING(SQRT(DATEDIFF(MONTH, #StartDate, #EndDate)+1)) )
, SqrtNRows AS ( SELECT * FROM RecursiveRowGenerator
UNION ALL SELECT 0, 0 )
SELECT TOP(DATEDIFF(MONTH, #StartDate, #EndDate)+1)
DateAdd(d,-1,DateAdd(m,1, DATEADD(month, DATEDIFF(month, 0, #StartDate) + A.Row# * POWER(2,CEILING(LOG(SQRT(DATEDIFF(MONTH, #StartDate, #EndDate)+1))/LOG(2))) + B.Row#, 0) ))
Row# FROM SqrtNRows A, SqrtNRows B ORDER BY A.Row#, B.Row#;
DECLARE #Date1 DATE
DECLARE #Date2 DATE
SET #Date1 = '20130401'
SET #Date2 = DATEADD(MONTH, 83, #Date1)
SELECT DATENAME(MONTH, #Date1) "Month", MONTH(#Date1) "Month Number", YEAR(#Date1) "Year"
INTO #Month
WHILE (#Date1 < #Date2)
BEGIN
SET #Date1 = DATEADD(MONTH, 1, #Date1)
INSERT INTO #Month
SELECT DATENAME(MONTH, #Date1) "Month", MONTH(#Date1) "Month Number", YEAR(#Date1) "Year"
END
SELECT * FROM #Month
ORDER BY [Year], [Month Number]
DROP TABLE #Month
declare #date1 datetime,
#date2 datetime,
#date datetime,
#month integer,
#nm_bulan varchar(20)
create table #month_tmp
( bulan integer null, keterangan varchar(20) null )
select #date1 = '2000-01-01',
#date2 = '2000-12-31'
select #month = month(#date1)
while (#month < 13)
Begin
IF #month = 1
Begin
SELECT #date = CAST( CONVERT(VARCHAR(25),DATEADD(dd,-(DAY(DATEADD(mm,0,#date1))-1),DATEADD(mm,0,#date1)),111) + ' 00:00:00' as DATETIME )
End
ELSE
Begin
SELECT #date = CAST( CONVERT(VARCHAR(25),DATEADD(dd,-(DAY(DATEADD(mm,#month -1,#date1))-1),DATEADD(mm,#month -1,#date1)),111) + ' 00:00:00' as DATETIME )
End
select #nm_bulan = DATENAME(MM, #date)
insert into #month_tmp
select #month as nilai, #nm_bulan as nama
select #month = #month + 1
End
select * from #month_tmp
drop table #month_tmp
go