Subtracting rows in SQL Server - sql

I have a dataset like this:
ID | IssueDate
194924 | 2013-07-31 00:00:00.000
194924 | 2010-06-15 00:00:00.000
194924 | 2012-07-30 00:00:00.000
194924 | 2012-12-11 00:00:00.000
194924 | 2014-08-04 00:00:00.000
194966 | 2012-06-02 00:00:00.000
194966 | 2011-02-03 00:00:00.000
194966 | 2011-02-01 00:00:00.000
194987 | 2013-04-25 00:00:00.000
194987 | 2010-12-03 00:00:00.000
I want to sort data with ID and IssueDate first, and then subtract IssueDates of two consecutive rows (to find the time between one row and next row), then calculate max, min and average of this times for each unique ID.

If your Sql Server version is 2014 then the below one might be help you.
Schema for your case:
CREATE TABLE #TAB (
ID BIGINT
,IssuDate DATETIME
)
INSERT INTO #TAB
SELECT 194924
,'2013-07-31 00:00:00.000'
UNION ALL
SELECT 194924
,'2010-06-15 00:00:00.000'
UNION ALL
SELECT 194924
,'2012-07-30 00:00:00.000'
UNION ALL
SELECT 194924
,'2012-12-11 00:00:00.000'
UNION ALL
SELECT 194924
,'2014-08-04 00:00:00.000'
UNION ALL
SELECT 194966
,'2012-06-02 00:00:00.000'
UNION ALL
SELECT 194966
,'2011-02-03 00:00:00.000'
UNION ALL
SELECT 194966
,'2011-02-01 00:00:00.000'
UNION ALL
SELECT 194987
,'2013-04-25 00:00:00.000'
UNION ALL
SELECT 194987
,'2010-12-03 00:00:00.000'
Result after sorting and finding the Time difference:
SELECT *, DATEDIFF(DD, ISNULL(LAG(ISSUDATE) OVER(PARTITION BY ID ORDER BY ID,IssuDate ), IssuDate),IssuDate) AS TIME_DIFF_IN_DAYS
FROM #TAB
For aggregation with min Max & avg
SELECT ID, MIN(TIME_DIFF_IN_DAYS) AS MIN_TIME_TAKEN, MAX(TIME_DIFF_IN_DAYS) MAX_TIME_TAKEN, AVG(TIME_DIFF_IN_DAYS) AVG_TIME_TAKEN FROM (
SELECT *, DATEDIFF(DD, ISNULL(LAG(ISSUDATE) OVER(PARTITION BY ID ORDER BY ID,IssuDate ), IssuDate),IssuDate) AS TIME_DIFF_IN_DAYS FROM #TAB
)AS A
WHERE TIME_DIFF_IN_DAYS>0 --This one you can comment if you want to show 0 diffence in time
GROUP BY ID

I am not sure about "and c1.id=c.id" in CTE1 coz I am not sure about your exact requrement. Neverthless you can try some thing like,
declare #t table(ID int,IssuDate datetime)
insert into #t values
(194924,'2013-07-31 00:00:00.000')
,(194924,'2010-06-15 00:00:00.000')
,(194924,'2012-07-30 00:00:00.000')
,(194924,'2012-12-11 00:00:00.000')
,(194924,'2014-08-04 00:00:00.000')
,(194966,'2012-06-02 00:00:00.000')
,(194966,'2011-02-03 00:00:00.000')
,(194966,'2011-02-01 00:00:00.000')
,(194987,'2013-04-25 00:00:00.000')
,(194987,'2010-12-03 00:00:00.000')
;with CTE as
(select *,ROW_NUMBER()over(order by id,IssuDate)rn
from #t
)
,Cte1 as
(
select *
,(select datediff(second,c.IssuDate,c1.IssuDate) from CTE c1 where c1.rn=c.rn+1 and c1.id=c.id)Time_between
from CTE C
)
select sum(Time_between),min(Time_between),avg(Time_between),max(Time_between) from cte1
group by id

