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

时间:2009-06-04 17:01:41

标签: sql-server tsql 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

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

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

经验:

  • 查看案例表达式中的模式,以便在可能和有价值的情况下减少它

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

    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
    
  • 或使用公用表表达式

       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
    
  • 或者使用用户定义的函数(目前没有例子:-))

  • 或使用查找表和连接

    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
    

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

11 个答案:

答案 0 :(得分:9)

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

Durations
------------------
low   high   value
0     30     1
31    60     2

等...

(SELECT value FROM Durations WHERE callDuration BETWEEN low AND high) as Duration

编辑:或者,在使用浮动并且between变得麻烦的情况下。

(SELECT value FROM Durations WHERE callDuration >= low AND callDuration <= high) as Duration

答案 1 :(得分: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

让我们打开它。

  • 调用内部(缩进)查询并内嵌视图(我们为其指定了别名d
  • 在外部查询中,我们可以引用duration
  • 中的别名d

这应该足以回答你的问题了。如果您正在寻找等效替换表达式,那么来自 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

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


[更新:]示例用户定义的函数(内联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)

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

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

其他详细信息

(务必检查性能和优化程序计划,以确保它与原始程序相同(或者不会显着差)。在过去,我遇到了将谓词推入内联视图的问题,看起来在你的情况下会出现问题。)

存储的视图

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

简化复杂表达

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

添加数据库“查找”表

有些答案建议在数据库中添加“查找”表。我不认为这是非常必要的。当然可以这样做,如果你希望能够动态地从duration导出callDuration的不同值,没有必须修改你的查询和没有必须运行任何DDL语句(例如,更改视图定义,或修改用户定义的函数)。

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

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

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

一些大的假设

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

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

callDuration BETWEEN 0 AND 30

NOT 等同于

callDuration > 0 AND callDuration < 30

答案 2 :(得分: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

不需要使用,将其替换为“where callduration&gt; 0”

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

答案 3 :(得分:4)

您需要将CASE进一步向下推入查询树,以使其投影对GROUP BY可见。这可以通过两种方式实现:

  1. 使用派生表(已经Spencer,Adam和Jeremy展示了如何)
  2. 使用公用表格表达式

    with duration_case as (
    select 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 )
       select count(callid), duration
       from duration_case
       group by duration
    
  3. 两种解决方案在各个方面都是相同的。我发现CTE更具可读性,有些人更喜欢派生表更具可移植性。

答案 4 :(得分:2)

callDuration除以60:

case
        when callDuration between 1 AND 29 then 1
        when callDuration > 600 then 12
        else (callDuration /60) + 2  end
end as duration

请注意between包含边界,我假设callDuration将被视为整数。


<强>更新
将此与其他一些答案相结合,您可以将整个查询归结为:

select count(d.callid), d.duration
from (   
       select callid
            , case
                when callDuration between 1 AND 29 then 1
                when callDuration > 600 then 12
                else (callDuration /60) + 2  end
              end as duration
        from callmetatbl
        where programid = 1001
              and callDuration > 0
    ) d
group by d.duration

答案 5 :(得分:1)

select count(callid), duration from
(
    select 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
) source
group by duration

答案 6 :(得分:1)

未测试:

select  count(callid) , duracion
from
    (select 
        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        
            else 0
        end as duracion
    from callmetatbl
    where programid = 1001) GRP
where duracion > 0
group by duracion

答案 7 :(得分:1)

将所有案例添加到表变量中并执行外部联接

DECLARE @t TABLE(durationFrom INT, durationTo INT, result INT)
--        when callDuration > 0 and callDuration < 30 then 1
INSERT INTO @t VALUES(1, 30, 1);
--        when callDuration >= 30 and callDuration < 60 then 2
INSERT INTO @t VALUES(30, 60, 2);

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

答案 8 :(得分:1)

这是我的镜头。您需要的所有组件都可以直接用SQL完成。

select
  count(1) as total
 ,(fixedDuration / divisor) + adder as duration
from
(
    select
      case/*(30s_increments_else_60s)*/when(callDuration<60)then(120)else(60)end as divisor
     ,case/*(increment_by_1_else_2)*/when(callDuration<30)then(1)else(2)end as adder
     ,(/*duration_capped@600*/callDuration+600-ABS(callDuration-600))/2 as fixedDuration
     ,callDuration
    from 
      callmetatbl
    where
      programid = 1001
    and 
      callDuration > 0
) as foo
group by
  (fixedDuration / divisor) + adder

这是我用于测试的SQL。 (我没有自己的个人callmetatbl;)

select
  count(1) as total
 ,(fixedDuration / divisor) + adder as duration
from
(
    select
      case/*(30s_increments_else_60s)*/when(callDuration<60)then(120)else(60)end as divisor
     ,case/*(increment_by_1_else_2)*/when(callDuration<30)then(1)else(2)end as adder
     ,(/*duration_capped@600*/callDuration+600-ABS(callDuration-600))/2 as fixedDuration
     ,callDuration
    from -- callmetatbl -- using test view below
      (  
       select 1001 as programid,   0 as callDuration union
       select 1001 as programid,   1 as callDuration union
       select 1001 as programid,  29 as callDuration union
       select 1001 as programid,  30 as callDuration union
       select 1001 as programid,  59 as callDuration union
       select 1001 as programid,  60 as callDuration union
       select 1001 as programid, 119 as callDuration union
       select 1001 as programid, 120 as callDuration union
       select 1001 as programid, 179 as callDuration union
       select 1001 as programid, 180 as callDuration union
       select 1001 as programid, 239 as callDuration union
       select 1001 as programid, 240 as callDuration union
       select 1001 as programid, 299 as callDuration union
       select 1001 as programid, 300 as callDuration union
       select 1001 as programid, 359 as callDuration union
       select 1001 as programid, 360 as callDuration union
       select 1001 as programid, 419 as callDuration union
       select 1001 as programid, 420 as callDuration union
       select 1001 as programid, 479 as callDuration union
       select 1001 as programid, 480 as callDuration union
       select 1001 as programid, 539 as callDuration union
       select 1001 as programid, 540 as callDuration union
       select 1001 as programid, 599 as callDuration union
       select 1001 as programid, 600 as callDuration union
       select 1001 as programid,1000 as callDuration
      ) as callmetatbl
    where
      programid = 1001
    and 
      callDuration > 0
) as foo
group by
  (fixedDuration / divisor) + adder

SQL输出如下所示,每个持续时间(存储桶)1到12计算2条记录。

total  duration
2             1
2             2
2             3
2             4
2             5
2             6
2             7
2             8
2             9
2            10
2            11
2            12

以下是“foo”子查询的结果:

divisor adder   fixedDuration  callDuration
120         1               1             1
120         1              29            29
120         2              30            30
120         2              59            59
60          2              60            60
60          2             119           119
60          2             120           120
60          2             179           179
60          2             180           180
60          2             239           239
60          2             240           240
60          2             299           299
60          2             300           300
60          2             359           359
60          2             360           360
60          2             419           419
60          2             420           420
60          2             479           479
60          2             480           480
60          2             539           539
60          2             540           540
60          2             599           599
60          2             600           600
60          2             600          1000

干杯。

答案 9 :(得分:1)

这里的用户定义函数出了什么问题?您既可以直观地清理代码,也可以通过这种方式集中管理功能。在性能方面,除非你在UDF中做了一些非常迟钝的事情,否则我看不到命中太可怕了。

答案 10 :(得分:1)

duration创建查找表 使用查找表也可以加快SELECT语句。

以下是查找表的最终结果。

select  count(a.callid), b.ID as duration
from    callmetatbl a
        inner join DurationMap b 
         on a.callDuration >= b.Minimum
        and a.callDuration < IsNUll(b.Maximum, a.CallDuration + 1)
group by  b.ID

这是查找表。

create table DurationMap (
    ID          int identity(1,1) primary key,
    Minimum     int not null,
    Maximum     int 
)

insert  DurationMap(Minimum, Maximum) select 0,30
insert  DurationMap(Minimum, Maximum) select 30,60
insert  DurationMap(Minimum, Maximum) select 60,120
insert  DurationMap(Minimum, Maximum) select 120,180
insert  DurationMap(Minimum, Maximum) select 180,240
insert  DurationMap(Minimum, Maximum) select 240,300
insert  DurationMap(Minimum, Maximum) select 300,360
insert  DurationMap(Minimum, Maximum) select 360,420
insert  DurationMap(Minimum, Maximum) select 420,480
insert  DurationMap(Minimum, Maximum) select 480,540
insert  DurationMap(Minimum, Maximum) select 540,600
insert  DurationMap(Minimum) select 600