Event grouping in time series - sql

I'm trying to build groups of precipitation events in my measurement data. I got a time, a measurement value and a flag noting if it's was raining:
00:00, 32.4, 0
00:10, 32.4, 0
00:20, 32.6, 1
00:30, 32.7, 1
00:40, 32.9, 1
00:50, 33.2, 1
01:00, 33.2, 0
01:10, 33.2, 0
01:20, 33.2, 0
01:30, 33.5, 1
01:40, 33.6, 1
01:50, 33.6, 0
02:00, 33.6, 0
...
Now I'd like to generate an event id for the precipitation events:
00:00, 32.4, 0, NULL
00:10, 32.4, 0, NULL
00:20, 32.6, 1, 1
00:30, 32.7, 1, 1
00:40, 32.9, 1, 1
00:50, 33.2, 1, 1
01:00, 33.2, 0, NULL
01:10, 33.2, 0, NULL
01:20, 33.2, 0, NULL
01:30, 33.5, 1, 2
01:40, 33.6, 1, 2
01:50, 33.6, 0, NULL
02:00, 33.6, 0, NULL
...
Then I'll be able to use grouping to summarize the events. Any hint how to do this in Oracle is much appreciated.
So far I was able to calculate the mentioned flag and the diff to the last row:
SELECT
measured_at,
station_id
ps, -- precipitation sum
ps - lag(ps, 1, NULL) OVER (ORDER BY measured_at ASC) as p, -- precipitation delta
CASE
WHEN ps - lag(ps, 1, NULL) OVER (ORDER BY measured_at ASC) > 0 THEN 1
ELSE 0
END as rainflag
FROM measurements;
I think it must be possible to generate the required event id somehow, but can't figure it out. Thanks for your time!
Final solution using mt0 answer:
DROP TABLE events;
CREATE TABLE events (measured_at, station_id, ps) AS
SELECT TO_DATE('2016-05-01 12:00', 'YYYY-MM-DD HH24:MI'), 'XYZ', 32.4 FROM DUAL UNION ALL
SELECT TO_DATE('2016-05-01 12:10', 'YYYY-MM-DD HH24:MI'), 'XYZ', 32.6 FROM DUAL UNION ALL
SELECT TO_DATE('2016-05-01 12:20', 'YYYY-MM-DD HH24:MI'), 'XYZ', 32.7 FROM DUAL UNION ALL
SELECT TO_DATE('2016-05-01 12:30', 'YYYY-MM-DD HH24:MI'), 'XYZ', 32.9 FROM DUAL UNION ALL
SELECT TO_DATE('2016-05-01 12:40', 'YYYY-MM-DD HH24:MI'), 'XYZ', 33.2 FROM DUAL UNION ALL
SELECT TO_DATE('2016-05-01 12:50', 'YYYY-MM-DD HH24:MI'), 'XYZ', 33.2 FROM DUAL UNION ALL
SELECT TO_DATE('2016-05-01 13:00', 'YYYY-MM-DD HH24:MI'), 'XYZ', 33.2 FROM DUAL UNION ALL
SELECT TO_DATE('2016-05-01 13:10', 'YYYY-MM-DD HH24:MI'), 'XYZ', 33.2 FROM DUAL UNION ALL
SELECT TO_DATE('2016-05-01 13:20', 'YYYY-MM-DD HH24:MI'), 'XYZ', 33.5 FROM DUAL UNION ALL
SELECT TO_DATE('2016-05-01 13:30', 'YYYY-MM-DD HH24:MI'), 'XYZ', 33.6 FROM DUAL UNION ALL
SELECT TO_DATE('2016-05-01 13:40', 'YYYY-MM-DD HH24:MI'), 'XYZ', 33.6 FROM DUAL UNION ALL
SELECT TO_DATE('2016-05-01 13:50', 'YYYY-MM-DD HH24:MI'), 'XYZ', 33.5 FROM DUAL UNION ALL
SELECT TO_DATE('2016-05-01 17:00', 'YYYY-MM-DD HH24:MI'), 'XYZ', 39.1 FROM DUAL UNION ALL
SELECT TO_DATE('2016-05-01 17:10', 'YYYY-MM-DD HH24:MI'), 'XYZ', 39.2 FROM DUAL UNION ALL
SELECT TO_DATE('2016-05-01 17:20', 'YYYY-MM-DD HH24:MI'), 'XYZ', 39.2 FROM DUAL;
WITH
flagged AS (
SELECT
measured_at,
station_id,
ps,
CASE
WHEN measured_at - lag(measured_at, 1, NULL) OVER (ORDER BY measured_at) = (1/144) THEN ps - lag(ps, 1, NULL) OVER (ORDER BY measured_at)
ELSE NULL
END as delta_p,
CASE
WHEN ps - lag(ps, 1, NULL) OVER (ORDER BY measured_at) > 0 THEN 1
ELSE 0
END AS rain
FROM events
),
eventmarked AS (
SELECT
f.*,
CASE
WHEN f.delta_p >= 0 THEN f.delta_p
ELSE NULL
END AS p,
CASE rain
WHEN 1 THEN COUNT(1) OVER (ORDER BY measured_at) - SUM(rain) OVER (ORDER BY measured_at)
END as event
FROM flagged f
),
summarized AS (
SELECT
em.*,
sum(CASE p WHEN 0 THEN NULL ELSE p END) OVER (PARTITION BY event ORDER BY measured_at) as e_ps
FROM eventmarked em
)
SELECT measured_at, station_id, ps, p, e_ps FROM summarized
ORDER BY measured_at;

