在 oracle 中加入时态表

Posted

技术标签:

【中文标题】在 oracle 中加入时态表【英文标题】:joining temporal tables in oracle 【发布时间】:2021-01-08 16:43:05 【问题描述】:

我正在寻找更好的解决方案来解决一个相当普遍的时态表问题。

说我们有

table_a (some_value int, date_from date, date_to date)

还有一系列类似的表格table_btable_c、...

实际系统是一个 HR 系统,用于跟踪人员、合同、任务、工资……所有这些都以上述方式注明日期。

我需要加入这样的表(例如,假设所有行都具有相同的some_value)并返回这种加入有效的时间段(即所有行在这段时间内重叠)。

有两个表很容易(暂时忽略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

随着表的增加,这变得更加困难,因为对于三个表(A、B、C),您需要检查 A 和 B、B 和 C、C 和 A 之间的重叠。对于 N 个表,它会随着 N^2 增长.

所以我编写了一个 pl/sql 流水线函数(称为 dated_join),给定两个间隔,它返回具有重叠周期的一行,如果不重叠则返回任何内容

所以我可以有三张桌子

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

这会线性缩放到 N 个值,因为每个周期只与前一个周期相连,并且会延续重叠的周期。

问题:是否可以使此策略与OUTER JOINs 一起使用?我找不到任何半体面的解决方案。

SQL:2011 临时扩展中有什么可以帮助解决这个问题吗?

感谢您的帮助

【问题讨论】:

样本数据和期望的结果会有所帮助。 【参考方案1】:

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

其中,对于样本数据:

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;

输出:

T1_FROM | T2_FROM | T3_FROM | T4_FROM | T1_TO | T2_TO | T3_TO | T4_TO :-------- | :-------- | :-------- | :-------- | :-------- | :-------- | :-------- | :-------- 20 年 3 月 15 日 | 20 年 3 月 16 日 | 20 年 3 月 15 日 | 20 年 3 月 16 日 | 20 年 3 月 18 日 | 20 年 3 月 18 日 | 20 年 3 月 17 日 | 20 年 3 月 19 日 20 年 2 月 10 日 | 20 年 2 月 9 日 | 20 年 2 月 8 日 | 20 年 2 月 8 日 | 20 年 2 月 15 日 | 20 年 2 月 16 日 | 20 年 2 月 17 日 | 20 年 2 月 17 日 20 年 1 月 1 日 | 20 年 1 月 5 日 | 20 年 1 月 9 日 | 20 年 1 月 2 日 | 20 年 1 月 10 日 | 20 年 1 月 15 日 | 20 年 1 月 16 日 | 20 年 1 月 12 日

并且,如果您希望它与范围的任何部分重叠,则交换 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 );

哪些输出:

T1_FROM | T2_FROM | T3_FROM | T4_FROM | T1_TO | T2_TO | T3_TO | T4_TO :-------- | :-------- | :-------- | :-------- | :-------- | :-------- | :-------- | :-------- 20 年 3 月 15 日 | 20 年 3 月 16 日 | 20 年 3 月 15 日 | 20 年 3 月 16 日 | 20 年 3 月 18 日 | 20 年 3 月 18 日 | 20 年 3 月 17 日 | 20 年 3 月 19 日 20 年 2 月 10 日 | 20 年 2 月 9 日 | 20 年 2 月 8 日 | 20 年 2 月 8 日 | 20 年 2 月 15 日 | 20 年 2 月 16 日 | 20 年 2 月 17 日 | 20 年 2 月 17 日 20 年 1 月 1 日 | 20 年 1 月 5 日 | 20 年 1 月 1 日 | 20 年 1 月 2 日 | 20 年 1 月 10 日 | 20 年 1 月 15 日 | 20 年 1 月 2 日 | 20 年 1 月 12 日 20 年 1 月 1 日 | 20 年 1 月 5 日 | 20 年 1 月 9 日 | 20 年 1 月 2 日 | 20 年 1 月 10 日 | 20 年 1 月 15 日 | 20 年 1 月 16 日 | 20 年 1 月 12 日

db小提琴here

【讨论】:

优秀的答案。我认为这是一个简洁的解决方案,其复杂性呈线性增长。我不知道dbfiddle。谢谢。 其实不等于FROM table1 t1 INNER JOIN table2 t2 ON 1=1 INNER JOIN table3 t3 ON 1=1 INNER JOIN table4 t4 ON ( LEAST( t1.date_to, t2.date_to, t3.date_to ) &gt; t4.date_from AND GREATEST( t1.date_from, t2.date_from, t3.date_from ) &lt; t4.date_to ); @user103716 INNER JOIN table2 t2 ON 1=1CROSS JOIN table2 t2 相同。我认为是一样的,但我不确定,您需要检查解释计划,看看执行更严格的过滤连接是否比使用交叉连接和单个过滤条件更有效。【参考方案2】:

如果您想要表中与时间段重叠的所有值,那么您似乎想要:

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 = ?

我没有看到任何“二次”复杂性。每个新表都使用完全相同的连接条件。

【讨论】:

对于 2 个表,您需要 1 个联接。对于 3 个表,您需要 3 个连接。对于 4 个表,您需要 6 个连接(a-b、a-c、a-d、b-c、b-d、c-d)。对于 N 个表,它是 N * (N-1) / 2。我认为您的查询错过了 b 和 c 之间的连接。如果 A 与 B 和 C 重叠,则不保证 B 与 C 重叠。 @user103716 。 . .我不这么认为。您应该只关心与第一个表的重叠。【参考方案3】:

改进@MT0 上面给出的解决方案,我们可以使用不同的重叠测试

GREATEST(t1.date_from, t2.date_from) < LEAST(t1.date_to, t2.date_to)

并将其扩展到任意数量的表

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, ...)

然后可以用一个测试替换内部连接

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);

而外连接变成了

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

【讨论】:

以上是关于在 oracle 中加入时态表的主要内容,如果未能解决你的问题,请参考以下文章

在 Oracle SQL Query 中加入(INNER JOIN)本地 Excel 表 - VBA

在 oracle sql 中加入更新

在 Oracle SQL SELECT 语句中加入条件

如何在oracle的sql语句中加入判断条件

仅当为输入参数找到某个元数据时,才在游标中加入 Oracle

如何在mysql查询的更新语句中加入多个表[重复]