Related

Recursively loop through a SQL table and find intervals based on Start and End Dates

I have a SQL table that contains employeeid, StartDateTime and EndDatetime as follows:
CREATE TABLE Sample
(
SNO INT,
EmployeeID NVARCHAR(10),
StartDateTime DATE,
EndDateTime DATE
)
INSERT INTO Sample
VALUES
( 1, 'xyz', '2018-01-01', '2018-01-02' ),
( 2, 'xyz', '2018-01-03', '2018-01-05' ),
( 3, 'xyz', '2018-01-06', '2018-02-01' ),
( 4, 'xyz', '2018-02-15', '2018-03-15' ),
( 5, 'xyz', '2018-03-16', '2018-03-19' ),
( 6, 'abc', '2018-01-16', '2018-02-25' ),
( 7, 'abc', '2018-03-08', '2018-03-19' ),
( 8, 'abc', '2018-02-26', '2018-03-01' )
I want the result to be displayed as
EmployeeID | StartDateTime | EndDateTime
------------+-----------------+---------------
xyz | 2018-01-01 | 2018-02-01
xyz | 2018-02-15 | 2018-03-19
abc | 2018-01-16 | 2018-03-01
abc | 2018-03-08 | 2018-03-19
Basically, I want to recursively look at records of each employee and datemine the continuity of Start and EndDates and make a set of continuous date records.
I wrote my query as follows:
SELECT *
FROM dbo.TestTable T1
LEFT JOIN dbo.TestTable t2 ON t2.EmpId = T1.EmpId
WHERE t1.EndDate = DATEADD(DAY, -1, T2.startdate)
to see if I could decipher something from the output looking for a pattern. Later realized that with the above approach, I need to join the same table multiple times to get the output I desire.
Also, there is a case that there can be multiple employee records, so I need direction on efficient way of getting this desired output.
Any help is greatly appreciated.
This will do it for you. Use a recursive CTE to get all the adjacent rows, then get the highest end date for each start date, then the first start date for each end date.
;with cte as (
select EmployeeID, StartDateTime, EndDateTime
from sample s
union all
select CTE.EmployeeID, CTE.StartDateTime, s.EndDateTime
from sample s
join cte on cte.EmployeeID=s.EmployeeID and s.StartDateTime=dateadd(d,1,CTE.EndDateTime)
)
select EmployeeID, Min(StartDateTime) as StartDateTime, EndDateTime from (
select EmployeeID, StartDateTime, Max(EndDateTime) as EndDateTime from cte
group by EmployeeID, StartDateTime
) q group by EmployeeID, EndDateTime
You can use this.
WITH T AS (
SELECT S1.SNO,
S1.EmployeeID,
S1.StartDateTime,
ISNULL(S2.EndDateTime, S1.EndDateTime) EndDateTime,
ROW_NUMBER() OVER(PARTITION BY S1.EmployeeId ORDER BY S1.StartDateTime)
- ROW_NUMBER() OVER(PARTITION BY S1.EmployeeId, CASE WHEN S2.StartDateTime IS NULL THEN 0 ELSE 1 END ORDER BY S1.StartDateTime ) RN,
ROW_NUMBER() OVER(PARTITION BY S1.EmployeeId, ISNULL(S2.EndDateTime, S1.EndDateTime) ORDER BY S1.EmployeeId, S1.StartDateTime) RN_END
FROM Sample S1
LEFT JOIN Sample S2 ON DATEADD(DAY,1,S1.EndDateTime) = S2.StartDateTime
)
SELECT EmployeeID, MIN(StartDateTime) StartDateTime,MAX(EndDateTime) EndDateTime FROM T
WHERE RN_END = 1
GROUP BY EmployeeID, RN
ORDER BY EmployeeID DESC, StartDateTime
Result:
EmployeeID StartDateTime EndDateTime
---------- ------------- -----------
xyz 2018-01-01 2018-02-01
xyz 2018-02-15 2018-03-19
abc 2018-01-16 2018-03-01
abc 2018-03-08 2018-03-19

