【问题标题】:Create Historical Table from Dates with Ranked Contracts (Gaps and Islands?)从具有排名合同的日期创建历史表(差距和孤岛?)
【发布时间】:2014-05-16 05:55:52
【问题描述】:

我在 Teradata 中遇到一个问题,我正在尝试构建一个历史合同表,其中列出了一个系统、相应的合同以及每个合同的开始和结束日期。然后将查询该表以作为时间点表进行报告。这里有一些代码可以更好地解释。

CREATE TABLE TMP_WORK_DB.SOLD_SYSTEMS 
(
SYSTEM_ID varchar(5),
CONTRACT_TYPE varchar(10),
CONTRACT_RANK int,
CONTRACT_STRT_DT date,
CONTRACT_END_DT date
);

INSERT INTO TMP_WORK_DB.SOLD_SYSTEMS  VALUES ('AAA', 'BEST', 10, '2012-01-01', '2012-06-30');
INSERT INTO TMP_WORK_DB.SOLD_SYSTEMS  VALUES ('AAA', 'BEST', 9, '2012-01-01', '2012-06-30');
INSERT INTO TMP_WORK_DB.SOLD_SYSTEMS  VALUES ('AAA', 'OK', 1, '2012-08-01', '2012-12-30');
INSERT INTO TMP_WORK_DB.SOLD_SYSTEMS  VALUES ('BBB', 'BEST', 10, '2013-12-01', '2014-03-02');
INSERT INTO TMP_WORK_DB.SOLD_SYSTEMS  VALUES ('BBB', 'BETTER', 7, '2013-12-01', '2017-03-02');
INSERT INTO TMP_WORK_DB.SOLD_SYSTEMS  VALUES ('BBB', 'GOOD', 4, '2016-12-02', '2017-12-02');
INSERT INTO TMP_WORK_DB.SOLD_SYSTEMS  VALUES ('CCC', 'BEST', 10, '2009-10-13', '2014-10-14');
INSERT INTO TMP_WORK_DB.SOLD_SYSTEMS  VALUES ('CCC', 'BETTER', 7, '2009-10-13', '2016-10-14');
INSERT INTO TMP_WORK_DB.SOLD_SYSTEMS  VALUES ('CCC', 'OK', 2, '2008-10-13', '2017-10-14');

所需的输出是:

SYSTEM_ID   CONTRACT_TYPE   CONTRACT_STRT_DT    CONTARCT_END_DT     CONTRACT_RANK
  AAA          BEST            01/01/2012          06/30/2012           10
  AAA          OK              08/01/2012          12/30/2012           1
  BBB          BEST            12/01/2013          03/02/2014           10
  BBB          BETTER          03/03/2014          03/02/2017           7
  BBB          GOOD            03/03/2017          12/02/2017           4
  CCC          OK              10/13/2008          10/12/2009           2
  CCC          BEST            10/13/2009          10/14/2014           10
  CCC          BETTER          10/15/2014          10/14/2016           7
  CCC          OK              10/15/2016          10/14/2017           2

我不一定要减少行数,而是希望在任何给定时间点获得 system_id 的正确状态。请注意,当排名较高的合约结束且排名较低的合约仍处于活动状态时,排名较低的合约会在排名较高的合约停止的地方接手。

我们使用的是 TD 14,我已经能够获得日期顺序流动且排名较高的简单记录,但在两个不同排名的合同涵盖多个日期跨度的重叠时遇到了问题。

我找到了这篇博文 (Sharpening Stones) 并且大部分时间都能正常工作,但我仍然无法为重叠合同设置新的开始日期。

任何帮助将不胜感激。谢谢。


*2014 年 4 月 4 日更新 *

我想出了以下代码,这正是我想要的,但我不确定性能。它适用于几百行的较小数据集,但我还没有在几百万行上测试过:

*2014 年 4 月 7 日更新 * 由于假脱机问题更新了日期子查询。此查询会在合约可能处于活动状态的所有日子里爆炸,然后使用 ROW_NUMBER 函数来获取每天排名最高的 CONTRACT_TYPE。然后将 MIN/MAX 函数划分为系统和合约类型,以便在排名最高的合约类型发生变化时获取。

