【问题标题】:Teradata gets division wrong?Teradata 划分错误?
【发布时间】:2018-05-07 16:38:58
【问题描述】:

我有一位同事不想在百分等级中包含空行。默认的 Teradata 函数似乎只是将 null 视为集合中的最小数字,因此我决定手动进行数学运算。我开始使用下面的查询来测试我的方程式

drop table tmp;

create multiset volatile table tmp (
  num byteint
) primary index (num)
  on commit preserve rows
;

insert into tmp
values (1)
;insert into tmp
values (2)
;insert into tmp
values (1)
;insert into tmp
values (4)
;insert into tmp
values (null)
;insert into tmp
values (4)
;insert into tmp
values (null)
;insert into tmp
values (2)
;insert into tmp
values (9)
;insert into tmp
values (null)
;insert into tmp
values (10)
;insert into tmp
values (10)
;insert into tmp
values (11)
;

select
  num,
  case
    when num is null then 0
    else cast(dense_rank() over (partition by case when num is not null then 1 else 2 end order by num) as number)
  end as str_rnk,
  q.nn,
  str_rnk/q.nn as pct_rnk
from tmp
cross join (
    select cast(count(num) as number) as nn from tmp
) q
order by num
;

所以我希望在结果集中看到的是:

num   str_rnk  nn  pct_rnk
null        0  10        0
null        0  10        0
null        0  10        0
   1        1  10      0.1
   1        1  10      0.1
   2        2  10      0.2
   2        2  10      0.2
   4        3  10      0.3
   4        3  10      0.3
   9        4  10      0.4
  10        5  10      0.5
  10        5  10      0.5

但我得到的结果看起来像是常规的rank 而不是dense_rank,如下所示:

num   str_rnk  nn  pct_rnk
null        0  10        0
null        0  10        0
null        0  10        0
   1        1  10      0.1
   1        1  10      0.1
   2        2  10      0.3
   2        2  10      0.3
   4        3  10      0.5
   4        3  10      0.5
   9        4  10      0.7
  10        5  10      0.8
  10        5  10      0.8

我知道我可以在子查询中设置排名,它会按照我期望的方式进行计算,但为什么不按照我现在的方式进行呢?

【问题讨论】:

    标签: teradata rank percentile dense-rank


    【解决方案1】:

    虽然这不能回答您的问题。这不是一个问题,这似乎是一个奇怪的问题,在同一个 SELECT 中运行 CAST 和 Dense_Rank 两次。

    考虑:

    select
      num,
      case
        when num is null then 0
        else cast(dense_rank() over (partition by case when num is not null then 1 else 2 end order by num) as number)
      end as str_rnk,
    
      case
        when num is null then 0
        else cast(dense_rank() over (partition by case when num is not null then 1 else 2 end order by num) as number)
      end as str_rnk2
    from tmp
    cross join (
        select cast(count(num) as number) as nn from tmp
    ) q;
    
    
    +--------+---------+----------+
    |  num   | str_rnk | str_rnk2 |
    +--------+---------+----------+
    | 1      |       1 |        1 |
    | 1      |       1 |        1 |
    | 2      |       2 |        3 |
    | 2      |       2 |        3 |
    | 4      |       3 |        5 |
    | 4      |       3 |        5 |
    | 9      |       4 |        7 |
    | 10     |       5 |        8 |
    | 10     |       5 |        8 |
    | 11     |       6 |       10 |
    | <null> |       0 |        0 |
    | <null> |       0 |        0 |
    | <null> |       0 |        0 |
    +--------+---------+----------+
    

    因为这里不需要 CAST:

    select
      num,
    
      case
        when num is null then 0
        else dense_rank() over (partition by case when num is not null then 1 else 2 END order by num) 
      end as str_rnk,
    
      case
        when num is null then 0
        else dense_rank() over (partition by case when num is not null then 1 else 2 END order by num) 
      end as str_rnk2
    from tmp
    cross join (
        select cast(count(num) as number) as nn from tmp
    ) q;
    
    +--------+---------+----------+
    |  num   | str_rnk | str_rnk2 |
    +--------+---------+----------+
    | 1      |       1 |        1 |
    | 1      |       1 |        1 |
    | 2      |       2 |        2 |
    | 2      |       2 |        2 |
    | 4      |       3 |        3 |
    | 4      |       3 |        3 |
    | 9      |       4 |        4 |
    | 10     |       5 |        5 |
    | 10     |       5 |        5 |
    | 11     |       6 |        6 |
    | <null> |       0 |        0 |
    | <null> |       0 |        0 |
    | <null> |       0 |        0 |
    +--------+---------+----------+
    

    您的查询,快速重写:

    select
      num,
      case
        when num is null then 0
        else dense_rank() over (partition by num * 0 order by num) 
        end as str_rnk, 
      str_rnk * 1.0/COUNT(*) OVER (PARTITION BY num * 0) as pct_rnk
    from tmp
    order by num
    ;
    
    +--------+---------+---------+
    |  num   | str_rnk | pct_rnk |
    +--------+---------+---------+
    | <null> |       0 |     0.0 |
    | <null> |       0 |     0.0 |
    | <null> |       0 |     0.0 |
    | 1      |       1 |     0.1 |
    | 1      |       1 |     0.1 |
    | 2      |       2 |     0.2 |
    | 2      |       2 |     0.2 |
    | 4      |       3 |     0.3 |
    | 4      |       3 |     0.3 |
    | 9      |       4 |     0.4 |
    | 10     |       5 |     0.5 |
    | 10     |       5 |     0.5 |
    | 11     |       6 |     0.6 |
    +--------+---------+---------+
    

    或者,如果您想完全摆脱 CASE 语句:

    select
      num,
      dense_rank() over (partition by num * 0 order by num) * (num * 0 + 1.0) as str_rnk,  
      str_rnk/COUNT(*) OVER (PARTITION BY num * 0) as pct_rnk
    from tmp
    order by num;
    

    【讨论】:

    • 很好地抓住了双重演员,删除其中一个就可以完成工作,但是有一些 TD 怪癖在起作用。 count(*) 包含空值,这不是我想要的。 str_rnknn 必须是某种接受十进制的类型,否则pct_rnk 中的结果全为0。我听说number 计算最准确? *(num*0+1) 返回空值,我需要 0 来显示,但我可以使用 zeroifnull 函数。最后,我将 ocunt 放在子查询中以确保它只运行一次。我确信 DBMS 已针对此类内容进行了优化,但我想确定。
    • num*0 为您的分组返回 0 或 NULL。它比 CASE 语句更快。 * (num*0+1) 将结果时间乘以 1,除非它为空,否则它将使整个结果为空,从而无需外部 CASE 语句。运行最后一个,我想你会看到你需要什么。 The 1.0 强制计算考虑小数点后一位,因此不需要强制转换。
    • 我肯定会在内部情况下使用乘法,但是* (num*0+1.0) 返回一个空值,我最终需要它为 0。我可以使用 zeroifnull 函数,但我认为这只是一个 case 语句的包装器。
    • 是的,您完全可以将整个内容包含在 zeroifnull() 中,或者返回到更长的 case 语句。我认为 CPU 使用率将是两者之间的平局。
    • num * 0 的技巧很巧妙 :-) 但我认为 CPU 差异不会很大...
    【解决方案2】:

    正如 JNevill 指出的那样,这是一个错误,您应该使用 Teradata 支持打开一个事件:

    SELECT
       num,
       -- cast to FLOAT or DECIMAL works as expected
       Cast(Dense_Rank() Over (ORDER BY num) AS NUMBER) AS a,
       a AS b
    FROM tmp
    
     num    a    b
    ----  ---  ---
       ?    1    1
       ?    1    1
       ?    1    1
       1    2    4
       1    2    4
       2    3    6
       2    3    6
       4    4    8
       4    4    8
       9    5   10
      10    6   11
      10    6   11
      11    7   13
    

    但是添加QUALIFY a&lt;&gt;b 会返回一个空结果:-)

    PERCENT_RANK的原始计算是基于

    Cast(Rank() Over (ORDER BY num) -1 AS DEC(18,6)) / Count(*) Over ()
    

    如果要排除 NULL,可以切换到 Count(num)NULLS LAST

    SELECT
       num,
       CASE
          WHEN num IS NOT NULL 
          THEN Cast(Dense_Rank() Over (ORDER BY num NULLS LAST) AS DECIMAL(18,6)) 
          ELSE 0
       END AS str_rnk,
       str_rnk / Count(num) Over ()
    FROM tmp
    

    或者使用巧妙的num * 0技巧:

    SELECT
       num,
       Coalesce(Dense_Rank()
                Over (ORDER BY num NULLS LAST) 
                 * (num * 0 +1.000000), 0) AS str_rnk,
       str_rnk / Count(num) Over ()
    FROM tmp
    

    【讨论】:

    • 听到您认为这是一个错误让您松了一口气。我试图思考“为什么”它可能会这样做,但找不到任何好的理由。
    猜你喜欢
    • 2016-12-01
    • 2018-05-24
    • 1970-01-01
    • 1970-01-01
    • 2018-06-28
    • 2013-11-04
    • 2014-04-08
    • 2015-05-14
    • 2015-10-19
    相关资源
    最近更新 更多