Number of rows per hour starting at a specific time - sql

In order to get data for some reporting, I have to know how much lines have been inserted per hour in a table starting at a specific hour for a specific day. I already found a part of the solution in another question but I didn't manage to find a way to adapt it in my case. This is the code I've written so far:
SELECT DATEADD(HOUR, DATEDIFF(HOUR, 0, t.mydatetime), 0) AS HOUR_CONCERNED,
COUNT(*) AS NB_ROWS
FROM mytable t
WHERE CONVERT(DATETIME, FLOOR(CONVERT(FLOAT, t.mydatetime))) = '2016-06-06'
GROUP BY DATEADD(HOUR, DATEDIFF(HOUR, 0, t.mydatetime), 0)
ORDER BY HOUR_CONCERNED;
It gives me the following results:
HOUR_CONCERNED NB_ROWS
------------------- --------
2016-06-06 10:00:00 2157
2016-06-06 11:00:00 60740
2016-06-06 12:00:00 66189
2016-06-06 13:00:00 77096
2016-06-06 14:00:00 90039
The problem is that I can't find a way to start my results at a specific time such as 9.30am and to get the number of rows per hour starting from this time. In other words, I'm looking for the number of rows between 9.30am and 10.30am, between 10.30am and 11.30am, etc. The results I'm looking for should look like this:
HOUR_CONCERNED NB_ROWS
------------------- --------
2016-06-06 09:30:00 3550
2016-06-06 10:30:00 33002
2016-06-06 11:30:00 42058
2016-06-06 12:30:00 55008
2016-06-06 13:30:00 72000
Is there an easy way to adapt my query and get those results ?