Oracle Setup:
CREATE TABLE events ( measured_at, station_id, ps ) AS
SELECT '00:00', 32.4, 0 FROM DUAL UNION ALL
SELECT '00:10', 32.4, 0 FROM DUAL UNION ALL
SELECT '00:20', 32.6, 1 FROM DUAL UNION ALL
SELECT '00:30', 32.7, 1 FROM DUAL UNION ALL
SELECT '00:40', 32.9, 1 FROM DUAL UNION ALL
SELECT '00:50', 33.2, 1 FROM DUAL UNION ALL
SELECT '01:00', 33.2, 0 FROM DUAL UNION ALL
SELECT '01:10', 33.2, 0 FROM DUAL UNION ALL
SELECT '01:20', 33.2, 0 FROM DUAL UNION ALL
SELECT '01:30', 33.5, 1 FROM DUAL UNION ALL
SELECT '01:40', 33.6, 1 FROM DUAL UNION ALL
SELECT '01:50', 33.6, 0 FROM DUAL UNION ALL
SELECT '02:00', 33.6, 0 FROM DUAL;
Query:
SELECT measured_at,
station_id,
ps,
CASE WHEN rainflag IS NOT NULL THEN DENSE_RANK() OVER ( ORDER BY rainflag ) END AS rainflag
FROM (
SELECT e.*,
CASE ps
WHEN 1
THEN COUNT( 1 ) OVER ( ORDER BY measured_at )
- SUM( ps ) OVER ( ORDER BY measured_at )
END AS rainflag
FROM events e
)
ORDER BY measured_at;
Query 2
SELECT measured_at,
station_id,
ps,
CASE ps WHEN 1
THEN SUM( rainflag ) OVER ( ORDER BY measured_at )
END AS rainflag
FROM (
SELECT e.*,
CASE WHEN ps > LAG( ps, 1, 0 ) OVER ( ORDER BY measured_at )
THEN 1
END AS rainflag
FROM events e
);
Output:
MEASURED_AT STATION_ID PS RAINFLAG
----------- ---------- ---------- ----------
00:00 32.4 0
00:10 32.4 0
00:20 32.6 1 1
00:30 32.7 1 1
00:40 32.9 1 1
00:50 33.2 1 1
01:00 33.2 0
01:10 33.2 0
01:20 33.2 0
01:30 33.5 1 2
01:40 33.6 1 2
01:50 33.6 0
02:00 33.6 0

Alternative solution using only LAG function.
In the subquery the column PS2 marks the rain started events. The main query simple sums this flag while ignoring the time that is not raining.
with ev as (
select measured_at, station_id, ps,
case when ps = 1 and lag(ps,1,0) over (order by measured_at) = 0
then 1 else 0 end ps2
from events)
select measured_at, station_id, ps, ps2,
case when ps = 1 then
sum(ps2) over (order by measured_at) end rf
from ev
;
MEASURED_AT STATION_ID PS PS2 RF
----------- ---------- ---------- ---------- ----------
00:00 32,4 0 0
00:10 32,4 0 0
00:20 32,6 1 1 1
00:30 32,7 1 0 1
00:40 32,9 1 0 1
00:50 33,2 1 0 1
01:00 33,2 0 0
01:10 33,2 0 0
01:20 33,2 0 0
01:30 33,5 1 1 2
01:40 33,6 1 0 2
01:50 33,6 0 0
02:00 33,6 0 0

Related

SQL Oracle - create a new column of dates to calculate a count based on datetime field

