【问题标题】:Create groups based on 10 day interval基于 10 天间隔创建组
【发布时间】:2018-04-17 14:41:12
【问题描述】:

我有如下数据表

Animal  Immunization_Date
Cat     1/18/2017
Cat     1/27/2017
Cat     5/7/2017
Cat     5/12/2017
Dog     1/1/2017
Dog     1/5/2017
Dog     1/7/2017
Dog     3/25/2017
Dog     4/18/2017

我正在尝试根据动物的 10 天间隔创建排名,这将导致以下结果。 (查找动物的第一个日期,然后在该日期后 10 天内的任何日子分配一组 1。然后为未分配给 1 的动物取下一个日期并将其分配给 2 和然后将 2 分配给该日期后 10 天内的任何日期等...)

Animal  Immunization_Date   10_Day_Group_Rank
Cat     1/18/2017           1
Cat     1/27/2017           1
Cat     5/7/2017            2
Cat     5/12/2017           2
Dog     1/1/2017            1
Dog     1/5/2017            1
Dog     1/7/2017            1
Dog     3/25/2017           2
Dog     4/18/2017           3

我一直在尝试以下代码,但我似乎无法让 10 天小组工作。

Select
dt.Animal,
dt.Immunization_Date,
sum(dt.10_day_Group) over(partition dt.Animal order by dt.Immunization_Date rows unbounded preceding) as 10_day_Group --creates a running total that is also the group
from
(
Select
Animal,
Immunization_Date,
case when min(Immunization_Date) over (partition by Animal order by Immunization_Date) <=10 then 1 else 0 end as 10_Day_Group --Create intervals of 10 days
from Table_A
) as dt

我不确定如何将 10 天分组放在一起。

case when min(Immunization_Date) over (partition by Animal order by Immunization_Date) <=10 then 1 else 0 end as 10_Day_Group

我可以在 Excel 中执行以下操作。我知道 excel 和 SQL 是不同的,但我希望如果看到它可以在 Excel 中完成,如果有什么可以在 SQL 中完成。

Excel 数据表如下所示(表从单元格A1 开始)。 (注意Animal 需要排序,Immunization_Date 需要排序才能使 Excel 公式起作用)

Animal  Immunization_Date   Dummy_1 10_Day_Group
Cat     1/18/2017       1/18/2017       1
Cat     1/27/2017       1/18/2017       1
Cat     5/7/2017        5/7/2017        2
Cat     5/12/2017       5/7/2017        2
Dog     1/1/2017        1/1/2017        1
Dog     1/5/2017        1/1/2017        1
Dog     1/7/2017        1/1/2017        1
Dog     3/25/2017       3/25/2017       2
Dog     4/18/2017       4/18/2017       3

Dummy_1 的公式如下 IFERROR(IF(AND(A2=A1,B2-C1&lt;=10),C1,B2),B2)

10_Day_Group 的公式如下 IFERROR(IF(AND(C2=C1,A2=A1),D1,IF(AND(A2=A1,C2&lt;&gt;C1),D1+1,1)),1)

【问题讨论】:

  • 如果数据中有 Cat, 1/29/2018 会怎样?
  • 感谢您指出这一点。 1/27/18 应该是 1/27/17 我在我的操作中更新了
  • 您的业务逻辑我不清楚,您的示例数据感觉不一致。例如,一只狗在 4 月 18 日接种了疫苗,在该日期前后 10 天没有接种其他疫苗,但 10 天排名为 3?
  • 由于在 2017 年 4 月 18 日之前 10 天没有日期,所以它将开始一个新的 10 天间隔,因此将是 3。所以狗的 10 天间隔将从 1/1 开始/17 之后 10 天内的任何日子都将被视为1。在该 10 天间隔之后的数据集中可用的下一个日期将被标记为2。在狗的例子中,那个日期是 3/25/17。
  • 这将变得混乱、复杂、昂贵并且可能很慢。你需要那个,还是一个近似值就足够了? (另外,请注意,虽然11-1=10{1,2,3,4,5,6,7,8,9,10} 组中有 10 天。因此,第 11 天自然会与第 1 天在不同的组中......)

标签: sql teradata rank


【解决方案1】:

@MatBailie 的递归答案相当不错,但是当每只动物的行数增加时,性能会变差。

当第一个 CTE 可以在易失性表中实现时,它会降低资源使用率(因为 Teradata 的优化器不会实现这个结果,该死):