Given a specific starting time, you can get hour blocks by finding the number of minutes since your start time, and dividing by 60, then adding this number of hours back to the start time e.g.
DECLARE #StartTime DATETIME2(0) = '20160606 09:30';
WITH DummyData (mydatetime) AS
( SELECT TOP 200 DATEADD(MINUTE, ROW_NUMBER() OVER(ORDER BY [object_id]) - 1, #StartTime)
FROM sys.all_objects
)
SELECT HoursSinceStart = FLOOR(DATEDIFF(MINUTE, #StartTime, mydatetime) / 60.0),
Display = DATEADD(HOUR, FLOOR(DATEDIFF(MINUTE, #StartTime, mydatetime) / 60.0), #StartTime),
Records = COUNT(*)
FROM DummyData
WHERE myDateTime >= #StartTime
GROUP BY FLOOR(DATEDIFF(MINUTE, #StartTime, mydatetime) / 60.0)
ORDER BY Display;
Which gives:
HoursSinceStart Display Records
0 2016-06-06 09:30:00 60
1 2016-06-06 10:30:00 40
2 2016-06-06 11:30:00 60
3 2016-06-06 12:30:00 20
I have left the HoursSinceStart column in, to hopefully assist in deconstructing the logic contained in the Display column
The problem with this method is that it will only give you results for blocks that exist, if you also need those that don't you will need to generate all time blocks using a numbers table, then left join to your data:
You can quickly generate a series of numbers using this:
DECLARE #StartTime DATETIME2(0) = '20160606 09:30';
-- GENERATE 10 ROWS
WITH N1 AS (SELECT N FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n (N)),
-- CROSS JOIN THE 10 ROWS TO GET 100 ROWS
N2 (N) AS (SELECT 1 FROM N1 AS N1 CROSS JOIN N1 AS N2),
--CROSS JOIN THE 100 ROWS TO GET 10,000 ROWS
N3 (N) AS (SELECT 1 FROM N2 AS N1 CROSS JOIN N2 AS N2),
--APPLY ROW_NUMBER TO GET A SET OF NUMBERS FROM 0 - 99,999
Numbers (N) AS (SELECT ROW_NUMBER() OVER(ORDER BY N) - 1 FROM N3)
SELECT *,
TimeStart = DATEADD(HOUR, N, #StartTime),
TimeEnd = DATEADD(HOUR, N + 1, #StartTime)
FROM Numbers;
Which gives something like:
N TimeStart TimeEnd
--------------------------------------------------
0 2016-06-06 09:30:00 2016-06-06 10:30:00
1 2016-06-06 10:30:00 2016-06-06 11:30:00
2 2016-06-06 11:30:00 2016-06-06 12:30:00
3 2016-06-06 12:30:00 2016-06-06 13:30:00
4 2016-06-06 13:30:00 2016-06-06 14:30:00
5 2016-06-06 14:30:00 2016-06-06 15:30:00
6 2016-06-06 15:30:00 2016-06-06 16:30:00
7 2016-06-06 16:30:00 2016-06-06 17:30:00
Then you can left join your data to this (you will probably need an end time too);
DECLARE #StartTime DATETIME2(0) = '20160606 09:30',
#EndTime DATETIME2(0) = '20160606 15:30';
WITH DummyData (mydatetime) AS
( SELECT TOP 200 DATEADD(MINUTE, ROW_NUMBER() OVER(ORDER BY [object_id]) - 1, #StartTime)
FROM sys.all_objects
),
-- GENERATE NUMBERS
N1 AS (SELECT N FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n (N)),
N2 (N) AS (SELECT 1 FROM N1 AS N1 CROSS JOIN N1 AS N2),
N3 (N) AS (SELECT 1 FROM N2 AS N1 CROSS JOIN N2 AS N2),
Numbers (N) AS (SELECT ROW_NUMBER() OVER(ORDER BY N) - 1 FROM N3),
TimePeriods AS
( SELECT TimeStart = DATEADD(HOUR, N, #StartTime),
TimeEnd = DATEADD(HOUR, N + 1, #StartTime)
FROM Numbers
WHERE DATEADD(HOUR, N, #StartTime) < #EndTime
)
SELECT tp.TimeStart, tp.TimeEnd, Records = COUNT(dd.myDateTime)
FROM TimePeriods AS tp
LEFT JOIN DummyData AS dd
ON dd.mydatetime >= tp.TimeStart
AND dd.mydatetime < tp.TimeEnd
GROUP BY tp.TimeStart, tp.TimeEnd
ORDER BY tp.TimeStart;
Which will return 0 where there are no records:
TimeStart TimeEnd Records
---------------------------------------------------------
2016-06-06 09:30:00 2016-06-06 10:30:00 60
2016-06-06 10:30:00 2016-06-06 11:30:00 60
2016-06-06 11:30:00 2016-06-06 12:30:00 60
2016-06-06 12:30:00 2016-06-06 13:30:00 20
2016-06-06 13:30:00 2016-06-06 14:30:00 0
2016-06-06 14:30:00 2016-06-06 15:30:00 0

Try this:
SELECT DATEADD( MINUTE, 30, DATEADD(HOUR, DATEDIFF(HOUR, 0, DATEADD( MINUTE, -30, t.mydatetime)), 0)) AS HOUR_CONCERNED,
COUNT(*) AS NB_ROWS
FROM mytable t
WHERE CONVERT(DATETIME, FLOOR(CONVERT(FLOAT, t.mydatetime))) = '2016-06-06'
GROUP BY DATEADD(HOUR, DATEDIFF(HOUR, 0, DATEADD( MINUTE, -30, t.mydatetime)), 0)
ORDER BY HOUR_CONCERNED;
I added a 30 min offset into the GROUP BY function to treat 9:30 as 9:00, 10:30 as 10:00 and so on. In the select part I reverse this offset to give a proper interval.
The WHERE condition in your query needs to change though for performance reasons. Instead of truncating timestamps to a nearest day, you should filter by a range:
WHERE t.mydatetime >= CONVERT( DATETIME, '2016-06-06' ) AND t.mydatetime < CONVERT( DATETIME, '2016-06-07' )

You need to have the time in the where clause, and set a greater than against the time you want to measure from? Also, you can use DATEPART to get the hours.
SELECT NB_ROWS = COUNT(*)
,HOUR_CONCERNED = DATEPART(HOUR, InsertedDate)
FROM table
WHERE InsertedDate = '20160531'
AND InsertedDate> time
GROUP BY DATEPART(HOUR, InsertedDate)

Related

Summarising a table containing timestamps into 10-minute periods

I have a SQL-Server table called UserConnections that is structured like this:
ID
User
From
To
1
Bob
31-jan-2023 09:00:00
31-jan-2023 10:00:00
2
Bob
31-jan-2023 12:00:00
31-jan-2023 15:00:00
3
Sally
31-jan-2023 14:00:00
31-jan-2023 16:00:00
and I want to create a summary table for the previous day specifying the number of users connected during each 10-minute period. So it would look something like this
Period Start
User Count
31-jan-2023 00:00:00
0
31-jan-2023 00:10:00
0
...
...
31-jan-2023 09:00:00
1
31-jan-2023 09:10:00
1
...
...
31-jan-2023 12:00:00
1
31-jan-2023 12:10:00
1
...
...
31-jan-2023 14:00:00
2
31-jan-2023 14:10:00
2
...
...
31-jan-2023 15:00:00
1
31-jan-2023 15:10:00
1
...
...
31-jan-2023 16:00:00
0
31-jan-2023 16:10:00
0
So I need to get the start of each 10-minute period, and then count the number of connections where the [from] <= PeriodStart and [To] >= PeriodEnd
Given the start and end I can probably do the counting but I have no idea how to get the 10-minute periods (I am not experienced with complex SQL!).
I've looked at a few Date/Time functions but really don't know where to start.
I've also looked at this: MSSQL Start and End Time Grouped by 10 Minute, for Elapsed Time
which looks similar but I'm having difficulty seeing how to adjust it for my data.
The first trick here, which is really the basis for the entire solution, is to generate a list of all the 10 minute intervals for the day. To accomplish this I used a tally table. I keep one as a view on my system like this. I got my version from Jeff Moden who has a great article on the topic.
create View [dbo].[cteTally] as
WITH
E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
)
select N from cteTally
Next I need to create a table so I have something to work with on my machine.
declare #Something table
(
ID int
, [User] varchar(10)
, [From] datetime
, [To] datetime
)
insert #Something values
(1, 'Bob', '31-jan-2023 09:00:00', '31-jan-2023 10:00:00')
, (2, 'Bob', '31-jan-2023 12:00:00', '31-jan-2023 15:00:00')
, (3, 'Sally', '31-jan-2023 14:00:00', '31-jan-2023 16:00:00')
Now that I have the foundation setup the query is fairly straight forward.
with DateVals as --used a cte here so I don't have to write out the date math over and over. ;)
(
select PeriodStart = dateadd(minute, (t.N - 1) * 10, convert(datetime, convert(date, getdate())))
from cteTally t
where t.N <= 144 --24 hours a day, 6 times an hour
)
select dv.PeriodStart
, UserCount = count(s.[From])
from DateVals dv
left join #Something s on s.[From] <= dv.PeriodStart and s.[To] > dv.PeriodStart
group by dv.PeriodStart
order by dv.PeriodStart

Time until midnight between two date and time columns

I need help with my query to calculate the time until midnight between two date and time columns
break down by day
This is the main table:
ID
Start_Time
End_time
DateDiff
32221
01-01-2022 13:10:00
01-03-2022 13:10:00
2880
My query:
SELECT
start_time.ID,
start_time.Date_Time AS Start_time,
end_time.Date_Time AS End_time,
DATEDIFF(minute, start_time.Date_Time, end_time.Date_Time) AS DateDiff
FROM
Main
what I need is similar to this:
ID
Date_start
End_time
DateDiff
32221
01-01-2022 13:10:00
01-01-2022 23:59:59
654
32221
01-02-2022 00:00:00
01-02-2022 23:59:59
1440
32221
01-03-2022 00:00:00
01-03-2022 13:10:00
781
how i can do that?
You can loop through the times, always adding the time untill midnight, untill your 'start_time + 1 day' is bigger than your end_time.
The below code can be run directly in SQL (mind the date notation, my SQL is in united states notation, so if yours is in Europe this will give you back results for 3 months instead of 3 days);
DECLARE #start_time datetime2 = '01/01/2022 13:00:00';
DECLARE #end_time datetime2 = '03/01/2022 14:00:00';
DECLARE #daily_end_time datetime2=NULL;
DECLARE #Table Table (start_time datetime2, end_time datetime2, diff nvarchar(8));
DECLARE #diff_minutes_start int = DATEDIFF(MINUTE,#start_time,DateDiff(day,0,dateadd(day,1,#start_time)));
DECLARE #diff_minutes_end int = DATEDIFF(minute,#end_time,DateDiff(day,0,dateadd(day,1,#end_time)))
SET #daily_end_time = DATEADD(mi,#diff_minutes_start,#start_time)
WHILE #daily_end_time < #end_time
BEGIN
INSERT INTO #Table (start_time,end_time,diff)
VALUES (
#start_time,
CASE WHEN DATEADD(day,1,#daily_end_time) > #end_time THEN #end_time ELSE
#daily_end_time END,
CASE WHEN DATEADD(day,1,#daily_end_time) > #end_time THEN #diff_minutes_end ELSE
#diff_minutes_start END )
SET #daily_end_time = DATEADD(mi,#diff_minutes_start,#start_time)
SET #start_time = DATEADD(mi,1,#daily_end_time);
select #diff_minutes_start =
DATEDIFF(MINUTE,#start_time,DateDiff(day,0,dateadd(day,1,#start_time)));
select #diff_minutes_end = DATEDIFF(minute,#end_time,DateDiff(day,0,dateadd(day,1,#end_time)))
END
SELECT * FROM #Table
And the results:
You may use a recursive CTE as the following:
With CTE As
(
Select ID, Start_Time, End_time, DATEADD(Second, -1, DATEADD(Day, DATEDIFF(Day,0, Start_Time), 1)) et
From main
Union All
Select C.ID, DATEADD(Second, 1, C.et), C.End_time, DATEADD(Day, 1, C.et)
From CTE C Join main T
On C.ID = T.ID
Where DATEADD(Second, 1, C.et) <= C.End_time
)
Select ID, Start_Time,
Case When End_Time <= et Then End_Time Else et End As End_Time,
DATEDIFF(Minute, Start_Time, DATEADD(Second, 1, Case When End_Time <= et Then End_Time Else et End)) As [DateDiff]
From CTE
Order By ID, Start_Time
See a demo with extended data sample from db<>fiddle.
You can also solve this with a tally table, using the expanded (to show different cases) sample data
ID
StartTime
EndTime
32221
2022-01-01 13:10:00
2022-01-03 13:10:00
32222
2022-02-02 10:10:00
2022-02-02 17:10:00
32223
2022-03-03 19:10:00
2022-03-04 08:10:00
32224
2022-04-04 19:10:00
2022-04-08 08:10:00
and the code
with cteSampleData as ( --Enter some sample data, include spans of 0, 1, and >1 days
SELECT * --Note that we need CONVERT to make sure the dates are treated as datetime not string!
FROM (VALUES(32221, CONVERT(datetime2(0), '01-01-2022 13:10:00'), CONVERT(datetime2(0), '01-03-2022 13:10:00') )
, (32222, '02-02-2022 10:10:00', '02-02-2022 17:10:00')
, (32223, '03-03-2022 19:10:00', '03-04-2022 08:10:00')
, (32224, '04-04-2022 19:10:00', '04-08-2022 08:10:00')
) as Samp(ID, StartTime, EndTime)
), cteWithControl as ( --Add some fields to make testing cledarer - you could do this as part of a subsequent step instead
SELECT *
, CONVERT(date, StartTime) as StartDate , CONVERT(date, EndTime) as EndDate
, DATEDIFF(day, StartTime , EndTime) as DiffDays
--, DATEDIFF(day, CONVERT(date, StartTime) , CONVERT(date, EndTime)) as DiffDays
FROM cteSampleData
), cteTally as ( --Get a list of integers to represent days, assume nothing lasts longer than a year
SELECT top 365 ROW_NUMBER() over (ORDER BY name) as Tally
FROM sys.objects --just a table we know has over 300 rows, look up tally tables for other generation methods
)--The real work begins below, partition the data into "same day" and "multi-day" spans
, cteSet as (
SELECT ID, StartTime, EndTime, DiffDays, 1 as DayNumber
FROM cteWithControl WHERE DiffDays = 0
UNION ALL
SELECT ID --For multi-day, cross with the tally table and treat first and last days special
, CASE WHEN T.Tally = 1 THEN StartTime --For the first day the start time is the real time
ELSE DATEADD (day, T.Tally - 1, startdate) END as StartTime --Otherwise it's the start of the day
, CASE WHEN T.Tally = DiffDays + 1 THEN EndTime --For the last day the end is the real end
ELSE DATEADD (second, -1, CONVERT(DATETIME2(0), DATEADD (day, T.Tally, startdate)))
END as EndTime --otherwise 1 second less than the next day
, DiffDays, Tally as DayNumber
FROM cteWithControl as D CROSS JOIN cteTally as T
WHERE DiffDays > 0 AND T.Tally <= D.DiffDays + 1
)--Now we display the results and calculate the length (in minutes) of each span
SELECT *
, DATEDIFF(MINUTE, StartTime, EndTime) as DateDiff
FROM cteSet
ORDER BY ID, DayNumber
we get the output
ID
StartTime
EndTime
DiffDays
DayNumber
DateDiff
32221
2022-01-01 13:10:00
2022-01-01 23:59:59
2
1
649
32221
2022-01-02 00:00:00
2022-01-02 23:59:59
2
2
1439
32221
2022-01-03 00:00:00
2022-01-03 13:10:00
2
3
790
32222
2022-02-02 10:10:00
2022-02-02 17:10:00
0
1
420
32223
2022-03-03 19:10:00
2022-03-03 23:59:59
1
1
289
32223
2022-03-04 00:00:00
2022-03-04 08:10:00
1
2
490
32224
2022-04-04 19:10:00
2022-04-04 23:59:59
4
1
289
32224
2022-04-05 00:00:00
2022-04-05 23:59:59
4
2
1439
32224
2022-04-06 00:00:00
2022-04-06 23:59:59
4
3
1439
32224
2022-04-07 00:00:00
2022-04-07 23:59:59
4
4
1439
32224
2022-04-08 00:00:00
2022-04-08 08:10:00
4
5
490

Split DATEDIFF into separate months

I currently have an issue whereby I am doing DATEDIFF in minutes between a start date and end date, however I when this date goes over into a new month I need the figures to be separate for each month.
Please see example data (both Text and Image view);
SELECT [BookingNum]
,[StartDate]
,[EndDate]
,[Location]
,DATEPART(m,startdate) AS [Month]
,DATEDIFF(MINUTE,StartDate,EndDate) AS [Minutes]
FROM [Test].[dbo].[Booking]
BookingNum StartDate EndDate Location Month Minutes
1 2019-02-05 12:54:00.000 2019-02-08 15:00:00.000 Area 1 2 4446
2 2019-05-02 10:41:00.000 2019-05-10 12:39:00.000 Area 2 5 11638
3 2019-06-01 10:30:00.000 2019-06-04 09:25:00.000 Area 3 6 4255
4 2019-02-02 09:41:00.000 2019-04-20 11:54:00.000 Area 1 2 111013
5 2019-03-29 19:09:00.000 2019-04-02 10:41:00.000 Area 3 3 5252
For rows 4 & 5 there would need to be additional rows as they go across multiple months.
Example for the data in row 4, I would want to see;
StartDate EndDate Location Month Minutes
2019-02-02 09:41:00.000 2019-02-28 23:59:00.000 Area 1 2 38298
2019-03-01 00:00:00.000 2019-03-31 23:59:00.000 Area 1 3 44639
2019-04-01 00:00:00.000 2019-04-20 23:59:00.000 Area 1 4 28074
This would then give me the total minutes for that month only between the start and end date.
Any help much appreciated.
Edit: Recursive CTE should do the trick! Basically, use recursion to keep getting the start date through the lesser of the EOM and the end date, until ultimately you reach the end date.
Fiddle
DECLARE #tbl TABLE (bookingnum INT, sd DATETIME, ed DATETIME)
INSERT INTO #tbl VALUES
(1, '2/5/2019 12:54 PM', '2/8/2019 3:00 PM'),
(2, '5/2/2019 10:41 AM', '5/10/2019 12:39 PM'),
(3, '6/1/2019 10:30 AM', '6/4/2019 9:25 AM'),
(4, '2/2/2019 9:41 AM', '5/20/2019 11:54 AM'),
(5, '3/29/2019 7:09 PM', '4/2/2019 10:41 AM')
;WITH cte AS (
SELECT bookingnum, sd, DATEADD(DAY, 1, EOMONTH(sd)) eom, ed,
CASE WHEN DATEADD(DAY, 1, EOMONTH(sd)) < ed THEN DATEADD(DAY, 1, EOMONTH(sd)) else ed END AS applied_ed
FROM #tbl
UNION ALL
SELECT bookingnum, applied_ed, DATEADD(DAY, 1, EOMONTH(applied_ed)) eom, ed,
CASE WHEN DATEADD(DAY, 1, EOMONTH(applied_ed)) < ed THEN DATEADD(DAY, 1, EOMONTH(applied_ed)) else ed END AS applied_ed
FROM cte
WHERE applied_ed < ed
)
SELECT bookingnum, sd, applied_ed AS ed, DATEDIFF(MINUTE, sd, applied_ed) minutes
FROM cte
ORDER BY bookingnum, sd
Returns:
bookingnum sd ed minutes
1 2019-02-05 12:54:00.000 2019-02-08 15:00:00.000 4446
2 2019-05-02 10:41:00.000 2019-05-10 12:39:00.000 11638
3 2019-06-01 10:30:00.000 2019-06-04 09:25:00.000 4255
4 2019-02-02 09:41:00.000 2019-03-01 00:00:00.000 38299
4 2019-03-01 00:00:00.000 2019-04-01 00:00:00.000 44640
4 2019-04-01 00:00:00.000 2019-05-01 00:00:00.000 43200
4 2019-05-01 00:00:00.000 2019-05-20 11:54:00.000 28074
5 2019-03-29 19:09:00.000 2019-04-01 00:00:00.000 3171
5 2019-04-01 00:00:00.000 2019-04-02 10:41:00.000 2081
This can be achieved using recursive CTE as follows. This calculates multiple months between startdate and enddate.
Fiddle: http://sqlfiddle.com/#!18/26568/4
create table #temp(
BookingNum int,
StartDate datetime,
EndDate datetime,
Location varchar(25),
)
insert into #temp
values(1,'2019-02-05 12:54:00','2019-02-08 15:00:00','Area 1'),
(2,'2019-05-02 10:41:00','2019-05-10 12:39:00','Area 2'),
(3,'2019-06-01 10:30:00','2019-06-04 09:25:00','Area 3'),
(4,'2019-02-02 09:41:00','2019-05-20 11:54:00','Area 1'),
(5,'2019-03-29 19:09:00','2019-04-02 10:41:00','Area 3')
;WITH cte AS
(
SELECT BookingNum,
StartDate,
CASE
WHEN DATEPART(m, EndDate) > DATEPART(m, startdate)
THEN DATEADD(s, -1, DATEADD(mm, DATEDIFF(m, 0, startdate) + 1, 0))
ELSE EndDate
END AS EndDate,
Location,
DATEPART(m, EndDate) - DATEPART(m, startdate) AS MonthDiff
FROM #temp
UNION ALL
SELECT cte.BookingNum,
CASE
WHEN cte.MonthDiff > 0
THEN DATEADD(month, DATEDIFF(month, 0, DATEADD(month, 1, cte.StartDate)), 0)
ELSE cte.StartDate
END AS startDate,
CASE
WHEN cte.MonthDiff > 0 AND DATEADD(d, -1, DATEADD(m, DATEDIFF(m, 0, DATEADD(month, 1, cte.StartDate)) + 1, 0)) < t.EndDate
THEN DATEADD(d, -1, DATEADD(m, DATEDIFF(m, 0, DATEADD(month, 1, cte.StartDate)) + 1, 0))
ELSE t.EndDate
END AS EndDate,
cte.Location,
(cte.MonthDiff - 1) MonthDiff
FROM cte
INNER JOIN #temp t ON cte.BookingNum = t.BookingNum
WHERE cte.MonthDiff > 0
)
SELECT BookingNum,
StartDate,
EndDate,
Location,
DATEPART(m, startdate) AS month,
DATEDIFF(minute, startdate, enddate) AS minutes
FROM cte
ORDER BY 1;
drop table #temp
Result:
BookingNum StartDate EndDate Location month minutes
----------- ----------------------- ----------------------- ------------------------- ----------- -----------
1 2019-02-05 12:54:00.000 2019-02-08 15:00:00.000 Area 1 2 4446
2 2019-05-02 10:41:00.000 2019-05-10 12:39:00.000 Area 2 5 11638
3 2019-06-01 10:30:00.000 2019-06-04 09:25:00.000 Area 3 6 4255
4 2019-02-02 09:41:00.000 2019-02-28 23:59:59.000 Area 1 2 38298
4 2019-03-01 00:00:00.000 2019-03-31 00:00:00.000 Area 1 3 43200
4 2019-04-01 00:00:00.000 2019-04-30 00:00:00.000 Area 1 4 41760
4 2019-05-01 00:00:00.000 2019-05-20 11:54:00.000 Area 1 5 28074
5 2019-03-29 19:09:00.000 2019-03-31 23:59:59.000 Area 3 3 3170
5 2019-04-01 00:00:00.000 2019-04-02 10:41:00.000 Area 3 4 2081
To achieve this you will need to create an additional table to join to that contains the months. You would then join to that table where the month of the date is between the dates in the calendar table, to do this you need to use a dateadd/datediff function to round your date to the first of the month e.g.: DATEADD(month, DATEDIFF(month, 0, StartDate),0). This works by calculating the difference in months between some random start date (in this case 0, I.e. 1/1/1900) and then adding those months back on to the start date.
Then you will need to round your start or end date up or down to the end of the month if they are not in the same month as the calendar table record, which will allow you to do a new calculation for the time.
The whole code would look something like this:
CREATE TABLE #MonthDate
(MonthDate date PRIMARY KEY);
INSERT INTO #MonthDate (MonthDate)
VALUES ('20190101'),('20190201'),('20190301'),('20190401'),('20190501'),('20190601');
WITH RoundedDates As
(SELECT b.StartDate,
B.EndDate,
DATEADD(month, DATEDIFF(month, 0, b.StartDate),0) AS RoundedStartDate,
DATEADD(month, DATEDIFF(month, 0, b.EndDate),0) AS RoundedEndDate
FROM Test.dbo.Booking AS b)
SELECT rd.StartDate
, rd.EndDate
, DATEDIFF(minute, CASE WHEN rd.RoundedStartDate = md.MonthDate THEN rd.StartDate ELSE md.MonthDate END, CASE WHEN rd.RoundedEndDate = md.MonthDate THEN rd.EndDate ELSE DATEADD(month,1,md.MonthDate) END) AS Minutes
FROM RoundedDates AS rd
INNER JOIN #MonthDate as md
ON md.MonthDate BETWEEN rd.RoundedStartDate AND rd.RoundedEndDate
http://sqlfiddle.com/#!18/70730/2

single query to retrieve data in sql for different shift patterns

I have two working shifts: 8:00:00 to 16:30:00 and 20:00:00 to 06:00:00. I want to create a stored procedure that will retrieve data from an SQL table when I pass the date
This is my tables Table1
ID DateTime EmpID
-------------------------------------
47 2014-12-07 08:00:00 1111
47 2014-12-07 15:25:00 1235
47 2014-12-07 23:55:00 4569
47 2014-12-08 00:00:00 4563
47 2014-12-08 02:00:00 7412
59 2014-12-08 04:00:00 8523
59 2014-12-05 10:30:00 5632
Table Product
ID DateTime ProductMade
47 2014-12-07 11:00:00 Milk
47 2014-12-07 08:00:00 Juice
47 2014-12-08 00:00:00 Bread
47 2014-12-08 04:00:00 Cakes
47 2014-12-07 21:00:00 Juice
89 2014-12-01 04:00:00 bread
query for shift 2 18:00 to 06:00
select Count(EmpID) as ID,Count (ProductMade) ProductsTotal, Count(EmpID) * Count (ProductMade) as Total
from Table 1 as T1
inner join Table_Product as Prod on t1.ID = Prod.ID
where T1.DateTime BETWEEN DATEADD(DAY, DATEDIFF(DAY, 0, GETDATE()-2), 0) + '18:00' and DATEADD(DAY, DATEDIFF(DAY, 0, GETDATE()-1), 0) + '06:00' and DepartmentID=47
So this will get all the records that has the same ID matching
Then I have to do another query for the first shift.
between 08:00 to 16:30
select Count(EmpID) as ID,Count (ProductMade) ProductsTotal, Count(EmpID) * Count (ProductMade) as Total
from Table 1 as T1
inner join Table_Product as Prod on t1.ID = Prod.ID
where DATEDIFF(day, CONVERT(VARCHAR(10), GETDATE(),110), CONVERT(VARCHAR(10), T1.DateTime,110))=-2 and DATEPART(HOUR,T1.DateTime) BETWEEN '07' AND '16' and DepartmentID=47
OutPUT FirstShift 08 :00 to 16:30
ID ProductMade Total
2 2 4
OutPut Second Shift 20:00 to 06:00
ID ProductMade Total
3 3 9
so the second shift request a the startdate of -2 and the end date of -1 this is the part thats different from the day shift. cause i will pass the number of days previous i want to go backwards from a select box
This will product something in 1 go. I'm still not sure what output you are looking for. What does the DateTime in the Table_Product represent?
DECLARE #days int
SET #days = 4;
WITH Tab as (
Select case when DATEPART(hour, t.DateTime) between 8 and 16 then 'A' else 'B' end AS Shift, *
from Table1 t
where t.DateTime between DateAdd(hour, 8, Convert(datetime, Floor(Convert(float, Convert(datetime, GetDate())) - #days)))
and DateAdd(hour, 30, Convert(datetime, Floor(Convert(float, Convert(datetime, GetDate())) - #days)))
),
Prod as (
Select case when DATEPART(hour, t.DateTime) between 8 and 16 then 'A' else 'B' end AS Shift, *
from Table_Product t
where t.DateTime between DateAdd(hour, 8, Convert(datetime, Floor(Convert(float, Convert(datetime, GetDate())) - #days)))
and DateAdd(hour, 30, Convert(datetime, Floor(Convert(float, Convert(datetime, GetDate())) - #days))))
Select ID, ProductMake, Shift, Count(*), (Select Count(*) from Tab where ID = t1.ID and Shift = t1.Shift) Total from Prod t1
GROUP BY ID, Shift, ProductMake
Something like this?
where ((#shift = 1 and t1.datetime between ... ) or (#shift = 2 and datediff ... ))

Dense_rank and sum

I have this common table expression
WITH total_hour
AS (
SELECT
employee_id,
SUM(ROUND(CAST(DATEDIFF(MINUTE, start_time, finish_time) AS NUMERIC(18, 0)) / 60, 2)) AS total_h
FROM Timesheet t
WHERE t.employee_id = #employee_id
AND DENSE_RANK() OVER (
ORDER BY DATEDIFF(DAY, '20130925', date_worked) / 7 DESC ) = #rank
GROUP BY t.personnel_id
)
This is the sample data:
ID employee_id worked_date start_time finish_time
1 1 2013-09-25 09:00:00 17:30:00
2 1 2013-09-26 07:00:00 17:00:00
8 1 2013-10-01 09:00:00 17:00:00
9 1 2013-10-04 09:00:00 17:00:00
12 1 2013-10-07 09:00:00 17:00:00
13 1 2013-10-30 09:00:00 17:00:00
14 1 2013-10-28 09:00:00 17:00:00
15 1 2013-11-01 09:00:00 17:00:00
Supposed Wednesday is the first day of the week and my based date is 2013-09-25. I want to get the total number of hours worked from 09-25 to 10-01 when #rank is 1 and total hour from 10-02 to 10-08 when #rank=2 and so on.
Thanks
To get the number of hours worked for an employee within a particular week, just use a suitable WHERE criteria. No need to use DENSE_RANK or similar windowed functions for this.
Assuming you have a #Week parameter, that contains an integer (0 for current week, 1 for last week, 2 for week before that, etc.):
SELECT
employee_id
SUM(ROUND(CAST(DATEDIFF(MINUTE, start_time, finish_time) AS NUMERIC(18, 0)) / 60, 2)) AS total_h
FROM
Timesheet t
WHERE
t.employee_id = #employee_id AND
date_worked BETWEEN DATEADD(ww, DATEDIFF(ww,0,GETDATE()) - #Week, 0)
AND DATEADD(ww, DATEDIFF(ww,0,GETDATE()) - #Week, 0) + 7
Here, I've used the current date (GETDATE()) as the base date, but you could just replace it with 20130925, if that's what you need.