I have a table showing subscriptions with a column for active date and a column for inactive date. I need a query which counts the number of active subscriptions for each specific date in a date range.
I'm struggling to add in a row for each date in my date range so that I can then compare with the other date columns in my table.
Example of my table
Example of the result I need
You can do it this way:
-- SAMPLE DATA
WITH
tbl AS
(
Select 'A' "A_TYPE", 1 "SUBSCRIPTION_ID", To_Date('10.10.2022', 'dd.mm.yyyy') "ACTIVE_DATE", Null "INACTIVE_DATE" From Dual Union All
Select 'A' "A_TYPE", 2 "SUBSCRIPTION_ID", To_Date('11.10.2022', 'dd.mm.yyyy') "ACTIVE_DATE", To_Date('14.10.2022', 'dd.mm.yyyy') "INACTIVE_DATE" From Dual Union All
Select 'A' "A_TYPE", 3 "SUBSCRIPTION_ID", To_Date('12.10.2022', 'dd.mm.yyyy') "ACTIVE_DATE", To_Date('14.10.2022', 'dd.mm.yyyy') "INACTIVE_DATE" From Dual Union All
Select 'B' "A_TYPE", 4 "SUBSCRIPTION_ID", To_Date('13.10.2022', 'dd.mm.yyyy') "ACTIVE_DATE", Null "INACTIVE_DATE" From Dual Union All
Select 'B' "A_TYPE", 5 "SUBSCRIPTION_ID", To_Date('14.10.2022', 'dd.mm.yyyy') "ACTIVE_DATE", To_Date('18.10.2022', 'dd.mm.yyyy') "INACTIVE_DATE" From Dual Union All
Select 'B' "A_TYPE", 6 "SUBSCRIPTION_ID", To_Date('15.10.2022', 'dd.mm.yyyy') "ACTIVE_DATE", Null "INACTIVE_DATE" From Dual
),
CTE To generate the dates for A_TYPE:
Here you can define range starting date (09.10.2022) and how many days you want the range to last (LEVEL <= 11)
dates AS
(
Select Distinct
t.A_TYPE "A_TYPE",
d.RANGE_DATE "RANGE_DATE"
From
(Select To_Date('09.10.2022', 'dd.mm.yyyy') + LEVEL - 1 "RANGE_DATE" From dual Connect By LEVEL <= 11) d
Left Join
tbl t ON(1 = 1)
Order By
t.A_TYPE,
d.RANGE_DATE
)
And main SQL:
SELECT
d.A_TYPE "A_TYPE",
d.RANGE_DATE "RANGE_DATE",
(Select Count(*) From tbl Where A_TYPE = d.A_TYPE And ACTIVE_DATE <= d.RANGE_DATE And Nvl(INACTIVE_DATE, To_Date('11.10.2062', 'dd.mm.yyyy')) > d.RANGE_DATE) "ACTIVE_COUNT"
FROM
dates d
This is the result:
/*
A_TYPE RANGE_DATE ACTIVE_COUNT
------ ---------- ------------
A 09-OCT-22 0
A 10-OCT-22 1
A 11-OCT-22 2
A 12-OCT-22 3
A 13-OCT-22 3
A 14-OCT-22 1
A 15-OCT-22 1
A 16-OCT-22 1
A 17-OCT-22 1
A 18-OCT-22 1
A 19-OCT-22 1
B 09-OCT-22 0
B 10-OCT-22 0
B 11-OCT-22 0
B 12-OCT-22 0
B 13-OCT-22 1
B 14-OCT-22 2
B 15-OCT-22 3
B 16-OCT-22 3
B 17-OCT-22 3
B 18-OCT-22 2
B 19-OCT-22 2
*/
Regards...
You can UNPIVOT your two date columns and then use a PARTITIONed OUTER JOIN to a calendar:
WITH data (type, subscription_id, is_active, dt) AS (
SELECT type, subscription_id, is_active, dt
FROM table_name
UNPIVOT (
dt FOR is_active IN (
active_date AS 1,
inactive_date AS 0
)
)
)
SELECT d.type,
c.day,
COUNT(CASE d.is_active WHEN 1 THEN 1 END) AS count_active,
COUNT(CASE d.is_active WHEN 0 THEN 1 END) AS count_inactive
FROM (
SELECT TRUNC(min_dt) + LEVEL - 1 AS day
FROM (
SELECT MIN(dt) AS min_dt,
MAX(dt) AS max_dt
FROM data
)
CONNECT BY TRUNC(min_dt) + LEVEL - 1 < max_dt
) c
LEFT OUTER JOIN data d
PARTITION BY (d.type)
ON (c.day <= d.dt AND d.dt < c.day + 1)
GROUP BY
d.type,
c.day
ORDER BY
d.type,
c.day
Which, for the sample data:
CREATE TABLE table_name (type, subscription_id, active_date, inactive_date) AS
SELECT 'A', 1, DATE '2022-11-01', NULL FROM DUAL UNION ALL
SELECT 'A', 2, DATE '2022-11-02', DATE '2022-11-10' FROM DUAL UNION ALL
SELECT 'A', 3, DATE '2022-11-03', NULL FROM DUAL UNION ALL
SELECT 'A', 4, DATE '2022-11-04', NULL FROM DUAL UNION ALL
SELECT 'B', 5, DATE '2022-11-05', NULL FROM DUAL UNION ALL
SELECT 'B', 6, DATE '2022-11-06', NULL FROM DUAL UNION ALL
SELECT 'B', 7, DATE '2022-11-07', DATE '2022-11-09' FROM DUAL UNION ALL
SELECT 'B', 8, DATE '2022-11-08', NULL FROM DUAL;
Outputs:
TYPE
DAY
COUNT_ACTIVE
COUNT_INACTIVE
A
01-NOV-22
1
0
A
02-NOV-22
1
0
A
03-NOV-22
1
0
A
04-NOV-22
1
0
A
05-NOV-22
0
0
A
06-NOV-22
0
0
A
07-NOV-22
0
0
A
08-NOV-22
0
0
A
09-NOV-22
0
0
B
01-NOV-22
0
0
B
02-NOV-22
0
0
B
03-NOV-22
0
0
B
04-NOV-22
0
0
B
05-NOV-22
1
0
B
06-NOV-22
1
0
B
07-NOV-22
1
0
B
08-NOV-22
1
0
B
09-NOV-22
0
1
fiddle
SELECT
type,
TO_DATE(date, 'DD-MM-YYYY') as date,
count(date) AS count_of_dates
FROM table
GROUP BY type, date
ORDER BY type

how to use windows function during merge in sql

