SQL - How to get a complete month calendar table - sql

I need to generate a report that will show 7 Columns Monday to Sunday and 5 Rows, just like a normal month calendar:
I need the dates in a list table, so the desire table result should look like this:
That way my report designer will automatically create a view like above.
The above table result should be generated on the fly with no need of any database table because after this I will have to left join the dates to one of my db tables so I can populate other kind of information that I will use to display in my report for each day.
Any clue?

SQL Fiddle for arbitrary sequence of dates, no tables required.
http://sqlfiddle.com/#!3/9eecb7/275
DECLARE #dtStartDate datetime
DECLARE #dtEndDate datetime
SET #dtStartDate = '2015-05-01'
SET #dtEndDate = '2015-05-31'
SELECT
CASE DATEPART(weekday, T.DateVal)
WHEN 1 THEN 'Sunday'
WHEN 2 THEN 'Monday'
WHEN 3 THEN 'Tuesday'
WHEN 4 THEN 'Wednesday'
WHEN 5 THEN 'Thursday'
WHEN 6 THEN 'Friday'
WHEN 7 THEN 'Saturday'
END AS WeekDay,
DATEPART(day, T.DateVal) AS [Date],
DATEPART(month, T.DateVal) AS [Month],
DATEPART(year, T.DateVal) AS [Year]
FROM
(
SELECT
DATEADD(day, SEQ.SeqValue, #dtStartDate) DateVal
FROM
(
SELECT
(HUNDREDS.SeqValue + TENS.SeqValue + ONES.SeqValue) SeqValue
FROM
(
SELECT 0 SeqValue
UNION ALL
SELECT 1 SeqValue
UNION ALL
SELECT 2 SeqValue
UNION ALL
SELECT 3 SeqValue
UNION ALL
SELECT 4 SeqValue
UNION ALL
SELECT 5 SeqValue
UNION ALL
SELECT 6 SeqValue
UNION ALL
SELECT 7 SeqValue
UNION ALL
SELECT 8 SeqValue
UNION ALL
SELECT 9 SeqValue
) ONES
CROSS JOIN
(
SELECT 0 SeqValue
UNION ALL
SELECT 10 SeqValue
UNION ALL
SELECT 20 SeqValue
UNION ALL
SELECT 30 SeqValue
UNION ALL
SELECT 40 SeqValue
UNION ALL
SELECT 50 SeqValue
UNION ALL
SELECT 60 SeqValue
UNION ALL
SELECT 70 SeqValue
UNION ALL
SELECT 80 SeqValue
UNION ALL
SELECT 90 SeqValue
) TENS
CROSS JOIN
(
SELECT 0 SeqValue
UNION ALL
SELECT 100 SeqValue
UNION ALL
SELECT 200 SeqValue
UNION ALL
SELECT 300 SeqValue
UNION ALL
SELECT 400 SeqValue
UNION ALL
SELECT 500 SeqValue
UNION ALL
SELECT 600 SeqValue
UNION ALL
SELECT 700 SeqValue
UNION ALL
SELECT 800 SeqValue
UNION ALL
SELECT 900 SeqValue
) HUNDREDS
) SEQ
) T
WHERE
T.DateVal <= #dtEndDate
ORDER BY
T.DateVal ASC

Edited to add beginning blank days. Same logic can be used to append blank days if necessary.
declare #begindate date = '5-1-2015'
declare #enddate date = '6-1-2015'
create table MyCal (MyWeekday varchar(10), MyDate varchar(2), MyMonth varchar(10), MyYear int)
declare #DaysFromMonday int =
case datepart(weekday, #begindate)
when 1 then 6
else datepart(weekday, #begindate) - 2
end
declare #datecounter date = dateadd(dd, -1* #daysFromMonday, #begindate)
while #datecounter < #enddate
begin
insert into MyCal values (
datename(weekday, #datecounter)
, case when #datecounter < #begindate then '' else cast(datepart(DD, #datecounter) as varchar) end
, datename(month, #datecounter)
, datepart(YYYY, #datecounter)
)
set #datecounter = dateadd(day, 1, #datecounter)
end
select * from MyCal

You can use this piece of code.
Replace the #tmp table with a proper table name that you prefer.
declare #start_date datetime, #end_date datetime, #cur_date datetime
set #start_date = '2015-05-01'
set #end_date = '2016-04-30'
set #cur_date = #start_date
create table #tmp
(weekday varchar(10),
date int,
month varchar(10),
year int)
while #cur_date <= #end_date
begin
insert into #tmp
select datename(dw, #cur_date), datepart(day, #cur_date), datename(month, #cur_date), datepart(year, #cur_date)
set #cur_date = dateadd(dd, 1, #cur_date)
end
select * from #tmp
drop table #tmp

Try this version - You will have to run one month at a time. I've also added the blanks if the last day of the month is not a Sunday e.g. Try he month of August 2015.
declare #start_date datetime, #end_date datetime, #cur_date datetime
set #start_date = '2015-05-01'
set #end_date = '2015-05-31'
set #cur_date = #start_date
create table #tmp
(weekday varchar(10),
date varchar(2),
month varchar(10),
year int)
while datepart(dw, #cur_date) > 2
begin
insert into #tmp
select datename(dw, dateadd(dd, -(datepart(dw, #cur_date) - 2), #start_date)), '', datename(month, #start_date), datepart(year, #start_date)
set #cur_date = dateadd(dd, -1, #cur_date)
end
set #cur_date = #start_date
while #cur_date <= #end_date
begin
insert into #tmp
select datename(dw, #cur_date), datepart(day, #cur_date), datename(month, #cur_date), datepart(year, #cur_date)
set #cur_date = dateadd(dd, 1, #cur_date)
end
set #cur_date = #end_date
while datepart(dw, #cur_date) > 1
begin
insert into #tmp
select datename(dw, dateadd(dd, 1, #cur_date)), '', datename(month, #end_date), datepart(year, #end_date)
set #cur_date = dateadd(dd, 1, #cur_date)
end
select * from #tmp
drop table #tmp
Hope this helps.

Here is an alternative method using multiple CTE queries and SQL EMONTH end of month function with DATENAME SQL function
declare #date as date = dateadd(mm,0,getdate())
;with monthdates as (
SELECT dateadd(dd,1,EOMONTH(dateadd(mm,-1,#date))) firstofmonth, EOMONTH (#date) lastofmonth
), calc as (
select
firstofmonth, lastofmonth,
dateadd(dd,
-1 * case when datepart(dw,firstofmonth)-2 >= 0 then datepart(dw,firstofmonth)-2 else datepart(dw,firstofmonth)+7-2 end,
firstofmonth
) firstofcalendar,
datediff(WEEK,
dateadd(dd,
-1 * case when datepart(dw,firstofmonth)-2 >= 0 then datepart(dw,firstofmonth)-2 else datepart(dw,firstofmonth)+7-2 end,
firstofmonth
),
lastofmonth) weekcount
from monthdates
), calendar1 as (
select
firstofcalendar,
firstofmonth,
lastofmonth,
case when dateadd(dd, weekcount * 7 - 1, firstofcalendar) = lastofmonth then lastofmonth
else
dateadd(dd, (weekcount+1) * 7 - 1, firstofcalendar)
end as lastofcalendar
from calc
), calendar as (
select
datename(dw,date) dayname,
case when month(date) = month(#date) then cast(cast(date as date) as nvarchar) else '' end as date
from calendar1
cross apply [dbo].[DateTable](firstofcalendar, lastofcalendar)
)
select *, datename(mm,#date) month, datepart(yyyy,#date) year from calendar

Something like this should do you. It should work for any start-of-week you choose: you just need to set datefirst N on your connection/query to suit. If you want the week to start on Monday, use set datefirst 1.
Here's the query:
set datefirst = 1 -- start the week on Monday
declare #month date = '1 May 2015'
;with
month_days as
(
select dd = -5 union all
select dd = -4 union all
select dd = -3 union all
select dd = -2 union all
select dd = -1 union all
select dd = 0 union all
select dd = 1 union all
select dd = 2 union all
select dd = 3 union all
select dd = 4 union all
select dd = 5 union all
select dd = 6 union all
select dd = 7 union all
select dd = 8 union all
select dd = 9 union all
select dd = 10 union all
select dd = 11 union all
select dd = 12 union all
select dd = 13 union all
select dd = 14 union all
select dd = 15 union all
select dd = 16 union all
select dd = 17 union all
select dd = 18 union all
select dd = 19 union all
select dd = 20 union all
select dd = 21 union all
select dd = 22 union all
select dd = 23 union all
select dd = 24 union all
select dd = 25 union all
select dd = 26 union all
select dd = 27 union all
select dd = 28 union all
select dd = 29 union all
select dd = 30 union all
select dd = 31 union all
select dd = 32 union all
select dd = 33 union all
select dd = 34 union all
select dd = 35 union all
select dd = 36 union all
select dd = 37
),
calendar as
(
select * ,
yyyymmdd = dateadd(day,t.dd-1,#month)
from month_days t
)
select Day_Of_Week = datename(weekday,c.yyyymmdd ) ,
DD = datepart(day , c.yyyymmdd ) ,
MM = datepart(month , c.yyyymmdd ) ,
YYYY = datepart(year , c.yyyymmdd )
from calendar c
where datediff(month,#month,c.yyyymmdd) = 0 -- current month
OR ( datediff(month,#month,c.yyyymmdd) < 0 -- previous month
and datepart(weekday,c.yyyymmdd) < datepart(weekday,#month) ) -- ...to pad out the first week
OR ( datediff(month,#month,c.yyyymmdd) > 0 -- next month
and datepart(weekday,c.yyyymmdd) >= datepart(weekday,dateadd(month,1,#month)) -- ... to pad out the last week
)
order by c.yyyymmdd
Running the above query produces the expected
Day_Of_Week DD MM YYYY
=========== == == ====
Monday 27 4 2015
Tuesday 28 4 2015
Wednesday 29 4 2015
Thursday 30 4 2015
Friday 1 5 2015
Saturday 2 5 2015
Sunday 3 5 2015
Monday 4 5 2015
Tuesday 5 5 2015
Wednesday 6 5 2015
Thursday 7 5 2015
Friday 8 5 2015
Saturday 9 5 2015
Sunday 10 5 2015
Monday 11 5 2015
Tuesday 12 5 2015
Wednesday 13 5 2015
Thursday 14 5 2015
Friday 15 5 2015
Saturday 16 5 2015
Sunday 17 5 2015
Monday 18 5 2015
Tuesday 19 5 2015
Wednesday 20 5 2015
Thursday 21 5 2015
Friday 22 5 2015
Saturday 23 5 2015
Sunday 24 5 2015
Monday 25 5 2015
Tuesday 26 5 2015
Wednesday 27 5 2015
Thursday 28 5 2015
Friday 29 5 2015
Saturday 30 5 2015
Sunday 31 5 2015
Monday 1 6 2015
Tuesday 2 6 2015
Wednesday 3 6 2015
Thursday 4 6 2015
Friday 5 6 2015
Saturday 6 6 2015

Related

SQL: Create multiple rows for a record based on months between two dates

My table has records as below for different Id's and different start and end dates
ID, Startdate, Enddate
1, 2017-02-14, 2018-11-05
I want to write an SQL without using date dimension table that gives below output: Basically one record for each month between start and end date.
1, 2017, 02
1, 2017, 03
1, 2017, 04
1, 2017, 05
1, 2017, 06
1, 2017, 07
1, 2017, 08
1, 2017, 09
1, 2017, 10
1, 2017, 11
1, 2017, 12
1, 2018, 01
1, 2018, 02
1, 2018, 03
1, 2018, 04
1, 2018, 05
1, 2018, 06
1, 2018, 07
1, 2018, 09
1, 2018, 10
1, 2018, 11
Please use below query example:
set #start_date = '2017-02-14';
set #end_date = LAST_DAY('2018-11-05');
WITH RECURSIVE date_range AS
(
select MONTH(#start_date) as month_, YEAR(#start_date) as year_, DATE_ADD(#start_date, INTERVAL 1 MONTH) as next_month_date
UNION
SELECT MONTH(dr.next_month_date) as month_, YEAR(dr.next_month_date) as year_, DATE_ADD(dr.next_month_date, INTERVAL 1 MONTH) as next_month_date
FROM date_range dr
where next_month_date <= #end_date
)
select month_, year_ from date_range
order by next_month_date desc
This is what I did and it worked like a charm:
-- sample data
WITH table_data
AS (
SELECT 1 AS id
,cast('2017-08-14' AS DATE) AS start_dt
,cast('2018-12-16' AS DATE) AS end_dt
UNION ALL
SELECT 2 AS id
,cast('2017-09-14' AS DATE) AS start_dt
,cast('2019-01-16' AS DATE) AS end_dt
)
-- find minimum date from the data
,starting_date (start_date)
AS (
SELECT min(start_dt)
FROM TABLE_DATA
)
--get all months between min and max dates
,all_dates
AS (
SELECT last_day(add_months(date_trunc('month', start_date), idx * 1)) month_date
FROM starting_date
CROSS JOIN _v_vector_idx
WHERE month_date <= add_months(start_date, abs(months_between((
SELECT min(start_dt) FROM TABLE_DATA), (SELECT max(end_dt) FROM TABLE_DATA))) + 1)
ORDER BY month_date
)
SELECT id
,extract(year FROM month_date)
,extract(month FROM month_date)
,td.start_dt
,td.end_dt
FROM table_data td
INNER JOIN all_dates ad
ON ad.month_date > td.start_dt
AND ad.month_date <= last_day(td.end_dt)
ORDER BY 1
,2
You have to generate date and from that have to pick year and month
select distinct year(date),month( date) from
(select * from (
select
date_add('2017-02-14 00:00:00.000', INTERVAL n5.num*10000+n4.num*1000+n3.num*100+n2.num*10+n1.num DAY ) as date
from
(select 0 as num
union all 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) n1,
(select 0 as num
union all 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) n2,
(select 0 as num
union all 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) n3,
(select 0 as num
union all 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) n4,
(select 0 as num
union all 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) n5
) a
where date >'2017-02-14 00:00:00.000' and date < '2018-11-05'
) as t

How to select rows from db where the given date range falls?

The table structure:
ID VersionID ShiftType Duration(Hr) FromDate ToDate
**********************************************************************
1 1 WORKING 12 20150702 20170528
2 1 WORKING 12 20150702 20170528
3 2 NOTWORKING 06 20170529 20170531
4 2 WORKING 06 20170529 20170531
5 2 NOTWORKING 06 20170529 20170531
6 2 WORKING 06 20170529 20170531
7 3 WORKING 08 20170601 0
8 3 NOTWORKING 08 20170601 0
9 3 WORKING 08 20170601 0
Here you can see I have 3 version's(1,2,3) of shift(like day-shifts for 10 days and night-shifts for next 20 days etc).
The present followed shift will not have the end time(Row 7,8 and 9).
For example from 20150702 to 20170528 I followed version 1 which is of total 24 hours(1 day) working with two shifts and from 20170529 to 20170531 followed version 2 with four shifts two working and two nonworking of total 24 hours(1 day).
Now if user provides date range I should get the shift followed on that date range.
Provided date range: From 20170528 To 20170614.
Expected result: All Rows.
Working with below query:
SELECT * FROM SHIFTS
WHERE (FFROMDATE >= 20170528 AND FFROMDATE <= 20170614) OR (FTODATE >= 20170528 AND FTODATE <= 20170614)
But doesn't work for: From 20170502 To 20170514.
Expected result: Rows 1,2(But none selected).
But doesn't work for: From 20170604 To 20170714.
Expected result: Rows 7,8,9(But none selected).
Thank you.
We have 3 distinct things we're looking for:
"Find all shifts that are entirely encompassed within my date range":
(FFROMDATE <= #StartDate AND FTODATE >= #EndDate)
Find all shifts that overlap with the start
(FFROMDATE >= #StartDate AND FFROMDATE <= #EndDate)
Find all shifts that overlap with the end
(FTODATE >= #StartDate AND FTODATE <= #EndDate)
Then combine them all together into one query.
SELECT * FROM SHIFTS
WHERE
(FFROMDATE < #StartDate AND FTODATE > #EndDate) OR
(FFROMDATE >= #StartDate AND FFROMDATE <= #EndDate) OR
(FTODATE >= #StartDate AND FTODATE <= #EndDate)
Make sure you test this with boundary values and remove = to suit your exact requirements.
This works for all three cases:
select *
from shifts
where fromdate <= range_end
and range_start <= case when todate = 0 then 99991231 else todate end
Test:
with shifts (id, fromdate, todate) as (
select 1, 20150702, 20170528 from dual union all
select 2, 20150702, 20170528 from dual union all
select 3, 20170529, 20170531 from dual union all
select 4, 20170529, 20170531 from dual union all
select 5, 20170529, 20170531 from dual union all
select 6, 20170529, 20170531 from dual union all
select 7, 20170601, 0 from dual union all
select 8, 20170601, 0 from dual union all
select 9, 20170601, 0 from dual ),
ranges (range_id, range_start, range_end) as (
select 1, 20170528, 20170614 from dual union all
select 2, 20170502, 20170514 from dual union all
select 3, 20170604, 20170714 from dual )
select *
from shifts cross join ranges
where fromdate <= range_end
and range_start <= case when todate = 0 then 99991231 else todate end
order by range_id, id
Solution for oracle:
SELECT * FROM SHIFTS
WHERE FFROMDATE<=20170614 AND (FTODATE>=20170528 OR NVL(FTODATE,0)=0)
For SQL server:
Instead of NVL you can use ISNULL or CASE. I didn't try on sql server because I don't have it.

SQL Convert Week Number to Date (dd/MM)

I am trying to convert the week number (for example: 21) in SQL-Server to the date (from the Monday of that week) in dd/MM format.
I have searched online but cannot seem to find anything that I could use.
Is this something I can do?
Any help or advice is appreciated.
Thank you in advance.
Try this,
declare #wk int set #wk = 21
declare #yr int set #yr = 2016
select dateadd (week, #wk-1, dateadd (year, #yr-1900, 0)) - 4 -
datepart(dw, dateadd (week, #wk-1, dateadd (year, #yr-1900, 0)) - 4) + 1
or try this way
declare #wk int = 21
select dateadd(week,#wk-1, DATEADD(wk, DATEDIFF(wk,-1,DATEADD(yy, DATEDIFF(yy,0,getdate()), 0)), 0))
You can do it something like:
declare #Week_Number int, #Year int, #Year_Start_Day date, #Week_Day date
select
#Week_Number = 1,
#Year = 2016
select #Year_Start_Day = cast(#Year as nvarchar(4)) + '0101'
select #Week_Day = dateadd(wk, #Week_Number, #Year_Start_Day)
select dateadd(dd, 1 - datepart(weekday, #Week_Day), #Week_Day)
This will do:
DECLARE #y int = 2016,
#w int = 21
SELECT CONVERT(nvarchar(5),DATEADD(day,#w*7-(DATEPART(WEEKDAY,CAST(#y as nvarchar(4))+'-01-01')-2),CAST(#y as nvarchar(4))+'-01-01'),3)
Output:
23/05
How about this?
DECLARE #YearNum SMALLINT = 2016;
DECLARE #WeekNum TINYINT=25;
select
SUBSTRING(CONVERT(VARCHAR(10),selected_date,105),0,6) AS WeeKDate
from
(select DATEADD(dd,t4.i*10000 + t3.i*1000 + t2.i*100 + t1.i*10 + t0.i,'1970-01-01') selected_date from
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t0,
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t1,
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t2,
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t3,
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t4) v
where YEAR(selected_date)=#YearNum
AND DATEPART(WK,selected_date)=#WeekNum
AND DATEPART(WEEKDAY,selected_date)=2 -- Monday

Next 5 Available Dates

I wonder if anyone could tell me how I can get the next 5 available dates using a table which only stores the Weekend dates and Bank Holiday dates.. So it has to select the next 5 days which do not collide with any dates in the table.
I would like to see the following results from this list of dates:
07/11/2015 (Saturday)
08/11/2015 (Sunday)
09/11/2015 (Holiday)
14/11/2015 (Saturday)
15/11/2015 (Sunday)
Results:
05/11/2015 (Thursday)
06/11/2015 (Friday)
10/11/2015 (Tuesday)
11/11/2015 (Wednesday)
12/11/2015 (Thursday)`
Based on limited information, here's a quick hack:
with offsets(n) as (
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
)
select top 5 dateadd(dd, n, cast(getdate() as date)) as dt from offsets
where dateadd(dd, n, cast(getdate() as date) not in (
select dt from <exclude_dates>
)
order by dt
A possible solution is to create a table of all possible dates in a year.
select top 5 date
from possible_dates
where date not in
(select date from unavailable_dates)
and date > [insert startdate here]
order by date

Select unexist date yyyy/mm

I have a SQL query as follows:
SELECT
R.BOOK_YM, COUNT(*)
FROM
#TB_TRANSED_GUI_AMOUNT AS R
GROUP
BY R.BOOK_YM
The selected results like as follows:
BOOK_YM | count(*)
---------+---------
201411 | 51
201412 | 142
201501 | 1
201506 | 1
How do I modify the result like the follows:
BOOK_YM | count(*)
--------+-----------
201411 | 51
201412 | 142
201501 | 1
201502 | 0
201503 | 0
201504 | 0
201505 | 0
201506 | 1
You can use the following to get a table of all YYYYMM between 2 dates and use the resulting #YMList variable in a join:
declare #StartDate date = cast(dateadd(month, -12, GetDate()) as date)
declare #EndDate date = cast(GetDate() as date)
declare #YMList TABLE (YYYYMM INT)
;with dates as (
SELECT cast(#StartDate as Date) [date]
UNION ALL
SELECT DATEADD(month,1,t.date)
FROM dates t
WHERE t.[date] < #EndDate
)
insert #YMList (YYYYMM)
select
YEAR([Date]) * 100 + MONTH([Date]) AS YYYYMM
from dates
WHERE [Date] < #EndDate
OPTION (MAXRECURSION 10000)
Try this: (I think BOOK_YM is a varchar field if not CAST it)
SELECT
Y.YYYY + M.MM AS BOOK_YM,
COUNT(R.BOOK_YM) AS CNT
FROM
(SELECT SUBSTRING(BOOK_YM, 1, 4) As YYYY
FROM t
GROUP BY SUBSTRING(BOOK_YM, 1, 4)) AS Y
CROSS JOIN
(SELECT '01' As MM
UNION ALL SELECT '02'
UNION ALL SELECT '03'
UNION ALL SELECT '04'
UNION ALL SELECT '05'
UNION ALL SELECT '06'
UNION ALL SELECT '07'
UNION ALL SELECT '08'
UNION ALL SELECT '09'
UNION ALL SELECT '10'
UNION ALL SELECT '11'
UNION ALL SELECT '12') AS M
LEFT JOIN
t AS R
ON Y.YYYY + M.MM = R.BOOK_YM
WHERE
Y.YYYY + M.MM BETWEEN (SELECT MIN(BOOK_YM) FROM t) AND (SELECT MAX(BOOK_YM) FROM t)
GROUP BY
Y.YYYY + M.MM