简化(别名)T-SQL CASE语句.有可能改善吗?

Vin*_*vic 5 t-sql sql-server case

正如你所看到的,这很糟糕.还有其他选择 我尝试在group by子句中使用列别名无济于事.

select count(callid) ,
case
        when callDuration > 0 and callDuration < 30 then 1
        when callDuration >= 30 and callDuration < 60 then 2
        when callDuration >= 60 and callDuration < 120 then 3
        when callDuration >= 120 and callDuration < 180 then 4
        when callDuration >= 180 and callDuration < 240 then 5
        when callDuration >= 240 and callDuration < 300 then 6
        when callDuration >= 300 and callDuration < 360 then 7
        when callDuration >= 360 and callDuration < 420 then 8
        when callDuration >= 420 and callDuration < 480 then 9
        when callDuration >= 480 and callDuration < 540 then 10
        when callDuration >= 540 and callDuration < 600 then 11
        when callDuration >= 600 then 12
end as duration
from callmetatbl
where programid = 1001 and callDuration > 0
group by case
        when callDuration > 0 and callDuration < 30 then 1
        when callDuration >= 30 and callDuration < 60 then 2
        when callDuration >= 60 and callDuration < 120 then 3
        when callDuration >= 120 and callDuration < 180 then 4
        when callDuration >= 180 and callDuration < 240 then 5
        when callDuration >= 240 and callDuration < 300 then 6
        when callDuration >= 300 and callDuration < 360 then 7
        when callDuration >= 360 and callDuration < 420 then 8
        when callDuration >= 420 and callDuration < 480 then 9
        when callDuration >= 480 and callDuration < 540 then 10
        when callDuration >= 540 and callDuration < 600 then 11
        when callDuration >= 600 then 12
end
Run Code Online (Sandbox Code Playgroud)

编辑:我真的想问如何有一个案例源,但无论如何都欢迎案例修改(虽然不太有用,因为间隔可能会被修改,甚至可能自动生成).

正如一些人所考虑的那样,callDuration确实是一个浮点数,因此一些列出的解决方案对我的用例无效,方法是将值保留在间隔之外.

教训:

  • 在case表达式中查找模式以尽可能减少它并且值得

     case
        when callDuration > 0 AND callDuration < 30 then 1
        when callDuration > 600 then 12
        else floor(callDuration/60) + 2  end
     end as duration
    
    Run Code Online (Sandbox Code Playgroud)
  • 使用内联视图来拥有案例的单一来源

    select count(d.callid), d.duration
    from (   
       select callid
            , case
               when callDuration > 0 AND callDuration < 30 then 1
               when callDuration > 600 then 12
               else floor(callDuration/60) + 2  end
              end as duration
        from callmetatbl
        where programid = 1001
              and callDuration > 0
    ) d
    group by d.duration
    
    Run Code Online (Sandbox Code Playgroud)
  • 或者使用公用表表达式

       with duration_case as (
          select callid ,
          case
            when callDuration > 0 AND callDuration < 30 then 1
            when callDuration > 600 then 12
            else floor(callDuration/60) + 2  end
          end as duration
       from callmetatbl
       where programid = 1001 and callDuration > 0 )
        select count(callid), duration
        from duration_case
        group by duration
    
    Run Code Online (Sandbox Code Playgroud)
  • 或者使用用户定义的函数(到目前为止没有例子:-))

  • 或者使用查找表和连接

    DECLARE @t TABLE(durationFrom float, durationTo float, result INT)
    --populate table with values so the query works
    select count(callid) , COALESCE(t.result, 12)
    from callmetatbl JOIN @t AS t ON callDuration >= t.durationFrom 
    AND callDuration < t.durationTo 
    where programid = 1001 and callDuration > 0
    
    Run Code Online (Sandbox Code Playgroud)

感谢大家,我很难选择一个接受的答案,因为很多人都在讨论问题的不同部分(我在那里认为这是一个简单的问题,答案简单明了:-),对不起这个混乱).

Spe*_*ort 9

你有没有理由不使用between?案例陈述本身并不太糟糕.如果你真的讨厌它,你可以将所有这些扔进桌子并映射它.

Durations
------------------
low   high   value
0     30     1
31    60     2
Run Code Online (Sandbox Code Playgroud)

等等...

(SELECT value FROM Durations WHERE callDuration BETWEEN low AND high) as Duration
Run Code Online (Sandbox Code Playgroud)

编辑:或者,在使用花车并且between变得麻烦的情况下.

(SELECT value FROM Durations WHERE callDuration >= low AND callDuration <= high) as Duration
Run Code Online (Sandbox Code Playgroud)


spe*_*593 9

问:如何在GROUP BY子句中使用别名

一种方法是使用内联视图.[编辑] Remus Rusanu的答案(+1!)给出了一个公用表表达式的例子来完成同样的事情.[/编辑]

内联视图为您提供复杂表达式的简单"别名",然后您可以在外部查询中的GROUP BY子句中引用该表达式:

