在SQL中组合和拆分日期范围

时间:2011-11-15 22:57:06

标签: sql sql-server tsql

我正在解决一个问题,我有几个表格,包括日期范围,开始日期和结束日期。我正在尝试的任务是在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。

我遇到麻烦的另一个问题是,当我有结束日期并且开始日期不匹配时。员工2对于第6行和第7行的上述情况显示了这一点。

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

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的日期是连续的。

1 个答案:

答案 0 :(得分:0)

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