single query to retrieve data in sql for different shift patterns - sql

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 ... ))

Related

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

Sum of data between 7AM to 7PM and 7PM to 7AM (next day)

I have following table:
Date Reading1 Reading2
2017-02-15 07:00:00.0000000 33 30
2017-02-15 07:15:00.0000000 32 31
2017-02-15 07:30:00.0000000 32 31
2017-02-15 07:45:00.0000000 33 30
2017-02-15 08:00:00.0000000 33 28
2017-02-15 08:15:00.0000000 32 29
2017-02-15 08:30:00.0000000 32 31
2017-02-15 08:45:00.0000000 34 31
2017-02-15 09:00:00.0000000 34 31
2017-02-15 09:15:00.0000000 34 30
2017-02-15 09:30:00.0000000 31 30
2017-02-15 09:45:00.0000000 32 32
........
2017-02-16 06:15:00.0000000 32 31
2017-02-16 06:30:00.0000000 35 32
2017-02-16 06:45:00.0000000 34 30
2017-02-16 07:00:00.0000000 34 31
I can sum the Reading1 and Reading2 column based on hour or date, but my problem is that I want to sum-up the column between 7AM to 7PM and then 7PM to 7AM of the next day. Any help will be highly appreciable.
'For sum on hour I am using following query'
--Sum on hour
select datepart(hour,Date), SUM(Reading1), SUM(Reading2)
from #LocalTempTable
group by
datepart(hour,Date),
dateadd(d, 0, datediff(d, 0,Date))
For any given day, I would approach this with a couple of BETWEEN subqueries. Something like:
declare #refDate datetime
declare #midPeriod datetime
declare #endPeriod datetime
set #refDate = '2017-02-15 07:00'
set #midPeriod = dateadd(hh, 12, #refDate)
set #endPeriod = dateadd(hh, 24, #refDate)
select #refDate PeriodStart,
(select sum(Reading1) from #LocalTempTable where Date between #refDate and #midPeriod) EarlyReading1,
(select sum(Reading1) from #LocalTempTable where Date between #midPeriod and #endPeriod) LateReading1,
(select sum(Reading2) from #LocalTempTable where Date between #refDate and #midPeriod) EarlyReading2,
(select sum(Reading2) from #LocalTempTable where Date between #midPeriod and #endPeriod) LateReading2
You can use a case statement to group on the hours to get the desired results:
select year(Date) as DateYear,
datepart(dy, Date) as DayOfYear,
case when datepart(hour,Date) >= 7 and datepart(hour, Date) < 19 then '7AM - 7PM' else '7PM - 7AM' end as HourGroup,
SUM(Reading1),
SUM(Reading2)
from #LocalTempTable
group by year(Date) as DateYear,
datepart(dy, Date) as DayOfYear,
case when datepart(hour,Date) >= 7 and datepart(hour, Date) < 19 then '7AM - 7PM' else '7PM - 7AM' end
Here's a full script with some sample data:
CREATE TABLE #Readings ([Date] DateTime, Reading1 int, Reading2 int)
INSERT INTO #Readings ([Date], Reading1, Reading2) VALUES ('2017-02-15 06:45:00', 1, 1)
INSERT INTO #Readings ([Date], Reading1, Reading2) VALUES ('2017-02-15 07:00:00', 2, 2)
INSERT INTO #Readings ([Date], Reading1, Reading2) VALUES ('2017-02-15 07:15:00', 3, 3)
INSERT INTO #Readings ([Date], Reading1, Reading2) VALUES ('2017-02-15 18:45:00', 4, 4)
INSERT INTO #Readings ([Date], Reading1, Reading2) VALUES ('2017-02-15 19:15:00', 5, 5)
INSERT INTO #Readings ([Date], Reading1, Reading2) VALUES ('2017-02-16 06:45:00', 6, 6)
INSERT INTO #Readings ([Date], Reading1, Reading2) VALUES ('2017-02-16 07:00:00', 7, 7)
SELECT
DATEADD(hh, (Half * 12) + 7, ModifiedDay) AS StartPeriod,
SUM(Reading1) AS SumOfReading1,
SUM(Reading2) AS SumOfReading2
FROM (
SELECT
[Date],
CAST(CAST(ModifiedDate AS DATE) AS DATETIME) AS [ModifiedDay],
DATEPART(hh, ModifiedDate) AS [DatePart],
CASE WHEN DATEPART(hh, ModifiedDate) < 12 THEN 0 ELSE 1 END AS Half,
Reading1,
Reading2
FROM (
SELECT
[Date],
DATEADD(hh, -7, [Date]) AS [ModifiedDate],
Reading1,
Reading2
FROM #Readings
) t
) t
GROUP BY DATEADD(hh, (Half * 12) + 7, ModifiedDay), Half
ORDER BY 1, 2
DROP TABLE #Readings
StartPeriod SumOfReading1 SumOfReading2
2017-02-14 19:00:00.000 1 1
2017-02-15 07:00:00.000 9 9
2017-02-15 19:00:00.000 11 11
2017-02-16 07:00:00.000 7 7
Without using a calendar table or cte:
test setup: http://rextester.com/ZENTG4450
select
FromDate = convert(varchar(10)
,min(dateadd(day,(datediff(hour,0,date)-7)/24,0))
,120)
, ThruDate = convert(varchar(10)
,max(dateadd(day,(datediff(hour,0,date)+5)/24,0))
,120)
, Hours = case ((datediff(hour,0,date)+5)/12)%2
when 1
then '7 AM to 7 PM'
else '7 PM to 7 AM'
end
, SumReading1=sum(Reading1)
, SumReading2=sum(Reading2)
from t
group by (datediff(hour,0,date)+5)/12
returns:
+------------+------------+--------------+-------------+-------------+
| FromDate | ThruDate | Hours | SumReading1 | SumReading2 |
+------------+------------+--------------+-------------+-------------+
| 2017-02-15 | 2017-02-15 | 7 AM to 7 PM | 392 | 364 |
| 2017-02-15 | 2017-02-16 | 7 PM to 7 AM | 101 | 93 |
| 2017-02-16 | 2017-02-16 | 7 AM to 7 PM | 34 | 31 |
+------------+------------+--------------+-------------+-------------+
using a calendar table or cte:
test setup: http://rextester.com/QOC88855
declare #fromdate date = '20170201'
declare #thrudate date = '20170228'
;with n as (select n from (values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(n))
, dates as (
select top ((datediff(day, #fromdate, #thrudate)+1)*2)
[FromDate]=dateadd(hour,7+12*((row_number() over (order by (select 1)) -1)%2)
,convert(datetime2(2)
,dateadd(day, (row_number() over (order by (select 1)) -1)/2, #fromdate))
)
, [ThruDate]=dateadd(hour,19+12*((row_number() over (order by (select 1)) -1)%2)
,convert(datetime2(2)
,dateadd(day, (row_number() over (order by (select 1)) -1)/2, #fromdate))
)
from n as deka
cross join n as hecto /* 100 days */
--cross join n as kilo /* 2.73 years */
--cross join n as [tenK] /* 27.3 years */
order by 1
)
select
FromDate=convert(varchar(20),FromDate,120)
, ThruDate=convert(varchar(20),ThruDate,120)
, SumReading1=sum(Reading1)
, SumReading2=sum(Reading2)
from dates d
inner join t
on t.date >= d.fromdate
and t.date < d.thrudate
group by d.FromDate, d.ThruDate
order by d.FromDate, d.ThruDate
returns:
+---------------------+---------------------+-------------+-------------+
| FromDate | ThruDate | SumReading1 | SumReading2 |
+---------------------+---------------------+-------------+-------------+
| 2017-02-15 07:00:00 | 2017-02-15 19:00:00 | 392 | 364 |
| 2017-02-15 19:00:00 | 2017-02-16 07:00:00 | 101 | 93 |
| 2017-02-16 07:00:00 | 2017-02-16 19:00:00 | 34 | 31 |
+---------------------+---------------------+-------------+-------------+
Assuming your [Date] column is a DATETIME column, you can do this:(Basically what it does is to group the time range from 7AM-7PM as one and 7PM-7AM as another.
select FORMAT(dateadd(hour,-7,[date]), 'yyyy-MM-dd') + case when DATEPART(hour,dateadd(hour,-7,[date])) between 0 and 11 then ' 7AM-7PM' ELSE ' 7PM-7AM' END as [TimeRange], SUM(Reading1), SUM(Reading2)
from #LocalTempTable
group by FORMAT(dateadd(hour,-7,[date]), 'yyyy-MM-dd') + case when DATEPART(hour,dateadd(hour,-7,[date])) between 0 and 11 then ' 7AM-7PM' ELSE ' 7PM-7AM' END
Assuming:
We need to SUM data for each day (not calculate total sum for all days)
We consider accuracy to minute, so 7AM = 420 minutes (from 0:00 AM) and 7PM = 1140 minutes
We split day to 2 group: group 1 > 7AM today and < 7PM today, group 2 >= 7PM today and <= 7AM tomorrow (E.G 20170228 will have 2 group:
20170228_1 and 20170228_2)
Then you could use this:
SELECT
CASE WHEN DATEPART(hh, date)*60 + DATEPART(mi, date) <= 420
THEN CONVERT(char(8), date - 1, 112) + '_2'
WHEN DATEPART(hh, date)*60 + DATEPART(mi, date) >= 1140
THEN CONVERT(char(8), date, 112) + '_2'
ELSE CONVERT(char(8), date, 112) + '_1'
END AS date_group,
SUM(reading1),
SUM(reading2)
FROM table_name
GROUP BY
CASE WHEN DATEPART(hh, date)*60 + DATEPART(mi, date) <= 420
THEN CONVERT(char(8), date - 1, 112) + '_2'
WHEN DATEPART(hh, date)*60 + DATEPART(mi, date) >= 1140
THEN CONVERT(char(8), date, 112) + '_2'
ELSE CONVERT(char(8), date, 112) + '_1'
END;

Number of rows per hour starting at a specific time

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)

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.