joining temporal tables in oracle - sql

I am looking for better solutions to a fairly generic problem with temporal tables.
say we have
table_a (some_value int, date_from date, date_to date)
and a series of similar tables table_b, table_c, ...
the actual system is an HR system that tracks persons, contracts, assignments, salaries, ... all they are all dated in the way described above.
I need to join such tables (e.g. imagine getting all rows with the same some_value) and return the period for which such join is valid (that is all the rows overlap over such period).
with two tables it is easy (ignore NULL values for the time being)
select a.some_value, greatest(a.date_from, b.date_from) date_from, least(a.date_to, b.date_to) date_to
from table_a a join table_b b on a.some_value = b.some_value
where a.date_from < b.date_to and b.date_from < a.date_to
this become quadratically harder with more tables, because with three table (A, B, C) you need to check the overlap between A and B, B and C, C and A. with N tables this grows as N^2.
so I have written a pl/sql pipelined function (call it dated_join) that given two intervals it returns one row with the overlapping period or nothing if it doesn't overlap
so I can have for three tables
select a.some_value, period_b.date_from, period_b.date_to
from table_a a
join table_b b on a.some_value = b.some_value
join table(dated_join(a.date_from, a.date_to, b.date_from, b.date_to)) period_a
join table_c c on a.some_value = c.some_value
join table(dated_join(period_a.date_from, period_a.date_to, c.date_from, c.date_to)) period_b
this scales linearly to N values, because each period is joined only with the previous one and it carries forward the overlapping period.
question: is it possible to make this strategy work with OUTER JOINs? I cannot find any half-decent solution.
is there anything in the SQL:2011 temporal extensions that would help with this?
thanks for your help