select all dates between two date column in table

I have a table like this:
Id | From | To
---+------------+------------
1 | 2018-01-28 | 2018-02-01
2 | 2018-02-10 | 2018-02-12
3 | 2018-02-27 | 2018-03-01
How to get all dates between From and To dates like this?
FromDate
----------
2018-01-28
2018-01-29
2018-01-30
2018-01-31
2018-02-01
2018-02-10
2018-02-11
2018-02-12
2018-02-27
2018-02-28
2018-02-01
Generate a calendar table containing all dates within, e.g. 2018, and then inner join that table to your current table:
DECLARE #todate datetime, #fromdate datetime
SELECT #fromdate='2018-01-01', #todate='2018-12-31'
;WITH calendar (FromDate) AS (
SELECT #fromdate AS FromDate
UNION ALL
SELECT DATEADD(day, 1, FromDate)
FROM Calendar
WHERE FromDate < #todate
)
SELECT t1.FromDate
FROM calendar t1
INNER JOIN yourTable t2
ON t1.FromDate BETWEEN t2.[From] AND t2.[To];
Demo
If you don't have that many dates, a recursive CTE is a pretty easy approach:
with cte as (
select id, fromdate as dte, fromdate, todate
from t
union all
select id, dateadd(day, 1, dte), fromdate, todate
from cte
where dte < todate
)
select id, dte
from cte;
A recursive CTE has a default "depth" of 100. That means that it will work for spans up to 100 dates long (for each id). You can override this with the MAXRECURSION option.
It is usually slightly more efficient to do this with some sort of numbers table. However, recursive CTEs are surprisingly efficient for this sort of calculation. And this is a good way to start learning about them.

How to get date range start date , to date from one date in SQL Server

I have only one date fromdate and I want Fromdate, Todate from data.
Here is my table data:
Name FromDate
---------------------------------------
Spisak, Gregory 2015-11-11 20:30:00.000
Patel, Tejal 2015-11-12 00:50:00.000
George, Legy 2015-11-12 03:52:00.000
Gorny,Alexander 2015-11-12 10:05:00.000
Weiner, Doron 2015-11-12 10:31:00.000
Bajaj, Jimmy 2015-11-12 12:24:00.000
Lee, Richard 2015-11-14 10:00:00.000
Soria, Alfie 2015-11-14 11:15:00.000
Weiner, Moshe 2015-11-16 09:37:00.000
Kariolis,Ioannis2015-11-17 12:15:00.000
Cleary, Tara A 2015-11-17 15:39:00.000
I want another ToDate column in which I want date of next name's fromdate.
For example:
Name FromDate ToDate
---------------------------------------------------------------
Spisak, Gregory 2015-11-11 20:30:00.000 2015-11-12 00:50:00.000
Patel, Tejal 2015-11-12 00:50:00.000 2015-11-12 03:52:00.000
try
select *, todt from yourTable t
outer apply
(select top 1 FromDate todt from yourTable
where FromDate>t.FromDate
order by FromDate)a
DECLARE #TEMP TABLE ( _Date DATE )
INSERT INTO #TEMP
( [_Date] )
VALUES ( GETDATE() -- _Date - date
)
INSERT INTO #TEMP
( [_Date] )
VALUES ( DATEADD(DAY, 1, GETDATE()) -- _Date - date
)
INSERT INTO #TEMP
( [_Date] )
VALUES ( DATEADD(DAY, 2, GETDATE()) -- _Date - date
)
INSERT INTO #TEMP
( [_Date] )
VALUES ( DATEADD(DAY, 3, GETDATE()) -- _Date - date
)
INSERT INTO #TEMP
( [_Date] )
VALUES ( DATEADD(DAY, 4, GETDATE()) -- _Date - date
);
WITH CTE
AS ( SELECT * ,
ROW_NUMBER() OVER ( PARTITION BY '' ORDER BY [_Date] ) rn
FROM #TEMP
)
SELECT CTE.[_Date] AS 'StartDate' ,
CTE2.[_Date] AS 'EndDate'
FROM CTE
INNER JOIN CTE AS CTE2 ON CTE2.rn = CTE.rn + 1