I am working in oracle sql. I have two table which is linked to each other by one column - company_id (see on the picture); I want to merge table 1 to table 2 and calculate 6 month average (6 month before period from table 2) of income for each company_id and each date of table2. I appreciate any code/idea how to solve this task.
You can use an analytic range window to calculate the averages for table1 and then JOIN the result to table2:
SELECT t2.*,
t1.avg_income_6,
t1.avg_income_12
FROM table2 t2
LEFT OUTER JOIN (
SELECT company_id,
dt,
ROUND(AVG(income) OVER (
PARTITION BY company_id
ORDER BY dt
RANGE BETWEEN INTERVAL '5' MONTH PRECEDING
AND INTERVAL '0' MONTH FOLLOWING
), 2) AS avg_income_6,
ROUND(AVG(income) OVER (
PARTITION BY company_id
ORDER BY dt
RANGE BETWEEN INTERVAL '11' MONTH PRECEDING
AND INTERVAL '0' MONTH FOLLOWING
), 2) AS avg_income_12
FROM table1
) t1
ON (t2.company_id = t1.company_id AND t2.dt = t1.dt);
Which, for the sample data:
CREATE TABLE table1 (company_id, dt, income) AS
SELECT 1, date '2019-01-01', 65 FROM DUAL UNION ALL
SELECT 1, date '2019-02-01', 58 FROM DUAL UNION ALL
SELECT 1, date '2019-03-01', 12 FROM DUAL UNION ALL
SELECT 1, date '2019-04-01', 81 FROM DUAL UNION ALL
SELECT 1, date '2019-05-01', 38 FROM DUAL UNION ALL
SELECT 1, date '2019-06-01', 81 FROM DUAL UNION ALL
SELECT 1, date '2019-07-01', 38 FROM DUAL UNION ALL
SELECT 1, date '2019-08-01', 69 FROM DUAL UNION ALL
SELECT 1, date '2019-09-01', 54 FROM DUAL UNION ALL
SELECT 1, date '2019-10-01', 90 FROM DUAL UNION ALL
SELECT 1, date '2019-11-01', 10 FROM DUAL UNION ALL
SELECT 1, date '2019-12-01', 12 FROM DUAL UNION ALL
SELECT 1, date '2020-01-01', 11 FROM DUAL UNION ALL
SELECT 1, date '2020-02-01', 83 FROM DUAL UNION ALL
SELECT 1, date '2020-03-01', 18 FROM DUAL UNION ALL
SELECT 1, date '2020-04-01', 28 FROM DUAL UNION ALL
SELECT 1, date '2020-05-01', 52 FROM DUAL UNION ALL
SELECT 1, date '2020-06-01', 21 FROM DUAL UNION ALL
SELECT 1, date '2020-07-01', 54 FROM DUAL UNION ALL
SELECT 1, date '2020-08-01', 30 FROM DUAL UNION ALL
SELECT 1, date '2020-09-01', 12 FROM DUAL UNION ALL
SELECT 1, date '2020-10-01', 25 FROM DUAL UNION ALL
SELECT 1, date '2020-11-01', 86 FROM DUAL UNION ALL
SELECT 1, date '2020-12-01', 4 FROM DUAL UNION ALL
SELECT 1, date '2021-01-01', 10 FROM DUAL UNION ALL
SELECT 1, date '2021-02-01', 72 FROM DUAL UNION ALL
SELECT 1, date '2021-03-01', 65 FROM DUAL UNION ALL
SELECT 1, date '2021-04-01', 25 FROM DUAL;
CREATE TABLE table2 (company_id, dt) AS
SELECT 1, date '2019-06-01' FROM DUAL UNION ALL
SELECT 1, date '2019-09-01' FROM DUAL UNION ALL
SELECT 1, date '2019-12-01' FROM DUAL UNION ALL
SELECT 1, date '2020-01-01' FROM DUAL UNION ALL
SELECT 1, date '2020-07-01' FROM DUAL UNION ALL
SELECT 1, date '2020-08-01' FROM DUAL UNION ALL
SELECT 1, date '2021-03-01' FROM DUAL UNION ALL
SELECT 1, date '2021-04-01' FROM DUAL;
Outputs:
COMPANY_ID
DT
AVG_INCOME_6
AVG_INCOME_12
1
2019-06-01 00:00:00
55.83
55.83
1
2019-09-01 00:00:00
60.17
55.11
1
2019-12-01 00:00:00
45.5
50.67
1
2020-01-01 00:00:00
41
46.17
1
2020-07-01 00:00:00
42.67
41.83
1
2020-08-01 00:00:00
33.83
38.58
1
2021-03-01 00:00:00
43.67
38.25
1
2021-04-01 00:00:00
43.67
38
db<>fiddle here
I don't think you need any window function here (if you were thinking of analytic functions); ordinary avg with appropriate join conditions should do the job.
Sample data:
SQL> with
2 table1 (company_id, datum, income) as
3 (select 1, date '2019-01-01', 65 from dual union all
4 select 1, date '2019-02-01', 58 from dual union all
5 select 1, date '2019-03-01', 12 from dual union all
6 select 1, date '2019-04-01', 81 from dual union all
7 select 1, date '2019-05-01', 38 from dual union all
8 select 1, date '2019-06-01', 81 from dual union all
9 select 1, date '2019-07-01', 38 from dual union all
10 select 1, date '2019-08-01', 69 from dual union all
11 select 1, date '2019-09-01', 54 from dual union all
12 select 1, date '2019-10-01', 90 from dual union all
13 select 1, date '2019-11-01', 10 from dual union all
14 select 1, date '2019-12-01', 12 from dual
15 ),
16 table2 (company_id, datum) as
17 (select 1, date '2019-06-01' from dual union all
18 select 1, date '2019-09-01' from dual union all
19 select 1, date '2019-12-01' from dual union all
20 select 1, date '2020-01-01' from dual union all
21 select 1, date '2020-07-01' from dual
22 )
Query begins here:
23 select b.company_id,
24 b.datum ,
25 round(avg(a.income), 2) result
26 from table1 a join table2 b on a.company_id = b.company_id
27 and a.datum > add_months(b.datum, -6)
28 and a.datum <= b.datum
29 group by b.company_id, b.datum;
COMPANY_ID DATUM RESULT
---------- -------- ----------
1 01.06.19 55,83
1 01.09.19 60,17
1 01.12.19 45,5
1 01.01.20 47
SQL>

