Averaging event start time from DateTime column - sql

I'm calculating average start times from events that run late at night and may not start until the next morning.
2018-01-09 00:01:38.000
2018-01-09 23:43:22.000
currently all I can produce is an average of 11:52:30.0000000
I would like the result to be ~ 23:52
the times averaged will not remain static as this event runs daily and I will have new data daily. I will likely take the most recent 10 records and average them.

Would be nice to have SQL you're running, but probably you just need to format properly your output, it should be something like this:
FORMAT(cast(<your column> as time), N'hh\:mm(24h)')

The following will both compute the average across the datetime field and also return the result as a 24hr time notation only.
SELECT CAST(CAST(AVG(CAST(<YourDateTimeField_Here> AS FLOAT)) AS DATETIME) AS TIME) [AvgTime] FROM <YourTableContaining_DateTime>

The following will calculate the average time of day, regardless of what day that is.
--SAMPLE DATA
create table #tmp_sec_dif
(
sample_date_time datetime
)
insert into #tmp_sec_dif
values ('2018-01-09 00:01:38.000')
, ('2018-01-09 23:43:22.000')
--ANSWER
declare #avg_sec_dif int
set #avg_sec_dif =
(select avg(a.sec_dif) as avg_sec_dif
from (
--put the value in terms of seconds away from 00:00:00
--where 23:59:00 would be -60 and 00:01:00 would be 60
select iif(
datepart(hh, sample_date_time) < 12 --is it morning?
, datediff(s, '00:00:00', cast(sample_date_time as time)) --if morning
, datediff(s, '00:00:00', cast(sample_date_time as time)) - 86400 --if evening
) as sec_dif
from #tmp_sec_dif
) as a
)
select cast(dateadd(s, #avg_sec_dif, '00:00:00') as time) as avg_time_of_day
The output would be an answer of 23:52:30.0000000

This code allows you to define a date division point. e.g. 18 identifies 6pm. The time calculation would then be based on seconds after 6pm.
-- Defines the hour of the day when a new day starts
DECLARE #DayDivision INT = 18
IF OBJECT_ID(N'tempdb..#StartTimes') IS NOT NULL DROP TABLE #StartTimes
CREATE TABLE #StartTimes(
start DATETIME NOT NULL
)
INSERT INTO #StartTimes
VALUES
('2018-01-09 00:01:38.000')
,('2018-01-09 23:43:22.000')
SELECT
-- 3. Add the number of seconds to a day starting at the
-- day division hour, then extract the time portion
CAST(DATEADD(SECOND,
-- 2. Average number of seconds
AVG(
-- 1. Get the number of seconds from the day division point (#DayDivision)
DATEDIFF(SECOND,
CASE WHEN DATEPART(HOUR,start) < #DayDivision THEN
SMALLDATETIMEFROMPARTS(YEAR(DATEADD(DAY,-1,start)),MONTH(DATEADD(DAY,-1,start)),DAY(DATEADD(DAY,-1,start)),#DayDivision,0)
ELSE
SMALLDATETIMEFROMPARTS(YEAR(start),MONTH(start),DAY(start),#DayDivision,0)
END
,start)
)
,'01 jan 1900 ' + CAST(#DayDivision AS VARCHAR(2)) + ':00') AS TIME) AS AverageStartTime
FROM #StartTimes

Related

Listing the hours between two timestamps and grouping by those hours

I am trying to ascertain a count of the couriers that are active every hour of a shift using the the start and end times of their shifts to create an array which I hope to group by. Firstly, when I run it I'm given epoch times back, secondly, I am not able to group by the hours array.
Does anyone have any solutions that they would kindly share with me?
**
SELECT
GENERATE_TIMESTAMP_ARRAY(CAST(fss.start_time_local AS TIMESTAMP), CAST(fss.end_time_local AS TIMESTAMP) , INTERVAL 1 hour) as hours,
#COUNT(sys_scheduled_shift_id) AS number_schedule_shift,
FROM just-data-warehouse.delco_analytics_team_dwh.fact_scheduled_shifts AS fss
#GROUP BY hours
**
For your reference the shift data for the courier is structured like so
To calculate how many couriers have been active at least one minute in every hour I would do it like this:
SELECT
CALENDAR.datetime
,SUM(workers.flag_worker) as n_workers
FROM (
-- CALENDAR
SELECT
cast(datetime as datetime) datetime
FROM UNNEST(GENERATE_TIMESTAMP_ARRAY('2022-01-01T00:00:00', '2022-01-02T00:00:00'
,INTERVAL 1 hour)) AS datetime
) CALENDAR
-- TABLE of SHIFTS
LEFT JOIN (
SELECT * , 1 flag_worker FROM
UNNEST(
ARRAY<STRUCT<worker_id string , shift_start datetime, shift_end datetime>>[
('Worker_01', '2022-01-01T06:00:00','2022-01-01T14:00:00')
,('Worker_02', '2022-01-01T10:00:00','2022-01-01T18:00:00')
]
)
AS workers
)workers
ON CALENDAR.datetime < workers.shift_end
AND DATETIME_ADD(CALENDAR.datetime, INTERVAL 1 hour) > workers.shift_start
GROUP BY CALENDAR.datetime
The idea is to build a calendar of datetimes and then join it with a table of shifts.
Instead of hours, the calendar can be modified to have fractions of hours. Also, there may be a more elegant way to build the calendar.

SQL Server - check if store is open

I have a table as below
StoreName | TimeOpens | DurationInMinutes | DayOfTheWeek
Dog Store '00:08:00.000' 400 1
Duck Store '00:08:00.000' 1300 1
Cat Store '00:08:00.000' 1440 1
I need to select all stores that are currently open. Times are stored in UTC so GETUTCDATE() can be used to get current time. The TimeOpens is a Time object and not a DATETIME, which is why I am a little confused how to write a query that will get me the correct answer. The scenarios are what if the time overlaps over midnight because the duration is that long as in case of Duck Store or what if the duration is 1440 minutes long meaning that its open 24 hours a day.
I thought about converting it to a DateTime first which would make things easier but then I'm not sure do I get today's date or yesterdays when calculating this. For example if I get today's date and its past midnight then I'm before the store opened which would mean its closed. I have the same problem with choosing the day of the week as if its past midnight its technically next day but it could still be open from previous day.
SELECT *
FROM Stores
WHERE GETUTCDATE() > CAST(LEFT(CONVERT(DATE, GETUTCDATE()), 10) + ' ' + LEFT(CONVERT(VARCHAR, S.[TimeOpens], 120), 12) AS DATETIME)
Edit, the query should somehow include dayoftheweek as well because each day could have a different schedule. its a 1-7 index.
You can do:
where (cast(getutcdate() as time) >= timeopens and
datediff(minute, cast(getutcdate() as time), timeopens) <= duration
) or
(cast(utcdate() as time) < dateadd(minute, duration, timeopens) and
day(dateadd(minute, duration, cast(timeopens as datetime))) > 0
)
you can use this condition to get if its still a valid opening hours.
SELECT *
FROM Stores
WHERE
datediff(mi,
dateadd(mi, DurationInMinutes,cast(concat(convert(DATE, getutcdate()), ' ', TimeOpens) as datetime))
, getutcdate()) < 0

SQL splitting time when over midnight

I am after some guidance on the best way to get useful information out of our MIS database
Scenario:- I want to check staff utilisation by a variable period that I can drill down into. This needs to then be split into days so I can assess over a 24 hour period what was done
The table is huge and has loads of columns we need to calculate, so ideally I need to split the records that span 2 days into 2
The table has a datetimeformat field that has user [starttime], it then has a separate field that has [duration] which is in decimal hours.
So an example would be:
ID StartTime Duration Qty username
1 2016-11-24 23:00:00 2.00 1000 Joe Bloggs
In the example above Joe starts at 11pm and works till 1 am, so what I need is to somehow split this record in my query to put anything before midnight as 1 record and anything after into another This example is pretty simple as it is half/half but some might start at 10pm and finish at 6pm so I would need 2 hours and 6 hours.
Not sure on the best way to do this, my initial thoughts was to create a cte where a start time is in 1 day and if the starttime + duration was in the next day then split the record.
Not sure if there is an easier way or if anyone has had to do this before.
Any help appreciated
#Joe has the right idea, here is pseudo-SQL
SELECT ID,StartTime,Duration,Qty,username
WHERE TRUNCATE(StartTime,DAY) = TRUNCATE(StartTime + Duration hours ,DAY)
UNION
SELECT ID,StartTime, TRUNCATE(StartTime,DAY) + 1 days - StartTime hours ,Qty,username
WHERE TRUNCATE(StartTime,DAY) < TRUNCATE(StartTime+Duration hours,DAY)
UNION
SELECT ID,TRUNCATE(StartTime+Duration hour,DAY),StartTime + duration hours - DATE(StartTime+Duration),Qty,username
WHERE TRUNCATE(StartTime,DAY) < TRUNCATE(StartTime+Duration hours,DAY)
Where TRUNCATE(timestamp,DAY) truncates a timestamp to YYYY-MM-DD 00:00:00
You can multiply rows with join. Make Tally table, simple table with numbers 1, 2, 3... and do a join. I will use table starting at zero here:
CREATE TABLE Tally0 (Number INT IDENTITY(0,1) PRIMARY KEY NOT NULL);
GO
INSERT INTO Tally0 DEFAULT VALUES;
GO 10000
Now the harders part is conversion between dates and numerics:
;WITH
tmp1 AS (SELECT *,
DATEDIFF(SECOND, CONVERT(DATE, StartTime), StartTime)/3600.0
+ DATEPART(NANOSECOND, StartTime)/(3600*1000000000.0) AS startingHours
FROM Record),
tmp2 AS (SELECT *,
startingHours + Duration AS endingHours,
(startingHours + Duration)/24.0 AS endingDays
FROM tmp1)
SELECT *,
CASE WHEN Number = 0 THEN StartTime
ELSE DATEADD(DAY, Number, CONVERT(DATE, StartTime))
END AS StartTime2,
CASE WHEN Number = 0 AND 1 < endingDays THEN 24 - startingHours
WHEN Number = 0 THEN Duration
WHEN Number + 1 < endingDays THEN 24
ELSE endingHours - Number * 24
END AS Duration2
FROM tmp2
JOIN Tally0 ON Number < endingDays

Reduce/Summarize and Replace Timestamped Records

I have a SQL table that has timestamped records for server performance data. This data is polled and stored every 1 minute for multiple servers. I want to keep data for a large period of time but reduce the number records for data older than six months.
For example, I have some old records like so:
Timestamp Server CPU App1 App2
1 ... 00:01 Host1 5 1 10
2 ... 00:01 Host2 10 5 20
3 ... 00:02 Host1 6 0 11
4 ... 00:02 Host2 11 5 20
5 ... 00:03 Host1 4 1 9
6 ... 00:04 Host2 9 6 19
I want to be able to reduce this data from every minute to every 10 minutes or possibly every hour for older data.
My initial assumption is that I'd average the values for times within a 10 minute time period and create a new timestamped record after deleting the old records. Could I create a sql query that generates the insert statements for the new summarized records? What would that query look like?
Or is there a better way to accomplish this summarization job?
You might also want to consider moving the summarized information into a different table so you don't end up in a situation where you're wondering if you're looking at "raw" or summarized data. Other benefits would be that you could include MAX, MIN, STDDEV and other values along with the AVG.
The tricky part is chunking out the times. The best way I could think of was to start with the output from the CONVERT(blah, Timestamp, 120) function:
-- Result: 2015-07-08 20:50:55
SELECT CONVERT(VARCHAR(19), CURRENT_TIMESTAMP, 120)
By cutting it off after the hour or after the 10-minute point you can truncate the times:
-- Hour; result is 2015-07-08 20
SELECT CONVERT(VARCHAR(13), CURRENT_TIMESTAMP, 120)
-- 10-minute point; result is 2015-07-08 20:50:5
SELECT CONVERT(VARCHAR(15), CURRENT_TIMESTAMP, 120)
With a little more massaging you can fill out the minutes for either one and CAST it back to a DATETIME or DATETIME2:
-- Hour increment
CAST(CONVERT(VARCHAR(13), CURRENT_TIMESTAMP, 120) + ':00' AS DATETIME)
-- 10-minute increment
CAST(CONVERT(VARCHAR(15), CURRENT_TIMESTAMP, 120) + 0' AS DATETIME)
Using the logic above, all times are truncated. In other words, the hour formula will convert Timestamp where 11:00 <= Timestamp < 12:00 to 11:00. The minute formula will convert Timestamp where 11:20 <= Timestamp < 11:30 to 11:20.
So the better part query looks like this (I've left out getting rid of the rows you've just summarized):
-- The hour-increment version
INSERT INTO myTableOrOtherTable
SELECT
CAST(CONVERT(VARCHAR(13), [Timestamp], 120) + ':00' AS DATETIME),
AVG(CPU),
AVG(App1),
AVG(App2)
FROM myTable
GROUP BY
CAST(CONVERT(VARCHAR(13), [Timestamp], 120) + ':00' AS DATETIME)
Assuming you have record for every minute, this is how you can group your records by 10 minutes:
SELECT
[Timestamp] = MIN([Timestamp]),
[Server],
CPU = AVG(CPU),
App1 = AVG(App1),
App2 = AVG(App2)
FROM (
SELECT *,
RN = (ROW_NUMBER() OVER(PARTITION BY [Server] ORDER BY [Timestamp]) - 1) / 10
FROM temp
)t
GROUP BY [Server], RN

Need to write SQL Server stored procedure to save one or more rows based on time calculation

I need to write a function that rounds time in one column, called StartTime, to display just the hour and insert it to another column called StartHour. In another column, called EndTime, I also need to use DATEPART to round up to the next incremented hour (ex: 23:33:00.0000000 would be 23 (the hour) + 1 (to round it up to the end hour) = 24 and I would need to save that to the EndHour column).
I want to insert these new values into new columns called StartHour and StartMinute, EndHour, and EndMinute (the StartTime and EndTime columns are the original values I'm working with in time(7) format and for historical purposes, I'm keeping them in their original columns).
Here's what I have so far in T-SQL:
SELECT (DATEPART(HOUR, [StartTime])) AS StartHour,(DATEPART(MINUTE, [StartTime])) AS StartMinute,
(DATEPART(HOUR, [EndTime])) AS EndHour,
(DATEPART(MINUTE, [EndTime])) AS EndMinute, StartTime, EndTime
FROM [test].[dbo].[Outage_Reports]
ORDER BY OutageDate ASC
Which produces:
StartHour StartMinute EndHour EndMinute Startime EndTime
16 0 17 30 16:00:00 17:30:00
I now need to write this conversion into a stored procedure that also inserts two (or more if the outage goes on for several hours) new rows into the table when the length between start and end is more than one hour. Or just inserts one row when the outage was below or equal to an hour. And then I need to account for that hourly progression in the start and end columns. Like this:
StartHour StartMinute EndHour EndMinute Startime EndTime
16 0 17 00 16:00:00 17:30:00
(the above reflects the first hour of outage, the second row below reflects the second half hour of the outage until it stopped...both will be tied to the same outage ticket in the table)
StartHour StartMinute EndHour EndMinute Startime EndTime
17 30 18 00 16:00:00 17:30:00
The idea is to track website outages hour by hour so they can join to an orders table that tracks orders by hour (and the orders never contain minutes...just hours). So the plan is to make two rows for an outage that goes on for 1.5 hours so the 16, 17 and all the way through the rounded up 18 hourly values can be tied to the orders table with the 30 minute column to act as another point of calculation (so an hour and a half would equal a full hour's worth of orders plus a half hour's worth of orders...split out row by row). This way I can better track trends during outages.
I'm a bit at a loss as to how to write this logic to a stored procedure. I would conceivably have to declare the new hour and minute values into variables and for the StartHour column always keep it rounded to the DATEPART hour value (even if it was 16:45...I want to insert 16 into the StartHour column). However, with the EndHour column, I want to insert a rounded up hourly value (17 and then 18 because we went over an hour in that case) into the EndHour column. This would hopefully describe (via these two rows) the close to two hours the outage occurred. And in turn it would allow for an easy join to our orders table
Any guidance on a stored procedure for this logic would be appreciated.
well, i didn't really understand the purpose!!
but the logic may be something like as below---
declare #StartTime time
declare #EndTime time
declare #Temp_StartTime time
declare #temp_StartHour int
declare #temp_EndHour int
declare #temp_StartMinute int
declare #temp_EndMinute int
SET #StartTime='2:30:00'
SET #EndTime='4:01:00'
SET #Temp_StartTime=#StartTime
SET #temp_StartHour=DATEPART(HOUR, #StartTime)
SET #temp_EndHour=DATEPART(HOUR, #EndTime)
SET #temp_StartMinute=DATEPART(MI, #StartTime)
SET #temp_EndMinute=DATEPART(MI, #EndTime)
if(#temp_EndMinute>0)
BEGIN
SET #temp_EndHour=#temp_EndHour+1
END
DECLARE #Temp_Table TABLE
(
StartHour int,
StartMinute int,
EndHour int,
EndMinute int,
StartTime time,
EndTime time
)
WHile((#temp_EndHour-#temp_StartHour>=1))
BEGIN
INSERT INTO #Temp_Table
SELECT (DATEPART(HOUR, #Temp_StartTime)) AS StartHour,(DATEPART(MINUTE, #Temp_StartTime)) AS StartMinute,
#temp_StartHour+1 AS EndHour,
0 AS EndMinute, #StartTime as StartTime, #EndTime as EndTime
SET #temp_StartHour=#temp_StartHour+1
SET #Temp_StartTime=DATEADD(HOUR,1,#Temp_StartTime)
if(DATEPART(MI, #Temp_StartTime)!=0)
BEGIN
SET #Temp_StartTime=DATEADD(MI,-#temp_StartMinute,#Temp_StartTime)
END
END
SELECT * FROM #Temp_Table
hope it'll help.