【问题标题】:How to find value in a range of following rows - SQL Teradata如何在以下行的范围内查找值 - SQL Teradata
【发布时间】:2020-08-25 17:44:42
【问题描述】:

我有一个包含以下列的表格: 帐户,validity_date,validity_month,金额。 对于每一行,我想检查字段“金额”中的值是否存在于下个月的行范围内。如果是,indicator=1,否则为 0。

account   validity_date   validity_month   amount    **required_column**
-------   -------------   ---------------  -------   ----------------
123        15oct2019       201910           400         0
123        20oct2019       201910           500         1
123        15nov2019       201911           1000        0
123        20nov2019       201911           500         0
123        20nov2019       201911           2000        1
123        15dec2019       201912           400         
123        15dec2019       201912           2000        

有人可以帮忙吗? 谢谢

【问题讨论】:

  • 如果 10oct 和 20nov 匹配会发生什么?这在“下个月”有效吗?还是您真的是指“未来 30 天内”?无论哪种情况,第 1 步都是为日期设置数字,而不是字符串。
  • 下个月是指下一个日历月的所有天数。如果 10oct 和 20nov 匹配,则有效。

标签: sql teradata partitioning


【解决方案1】:

validity_month/100*12 + validity_month MOD 100 计算月份数(用于跨年份比较,从 1 月到前 12 月),内部 ROW_NUMBER 将每月相同数量的多行减少为单行(DISTINCT 类型):

SELECT dt.*
  ,CASE -- next row is from next month
     WHEN Lead(nextMonth IGNORE NULLS)
          Over (PARTITION BY account, amount
                ORDER BY validity_date)
        = (validity_month/100*12 + validity_month MOD 100) +1
     THEN 1
     ELSE 0
   END
FROM
 (
   SELECT t.*
     ,CASE -- one row per account/month/amount
        WHEN Row_Number()
             Over (PARTITION BY account, amount, validity_month
                   ORDER BY validity_date ) = 1 
            THEN validity_month/100*12 + validity_month MOD 100
            END AS nextMonth
   FROM tab AS t
 ) AS dt

编辑:

前面是精确匹配数量,对于范围匹配,查询可能很难用 OLAP 函数编写,但使用关联子查询很容易:

SELECT t.*
  ,CASE
     WHEN 
      ( -- check if there's a row in the next month matching the current amount +/- 10 percent
        SELECT Count(*)
        FROM tab AS t2
        WHERE t2.account_ = t.account_
        AND (t2.validity_month/100*12 + t2.validity_month MOD 100)
          = ( t.validity_month/100*12 +  t.validity_month MOD 100) +1
        AND t2.amount BETWEEN t.amount * 0.9 AND t.amount * 1.1
      ) > 0 
    THEN 1
    ELSE 0
  END
FROM tab AS t

但是性能可能真的很差......

【讨论】:

  • 谢谢dnoeth!如果我不一定需要相同的金额,但如果金额在 10% 的范围内关闭(这意味着如果在 2019 年 10 月我有 500 的行,而在下个月我有 520,我也想放置指标=1)。非常感谢您的帮助!
  • 那是完全不同的逻辑,容易写,但很难执行
【解决方案2】:

只是添加另一种使用标准 SQL 执行此操作的方法。此查询将在满足条件时返回 1,在不满足时返回 0,在没有下个月可评估时返回 null(如结果列中所暗示的那样)。

假设我们在account 字段上进行分区。还包括基于所做评论的 amount 字段的 10% 范围匹配。请注意,如果您有一个 id 字段,则应该包含它(如果两行具有相同的 accountvalidity_datevalidity_monthamount,则由于DISTINCT,将只有一个结果行)。

性能方面,应该类似于@dnoeth 的答案。

SELECT DISTINCT
  t1.account, 
  t1.validity_date,
  t1.validity_month, 
  t1.amount, 
  CASE 
    WHEN t2.amount IS NOT NULL THEN 1 
    WHEN MAX(t1.validity_month) OVER (PARTITION BY t1.account) > t1.validity_month THEN 0
    ELSE NULL
  END AS flag
FROM `project.dataset.table` t1
LEFT JOIN `project.dataset.table` t2 
ON 
  t2.account = t1.account AND
  DATE_DIFF(
    PARSE_DATE("%Y%m", CAST(t2.validity_month AS STRING)),
    PARSE_DATE("%Y%m", CAST(t1.validity_month AS STRING)),
    MONTH
  ) = 1 AND
  t2.amount BETWEEN t1.amount * 0.9 AND t1.amount * 1.1;

【讨论】:

  • 标准 SQL 不支持 DATE_DIFF/PARSE_DATE,这是 T-SQL 语法 :-) 否则性能(至少在 Teradata 中)应该是相似的。连接只是一个潜在的问题:它可能会返回重复的行(例如,2 行 400 匹配 3 行 390/400/410 下个月返回 2*3=6 行),然后您需要添加最终的 DISTINCT 可能删除现有的重复项...
  • @dnoeth 感谢您指出加入问题 - 现在已修复。 BigQuery 标准 SQL(我使用的)与“标准”标准 SQL 不同吗?
  • Google 将其命名为“标准 SQL”并不意味着它实际上在实现 ISO 9075 SQL:2016 :-) 当然,大多数语法都是标准的,但总有一些变化。
【解决方案3】:

假设值在一个月内是唯一的,并且每个帐户的每个月都有一个值,您可以将其简化为:

select t.*,
       (case when lead(seqnum) over (partition by account, amount order by validity_month) = seqnum + 1
             then 1 else 0
        end)
from (select t.*,
             dense_rank() over (partition by account order by validity_month) as seqnum
      from t
     ) t;

注意:这会将0 放在上个月而不是NULL,但可以轻松调整。

您可以使用月份算术在不使用子查询的情况下执行此操作。目前尚不清楚validity_month 的数据类型是什么。如果我假设一个数字:

select t.*,
       (case when lead(floor(validity_month / 100) * 12 + (validity_month mod 100)
                      ) over (partition by account, amount order by validity_month) = 
                  (validity_month / 100) * 12 + (validity_month mod 100) - 1
             then 1 else 0
        end)
from t;

【讨论】:

    猜你喜欢
    • 2020-08-18
    • 2021-08-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-01
    相关资源
    最近更新 更多