Calculate Daily Outage in Postgresql - sql

I have for example outage Start DateTime: '2017-09-09 06:56:22' and End DateTime: '2017-09-13 14:22:45'.
Now I want to get the outage duration Daily, if the whole there was outage then give me '24:00:00' but if there was outage for just part of the day then we do subtraction ex: startTime - DayEndTime(StartTime) that is 00:00:00 the end of that start day.
So each day will be calculated for if there is outage the whole day then its 24:00:00.
How do i solve this in postgresql? Please help
see table below
StartTime EndTime OutageTime
2017-09-09 6:56:32 2017-09-10 0:00:00 17:03:28
2017-09-10 0:00:00 2017-09-11 0:00:00 24:00:00
2017-09-11 0:00:00 2017-09-12 0:00:00 24:00:00
2017-09-12 0:00:00 2017-09-13 0:00:00 24:00:00
2017-09-13 0:00:00 2017-09-13 14:22:45 14:22:45

You can just calculate the difference and replace it with 24:00:00 whenever it is a whole day:
SELECT starttime,
endtime,
CASE endtime - starttime
WHEN INTERVAL '1 day'
THEN '24:00:00'
WHEN INTERVAL '1 day 1 hour'
THEN '25:00:00'
ELSE CAST(endtime - starttime AS text)
END AS outagetime
FROM outages;

with t (startTime, endTime) as (values
('2017-09-09 6:56:32'::timestamptz,'2017-09-10 0:00:00'::timestamptz),
('2017-09-10 0:00:00','2017-09-11 0:00:00'),
('2017-09-11 0:00:00','2017-09-12 0:00:00'),
('2017-09-12 0:00:00','2017-09-13 0:00:00'),
('2017-09-13 0:00:00','2017-09-13 14:22:45'))
select startTime, endTime, upper(i) - lower(i) as a
from
(
select tstzrange(startTime, endTime) as tstzr, startTime, endTime
from t
) t
inner join (
select tstzrange(d, d + interval '1 day') as d
from
generate_series (
(select min(startTime)::date from t),
(select max(endTime) from t),
'1 day'
) gs (d)
) gs on d && tstzr
cross join lateral (
select tstzr * d as i
) cjl
;
starttime | endtime | a
------------------------+------------------------+----------
2017-09-09 06:56:32-03 | 2017-09-10 00:00:00-03 | 17:03:28
2017-09-10 00:00:00-03 | 2017-09-11 00:00:00-03 | 1 day
2017-09-11 00:00:00-03 | 2017-09-12 00:00:00-03 | 1 day
2017-09-12 00:00:00-03 | 2017-09-13 00:00:00-03 | 1 day
2017-09-13 00:00:00-03 | 2017-09-13 14:22:45-03 | 14:22:45

You can do this using generate_series(). The following is the logic for getting the results each day:
select (case when date_trunc('day', g.dte) = date_trunc('day', o.start_time)
then g.dte
else date_trunc('day', g.dte)
end),
(case when date_trunc('day', g.dte) < date_trunc('day', o.end_time)
then date_trunc('day', g.dte) + interval '1 day'
else o.end_time
end)
from outages o, lateral
generate_series(start_time, end_time, interval '1 day') g(dte)
where g.dte < o.end_time;
You can use a subquery to get the span on each day.

Related

How to fill the time gap after grouping date record for months in postgres