CREATE VOLATILE TABLE boundaries AS
(
  SELECT
    i.*, -- need to add the alias 
    (
      SELECT MIN(immunization_date)
        FROM immunizations
       WHERE animal             = i.animal
         AND immunization_date >= i.immunization_date + 10
    )
      AS next_boundary_date
  FROM
    immunizations   i
 )
WITH DATA 
UNIQUE PRIMARY INDEX(animal, immunization_date)
ON COMMIT PRESERVE ROWS;

但是当您可以使用临时表时,您也可以使用简单的递归:

CREATE VOLATILE TABLE vt AS
 (
   SELECT
      animal, 
      immunization_date,
      Row_Number() -- add row number to simplify recursive processing
      Over (PARTITION BY animal
            ORDER BY immunization_date) AS rn
   FROM immunizations AS i
 )
WITH DATA 
UNIQUE PRIMARY INDEX(animal, rn)
ON COMMIT PRESERVE ROWS;

WITH RECURSIVE cte AS
 (
   SELECT
      animal, immunization_date, rn,
      immunization_date+10 AS end_date, -- define the end of the range 
      1 AS grp -- SMALLINT = limited to 127 group, CAST to a larger INT for more groups
   FROM vt
   WHERE rn = 1 -- oldest row

   UNION ALL

   SELECT 
      vt.animal, vt.immunization_date, vt.rn,
      -- check if the current row's date is within the 10 day range
      -- otherwise increase the group number and define the new range end
      CASE WHEN vt.immunization_date < end_date THEN cte.end_date ELSE vt.immunization_date +10 END,
      CASE WHEN vt.immunization_date < end_date THEN cte.grp      ELSE cte.grp+1 END
   FROM cte
   JOIN vt
     ON vt.animal = cte.animal
    AND vt.rn = cte.rn+1
 )
SELECT *
FROM cte
ORDER BY 1,2

【讨论】:

  • 不知道 TeraData 但不是针对 SQL Server 中的每一行进行范围搜索(或针对每次迭代的整个表),它是按需完成的(所以从 100% 到 10% 的行)。然后使用范围可以最小化递归需要达到的深度,再次降低高达 10倍。
  • @MatBailie:由于 Teradata 是一个大规模的并行 DBMS,因此任何不基于平等的处理都不是很有效,即 same animalimmunization_date >= i。免疫日期 + 10。然后处理额外的递归级别可能会更快(并且有些动物可以有 50 多个实例,而有些动物只有 1 个实例并不是一个很高的数字)。当然,当每组的行数很高时,您的解决方案应该非常有效(然后我会将您的标量子查询重写为基于在 Teradat 上非常有效的 OLAP 函数的解决方案)
  • 我想不出可以确定下一个边界的解析函数方法...
  • 没有相关子查询的递归,但仍然使用范围搜索方法...sqlfiddle.com/#!18/5af35/32
  • 这是对分析函数的子查询的重写:sqlfiddle.com/#!18/6a068/1
【解决方案2】:

一个近似值...

SELECT
  animal,
  immunization_date,
  DENSE_RANK() OVER (PARTITION BY animal
                         ORDER BY base_date,
                                  CAST(immunization_date - base_date AS INT) / 10
                    )
                      AS group_id
FROM
(  
  SELECT
    animal,
    immunization_date,
    MAX(
      CASE WHEN immunization_date < lagged_immunization_date + 10
           THEN NULL
           ELSE immunization_date
      END
    )
      OVER (PARTITION BY animal
                ORDER BY immunization_date
                    ROWS UNBOUNDED PRECEDING
           )
             AS base_date
  FROM
  (
    SELECT
      animal,
      immunization_date,
      LAG(immunization_date) OVER (PARTITION BY animal
                                       ORDER BY immunization_date
                                  )
                                    AS lagged_immunization_date
    FROM
      yourData
  )
    lagged_dates
)
  base_dated

SQLFiddle 没有 TeraData 但上面的代码应该可以在 TeraData 中运行 SQL Server...http://sqlfiddle.com/#!18/68260/1

【讨论】:

【解决方案3】:

我们可以利用 Teradata 的 PERIOD 数据类型及其相关函数来帮助解决这个问题,而不会太复杂。

这很接近。不准确,但很接近:

WITH ta_period AS
(
        SELECT
            PERIOD(immunization_date - INTERVAL '10' DAY, immunization_date) AS periodbucket,
            ROW_NUMBER() OVER (PARTITION BY animal ORDER BY immunization_date) AS animal_row,
            table_a.animal,
            table_a.immunization_date
        FROM table_a
)
,cal_buckets AS
(

    SELECT calendar_dateFROM Sys_Calendar."CALENDAR" cal
    WHERE calendar_date >= (SELECT MIN(immunization_date) FROM table_a)
        AND calendar_date <= (SELECT MAX(immunization_date) FROM table_a)
)
SELECT
    TA.animal,
    TA.immunization_date,
    cal_buckets.bucket,
    DENSE_RANK() OVER (PARTITION BY ta.animal ORDER BY ta_normal.periodbucket, cal_buckets.bucket ) AS ten_day_bucket
FROM
    (
        SELECT NORMALIZE    
            ta_period.animal,   
            ta_period.periodbucket P_INTERSECT ta_period.periodbucket AS periodbucket
        FROM ta_period LEFT OUTER JOIN ta_period ta_period2 
            ON ta_period.periodbucket CONTAINS ta_period2.immunization_date
                AND ta_period.animal = ta_period2.animal
                AND ta_period.animal_row <> ta_period2.animal_row   
    ) ta_normal
    INNER JOIN ta_period ta ON
        ta_normal.animal = ta.animal
        AND ta_normal.periodbucket P_INTERSECT ta.periodbucket IS NOT NULL
    INNER JOIN cal_buckets ON
        ta.immunization_date = cal_buckets.calendar_date;

【讨论】:

  • (我无法对此进行测试,所以如果我错了,我深表歉意,对于符号...) 使用“日期”1, 9, 12, 15, 18, 21, 24。 .. 连接将产生1:{NULL}, 9:{1}, 12:{9}, 15:{9, 12}, 18:{9, 12, 15}, 21:{12, 15, 18}, 23:{15, 18, 21}COALESCE(MIN(),SELF) 将产生 1:{1}, 9:{1}, 12:{9}, 15:{9}, 18:{9}, 21:{12}, 23:{15}DENSE_RANK() 将产生 1:{1}, 9:{1}, 12:{2}, 15:{2}, 18:{2}, 21:{3}, 23:{4}。因此,日期 24 应该在第 3 组中,而在第 4 组中。
  • 一个更短的例子,更容易手动计算,“日期”1, 7, 13, 20... 连接将产生1:{NULL}, 7:{1}, 13:{7}, 20:{13}COALESCE(MIN(),SELF) 将产生 1:{1}, 7:{1}, 13:{7}, 20:{13}DENSE_RANK() 将产生 1:{1}, 7:{1}, 13:{2}, 20:{3}。因此,日期 20 应该在第 2 组中,而在第 3 组中。
  • 老实说,我认为最好的办法是根据序列的最小值固定 10 天“窗口”。然后1, 9, 19, 28, 31, 47, 56 将进入{1-10} {11-20} {21-30} {31-40} {41-50} {51-60} 的桶,因此给出{1, 9}{19}{28}{31}{47}{56} 的组。然后可以通过在前 9 天没有条目的行重新启动边界来“改进”。然后桶将是{1-10} {19-28} {29-38} {47-56},因此给出{1, 9}{19, 28}{31}{47, 56} 的组。任何更复杂的东西都需要一个循环......
  • 我很难理解你的符号,但我很欣赏它在评论中的表现!我不同意您使用存储桶的想法(我也对此进行了探索,使用每只动物的开始日期作为存储桶的起点并使用 sys_calendar.calendar 来构建存储桶)。这是一个奇怪的解决方案,我放弃了它有两个原因:1. 这不是 OP 的要求。 2. 我有 3 个子查询,但仍然没有很好的结果。我相信这个解决方案符合 OP 的意图,而且非常简洁。它也应该在大型数据集上表现良好。
  • 我现在得到你的符号。我知道这不适合像1,5,12 这样的东西,它们都应该产生1。回到绘图板! (不幸的是,递归 CTE 看起来更有可能是一种好的且昂贵的拟合)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-12-03
  • 1970-01-01
  • 2021-01-26
  • 2020-10-12
  • 1970-01-01
  • 2021-07-16
  • 1970-01-01
相关资源
最近更新 更多