If you want to join multiple table so that they all overlap the same period then you can use GREATEST and LEAST:
SELECT t1.date_from AS t1_from,
t2.date_from AS t2_from,
t3.date_from AS t3_from,
t4.date_from AS t4_from,
t1.date_to AS t1_to,
t2.date_to AS t2_to,
t3.date_to AS t3_to,
t4.date_to AS t4_to
FROM table1 t1
INNER JOIN table2 t2
ON ( t1.date_to > t2.date_from
AND t1.date_from < t2.date_to )
INNER JOIN table3 t3
ON ( LEAST( t1.date_to, t2.date_to ) > t3.date_from
AND GREATEST( t1.date_from, t2.date_from ) < t3.date_to )
INNER JOIN table4 t4
ON ( LEAST( t1.date_to, t2.date_to, t3.date_to ) > t4.date_from
AND GREATEST( t1.date_from, t2.date_from, t3.date_from ) < t4.date_to );
Which, for the sample data:
CREATE TABLE table1 ( date_from, date_to ) AS
SELECT DATE '2020-01-01', DATE '2020-01-10' FROM DUAL UNION ALL
SELECT DATE '2020-02-10', DATE '2020-02-15' FROM DUAL UNION ALL
SELECT DATE '2020-03-15', DATE '2020-03-18' FROM DUAL;
CREATE TABLE table2 ( date_from, date_to ) AS
SELECT DATE '2020-01-05', DATE '2020-01-15' FROM DUAL UNION ALL
SELECT DATE '2020-02-09', DATE '2020-02-16' FROM DUAL UNION ALL
SELECT DATE '2020-03-16', DATE '2020-03-18' FROM DUAL;
CREATE TABLE table3 ( date_from, date_to ) AS
SELECT DATE '2020-01-01', DATE '2020-01-02' FROM DUAL UNION ALL
SELECT DATE '2020-01-09', DATE '2020-01-16' FROM DUAL UNION ALL
SELECT DATE '2020-02-08', DATE '2020-02-17' FROM DUAL UNION ALL
SELECT DATE '2020-03-15', DATE '2020-03-17' FROM DUAL;
CREATE TABLE table4 ( date_from, date_to ) AS
SELECT DATE '2020-01-02', DATE '2020-01-12' FROM DUAL UNION ALL
SELECT DATE '2020-02-08', DATE '2020-02-17' FROM DUAL UNION ALL
SELECT DATE '2020-03-16', DATE '2020-03-19' FROM DUAL;
Outputs:
T1_FROM | T2_FROM | T3_FROM | T4_FROM | T1_TO | T2_TO | T3_TO | T4_TO
:-------- | :-------- | :-------- | :-------- | :-------- | :-------- | :-------- | :--------
15-MAR-20 | 16-MAR-20 | 15-MAR-20 | 16-MAR-20 | 18-MAR-20 | 18-MAR-20 | 17-MAR-20 | 19-MAR-20
10-FEB-20 | 09-FEB-20 | 08-FEB-20 | 08-FEB-20 | 15-FEB-20 | 16-FEB-20 | 17-FEB-20 | 17-FEB-20
01-JAN-20 | 05-JAN-20 | 09-JAN-20 | 02-JAN-20 | 10-JAN-20 | 15-JAN-20 | 16-JAN-20 | 12-JAN-20
and, if you want it so that there is an overlap with any part of the ranges, then swap the GREATEST and LEAST:
SELECT t1.date_from AS t1_from,
t2.date_from AS t2_from,
t3.date_from AS t3_from,
t4.date_from AS t4_from,
t1.date_to AS t1_to,
t2.date_to AS t2_to,
t3.date_to AS t3_to,
t4.date_to AS t4_to
FROM table1 t1
INNER JOIN table2 t2
ON ( t1.date_to > t2.date_from
AND t1.date_from < t2.date_to )
INNER JOIN table3 t3
ON ( GREATEST( t1.date_to, t2.date_to ) > t3.date_from
AND LEAST( t1.date_from, t2.date_from ) < t3.date_to )
INNER JOIN table4 t4
ON ( GREATEST( t1.date_to, t2.date_to, t3.date_to ) > t4.date_from
AND LEAST( t1.date_from, t2.date_from, t3.date_from ) < t4.date_to );
Which outputs:
T1_FROM | T2_FROM | T3_FROM | T4_FROM | T1_TO | T2_TO | T3_TO | T4_TO
:-------- | :-------- | :-------- | :-------- | :-------- | :-------- | :-------- | :--------
15-MAR-20 | 16-MAR-20 | 15-MAR-20 | 16-MAR-20 | 18-MAR-20 | 18-MAR-20 | 17-MAR-20 | 19-MAR-20
10-FEB-20 | 09-FEB-20 | 08-FEB-20 | 08-FEB-20 | 15-FEB-20 | 16-FEB-20 | 17-FEB-20 | 17-FEB-20
01-JAN-20 | 05-JAN-20 | 01-JAN-20 | 02-JAN-20 | 10-JAN-20 | 15-JAN-20 | 02-JAN-20 | 12-JAN-20
01-JAN-20 | 05-JAN-20 | 09-JAN-20 | 02-JAN-20 | 10-JAN-20 | 15-JAN-20 | 16-JAN-20 | 12-JAN-20
db<>fiddle here

If you want all values from tables that overlap with the timeperiod, then you seem to want:
select *
from table_a a left join
table_b b
on b.date_from < a.date_to and
b.date_to > a.date_from left join
table_c c
on c.date_from < a.date_to and
c.date_to > a.date_from
where a.value = ?
I don't see any "quadratic" complexity. Exactly the same join conditions are used for each new table.

