【问题标题】:How to merge two versioned tables?如何合并两个版本化的表?
【发布时间】:2020-03-08 21:49:05
【问题描述】:

有两个表:

Table1(id, p1, p2, date_from, date_to):

(1, 'z', 55, '01.05.2010 12:30:20', '17.05.2010 13:10:14'),
(1, 'c', null, '17.05.2010 13:10:15', '18.01.2010 04:13:15'),
(1, 'c', 25, '18.01.2010 04:13:16', '01.01.9999 00:00:00');

Table2(id, p3, date_from, date_to):

(1, 15, '01.04.2010 12:30:20', '02.05.2010 13:10:14'),
(1, 35, '02.05.2010 13:10:15', '01.01.9999 00:00:00');

在合并2个表时,必须有字段包含每个参数的更改历史记录。那就是:

选择(id,p1,p2,p3,date_from,date_to):

(1, null, null, 10, '01.04.2010 12:30:20', '01.05.2010 12:30:19'),
(1, 'z', 55, 15, '01.05.2010 12:30:20', '02.05.2010 13:10:14'),
(1, 'z', 55, 35, '02.05.2010 13:10:15', '17.05.2010 13:10:14'),
(1, 'c', null, 35, '17.05.2010 13:10:15', '18.01.2010 04:13:15'),
(1, 'c', 25, 35, '18.01.2010 04:13:16', '01.01.9999 00:00:00');

如果两个表中的记录在时间上(或部分时间)不重叠,则应输出 null。

到目前为止我已经完成了:

select 
    a.id, 
    case 
        when a.date_to between b.date_from and b.date_to 
            or a.date_from between b.date_from and b.date_to 
        then p1 
    end as p1,
    case 
        when a.date_to between b.date_from and b.date_to 
            or a.date_from between b.date_from and b.date_to 
        then p2 
    end as p2, 
    p3, 
    a.date_from as af, 
    b.date_from as bf, 
    a.date_to as at, 
    b.date_to as bt 
from 
    Table1 a 
    left join Table2 b 
on 
    a.id=b.id 
    and (b.date_from between a.date_from and a.date_to 
         or a.date_from between b.date_from and b.date_to)

【问题讨论】:

  • Table1 第 2 行中的样本数据的 to 日期早于 from 日期...
  • 您的预期结果在第一行中p3 的值也有误。

标签: sql database postgresql


【解决方案1】:

您可以通过首先使用一系列 CTE 计算每个 id 值的所有范围来获得所需的结果:

WITH dates_from AS (
  SELECT id, date_from FROM Table1
  UNION
  SELECT id, date_to + interval '1 second' FROM Table1
  UNION
  SELECT id, date_from FROM Table2
  UNION
  SELECT id, date_to + interval '1 second' FROM Table2
),
dates_to AS (
  SELECT id, date_to FROM Table1
  UNION
  SELECT id, date_from - interval '1 second' FROM Table1
  UNION
  SELECT id, date_to FROM Table2
  UNION
  SELECT id, date_from - interval '1 second' FROM Table2
),
ranges AS (
  SELECT df.id, date_from, MIN(date_to) AS date_to
  FROM dates_from df
  JOIN dates_to dt ON dt.id = df.id AND dt.date_to > df.date_from
  GROUP BY df.id, date_from
)
SELECT *
FROM ranges

对于您的示例数据(经过调整以使 Table1 的第 2 行中的 to 日期大于 from 日期),这给出:

id  date_from               date_to
1   2010-05-17 13:10:15     2011-01-18 04:13:15
1   2010-05-01 12:30:20     2010-05-02 13:10:14
1   2010-04-01 12:30:20     2010-05-01 12:30:19
1   2010-05-02 13:10:15     2010-05-17 13:10:14
1   2011-01-18 04:13:16     9999-01-01 00:00:00

然后可以将此表LEFT JOINed 到两个表(在重叠范围上)以提取每个范围的适当p1p2p3 值:

SELECT r.id,
       t1.p1, t1.p2, t2.p3,
       r.date_from, r.date_to
FROM ranges r
LEFT JOIN Table1 t1 ON t1.id = r.id AND t1.date_from <= r.date_to and t1.date_to >= r.date_from
LEFT JOIN Table2 t2 ON t2.id = r.id AND t2.date_from <= r.date_to and t2.date_to >= r.date_from
ORDER BY r.id, r.date_from

输出:

id  p1  p2  p3  date_from               date_to
1           15  2010-04-01 12:30:20     2010-05-01 12:30:19
1   z   55  15  2010-05-01 12:30:20     2010-05-02 13:10:14
1   z   55  35  2010-05-02 13:10:15     2010-05-17 13:10:14
1   c       35  2010-05-17 13:10:15     2011-01-18 04:13:15
1   c   25  35  2011-01-18 04:13:16     9999-01-01 00:00:00

Demo (including values for a second id) on dbfiddle

【讨论】:

  • 谢谢,但是没有2011,换表不好
  • @Sergey 我将 2010 更改为 2011,因为对于这一行:(1, 'c', null, '17.05.2010 13:10:15', '18.01.2010 04:13:15'), to 日期在 from 日期之前,这没有意义。
【解决方案2】:

试试这个方法:

select COALESCE(t1.id,t2.id) as id,
t1.p1,t1.p2,t2.p3,
COALESCE(t1.date_from,t2.date_from) as date_from,
COALESCE(t1.date_to,t2.date_to) as date_to
from Table1 t1
FULL OUTER JOIN Table2 t2 ON t1.id = t2.id and t1.date_from= t2.date_from and t1.date_to= t2.date_to;

【讨论】:

  • 提示:使用coalesce() 代替您的大小写表达式。
猜你喜欢
  • 1970-01-01
  • 2018-03-07
  • 1970-01-01
  • 2019-11-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-18
相关资源
最近更新 更多