sql query to find absent list with date in sql server

I have two attendance tables.One isemployeelist and another is attendence_info.Employeelist contain Emp_Id and Emp_name. Attendance_info is Emp_Id, Date.As below:
Emp_ID Date
----------- -----------------------
1 2014-12-11 00:00:00.000
2 2014-12-11 00:00:00.000
4 2014-12-11 00:00:00.000
5 2014-12-11 00:00:00.000
2 2014-12-10 00:00:00.000
4 2014-12-10 00:00:00.000
5 2014-12-10 00:00:00.000
1 2014-12-09 00:00:00.000
2 2014-12-09 00:00:00.000
3 2014-12-09 00:00:00.000
Here each date some id are absent. I want to find out all absent list with date.Please help to find it by Sql server query. My desired output should be as below:
absentId Date
3 2014-12-11 00:00:00.000
1 2014-12-10 00:00:00.000
3 2014-12-10 00:00:00.000
4 2014-12-09 00:00:00.000
5 2014-12-09 00:00:00.000
You can do this by generating a list of all employees and dates and then removing the ones where the employee is present. This is basically a cross join and left join:
select el.emp_id, d.date
from (select distinct date from Attendance_info) d cross join
employeelist el left join
Attendance_info ai
on ai.date = d.date and ai.emp_id = el.emp_id
where ai.emp_id is null;
select e.Emp_ID as absentId,a.Date as date
from employeelist e
join attendence_info a
on e.Emp_ID=a.Emp_ID
order by a.Date
DECLARE #StartDate DATETIME = '2014-12-09'
DECLARE #EndDate DATETIME = '2014-12-11'
;WITH MyCte ([Date])
AS
(
SELECT [Date] = #StartDate
UNION ALL
SELECT DATEADD(DAY, 1, [Date]) FROM MyCte WHERE [Date] < #EndDate
)
,EmployeeList ([EmpID], [Date])
AS
(
SELECT 1, '2014-12-11 00:00:00.000' UNION
SELECT 2, '2014-12-11 00:00:00.000' UNION
SELECT 4, '2014-12-11 00:00:00.000' UNION
SELECT 5, '2014-12-11 00:00:00.000' UNION
SELECT 2, '2014-12-10 00:00:00.000' UNION
SELECT 4, '2014-12-10 00:00:00.000' UNION
SELECT 5, '2014-12-10 00:00:00.000' UNION
SELECT 1, '2014-12-09 00:00:00.000' UNION
SELECT 2, '2014-12-09 00:00:00.000' UNION
SELECT 3, '2014-12-09 00:00:00.000'
)
SELECT DISTINCT E.[EmpID], M.[Date]
FROM EmployeeList E
CROSS APPLY (SELECT [Date] FROM MyCTE WHERE [Date] NOT IN (SELECT [Date] FROM EmployeeList WHERE EmpID = E.EmpID)) M

Calculating total time excluding overlapped time & breaks in SQLServer

