Get 24 hour average by time of day - sql

I have a simple table with DateTime "PostDate" and decimal "NumericValue".'
Each row has a timestamp with seconds.
If I do a standard 1440 minute average I get the average at midnight of each day. I would like to get the average for 24 hours at 7am between a date range. Like 1/1/2020 07:00:00 - 3/1/2020 07:00:00

You can offset the datetime by 7 hours, then aggregate:
select
cast(dateadd(hour, -7, postdate) as date) dy,
avg(numericvalue) avg_value
from mytable
group by cast(dateadd(hour, -7, postdate) as date)

Related

Using TIME_DIFF with multiple conditions in Google BigQuery

I am trying to calculate the worked hours for specific days in Google BigQuery (SQL).
The pay wage is $10 when you work on a day time but $15 when you work on a night time.
Day time is defined as 6am to 10pm whereas night time is defined as 10pm to 6am.
Employees can work flexibly as they are limousine drivers.
The following is an example of my table:
id
start_at
end_at
date
abc123
04:00:00
07:00:00
2020-01-05
abc123
09:00:00
15:32:00
2020-01-05
abc123
23:00:00
23:35:00
2020-01-05
abc123
23:40:00
23:59:00
2020-01-05
abc123
23:59:00
01:35:00
2020-01-05
abc123
02:02:00
04:35:00
2020-01-06
abc123
05:40:00
06:59:00
2020-01-06
So the actual work hours is calculated by taking the difference between start_at and end_at but the day time and night time conditions are becoming a hassle in my query..
*the date column is based on start_at. Even when you start at 11:59pm and end at the next day 12:05am, the date follows the date of the start_at instead of end_at.
Any ideas? Thanks in advance!
Consider below solution
create temp function night_day_split(start_at time, end_at time, date date) as (array(
select as struct
extract(date from time_point) day,
if(extract(hour from time_point) between 6 and 22, 'day', 'night') day_night,
count(1) minutes
from unnest(generate_timestamp_array(
timestamp(datetime(date, start_at)),
timestamp(datetime(if(start_at < end_at, date, date + 1), end_at)),
interval 1 minute
)) time_point
group by 1, 2
));
select id, day,
sum(if(day_night = 'day', minutes, null)) day_minutes,
sum(if(day_night = 'night', minutes, null)) night_minutes
from yourtable,
unnest(night_day_split(start_at, end_at, date)) v
group by id, day
if applied to sample data in your question - output is
You can try following code :-
with mytable as (
select 'abc123' id, cast( '04:00:00' as time) start_dt, cast( '07:00:00' as time) end_dt, date('2020-01-05' ) date union all
select 'abc123', cast( '09:00:00' as time), cast( '15:32:00' as time), date('2020-01-05') union all
select 'abc123', cast( '23:00:00' as time), cast( '23:35:00' as time), date('2020-01-05' ) union all
select 'abc123', cast('23:40:00' as time), cast( '23:59:00' as time), date('2020-01-05') union all
select 'abc123', cast ('23:59:00' as time), cast( '01:35:00' as time), date('2020-01-05') union all
select 'abc123', cast('02:02:00' as time), cast( '04:35:00' as time), date('2020-01-06') union all
select 'abc123', cast('05:40:00' as time), cast( '06:59:00' as time), date('2020-01-06')
)
select id, date, sum (value) as sal from(
select id, date,
case when start_dt > cast( '06:00:00' as time) and end_dt < cast( '22:00:00' as time) and start_dt < end_dt then (time_diff(end_dt, start_dt, Minute)/60) * 10
when start_dt < cast( '06:00:00' as time) and end_dt < cast( '06:00:00' as time) then (time_diff(end_dt, start_dt, Minute)/60) * 15
when start_dt < cast( '06:00:00' as time) and end_dt < cast( '22:00:00' as time) then (time_diff(cast( '06:00:00' as time), start_dt, Minute)/60) * 15 + (time_diff( end_dt,cast( '06:00:00' as time), Minute)/60) * 10
when start_dt > cast( '22:00:00' as time) and end_dt < cast( '06:00:00' as time) then (time_diff(cast( '23:59:00' as time), start_dt, Minute)/60) * 15 + (time_diff( end_dt,cast( '00:00:00' as time), Minute)/60) * 15
when start_dt > cast( '22:00:00' as time) and end_dt > cast( '22:00:00' as time) then (time_diff(end_dt, start_dt, Minute)/60) * 15
else 0
end as value
from mytable) group by id, date
Output :-
You can further group by on month for monthly salary.

Compute average of data between time period in Oracle sql