*更新 - 2 - 04/07/2014 * 我清理了查询,它似乎执行得更好一些。

SELECT 
    SYSTEM_ID
,   CONTRACT_TYPE
,   MIN(CALENDAR_DATE) NEW_START_DATE
,   MAX(CALENDAR_DATE) NEW_END_DATE
,   CONTRACT_RANK
FROM (
SELECT 
    CALENDAR_DATE
,   SYSTEM_ID
,   CONTRACT_TYPE
,   CONTRACT_RANK
,   ROW_NUMBER() OVER (PARTITION BY SYSTEM_ID, CALENDAR_DATE ORDER BY CONTRACT_RANK DESC, CONTRACT_STRT_DT DESC, CONTRACT_END_DT DESC) AS RNK
FROM SOLD_SYSTEMS t1
JOIN (
    SELECT CALENDAR_DATE
    FROM FULL_CALENDAR_TABLE ia     
    WHERE CALENDAR_DATE > DATE'2013-01-01'
    )dt
ON CALENDAR_DATE BETWEEN CONTRACT_STRT_DT AND CONTRACT_END_DT
QUALIFY RNK = 1
)z1
GROUP BY 1,2,5

【问题讨论】:

  • 在此期间内的每一天将数据分解为一行也可以使用 EXPAND ON 完成,但您最终会得到一个潜在的巨大线轴。您是否需要编写一个查询来获得该结果?还是一个带有易失性表的两步过程也可以?我有一个类似的问题,我可以重写......
  • 你对爆炸数据的看法是对的,它需要一个巨大的线轴。我可以使用尽可能多的步骤和任何方法来完成这项工作。 @rpc1 的答案很接近,但仍然存在未选择正确结束日期的情况。我目前正在努力解决这个问题。
  • 我清理了CREATE TABLEINSERT 数据语句,以使用@dnoeth 的解决方案提供一个完全有效的解决方案。很棒的工作。

标签: sql date teradata gaps-and-islands


【解决方案1】:
SEL system_id,contract_type,MAX(contract_rank),
CASE    WHEN contract_strt_dt<prev_end_dt THEN prev_end_dt+1 
ELSE    contract_strt_dt 
END AS new_start ,contract_strt_dt,contract_end_dt,
MIN(contract_end_dt) OVER (PARTITION BY system_id  
ORDER   BY contract_strt_dt,contract_end_dt ROWS BETWEEN 1 PRECEDING 
AND 1 PRECEDING) prev_end_dt

FROM sold_systems
GROUP BY system_id,contract_type,contract_strt_dt,contract_end_dt
ORDER   BY contract_strt_dt,contract_end_dt,prev_end_dt

【讨论】:

  • 这并没有真正奏效。它按步进顺序创建了新日期,但新的开始日期有时大于结束日期和 prev_end 日期。但是在同一个查询中执行 prev_end_dt 时,你给了我一些新的思考。
  • @thewrstcoderever 查询已编辑,请让我知道用例失败。谢谢
  • 这仍然不起作用,因为它留下了不应包含的较低级别的段。如果每个日期范围都是唯一的,并且每个 system_id 只有一个排名靠前的合同类型,则数据需要尽可能流畅。
【解决方案2】:

我想我明白了...... 试试这个

select  SYSTEM_ID, CONTRACT_TYPE,CONTRACT_RANK,
case
    when CONTRACT_STRT_DT<NEW_START_DATE then NEW_START_DATE  /*if new_star_date overlap startdate then get new_Start_date */
    else CONTRACT_STRT_DT
    end as new_contract_str_dt,
     CONTRACT_END_DT
from
(select t1.SYSTEM_ID,t1.CONTRACT_TYPE,t1.CONTRACT_RANK,t1.CONTRACT_STRT_DT,t1.CONTRACT_END_DT,
 coalesce(max(t1.CONTRACT_END_DT) over (partition by t1.SYSTEM_ID  order by t1.CONTRACT_RANK desc rows between UNBOUNDED PRECEDING and 1 preceding  ), t1.CONTRACT_STRT_DT)  NEW_START_DATE
from SOLD_SYSTEMS t1 

) as  temp1
/*you may remove fully overlapped contracts*/
where  NEW_START_DATE<=CONTRACT_END_DT