From the list of start time and end times from a select query, I need to find out the total time excluding overlapping time and breaks.
StartTime EndTime
2014-10-01 10:30:00.000 2014-10-01 12:00:00.000 -- 90 mins
2014-10-01 10:40:00.000 2014-10-01 12:00:00.000 --0 since its overlapped with previous
2014-10-01 10:42:00.000 2014-10-01 12:20:00.000 -- 20 mins excluding overlapped time
2014-10-01 10:40:00.000 2014-10-01 13:00:00.000 -- 40 mins
2014-10-01 10:44:00.000 2014-10-01 12:21:00.000 -- 0 previous ones have already covered this time range
2014-10-13 15:50:00.000 2014-10-13 16:00:00.000 -- 10 mins
So the total should be 160 mins in this case.
I don't want to use so many loops to get through with this. Looking for some simple solution.
DECLARE #table TABLE (StartTime DateTime2, EndTime DateTime2)
INSERT INTO #table SELECT '2014-10-01 10:30:00.000', '2014-10-01 12:00:00.000'
INSERT INTO #table SELECT '2014-10-01 10:40:00.000', '2014-10-01 12:00:00.000'
INSERT INTO #table SELECT '2014-10-01 10:42:00.000', '2014-10-01 12:20:00.000'
INSERT INTO #table SELECT '2014-10-01 10:40:00.000', '2014-10-01 13:00:00.000'
INSERT INTO #table SELECT '2014-10-01 10:44:00.000', '2014-10-01 12:21:00.000'
INSERT INTO #table SELECT '2014-10-13 15:50:00.000', '2014-10-13 16:00:00.000'
;WITH addNR AS ( -- Add row numbers
SELECT StartTime, EndTime, ROW_NUMBER() OVER (ORDER BY StartTime, EndTime) AS RowID
FROM #table AS T
), createNewTable AS ( -- Recreate table according overlap time
SELECT StartTime, EndTime, RowID
FROM addNR
WHERE RowID = 1
UNION ALL
SELECT
CASE
WHEN a.StartTime <= AN.StartTime AND AN.StartTime <= a.EndTime THEN a.StartTime
ELSE AN.StartTime END AS StartTime,
CASE WHEN a.StartTime <= AN.EndTime AND AN.EndTime <= a.EndTime THEN a.EndTime
ELSE AN.EndTime END AS EndTime,
AN.RowID
FROM addNR AS AN
INNER JOIN createNewTable AS a
ON a.RowID + 1 = AN.RowID
), getMinutes AS ( -- Get difference in minutes
SELECT DATEDIFF(MINUTE,StartTime,MAX(EndTime)) AS diffMinutes
FROM createNewTable
GROUP BY StartTime
)
SELECT SUM(diffMinutes) AS Result
FROM getMinutes
And the result is 160
To get the result with the data you gave, I assume that the end time is not included (otherwise it would be 91 minutes for the first run). With that in mind, this will give you the result you want with no cursors or loops. If the times span multiple days, the logic will need to be adjusted.
--Create sample data
CREATE TABLE TimesToCheck
([StartTime] datetime, [EndTime] datetime)
;
INSERT INTO TimesToCheck
([StartTime], [EndTime])
VALUES
('2014-10-01 10:30:00', '2014-10-01 12:00:00'),
('2014-10-01 10:40:00', '2014-10-01 12:00:00'),
('2014-10-01 10:42:00', '2014-10-01 12:20:00'),
('2014-10-01 10:40:00', '2014-10-01 13:00:00'),
('2014-10-01 10:44:00', '2014-10-01 12:21:00'),
('2014-10-13 15:50:00', '2014-10-13 16:00:00')
;--Now the solution.
;WITH
E1(N) AS (
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
), -- 1*10^1 or 10 rows
E2(N) AS (SELECT 1 FROM E1 a, E1 b), -- 1*10^2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), -- 1*10^4 or 10,000 rows
N AS (SELECT TOP (3600) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))-1 AS Number FROM E4),
TimeList AS (SELECT CAST(DATEADD(minute,n.number,0) as time) AS m FROM N),
--We really only need the Timelist table. If it is already created, we can start here.
ActiveTimes AS (SELECT DISTINCT t.m FROM TimeList T
INNER JOIN TimesToCheck C ON t.m BETWEEN CAST(c.StartTime as time) AND CAST(DATEADD(minute,-1,c.EndTime) as time))
SELECT COUNT(*) FROM ActiveTimes