【问题标题】:Combine and Split Date Ranges In SQL在 SQL 中合并和拆分日期范围
【发布时间】:2011-11-16 22:54:17
【问题描述】:

我正在处理一个问题,我有几个表格,其中包含日期范围、开始日期和结束日期。我正在尝试的任务是在 TSQL (2008 R2) 中获取这些表并将它们组合成一个非规范化表,以便在我们的报告套件中更好地查找。

结果表需要是每个组合表中的数据,其日期范围已更改,以便当一个表的数据在另一个表的期间内发生更改时,第一个表的日期会更改并创建新记录来表示新的范围。

基本上它需要是日期范围的拆分组合。

出于这个问题的目的,我假设我有两个表需要合并,但实际上我必须为几组表执行此操作,每组包含 3-6 个表。

这是表 A 和 B 的示例:

表 A:

IdentityId    EmployeeId  StartDateId  EndDateId    Value
---------------------------------------------------------
1             1           1980-01-01   1980-02-01   A
2             1           1980-02-02   1980-03-01   B
3             1           1980-03-01   -1           C
4             2           1980-01-15   1980-02-01   D
5             2           1980-02-02   1980-02-20   E
6             2           1980-02-21   -1           F

表 B

IdentityId    EmployeeId  StartDateId  EndDateId    Value
---------------------------------------------------------
1             1           1980-01-10   1980-02-01   G
2             1           1980-02-02   1980-03-01   H
3             1           1980-03-02   -1           I
4             2           1980-01-10   1980-02-06   J
5             2           1980-02-07   1980-03-01   K
6             2           1980-03-02   -1           L

组合将是(结果表中的标识列只是一个运行计数器;它不对应于表 A 或 B 中的标识列)

IdentityId    EmployeeId  StartDateId  EndDateId   TableA_Value    TableB_Value
-------------------------------------------------------------------------------
1             1           1980-01-01   1980-01-10  A               G
2             1           1980-01-10   1980-02-01  A               G
3             1           1980-02-02   1980-03-01  B               H
4             1           1980-03-02   -1          C               I
5             2           1980-01-10   1980-01-15  D               J
6             2           1980-01-15   1980-02-01  D               J
7             2           1980-02-02   1980-02-06  E               J
8             2           1980-02-07   1980-02-20  E               K
9             2           1980-02-21   1980-03-01  F               K
10            2           1980-03-02   -1          F               L

一个问题是我不想依赖每个员工都有一组连续的日期这一事实(即,对于有序集中的单个员工 ID,后续记录的开始日期是结束上一次记录的日期)。在上面的例子中,我试图给出我在数据集中可以想象的所有场景。唯一的假设是源系统有一个“结束日期范围”,它是一个记录,该记录是以员工的最大结束日期作为其 StartDateId 创建的,而 EndDateId 的值为 -1。

另一个让我难以理解的问题是,当我的结束日期和开始日期不匹配时。上面第 6 行和第 7 行的员工 2 的场景显示了这一点。

我的第一种方法是将每个表连接到一个日期维度(基数是一天),如下所示:

SELECT T1.IdentityId AS FactId, DD.DimDateId
FROM
    [dbo].[Table_A] T1 JOIN [dbo].[DimDate] DD
        ON DD.DimDateId BETWEEN T1.StartDimDateId AND T1.EndDimDateId

然后获取每个结果集并将它们连接在一起。我的想法是,此时我可以看到每组数据重叠的日期。但是,一旦我爆发到个别日子,我无法想办法回到一系列日期并且我遇到了性能限制,因为我在每个表中有数十万行,每行的日期范围平均约为一个月。

有人可以告诉我/帮助我了解此问题的解决方案吗?我正在使用 SSIS 来解决这个问题,但此时我正在尝试执行 SQL,因为我最初认为这会带来性能优势。

我认为在处理报告数据库和分析解决方案时会经常出现这个特殊问题(这是这个特殊问题试图解决的问题)?

编辑

我对填充表格的内容进行了一些研究。给定员工 ID 的日期似乎是连续的。

【问题讨论】:

  • 我无法确定 IdentityID 7 和 8 在您的结果中来自哪里 + 在您的 sqlstatement 中,您在 Start-EndDimDateId 之间提到了 DimDateID,但您的表 B 包含 StartDateID 和 EndDateId。底线,如果你的输入和输出是正确的,我不知道你用什么逻辑到达那里
  • 糟糕!感谢您指出了这一点。在昨天结束时写下这个问题和今天早上编辑它之间,我想我会犯一两个错误。表中的身份 ID 只是运行计数器。我正在尝试在记事本上计算逻辑,只是将其错误地复制到屏幕上。
  • 你的平台有 OVERLAPS 算子吗?
  • 我不知道 2008 R2 中 T-SQL 中的 OVERLAPS 操作。我已经确定重叠:TBL_A.DimEmployeeId = TBL_B.DimEmployeeId AND (TBL_B.DimEndDateId = -1 OR TBL_A.StartDimDateId = TBL_B.DimStartDateId)跨度>
  • @Lieven 感谢您指出这一点。我再次检查了我的数据,并与填充我正在使用的每个表的方法的设计者交谈,并确认如果结束日期是 MM-dd,则下一条记录将是 MM-(dd + 1)。这将否定您之前遇到的问题。