它更简单,并且有一个很好的执行计划......你可以处理大表(不要忘记收集统计信息)

【讨论】:

  • 我确实更改了该子查询,因为它正在假脱机。 UPDATED CODEFROM SOLD_SYSTEMS t1JOIN (SELECT CALENDAR_DATEFROM FULL_CALENDAR_TABLE iaWHERE CALENDAR_DATE &gt; DATE'2005-01-01')dtON CALENDAR_DATE BETWEEN CONTRACT_STRT_DT AND CONTRACT_END_DT
  • 基本上我需要一个表格,其中包含每个合同活动期间排名最高的合同类型。系统可以同时激活多个合约类型,但我们只想显示排名最高的活跃合约是什么。系统 AAA 是一个简单的案例,因为没有重叠。系统 BBB 有 3 个重叠的合约。所以从 12/1/13 到 3/2/14 最高排名是“BEST”,同时有更好但排名较低,所以直到 3/3/14 才考虑。在 BETTER 结束后,即 3/3/17 到 12/2/17 结束后,GOOD 合同将被视为排名最高
  • 这有意义吗?我们正在努力完成两件事。一是合并重叠日期,二是在合同有效期内给出排名最高的合同。最终结果将是一个我们可以查询 SELECT CONTRACT_TYPE FROM SYS_CONTARCT_HIST WHERE DATE'2014-12-05' BETWEEN NEW_STRT_DT AND NEW_END_DT 的表
  • 我想你可能有一些东西在这里。起初它排除了我需要的一些记录,但我修改了分区语句以获取丢失的日期。 coalesce(max(t1.CONTRACT_END_DT) over (partition by t1.SYSTEM_ID, CONTRACT_STRT_DT
  • 我使用此条件“在 UNBOUNDED PRECEDING 和前 1 行之间的行”来获取先前合同的最大日期(当您从选择中删除完全重叠的合同时,这很有用)。希望我的回答有用
【解决方案3】:

以下方法使用 TD13.10 中的新 PERIOD 函数。

-- 1. TD_SEQUENCED_COUNT can't be used in joins, so create a Volatile Table
-- 2. TD_SEQUENCED_COUNT can't use additional columns (e.g. CONTRACT_RANK),
--    so simply create a new row whenever a period starts or ends without
--    considering CONTRACT_RANK
CREATE VOLATILE TABLE vt AS
 (
   WITH cte
    (
      SYSTEM_ID
     ,pd
    )
   AS
    (
      SELECT
         SYSTEM_ID
-- PERIODs can easily be constructed on-the-fly, but the end date is not inclusive,
-- so I had to adjust to your implementation, CONTRACT_END_DT +/- 1:
        ,PERIOD(CONTRACT_STRT_DT, CONTRACT_END_DT + 1) AS pd
      FROM SOLD_SYSTEMS
     )
   SELECT
      SYSTEM_ID
     ,BEGIN(pd) AS CONTRACT_STRT_DT
     ,END(pd) - 1 AS CONTRACT_END_DT
   FROM
      TABLE (TD_SEQUENCED_COUNT
            (NEW VARIANT_TYPE(cte.SYSTEM_ID) 
            ,cte.pd) 
      RETURNS (SYSTEM_ID VARCHAR(5)
              ,Policy_Count INTEGER 
              ,pd PERIOD(DATE))
      HASH BY SYSTEM_ID 
      LOCAL ORDER BY SYSTEM_ID ,pd) AS dt
 )
WITH DATA 
PRIMARY INDEX (SYSTEM_ID)
ON COMMIT PRESERVE ROWS
;

-- Find the matching CONTRACT_RANK
SELECT
   vt.SYSTEM_ID
  ,t.CONTRACT_TYPE
  ,vt.CONTRACT_STRT_DT
  ,vt.CONTRACT_END_DT
  ,t.CONTRACT_RANK
FROM vt
-- If both vt and SOLD_SYSTEMS have a NUPI on SYSTEM_ID this join should be
-- quite efficient
JOIN SOLD_SYSTEMS AS t
  ON vt.SYSTEM_ID = t.SYSTEM_ID
 AND      ( t.CONTRACT_STRT_DT,  t.CONTRACT_END_DT)
 OVERLAPS (vt.CONTRACT_STRT_DT, vt.CONTRACT_END_DT)
QUALIFY 
-- As multiple contracts for the same period are possible:
-- find the row with the highest rank
   ROW_NUMBER() 
   OVER (PARTITION BY vt.SYSTEM_ID,vt.CONTRACT_STRT_DT
         ORDER BY t.CONTRACT_RANK DESC, vt.CONTRACT_END_DT DESC) = 1
ORDER BY 1,3
;

-- Previous query might return consecutive rows with the same CONTRACT_RANK, e.g.
-- BBB  BETTER  2014-03-03  2016-12-01  7
-- BBB  BETTER  2016-12-02  2017-03-02  7

-- If you don't want that you have to normalize the data:
WITH cte
 (
   SYSTEM_ID
  ,CONTRACT_STRT_DT
  ,CONTRACT_END_DT
  ,CONTRACT_RANK
  ,CONTRACT_TYPE
  ,pd
 )
AS
 (
   SELECT
      vt.SYSTEM_ID
     ,vt.CONTRACT_STRT_DT
     ,vt.CONTRACT_END_DT
     ,t.CONTRACT_RANK
     ,t.CONTRACT_TYPE
     ,PERIOD(vt.CONTRACT_STRT_DT, vt.CONTRACT_END_DT + 1) AS pd
   FROM vt
   JOIN SOLD_SYSTEMS AS t
     ON vt.SYSTEM_ID = t.SYSTEM_ID
        AND       ( t.CONTRACT_STRT_DT,  t.CONTRACT_END_DT)
         OVERLAPS (vt.CONTRACT_STRT_DT, vt.CONTRACT_END_DT)
   QUALIFY
      ROW_NUMBER() 
      OVER (PARTITION BY vt.SYSTEM_ID,vt.CONTRACT_STRT_DT
            ORDER BY t.CONTRACT_RANK DESC, vt.CONTRACT_END_DT DESC) = 1
 )
SELECT
   SYSTEM_ID
  ,CONTRACT_TYPE
  ,BEGIN(pd) AS CONTRACT_STRT_DT
  ,END(pd) - 1 AS CONTRACT_END_DT
  ,CONTRACT_RANK
FROM
   TABLE (TD_NORMALIZE_MEET
         (NEW VARIANT_TYPE(cte.SYSTEM_ID
                          ,cte.CONTRACT_RANK
                          ,cte.CONTRACT_TYPE)
         ,cte.pd) 
   RETURNS (SYSTEM_ID VARCHAR(5)
           ,CONTRACT_RANK INT
           ,CONTRACT_TYPE VARCHAR(10)
           ,pd PERIOD(DATE))
   HASH BY SYSTEM_ID 
   LOCAL ORDER BY SYSTEM_ID, CONTRACT_RANK, CONTRACT_TYPE, pd ) A
ORDER BY 1, 3;

编辑:这是在没有易失性表和 TD_SEQUENCED_COUNT 的情况下获得第二次查询结果的另一种方法:

SELECT
   t.SYSTEM_ID
  ,t.CONTRACT_TYPE
  ,BEGIN(CONTRACT_PERIOD)  AS CONTRACT_STRT_DT
  ,END(CONTRACT_PERIOD)- 1 AS CONTRACT_END_DT
  ,t.CONTRACT_RANK
  ,dt.p P_INTERSECT PERIOD(t.CONTRACT_STRT_DT,t.CONTRACT_END_DT + 1) AS CONTRACT_PERIOD
FROM
 (
   SELECT
      dt.SYSTEM_ID
     ,PERIOD(d, MIN(d)
                OVER (PARTITION BY dt.SYSTEM_ID
                      ORDER BY d
                      ROWS BETWEEN 1 FOLLOWING AND UNBOUNDED FOLLOWING)) AS p
   FROM
    (
      SELECT
         SYSTEM_ID
        ,CONTRACT_STRT_DT AS d
      FROM SOLD_SYSTEMS
      UNION
      SELECT
         SYSTEM_ID
        ,CONTRACT_END_DT + 1 AS d
      FROM SOLD_SYSTEMS
    ) AS dt 
    QUALIFY p IS NOT NULL
 ) AS dt
JOIN SOLD_SYSTEMS AS t
  ON dt.SYSTEM_ID = t.SYSTEM_ID
WHERE CONTRACT_PERIOD IS NOT NULL
QUALIFY 
   ROW_NUMBER() 
   OVER (PARTITION BY dt.SYSTEM_ID,p
         ORDER BY t.CONTRACT_RANK DESC, t.CONTRACT_END_DT DESC) = 1
ORDER BY 1,3

基于此,您还可以在单​​个查询中包含规范化:

WITH cte
 (
   SYSTEM_ID
  ,CONTRACT_TYPE
  ,CONTRACT_STRT_DT
  ,CONTRACT_END_DT
  ,CONTRACT_RANK
  ,pd
 )
AS
 (
   SELECT
      t.SYSTEM_ID
     ,t.CONTRACT_TYPE
     ,BEGIN(CONTRACT_PERIOD)  AS CONTRACT_STRT_DT
     ,END(CONTRACT_PERIOD)- 1 AS CONTRACT_END_DT
     ,t.CONTRACT_RANK
     ,dt.p P_INTERSECT PERIOD(t.CONTRACT_STRT_DT,t.CONTRACT_END_DT + 1) AS CONTRACT_PERIOD
   FROM
    (
      SELECT
         dt.SYSTEM_ID
        ,PERIOD(d, MIN(d)
                   OVER (PARTITION BY dt.SYSTEM_ID
                         ORDER BY d
                         ROWS BETWEEN 1 FOLLOWING AND UNBOUNDED FOLLOWING)) AS p
      FROM
       (
         SELECT
            SYSTEM_ID
           ,CONTRACT_STRT_DT AS d
         FROM SOLD_SYSTEMS
         UNION
         SELECT
            SYSTEM_ID
           ,CONTRACT_END_DT + 1 AS d
         FROM SOLD_SYSTEMS
       ) AS dt 
       QUALIFY p IS NOT NULL
    ) AS dt
   JOIN SOLD_SYSTEMS AS t
     ON dt.SYSTEM_ID = t.SYSTEM_ID
   WHERE CONTRACT_PERIOD IS NOT NULL
   QUALIFY 
      ROW_NUMBER() 
      OVER (PARTITION BY dt.SYSTEM_ID,p
            ORDER BY t.CONTRACT_RANK DESC, t.CONTRACT_END_DT DESC) = 1
 )
SELECT
   SYSTEM_ID
  ,CONTRACT_TYPE
  ,BEGIN(pd) AS CONTRACT_STRT_DT
  ,END(pd) - 1 AS CONTRACT_END_DT
  ,CONTRACT_RANK
FROM 
   TABLE (TD_NORMALIZE_MEET
         (NEW VARIANT_TYPE(cte.SYSTEM_ID
                          ,cte.CONTRACT_RANK
                          ,cte.CONTRACT_TYPE)
         ,cte.pd) 
   RETURNS (SYSTEM_ID VARCHAR(5)
           ,CONTRACT_RANK INT
           ,CONTRACT_TYPE VARCHAR(10)
           ,pd PERIOD(DATE))
   HASH BY SYSTEM_ID 
   LOCAL ORDER BY SYSTEM_ID, CONTRACT_RANK, CONTRACT_TYPE, pd ) A
ORDER BY 1, 3;

【讨论】:

  • 哇。 @dnoeth 这非常有效。你应该喝啤酒或更好的一整桶。 ;-) 我已经阅读了新的 PERIOD 数据类型,但不知道它可以让这样的问题看起来如此简单。我想当您像您一样是 TD 大师时会有所帮助。谢谢。我希望您不介意,但我更正了 SYSTEM_ID VARCHAR(5) 列数据长度,因此这将是其他人使用的完整示例。 (如果版主允许编辑)再次感谢。
猜你喜欢
  • 1970-01-01
  • 2020-04-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多