Get sequence of days from days - sql

We have a table of days like:
ID Date
1 2015-07-29
2 2015-07-30
3 2015-07-31
4 2015-08-01
5 2015-08-03
7 2015-08-04
8 2015-08-05
9 2015-08-06
10 2015-08-07
11 2015-08-10
And we want to find all the sequences (day+1). The result should be something like this:
Start End
2015-07-29 2015-08-01
2015-08-03 2015-08-07
2015-08-10 2015-08-10
1. Update
First I modified Deepanshu Kalra answer to use RowNumber instead of the Id (Id is autoincrement, so its possible that ids are missing)
DECLARE #P TABLE(DATE DATE)
INSERT INTO #P
SELECT MIN([DATE])
FROM MietvertragsArtikelDays
UNION
SELECT T1.[DATE]
FROM (SELECT ROW_NUMBER() OVER (ORDER BY [DATE]) RowNumber, [DATE] FROM MietvertragsArtikelDays) AS T1 INNER JOIN (SELECT ROW_NUMBER() OVER (ORDER BY [DATE]) RowNumber, [DATE] FROM MietvertragsArtikelDays) AS T2 ON T1.RowNumber=T2.RowNumber+1
WHERE DATEDIFF(DAY,T2.[DATE],T1.[DATE]) <>1
UNION
SELECT T2.[DATE]
FROM (SELECT ROW_NUMBER() OVER (ORDER BY [DATE]) RowNumber, [DATE] FROM MietvertragsArtikelDays) AS T1 INNER JOIN (SELECT ROW_NUMBER() OVER (ORDER BY [DATE]) RowNumber, [DATE] FROM MietvertragsArtikelDays) AS T2 ON T1.RowNumber=T2.RowNumber+1
WHERE DATEDIFF(DAY,T2.[DATE],T1.[DATE]) <>1
DECLARE #X TABLE(DATE DATE, RN INT)
INSERT INTO #X
SELECT *, ROW_NUMBER() OVER(ORDER BY [DATE]) AS X FROM #P
SELECT A.[DATE] Start, ISNULL(B.[DATE],A.[DATE]) [End] FROM #X A
LEFT JOIN (SELECT [DATE], RN-1 AS RN FROM #X) B
ON A.RN=B.RN
WHERE A.RN%2=1
2. Update
The most elegant solution is Ughais
;WITH CTE as
(
SELECT *,DATEDIFF(D,0,[Date]) - ROW_NUMBER()OVER(ORDER BY ID ASC) grp
FROM MietvertragsArtikelDays
)
SELECT MIN([Date]),MAX([Date])
FROM CTE
GROUP BY grp

This is an Islands and Gap problem. You can use ROW_NUMBER and DATEDIFF. Something like this.
SQL Fiddle
Sample Data
DECLARE #Dates TABLE
([ID] int, [Date] datetime);
INSERT INTO #Dates
([ID], [Date])
VALUES
(1, '2015-07-29 00:00:00'),
(2, '2015-07-30 00:00:00'),
(3, '2015-07-31 00:00:00'),
(4, '2015-08-01 00:00:00'),
(5, '2015-08-03 00:00:00'),
(7, '2015-08-04 00:00:00'),
(8, '2015-08-05 00:00:00'),
(9, '2015-08-06 00:00:00'),
(10, '2015-08-07 00:00:00'),
(11, '2015-08-10 00:00:00');
Query
;WITH CTE as
(
SELECT *,DATEDIFF(D,0,[Date]) - ROW_NUMBER()OVER(ORDER BY ID ASC) grp
FROM #Dates
)
SELECT MIN([Date]),MAX([Date])
FROM CTE
GROUP BY grp
Output
2015-07-29 00:00:00.000 2015-08-01 00:00:00.000
2015-08-03 00:00:00.000 2015-08-07 00:00:00.000
2015-08-10 00:00:00.000 2015-08-10 00:00:00.000

As I started from an answer which was already posted, maybe I made it very complex. But it works.
DECLARE #T TABLE(ID INT, DATE DATE)
INSERT INTO #T
SELECT 1, '2015-07-29' UNION ALL
SELECT 2, '2015-07-30' UNION ALL
SELECT 3, '2015-07-31' UNION ALL
SELECT 4, '2015-08-01' UNION ALL
SELECT 5, '2015-08-03' UNION ALL
SELECT 7, '2015-08-04' UNION ALL
SELECT 8, '2015-08-05' UNION ALL
SELECT 9, '2015-08-06' UNION ALL
SELECT 10, '2015-08-07' UNION ALL
SELECT 11, '2015-08-10'
DECLARE #P TABLE(DATE DATE)
INSERT INTO #P
SELECT MIN([DATE])
FROM #T
UNION
SELECT T1.[DATE]
FROM #T AS T1 INNER JOIN #T AS T2 ON T1.ID=T2.ID+1
WHERE DATEDIFF(DAY,T2.[DATE],T1.[DATE]) <>1
UNION
SELECT T2.[DATE]
FROM #T AS T1 INNER JOIN #T AS T2 ON T1.ID=T2.ID+1
WHERE DATEDIFF(DAY,T2.[DATE],T1.[DATE]) <>1
DECLARE #X TABLE(DATE DATE, RN INT)
INSERT INTO #X
SELECT *, ROW_NUMBER() OVER(ORDER BY [DATE]) AS X FROM #P
SELECT A.[DATE], B.[DATE] FROM #X A
LEFT JOIN (SELECT [DATE], RN-1 AS RN FROM #X) B
ON A.RN=B.RN
WHERE A.RN%2=1
Please excuse the standards and all. Will edit later in the day. Sorry for that.

This will work whatever the order and value of Id is:
Declare #dates table(ID int, D datetime)
Insert Into #dates(ID, D)
values (1, '2015-07-29')
, (2, '2015-07-30')
, (3, '2015-07-31')
, (4, '2015-08-01')
, (5, '2015-08-03')
, (7, '2015-08-04')
, (8, '2015-08-05')
, (9, '2015-08-06')
, (10, '2015-08-07')
, (11, '2015-08-10')
; With start(ID, D) as (
-- Get 1st Dates
Select d1.ID, d1.D From #dates as d1
Left Join #dates as d2 On d1.D = DATEADD(DAY, 1, d2.D)
Where d2.ID is NULL
), loop(startD, endD) as (
-- Loop through consecutives dates
Select D, D From start
Union All
Select l.startD, s.D From loop as l
Inner Join #dates as s On s.D = DATEADD(DAY, 1, l.endD)
)
-- Get max end date for each start date
Select startD as [Start], max(endD) as [End] From loop group by startD
Output:
Start End
2015-07-29 2015-08-01
2015-08-03 2015-08-07
2015-08-10 2015-08-10

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

Finding duplicate records in a specific date range

I have a table where I have 4 columns
Serial(nvarchar), SID(nvarchar), DateCreated(Date), CID(unique and int)
I want to find the records where there is duplicate serial and SID and where the 2 duplicate serial fall between date range of 180 days.
please help
Sample Data
Serial SID DateCreated CID
02302-25-0036 HONMD01 2017-05-01 00:00:00.000 1
02302-25-0036 HONMD01 2017-05-01 00:00:00.000 3
0264607 HONMD01 2017-05-01 00:00:00.000 65
0264607 HONMD01 2016-05-01 00:00:00.000 45
03118-09-0366 PRIVA00 2016-05-20 00:00:00.000 34
03118-09-0366 PRIVA00 2016-05-20 00:00:00.000 87
0969130 140439 2017-05-09 00:00:00.000 32
0969130 140439 2017-05-09 00:00:00.000 23
1049567 INIIL00 2017-04-12 00:00:00.000 76
create table #Test (Serial nvarchar(20), [SID] nvarchar(10), DateCreated datetime, CID int)
Insert into #Test values ('02302-25-0036', 'HONMD01', '2017-05-01 00:00:00.000', 1)
, ('02302-25-0036', 'HONMD01', '2017-05-01 00:00:00.000', 3)
, ('0264607', 'HONMD01', '2017-05-01 00:00:00.000', 65)
, ('0264607', 'HONMD01', '2016-05-01 00:00:00.000', 45)
, ('03118-09-0366', 'PRIVA00', '2016-05-20 00:00:00.000', 34)
, ('03118-09-0366', 'PRIVA00', '2016-05-20 00:00:00.000', 87)
, ('0969130', '140439', '2017-05-09 00:00:00.000', 32)
, ('0969130', '140439', '2017-05-09 00:00:00.000', 23)
, ('1049567', 'INIIL00', '2017-04-12 00:00:00.000', 76)
select distinct a.*
from
(
select t.*
from #Test t
inner join (
Select Serial, [SID]
from #Test
group by Serial, [SID]
Having count(*)>=2
) d on d.Serial = t.Serial and t.SID= t.SID
) a
full outer join
(
select t.*
from #Test t
inner join (
Select Serial, [SID]
from #Test
group by Serial, [SID]
Having count(*)>=2
) d on d.Serial = t.Serial and t.SID= t.SID
) b on a.Serial = b.Serial and a.SID= b.SID
where datediff(d,a.DateCreated, b.DateCreated)<180
Try to do this:
with cte as (
select
serial,
sid,
dateCreated,
cid,
coalesce(max(dateCreated) over(partition by serial, sid order by cid, dateCreated asc rows between unbounded preceding and 1 preceding), '1900-01-01') as last,
coalesce(min(dateCreated) over(partition by serial, sid order by cid, dateCreated asc rows between 1 following and unbounded following), '5999-01-01') as next
from table_name
)
select *
from cte
where
datediff(day, last, dateCreated) >= 180
and datediff(day, dateCreated, next) >= 180
This was a challenging question ! I have left final output with *(PreviousDate, rno) for easy understanding. Here is my way to solve :
Create table #t(Serial nvarchar(100),SID nvarchar(100),DateCreated date,CID int)
Insert into #t values
('02302-25-0036', 'HONMD01', '2017-05-01 00:00:00.000', 1),
('02302-25-0036', 'HONMD01', '2017-05-01 00:00:00.000', 3),
('0264607', 'HONMD01', '2017-05-01 00:00:00.000', 65),
('0264607', 'HONMD01', '2016-05-01 00:00:00.000', 45),
('03118-09-0366', 'PRIVA00', '2016-05-20 00:00:00.000', 34),
('03118-09-0366', 'PRIVA00', '2016-05-20 00:00:00.000', 87),
('0969130', '140439', '2017-05-09 00:00:00.000', 32),
('0969130', '140439', '2017-05-09 00:00:00.000', 23),
('1049567', 'INIIL00', '2017-04-12 00:00:00.000', 76)
Select iq2.*
FROM
(Select iq.Serial, iq.SID, iq.DateCreated, iq.CID, iq.PreviousDate,
ROW_NUMBER() OVER (PARTITION BY iq.Serial,iq.SID, CASE WHEN DATEDIFF(day, iq.DateCreated, iq.PreviousDate) <= 180 THEN 1 ELSE 0 END
ORDER BY Serial,SID) rno
FROM
(select Serial,SID,DateCreated,CID,
MAX(DateCreated) OVER (PARTITION BY Serial,SID ORDER BY Serial,SID) maxDate,
DATEADD(day,-180,MAX(DateCreated) OVER (PARTITION BY Serial,SID ORDER BY Serial,SID)) PreviousDate
from #t
)iq
)iq2
where iq2.rno <> 1
output :
Serial SID DateCreated CID PreviousDate rno
---------- ------- ---------- ---- ----------- ----
02302-25-0036 HONMD01 2017-05-01 3 2016-11-02 2
03118-09-0366 PRIVA00 2016-05-20 87 2015-11-22 2
0969130 140439 2017-05-09 23 2016-11-10 2
PS : PreviousDate is MAX PreviousDate

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 TSQL for Workers per Hour

I have a log with fingerprint timestamps as follows:
Usr TimeStamp
-------------------------
1 2015-07-01 08:01:00
2 2015-07-01 08:05:00
3 2015-07-01 08:07:00
1 2015-07-01 10:05:00
3 2015-07-01 11:00:00
1 2015-07-01 12:01:00
2 2015-07-01 13:03:00
2 2015-07-01 14:02:00
1 2015-07-01 16:03:00
2 2015-07-01 18:04:00
And I wish an output of workers per hour (rounding to nearest hour)
The theoretical output should be:
7:00 0
8:00 3
9:00 3
10:00 2
11:00 1
12:00 2
13:00 1
14:00 2
15:00 2
16:00 1
17:00 1
18:00 0
19:00 0
Can anyone think on how to approach this as SQL or if no other way, through TSQL?
Edit: The timestamps are logins and logouts of the different users. So at 8am 3 users logged in and the same 3 are still working at 9am. One of them leaves at 10am. etc
To start with you can use datepart to get hours for the days as following and then use group by user
SELECT DATEPART(HOUR, GETDATE());
SQL Fiddle
SELECT Convert(varchar(5),DATEPART(HOUR, timestamp)) + ':00' as time,
count(usr) as users
from tbl
group by DATEPART(HOUR, timestamp)
You need a datetime hour table to do this.
Note : This is just a example of showing how the query should work for one day. Replace the CTE with datetime hour table. In datetime hour table every date should start with 07:00:00 hour and end with 19:00:00 hour
When you want to do this for more than one day then you may have to include the Cast(dt.date_time AS DATE) in select and group by to differentiate the hour belong to which day
WITH datetime_table
AS (SELECT '2015-07-01 07:00:00' AS date_time
UNION ALL
SELECT '2015-07-01 08:00:00'
UNION ALL
SELECT '2015-07-01 09:00:00'
UNION ALL
SELECT '2015-07-01 10:00:00'
UNION ALL
SELECT '2015-07-01 11:00:00'
UNION ALL
SELECT '2015-07-01 12:00:00'
UNION ALL
SELECT '2015-07-01 13:00:00'
UNION ALL
SELECT '2015-07-01 14:00:00'
UNION ALL
SELECT '2015-07-01 15:00:00'
UNION ALL
SELECT '2015-07-01 16:00:00'
UNION ALL
SELECT '2015-07-01 17:00:00'
UNION ALL
SELECT '2015-07-01 18:00:00'
UNION ALL
SELECT '2015-07-01 19:00:00')
SELECT Datepart(hour, dt.date_time),
Hour_count=Count(t.id)
FROM datetime_table dt
LEFT OUTER JOIN Yourtable t
ON Cast(t.dates AS DATE) = Cast(dt.date_time AS DATE)
AND Datepart(hour, t.dates) =
Datepart(hour, dt.date_time)
GROUP BY Datepart(hour, dt.date_time)
SQLFIDDLE DEMO
You just need to group by hours and date. Check this below query and hope this helps you:
Create table #t1
(
usr int,
timelog datetime
)
Insert into #t1 values(1, '2015-07-01 08:01:00')
Insert into #t1 values(2, '2015-07-01 08:05:00')
Insert into #t1 values(3, '2015-07-01 08:07:00')
Insert into #t1 values(1, '2015-07-01 10:05:00')
Insert into #t1 values(3, '2015-07-01 11:00:00')
Insert into #t1 values(1, '2015-07-01 12:01:00')
Insert into #t1 values(2, '2015-07-01 13:03:00')
Insert into #t1 values(2, '2015-07-01 14:02:00')
Insert into #t1 values(1, '2015-07-01 16:03:00')
Insert into #t1 values(2, '2015-07-01 18:04:00')
Select cast(timelog as varchar(11)) as LogDate, Datepart(hour, timelog) as LogTime, count(usr) as UserCount from #t1
Group by Datepart(hour, timelog), cast(timelog as varchar(11))
The harder part is creating the zeros where data is missing. The usual approach is to generate a list of all possible "slots" and then do an outer join to the actual data. I'm assuming that you only want to run this for a single day at a time.
My approach, which is just an example, works because it does a cross join of two tables with 6 and 4 rows respectively and 6 times 4 is 24.
select f1.d * 6 + f0.d, coalesce(data.cnt, 0)
from
(
select 0 as d union all select 1 union all select 2 union all
select 3 union all select 4 union all select 5
) as f0,
(
select 0 as d union all select 1 union all
select 2 union all select 3
) as f1
left outer join
(
select
cast(datepart(hh, TimeStamp) as varchar(2)) + ':00' as hr,
count(*) as cnt
from LOG
group by datepart(hh, TimeStamp)
) as data
on data.hr = f1.d * 6 + f0.d
First you need to round up time to the closest hour
DATEADD(HOUR, DATEDIFF(HOUR, 0, DATEADD(MI, 30, TimeStamp)), 0)
As you see first we add 30 minutes to the original time (DATEADD(MI, 30, TimeStamp))
This approach will round up 08:04 to 08:00 or 07:58 to 8:00 too.
As I assume some workers can start working little bid early
SELECT DATEADD(HOUR, DATEDIFF(HOUR, 0, DATEADD(MI, 30, TimeStamp)), 0) As FingertipTime
FROM Fingertips
You can create a Computed column if you use rounded timestamp often
ALTER TABLE Fingertips ADD RoundedTimeStamp AS (DATEADD(HOUR, DATEDIFF(HOUR, 0, DATEADD(MI, 30, TimeStamp)), 0));
For comparing timestamps with constants of work hours you can find different methods. I will use a variable of type TABLE where i generate work hours for current day
Then using LEFT JOIN and GROUP BY we get quantity of timestamps
DECLARE #WorkHours TABLE(WorkHour DATETIME)
INSERT INTO #WorkHours (WorkHour) VALUES
('2015-07-01 07:00'),
('2015-07-01 08:00'),
('2015-07-01 09:00'),
('2015-07-01 10:00'),
('2015-07-01 11:00'),
('2015-07-01 12:00'),
('2015-07-01 13:00'),
('2015-07-01 14:00'),
('2015-07-01 15:00'),
('2015-07-01 16:00'),
('2015-07-01 17:00'),
('2015-07-01 18:00'),
('2015-07-01 19:00')
SELECT wh.Workhour
, COUNT(ft.TimeStamp) As Quantity
FROM #WorkHours wh
LEFT JOIN Fingertips ft ON ft.RoundedTimeStamp = wh.WorkHour
GROUP BY wh.WorkHour
Check this SQL Fiddle
Many separate parts that have to be glued together to get this done.
First rounding, this is easily done with obtaining the hour part of the date + 30 minutes. Then determine start and end records. If there are no fields to indicate this and assuming the first occurrence of a day is the login or start, you can use row_number and use the odd numbers as start records.
Then start and end have to be coupled, in sql server 2012 and higher this can be easily done with the lead function
To get the missing hours a sequence has to be created with all the hours. Several options for this (good link here), but I like the approach of using row_number on a table that is sure to contain enough rows (with a proper column for order by), such as sys.all_objects used in the link. That way hours 7 to 19 could be created as: select top 13 ROW_NUMBER() over (order by object_id) + 6 [Hour] from sys.all_objects
If there's only one date to check on, the query can simple left join on the hour of the timestamp fingerprints. If there are more dates, a second sequence could be created cross applied to the times to get all dates. Assuming the one date, final code would be:
declare #t table(Usr int, [timestamp] datetime)
insert #t values
(1 , '2015-07-01 08:01:00'),
(2 , '2015-07-01 08:05:00'),
(3 , '2015-07-01 08:07:00'),
(1 , '2015-07-01 10:05:00'),
(3 , '2015-07-01 11:00:00'),
(1 , '2015-07-01 12:01:00'),
(2 , '2015-07-01 13:03:00'),
(2 , '2015-07-01 14:02:00'),
(1 , '2015-07-01 16:03:00'),
(2 , '2015-07-01 18:04:00'),
(2 , '2015-07-01 18:04:00')
;with usrHours as
(
select Usr, datepart(hour, DATEADD(minute,30, times.timestamp)) [Hour] --convert all times to the rounded hour (rounding by adding 30 minutes)
, ROW_NUMBER() over (partition by usr order by [timestamp] ) rnr
from #t times --#t should be your logging table
), startend as --get next (end) hour by using lead
(
select Usr, [hour] StartHour , LEAD([Hour]) over (partition by usr order by rnr) NextHour ,rnr
from usrHours
),hours as --sequence of hours 7 to 19
(
select top 13 ROW_NUMBER() over (order by object_id) + 6 [Hour] from sys.all_objects
)
select cast([Hour] as varchar) + ':00' [Hour], COUNT(startend.usr) Users
from hours --sequence is leading
left join startend on hours.Hour between startend.StartHour and startend.NextHour
and rnr % 2 = 1 --every odd row number is a start time
group by Hours.hour
Here is my final working code:
create table tsts(id int, dates datetime)
insert tsts values
(1 , '2015-07-01 08:01:00'),
(2 , '2015-07-01 08:05:00'),
(3 , '2015-07-01 08:07:00'),
(1 , '2015-07-01 10:05:00'),
(3 , '2015-07-01 11:00:00'),
(1 , '2015-07-01 12:01:00'),
(2 , '2015-07-01 13:03:00'),
(2 , '2015-07-01 14:02:00'),
(1 , '2015-07-01 16:03:00'),
(2 , '2015-07-01 18:04:00')
select horas.hora, isnull(sum(math) over(order by horas.hora rows unbounded preceding),0) as Employees from
(
select 0 as hora union all
select 1 as hora union all
select 2 as hora union all
select 3 as hora union all
select 4 as hora union all
select 5 as hora union all
select 6 as hora union all
select 7 as hora union all
select 8 as hora union all
select 9 as hora union all
select 10 as hora union all
select 11 as hora union all
select 12 as hora union all
select 13 as hora union all
select 14 as hora union all
select 15 as hora union all
select 16 as hora union all
select 17 as hora union all
select 18 as hora union all
select 19 as hora union all
select 20 as hora union all
select 21 as hora union all
select 22 as hora union all
select 23
) as horas
left outer join
(
select hora, sum(math) as math from
(
select id, hora, iif(rowid%2 = 1,1,-1) math from
(
select row_number() over (partition by id order by id, dates) as rowid, id, datepart(hh,dateadd(mi, 30, dates)) as hora from tsts
) as Q1
) as Q2
group by hora
) as Q3
on horas.hora = Q3.hora
SQL Fiddle

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