简体   繁体   English

在 oracle 中加入时态表

[英]joining temporal tables in oracle

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 , ...和一系列类似的表table_btable_c ,...

the actual system is an HR system that tracks persons, contracts, assignments, salaries, ... all they are all dated in the way described above.实际的系统是一个 HR 系统,用于跟踪人员、合同、任务、工资……所有这些都以上述方式注明日期。

I need to join such tables (eg 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).我需要加入这样的表(例如,想象得到所有具有相同some_value的行)并返回这种加入有效的时间段(即所有行在这段时间内重叠)。

with two tables it is easy (ignore NULL values for the time being)有两个表很容易(暂时忽略NULL值)

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.随着表的增多,这将变得更加困难,因为对于三个表(A、B、C),您需要检查 A 和 B、B 和 C、C 和 A 之间的重叠。对于 N 个表,这将增长为 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所以我写了一个 pl/sql 流水线 function (称为dated_join ),给定两个间隔,它返回一行有重叠的周期,如果不重叠,则返回任何内容

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.这线性地缩放到 N 个值,因为每个周期只与前一个周期相连,并且它延续了重叠的周期。

question: is it possible to make this strategy work with OUTER JOIN s?问题:是否可以使此策略与OUTER JOIN一起使用? I cannot find any half-decent solution.我找不到任何半体面的解决方案。

is there anything in the SQL:2011 temporal extensions that would help with this? SQL:2011 临时扩展中有什么可以帮助解决这个问题吗?

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 :如果您想加入多个表,以便它们都在同一时期重叠,那么您可以使用GREATESTLEAST

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 | T1_FROM | T2_FROM | T2_FROM | T3_FROM | T3_FROM | T4_FROM | T4_FROM | T1_TO | T1_TO | T2_TO | T2_TO | T3_TO | T3_TO | T4_TO:-------- |:-------- |:-------- |:-------- |:-------- |:-------- |:-------- |:-------- 15-MAR-20 | T4_TO:-------- |:-------- |:-------- |:-------- |:-------- |:-------- |:-------- |:-------- 15-MAR-20 | 16-MAR-20 | 20 年 3 月 16 日 | 15-MAR-20 | 20 年 3 月 15 日 | 16-MAR-20 | 20 年 3 月 16 日 | 18-MAR-20 | 20 年 3 月 18 日 | 18-MAR-20 | 20 年 3 月 18 日 | 17-MAR-20 | 20 年 3 月 17 日 | 19-MAR-20 10-FEB-20 | 20 年 3 月 19 日 20 年 2 月 10 日 | 09-FEB-20 | 20 年 2 月 9 日 | 08-FEB-20 | 20 年 2 月 8 日 | 08-FEB-20 | 20 年 2 月 8 日 | 15-FEB-20 | 20 年 2 月 15 日 | 16-FEB-20 | 20 年 2 月 16 日 | 17-FEB-20 | 20 年 2 月 17 日 | 17-FEB-20 01-JAN-20 | 20 年 2 月 17 日 20 年 1 月 1 日 | 05-JAN-20 | 20 年 1 月 5 日 | 09-JAN-20 | 20 年 1 月 9 日 | 02-JAN-20 | 20 年 1 月 2 日 | 10-JAN-20 | 20 年 1 月 10 日 | 15-JAN-20 | 20 年 1 月 15 日 | 16-JAN-20 | 20 年 1 月 16 日 | 12-JAN-20 20 年 1 月 12 日

and, if you want it so that there is an overlap with any part of the ranges, then swap the GREATEST and LEAST :并且,如果您希望它与范围的任何部分重叠,则交换GREATESTLEAST

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 | T1_FROM | T2_FROM | T2_FROM | T3_FROM | T3_FROM | T4_FROM | T4_FROM | T1_TO | T1_TO | T2_TO | T2_TO | T3_TO | T3_TO | T4_TO:-------- |:-------- |:-------- |:-------- |:-------- |:-------- |:-------- |:-------- 15-MAR-20 | T4_TO:-------- |:-------- |:-------- |:-------- |:-------- |:-------- |:-------- |:-------- 15-MAR-20 | 16-MAR-20 | 20 年 3 月 16 日 | 15-MAR-20 | 20 年 3 月 15 日 | 16-MAR-20 | 20 年 3 月 16 日 | 18-MAR-20 | 20 年 3 月 18 日 | 18-MAR-20 | 20 年 3 月 18 日 | 17-MAR-20 | 20 年 3 月 17 日 | 19-MAR-20 10-FEB-20 | 20 年 3 月 19 日 20 年 2 月 10 日 | 09-FEB-20 | 20 年 2 月 9 日 | 08-FEB-20 | 20 年 2 月 8 日 | 08-FEB-20 | 20 年 2 月 8 日 | 15-FEB-20 | 20 年 2 月 15 日 | 16-FEB-20 | 20 年 2 月 16 日 | 17-FEB-20 | 20 年 2 月 17 日 | 17-FEB-20 01-JAN-20 | 20 年 2 月 17 日 20 年 1 月 1 日 | 05-JAN-20 | 20 年 1 月 5 日 | 01-JAN-20 | 20 年 1 月 1 日 | 02-JAN-20 | 20 年 1 月 2 日 | 10-JAN-20 | 20 年 1 月 10 日 | 15-JAN-20 | 20 年 1 月 15 日 | 02-JAN-20 | 20 年 1 月 2 日 | 12-JAN-20 01-JAN-20 | 20 年 1 月 12 日 20 年 1 月 1 日 | 05-JAN-20 | 20 年 1 月 5 日 | 09-JAN-20 | 20 年 1 月 9 日 | 02-JAN-20 | 20 年 1 月 2 日 | 10-JAN-20 | 20 年 1 月 10 日 | 15-JAN-20 | 20 年 1 月 15 日 | 16-JAN-20 | 20 年 1 月 16 日 | 12-JAN-20 20 年 1 月 12 日

db<>fiddle here db<> 在这里摆弄

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改进@MT0 上面给出的解决方案,我们可以使用不同的重叠测试

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 https://dbfiddle.uk/?rdbms=oracle_18&fiddle=e43b7c58208b7ea85af24ec0ec0ca4a7

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM