【问题标题】:Group By with Case Statement Doesn't Count ZerosGroup By with Case 语句不计算零
【发布时间】:2022-01-15 08:13:53
【问题描述】:

我有以下 SQL:

WITH MYFILTER AS (
SELECT
CASE WHEN STATE IN ('A', 'B') then
case
   when (NUM/DENOM - 1.0) < -0.3 then 'Less than -30%'
           when (NUM/DENOM - 1.0) >= -0.3
           and (NUM/DENOM - 1.0) < -0.2  then '-30% to -20%'
           when (NUM/DENOM - 1.0) >= -0.2
           and (NUM/DENOM - 1.0) < -0.1  then '-20% to -10%'
           when (NUM/DENOM - 1.0) >= -0.1
           and (NUM/DENOM - 1.0) < 0.0   then '-10% to 0%'
           when (NUM/DENOM - 1.0) >= 0.0
           and (NUM/DENOM - 1.0) < 0.1    then '0% to 10%'
           when (NUM/DENOM - 1.0) >= 0.1
           and (NUM/DENOM - 1.0) < 0.2    then '10% to 20%'
           when (NUM/DENOM - 1.0) >= 0.2
           and (NUM/DENOM - 1.0) < 0.3    then '20% to 30%'
           when (NUM/DENOM - 1.0) >= 0.3  THEN 'At least 30%'
           end
ELSE case
   when (NUM/DENOM < -0.3 then 'Less than -30%'
           when (NUM/DENOM >= -0.3
           and (NUM/DENOM < -0.2  then '-30% to -20%'
           when (NUM/DENOM >= -0.2
           and (NUM/DENOM < -0.1  then '-20% to -10%'
           when (NUM/DENOM >= -0.1
           and (NUM/DENOM < 0.0   then '-10% to 0%'
           when (NUM/DENOM >= 0.0
           and (NUM/DENOM < 0.1    then '0% to 10%'
           when (NUM/DENOM >= 0.1
           and (NUM/DENOM < 0.2    then '10% to 20%'
           when (NUM/DENOM >= 0.2
           and (NUM/DENOM < 0.3    then '20% to 30%'
           when (NUM/DENOM >= 0.3  THEN 'At least 30%'
           end
END AS indrange
FROM MYTABLE
WHERE DENOM <> 0 AND
YEAR = 2020 AND
MONTH = 11
)
SELECT
indrange,
count (*) AS total
FROM FILTER
GROUP BY indrange

只要比率不属于某个范围(例如,我的表中没有 NUM/DENOM - 1 > 0.3 的行),那么我最终得到的结果不包括“至少 30% " 作为具有 0 值的行。相反,该行根本不存在。我将如何更改代码以使其仍包含具有相应 0 值的“至少 30%”行?换句话说,我明白了:

INDRANGE        TOTAL
Less than -30%  285
-30% to -20%    1,608
-20% to -10%    7,409
-10% to 0%      164,212
0% to 10%       169,665
10% to 20%      1

但我想要这个:

INDRANGE        TOTAL
Less than -30%  285
-30% to -20%    1,608
-20% to -10%    7,409
-10% to 0%      164,212
0% to 10%       169,665
10% to 20%      1
20% to 30%      0
At Least 30%    0

我该怎么做?这是 DB2。

【问题讨论】:

  • 在每个范围的 1 行中的 CTE 联合中,当您从 CTE 中选择时,从每个结果中减去 1 count(*)-1 问题是当您没有与 case 表达式匹配的数据时,您将没有行.确保您获得所有行;在每个可能范围的空白行中联合,但是这会将每个范围的计数增加 1...所以从 CTE 中选择的每个范围中减去 1。
  • @xQbert,我正在为如何做到这一点的语法而苦苦挣扎,你能提供一个例子吗?谢谢。
  • 提供的示例。没有要测试的环境,所以我不确定我的语法是否正确。

标签: sql join group-by db2 relational-database


【解决方案1】:

您可以在范围和现有 CTE 之间使用 LEFT JOIN。例如:

with myranges (indrange) as (
  select 'Less than -30%'         from sysibm.sysdummy1
  union all select '-30% to -20%' from sysibm.sysdummy1
  union all select '-20% to -10%' from sysibm.sysdummy1
  union all select '-10% to 0%'   from sysibm.sysdummy1
  union all select '0% to 10%'    from sysibm.sysdummy1
  union all select '10% to 20%'   from sysibm.sysdummy1
  union all select '20% to 30%'   from sysibm.sysdummy1
  union all select 'At Least 30%' from sysibm.sysdummy1
),
myfilter as (
  -- here add you existing CTE
)
select r.indrange, count(f.indrange) AS total
from myranges r
left join myfilter f on f.indrange = r.indrange
group by r.indrange

【讨论】:

    【解决方案2】:

    未经测试:但基于我之前的 cmets 的类似内容。

    • 添加了一个 MyRanges cte 列出每个范围 1 次,从而确保每个范围都包含在最终查询中。
    • 在最终查询中使用 Union all 将其与来自 MyFilter 的结果结合起来
    • 从 count(*) 结果中减去 1,因为我们将 myRanges union 的结果增加了 1

    .

    WITH  MYRANGES AS(
      SELECT 'Less than -30%' as INDRANGE FROM DUAL UNION ALL
      SELECT '-30% to -20%'    as INDRANGE FROM DUAL UNION ALL
      SELECT '-20% to -10%'    as INDRANGE FROM DUAL UNION ALL
      SELECT '-10% to 0%'      as INDRANGE FROM DUAL UNION ALL
      SELECT '0% to 10%'       as INDRANGE FROM DUAL UNION ALL
      SELECT '10% to 20%'      as INDRANGE FROM DUAL UNION ALL
      SELECT '20% to 30%'      as INDRANGE FROM DUAL UNION ALL
      SELECT 'At Least 30%'    as INDRANGE FROM DUAL),
    MYFILTER AS (SELECT
      CASE WHEN STATE IN ('A', 'B') then
      case
       when (NUM/DENOM - 1.0) < -0.3 then 'Less than -30%'
               when (NUM/DENOM - 1.0) >= -0.3
               and (NUM/DENOM - 1.0) < -0.2  then '-30% to -20%'
               when (NUM/DENOM - 1.0) >= -0.2
               and (NUM/DENOM - 1.0) < -0.1  then '-20% to -10%'
               when (NUM/DENOM - 1.0) >= -0.1
               and (NUM/DENOM - 1.0) < 0.0   then '-10% to 0%'
               when (NUM/DENOM - 1.0) >= 0.0
               and (NUM/DENOM - 1.0) < 0.1    then '0% to 10%'
               when (NUM/DENOM - 1.0) >= 0.1
               and (NUM/DENOM - 1.0) < 0.2    then '10% to 20%'
               when (NUM/DENOM - 1.0) >= 0.2
               and (NUM/DENOM - 1.0) < 0.3    then '20% to 30%'
               when (NUM/DENOM - 1.0) >= 0.3  THEN 'At least 30%'
               end
    ELSE case
       when (NUM/DENOM < -0.3 then 'Less than -30%'
               when (NUM/DENOM >= -0.3
               and (NUM/DENOM < -0.2  then '-30% to -20%'
               when (NUM/DENOM >= -0.2
               and (NUM/DENOM < -0.1  then '-20% to -10%'
               when (NUM/DENOM >= -0.1
               and (NUM/DENOM < 0.0   then '-10% to 0%'
               when (NUM/DENOM >= 0.0
               and (NUM/DENOM < 0.1    then '0% to 10%'
               when (NUM/DENOM >= 0.1
               and (NUM/DENOM < 0.2    then '10% to 20%'
               when (NUM/DENOM >= 0.2
               and (NUM/DENOM < 0.3    then '20% to 30%'
               when (NUM/DENOM >= 0.3  THEN 'At least 30%'
               end
    END AS indrange
    FROM MYTABLE
    WHERE DENOM <> 0 AND
      YEAR = 2020 AND
      MONTH = 11
    ),
    
    SELECT indrange, count (*)-1 AS total
    FROM   (SELECT * FROM MyFilter UNION ALL
            SELECT * FROM MyRanges) as MyFilterandAllRanges
    GROUP BY indrange
    

    好吧,既然我已经做了一些事情,我已经考虑过了,我可以做得更好......

    所以将最终查询修改为....,我们不必弄乱数字和数学。我们基本上采用范围的“主列表”并将其外部连接到我们的总数中,以便始终包含所有范围,并且只有匹配的范围才会显示总数。我们使用合并来处理一些可能不存在的事实并替换所需的 0。

    SELECT MyRanges.indrange, coalesce(total,0) as total
    FROM   MyRanges
    LEFT JOIN (SELECT count(*) total, Indrange 
               FROM MyFilter
               GROUP BY indrange) as sub
         on MyRanges.indrange = sub.indrange
    

    【讨论】:

    • 仅供参考,您的 CASE 语句过于复杂,因为 CASE 语句在遇到第一个匹配项时会停止处理。因此,例如,您不需要 "(NUM/DENOM - 1.0) >= -0.3",因为任何与此值不匹配的记录都将被第一个 WHEN 子句拾取,并且永远不会到达此 WHEN 子句
    • @nickW 我正在使用原始帖子中的案例表达式。我不是想改进或评论它;只需解决提出的问题。 (id'说评论更属于原始帖子而不是答案:P)
    • 不幸的是,我收到一条错误消息,说“Dual”是一个未定义的名称。我做了一个快速的谷歌搜索,并没有为此想出太多。 Dual 关键字在 DB2 中有效吗?我不熟悉它。
    • 在 DB2 中,您可以使用 sysibm.sysdummy1 代替 dual
    • 我认为 DB2 支持基于 stackoverflow.com/questions/3732422/select-from-nothing 的双重功能哦。
    猜你喜欢
    • 2020-10-26
    • 2020-04-01
    • 2013-11-19
    • 1970-01-01
    • 2017-03-22
    • 2021-03-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多