Oracle SQL: Count a time period when the value was over the threshold

My table MEASUREMENTS (Oracle SQL 12) has 3 columns: DT - measurement timestamp, MEASUREMENT - value, THRESHOLD - upper limit.
Sometimes measurements are above the threshold. Trying to calculate time periods when the measurement value was higher than the threshold.
DT | MEASUREMENT | THRESHOLD
---------------+-------------+--------------------
04.08.16 01:10 | 60,5 | 70,0
04.08.16 01:20 | 65,5 | 70,0
04.08.16 01:30 | 68,1 | 70,0
04.08.16 01:40 | 70,1* | 70,0 //period start
04.08.16 01:50 | 70,1* | 70,0
04.08.16 02:00 | 70,75* | 70,0 //period end
04.08.16 02:10 | 53,5 | 70,0
04.08.16 02:20 | 50,15 | 70,0
04.08.16 02:30 | 52,15 | 70,0
04.08.16 02:40 | 53,15 | 70,0
Expected result (02:00-01:40=00:20):
DURATION | START | END
---------+----------------+---------------
00:20 | 04.08.16 01:40 | 04.08.16 02:00
You can use row_number() to identify the periods. This is a gaps-and-islands problem. The following returns each period where the measurement exceeds the threshold:
select max(dt) - min(dt) as duration, min(dt), max(dt)
from (select t.*,
row_number() over (order by dt) as seqnum,
row_number() over (partition by (case when measurement > threshold then 1 else 2 end), order by dt) as seqnum_t
from t
) t
where measurement > threshold
group by (seqnum - seqnum_t)
You can use the MATCH_RECOGNIZE clause (plus some extra info):
WITH t (DT, MEASUREMENT, THRESHOLD) AS (
SELECT TO_DATE('01:10', 'hh24:mi'), 60.5 , 70 FROM dual UNION ALL
SELECT TO_DATE('01:20', 'hh24:mi'), 65.5 , 70 FROM dual UNION ALL
SELECT TO_DATE('01:30', 'hh24:mi'), 68.1 , 70 FROM dual UNION ALL
SELECT TO_DATE('01:40', 'hh24:mi'), 70.1 , 70 FROM dual UNION ALL
SELECT TO_DATE('01:50', 'hh24:mi'), 70.1 , 70 FROM dual UNION ALL
SELECT TO_DATE('02:00', 'hh24:mi'), 70.75 , 70 FROM dual UNION ALL
SELECT TO_DATE('02:10', 'hh24:mi'), 53.5 , 70 FROM dual UNION ALL
SELECT TO_DATE('02:20', 'hh24:mi'), 50.15 , 70 FROM dual UNION ALL
SELECT TO_DATE('02:30', 'hh24:mi'), 52.15 , 70 FROM dual UNION ALL
SELECT TO_DATE('02:40', 'hh24:mi'), 53.15 , 70 FROM dual)
SELECT MEASUREMENT_MAX, match_num, FIRST_DT, LAST_DT, (LAST_DT-FIRST_DT)*24*60 AS DURATION
FROM t
MATCH_RECOGNIZE (
ORDER BY DT
MEASURES
FINAL MAX(MEASUREMENT) AS MEASUREMENT_MAX,
MATCH_NUMBER() AS match_num,
FINAL LAST(DT) AS LAST_DT,
FINAL FIRST(DT) AS FIRST_DT
PATTERN (a+)
DEFINE
a AS MEASUREMENT > THRESHOLD);
MEASUREMENT_MAX match_num FIRST_DT LAST_DT DURATION
70.75 3 01.06.2018 01:40:00 01.06.2018 02:00:00 20
You don't need to use two row_numbers, you can directly use it via cumulative approach :
select max(dt) - min(dt) as duration, min(dt), max(dt)
from (select *, row_number() over (order by dt) as seq,
sum(case when measurement > threshold then 1 else 0 end) over(order by dt) as grp
from table
) t
where measurement > threshold
group by (seq - grp);
Using row_number analytical function:
SQL> WITH measurements (DT, MEASUREMENT, THRESHOLD) AS (
2 select to_date('04.08.16 01:10', 'DD.MM.YY HH24:MI'), 60.5, 70.0 from dual union all
3 select to_date('04.08.16 01:20', 'DD.MM.YY HH24:MI'), 65.5, 70.0 from dual union all
4 select to_date('04.08.16 01:30', 'DD.MM.YY HH24:MI'), 68.1, 70.0 from dual union all
5 select to_date('04.08.16 01:40', 'DD.MM.YY HH24:MI'), 70.1, 70.0 from dual union all
6 select to_date('04.08.16 01:50', 'DD.MM.YY HH24:MI'), 70.1, 70.0 from dual union all
7 select to_date('04.08.16 02:00', 'DD.MM.YY HH24:MI'), 70.75, 70.0 from dual union all
8 select to_date('04.08.16 02:10', 'DD.MM.YY HH24:MI'), 53.5, 70.0 from dual union all
9 select to_date('04.08.16 02:20', 'DD.MM.YY HH24:MI'), 50.15, 70.0 from dual union all
10 select to_date('04.08.16 02:30', 'DD.MM.YY HH24:MI'), 52.15, 70.0 from dual union all
11 select to_date('04.08.16 02:40', 'DD.MM.YY HH24:MI'), 53.15, 70.0 from dual),
12 ---------------------
13 ---- end of data preparation
14 ---------------------
15 calculated_values AS (
16 SELECT DT,
17 MEASUREMENT,
18 THRESHOLD,
19 row_number() OVER (ORDER BY dt) - row_number() OVER (PARTITION BY CASE WHEN MEASUREMENT > THRESHOLD THEN 1 ELSE 0 END ORDER BY dt) rn,
20 CASE WHEN MEASUREMENT > THRESHOLD THEN 1 ELSE 0 END threshold_flag
21 FROM measurements)
22 SELECT cast(numtodsinterval(MAX(dt)-MIN(dt), 'DAY') AS INTERVAL DAY(0) TO SECOND(0)) AS duration,
23 MIN(dt) AS "START",
24 MAX(dt) AS "END"
25 FROM calculated_values
26 WHERE threshold_flag > 0
27 GROUP BY rn;
OUTPUT:
DURATION START END
----------- -------------------- --------------------
+0 00:20:00 8/4/2016 1:40:00 8/4/2016 2:00:00
Your query will be:
WITH calculated_values AS (
SELECT DT,
MEASUREMENT,
THRESHOLD,
row_number() OVER (ORDER BY dt) - row_number() OVER (PARTITION BY CASE WHEN MEASUREMENT > THRESHOLD THEN 1 ELSE 0 END ORDER BY dt) rn,
CASE WHEN MEASUREMENT > THRESHOLD THEN 1 ELSE 0 END threshold_flag
FROM measurements)
SELECT cast(numtodsinterval(MAX(dt)-MIN(dt), 'DAY') AS INTERVAL DAY(0) TO SECOND(0)) AS duration,
MIN(dt) AS "START",
MAX(dt) AS "END"
FROM calculated_values
WHERE threshold_flag > 0
GROUP BY rn;

