sql server print all dates between two date columns - sql

i have a table containing 3 columns. ID,Start_Date,End_Date.i want to print all the days between Start_Date and End_Date along with ID.
For Example i have table
+----+------------+------------+
| ID | Start_Date | End_Date |
+----+------------+------------+
| 1 | 2017-01-01 | 2017-01-05 |
+----+------------+------------+
and i want result like
+----+------------+
| ID | Date |
+----+------------+
| 1 | 2017-01-01 |
| 1 | 2017-01-02 |
| 1 | 2017-01-03 |
| 1 | 2017-01-04 |
| 1 | 2017-04-05 |
+----+------------+

Use Common table expression :
DECLARE #StartDT DATETIME = '2017-01-01'
DECLARE #EndDT DATETIME = '2017-01-05'
DECLARE #Id INT = 1
;WITH CTE (_id , _Date)AS
(
SELECT #Id , #StartDT
UNION ALL
SELECT #Id , DATEADD(DAY,1,_Date)
FROM CTE
WHERE _Date < #EndDT
)
SELECT * FROM CTE

Create a so called tally table (see e. g. here: https://dwaincsql.com/2014/03/27/tally-tables-in-t-sql/) and use it to create all dates between "from" and "to" date.
SELECT TOP 1000000 N=IDENTITY(INT, 1, 1)
INTO dbo.Tally
FROM master.dbo.syscolumns a CROSS JOIN master.dbo.syscolumns b;
go
declare #dateFrom datetime = '20170101';
declare #dateTo datetime = '20170105';
select dateadd(day, N - 1, #dateFrom)
from Tally
where N between 1 and datediff(day, #dateFrom, #dateTo) + 1
Or:
select dateadd(day, t.N - 1, o.DateFrom)
from Tally t
cross join OtherTable o
where t.N between 1 and datediff(day, o.DateFrom, o.DateTo) + 1
A tally table is very useful for such cases, it could also be filled with dates in a second column, starting from 1900-01-01 or so.

DECLARE #StartDT DATETIME = '2017-01-01'
DECLARE #EndDT DATETIME = '2017-01-05'
DECLARE #Id INT = 1
SELECT RANK() OVER (
ORDER BY (SELECT 1)) AS SeqNo
,CAST (Start_Date AS DATE) AS Start_Date
FROM (
SELECT #StartDT + Row_Number() OVER (ORDER BY Rno) - 1 AS Start_Date
FROM (
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 0)) - 1 AS Rno
FROM master..spt_values
) Dt
) Dt2
WHERE Dt2.Start_Date <= #EndDT
OutPut
SeqNo Start_Date
--------------------
1 2017-01-01
1 2017-01-02
1 2017-01-03
1 2017-01-04
1 2017-01-05

Related

Getting duplicate dates while repeating the rows

I'm trying to rotate or repeat the sfhitId(1,2) between the date range for each employee.
Everything is working fine but I don't know how to stop getting duplicate dates means why I am getting dublicate dates and how can I rid from it...
Can anyone help me with this?
My only requirement is if any employeeid has 1 or more than 1 shift then shiftId should repeat between given date range for each employee.
DECLARE #TempTable TABLE (EmployeeId int, ShiftId int)
INSERT INTO #TempTable
SELECT 1 , 1
UNION ALL
SELECT 1, 3
UNION ALL
SELECT 2, 3
DECLARE #StartDate datetime = '2020-03-05',
#EndDate datetime = '2020-03-09';
WITH theDates AS
(
SELECT #StartDate AS theDate
UNION ALL
SELECT DATEADD(DAY, 1, theDate)
FROM theDates
WHERE DATEADD(DAY, 1, theDate) <= #EndDate
)
SELECT theDate, EmployeeID, SHiftId
FROM theDates
CROSS APPLY #TempTable
ORDER BY EmployeeId, theDate
OPTION (MAXRECURSION 0);
and I want result like this...
theDate EmployeeID SHiftId
2020-03-05 1 1
2020-03-06 1 3
2020-03-07 1 1
2020-03-08 1 3
2020-03-09 1 1
2020-03-05 2 3
2020-03-06 2 3
2020-03-07 2 3
2020-03-08 2 3
2020-03-09 2 3
Use window functions to join the 2 tables:
DECLARE #TempTable TABLE (EmployeeId int, ShiftId int)
INSERT INTO #TempTable
SELECT 1 , 1
UNION ALL
SELECT 1, 3
UNION ALL
SELECT 2, 3
DECLARE #StartDate datetime = '2020-03-05',
#EndDate datetime = '2020-03-09';
WITH
theDates AS (
SELECT 1 rn, #StartDate AS theDate
UNION ALL
SELECT rn + 1, DATEADD(DAY, 1, theDate)
FROM theDates
WHERE DATEADD(DAY, 1, theDate) <= #EndDate
),
theShifts AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY EmployeeId ORDER BY ShiftId) rn,
COUNT(*) OVER (PARTITION BY EmployeeId) counter
FROM #TempTable
)
SELECT d.theDate, s.EmployeeID, s.ShiftId
FROM theDates d INNER JOIN theShifts s
ON s.rn % s.counter = d.rn % s.counter
ORDER BY s.EmployeeId, d.theDate
OPTION (MAXRECURSION 0);
See the demo.
Results:
> theDate | EmployeeID | ShiftId
> :---------------------- | ---------: | ------:
> 2020-03-05 00:00:00.000 | 1 | 1
> 2020-03-06 00:00:00.000 | 1 | 3
> 2020-03-07 00:00:00.000 | 1 | 1
> 2020-03-08 00:00:00.000 | 1 | 3
> 2020-03-09 00:00:00.000 | 1 | 1
> 2020-03-05 00:00:00.000 | 2 | 3
> 2020-03-06 00:00:00.000 | 2 | 3
> 2020-03-07 00:00:00.000 | 2 | 3
> 2020-03-08 00:00:00.000 | 2 | 3
> 2020-03-09 00:00:00.000 | 2 | 3

