Find all rows where start date is before a prior end date - sql

Is there a way to pull back all records that have overlapping datetimes based on a user?
For instance;
TableA has the following rows;
TrainerID StartTime EndTime
1234 10-1-2015 08:30 10-1-2015 09:00
1234 10-1-2015 08:45 10-1-2015 09:15
1234 10-1-2015 09:30 10-1-2015 10:00
2345 10-1-2015 08:45 10-1-2015 09:15
2345 10-1-2015 09:30 10-1-2015 10:00
I need a query that can pull ONLY the following record because it's start time is before the previous end time for the trainer (double booked):
1234 10-1-2015 08:45 10-1-2015 09:15

The EXIST code below should give you that answer. The code ensures that the start time of the clashing entry is before the start of the main list entry while the start time of the clash is still after the start time of the mail list entry.
SELECT *
FROM tblTest clashing
WHERE EXISTS
(
SELECT 1
FROM tblTest mainlist
WHERE clashing.trainderid = mainlist.trainderid
AND clashing.starttime < mainlist.endtime
AND clashing.starttime > mainlist.starttime
)
This can also be written with an IN statement, but EXIST is much more efficient

To remove overlapping dates you can use:
Demo
CREATE TABLE #TABLEA( TrainerID INT, StartDate DATETIME, EndDate DATETIME);
INSERT INTO #TABLEA
SELECT 1234, '10-1-2015 08:30', '10-1-2015 09:00'
UNION ALL SELECT 1234 , '10-1-2015 08:45', '10-1-2015 09:15'
UNION ALL SELECT 1234 , '10-1-2015 09:30', '10-1-2015 10:00'
UNION ALL SELECT 2345 , '10-1-2015 08:45', '10-1-2015 09:15'
UNION ALL SELECT 2345 , '10-1-2015 09:30', '10-1-2015 10:00';
SELECT
D.TrainerID,
[StartTime] = D.StartDate,
[EndTime] = (SELECT MIN(E.EndDate)
FROM #TABLEA E
WHERE E.EndDate >= D.EndDate
AND E.TrainerID = D.TrainerID
AND NOT EXISTS (SELECT 1
FROM #TABLEA E2
WHERE E.StartDate < E2.StartDate
AND E.EndDate > E2.StartDate
AND E.TrainerID = E2.TrainerID))
FROM #TABLEA D
WHERE NOT EXISTS ( SELECT 1
FROM #TABLEA D2
WHERE D.StartDate < D2.EndDate
AND D.EndDate > D2.EndDate
AND D.TrainerID = D2.TrainerID);

You can use below code to get the required row, however based on your logic row from next trainer id (i.e. 2345) will also be qualified
DECLARE #Trainers TABLE
(
TrainerId INT,
Start_Time datetime,
End_Time datetime
)
INSERT INTO #Trainers VALUES
(1234,'10-1-2015 08:30','10-1-2015 09:00 '),
(1234,'10-1-2015 08:45','10-1-2015 09:15'),
(1234,'10-1-2015 09:30','10-1-2015 10:00'),
(2345 ,' 10-1-2015 08:45','10-1-2015 09:15'),
(2345 ,' 10-1-2015 09:30 ',' 10-1-2015 10:00')
;WITH TrainersTemp AS
(
SELECT *, ROW_NUMBER() OVER ( ORDER BY trainerid) AS rn
FROM #Trainers
)
SELECT CX.TrainerId, CX.Start_Time, CX.End_Time
FROM TrainersTemp CX JOIN TrainersTemp CY
ON CX.rn = CY.rn + 1
WHERE CY.End_Time < CX.Start_Time
Demo (SQL fiddle is down again)
or if you want to see all rows except the faulty one then use below code
;WITH TrainersTempAll AS
(
SELECT *, ROW_NUMBER() OVER ( ORDER BY trainerid) AS rn
FROM #Trainers
)
SELECT CX.TrainerId, CX.Start_Time, CX.End_Time
FROM TrainersTempAll CX JOIN TrainersTempAll CY
ON CX.rn = CY.rn + 1

Firstly you should sort by trainerId and Start_time. And then join two tables with correct condition.
Try this query:
;WITH TrainersTemp AS
(
SELECT *, ROW_NUMBER() OVER ( ORDER BY trainerid, Start_Time) AS row_num
FROM Trainers
)
select t2.* from TrainersTemp t1
join TrainersTemp t2 on t1.TrainerId = t2.TrainerId and t1.row_num = t2.row_num-1
where t2.Start_Time<t1.End_Time