Dense_rank query in sql(4 different columns) in

I have a table as follows:
Sn no. t_time Value rate
ABC 17-MAY-18 08:00:00 100.00 3
ABC 17-MAY-18 22:00:00 200.00 1
ABC 16-MAY-18 08:00:00 100.00 1
XYZ 14-MAY-18 01:00:00 700.00 1
XYZ 15-MAY-18 10:00:00 500.00 2
XYZ 15-MAY-18 13:00:00 100.00 2
And I want to generate the output as follows:
Sn no. New_value
ABC 150
XYZ 450
It is grouped by the Sn no. The New_value is the latest time of each date value multiplied by rate, and then averaged together.
For example ABC new_value is
Average of:[(100*1) and (200*1)]
Its a large dataset. How do I write a query for the above in the most efficient way. Please help.
You can use analytical function(row_number()) to achieve the result
SQL> WITH cte_table(Snno, t_time, Value, rate) AS (
2 SELECT 'ABC', to_date('2018-05-17 08:00:00', 'YYYY-MM-DD HH24:MI:SS'), 100.00, 3 FROM DUAL UNION ALL
3 SELECT 'ABC', to_date('2018-05-17 22:00:00', 'YYYY-MM-DD HH24:MI:SS'), 200.00, 1 FROM DUAL UNION ALL
4 SELECT 'ABC', to_date('2018-05-16 08:00:00', 'YYYY-MM-DD HH24:MI:SS'), 100.00, 1 FROM DUAL UNION ALL
5 SELECT 'XYZ', to_date('2018-05-14 01:00:00', 'YYYY-MM-DD HH24:MI:SS'), 700.00, 1 FROM DUAL UNION ALL
6 SELECT 'XYZ', to_date('2018-05-15 10:00:00', 'YYYY-MM-DD HH24:MI:SS'), 500.00, 2 FROM DUAL UNION ALL
7 SELECT 'XYZ', to_date('2018-05-15 13:00:00', 'YYYY-MM-DD HH24:MI:SS'), 100.00, 2 FROM DUAL),
8 --------------------------------
9 -- End of data preparation
10 --------------------------------
11 rn_table AS (
12 SELECT t.*, row_number() OVER (PARTITION BY TRUNC(t_time) ORDER BY t_time DESC) AS rn
13 FROM cte_table t)
14 SELECT snno,
15 AVG(VALUE * rate) new_value
16 FROM rn_table
17 WHERE rn = 1
18 GROUP BY snno;
Output:
SNNO NEW_VALUE
---- ----------
ABC 150
XYZ 450
Use the ROW_NUMBER (or RANK/DENSE_RANK if it is more appropriate) analytic function in a sub-query and then aggregate in the outer query:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE table_name ( Snno, t_time, Value, rate ) AS
SELECT 'ABC', TIMESTAMP '2018-05-17 08:00:00', 100.00, 3 FROM DUAL UNION ALL
SELECT 'ABC', TIMESTAMP '2018-05-17 22:00:00', 200.00, 1 FROM DUAL UNION ALL
SELECT 'ABC', TIMESTAMP '2018-05-16 08:00:00', 100.00, 1 FROM DUAL UNION ALL
SELECT 'XYZ', TIMESTAMP '2018-05-14 01:00:00', 700.00, 1 FROM DUAL UNION ALL
SELECT 'XYZ', TIMESTAMP '2018-05-15 10:00:00', 500.00, 2 FROM DUAL UNION ALL
SELECT 'XYZ', TIMESTAMP '2018-05-15 13:00:00', 100.00, 2 FROM DUAL;
Query 1:
SELECT snno,
AVG( value * rate ) As new_value
FROM (
SELECT t.*,
ROW_NUMBER() OVER (
PARTITION BY snno, value
ORDER BY t_time DESC
) AS rn
FROM table_name t
)
WHERE rn = 1
GROUP BY snno
Results:
| SNNO | NEW_VALUE |
|------|-------------------|
| ABC | 250 |
| XYZ | 633.3333333333334 |