improving on the solution given above by #MT0 we can use a different overlap test
GREATEST(t1.date_from, t2.date_from) < LEAST(t1.date_to, t2.date_to)
and extend it to any number of tables
GREATEST(t1.date_from, t2.date_from, t3.date_from, t4.date_from, ...)
< LEAST(t1.date_to, t2.date_to, t3.date_to, t4.date_to, ...)
the inner join can then be replaced by one single test
SELECT t1.date_from AS t1_from,
t2.date_from AS t2_from,
t3.date_from AS t3_from,
t4.date_from AS t4_from,
t1.date_to AS t1_to,
t2.date_to AS t2_to,
t3.date_to AS t3_to,
t4.date_to AS t4_to
FROM table1 t1
CROSS JOIN table2 t2
CROSS JOIN table3 t3
CROSS JOIN table4 t4
WHERE
GREATEST(t1.date_from, t2.date_from, t3.date_from, t4.date_from)
< LEAST(t1.date_to, t2.date_to, t3.date_to, t4.date_to);
while the outer join becomes
SELECT t1.date_from AS t1_from,
t2.date_from AS t2_from,
t3.date_from AS t3_from,
t4.date_from AS t4_from,
t1.date_to AS t1_to,
t2.date_to AS t2_to,
t3.date_to AS t3_to,
t4.date_to AS t4_to
FROM table1 t1
left outer join table2 t2 on
greatest(t1.date_from, t2.date_from) < least(t1.date_to, t2.date_to)
left outer join table3 t3 on
greatest(t1.date_from, t2.date_from, t3.date_from) < least(t1.date_to, t2.date_to, t3.date_to)
left outer join table4 t4 on
greatest(t1.date_from, t2.date_from, t3.date_from, t4.date_from) < least(t1.date_to, t2.date_to, t3.date_to, t4.date_to);
https://dbfiddle.uk/?rdbms=oracle_18&fiddle=e43b7c58208b7ea85af24ec0ec0ca4a7

Related

SQL Merge rows instead of UNION ALL