I have the following table named IMETERDATA:
DEVNAME VARCHAR2(25)
DEVID VARCHAR2(8)
USEDATE TIMESTAMP(6)
INSTANTPOWER NUMBER(3,0)
TOTALENERGY NUMBER(7,4)
ROWNUMBER NUMBER(4,0)
I want to compute and show average of Totalenergy on 2-hour interval. For instance, if I have a data for specific date (e.g. Nov 22, 2016), I want to calculate average of Totalenergy for period of: 12am-2am, 2am-4am .... 10pm-12pm. I want to calculate the average for all dates with specified interval. What I have done so far:
select to_number(to_char(USEDATE, 'HH24')) as "HOUR",
avg(TOTALENERGY) as "AVERAGE", TRUNC(USEDATE, 'DD') as "DATE"
from IMETERDATA
WHERE TRUNC(USEDATE) in (select DISTINCT(TRUNC(USEDATE, 'DD'))
from IMETERDATA) and (to_number(to_char(USEDATE, 'HH24')) >= 12 and to_number(to_char(USEDATE, 'HH24')) < 14 )
group by to_number(to_char(USEDATE, 'HH24')), TRUNC(USEDATE, 'DD');
This query gives only average from 12pm to 2pm.How can I calculate for 24 hours? I want result from 12am to 11.59pm with interval of 2 hours:
12AM - 2AM ---> 58.50
2AM - 4AM ----> 60.35
...
10PM - 11.59PM --> 40.35
Hmm. Can I do it without creating another table?
Sure. Use a query like below (this example generates 2-hour periods for 2 days):
ALTER SESSION SET NLS_DATE_FORMAT = 'yyyy-mm-dd hh24:mi'
;
SELECT date '2016-11-22' + NUMTODSINTERVAL( 2 * (level - 1), 'HOUR' ) as period_start
FROM dual
CONNECT BY LEVEL <= 2 * 12 ; -- 2 days of 12 "two hours" periods
PERIOD_START
----------------
2016-11-22 00:00
2016-11-22 02:00
2016-11-22 04:00
2016-11-22 06:00
2016-11-22 08:00
2016-11-22 10:00
2016-11-22 12:00
2016-11-22 14:00
2016-11-22 16:00
2016-11-22 18:00
2016-11-22 20:00
2016-11-22 22:00
2016-11-23 00:00
2016-11-23 02:00
2016-11-23 04:00
2016-11-23 06:00
2016-11-23 08:00
2016-11-23 10:00
2016-11-23 12:00
2016-11-23 14:00
2016-11-23 16:00
2016-11-23 18:00
2016-11-23 20:00
2016-11-23 22:00
24 rows selected
And then join a result of the above query to your table and calculate averages
SELECT x.PERIOD_START,
AVG( i.TOTALENERGY )
FROM (
the_above_query
) x
JOIN IMETERDATA i
ON i.USEDATE >= x.PERIOD_START AND i.USEDATE < x.PERIOD_START + interval '2' hour
GROUP BY x.PERIOD_START
Have a look at Analytic Functions: windowing_clause, there you can do it straight forward.
select USEDATE,
AVG(TOTALENERGY) OVER (ORDER BY USEDATE RANGE BETWEEN INTERVAL '1' HOUR PRECEDING AND INTERVAL '1' HOUR FOLLOWING) as AVERAGE,
MIN(USEDATE) OVER (ORDER BY USEDATE RANGE BETWEEN INTERVAL '1' HOUR PRECEDING AND INTERVAL '1' HOUR FOLLOWING) as INTERVAL_START,
MAXUSEDATE) OVER (ORDER BY USEDATE RANGE BETWEEN INTERVAL '1' HOUR PRECEDING AND INTERVAL '1' HOUR FOLLOWING) as INTERVAL_END
from IMETERDATA;
This query gives you all averages for each time +/- 1 hour (i.e. 2 hours).
If you need just the times of given hours you can use
with t as
(select USEDATE,
AVG(TOTALENERGY) OVER (ORDER BY USEDATE RANGE BETWEEN INTERVAL '1' HOUR PRECEDING AND INTERVAL '1' HOUR FOLLOWING) as AVERAGE,
MIN(USEDATE) OVER (ORDER BY USEDATE RANGE BETWEEN INTERVAL '1' HOUR PRECEDING AND INTERVAL '1' HOUR FOLLOWING) as INTERVAL_START,
MAXUSEDATE) OVER (ORDER BY USEDATE RANGE BETWEEN INTERVAL '1' HOUR PRECEDING AND INTERVAL '1' HOUR FOLLOWING) as INTERVAL_END
from IMETERDATA)
select *
from t
where USEDATE = TRUNC(USEDATE, 'HH')
AND EXTRACT(HOUR FROM USEDATE) IN (1,3,5,7,...);
Instead of EXTRACT(HOUR FROM USEDATE) IN (1,3,5,7,...) you could also use MOD(EXTRACT(HOUR FROM USEDATE), 1) = 1
Maybe this is not 100% what you are looking for (your question is not so clear in that regards) but I assume you get an idea how to use it.

MSSQL: How can I find records with a date field in a time range of 24 hours always starting from 6am to 6am next day

