splitting overlapping dates in SQL - sql

I'm on SQLServer 2008 R2
I'm trying to create a report and chart for a a manufacturing resource's activity for a give period (typically 30-90 days)
Jobs are created for the length of the run (e.g. 4 days). If the weekend is not worked and the above jobs starts on a Friday, the resource's activity needs to show 1 day running, 2 days down, 3 days running without the production scheduler having to make it two jobs. I have the jobs' schedules in one table and the downtimes in another (so think of DT like some sort of calendar table). Unusually, the end time is supplied with the downtime factored in.
So I need the query to create 3 datetime ranges for this job: Fri running, Sat,Sun down, Mon,Tues,Wed Running. Note: a single job can have multiple downtime events.
Been going round in circles on this for a while. i'm sure there's an elegant way to do it: I just can't find it. I've found several similar post, but can't apply any to my case (or at least can;t get them to work)
Below is some sample date and expected results. I hope the explanation and example data is clear.
-- Create tables to work with / Source and Destination
CREATE TABLE #Jobs
(
ResourceID int
,JobNo VARCHAR(10)
,startdate SMALLDATETIME
,enddate SMALLDATETIME
)
CREATE TABLE #Downtime
(
ResourceID INT
,Reason VARCHAR(10)
,startdate SMALLDATETIME
,enddate SMALLDATETIME
)
CREATE TABLE #Results
(
ResourceID INT
,Activity VARCHAR(10)
,startdate SMALLDATETIME
,enddate SMALLDATETIME
,ActivityType varchar(1)
)
-- Job Schedule
INSERT INTO [#Jobs]
(
[ResourceID],
[JobNo],
startdate
,enddate
)
SELECT 1, 'J1', '2014-04-01 08:00' ,'2014-04-01 17:00'
UNION ALL
SELECT 1, 'J2', '2014-04-01 17:00' , '2014-04-01 23:00'
UNION ALL
SELECT 2, 'J3', '2014-04-01 08:00' ,'2014-04-01 23:00'
UNION ALL
SELECT 3, 'J4', '2014-04-01 08:00' ,'2014-04-01 09:00'
SELECT * FROM #jobs
-- Downtime Scehdule
INSERT INTO [#Downtime]
(
[ResourceID],
Reason,
startdate
,enddate
)
SELECT 1, 'DOWN', '2014-04-01 10:00' ,'2014-04-01 11:00'
UNION ALL
SELECT 1, 'DOWN', '2014-04-01 21:00' , '2014-04-01 22:00'
UNION ALL
SELECT 2, 'DOWN', '2014-04-01 10:00' ,'2014-04-01 11:00'
UNION ALL
SELECT 2, 'DOWN', '2014-04-01 21:00' , '2014-04-01 22:00'
UNION ALL
SELECT 3, 'DOWN', '2014-04-01 10:00' ,'2014-04-01 11:00'
UNION ALL
SELECT 3, 'DOWN', '2014-04-01 21:00' , '2014-04-01 22:00'
SELECT * FROM #Downtime
-- Expected Results
INSERT INTO [#Results]
(
Activity,
[ResourceID],
startdate
,enddate
,[ActivityType]
)
SELECT 'J1', 1, '2014-04-01 08:00' ,'2014-04-01 10:00', 'P'
UNION ALL
SELECT 'DOWN', 1, '2014-04-01 10:00' , '2014-04-01 11:00', 'D'
UNION ALL
SELECT 'J1', 1, '2014-04-01 11:00' ,'2014-04-01 17:00', 'P'
UNION ALL
SELECT 'J2', 1, '2014-04-01 17:00' , '2014-04-01 21:00', 'P'
UNION ALL
SELECT 'DOWN', 1, '2014-04-01 21:00' , '2014-04-01 22:00', 'D'
UNION ALL
SELECT 'J2', 1, '2014-04-01 22:00' ,'2014-04-01 23:00', 'P'
UNION ALL
SELECT 'J3', 2, '2014-04-01 08:00' ,'2014-04-01 10:00', 'P'
UNION ALL
SELECT 'DOWN', 2, '2014-04-01 10:00' , '2014-04-01 11:00', 'D'
UNION ALL
SELECT 'J3', 2, '2014-04-01 11:00' ,'2014-04-01 21:00', 'P'
UNION ALL
SELECT 'DOWN', 2, '2014-04-01 21:00' , '2014-04-01 22:00', 'D'
UNION ALL
SELECT 'J3', 2, '2014-04-01 22:00' ,'2014-04-01 23:00', 'P'
UNION ALL
SELECT 'J4', 3, '2014-04-01 08:00' ,'2014-04-01 09:00', 'P'
UNION ALL
SELECT 'DOWN', 3, '2014-04-01 10:00' , '2014-04-01 11:00', 'D'
UNION ALL
SELECT 'DOWN', 3, '2014-04-01 21:00' , '2014-04-01 22:00', 'D'
SELECT * FROM #Results
ORDER BY [ResourceID], Startdate
DELETE FROM #Results
|--------------------------J1------------------------------------| running
|----D1-----| |-------D2-------| down
|--J1--|----D1-----|-------J1------|-------D2-------|-----J1-----| result
|-----------------------------J1-----------| running
|----D1-------| down
|-----------------J1-----------------------| |----D1-------| result
Can someone point me in the right direction?
This is the closest I've got. Works great when there is an overlap, but fails on J4 where job ends before downtime
WITH cte
AS ( SELECT
ROW_NUMBER() OVER ( ORDER BY ResourceID, dt ) AS Rno
,x.ResourceID
,x.Activity
,Dt
,xdt.ActivityType
FROM
(
SELECT
ResourceID
,JobNo AS Activity
,startdate
,enddate
,'P' AS ActivityType
FROM #Jobs
UNION ALL
SELECT
ResourceID
,Reason AS Activity
,startdate
,enddate
,'D' AS ActivityType
FROM #Downtime
) AS x
CROSS APPLY
(
VALUES ( x.startdate, x.ActivityType),
( x.enddate, x.ActivityType) ) AS xdt
( Dt, ActivityType )
)
SELECT
x.ResourceID
,CASE WHEN x.Activity > x1.Activity THEN x.Activity
ELSE x1.Activity
END AS Activity
,x.dt AS StartDate
,x1.Dt AS EndDate
,CASE WHEN x.ActivityType > x1.ActivityType THEN x.ActivityType
ELSE x1.ActivityType
END AS activitytype
FROM
cte AS x
LEFT OUTER JOIN cte AS x1 ON x.ResourceID = x1.ResourceID
AND x.Rno = x1.Rno - 1
WHERE
x1.Dt IS NOT NULL
AND x1.Dt <> x.Dt;
Thanks
Mark

You were actually pretty close - rather than doing everything in the initial CTE, you actually want to join back to the original data later. Essentially, you're performing a variant on the answer supplied here.
The following query should get you what you need:
WITH AllDates AS (SELECT a.*, ROW_NUMBER() OVER(PARTITION BY resourceId ORDER BY rangeDate) AS rn
FROM (SELECT resourceId, startDate
FROM Jobs
UNION ALL
SELECT resourceId, endDate
FROM Jobs
UNION ALL
SELECT resourceId, startDate
FROM Downtime
UNION ALL
SELECT resourceId, endDate
FROM DownTime) a(resourceId, rangeDate)),
Range AS (SELECT startRange.resourceId,
startRange.rangeDate AS startDate, endRange.rangeDate AS endDate
FROM AllDates startRange
JOIN AllDates endRange
ON endRange.resourceId = startRange.resourceId
AND endRange.rn = startRange.rn + 1
AND endRange.rangeDate <> startRange.rangeDate)
SELECT Range.resourceId, Range.startDate, Range.endDate,
COALESCE(Downtime.reason, Jobs.jobNo) as activity
FROM Range
LEFT JOIN Jobs
ON Jobs.resourceId = Range.resourceId
AND Jobs.startDate <= Range.startDate
AND Jobs.endDate >= Range.endDate
LEFT JOIN Downtime
ON Downtime.resourceId = Range.resourceId
AND Downtime.startDate <= Range.startDate
AND Downtime.endDate >= Range.endDate
WHERE Jobs.jobNo IS NOT NULL
OR Downtime.reason IS NOT NULL
(And working fiddle. This should actually be ANSI-standard SQL)
...which yields the expected:
RESOURCEID STARTDATE ENDDATE ACTIVITY
----------------------------------------------------------------------------
1 2014-04-01 08:00:00 2014-04-01 10:00:00 J1
1 2014-04-01 10:00:00 2014-04-01 11:00:00 DOWN
1 2014-04-01 11:00:00 2014-04-01 17:00:00 J1
1 2014-04-01 17:00:00 2014-04-01 21:00:00 J2
1 2014-04-01 21:00:00 2014-04-01 22:00:00 DOWN
1 2014-04-01 22:00:00 2014-04-01 23:00:00 J2
2 2014-04-01 08:00:00 2014-04-01 10:00:00 J3
2 2014-04-01 10:00:00 2014-04-01 11:00:00 DOWN
2 2014-04-01 11:00:00 2014-04-01 21:00:00 J3
2 2014-04-01 21:00:00 2014-04-01 22:00:00 DOWN
2 2014-04-01 22:00:00 2014-04-01 23:00:00 J3
3 2014-04-01 08:00:00 2014-04-01 09:00:00 J4
3 2014-04-01 10:00:00 2014-04-01 11:00:00 DOWN
3 2014-04-01 21:00:00 2014-04-01 22:00:00 DOWN

Related

How do I do this ? Time interval

there is a table.
(1, 'b', '2010-01-01 00:00:00', '2020-01-01 00:00:00'),
(1, 'z', '2010-02-01 00:00:00', '2015-01-01 00:00:00'),
How to do this:
(1, 'b', '2010-01-01 00:00:00', '2010-01-31 23:59:59'),
(1, 'z', '2010-02-01 00:00:00', '2015-01-01 00:00:00'),
(1, 'b', '2015-01-01 00:00:01', '2020-01-01 00:00:00');
You can do it this way:
I ddint add the part when you take away a second from the enddate or add a second to the fromdate as I didnt see the logic there
with cte as
(
select 1 as a, 'b' as b, cast('2010-01-01 00:00:00'as date) as start_, cast('2020-01-01 00:00:00'as date) as end_
union select 1, 'z', '2010-02-01 00:00:00', '2015-01-01 00:00:00'
),
cte2 as
(
select start_ as date_ from cte union select end_ from cte
),
cte3 as
(
select a, b, date_ from cte2 a inner join cte b on date_ between start_ and end_
),
final as
(
select a.a, a.b, a.date_ as startdate,
case when a.b = lead(a.b)over(order by a.date_) then lead(a.date_)over(order by a.date_) end as enddate
from cte3 a
)
select * from final where enddate is not null order by startdate
Output:
a b startdate enddate
1 b 2010-01-01 2010-02-01
1 z 2010-02-01 2015-01-01
1 b 2015-01-01 2020-01-01

Query to compare dates patients are missing from hospital census

I have a hospital bed census that is triggered and creates a date/time stamped row in a table. when the bed check portion is done it labels the event census. i have found that some patients on days they were in the hospital have not been timestamped with the event census. I am trying to write a query to capture all patients that may have had this issue.
i need to capture the patients between their admit and discharge dates, and then any day they do not have a time stamp event of census. for example, this patient does not have a census on the 12th or 13th but does on the 14th. i want to be able to pull this pat_id and dates they are not stamped with census.
11-APR-2019 11:59:00 PM CENSUS
12-APR-2019 03:12:00 PM TRANSFER OUT
12-APR-2019 03:12:00 PM TRANSFER IN
14-APR-2019 07:06:00 AM PATIENT UPDATE
14-APR-2019 11:40:00 AM TRANSFER OUT
14-APR-2019 11:40:00 AM TRANSFER IN
14-APR-2019 11:59:00 PM CENSUS
I created a calendar portion to my query. then i created a query to capture patients in a time frame. from there i am a bit stuck.
DATE1
AS
(select
to_char(dates,'MM/DD/YYYY') AS WEEK_DATE,
dates,
to_char(dates,'D') weekday,
to_char(dates,'mm') m_onth,
to_char(dates,'ww') week_of_year,
to_char(dates,'dd') month_day,
to_char(dates,'ddd') Year_day,
SUBSTR(dates,1,2) AS WEEKDATE
from (SELECT TRUNC(to_date(v.yyyy,'YYYY'),'YY') +LEVEL - 1 DATES
FROM ( SELECT 2019 yyyy FROM dual ) v
CONNECT BY LEVEL < 366
)
)
,
ADT
AS (select distinct
adt.pat_id,
peh.y_mrn,
adt.DEPARTMENT_ID,
adp.department_name,
--peh.HOSP_ADMSN_TIME,
to_char(peh.HOSP_ADMSN_TIME,'MM/DD/YYYY') AS HOSP_ADMSN_TIME2,
--peh.HOSP_DISCH_TIME,
to_char(peh.HOSP_DISCH_TIME,'MM/DD/YYYY') AS HOSP_DISCH_TIME2,
adt.effective_time,
to_char(aDT.effective_time,'MM/DD/YYYY') AS EFFECT_DATE,
--LEAD(adt.effective_time) over (partition by ADT.pat_id order by ADT.pat_id, adt.effective_time) AS NEXT_EFF_DATE,
--CASE WHEN adt.event_type_c =6 THEN adt.effective_time END AS CENSUS_DATE,
et.title as event_type,
adt.event_type_c,
peh.ADT_PAT_CLASS_C,
Adt.event_subtype_c--,
--LAG(adt.effective_time) over (partition by ADT.pat_id order by ADT.pat_id, adt.effective_time) AS PREV_EFF_DATE
from
clarity_adt adt
left OUTER join
pat_enc_hsp peh
on
peh.pat_enc_csn_id = adt.pat_enc_csn_id
left outer join
clarity_dep adp
on adt.department_id = adp.department_id
left OUTER join
zc_event_type et
on adt.event_type_c = et.event_type_c
where
adt.effective_time between '08-apr-2019' and '15-apr-2019'
order by adt.effective_time
)
,
ADT2
AS
(
SELECT-- DISTINCT
D.WEEK_DATE,
A.HOSP_ADMSN_TIME2,
A.EFFECT_DATE,
A.PAT_ID,
CASE WHEN D.WEEK_DATE IS NOT NULL AND A.EFFECT_DATE IS NULL AND A.event_type <> 'CENSUS' THEN 1
WHEN D.WEEK_DATE IS NOT NULL AND A.EFFECT_DATE IS NULL AND A.event_type IS NULL THEN 1
WHEN D.WEEK_DATE IS NOT NULL AND A.EFFECT_DATE IS NOT NULL AND A.event_type <> 'CENSUS' THEN 1 ELSE 0
END AS NO_ADT_INFO,
A.event_type,
A.HOSP_DISCH_TIME2
FROM
DATE2 D
LEFT OUTER JOIN
ADT A
ON
D.WEEK_DATE = A.EFFECT_DATE
ORDER BY
D.WEEK_DATE)
i would like to end up with the patient id, the day of the week they have no census, the hosp admission & discharge dates
PAT_ID WEEK_DATE EVENT_TYPE HOSP_ADMSN_TIME HOSP_DISCH_TIME
ABCDEF 4/12/2019 NO CENSUS 4/10/2019 4/19/2019
ABCDEF 4/13/2019 NO CENSUS 4/10/2019 4/19/2019
GHIJK 4/8/2019 NO CENSUS 4/2/2019 4/12/2019
GHIJK 4/11/2019 NO CENSUS 4/2/2019 4/12/2019
Here is sample data for two patients:
events(pat_id, event_date, event_type) as (
select 'ABCD', to_date('2019-04-11 23:59', 'yyyy-mm-dd hh24:mi'), 'CENSUS' from dual union all
select 'ABCD', to_date('2019-04-12 15:12', 'yyyy-mm-dd hh24:mi'), 'TRANSFER OUT' from dual union all
select 'ABCD', to_date('2019-04-12 15:12', 'yyyy-mm-dd hh24:mi'), 'TRANSFER IN' from dual union all
select 'ABCD', to_date('2019-04-14 07:06', 'yyyy-mm-dd hh24:mi'), 'PATIENT UPDATE' from dual union all
select 'ABCD', to_date('2019-04-14 11:40', 'yyyy-mm-dd hh24:mi'), 'TRANSFER OUT' from dual union all
select 'ABCD', to_date('2019-04-14 11:40', 'yyyy-mm-dd hh24:mi'), 'TRANSFER IN' from dual union all
select 'ABCD', to_date('2019-04-14 23:59', 'yyyy-mm-dd hh24:mi'), 'CENSUS' from dual union all
select 'GHIJ', to_date('2019-05-17 23:59', 'yyyy-mm-dd hh24:mi'), 'CENSUS' from dual union all
select 'GHIJ', to_date('2019-05-19 23:59', 'yyyy-mm-dd hh24:mi'), 'CENSUS' from dual ),
peh(pat_id, hosp_admsn_time, hosp_disch_time) as (
select 'ABCD', date '2019-04-11', date '2019-04-14' from dual union all
select 'GHIJ', date '2019-05-17', date '2019-05-20' from dual ),
You can create recursive query generating days for each patient and check if there is CENSUS event for each of these days:
with cte(pat_id, num, adm, dis) as (
select pat_id, 0, hosp_admsn_time, hosp_disch_time from peh
union all
select pat_id, num + 1, adm, dis from cte where num < dis - adm)
select pat_id, day, 'NO CENSUS' info, adm, dis
from (select pat_id, adm + num day, adm, dis from cte) d
where not exists (
select 1
from events
where pat_id = d.pat_id and trunc(event_date) = d.day and event_type = 'CENSUS')
order by pat_id, day;
Result:
PAT_ID DAY INFO ADM DIS
------ ----------- --------- ----------- -----------
ABCD 2019-04-12 NO CENSUS 2019-04-11 2019-04-14
ABCD 2019-04-13 NO CENSUS 2019-04-11 2019-04-14
GHIJ 2019-05-18 NO CENSUS 2019-05-17 2019-05-20
GHIJ 2019-05-20 NO CENSUS 2019-05-17 2019-05-20
dbfiddle demo

create dynamic records from time stamps

I have the following table:
Id Date Time Location leadHourDiff
3 2017-01-01 2017-01-01 13:00:00.000 Boston 2
15 2017-01-01 2017-01-01 13:00:00.000 Philly 1
16 2017-01-01 2017-01-01 15:00:00.000 Philly 1
and i would like dynamically create the hour records between Time and (Time + leadHourDiff)
so the end result would be:
Date Time Location
2017-01-01 2017-01-01 13:00:00.000 Boston --main record
2017-01-01 2017-01-01 14:00:00.000 Boston --new record
2017-01-01 2017-01-01 15:00:00.000 Boston --new record
2017-01-01 2017-01-01 13:00:00.000 Philly --main record
2017-01-01 2017-01-01 14:00:00.000 Philly --new record
2017-01-01 2017-01-01 15:00:00.000 Philly --main record
2017-01-01 2017-01-01 16:00:00.000 Philly --new record
One option is to use a numbers table (This can be generated with a recursive cte) and join the leadHourDiff column on to that.
with numbers(num) as (select 0
union all
select num+1 from numbers where num < 100 --change this as needed
)
select t.*,dateadd(hour,n.num,t.datetime_col) as new_datetime
from tbl t
join numbers n on t.leadHourDiff >= n.num
A simple way is to use a recursive CTE:
with cte as (
select id, date, time, Location, leadHourDiff
from t
union all
select id, date, dateadd(hour, 1, time), location, leadHourDiff - 1
from cte
where leadHourDiff >= 0
)
select date, time, Location
from cte
order by location, date, time;
Here's how I ended up doing this. Also, forgot to mention that I only wanted the missing time values. That was an tpyo on my part. Here's the whole solution
CREATE TABLE #Orders(
Id int IDENTITY(1,1)
,[Time] datetime
,[Location] varchar(20)
,OrderAmt int
)
INSERT INTO #Orders
SELECT '2017-01-01 11:00:00', 'Boston', 23 UNION ALL
SELECT '2017-01-01 12:00:00', 'Boston', 31 UNION ALL
SELECT '2017-01-01 13:00:00', 'Boston', 45 UNION ALL
SELECT '2017-01-01 16:00:00', 'Boston', 45 UNION ALL ---15
SELECT '2017-01-01 17:00:00', 'Boston', 67 UNION ALL
SELECT '2017-01-01 18:00:00', 'Boston', 89 UNION ALL
SELECT '2017-01-01 19:00:00', 'Boston', 90 UNION ALL
SELECT '2017-01-01 20:00:00', 'Boston', 123 UNION ALL
SELECT '2017-01-01 21:00:00', 'Boston', 145 UNION ALL
SELECT '2017-01-01 22:00:00', 'Boston', 156 UNION ALL
SELECT '2017-01-01 23:00:00', 'Boston', 145 UNION ALL
SELECT '2017-01-02 00:00:00', 'Boston', 167 UNION ALL
SELECT '2017-01-01 11:00:00', 'Philly', 23 UNION ALL
SELECT '2017-01-01 12:00:00', 'Philly', 31 UNION ALL
SELECT '2017-01-01 13:00:00', 'Philly', 45 UNION ALL
SELECT '2017-01-01 15:00:00', 'Philly', 45 UNION ALL
SELECT '2017-01-01 17:00:00', 'Philly', 67 UNION ALL
SELECT '2017-01-01 18:00:00', 'Philly', 89 UNION ALL
SELECT '2017-01-01 19:00:00', 'Philly', 90 UNION ALL
SELECT '2017-01-01 20:00:00', 'Philly', 123 UNION ALL
SELECT '2017-01-01 21:00:00', 'Philly', 145 UNION ALL
SELECT '2017-01-01 22:00:00', 'Philly', 156 UNION ALL
SELECT '2017-01-01 23:00:00', 'Philly', 145 UNION ALL
SELECT '2017-01-02 00:00:00', 'Philly', 167
;WITH HourDiff AS (
SELECT *
FROM
(
SELECT
Id
,CAST([Time] AS date) AS [Date]
,[Time]
,[Location]
,COALESCE(lead(DATEPART(HOUR, [Time])) OVER(PARTITION BY [Location], CAST([Time] AS date) ORDER BY [Time] ASC ) - DATEPART(HOUR, [Time]),1)-1 AS leadHourDiff
FROM #Orders
) t1
WHERE t1.leadHourDiff <> 0
)
, CTE AS (
SELECT
Location
,DATEADD(HOUR, leadHourDiff, [Time]) AS missingTime
FROM HourDiff
UNION ALL
SELECT
Location
,DATEADD(HOUR, leadHourDiff - 1, [Time]) AS missingTime
FROM HourDiff
WHERE Time < DATEADD(HOUR, leadHourDiff - 1, [Time])
)
SELECT
Location
,CAST(missingTime AS time) AS missingTime
FROM CTE
ORDER BY Location, missingTime
DROP TABLE #Orders
Final result:
Location missingTime
Boston 14:00:00.000
Boston 15:00:00.000
Philly 14:00:00.000
Philly 16:00:00.000
UPDATE:
here's an update..the final CTE was not working properly when i add new data for new york
new data for new york:
SELECT '2017-01-01 11:00:00', 'New York', 23 UNION ALL
SELECT '2017-01-01 20:00:00', 'New York', 31 UNION ALL
new final CTE:
, CTE AS (
SELECT
Location
,DATEADD(HOUR, leadHourDiff, [Time]) AS missingTime
,[Time]
,leadHourDiff
FROM HourDiff
UNION ALL
SELECT
Location
,DATEADD(HOUR, leadHourDiff - 1 , [Time]) AS missingTime
,[Time]
,leadHourDiff - 1
FROM CTE
WHERE leadHourDiff >= 0
AND Time < DATEADD(HOUR, leadHourDiff - 1, [Time])
)
Final result:
Location missingTime
Boston 14:00:00.0000000
Boston 15:00:00.0000000
New York 12:00:00.0000000
New York 13:00:00.0000000
New York 14:00:00.0000000
New York 15:00:00.0000000
New York 16:00:00.0000000
New York 17:00:00.0000000
New York 18:00:00.0000000
New York 19:00:00.0000000
Philly 14:00:00.0000000
Philly 16:00:00.0000000

Update a record in table 1 with a record from table 2 based on a date in table 1

I'm new to the site, and I'm hoping you can help...
I'm trying to stamp the LegNumber from Table 2 into Table 1 based on the datetime of the record in Table1 falling between the datetime of the record(s) from Table 2.
In my example, the records in Table 1 with a datetime that falls between 4/5/16 4:02 AM and 4/7/16 6:53 AM should be stamped with LegNumber 1862410 (from Table 2) based on the datetimes of 4/5/16 8:14 AM thru 4/5/16 4:09 PM. Hopefully, I haven't made this inquiry too confusing.
At first we create to cte's and gather time intervals, then select to show output:
;WITH LegsRowNumbers AS (
SELECT *, ROW_NUMBER() OVER (ORDER BY LegStartDate ASC) as rn
FROM Table2 t2
), GetLegsIntervals AS (
SELECT l1.Tractor,
--Here may be another format, check CAST and CONVERT article on MSDN
CONVERT(datetime,l1.LegStartDate, 120) as LegStartDate,
DATEADD(minute,-1,l2.LegStartDate) as LegEndDate,
l1.LegNumber
FROM LegsRowNumbers l1
LEFT JOIN LegsRowNumbers l2
ON l2.rn = l1.rn+1
)
SELECT t.Tractor,
t.TollExitDateTime,
g.LegNumber
FROM Table1 t
LEFT JOIN GetLegsIntervals g
ON t.TollExitDateTime between g.LegStartDate and g.LegEndDate
Output:
Tractor TollExitDateTime LegNumber
1404 2016-04-03 05:21 AM NULL
1404 2016-04-03 05:34 AM NULL
1404 2016-04-03 06:28 AM NULL
1404 2016-04-03 02:36 PM NULL
1404 2016-04-03 03:13 PM NULL
1404 2016-04-03 03:29 PM NULL
1404 2016-04-05 08:14 AM 1862410
1404 2016-04-05 08:26 AM 1862410
1404 2016-04-05 09:26 AM 1862410
1404 2016-04-05 03:15 PM 1862410
1404 2016-04-05 03:53 PM 1862410
1404 2016-04-05 04:09 PM 1862410
You can change last query to UPDATE:
UPDATE t
SET LegNumber = g.LegNumber
FROM Table1 t
LEFT JOIN GetLegsIntervals g
ON t.TollExitDateTime between g.LegStartDate and g.LegEndDate
If you are one SQL Server 2012+, you can use the LEAD to set the LegEndDate for each:
create table TABLE1(TRACTOR integer, TOLLEXITDATETIME datetime);
create table TABLE2(TRACTOR integer, LEGSTARTDATE datetime, LEGNUMBER integer);
insert into TABLE1
select '1404', '4/3/2016 5:21:00 AM' union all
select '1404', '4/5/2016 5:21:00 AM' union all
select '1404', '4/6/2016 5:21:00 AM'
;
insert into TABLE2
select '1404', '4/4/2016 3:54:00 AM', 1862405 union all
select '1404', '4/5/2016 4:02:00 AM', 1862410 union all
select '1404', '4/7/2016 6:53:00 AM', 1865901
;
with TEMP_TABLE2(TRACTOR, LEGSTARTDATE, LEGENDDATE, LEGNUMBER)
AS(
select
TRACTOR,
LEGSTARTDATE,
lead(LEGSTARTDATE) over (partition by TRACTOR order by LEGSTARTDATE) LEGENDDATE,
LEGNUMBER
from TABLE2
)
select
t1.TRACTOR,
t1.TOLLEXITDATETIME,
t2.LEGNUMBER
from TABLE1 t1
left outer join TEMP_TABLE2 t2
on t1.TRACTOR = t2.TRACTOR
and t1.TOLLEXITDATETIME between t2.LEGSTARTDATE and coalesce(t2.LEGENDDATE, '12/31/9999')
Thank you mo2 for starter table code :)
And thank you gofr1 for the
ROW_NUMBER() OVER (ORDER BY LegStartDate ASC) as rn because even though I use it all the time I completely blanked it.
Table I used for testing
create table Table2(Tractor integer, LegStartDate datetime, LegNumber integer primary key);
create table Table1(Tractor integer, TollExitDateTime datetime, LegNumber integer FOREIGN KEY REFERENCES Table2(Legnumber));
insert into TABLE1
select '1404', '4/3/2016 5:21:00 AM', NULL union all
select '1404', '4/3/2016 5:34:00 AM', NULL union all
select '1404', '4/3/2016 6:28:00 AM', NULL union all
select '1404', '4/3/2016 2:36:00 PM', NULL union all
select '1404', '4/3/2016 3:13:00 PM', NULL union all
select '1404', '4/3/2016 3:29:00 PM', NULL union all
select '1404', '4/5/2016 8:14:00 AM', NULL union all
select '1404', '4/5/2016 8:26:00 AM', NULL union all
select '1404', '4/5/2016 9:26:00 AM', NULL union all
select '1404', '4/5/2016 3:15:00 PM', NULL union all
select '1404', '4/5/2016 3:53:00 PM', NULL union all
select '1404', '4/5/2016 4:09:00 PM', NULL
;
insert into TABLE2
select '5000', '4/4/2016 3:54:00 AM', 5555555 union all --testing purpose
select '1404', '4/3/2016 5:21:00 AM', 8888888 union all --testing purpose
select '1404', '4/4/2016 3:54:00 AM', 1862405 union all
select '1404', '4/5/2016 4:02:00 AM', 1862410 union all
select '1404', '4/7/2016 6:53:00 AM', 1865901
;
This is the code I used to display, update, then display again for testing.
--This will just display what leg number goes with what
--I will probably be yelled at for not using joins. Normally I do but it worked perfectly fine in this situation to not so I did not bother to.
;WITH LegRows AS (SELECT *, ROW_NUMBER() OVER (ORDER BY t2a.LegStartDate ASC) as rn FROM Table2 t2a)
SELECT t1a.Tractor, t1a.TollExitDateTime, t1a.LegNumber, r1.LegStartDate, r1.LegNumber FROM Table1 t1a, LegRows r1, LegRows r2 WHERE t1a.TollExitDateTime BETWEEN r1.LegStartDate AND r2.LegStartDate AND r1.rn+1 = r2.rn
--This updates the leg information
;WITH LegRows AS (SELECT *, ROW_NUMBER() OVER (ORDER BY t2a.LegStartDate ASC) as rn FROM Table2 t2a)
UPDATE Table1 Set LegNumber = r1.LegNumber FROM Table1 t1a, LegRows r1, LegRows r2 WHERE t1a.TollExitDateTime BETWEEN r1.LegStartDate AND r2.LegStartDate AND r1.rn+1 = r2.rn
--This again displays what leg number goes with what to confirm the update
;WITH LegRows AS (SELECT *, ROW_NUMBER() OVER (ORDER BY t2a.LegStartDate ASC) as rn FROM Table2 t2a)
SELECT t1a.Tractor, t1a.TollExitDateTime, t1a.LegNumber, r1.LegStartDate, r1.LegNumber FROM Table1 t1a, LegRows r1, LegRows r2 WHERE t1a.TollExitDateTime BETWEEN r1.LegStartDate AND r2.LegStartDate AND r1.rn+1 = r2.rn

Need help in sql query of Time Management system

I have 2 tables 1 is EMployeeMaster and another is Attendance
***EmployeeMaster***
EmployeeId EmployeeName DepartmentId
1 ABC 1
2 XYZ 2
3 PQR 2
4 WXY 1
Now i have another table Attendance
***Attendance***
AttendanceId EmployeeId Date InTime OutTime
1 1 2011-04-04 00:00:00 10:00 AM 6:30 PM
2 2 2011-04-04 00:00:00 09:45 AM 7:10 PM
Once employee comes in office and put his finger on device his entry will be go to Attendance table with InTime ,EMployeeId and Date.
So the employee who is not come into office ,his entry will not exist in the Attendance table.
Now i want to generate daily report..It should show attendance of all the employee of company along with their Intime/outTime by date.
All the employees who are absent they also should be displayed in this report.
So i want :
EmployeeId EMployeeName DepartmentId Date InTime OutTime
1 ABC 1 2011-04-04 00:00:00 10:00 AM 6:30 PM
2 XYZ 2 2011-04-04 00:00:00 09:45 AM 7:10 PM
3 PQR 2 NULL/- NULL/- NULL/-
4 WXY 1 NULL/- NULL/- NULL/-
Can you tell me what should be query???
You should use a left outer join.
declare #E table (EmployeeId int, EmployeeName varchar(50), DepartmentId int)
declare #A table (AttendanceId int, EmployeeId int, [Date] date, InTime time, OutTime time)
insert into #E values
(1, 'ABC', 1),
(2, 'XYZ', 2),
(3, 'PQR', 2),
(4, 'WXY', 1)
insert into #A values
(1, 1, '2011-04-04 00:00:00', '10:00 AM', '6:30 PM'),
(2, 2, '2011-04-04 00:00:00', '09:45 AM', '7:10 PM')
select
E.EmployeeId,
E.EmployeeName,
E.DepartmentId,
A.[Date],
A.InTime,
A.OutTime
from #E as E
left outer join #A as A
on E.EmployeeId = A.EmployeeId and
A.[Date] = '2011-04-04 00:00:00'
Result
EmployeeId EmployeeName DepartmentId Date InTime OutTime
----------- -------------------------------------------------- ------------ ---------- ---------------- ----------------
1 ABC 1 2011-04-04 10:00:00.0000000 18:30:00.0000000
2 XYZ 2 2011-04-04 09:45:00.0000000 19:10:00.0000000
3 PQR 2 NULL NULL NULL
4 WXY 1 NULL NULL NULL
Edit 1
If you need to do this for one month I would use some kind of number table or calendar table. Here I have used a cte to build the calendar using #FromDate and #ToDate
declare #E table (EmployeeId int, EmployeeName varchar(15), DepartmentId int)
declare #A table (AttendanceId int, EmployeeId int, [Date] date, InTime time, OutTime time)
insert into #E values
(1, 'ABC', 1),
(2, 'XYZ', 2),
(3, 'PQR', 2),
(4, 'WXY', 1)
insert into #A values
(1, 1, '2011-04-02', '04:00 AM', '4:30 PM'),
(2, 2, '2011-04-02', '05:00 AM', '5:30 PM'),
(3, 1, '2011-04-03', '06:00 AM', '6:30 PM'),
(4, 2, '2011-04-03', '07:00 AM', '7:30 PM'),
(5, 1, '2011-04-04', '08:00 AM', '8:30 PM'),
(6, 2, '2011-04-05', '09:00 AM', '9:10 PM')
-- Set FromDate to first day of month
declare #FromDate date = '20110401'
-- Set ToDate to last day of month
declare #ToDate date = '20110405'
-- Create cte with all dates between FromDate and ToDate
;with cteCal as
(
select #FromDate as [Date]
union all
select dateadd(d, 1, [Date]) as [Date]
from cteCal
where [Date] < #ToDate
)
select
E.EmployeeId,
E.EmployeeName,
E.DepartmentId,
C.[Date],
A.InTime,
A.OutTime
from cteCal as C
cross join #E as E
left outer join #A as A
on E.EmployeeId = A.EmployeeId and
C.[Date] = A.[Date]
order by C.[Date], E.EmployeeName
option (maxrecursion 0)
Should look like this:
select
EmployeeId,
EmployeeName,
DepartmentId,
Date,
InTime,
OutTime
from EmployeeMaster em
left join Attendance a on em.EmployeeId=a.EmployeeId and Date='2011-04-04 00:00:00'
select E.EmloyeeId, EmployeeName, DepartmentId, Date, InTime, OutTime
from EmployeeMaster as e
LEFT JOIN Attendance as a ON a.EmloyeeId = e.EmloyeeId