As you use SQL Server 2012 you can use LAG function, which would be likely more efficient than self-join. The query becomes pretty simple as well.
For each row LAG gives you EndTime from the previous row (partitioned by TrainerID). Then just compare StartTime from the current row with EndTime from the previous row.
SQL Fiddle
WITH
CTE
AS
(
SELECT
TrainerID
,StartTime
,EndTime
,LAG(EndTime) OVER(PARTITION BY TrainerID ORDER BY StartTime) AS PrevEndTime
FROM TableA
)
SELECT
TrainerID
,StartTime
,EndTime
FROM CTE
WHERE StartTime < PrevEndTime
;
Results:
| TrainerID | StartTime | EndTime |
|-----------|---------------------------|---------------------------|
| 1234 | October, 01 2015 08:45:00 | October, 01 2015 09:15:00 |

Related

If time col is null then replace with 00:00

I am working on a hospital database and table details are
Patient number Dischargeto Date Time
212 Hospital1 16/10/2018 14:00:00
212 Hospital2 18/10/2018 10:00:00
212 Hospital3 20/10/2018 18:00:00
212 Home 22/10/2018 10:00:00
213 Hostpital1 11/11/2018 11:00:00
213 Death 14/11/2018 18:00:00
214 Hospital 1 28/12/2011 14:00:00
214 Home 05/01/2012 NULL
Info:
Final destination of the patient
212 is Home
213 is Death
214 is home
I want patients whose final destination is not death
so I wrote this query
select *
from
(select
Patient number, DischargeTo, Date, Time,
ROW_NUMBER() OVER(PARTITION BY Patientnumber order by Date desc, Time desc) as testcount
from tablename) abc
where testcount = 1
and
DischargeTo not like '%Death%'
results are not correct where time is null. I want to convert
if time is null then it converts to 00:00:00
and so the sorting could be corrected.
Thanks behorehand
make your date time as valid sql datetime before sorting.
select *
from
(select
Patient number, DischargeTo, Date, Time,
ROW_NUMBER() OVER(PARTITION BY Patientnumber order by cast(concat(RIGHT([Date],4) + '' + SUBSTRING([Date],4,2) + '' + LEFT([Date],2), ' ', [Time]) as datetime)) as testcount
from tablename) abc
where testcount = 1
and
DischargeTo not like '%Death%'
I had switch the day and month in the date string as the datetime conversion was giving out-of-bounds error. Try the following:
with hospitaldt(PatientId, DischargeTo, dt) as
(
select PatientId, DischargeTo, convert(datetime, concat(Date, " ", isnull(Time, "00:00:00")))
from hospital
)
select t1.PatientId, t1.DischargeTo, t1.dt
from hospitaldt t1
where t1.dt =
(
select max(t2.dt)
from hospitaldt t2
where t2.DischargeTo not like "%Death%"
and t2.PatientId = t1.PatientId
I think "death" only happens once, so you don't need to look for the final status. Just check for whether death is ever there:
select patient_number
from tablename
group by patient_number
having sum(case when dischargeto = 'Death' then 1 else 0 end) = 0;

Group time series by time intervals (e.g. days) with aggregate of duration

I have a table containing a time series with following information. Each record represents the event of "changing the mode".
Timestamp | Mode
------------------+------
2018-01-01 12:00 | 1
2018-01-01 18:00 | 2
2018-01-02 01:00 | 1
2018-01-02 02:00 | 2
2018-01-04 04:00 | 1
By using the LEAD function, I can create a query with the following result. Now each record contains the information, when and how long the "mode was active".
Please check the 2nd and the 4th record. They "belong" to multiple days.
StartDT | EndDT | Mode | Duration
------------------+------------------+------+----------
2018-01-01 12:00 | 2018-01-01 18:00 | 1 | 6:00
2018-01-01 18:00 | 2018-01-02 01:00 | 2 | 7:00
2018-01-02 01:00 | 2018-01-02 02:00 | 1 | 1:00
2018-01-02 02:00 | 2018-01-04 04:00 | 2 | 50:00
2018-01-04 04:00 | (NULL) | 1 | (NULL)
Now I would like to have a query that groups the data by day and mode and aggregates the duration.
This result table is needed:
Date | Mode | Total
------------+------+-------
2018-01-01 | 1 | 6:00
2018-01-01 | 2 | 6:00
2018-01-02 | 1 | 1:00
2018-01-02 | 2 | 23:00
2018-01-03 | 2 | 24:00
2018-01-04 | 2 | 04:00
I didn't known how to handle the records that "belongs" to multiple days. Any ideas?
create table ChangeMode ( ModeStart datetime2(7), Mode int )
insert into ChangeMode ( ModeStart, Mode ) values
( '2018-11-15T21:00:00.0000000', 1 ),
( '2018-11-16T17:18:19.1231234', 2 ),
( '2018-11-16T18:00:00.5555555', 1 ),
( '2018-11-16T18:00:01.1234567', 2 ),
( '2018-11-16T19:02:22.8888888', 1 ),
( '2018-11-16T20:00:00.9876543', 2 ),
( '2018-11-17T09:00:00.0000000', 1 ),
( '2018-11-17T23:23:23.0230450', 2 ),
( '2018-11-19T17:00:00.0172839', 1 ),
( '2018-11-20T03:07:00.7033077', 2 )
;
with
-- Determine the earliest and latest dates.
-- Cast to date to remove the time portion.
-- Cast results back to datetime because we're going to add hours later.
MinMaxDates
as
(select cast(min(cast(ModeStart as date))as datetime) as MinDate,
cast(max(cast(ModeStart as date))as datetime) as MaxDate from ChangeMode),
-- How many days have passed during that period
Dur
as
(select datediff(day,MinDate,MaxDate) as Duration from MinMaxDates),
-- Create a list of numbers.
-- These will be added to MinDate to get a list of dates.
NumList
as
( select 0 as Num
union all
select Num+1 from NumList,Dur where Num<Duration ),
-- Create a list of dates by adding those numbers to MinDate
DayList
as
( select dateadd(day,Num,MinDate)as ModeDate from NumList, MinMaxDates ),
-- Create a list of day periods
PeriodList
as
( select ModeDate as StartTime,
dateadd(day,1,ModeDate) as EndTime
from DayList ),
-- Use LEAD to get periods for each record
-- Final record would return NULL for ModeEnd
-- We replace that with end of last day
ModePeriodList
as
( select ModeStart,
coalesce( lead(ModeStart)over(order by ModeStart),
dateadd(day,1,MaxDate) ) as ModeEnd,
Mode from ChangeMode, MinMaxDates ),
ModeDayList
as
( select * from ModePeriodList, PeriodList
where ModeStart<=EndTime and ModeEnd>=StartTime
),
-- Keep the later of the mode start time, and the day start time
-- Keep the earlier of the mode end time, and the day end time
ModeDayPeriod
as
( select case when ModeStart>=StartTime then ModeStart else StartTime end as StartTime,
case when ModeEnd<=EndTime then ModeEnd else EndTime end as EndTime,
Mode from ModeDayList ),
SumDurations
as
( select cast(StartTime as date) as ModeDate,
Mode,
DateDiff_Big(nanosecond,StartTime,EndTime)
/3600000000000
as DurationHours from ModeDayPeriod )
-- List the results in order
-- Use MaxRecursion option in case there are more than 100 days
select ModeDate as [Date], Mode, sum(DurationHours) as [Total Duration Hours]
from SumDurations
group by ModeDate, Mode
order by ModeDate, Mode
option (maxrecursion 0)
Result is:
Date Mode Total Duration Hours
---------- ----------- ---------------------------------------
2018-11-15 1 3.00000000000000
2018-11-16 1 18.26605271947221
2018-11-16 2 5.73394728052777
2018-11-17 1 14.38972862361111
2018-11-17 2 9.61027137638888
2018-11-18 2 24.00000000000000
2018-11-19 1 6.99999519891666
2018-11-19 2 17.00000480108333
2018-11-20 1 3.11686202991666
2018-11-20 2 20.88313797008333
you could use a CTE to create a table of days then join the time slots to it
DECLARE #MAX as datetime2 = (SELECT MAX(CAST(Timestamp as date)) MX FROM process);
WITH StartEnd AS (select p1.Timestamp StartDT,
P2.Timestamp EndDT ,
p1.mode
from process p1
outer apply
(SELECT TOP 1 pOP.* FROM
process pOP
where pOP.Timestamp > p1.Timestamp
order by pOP.Timestamp asc) P2
),
CAL AS (SELECT (SELECT MIN(cast(StartDT as date)) MN FROM StartEnd) DT
UNION ALL
SELECT DATEADD(day,1,DT) DT FROM CAL WHERE CAL.DT < #MAX
),
TMS AS
(SELECT CASE WHEN S.StartDT > C.DT THEN S.StartDT ELSE C.DT END AS STP,
CASE WHEN S.EndDT < DATEADD(day,1,C.DT) THEN S.ENDDT ELSE DATEADD(day,1,C.DT) END AS STE
FROM StartEnd S JOIN CAL C ON NOT(S.EndDT <= C.DT OR S.StartDT>= DATEADD(day,1,C.dt))
)
SELECT *,datediff(MI ,TMS.STP, TMS.ste) as x from TMS
The following uses recursive CTE to build a list of dates (a calendar or number table works equally well). It then intersect the dates with date times so that missing dates are populated with matching data. The important bit is that for each row, if start datetime belongs to previous day then it is clamped to 00:00. Likewise for end datetime.
DECLARE #t TABLE (timestamp DATETIME, mode INT);
INSERT INTO #t VALUES
('2018-01-01 12:00', 1),
('2018-01-01 18:00', 2),
('2018-01-02 01:00', 1),
('2018-01-02 02:00', 2),
('2018-01-04 04:00', 1);
WITH cte1 AS (
-- the min and max dates in your data
SELECT
CAST(MIN(timestamp) AS DATE) AS mindate,
CAST(MAX(timestamp) AS DATE) AS maxdate
FROM #t
), cte2 AS (
-- build all dates between min and max dates using recursive cte
SELECT mindate AS day_start, DATEADD(DAY, 1, mindate) AS day_end, maxdate
FROM cte1
UNION ALL
SELECT DATEADD(DAY, 1, day_start), DATEADD(DAY, 2, day_start), maxdate
FROM cte2
WHERE day_start < maxdate
), cte3 AS (
-- pull end datetime from next row into current
SELECT
timestamp AS dt_start,
LEAD(timestamp) OVER (ORDER BY timestamp) AS dt_end,
mode
FROM #t
), cte4 AS (
-- join datetime with date using date overlap query
-- then clamp start datetime to 00:00 of the date
-- and clamp end datetime to 00:00 of next date
SELECT
IIF(dt_start < day_start, day_start, dt_start) AS dt_start_fix,
IIF(dt_end > day_end, day_end, dt_end) AS dt_end_fix,
mode
FROM cte2
INNER JOIN cte3 ON day_end > dt_start AND dt_end > day_start
)
SELECT dt_start_fix, dt_end_fix, mode, datediff(minute, dt_start_fix, dt_end_fix) / 60.0 AS total
FROM cte4
DB Fiddle
Thanks everybody!
The answer from Cato put me on the right track. Here my final solution:
DECLARE #Start AS datetime;
DECLARE #End AS datetime;
DECLARE #Interval AS int;
SET #Start = '2018-01-01';
SET #End = '2018-01-05';
SET #Interval = 24 * 60 * 60;
WITH
cteDurations AS
(SELECT [Timestamp] AS StartDT,
LEAD ([Timestamp]) OVER (ORDER BY [Timestamp]) AS EndDT,
Mode
FROM tblLog
WHERE [Timestamp] BETWEEN #Start AND #End
),
cteTimeslots AS
(SELECT #Start AS StartDT,
DATEADD(SECOND, #Interval, #Start) AS EndDT
UNION ALL
SELECT EndDT,
DATEADD(SECOND, #Interval, EndDT)
FROM cteTimeSlots WHERE StartDT < #End
),
cteDurationsPerTimesplot AS
(SELECT CASE WHEN S.StartDT > C.StartDT THEN S.StartDT ELSE C.StartDT END AS StartDT,
CASE WHEN S.EndDT < C.EndDT THEN S.EndDT ELSE C.EndDT END AS EndDT,
C.StartDT AS Slot,
S.Mode
FROM cteDurations S
JOIN cteTimeslots C ON NOT(S.EndDT <= C.StartDT OR S.StartDT >= C.EndDT)
)
SELECT Slot,
Mode,
SUM(DATEDIFF(SECOND, StartDT, EndDT)) AS Duration
FROM cteDurationsPerTimesplot
GROUP BY Slot, Mode
ORDER BY Slot, Mode;
With the variable #Interval you are able to define the size of the timeslots.
The CTE cteDurations creates a subresult with the durations of all necessary entries by using the TSQL function LEAD (available in MSSQL >= 2012). This will be a lot faster than an OUTER APPLY.
The CTE cteTimeslots generates a list of timeslots with start time and end time.
The CTE cteDurationsPerTimesplot is a subresult with a JOIN between cteDurations and cteTimeslots. This this the magic JOIN statement from Cato!
And finally the SELECT statement will do the grouping and sum calculation per Slot and Mode.
Once again: Thanks a lot to everybody! Especially to Cato! You saved my weekend!
Regards
Oliver

Generate a list of dates between 2 dates for more than one record

I am trying to write SQL to generate the following data
Date Count
2018-09-24 2
2018-09-25 2
2018-09-26 2
2018-09-27 2
2018-09-28 2
2018-09-29 1
A sample of the base table I am using is
ID StartDate EndDate
187267 2018-09-24 2018-10-01
187270 2018-09-24 2018-09-30
So I'm trying to get a list of dates between 2 dates and then count how many base data records there are in each date.
I started using a temporary table and attempting to loop through the records to get the results but I'm not sure if this is the right approach.
I have this code so far
WITH ctedaterange
AS (SELECT [Dates] = (select ea.StartWork from EngagementAssignment ea where ea.EngagementAssignmentId IN(SELECT ea.EngagementAssignmentId
FROM EngagementLevel el INNER JOIN
EngagementAssignment ea ON el.EngagementLevelID = ea.EngagementLevelId
WHERE el.JobID = 15072 and ea.AssetId IS NOT NULL))
UNION ALL
SELECT [dates] + 1
FROM ctedaterange
WHERE [dates] + 1 < = (select ea.EndWork from EngagementAssignment ea where ea.EngagementAssignmentId IN(SELECT ea.EngagementAssignmentId
FROM EngagementLevel el INNER JOIN
EngagementAssignment ea ON el.EngagementLevelID = ea.EngagementLevelId
WHERE el.JobID = 15072 and ea.AssetId IS NOT NULL)))
SELECT [Dates], Count([Dates])
FROM ctedaterange
GROUP BY [Dates]
But I get this error
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
I get correct results when the job I use only generates one record in the subselect in the where clause, ie:
SELECT ea.EngagementAssignmentId
FROM EngagementLevel el INNER JOIN
EngagementAssignment ea ON el.EngagementLevelID = ea.EngagementLevelId
WHERE el.JobID = 15047 and ea.AssetId IS NOT NULL
generates one record.
The results look like this:
Dates (No column name)
2018-09-24 02:00:00.000 1
2018-09-25 02:00:00.000 1
2018-09-26 02:00:00.000 1
2018-09-27 02:00:00.000 1
2018-09-28 02:00:00.000 1
2018-09-29 02:00:00.000 1
2018-09-30 02:00:00.000 1
2018-10-01 02:00:00.000 1
you can generate according to your range by changing from and to date
DECLARE
#DateFrom DATETIME = GETDATE(),
#DateTo DATETIME = '2018-10-30';
WITH DateGenerate
AS (
SELECT #DateFrom as MyDate
UNION ALL
SELECT DATEADD(DAY, 1, MyDate)
FROM DateGenerate
WHERE MyDate < #DateTo
)
SELECT
MyDate
FROM
DateGenerate;
Well, if you only have a low date range, you can use a recursive CTE as demonstrated in the other answers. The problem with a recursive CTE is with large ranges, where it starts to be ineffective - So I wanted to show you a different approach, that builds the calendar CTE without using recursion.
First, Create and populate sample table (Please save us this step in your future questions):
DECLARE #T AS TABLE
(
ID int,
StartDate date,
EndDate date
)
INSERT INTO #T (ID, StartDate, EndDate) VALUES
(187267, '2018-09-24', '2018-10-01'),
(187270, '2018-09-24', '2018-09-30')
Then, get the first start date and the number of dates you need in the calendar cte:
DECLARE #DateDiff int, #StartDate Date
SELECT #DateDiff = DATEDIFF(DAY, MIN(StartDate), Max(EndDate)),
#StartDate = MIN(StartDate)
FROM #T
Now, construct the calendar cte based on row_number (that is, unless you already have a numbers (tally) table you can use):
;WITH Calendar(TheDate)
AS
(
SELECT TOP(#DateDiff + 1) DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY ##SPID)-1, #StartDate)
FROM sys.objects t0
-- unremark the next row if you don't get enough records...
-- CROSS JOIN sys.objects t1
)
Note that I'm using row_number() - 1 and therefor have to select top(#DateDiff + 1)
Finally - the query:
SELECT TheDate, COUNT(ID) As NumberOfRecords
FROM Calendar
JOIN #T AS T
ON Calendar.TheDate >= T.StartDate
AND Calendar.TheDate <= T.EndDate
GROUP BY TheDate
Results:
TheDate | NumberOfRecords
2018-09-24 | 2
2018-09-25 | 2
2018-09-26 | 2
2018-09-27 | 2
2018-09-28 | 2
2018-09-29 | 2
2018-09-30 | 2
2018-10-01 | 1
You can see a live demo on rextester.
Can you please try following SQL CTE query where I have used a SQL dates table function [dbo].[DatesTable] which produces a list of dates between min date and max date in the source table
;with boundaries as (
select
min(StartDate) minD, max(EndDate) maxD
from DateRanges
), dates as (
select
dates.[date]
from boundaries
cross apply [dbo].[DatesTable](minD, maxD) as dates
)
select dates.[date], count(*) as [count]
from dates
inner join DateRanges
on dates.date between DateRanges.StartDate and DateRanges.EndDate
group by dates.[date]
order by dates.[date]
The output is as expected
Try this: demo
WITH cte1
AS (SELECT id,sdate,edate from t
union all
select c.id,DATEADD(DAY, 1, c.sdate),c.edate from cte1 c where DATEADD(DAY, 1, c.sdate)<=c.edate
)
SELECT sdate,count(id) as total FROM cte1
group by sdate
OPTION (MAXRECURSION 0)
Output:
sdate total
2018-09-24 2
2018-09-25 2
2018-09-26 2
2018-09-27 2
2018-09-28 2
2018-09-29 2
2018-09-30 1

SQL breakout date range to rows

I am trying to take given date ranges found in a data set and divide them into unique rows for each day in the range (example below). Doing the opposite in SQL is pretty straight forward, but I am struggling to achieve the desired query output.
Beginning data:
ITEM START_DATE END_DATE
A 1/1/2015 1/5/2015
B 2/5/2015 2/7/2015
Desired query output:
ITEM DATE_COVERED
A 1/1/2015
A 1/2/2015
A 1/3/2015
A 1/4/2015
A 1/5/2015
B 2/5/2015
B 2/6/2015
B 2/7/2015
The fastest way will be some tally table:
DECLARE #t TABLE
(
ITEM CHAR(1) ,
START_DATE DATE ,
END_DATE DATE
)
INSERT INTO #t
VALUES ( 'A', '1/1/2015', '1/5/2015' ),
( 'B', '2/5/2015', '2/7/2015' )
;WITH cte AS(SELECT -1 + ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) d FROM
(VALUES(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) t1(n) CROSS JOIN
(VALUES(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) t2(n) CROSS JOIN
(VALUES(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) t3(n) CROSS JOIN
(VALUES(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) t4(n))
SELECT t.ITEM, ca.DATE_COVERED FROM #t t
CROSS APPLY(SELECT DATEADD(dd, d, t.START_DATE) AS DATE_COVERED
FROM cte
WHERE DATEADD(dd, d, t.START_DATE) BETWEEN t.START_DATE AND t.END_DATE) ca
ORDER BY t.ITEM, ca.DATE_COVERED
Query:
SQLFiddleExample
SELECT t.ITEM,
DATEADD(day,n.number, t.START_DATE) AS DATE_COVERED
FROM Table1 t,
(SELECT number
FROM master..spt_values
WHERE [type] = 'P') n
WHERE START_DATE <= DATEADD(day,n.number, t.START_DATE)
AND END_DATE >= DATEADD(day,n.number, t.START_DATE)
Result:
| ITEM | DATE_COVERED |
|------|--------------|
| A | 2015-01-01 |
| A | 2015-01-02 |
| A | 2015-01-03 |
| A | 2015-01-04 |
| A | 2015-01-05 |
| B | 2015-02-05 |
| B | 2015-02-06 |
| B | 2015-02-07 |
NOTE: this only works if the difference between your startdate and enddate is a maximum of 2047 days (master..spt_values only allows 0..2047 range of values)
select item, dateadd(d,v.number,d.start_date) adate
from begindata d
join master..spt_values v on v.type='P'
and v.number between 0 and datediff(d, start_date, end_date)
order by adate;
I'd like to say I did this myself but I got the code from this
Here is a fiddle with your expected result
TRY THIS...
CREATE TABLE Table1
([ITEM] varchar(1), [START_DATE] date, [END_DATE] date)
;
INSERT INTO Table1
([ITEM], [START_DATE], [END_DATE])
VALUES ('A', '2015-01-01', '2015-01-05'), ('B', '2015-02-05', 2015-02-07');
WITH Days
AS ( SELECT ITEM, START_DATE AS [Date], 1 AS [level] from Table1
UNION ALL
SELECT TABLE1.ITEM, DATEADD(DAY, 1, [Date]), [level] + 1
FROM Days,Table1
WHERE DAYS.ITEM=TABLE1.ITEM AND [Date] < END_DATE )
SELECT distinct [Date]
FROM Days
DEMO

Ignore date list from CTE

CTE gives a below result
Name | StartDateTime | EndDateTime
--------------------+-------------------------+------------------------
Hair Massage | 2014-02-15 09:00:00.000 | 2014-02-15 10:00:00.000
Hair Massage | 2014-02-15 10:00:00.000 | 2014-02-15 11:00:00.000
(X)Hair Massage | 2014-02-23 09:00:00.000 | 2014-02-23 10:00:00.000
(X)Hair Cut | 2014-02-20 12:15:00.000 | 2014-02-20 13:00:00.000
Hair Cut | 2014-03-07 11:30:00.000 | 2014-03-07 12:15:00.000
Also I have Holidays
Id | StartDateTime | EndDateTime
-------------+--------------------+-------------------
1 | 20140223 00:00:00 | 20140224 23:59:00
And EventBooking
EventId | StartDateTime | EndDateTime
-------------+-------------------------+------------------------
1 | 2014-02-20 12:15:00.000 | 2014-02-20 13:00:00.000
I want to remove the dates falls under holidays and EventBooking from my CTE.
I mean remove the (X) recods from my CTE
RESULT=CTE- BookedSchedule-Holidays
with HoliDaysCte2 as
(
select StartdateTime,EndDateTime from Holidays
union all
select StartdateTime,EndDateTime from EventBooking
)
SELECT
Name,
StartDateTime,
EndDateTime
FROM CTE WHERE not exists (select 1
from HoliDaysCte2 h
where cast(a.RepeatEventDate as DATETIME) between
cast(h.startdatetime as DATETIME)
and cast(h.enddatetime as DATETIME)
)
Here is my SQL FIDDLE DEMO
Okay Assuming this is your schema
CREATE TABLE dbo.StaffSchedule
( ID INT IDENTITY(1, 1) NOT NULL,
Name Varchar(50),
StartdateTime DATETIME2 NOT NULL,
EndDateTime DATETIME2 NOT NULL
);
CREATE TABLE dbo.BookedSchedules
( ID INT IDENTITY(1, 1) NOT NULL,
StaffId INT,
StartdateTime DATETIME2 NOT NULL,
EndDateTime DATETIME2 NOT NULL
);
CREATE TABLE dbo.Holidays
( ID INT,
StartdateTime DATETIME2 NOT NULL,
EndDateTime DATETIME2 NOT NULL
);
INSERT dbo.StaffSchedule (Name, StartdateTime, EndDateTime)
VALUES
('Hair Massage','2014-02-15 09:00:00.000','2014-02-15 10:00:00.000'),
('Hair Massage','2014-02-15 10:00:00.000','2014-02-15 11:00:00.000'),
('(X)Hair Massage','2014-02-23 09:00:00.000','2014-02-23 10:00:00.000'),
('(X)Hair Cut','2014-02-20 12:15:00.000','2014-02-20 13:00:00.000'),
('Hair Cut','2014-03-07 11:30:00.000','2014-03-07 12:15:00.000');
INSERT dbo.BookedSchedules (StaffId, StartdateTime, EndDateTime)
VALUES
(1,'2014-02-20 12:15:00.000','2014-02-20 13:00:00.000');
INSERT dbo.Holidays (ID,StartdateTime, EndDateTime)
VALUES
(1,'20140223 00:00:00','20140224 23:59:00');
Does this solves your issue?
select * from StaffSchedule SS
where
not exists(
select * from NonBookingSlots NBS
where (dateadd(MICROSECOND,1,ss.StartdateTime)
between nbs.StartdateTime and nbs.EndDateTime)
or (dateadd(MICROSECOND,-1,ss.EndDateTime)
between nbs.StartdateTime and nbs.EndDateTime))
ok try this,
create one more cte,
,cte2 as
(
select * from #Holidays
union all
select BookingID,StartdateTime,EndDateTime from #EventBooking
)
then as usual
AND not exists (select 1
from cte2 h
where cast(a.RepeatEventDate as date) between cast(h.startdatetime as date) and cast(h.enddatetime as date)
)
this one is latest (datetime conversion very confusing,i just started
from #Gordon query.
AND not exists (select 1
from cte2 h
where cast(DATEADD(SECOND, DATEDIFF(SECOND, 0, StartTime), RepeatEventDate) as datetime) between cast(h.startdatetime as datetime) and cast(h.enddatetime as datetime)
)
RESULT= CTE - BookedSchedule - Holidays
Will be equal to use set theories subtract operation, in sql server you may use Except (Minus in Oracle).
select StaffId, StartdateTime,EndDateTime from StaffSchedule -- CTE
except
(select StaffId, StartdateTime,EndDateTime from BookedSchedules) -- BookedSchedule
except
(select StaffSchedule.StaffId, StaffSchedule.StartdateTime , StaffSchedule.EndDateTime
from StaffSchedule
inner join Holidays
on
cast(Holidays.StartdateTime As Date) = cast(StaffSchedule.StartdateTime As Date)
and
cast(Holidays.EndDateTime As Date) = cast(StaffSchedule.EndDateTime As Date)
) -- Holidays
;
Sqlfiddle demo
If a multi-day holiday could be inserted, like:
INSERT dbo.Holidays (StartdateTime, EndDateTime)
VALUES
('2014-03-05 00:00:00.000', '2014-03-07 23:59:00.000');
Using the query bellow to extract staff-holidays will be useful:
(select StaffSchedule.StaffId, StaffSchedule.StartdateTime , StaffSchedule.EndDateTime
from StaffSchedule
inner join Holidays
on
cast(Holidays.StartdateTime As Date) <= cast(StaffSchedule.StartdateTime As Date)
and
cast(Holidays.EndDateTime As Date) >= cast(StaffSchedule.EndDateTime As Date)
)
Please try:
select * From StaffSchedule
where ID not in(
select
ID
From StaffSchedule a inner join
(
select StartdateTime, EndDateTime From dbo.BookedSchedules
union all
select StartdateTime, EndDateTime From dbo.Holidays
)b on a.StartdateTime between b.StartdateTime and b.EndDateTime and
a.EndDateTime between b.StartdateTime and b.EndDateTime)
Chekck SQL Fiddle Demo
This will surely help you.....
WITH CTE AS (
SELECT
S.ID,
S.StaffId ,
S.StartdateTime,
S.EndDateTime,
H.StartdateTime 'HolydayStartDate' ,
H.EndDateTime AS 'HolydayDateDate',
B.StartdateTime AS 'BookedStartDate',
B.EndDateTime AS 'BookedEndDate'
FROM #StaffSchedule S
LEFT JOIN #Holidays H ON S.StartdateTime >= H.StartdateTime AND S.EndDateTime <= H.EndDateTime
LEFT JOIN #BookedSchedules B ON B.StaffId = S.StaffId AND B.StartdateTime = S.StartdateTime AND B.EndDateTime = S.EndDateTime
)
SELECT * FROM CTE
WHERE
HolydayStartDate IS NULL AND
HolydayDateDate IS NULL AND
BookedStartDate IS NULL AND
BookedEndDate IS NULL
To check for overlapping periods you need to do:
where p1.StartdateTime < p2.enddatetime
and p1.enddatetime > p2.startdatetime
Depending on your needs it might be >=/<= instead of >/<.
Based on your fiddle:
with NonBookingSlots as
(
select StartdateTime,EndDateTime from Holidays
union all
select StartdateTime,EndDateTime from BookedSchedules
)
SELECT
*
FROM StaffSchedule as ss
WHERE StaffId=1
AND not exists (select *
from NonBookingSlots h
where h.StartdateTime < ss.enddatetime
and h.enddatetime > ss.startdatetime
)
I think this will work for you -
SELECT ss.* FROM StaffSchedule ss
LEFT JOIN BookedSchedules bs
ON (ss.StartdateTime BETWEEN bs.StartdateTime AND bs.EndDateTime)
AND (ss.EndDateTime BETWEEN bs.StartdateTime AND bs.EndDateTime)
LEFT JOIN Holidays h
ON (ss.StartdateTime BETWEEN h.StartdateTime AND h.EndDateTime)
AND (ss.EndDateTime BETWEEN h.StartdateTime AND h.EndDateTime)
WHERE bs.ID IS NULL AND h.StartdateTime IS NULL AND h.EndDateTime IS NULL
SQL Fiddle