What i want to do is:
If i select a record from the date 2016-06-01 06:00:00 to 2016-06-02 05:59:59 it should display under 2016-06-01 and not under 02
...GROUP BY CAST((DATEADD(hour, -6, YourDate) AS DATE)
if you want to find records occurring in '2016-08-05' (according to your requirement you do
CAST((DATEADD(hour, -6, YourDate) AS DATE) = 2016-08-05'
note that in my method 06:00:00 then acts like 'midnight' in a regular day system - i.e. at the stroke of 6, it is a new day
Simply subtract six hours:
select dateadd(hour, -6, mydate)
from mytable
this is i want to do. The records after 00:00:00 to 06:00:00 should comes under '2016-06-01 06:00:00' if there is a records for the date from 2016-06-01 06:00:00 to 2016-06-02 06:00:00
CASE
WHEN DATEPART(HOUR, RechargeOn) < DATEADD(HOUR, 6, RechargeOn)
DATEADD(HOUR, 18, DATEADD(DAY, -1, RechargeOn))
RechargeOn
END

Calculate hourly average of values in a time interval

I'm not really a sql guy, so maybe what I'm trying to do is simple, but I can't find an easy solution.
I have a series of hourly data, between two dates. Something like this:
DATETIME VALUE
-------------------------
2014-01-01 01:00 104
2014-01-01 02:00 56
...
2014-01-04 23:00 65
2014-01-05 00:00 145
What I want is, for each hour, getting the average of the values of each day at that hour, so I end the query with something like this:
01:00 67.65
02:00 43.00
....
00:00 89.45
The "01:00" value will be the average of all the "01:00" values of each day, and so on.
The algorithm is easy, but my SQL skills are quite weak :-)
BONUS
It would be awesome if the answer would include a variation of the same problem: calculating the averages by weekdays and hour, and not only by hour:
Monday 01:00 34.23
Monday 02:00 54.34
...
Monday 23:00 241.34
Tuesday 00:00 89.43
....
Sunday 23:00 49.33
You can use datename , datepart and group by
select datename(weekday, [datetime]) as [Day],
datepart(hour, [datetime]) as [Hour],
avg(value) as AvgValue,
datepart(weekday, [datetime]) as [DayNo]
from table1
group by datename(weekday, [datetime]), datepart(weekday, [datetime]),
datepart(hour, [datetime])
order by datepart(weekday, [datetime]), datepart(hour, [datetime])
Below is an example of a general aggregate query you can use to group by a time interval.
WITH intervals AS (
SELECT DATEADD(hour, DATEDIFF(hour, '', DATETIME), '') AS TimeInterval
,VALUE
FROM dbo.Foo
)
SELECT
TimeInterval
, DATENAME(weekday, TimeInterval) AS Weekday
, CAST(TimeInterval AS time)
, AVG(VALUE) AS AvgValue
FROM intervals
GROUP BY TimeInterval
ORDER BY TimeInterval;

PostgreSQL time range duration over time series with default end if null

I need to be able to calculate the duration (in seconds) between two time stamps as an aggregate over a time series using a default end_datetime if it is null.
Imagine you have something like a punch card when you puch in and out:
username, start_datetime, end_datetime
What I want is a generated time series of the last N minutes with the duration for all users that overlap within that time frame. So it would be the SUM(end_datetime - start_datetime) where you would COALESCE a default end_datetime if it is null.
So the basic pieces I think I need are:
Generate the time interval:
select TIMESTAMP '2013-01-01 12:01:00' - (interval '1' minute * generate_series(0,5)) as timestamps;
COALESCE a default end_datetime
COALESCE(end_datetime, NOW())
Figure out the seconds difference between the start and end dates
So if one user logged in at 11:56:50 and it is now 12:01:40 we should get a table like:
timestamps duration
-------------------------------------
2013-01-01 12:01:00 40
2013-01-01 12:00:00 60
2013-01-01 11:59:00 60
2013-01-01 11:58:00 60
2013-01-01 11:57:00 60
2013-01-01 11:56:00 10
with t as (select '2013-01-01 11:56:50'::timestamp startt, '2013-01-01 12:01:40'::timestamp endt)
select
timestamps,
extract(epoch from
case
when timestamps=date_trunc('minute',startt) then date_trunc('minute',startt) + interval '1 minute' - startt
when timestamps =date_trunc('minute',endt) then endt- date_trunc('minute',endt)
else interval '60 seconds' end) as durations
from
(select generate_series(date_trunc('minute',startt),date_trunc('minute',endt),'1 minute') timestamps, * from t) a
order by
timestamps desc;
2013-01-01 12:01:00;40
2013-01-01 12:00:00;60
2013-01-01 11:59:00;60
2013-01-01 11:58:00;60
2013-01-01 11:57:00;60
2013-01-01 11:56:00;10
If you have multiple rows with start and end timestamp than the following will work:
select
id,
timestamps,
extract(epoch from
case
when timestamps=date_trunc('minute',startt) then date_trunc('minute',startt) + interval '1 minute' - startt
when timestamps =date_trunc('minute',endt) then endt- date_trunc('minute',endt)
else interval '60 seconds' end) as durations
from
(
select
id,
generate_series(date_trunc('minute',startt) ,
coalesce(date_trunc('minute',endt),date_trunc('minute',Now())),'1 minute') as timestamps,
startt, endt
from test
) a
order by
id, timestamps desc
SQLFiddle