I'm doing a UNION ALL to get the results as shows in the table below. This approach is causing to have unnecessary rows. The three columns DESK, SEGMENT and SUPERVISOR are independent and have no relationship.
Code
SELECT ID, DESK, '' as SEGMENT, '' as SUPERVISOR FROM myTable1
UNION ALL
SELECT ID, '' AS DESK, SEGMENT, '' as SUPERVISOR FROM myTable2
UNION ALL
SELECT ID, '' AS DESK, '' as SEGMENT, SUPERVISOR FROM myTable3
Result:
+------+------------+---------+------------+
| ID | DESK | SEGMENT | SUPERVISOR | TOTAL ENTRIES
+------+------------+---------+------------+
| 4782 | OIL & GAS | | | 23
+------+------------+---------+------------+
| 4782 | AUTOMOTIVE | | | 23
+------+------------+---------+------------+
| 4782 | | GLOBAL | | 23
+------+------------+---------+------------+
| 4782 | | | DANIEL | 23
+------+------------+---------+------------+
| 4782 | | | JAMES | 23
+------+------------+---------+------------+
How can I query to get the below result?
Expected Result:
+------+------------+---------+------------+
| ID | DESK | SEGMENT | SUPERVISOR | TOTAL ENTRIES
+------+------------+---------+------------+
| 4782 | OIL & GAS | GLOBAL | DANIEL | 23
+------+------------+---------+------------+
| 4782 | AUTOMOTIVE | | JAMES | 23
+------+------------+---------+------------+
You can use ROW_NUMBER() analytic function with partitioned by ID column along with FULL OUTER JOIN for those three tables like this :
SELECT NVL(NVL(t2.ID,t3.ID),t1.ID) AS ID, desk, segment, supervisor
FROM ( SELECT t1.*, ROW_NUMBER() OVER (PARTITION BY ID ORDER BY 0) AS rn FROM myTable1 t1 ) t1
FULL JOIN ( SELECT t2.*, ROW_NUMBER() OVER (PARTITION BY ID ORDER BY 0) AS rn FROM myTable2 t2 ) t2
ON t2.ID = t1.ID AND t2.rn = t1.rn
FULL JOIN ( SELECT t3.*, ROW_NUMBER() OVER (PARTITION BY ID ORDER BY 0) AS rn FROM myTable3 t3 ) t3
ON t3.ID = t1.ID AND t3.rn = t1.rn;
ID DESK SEGMENT SUPERVISOR
---- ---------- ------- ----------
4782 AUTOMOTIVE GLOBAL JAMES
4782 OIL & GAS DANIEL
Demo
P.S: I left ORDER BY 0 as ORDER BY option is mandatory for ROW_NUMBER(), you can replace zero with a proper column or identifier for you.
You can try this:
SELECT table1.ID, table1.DESK, table2.SEGMENT, (select SUPERVISOR from (select SUPERVISOR, ROWNUM AS RN FROM table3) WHERE RN = 1) SUPERVISOR
FROM table1 JOIN table2 on table1.ID = table2.ID
WHERE table1.DESK = 'OIL & GAS'
UNION ALL
SELECT table1.ID, table1.DESK, null SEGMENT, (select SUPERVISOR from (select SUPERVISOR, ROWNUM AS RN FROM table3) WHERE RN = 2) SUPERVISOR
FROM table1 JOIN table2 on table1.ID = table2.ID
WHERE table1.DESK = 'AUTOMOTIVE'
https://dbfiddle.uk/?rdbms=oracle_11.2&fiddle=c5594bb1d99579611d2669f6bab675a2
You can try a query like the one below. I don't know where the 23 is coming from so I did not factor it into the query, but if it is a column in one of the three tables, similar logic can be used to add it to the results.
Query
WITH
table1 (id, desk)
AS
(SELECT 4782, 'OIL & GAS' FROM DUAL
UNION ALL
SELECT 4782, 'AUTOMOTIVE' FROM DUAL),
table2 (id, segment) AS (SELECT 4782, 'GLOBAL' FROM DUAL),
table3 (id, supervisor)
AS
(SELECT 4782, 'DANIEL' FROM DUAL
UNION ALL
SELECT 4782, 'JAMES' FROM DUAL)
SELECT *
FROM (SELECT t1.id,
CASE WHEN t1.desk = LAG (t1.desk) OVER (ORDER BY t1.desk) THEN NULL ELSE t1.desk END
AS desk,
CASE
WHEN t2.segment = LAG (t2.segment) OVER (ORDER BY t2.segment) THEN NULL
ELSE t2.segment
END
AS segment,
CASE
WHEN t3.supervisor = LAG (t3.supervisor) OVER (ORDER BY t3.supervisor) THEN NULL
ELSE t3.supervisor
END
AS supervisor
FROM table1 t1, table2 t2, table3 t3
WHERE t1.id = t2.id AND t1.id = t3.id)
WHERE desk IS NOT NULL OR segment IS NOT NULL OR supervisor IS NOT NULL;
Result
ID DESK SEGMENT SUPERVISOR
_______ _____________ __________ _____________
4782 AUTOMOTIVE GLOBAL DANIEL
4782 OIL & GAS JAMES

OracleSQL -> Sort View by three levels