Oracle SQL: Find the date 7 days ahead of a specified date excluding Sundays and other specific dates

I need to find what the date is 7 days ahead of any given date. The start date can be any date, however, the end date should not be a Sunday or a day marked as a holiday. There can only ever be 2 consecutive holiday days.
I have code that works, however, it's very long-winded. Is there potentially a simpler solution?
Also, if the required date range changed at some point in the future or the possible number of consecutive holidays changed, the script would need to be updated in multiple places, if this could be reduced then that would be great.
I already have a table (CALENDAR) containing a substantial range of dates, their day of the week and whether the date is considered a holiday. Something like this:
START_DATE, DAY_OF_WEEK, HOLIDAY
10-DEC-17 , Sun , 0
11-DEC-17 , Mon , 0
12-DEC-17 , Tue , 0
13-DEC-17 , Wed , 0
14-DEC-17 , Thu , 0
15-DEC-17 , Fri , 0
16-DEC-17 , Sat , 0
17-DEC-17 , Sun , 0
18-DEC-17 , Mon , 0
19-DEC-17 , Tue , 0
20-DEC-17 , Wed , 0
21-DEC-17 , Thu , 0
22-DEC-17 , Fri , 0
23-DEC-17 , Sat , 0
24-DEC-17 , Sun , 0
25-DEC-17 , Mon , 1
26-DEC-17 , Tue , 1
27-DEC-17 , Wed , 0
28-DEC-17 , Thu , 0
29-DEC-17 , Fri , 0
30-DEC-17 , Sat , 0
31-DEC-17 , Sun , 0
01-JAN-18 , Mon , 1
etc...
The expected OUTPUT would be something like:
START_DATE, END_DATE
10-DEC-17, 18-DEC-17
11-DEC-17, 18-DEC-17
12-DEC-17, 19-DEC-17
13-DEC-17, 20-DEC-17
14-DEC-17, 21-DEC-17
15-DEC-17, 22-DEC-17
16-DEC-17, 23-DEC-17
17-DEC-17, 27-DEC-17
18-DEC-17, 27-DEC-17
19-DEC-17, 27-DEC-17
20-DEC-17, 27-DEC-17
21-DEC-17, 28-DEC-17
22-DEC-17, 29-DEC-17
23-DEC-17, 30-DEC-17
24-DEC-17, 02-JAN-18
25-DEC-17, 02-JAN-18
26-DEC-17, 02-JAN-18
27-DEC-17, 03-JAN-18
28-DEC-17, 04-JAN-18
etc...
Below is my existing code. My approach is that as there can only ever be 3 consecutive excluded days (a Sunday followed by 2 holiday days), then I check 4 days ahead for each date and then take the first one which is not an excluded date. One of the 4 should always be a valid end date.
with temp as
(
select
start_date,
case
when lead(day_of_week, 7) over(order by start_date) = 'Sun'
or lead(holiday, 7) over(order by start_date) = 1
then null
else
lead(start_date, 7) over(order by start_date)
end as days_7,
case
when lead(day_of_week, 8) over(order by start_date) = 'Sun'
or lead(holiday, 8) over(order by start_date) = 1
then null
else
lead(start_date, 8) over(order by start_date)
end as days_8,
case
when lead(day_of_week, 9) over(order by start_date) = 'Sun'
or lead(holiday, 9) over(order by start_date) = 1
then null
else
lead(start_date, 9) over(order by start_date)
end as days_9,
case
when lead(day_of_week, 10) over(order by start_date) = 'Sun'
or lead(holiday, 10) over(order by start_date) = 1
then null
else
lead(start_date, 10) over(order by start_date)
end as days_10
from
calendar
)
select
start_date,
COALESCE(days_7, days_8, days_9, days_10) as end_date
from
temp
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE Calendar( START_DATE, HOLIDAY ) AS
SELECT TO_DATE( '10-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '11-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '12-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '13-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '14-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '15-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '16-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '17-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '18-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '19-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '20-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '21-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '22-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '23-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '24-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '25-DEC-17', 'DD-MON-YY' ), 1 FROM DUAL UNION ALL
SELECT TO_DATE( '26-DEC-17', 'DD-MON-YY' ), 1 FROM DUAL UNION ALL
SELECT TO_DATE( '27-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '28-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '29-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '30-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '31-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '01-JAN-18', 'DD-MON-YY' ), 1 FROM DUAL;
Query 1:
SELECT start_date,
LEAD( end_date, 7 ) OVER ( ORDER BY start_date ) AS end_date
FROM (
SELECT start_date,
LAST_VALUE(
CASE
WHEN Holiday = 0
AND start_date - TRUNC( start_date, 'IW' ) < 6
THEN start_date
END
) IGNORE NULLS OVER ( ORDER BY start_date DESC ) AS End_date
FROM Calendar
)
Results:
| START_DATE | END_DATE |
|----------------------|----------------------|
| 2017-12-10T00:00:00Z | 2017-12-18T00:00:00Z |
| 2017-12-11T00:00:00Z | 2017-12-18T00:00:00Z |
| 2017-12-12T00:00:00Z | 2017-12-19T00:00:00Z |
| 2017-12-13T00:00:00Z | 2017-12-20T00:00:00Z |
| 2017-12-14T00:00:00Z | 2017-12-21T00:00:00Z |
| 2017-12-15T00:00:00Z | 2017-12-22T00:00:00Z |
| 2017-12-16T00:00:00Z | 2017-12-23T00:00:00Z |
| 2017-12-17T00:00:00Z | 2017-12-27T00:00:00Z |
| 2017-12-18T00:00:00Z | 2017-12-27T00:00:00Z |
| 2017-12-19T00:00:00Z | 2017-12-27T00:00:00Z |
| 2017-12-20T00:00:00Z | 2017-12-27T00:00:00Z |
| 2017-12-21T00:00:00Z | 2017-12-28T00:00:00Z |
| 2017-12-22T00:00:00Z | 2017-12-29T00:00:00Z |
| 2017-12-23T00:00:00Z | 2017-12-30T00:00:00Z |
| 2017-12-24T00:00:00Z | (null) |
| 2017-12-25T00:00:00Z | (null) |
| 2017-12-26T00:00:00Z | (null) |
| 2017-12-27T00:00:00Z | (null) |
| 2017-12-28T00:00:00Z | (null) |
| 2017-12-29T00:00:00Z | (null) |
| 2017-12-30T00:00:00Z | (null) |
| 2017-12-31T00:00:00Z | (null) |
| 2018-01-01T00:00:00Z | (null) |
given holidays table:
with holidays as (
select to_date('10-DEC-17','dd-MON-YY') as mydate , 'Sun' as myday, 0 holiday from dual
union all
select to_date('11-DEC-17','dd-MON-YY') as mydate , 'Mon' as myday, 0 holiday from dual
union all
select to_date('12-DEC-17','dd-MON-YY') as mydate , 'Tue' as myday, 0 holiday from dual
union all
select to_date('13-DEC-17','dd-MON-YY') as mydate , 'Wed' as myday, 0 holiday from dual
union all
select to_date('14-DEC-17','dd-MON-YY') as mydate , 'Thu' as myday, 0 holiday from dual
union all
select to_date('15-DEC-17','dd-MON-YY') as mydate , 'Fri' as myday, 0 holiday from dual
union all
select to_date('16-DEC-17','dd-MON-YY') as mydate , 'Sat' as myday, 0 holiday from dual
union all
select to_date('17-DEC-17','dd-MON-YY') as mydate , 'Sun' as myday, 0 holiday from dual
union all
select to_date('18-DEC-17','dd-MON-YY') as mydate , 'Mon' as myday, 0 holiday from dual
union all
select to_date('19-DEC-17','dd-MON-YY') as mydate , 'Tue' as myday, 0 holiday from dual
union all
select to_date('20-DEC-17','dd-MON-YY') as mydate , 'Wed' as myday, 0 holiday from dual
union all
select to_date('21-DEC-17','dd-MON-YY') as mydate , 'Thu' as myday, 0 holiday from dual
union all
select to_date('22-DEC-17','dd-MON-YY') as mydate , 'Fri' as myday, 0 holiday from dual
union all
select to_date('23-DEC-17','dd-MON-YY') as mydate , 'Sat' as myday, 0 holiday from dual
union all
select to_date('24-DEC-17','dd-MON-YY') as mydate , 'Sun' as myday, 0 holiday from dual
union all
select to_date('25-DEC-17','dd-MON-YY') as mydate , 'Mon' as myday, 1 holiday from dual
union all
select to_date('26-DEC-17','dd-MON-YY') as mydate , 'Tue' as myday, 1 holiday from dual
union all
select to_date('27-DEC-17','dd-MON-YY') as mydate , 'Wed' as myday, 0 holiday from dual
union all
select to_date('28-DEC-17','dd-MON-YY') as mydate , 'Thu' as myday, 0 holiday from dual
union all
select to_date('29-DEC-17','dd-MON-YY') as mydate , 'Fri' as myday, 0 holiday from dual
union all
select to_date('30-DEC-17','dd-MON-YY') as mydate , 'Sat' as myday, 0 holiday from dual
union all
select to_date('31-DEC-17','dd-MON-YY') as mydate , 'Sun' as myday, 0 holiday from dual
union all
select to_date('01-JAN-18','dd-MON-YY') as mydate , 'Mon' as myday, 1 holiday from dual),
Here I find working days and then use first row to get the desired date
working as (
select holidays.*, case when myday='Sun' or holiday=1
then 0 else 1 end as working_day from holidays
)
select mydate from (
select mydate from working where
mydate>=to_date('21/12/2017','dd/mm/rrrr')+7
order by (case when working_day=0 then null else mydate end) asc nulls last
) where rownum=1