select count(d.callid)
     , d.duration
  from (select callid
             , case
               when callDuration >= 600 then 12
               when callDuration >= 540 then 11
               when callDuration >= 480 then 10
               when callDuration >= 420 then 9
               when callDuration >= 360 then 8
               when callDuration >= 300 then 7
               when callDuration >= 240 then 6
               when callDuration >= 180 then 5
               when callDuration >= 120 then 4
               when callDuration >=  60 then 3
               when callDuration >=  30 then 2
               when callDuration >    0 then 1
               --else null
               end as duration
             from callmetatbl
            where programid = 1001
              and callDuration > 0
       ) d
group by d.duration
Run Code Online (Sandbox Code Playgroud)

让我们打开它.

  • 调用内部(缩进)查询和内联视图(我们给它一个别名d)
  • 在外部查询,大家可以参考别名durationd

这应该足以回答你的问题.如果你正在寻找一个等价的替换表达式,tekBlues(+ 1!)中的那个是正确的答案(它适用于边界和非整数.)

使用tekBlues(+1!)的替换表达式:

select count(d.callid)
     , d.duration
  from (select callid
             , case 
               when callduration >=30 and callduration<600
                    then floor(callduration/60)+2
               when callduration>0 and callduration< 30
                    then 1 
               when callduration>=600
                    then 12
               end as duration
          from callmetatbl
         where programid = 1001
           and callDuration > 0
       ) d
 group by d.duration
Run Code Online (Sandbox Code Playgroud)

(这应该足以回答你的问题了.)


[UPDATE:]示例用户定义的函数(内联CASE表达式的替换)

CREATE FUNCTION [dev].[udf_duration](@cd FLOAT)
RETURNS SMALLINT
AS
BEGIN
  DECLARE @bucket SMALLINT
  SET @bucket = 
  CASE
  WHEN @cd >= 600 THEN 12
  WHEN @cd >= 540 THEN 11
  WHEN @cd >= 480 THEN 10
  WHEN @cd >= 420 THEN 9
  WHEN @cd >= 360 THEN 8
  WHEN @cd >= 300 THEN 7
  WHEN @cd >= 240 THEN 6
  WHEN @cd >= 180 THEN 5
  WHEN @cd >= 120 THEN 4
  WHEN @cd >=  60 THEN 3
  WHEN @cd >=  30 THEN 2
  WHEN @cd >    0 THEN 1
  --ELSE NULL
  END
  RETURN @bucket
END

select count(callid)
     , [dev].[udf_duration](callDuration)
  from callmetatbl
 where programid = 1001
   and callDuration > 0
 group by [dev].[udf_duration](callDuration)
Run Code Online (Sandbox Code Playgroud)

注意:请注意,用户定义的函数会增加开销,并且(当然)会在另一个数据库对象上添加依赖项.

此示例函数等效于原始表达式.OP CASE表达式没有任何间隙,但它确实引用了每个"断点"两次,我更喜欢只测试下限.(CASE在满足条件时返回.反向执行测试可以使未处理的情况(<= 0或NULL)在没有测试的情况下通过,ELSE NULL不需要,但可以添加完整性.

额外细节

(请务必检查性能和优化程序计划,以确保它与原始程序相同(或者不会明显差于原始程序).过去,我在将谓词推入内联视图时遇到问题,看起来不看喜欢这样你的情况会有问题.)

存储的视图

请注意,内联视图也可以作为视图定义存储在数据库中.但除了从你的陈述中"隐藏"复杂的表达之外,没有理由这样做.

简化复杂的表达

使复杂表达式"更简单"的另一种方法是使用用户定义的函数.但是用户定义的函数会带来一系列问题(包括性能下降).

添加数据库"查找"表

一些答案建议在数据库中添加"查找"表.我不认为这是非常必要的.它可以做,当然,并可以使意义,如果你希望能够为获得不同的值durationcallDuration,对飞,不必修改您的查询,而无需为运行任何DDL语句(如更改视图定义,或修改用户定义的函数).

通过连接到"查找"表,一个好处是您可以通过在"查找"表上执行DML操作来使查询返回不同的结果集.

但同样的优势实际上也可能是一个缺点.

如果利益实际上超过了下行,请仔细考虑.考虑新表对单元测试的影响,如何验证查找表的内容是否有效且未更改(任何重叠?任何间隙?),对代码的持续维护(由于额外的复杂性)的影响.

一些大的假设

这里给出的很多答案似乎都假设它callDuration是一个INTEGER数据类型.似乎他们忽略了它不是整数的可能性,但也许我错过了问题中的那个金块.

这是一个相当简单的测试用例来证明:

callDuration BETWEEN 0 AND 30
Run Code Online (Sandbox Code Playgroud)

不是等同于

callDuration > 0 AND callDuration < 30
Run Code Online (Sandbox Code Playgroud)


tek*_*ues 5

案件可以这样写:

case 
when callduration >=30 and callduration<600 then floor(callduration/60)+2
when callduration>0 and callduration< 30 then 1 
when callduration>=600 then 12
end
Run Code Online (Sandbox Code Playgroud)

没有需要,用"callduration> 0"替换它

我喜欢之前给出的翻译表答案!这是最好的解决方案