I created the following View in my Oracle SQL Developer:
The Results are not sorted In the way I need them.
I need to sort the results like this:
So If you would draw it as a graph with a depth of three
It would look like this:
The Maximum level depth is three. I tried different approaches with select connect_by_root, but it does not work. ORDER BY Also does no work because of the tree structure of the data.
Does anyone have a tip for me ?
You could use a left join:
select t.*
from t left join
t tp
on tp.id = t.parent_id
order by coalesce(tp.parent_id, t.parent_id, t.id),
coalesce(t.parent_id, t.id),
t.id
This assumes that the parent ids are smaller than the id, which seems to be true for your data. It is not a necessary assumption, but it simplifies the logic.
With a LEFT self join and conditional sorting:
select t1.*
from tablename t1
left join tablename t2 on t2.id = t1.parent_id
order by coalesce(t2.parent_id, t1.parent_id, t1.id),
case when t1.parent_id is null then 0 else 1 end,
case when t2.parent_id is not null then t1.parent_id else t1.id end,
case when t2.parent_id is null then 0 else 1 end,
t1.id
See the demo.
Results:
> ID | PARENT_ID | LEVEL
> -----: | --------: | :--------
> 29101 | null | LVL_ONE
> 153799 | 29101 | LVL_TWO
> 153800 | 153799 | LVL_THREE
> 153801 | 153799 | LVL_THREE
> 153803 | 29101 | LVL_TWO
> 153804 | 153803 | LVL_THREE
> 153802 | null | LVL_ONE
> 153805 | 153802 | LVL_TWO
> 153806 | 153805 | LVL_THREE
If the view you have used is a hierarchial query, then you have an option to "order siblings by", i would suggest you use that
Here is an example
https://oracle-base.com/articles/misc/hierarchical-queries
drop table a1;
CREATE TABLE a1
as
SELECT 153804 as ID, 153803 as PARENT_ID
FROM DUAL
UNION ALL
SELECT 153801, 153799
FROM DUAL
UNION ALL
SELECT 153803, 29101
FROM DUAL
UNION ALL
SELECT 29101, NULL
FROM DUAL
UNION ALL
SELECT 153802, NULL
FROM DUAL
UNION ALL
SELECT 153805, 153802
FROM DUAL
UNION ALL
SELECT 153806, 153805
FROM DUAL
UNION ALL
SELECT 153800, 153799
FROM DUAL
UNION ALL
SELECT 153799, 29101
FROM DUAL
;
select id, parent_id, level, 'LEVEL_' || level as level_desc
from a1
start with parent_id is null
connect by parent_id = prior(id)
order siblings by id
;

how to combine multiple column of different table into one table

I have three tables a, b and c and need to arrange these table data as target table and all of these tables (a, b, c) are not in database they are fetched from from single table using queries as alias and need to arrange these tables into target table using query. How to do that
table a | table b | table c
| |
id | a_vals | id | b_vals | id | c_vals
------------ | -------------- | -------------
1 | 123 | 1 | 123 | 1 | 123
2 | 124 | 2 | 142 | 2 | 142
3 | 234 | 4 | 234 | 5 | 234
target table
id | a_val| b_val| c_val
1 | 123 | 123 | 123
2 | 124 | 142 | 142
3 | 234 | - | -
4 | - | 234 | -
5 | - | | 234
Since a, b and c share the same name for the column you want to join, you could use "USING" to avoid duplicate keys in the resulting table:
SELECT *
FROM a
FULL OUTER JOIN b USING (id)
FULL OUTER JOIN c USING (id);
Alternativly, since a, b and c's value column all have distinct names you could use NATURAL JOIN:
SELECT *
FROM a
NATURAL FULL OUTER JOIN b
NATURAL FULL OUTER JOIN c;
Be careful not to accidentally rename any of the other columns tho, since natural join removes any duplicate columns.
You can also omit the "OUTER" keyword if you like, but i would leave it for clarity, (since LEFT, RIGHT, and FULL imply an outer join).
See https://www.postgresql.org/docs/10/static/queries-table-expressions.html for details
Please try this:
select aa.id, a_val, b_val, c_val from
(select distinct id as id from table_a
union
select distinct id as id from table_b
union
select distinct id as id from table_c)aa
left join (select id, a_val from table_a)bb on aa.id = bb.id
left join (select id, b_val from table_b)cc on aa.id = cc.id
left join (select id, c_val from table_c)dd on aa.id = dd.id order by aa.id;
Try this code
SELECT
CASE
WHEN t1.id IS not null THEN t1.id
WHEN t2.id IS not null THEN t2.id
ELSE t3.id
END
AS id,
t1.a_vals AS a_val,
t2.b_vals as b_val,
t3.c_vals as c_val
FROM a t1 FULL OUTER JOIN b t2 ON t1.id=t2.id FULL OUTER JOIN c t3 ON
CASE
WHEN t1.id IS not null THEN t1.id
ELSE t2.id
END = t3.id
OR
SELECT COALESCE(t1.id, t2.id, t3.id) as id ,
t1.a_vals AS a_val,
t2.b_vals as b_val,
t3.c_vals as c_val
FROM a t1 FULL OUTER JOIN b t2 ON t1.id=t2.id
FULL OUTER JOIN c t3 ON COALESCE(t1.id, t2.id) = t3.id
You are looking for the ANSI-standard FULL OUTER JOIN:
select coalesce(a.id, b.id, c.id) as id, a.val, b.val, c.val
from a full join
b
on a.id = b.id full join
c
on c.id = coalesce(a.id, b.id);
You can also implement this with union all/group by:
select id, max(a_val) as a_val, max(b_val) as b_val, max(c_val) as c_val
from ((select id, val as a_val, null as b_val, null as c_val
from a
) union all
(select id, null as a_val, val as b_val, null as c_val
from b
) union all
(select id, null as a_val, null as b_val, val as c_val
from c
)
) abc
group by id;
This is probably better done in the 'front end' e.g. this is the kind of thing a reporting tool is designed for.
Avoiding nulls and outer joins (because they by definition produce nulls):
SELECT a_val, b_val, c_val
FROM a
NATURAL JOIN b
NATURAL JOIN c
UNION
SELECT a_val, '-' AS b_val, '-' AS c_val
FROM a
WHERE id NOT IN ( SELECT id FROM b )
AND id NOT IN ( SELECT id FROM c )
UNION
SELECT '-' AS a_val, b_val, '-' AS c_val
FROM b
WHERE id NOT IN ( SELECT id FROM a )
AND id NOT IN ( SELECT id FROM c )
UNION
SELECT '-' AS a_val, '-' AS b_val, c_val
FROM c
WHERE id NOT IN ( SELECT id FROM a )
AND id NOT IN ( SELECT id FROM b );