I have table records as -
date n_count
2020-02-19 00:00:00 4
2020-07-14 00:00:00 1
2020-07-17 00:00:00 1
2020-07-30 00:00:00 2
2020-08-03 00:00:00 1
2020-08-04 00:00:00 2
2020-08-25 00:00:00 2
2020-09-23 00:00:00 2
2020-09-30 00:00:00 3
2020-10-01 00:00:00 11
2020-10-05 00:00:00 12
2020-10-19 00:00:00 1
2020-10-20 00:00:00 1
2020-10-22 00:00:00 1
2020-11-02 00:00:00 376
2020-11-04 00:00:00 72
2020-11-11 00:00:00 1
I want to be grouped all the records into months for finding month total count which is working, but there is a missing of month. how to fill this gap.
time month_count
"2020-02-01" 4
"2020-07-01" 4
"2020-08-01" 5
"2020-09-01" 5
"2020-10-01" 26
"2020-11-01" 449
This is what I have tried.
SELECT (date_trunc('month', date))::date AS time,
sum(n_count) as month_count
FROM table1
group by time
order by time asc
You can use generate_series() to generate all starts of months between the earliest and latest date available in the table, then bring the table with a left join:
select d.dt, coalesce(sum(t.n_count), 0) as month_count
from (
select generate_series(date_trunc('month', min(date)), date_trunc('month', max(date)), '1 month') as dt
from table1
) as d(dt)
left join table1 t on t.date >= d.dt and t.date < d.dt + interval '1 month'
group by d.dt
order by d.dt
I would simply UNION a date series, generated from MIN and MAX date:
demo:db<>fiddle
WITH cte AS ( -- 1
SELECT
*,
date_trunc('month', date)::date AS time
FROM
t
)
SELECT
time,
SUM(n_count) as month_count --3
FROM (
SELECT
time,
n_count
FROM cte
UNION
SELECT -- 2
generate_series(
(SELECT MIN(time) FROM cte),
(SELECT MAX(time) FROM cte),
interval '1 month'
)::date,
0
) s
GROUP BY time
ORDER BY time
Use CTE to calculate date_trunc only once. Could be left out if you like to call your table twice in the UNION below
Generate monthly date series from MIN to MAX date containing your n_count value = 0. Add it to the table
Do your calculation

Alternative to this query to run under MariaDb 10.1