How to build a recursive table in inline-table-valued function (iTVF)?

I have four parameters to be accepted by the function:
#FromDate DATE, #ToDate DATE, #DataInterval INT, #RangeInterval INT
The result set is obtained using a CTE:
;WITH Dates_CTE (FromDt, ToDt) AS
(
SELECT
DATEADD(D,-#RangeInterval,#FromDate) AS [FromDt],
#FromDate AS [ToDt]
UNION ALL
SELECT
DATEADD(D,#DataInterval,D.FromDt) AS [FromDt],
DATEADD(D,#DataInterval,D.ToDt) AS [ToDt]
FROM Dates_CTE D
WHERE D.ToDt <= #ToDate
)
But using a CTE makes it a multi-table-valued function and it becomes a performance-killer.
Is there another way to achieve this in an inline function?
The example of the desired result with the input #FromDate = '20180701', #ToDate = '20180901', #DataInterval = 5, #RangeInterval = 30 is:
+------------+------------+
| FromDate | ToDate |
+------------+------------+
| 2018-06-01 | 2018-07-01 |
| 2018-06-06 | 2018-07-06 |
| 2018-06-11 | 2018-07-11 |
| 2018-06-16 | 2018-07-16 |
| 2018-06-21 | 2018-07-21 |
| 2018-06-26 | 2018-07-26 |
| 2018-07-01 | 2018-07-31 |
| 2018-07-06 | 2018-08-05 |
| 2018-07-11 | 2018-08-10 |
| 2018-07-16 | 2018-08-15 |
| 2018-07-21 | 2018-08-20 |
| 2018-07-26 | 2018-08-25 |
| 2018-07-31 | 2018-08-30 |
| 2018-08-05 | 2018-09-04 |
+------------+------------+
How can we achieve this result in an iTVF? Thanks in advance.
Really you need no recursion. Use a table of numbers. Here the table of numbers is generated on the fly it can be persisted in the db for better perfomance.
DECLARE #FromDate DATE, #ToDate DATE, #DataInterval INT, #RangeInterval INT;
SELECT #FromDate = '20180701', #ToDate = '20180901', #DataInterval = 5, #RangeInterval = 30;
-- table of 1000 numbers starting 0
with t0(n) as (
select n
from (
values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)
) t(n)
),nmbs as(
select row_number() over(order by t1.n) - 1 n
from t0 t1, t0 t2, t0 t3
)
select
DATEADD(D, #DataInterval*n - #RangeInterval, #FromDate) FromDate
,DATEADD(D, #DataInterval*n, #FromDate) ToDate
from nmbs
where DATEADD(D, #DataInterval*n , #FromDate) <= #ToDate;
You may need to adjust where conditions per your requierments.
Demo with the persisted table of numbers (aka tally table).
You can use a tally table inside your inline table-value function.
CREATE TABLE Numbers (N INT PRIMARY KEY NOT NULL);
INSERT INTO Numbers
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) -1 N
FROM
(
VALUES (NULL), (NULL), (NULL), (NULL), (NULL)
) T1(V) CROSS JOIN
(
VALUES (NULL), (NULL), (NULL), (NULL), (NULL)
) T2(VV) CROSS JOIN
(
VALUES (NULL), (NULL), (NULL), (NULL), (NULL) --Add Values as needed
) T3(VVV);
Then create your function as
CREATE FUNCTION dbo.MyFunc
(
#FromDate DATE,
#ToDate DATE,
#DataInterval INT,
#RangeInterval INT
)
RETURNS TABLE
RETURN
SELECT DATEADD(Day, (#DataInterval * N) - #RangeInterval, #FromDate) FromDate,
DATEADD(Day, #DataInterval * N, #FromDate) ToDate
FROM Numbers
WHERE DATEADD(Day, #DataInterval * N, #FromDate) <= #ToDate;
And finally, use it as
SELECT *
FROM dbo.MyFunc ('20180701', '20180901', 5, 30);
Online Demo
you can use while :
declare #FromDate DATE = '20180701', #ToDate DATE= '20180901', #DataInterval INT= 5, #RangeInterval INT= 30,#date date ,#r int =0
declare #table table (d1 date, d2 date)
set #date= #FromDate
while (#date <#ToDate )
begin
insert into #table
select dateadd(d,#r,#FromDate ),DATEADD(D,-#RangeInterval+#r,#FromDate)
set #r=#r+#DataInterval
set #date = dateadd(d,#r,#FromDate )
end
select * from #table

Create a calendar day table

I am trying to write a code where i can plug in a date and my table below will populate the expected date table with all the date for the particular month from CD1(Calendar Day 1) all the way to CD30 or CD31 or in February case CD28. I know i should begin my code with something like
Declare #startdate as datetime
Set #startdate = '20170401'
But after that I get confused with the DateAdd and DatePart code to create this query to produce the results
date rule | expected date |
----------------------------
| CD1 | 4/1/2017 |
| CD2 | 4/2/2017 |
| CD3 | 4/3/2017 |
| CD4 | 4/4/2017 |
| CD5 | 4/5/2017 |
| CD6 | 4/6/2017 |
Can anyone provide any assistance?
Try this,
Declare #startdate as datetime
Set #startdate = '20170401'
;with cte as
(
select #startdate dt,1 ruleid
union ALL
select dateadd(day,1,dt)
,ruleid+1
from cte
where
dt<dateadd(day,-1,dateadd(month, datediff(month,0,#startdate)+1,0))
)
select *,'CD'+cast(ruleid as varchar) CalenderRule
from cte
DECLARE #startdate datetime = '2017-04-01'
DECLARE #startdate_for_loop datetime
SET #startdate_for_loop = #startdate
CREATE TABLE #T (date_rule nvarchar(100), exp_date datetime)
declare #x int = 1
WHILE MONTH(#startdate) = MONTH(#startdate_for_loop)
BEGIN
INSERT INTO #T VALUES ('CD' + CAST(#x as nvarchar(max)), #startdate_for_loop)
SET #x = #x + 1
SET #startdate_for_loop = DATEADD(DD, 1, #startdate_for_loop)
END
SELECT * FROM #T
Try below query, this will give you the required output:
DECLARE #STARTDATE DATETIME
SET #STARTDATE= CAST(MONTH(CURRENT_TIMESTAMP) AS VARCHAR(100))+'/'+'01'+'/'+CAST(YEAR(CURRENT_TIMESTAMP) AS VARCHAR(100))
;WITH MONTHDATA
AS
(SELECT #STARTDATE MONTHDATE
UNION ALL
SELECT DATEADD(D,1,MONTHDATE) FROM MONTHDATA WHERE MONTHDATE<DATEADD(D,-1,DATEADD(M,1,#STARTDATE))
)
SELECT 'CD'+CAST( (ROW_NUMBER()OVER (ORDER BY MONTHDATE)) AS VARCHAR(100))DATE_RULE,CONVERT(VARCHAR,MONTHDATE,101)MONTHDATE FROM MONTHDATA
OUTPUT
----------------------
DATE_RULE MONTHDATE
----------------------
CD1 03/01/2017
CD2 03/02/2017
CD3 03/03/2017
.
.
.
CD29 03/29/2017
CD30 03/30/2017
CD31 03/31/2017
----------------------

How can I solve this query?

i have one table contain 2 fields named as date,val
datas are
date val
2014-08-01 A
2014-08-02 B
2014-08-03 A
2014-08-04 A
2014-08-05 B
2014-08-06 B
2014-08-07 A
2014-08-08 A
2014-08-09 B
2014-08-10 A
2014-08-11 A
i want a table the output like this
MIN MAX A B
2014-08-01 2014-08-05 3 2
2014-08-06 2014-08-11 4 2
The result will be calculated based on number of days, like for 5 days. in this question I selected 5 days interval.From this table i want to find the count of value 'A' and the count of value 'B' in the selected interval.
Can any one find a solution for this. Any help will be appreciated.
i think this will help you :
DECLARE #MinDate DATETIME
DECLARE #MaxDate DATETIME
DECLARE #StartDate DATETIME
DECLARE #DayCount INT
DECLARE #A_Count INT
DECLARE #B_Count INT
SET #DayCount = 5
SELECT TOP 1 #MinDate=[date] FROM table_1 ORDER BY date
SELECT TOP 1 #MaxDate=[date] FROM table_1 ORDER BY date DESC
CREATE TABLE #temp_table (
_Min DATETIME,
_Max DATETIME,
_A INT,
_B INT
)
SET #StartDate=#MinDate
WHILE #StartDate < #MaxDate
BEGIN
SELECT #A_Count = COUNT(*) FROM table_1 WHERE date BETWEEN #StartDate AND DATEADD(dd,#DayCount-1,#StartDate) AND val = 'A'
SELECT #B_Count = COUNT(*) FROM table_1 WHERE date BETWEEN #StartDate AND DATEADD(dd,#DayCount-1,#StartDate) AND val = 'B'
INSERT INTO #temp_table VALUES (#StartDate,DATEADD(dd,#DayCount-1,#StartDate),#A_Count,#B_Count)
SET #StartDate = DATEADD(dd,#DayCount,#StartDate)
END
SELECT * FROM #temp_table
DROP TABLE #temp_table
and SqlFiddle demo: click here
You can execute a query like this:
SELECT MAX(date), MIN(date), val, COUNT(val) AS occurence
FROM table
WHERE date>minDate AND date<maxDate
GROUP BY val
where minDate and maxDate are your varables.
Result will be read in this way: Value "val" apper "occurence" time between minDate and maxDate
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(date DATE NOT NULL
,val CHAR(1) NOT NULL
,PRIMARY KEY (date)
);
INSERT INTO my_table VALUES
('2014-08-01','A'),
('2014-08-02','B'),
('2014-08-03','A'),
('2014-08-04','A'),
('2014-08-05','B'),
('2014-08-06','B'),
('2014-08-07','A'),
('2014-08-08','A'),
('2014-08-09','B'),
('2014-08-10','A'),
('2014-08-11','A');
SELECT MIN(date) min_date
, MAX(date) max_date
, SUM(val = 'A') A
, SUM(val = 'B') B
FROM my_table
GROUP
BY CEILING(TO_DAYS(date)/5)*5;
+------------+------------+------+------+
| min_date | max_date | A | B |
+------------+------------+------+------+
| 2014-08-01 | 2014-08-05 | 3 | 2 |
| 2014-08-06 | 2014-08-10 | 3 | 2 |
| 2014-08-11 | 2014-08-11 | 1 | 0 |
+------------+------------+------+------+

Query to return all the days of a month

This problem is related to this, which has no solution in sight: here
I have a table that shows me all sessions of an area.
This session has a start date.
I need to get all the days of month of the start date of the session by specific area (in this case)
I have this query:
SELECT idArea, idSession, startDate FROM SessionsPerArea WHERE idArea = 1
idArea | idSession | startDate |
1 | 1 | 01-01-2013 |
1 | 2 | 04-01-2013 |
1 | 3 | 07-02-2013 |
And i want something like this:
date | Session |
01-01-2013 | 1 |
02-01-2013 | NULL |
03-01-2013 | NULL |
04-01-2013 | 1 |
........ | |
29-01-2013 | NULL |
30-01-2013 | NULL |
In this case, the table returns me all the days of January.
The second column is the number of sessions that occur on that day, because there may be several sessions on the same day.
Anyone can help me?
Please try:
DECLARE #SessionsPerArea TABLE (idArea INT, idSession INT, startDate DATEtime)
INSERT #SessionsPerArea VALUES (1,1,'2013-01-01')
INSERT #SessionsPerArea VALUES (1,2,'2013-01-04')
INSERT #SessionsPerArea VALUES (1,3,'2013-07-02')
DECLARE #RepMonth as datetime
SET #RepMonth = '01/01/2013';
WITH DayList (DayDate) AS
(
SELECT #RepMonth
UNION ALL
SELECT DATEADD(d, 1, DayDate)
FROM DayList
WHERE (DayDate < DATEADD(d, -1, DATEADD(m, 1, #RepMonth)))
)
SELECT *
FROM DayList t1 left join #SessionsPerArea t2 on t1.DayDate=startDate and t2.idArea = 1
This will work:
DECLARE #SessionsPerArea TABLE (idArea INT, idSession INT, startDate DATE)
INSERT #SessionsPerArea VALUES
(1,1,'2013-01-01'),
(1,2,'2013-01-04'),
(1,3,'2013-07-02')
;WITH t1 AS
(
SELECT startDate
, DATEADD(MONTH, DATEDIFF(MONTH, '1900-01-01', startDate), '1900-01-01') firstInMonth
, DATEADD(DAY, -1, DATEADD(MONTH, DATEDIFF(MONTH, '1900-01-01', startDate) + 1, '1900-01-01')) lastInMonth
, COUNT(*) cnt
FROM #SessionsPerArea
WHERE idArea = 1
GROUP BY
startDate
)
, calendar AS
(
SELECT DISTINCT DATEADD(DAY, c.number, t1.firstInMonth) d
FROM t1
JOIN master..spt_values c ON
type = 'P'
AND DATEADD(DAY, c.number, t1.firstInMonth) BETWEEN t1.firstInMonth AND t1.lastInMonth
)
SELECT d date
, cnt Session
FROM calendar c
LEFT JOIN t1 ON t1.startDate = c.d
It uses simple join on master..spt_values table to generate rows.
Just an example of calendar table. To return data for a month adjust the number of days between < 32, for a year to 365+1. You can calculate the number of days in a month or between start/end dates with query. I'm not sure how to do this in SQL Server. I'm using hardcoded values to display all dates in Jan-2013. You can adjust start and end dates for diff. month or to get start/end dates with queries...:
WITH data(r, start_date) AS
(
SELECT 1 r, date '2012-12-31' start_date FROM any_table --dual in Oracle
UNION ALL
SELECT r+1, date '2013-01-01'+r-1 FROM data WHERE r < 32 -- number of days between start and end date+1
)
SELECT start_date FROM data WHERE r > 1
/
START_DATE
----------
1/1/2013
1/2/2013
1/3/2013
...
...
1/31/2013