Oracle Join tables with range of dates in first table and dates in second table

I have two tables in an Oracle database:
The first table has a date range and I need help in writing a SQL query to find all the records from second table as in the result table below. The first four digits in the date is year and last two are session (10-Fall; 20-Spring; 30-Summer).
1) Table1
seqnum | min_date| max_date |c_id
1 | 201210 | 201210 | 100
1 | 201220 | 201330 | 150
1 | 201410 | 201410 | 200
2) Table2
seqnum | b_date
1 | 201210
1 | 201220
1 | 201230
1 | 201310
1 | 201320
1 | 201330
1 | 201410
1 | 201420
1 | 201430
3) Result table
seqnum | b_date | c_id
1 | 201210 | 100
1 | 201220 | 150
1 | 201230 | 150
1 | 201310 | 150
1 | 201320 | 150
1 | 201330 | 150
1 | 201410 | 200
1 | 201420 | 200
1 | 201430 | 200
If Table1 have only the first record then all the dates in Table2 must be associated with c_id 100 only.
To do this as simply as possible:
select t2.seqnum, t2.b_date, coalesce(t1.c_id, t3.max_id) as c_id
from table2 t2
left outer join table1 t1
on t2.b_date between t1.min_date and t1.max_date
cross join (select max(c_id) as max_id from table1) t3
order by t1.c_id, t2.b_date
SQLFiddle here
Share and enjoy.
Fiddle: http://sqlfiddle.com/#!4/45c72/10/0
select t2.seqnum,
t2.b_date,
case when t2.b_date < min_rg then x.c_id
when t2.b_date > max_rg then y.c_id
else t1.c_id
end as c_id
from (select min(min_date) as min_rg, max(max_date) as max_rg from table1) z
join table1 x
on x.min_date = z.min_rg
join table1 y
on y.max_date = z.max_rg
cross join table2 t2
left join table1 t1
on t2.b_date between t1.min_date and t1.max_date
order by b_date
When B_DATE on table2 is lower than the first MIN_DATE on table1 it will show C_ID from table1 of the lowest MIN_DATE (100 in your case, right now).
When B_DATE on table2 is higher than the last MAX_DATE on table1 it will show C_ID from table1 of the highest MAX_DATE (200 in your case, right now).
with table1 as (
select 1 seqnum, 201210 min_date, 201210 max_date, 100 c_id from dual
union all select 1, 201220, 201330, 150 from dual
union all select 1, 201410, 201410, 200 from dual
),
table2 as (
select 1 seqnum, 201210 b_date from dual
union all select 1, 201220 from dual
union all select 1, 201230 from dual
union all select 1, 201310 from dual
union all select 1, 201320 from dual
union all select 1, 201330 from dual
union all select 1, 201410 from dual
union all select 1, 201420 from dual
union all select 1, 201430 from dual
),
semi as (
select t2.seqnum, t2.b_date, t1.c_id,
-- since Oracle 11g
--lag(c_id IGNORE NULLS) over(partition by t2.seqnum order by t2.b_date) prev_c_id
last_value(c_id IGNORE NULLS) over(partition by t2.seqnum
order by t2.b_date
ROWS BETWEEN UNBOUNDED PRECEDING
AND 1 PRECEDING) prev_c_id
from table2 t2 left join table1 t1
on t2.seqnum = t1.seqnum and t2.b_date between t1.min_date and t1.max_date
)
select seqnum, b_date, nvl(c_id, prev_c_id) c_id
from semi;
This can be done with analytic functions.
Left join Table2 with Table1
Calculate previous (rows are ordered by b_date) not null value of c_id with (LAG or LAST_VALUE + windowing) for each seqnum.
If c_id is NULL then show the first not null previous value.