This query works as expected under Mysql 8, but MariaDB 10.1 is used on my server. Do you know if an alternative exists to this ? And how to achieve it ?
SELECT * FROM (
SELECT
*,
SEC_TO_TIME(SUM(TIME_TO_SEC(TIMEDIFF(hs.`ending_hour`, hs.`starting_hour`))) OVER (ORDER BY hs.starting_hour RANGE BETWEEN INTERVAL '12' HOUR PRECEDING AND INTERVAL '12' HOUR following)) AS tot
FROM
time_table hs
WHERE hs.`starting_hour` > DATE_SUB(NOW(), INTERVAL 50 DAY) AND hs.`ending_hour` <= NOW()
ORDER BY hs.`starting_hour` ASC
) t1
HAVING tot >= '14:00:00'
;
fiddle
The problem is RANGE BETWEEN INTERVAL on OVER window function doesn't exists under MariaDB at this moment.
Thank you
Sample data:
id starting_hour ending_hour
------ ------------------- ---------------------
1 2018-09-02 06:00:00 2018-09-02 08:30:00
2 2018-09-02 08:30:00 2018-09-02 10:00:00
4 2018-09-03 11:00:00 2018-09-03 15:00:00
5 2018-09-04 15:30:00 2018-09-04 16:00:00
6 2018-09-04 16:15:00 2018-09-04 17:00:00
7 2018-09-19 00:00:00 2018-09-19 03:00:00
8 2018-09-19 04:00:00 2018-09-19 15:00:00
9 2018-09-20 00:00:00 2018-09-20 22:01:00
10 2018-10-21 12:00:00 2018-10-21 11:00:00
11 2018-10-29 09:09:00 2018-10-29 10:10:00
12 2018-10-09 02:10:00 2018-10-09 14:00:00
In my use case id 7, 8 and 9 are the results.
RE-EDIT
Thanks to #Gordon Linoff answer's, this is the corrected query.
But finally doesn't work as expected. Increasing INTERVAL 50 DAY return non wanted rows that MySQL window function doesn't.
SELECT hs.*,
(
SELECT SEC_TO_TIME(SUM(TIME_TO_SEC(TIMEDIFF(hs2.ending_hour, hs2.starting_hour))))
FROM hours_sailor hs2
WHERE hs2.starting_hour >= DATE_SUB(hs.starting_hour, INTERVAL 12 HOUR) AND hs2.starting_hour <= DATE_SUB(NOW(), INTERVAL 12 HOUR)
) AS duration
FROM `time_table` hs
WHERE hs.`starting_hour` > DATE_SUB(NOW(), INTERVAL 50 DAY) AND hs.`ending_hour` <= NOW()
HAVING duration >= '14:00:00'
ORDER BY hs.starting_hour ASC;
You can express this using a correlated subquery. I think this is the equivalent logic:
SELECT hs.*,
(SELECT SEC_TO_TIME(SUM(TIME_TO_SEC(TIMEDIFF(hs2.ending_hour, hs2.starting_hour)))
FROM time_table hs2
WHERE hs2.starting_hour >= hs.starting_hour - INTERVAL '12' HOUR AND
hs2.starting_hour <= hs.starting_hour + INTERVAL '12' HOUR
) AS tot
FROM time_table hs
WHERE hs.starting_hour > DATE_SUB(NOW(), INTERVAL 50 DAY) AND
hs.ending_hour <= NOW()
HAVING tot >= '14:00:00'
ORDER BY hs.starting_hour ASC;
EDIT:
If you also want the timing restriction for the "range" calculation, you need to include it in the subquery. This filtering is built into the window function, but it is more often a hinderance than feature:
SELECT hs.*,
(SELECT SEC_TO_TIME(SUM(TIME_TO_SEC(TIMEDIFF(hs2.ending_hour, hs2.starting_hour)))
FROM time_table hs2
WHERE hs2.starting_hour >= hs.starting_hour - INTERVAL '12' HOUR AND
hs2.starting_hour <= hs.starting_hour + INTERVAL '12' HOUR AND
hs2.starting_hour > DATE_SUB(NOW(), INTERVAL 50 DAY) AND
hs2.ending_hour <= NOW()
) AS tot
FROM time_table hs
WHERE hs.starting_hour > DATE_SUB(NOW(), INTERVAL 50 DAY) AND
hs.ending_hour <= NOW()
HAVING tot >= '14:00:00'
ORDER BY hs.starting_hour ASC;

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.

Get classroom available hours between date time range

I'm, using Oracle 11g and I have this problem. I couldn't come up with any ideas to solve it yet.
I have a table with occupied classrooms. What I need to find are the hours available between a datetime range. For example, I have rooms A, B and C, the table of occupied classrooms looks like this:
Classroom start end
A 10/10/2013 10:00 10/10/2013 11:30
B 10/10/2013 09:15 10/10/2013 10:45
B 10/10/2013 14:30 10/10/2013 16:00
What I need to get is something like this:
with date time range between '10/10/2013 07:00' and '10/10/2013 21:15'
Classroom avalailable_from available_to
A 10/10/2013 07:00 10/10/2013 10:00
A 10/10/2013 11:30 10/10/2013 21:15
B 10/10/2013 07:00 10/10/2013 09:15
B 10/10/2013 10:45 10/10/2013 14:30
B 10/10/2013 16:00 10/10/2013 21:15
C 10/10/2013 07:00 10/10/2013 21:15
Is there a way I can accomplish that with sql or pl/sql?
I was looking at a solution similar in concept at least to Wernfried's, but I think it's different enough to post as well. The start is the same idea, first generating the possible time slots, and assuming you're looking at 15-minute windows: I'm using CTEs because I think they're clearer than nested selects, particularly with this many levels.
with date_time_range as (
select to_date('10/10/2013 07:00', 'DD/MM/YYYY HH24:MI') as date_start,
to_date('10/10/2013 21:15', 'DD/MM/YYYY HH24:MI') as date_end
from dual
),
time_slots as (
select level as slot_num,
dtr.date_start + (level - 1) * interval '15' minute as slot_start,
dtr.date_start + level * interval '15' minute as slot_end
from date_time_range dtr
connect by level <= (dtr.date_end - dtr.date_start) * (24 * 4) -- 15-minutes
)
select * from time_slots;
This gives you the 57 15-minute slots between the start and end date you specified. The CTE for date_time_range isn't strictly necessary, you could put your dates straight into the time_slots conditions, but you'd have to repeat them and that then introduces a possible failure point (and means binding the same value multiple times, from JDBC or wherever).
Those slots can then be cross-joined to the list of classrooms, which I'm assuming are already in another table, which gives you 171 (3x57) combinations; and those can be compared with existing bookings - once those are eliminated you're left with the 153 15-minute slots that have no booking.
with date_time_range as (...),
time_slots as (...),
free_slots as (
select c.classroom, ts.slot_num, ts.slot_start, ts.slot_end,
lag(ts.slot_end) over (partition by c.classroom order by ts.slot_num)
as lag_end,
lead(ts.slot_start) over (partition by c.classroom order by ts.slot_num)
as lead_start
from time_slots ts
cross join classrooms c
left join occupied_classrooms oc on oc.classroom = c.classroom
and not (oc.occupied_end <= ts.slot_start
or oc.occupied_start >= ts.slot_end)
where oc.classroom is null
)
select * from free_slots;
But then you have to collapse those into contiguous ranges. There are various ways of doing that; here I'm peeking at the previous and next rows to decide if a particular value is the edge of a range:
with date_time_range as (...),
time_slots as (...),
free_slots as (...),
free_slots_extended as (
select fs.classroom, fs.slot_num,
case when fs.lag_end is null or fs.lag_end != fs.slot_start
then fs.slot_start end as slot_start,
case when fs.lead_start is null or fs.lead_start != fs.slot_end
then fs.slot_end end as slot_end
from free_slots fs
)
select * from free_slots_extended
where (fse.slot_start is not null or fse.slot_end is not null);
Now we're down to 12 rows. (The outer where clause eliminates all 141 of the 153 slots from the previous step which are mid-range, since we only care about the edges):
CLASSROOM SLOT_NUM SLOT_START SLOT_END
--------- ---------- ---------------- ----------------
A 1 2013-10-10 07:00
A 12 2013-10-10 10:00
A 19 2013-10-10 11:30
A 57 2013-10-10 21:15
B 1 2013-10-10 07:00
B 9 2013-10-10 09:15
B 16 2013-10-10 10:45
B 30 2013-10-10 14:30
B 37 2013-10-10 16:00
B 57 2013-10-10 21:15
C 1 2013-10-10 07:00
C 57 2013-10-10 21:15
So those represent the edges, but on separate rows, and a final step combines them:
...
select distinct fse.classroom,
nvl(fse.slot_start, lag(fse.slot_start)
over (partition by fse.classroom order by fse.slot_num)) as slot_start,
nvl(fse.slot_end, lead(fse.slot_end)
over (partition by fse.classroom order by fse.slot_num)) as slot_end
from free_slots_extended fse
where (fse.slot_start is not null or fse.slot_end is not null)
Or putting all that together:
with date_time_range as (
select to_date('10/10/2013 07:00', 'DD/MM/YYYY HH24:MI') as date_start,
to_date('10/10/2013 21:15', 'DD/MM/YYYY HH24:MI') as date_end
from dual
),
time_slots as (
select level as slot_num,
dtr.date_start + (level - 1) * interval '15' minute as slot_start,
dtr.date_start + level * interval '15' minute as slot_end
from date_time_range dtr
connect by level <= (dtr.date_end - dtr.date_start) * (24 * 4) -- 15-minutes
),
free_slots as (
select c.classroom, ts.slot_num, ts.slot_start, ts.slot_end,
lag(ts.slot_end) over (partition by c.classroom order by ts.slot_num)
as lag_end,
lead(ts.slot_start) over (partition by c.classroom order by ts.slot_num)
as lead_start
from time_slots ts
cross join classrooms c
left join occupied_classrooms oc on oc.classroom = c.classroom
and not (oc.occupied_end <= ts.slot_start
or oc.occupied_start >= ts.slot_end)
where oc.classroom is null
),
free_slots_extended as (
select fs.classroom, fs.slot_num,
case when fs.lag_end is null or fs.lag_end != fs.slot_start
then fs.slot_start end as slot_start,
case when fs.lead_start is null or fs.lead_start != fs.slot_end
then fs.slot_end end as slot_end
from free_slots fs
)
select distinct fse.classroom,
nvl(fse.slot_start, lag(fse.slot_start)
over (partition by fse.classroom order by fse.slot_num)) as slot_start,
nvl(fse.slot_end, lead(fse.slot_end)
over (partition by fse.classroom order by fse.slot_num)) as slot_end
from free_slots_extended fse
where (fse.slot_start is not null or fse.slot_end is not null)
order by 1, 2;
Which gives:
CLASSROOM SLOT_START SLOT_END
--------- ---------------- ----------------
A 2013-10-10 07:00 2013-10-10 10:00
A 2013-10-10 11:30 2013-10-10 21:15
B 2013-10-10 07:00 2013-10-10 09:15
B 2013-10-10 10:45 2013-10-10 14:30
B 2013-10-10 16:00 2013-10-10 21:15
C 2013-10-10 07:00 2013-10-10 21:15
SQL Fiddle.
It is always a challenge when you like to "select something which does not exist". First you need a list of all available classrooms and times (in interval of 15 Minutes). Then you can select them by skipping the occupied items.
I managed to make a query without any PL/SQL:
CREATE TABLE Table1
(Classroom VARCHAR2(10), start_ts DATE, end_ts DATE);
INSERT INTO Table1 VALUES ('A', TIMESTAMP '2013-01-10 10:00:00', TIMESTAMP '2013-01-10 11:30:00');
INSERT INTO Table1 VALUES ('B', TIMESTAMP '2013-01-10 09:15:00', TIMESTAMP '2013-01-10 10:45:00');
INSERT INTO Table1 VALUES ('B', TIMESTAMP '2013-01-10 14:30:00', TIMESTAMP '2013-01-10 16:00:00');
WITH all_rooms AS
(SELECT CHR(64+LEVEL) AS ROOM FROM dual CONNECT BY LEVEL <= 3),
all_times AS
(SELECT CAST(TIMESTAMP '2013-01-10 07:00:00' + (LEVEL-1) * INTERVAL '15' MINUTE AS DATE) AS TIMES, LEVEL AS SLOT
FROM DUAL
CONNECT BY TIMESTAMP '2013-01-10 07:00:00' + (LEVEL-1) * INTERVAL '15' MINUTE <= TIMESTAMP '2013-01-10 21:15:00'),
all_free_slots AS
(SELECT ROOM, TIMES, SLOT,
CASE SLOT-LAG(SLOT, 1, 0) OVER (PARTITION BY ROOM ORDER BY SLOT)
WHEN 1 THEN 0
ELSE 1
END AS NEW_WINDOW
FROM all_times
CROSS JOIN all_rooms
WHERE NOT EXISTS
(SELECT 1 FROM TABLE1 WHERE ROOM = CLASSROOM AND TIMES BETWEEN START_TS + INTERVAL '1' MINUTE AND END_TS - INTERVAL '1' MINUTE)),
free_time_windows AS
(SELECT ROOM, TIMES, SLOT,
SUM(NEW_WINDOW) OVER (PARTITION BY ROOM ORDER BY SLOT) AS WINDOW_ID
FROM all_free_slots)
SELECT ROOM,
TO_CHAR(MIN(TIMES), 'yyyy-mm-dd hh24:mi') AS free_time_start,
TO_CHAR(MAX(TIMES), 'yyyy-mm-dd hh24:mi') AS free_time_end
FROM free_time_windows
GROUP BY ROOM, WINDOW_ID
HAVING MAX(TIMES) - MIN(TIMES) > 0
ORDER BY ROOM, 2;
ROOM FREE_TIME_START FREE_TIME_END
---- ----------------------------------
A 2013-01-10 07:00 2013-01-10 10:00
A 2013-01-10 11:30 2013-01-10 21:15
B 2013-01-10 07:00 2013-01-10 09:15
B 2013-01-10 10:45 2013-01-10 14:30
B 2013-01-10 16:00 2013-01-10 21:15
C 2013-01-10 07:00 2013-01-10 21:15
In order to understand the query you can split the sub-queries from top, e.g.
WITH all_rooms AS
(SELECT CHR(64+LEVEL) AS ROOM FROM dual CONNECT BY LEVEL <= 3),
all_times AS
(SELECT CAST(TIMESTAMP '2013-01-10 07:00:00' + (LEVEL-1) * INTERVAL '15' MINUTE AS DATE) AS TIMES, LEVEL AS SLOT
FROM DUAL
CONNECT BY TIMESTAMP '2013-01-10 07:00:00' + (LEVEL-1) * INTERVAL '15' MINUTE <= TIMESTAMP '2013-01-10 21:15:00')
SELECT ROOM, TIMES, SLOT,
CASE SLOT-LAG(SLOT, 1, 0) OVER (PARTITION BY ROOM ORDER BY SLOT)
WHEN 1 THEN 0
ELSE 1
END AS NEW_WINDOW
FROM all_times
CROSS JOIN all_rooms
WHERE NOT EXISTS (SELECT 1 FROM TABLE1 WHERE ROOM = CLASSROOM AND TIMES BETWEEN START_TS + INTERVAL '1' MINUTE AND END_TS - INTERVAL '1' MINUTE)
ORDER BY ROOM, SLOT

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