如何组合两个同时更改的属性历史表

时间:2014-06-06 19:57:57

标签: mysql sql sql-server oracle teradata

我有两个表跟踪相同对象的不同属性历史记录集。表格可能如下所示:

T1:
ID    | VERSION_ST    | VERSION_END | Attr1 
-----------------------------------------------
1     | 2012-01-01    | 2013-05-07  | Red
1     | 2013-05-08    | 2014-04-01  | Blue
1     | 2014-04-02    | NULL        | Green

T2:
ID    | VERSION_ST    | VERSION_END | Attr2 
-----------------------------------------------
1     | 2012-01-01    | 2013-06-04  | Large
1     | 2013-06-05    | 2014-07-15  | Medium
1     | 2014-07-16    | NULL        | Large

如何编写将这些属性历史记录组合在一起的查询,以便我可以看到两个属性的准确版本开始和结束?

结果集可能如下所示:

ID    | VERSION_ST    | VERSION_END | Attr1    | Attr2   
-----------------------------------------------------------
1     | 2012-01-01    | 2013-05-07  | Red      | Large
1     | 2013-05-08    | 2013-06-04  | Blue     | Large    
1     | 2013-06-05    | 2014-04-01  | Blue     | Medium    
1     | 2014-04-02    | 2014-07-15  | Green    | Medium        
1     | 2014-07-16    | NULL        | Green    | Large    

当我尝试加入on T1.ID = T2.ID and T1.START between T2.START and T2.END时,只返回了三行,因此它无法准确跟踪T2中的更改。如果我扭转它也是一样的。不知道如何同时做两件事。

我可以访问各种数据库系统来完成这项工作,如果其中任何具有此功能,我会接受它作为答案。

6 个答案:

答案 0 :(得分:1)

这适用于您的示例数据,但在某些情况下可能会失败:

SELECT
   CASE WHEN t1.version_st < t2.version_st THEN t2.version_st ELSE t1.version_st END,
   CASE WHEN t1.version_end < t2.version_end THEN t1.version_end ELSE t2.version_end END,
   t1.attr1,
   t2.attr2
FROM t1 JOIN t2 
  ON T1.ID = T2.ID
 AND (t1.VERSION_ST, COALESCE(t1.VERSION_END, DATE '9999-12-31')) OVERLAPS
     (t2.VERSION_ST, COALESCE(t2.VERSION_END, DATE '9999-12-31'))

编辑: Teradata支持OVERLAPS(大多数DBMS不知道),但可以替换为:

FROM t1 JOIN t2 
  ON T1.ID = T2.ID
 AND t1.version_st < COALESCE(t2.version_end, DATE '9999-12-31')
 AND t2.version_st < COALESCE(t1.version_end, DATE '9999-12-31')

答案 1 :(得分:0)

您尝试过的是正确的,因为当您查看输出时,行3 蓝色|中等是不可能的,因为表1中的蓝色Version_st不在指定范围之间。 如果您尝试使用' ISNULL(T2.End,'9999-12-31'),您将获得4条记录的输出,假设null表示其处于活动状态。 如果是oracle使用NVL功能。

答案 2 :(得分:0)

on T1.ID = T2.ID and T1.START between T2.START and T2.END

本身不起作用,因为T1的结束可能落在T2的开始/结束之间。

尝试:

on T1.ID = T2.ID and (T1.START between T2.START and T2.END or T1.END between T2.START and T2.END)

要处理空值,请使用COALLESCE(date,CURRENT_DATE),以便我们将具有空日期的任何内容视为今天继续。在Oracle中,current_date是SYSDATE,在Sql Server中它是GETDATE()。

答案 3 :(得分:0)

我认为这可以满足您的需求:

select
t1.version_st as t1_start,
t1.version_end as t1_end,
t2.version_st as t2_start,
t2.version_end as t2_end,
t1.attr1,
t2.attr2
from
t1
full join t2
on T1.ID = T2.ID and 
((T1.version_st between T2.version_st and T2.version_end )
 or (T1.version_END between T2.version_st and T2.version_end))

我认为你需要完全加入,因为无法保证日期排成一行(如t2的最后一行)。

SQL Fiddle

答案 4 :(得分:0)

这似乎有效:

SELECT * FROM t1 a
INNER JOIN t2 b
on a.id = b.id and a.VERSION_ST between b.VERSION_ST and b.VERSION_END

UNION

SELECT * FROM t2 a
INNER JOIN t1 b
on a.id = b.id and a.VERSION_ST between b.VERSION_ST and b.VERSION_END

http://sqlfiddle.com/#!3/a5409/25

答案 5 :(得分:0)

如果一行中的一列与另一行中的另一列之间存在依赖关系,则会创建我称之为行跨越依赖关系的行。在您的情况下,依赖关系介于一行的VERSION_END和序列中下一行的VERSION_ST之间。这使得非常困难的DML和许多数据完整性令人头痛。此外,即使在同一行中,也可以在不同的上下文中使用两个日期字段。也就是说,一行的有效持续时间从VERSION_ST('yyy-mm-dd 00:00:00')的早晨午夜开始到VERSION_END晚上的最后一刻('yyy-mm-dd' 23:59:59' )。如果不出意外,这会导致混淆。

幸运的是,只需删除VERSION_END即可解决所有问题。这不是必需的。行的有效持续时间从VERSION_ST中的日期开始,并一直有效,直到下一行的VERSION_ST。 “当前”行是没有下一行的行。

请注意,下面的查询为您提供了所需的输出,而根本不使用VERSION_END。第一个查询返回Attr1创建/更新的日期以及当时的Attr2值。第二个查询返回Attr2创建/更新的日期以及此时的Attr1值。 union删除重复的行(Attr1和Attr2都是同时创建的,因此会出现在两个查询中)。

select t1.id, t1.VERSION_ST, t1.Attr1, t2.Attr2
from   t1
left join t2
  on   t2.id = t1.id
  and  t2.VERSION_ST =(
        select max( VERSION_ST )
        from   t2
        where  VERSION_ST <= t1.VERSION_ST )
union
select t2.id, t2.VERSION_ST, t1.Attr1, t2.Attr2
from   t2
left join t1
  on   t1.id = t2.id
  and  t1.VERSION_ST =(
        select max( VERSION_ST )
        from   t1
        where  VERSION_ST <= t2.VERSION_ST );