PostgreSQL use value from previous row if missing

I have a following query:
WITH t as (
SELECT date_trunc('hour', time_series) as trunc
FROM generate_series('2013-02-27 22:00'::timestamp, '2013-02-28 2:00',
'1 hour') as time_series
GROUP BY trunc
ORDER BY trunc
)
SELECT DISTINCT ON(trunc) trunc, id
FROM t
LEFT JOIN (
SELECT id, created, date_trunc('hour', created) as trunc_u
FROM event
ORDER BY created DESC
) u
ON trunc = trunc_u
which yields the following result:
"2013-02-27 22:00:00";
"2013-02-27 23:00:00";2
"2013-02-28 00:00:00";5
"2013-02-28 01:00:00";
"2013-02-28 02:00:00";
Table event has id, created and some other columns, but only those are relevant here. The query above gives me id of last event generated per given trunc time period (thanks to DISTINCT ON I get a nice aggregation per period).
Now, this query yields NULL if no events happened in given time period. I would like it to return the previous available id, even if it is from different time period. I.e.:
"2013-02-27 22:00:00";0
"2013-02-27 23:00:00";2
"2013-02-28 00:00:00";5
"2013-02-28 01:00:00";5
"2013-02-28 02:00:00";5
I am sure I am missing some easy way to accomplish this. Any advice?
You ca mix a self join and windows functions
Simplifying I take this table with this sample values:
create table t ( a int, b int);
insert into t values
( 1, 1),
( 2, Null),
( 3, Null),
( 4, 2 ),
( 5, Null),
( 6, Null);
In your query a is trunc_u and b is your id.
The query is:
with cte as (
select
t1.a,
coalesce( t1.b, t2.b, 0) as b,
rank() OVER
(PARTITION BY t1.a ORDER BY t2.a DESC) as pos
from t t1
left outer join t t2
on t2.b is not null and
t2.a < t1.a
)
select a, b
from cte
where pos = 1;
And results:
| A | B |
---------
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 2 |
| 5 | 2 |
| 6 | 2 |
Try:
WITH t as (
SELECT time_series as trunc
FROM generate_series('2013-02-27 22:00'::timestamp, '2013-02-28 2:00',
'1 hour') as time_series
)
SELECT DISTINCT ON(t.trunc) t.trunc, e.id
FROM t
JOIN event e
ON e.created < t.trunc
ORDER BY t.trunc, e.created DESC
If it is too slow - tell me. I will give you a faster query.