标签: sql sql-server tsql


【解决方案1】:
-- create some tables and data
-- I use the standard convention of using NULL as the end-date
-- for a "still open" interval.
CREATE TABLE tmp.tablea
    ( id serial NOT NULL
    , uid INTEGER NOT NULL
    , begin_date DATE NOT NULL
    , end_date DATE 
    , aval CHAR(1) 
    );
INSERT INTO tmp.tablea (uid, begin_date, end_date, aval) VALUES
  (1, '1980-01-01', '1980-02-01', 'A' )
, (1, '1980-02-01', '1980-03-01', 'B' )
, (1, '1980-03-01', NULL,         'C' )
, (2, '1980-01-15', '1980-02-01', 'D' )
, (2, '1980-02-01', '1980-02-20', 'E' )
, (2, '1980-02-20', NULL,         'F' )
    ;

DROP TABLE tmp.tableb;
CREATE TABLE tmp.tableb
    ( id serial NOT NULL
    , uid INTEGER NOT NULL
    , begin_date DATE NOT NULL
    , end_date DATE 
    , bval CHAR(1) 
    );
INSERT INTO tmp.tableb (uid, begin_date, end_date, bval) VALUES
 (1, '1980-01-10', '1980-02-01', 'G')
,(1, '1980-02-01', '1980-03-01', 'H' )
,(1, '1980-03-01', NULL,         'I' )
,(2, '1980-01-10', '1980-02-06', 'J' )
,(2, '1980-02-06', '1980-03-01', 'K' )
,(2, '1980-03-01', NULL,         'L' )
    ;

SET search_path='tmp';

UPDATE Yet another edit (between is too ugly), fencepost-errors all over the place...

SELECT aa.uid AS uid
    , GREATEST (aa.begin_date, bb.begin_date) as the_begin
    , LEAST (aa.end_date, bb.end_date) as the_end
    , aa.aval
    , aa.begin_date AS bega
    , aa.end_date AS enda
    , bb.bval
    , bb.begin_date AS begb
    , bb.end_date AS endb
FROM tablea aa
    , tableb bb
    WHERE aa.uid = bb.uid
    AND (0=1
        OR (aa.begin_date >= bb.begin_date AND aa.begin_date < bb.end_date  )
        OR (bb.begin_date >= aa.begin_date AND bb.begin_date < aa.end_date )
        OR (bb.end_date > aa.begin_date AND bb.end_date <= aa.end_date  )
        OR (aa.end_date > bb.begin_date AND aa.end_date <= bb.end_date )
        OR (aa.end_date IS NULL AND bb.begin_date >= aa.begin_date)
        OR (bb.end_date IS NULL AND aa.begin_date >= bb.begin_date)
        )
    ;

结果:

 uid | the_begin  |  the_end   | aval |    bega    |    enda    | bval |    begb    |    endb    
-----+------------+------------+------+------------+------------+------+------------+------------
   1 | 1980-01-10 | 1980-02-01 | A    | 1980-01-01 | 1980-02-01 | G    | 1980-01-10 | 1980-02-01
   1 | 1980-02-01 | 1980-03-01 | B    | 1980-02-01 | 1980-03-01 | H    | 1980-02-01 |  1980-03-01
   1 | 1980-03-01 |            | C    | 1980-03-01 |            | I    | 1980-03-01 | 
   2 | 1980-01-15 | 1980-02-01 | D    | 1980-01-15 | 1980-02-01 | J    | 1980-01-10 | 1980-02-06
   2 | 1980-02-01 | 1980-02-06 | E    | 1980-02-01 | 1980-02-20 | J    | 1980-01-10 | 1980-02-06
   2 | 1980-02-06 | 1980-02-20 | E    | 1980-02-01 | 1980-02-20 | K    | 1980-02-06 | 1980-03-01
   2 | 1980-02-20 | 1980-03-01 | F    | 1980-02-20 |            | K    | 1980-02-06 | 1980-03-01
   2 | 1980-03-01 |            | F    | 1980-02-20 |            | L    | 1980-03-01 | 
(8 rows)

【讨论】:

  • 这大约是我在纸上工作的时候。结果行有效,但集合不完整。例如,第一行从 1980-01-10 开始,到 1980-02-01 结束。这包括需要拆分的整个时间范围,如我在问题中的结果集中所示。它将分为两行开始:1980-01-01 结束:1980-01-10 和开始:1980-01-10 结束:1980-02-01。您的 where 子句确实帮助我找到了一些缺失的案例,因此我正在尝试改进这种方法。
  • 但是你的结果集中不应该有记录 {03-01,03-02,C,I} 吗? (在 IdentityId 3 和 4 之间)否则:请改写。
猜你喜欢
  • 1970-01-01
  • 2010-09-13
  • 1970-01-01
  • 2014-10-13
  • 2020-01-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-01
相关资源
最近更新 更多