CASE语句和DECODE是等效的吗?

时间:2012-12-04 22:04:02

标签: sql oracle case

似乎简单的CASE表达式和DECODE函数是等价的,并且它们返回的结果应该是相同的。是吗?

文档中有关于simple CASE expression

的以下内容
  

简单的CASE表达式返回第一个结果   selector_value匹配选择器。剩下的表达式不是   评估。如果没有selector_value与selector匹配,则表示CASE表达式   如果存在则返回else_result,否则返回NULL。

将此与DECODE function相比较,描述似乎相同。

  

DECODE逐个将expr与每个搜索值进行比较。如果expr相等   搜索,然后Oracle数据库返回相应的结果。如果   找不到匹配项,然后Oracle返回默认值。如果省略default,   然后Oracle返回null。

由于searched CASE expression可以等同于简单,因此可以理解为相同。

这三个陈述似乎都返回相同的结果,0。

select case 1 when 2 then null else 0 end as simple_case
     , case when 1 = 2 then null else 0 end as searched_case
     , decode(1, 2, null, 0) as decode
  from dual

简单的CASE表达式和DECODE函数(以及在特定情况下搜索的CASE表达式)是否总是返回相同的结果?

3 个答案:

答案 0 :(得分:24)

简短回答,不。

接近稍长的答案。

只有出现从每个语句获得的结果是相同的。如果我们使用DUMP函数来评估返回的数据类型,您将看到我的意思:

SQL> select dump(case 1 when 2 then null else 0 end) as simple_case
  2       , dump(case when 1 = 2 then null else 0 end) as searched_case
  3       , dump(decode(1, 2, null, 0)) as decode
  4    from dual;

SIMPLE_CASE        SEARCHED_CASE      DECODE
------------------ ------------------ -----------------
Typ=2 Len=1: 128   Typ=2 Len=1: 128   Typ=1 Len=1: 48

SQL Fiddle

您可以看到DECODE的数据类型是1,而两个CASE语句“返回”数据类型2.使用Oracle的Data Type Summary,DECODE返回VARCHAR2(数据类型1),而CASE语句是“返回”数字(数据类型2)。

我认为这是因为,正如名称所示,DECODE是函数而CASE不是,这意味着它们在内部以不同的方式实现。没有真正的方法来证明这一点。

你可能认为这并没有真正影响任何事情。如果你需要它是一个数字,Oracle将隐式地将字符转换为implicit conversion rules下的数字,对吧?这也不是真的,它是won't work in a UNION,因为数据类型具有相同; Oracle不会进行任何隐式转换,以便您轻松完成任务。其次,这是Oracle关于隐式转换的说法:

  

Oracle建议您指定显式转换,而不是依赖隐式或自动转换,原因如下:

     
      
  • 使用显式数据类型转换函数时,SQL语句更容易理解。

  •   
  • 隐式数据类型转换可能会对性能产生负面影响,尤其是当列值的数据类型转换为常量的数据类型而不是相反时。

  •   
  • 隐式转换取决于它发生的上下文,并且在每种情况下可能无法以相同的方式工作。例如,从datetime值到VARCHAR2值的隐式转换可能会返回意外的年份,具体取决于NLS_DATE_FORMAT的值   参数。

  •   
  • 隐式转换的算法可能会在软件版本和Oracle产品之间发生变化。显式转换的行为更具可预测性。

  •   

这不是一个漂亮的清单;但倒数第二点让我很好地约会。如果我们采用上一个查询并将其转换为使用日期的查询:

select case sysdate when trunc(sysdate) then null 
                    else sysdate 
       end as simple_case
     , case when sysdate = trunc(sysdate) then null 
            else sysdate 
       end as searched_case
     , decode(sysdate, trunc(sysdate), null, sysdate) as decode
  from dual;

再一次,在此查询中使用DUMP,CASE语句返回数据类型12,即DATE。 DECODE已将sysdate转换为VARCHAR2。

SQL> select dump(case sysdate when trunc(sysdate) then null
  2                           else sysdate
  3              end) as simple_case
  4       , dump(case when sysdate = trunc(sysdate) then null
  5                   else sysdate
  6              end) as searched_case
  7       , dump(decode(sysdate, trunc(sysdate), null, sysdate)) as decode
  8    from dual;

SIMPLE_CASE          
---------------------------------- 
Typ=12 Len=7: 120,112,12,4,22,18,7 
SEARCHED_CASE
---------------------------------- 
Typ=12 Len=7: 120,112,12,4,22,18,7
DECODE
---------------------------------- 
Typ=1 Len=19: 50,48,49,50,45,49,50,45,48,52,32,50,49,58,49,55,58,48,54

SQL Fiddle

注意(在SQL Fiddle中)使用会话NLS_DATE_FORMAT将DATE转换为字符。

将日期隐式转换为VARCHAR2可能会导致问题。如果您打算使用TO_CHAR,要将日期转换为字符,您的查询就会在您不期望的地方中断。

SQL> select to_char( decode( sysdate
  2                         , trunc(sysdate), null
  3                         , sysdate )
  4                 , 'yyyy-mm-dd') as to_char
  5    from dual;
select to_char( decode( sysdate
                *
ERROR at line 1:
ORA-01722: invalid number

SQL Fiddle

同样,日期算术不再有效:

SQL>
SQL>
SQL> select decode(sysdate, trunc(sysdate), null, sysdate) + 1 as decode
  2    from dual;
select decode(sysdate, trunc(sysdate), null, sysdate) + 1 as decode
       *
ERROR at line 1:
ORA-01722: invalid number

SQL Fiddle

有趣的是,如果其中一个可能的结果为NULL,则DECODE仅将表达式转换为VARCHAR2。如果默认值为NULL,则不会发生这种情况。例如:

SQL> select decode(sysdate, sysdate, sysdate, null) as decode
  2    from dual;

DECODE
-------------------
2012-12-04 21:18:32

SQL> select dump(decode(sysdate, sysdate, sysdate, null)) as decode
  2    from dual;

DECODE
------------------------------------------    
Typ=13 Len=8: 220,7,12,4,21,18,32,0

SQL Fiddle

请注意,DECODE返回的数据类型为13.这没有记录,但我认为是日期算术等的日期类型。

简而言之,如果可能,请避免使用DECODE;您可能不一定得到您期望的数据类型。致quote Tom Kyte

  

解码有点模糊 - CASE非常清楚。事情是   在CASE中很容易做到解码很容易做的事情   在CASE中很容易做到与解码几乎无关。 CASE,逻辑   明智的,赢得胜利。


为了完成,DECODE和CASE之间存在两个功能差异。

  1. DECODE不能在PL / SQL中使用。
  2. CASE不能用于直接比较空值

    SQL> select case null when null then null else 1 end as case1
      2        , case when null is null then null else 1 end as case2
      3        , decode(null, null, null, 1) as decode
      4    from dual
      5         ;
    
         CASE1      CASE2 DECODE
    ---------- ---------- ------
             1
    

    SQL Fiddle

答案 1 :(得分:13)

Ben对DECODE和CASE之间的差异写了一个冗长的答案。他演示了DECODE和CASE可能会为显然相同的值集返回不同的数据类型,而没有正确解释为什么会发生这种情况。

DECODE()非常规范:它始终是第一个结果参数的数据类型。 Oracle将隐式转换应用于所有其他结果参数。如果(例如)第一个结果参数是数字且默认值是日期,则会抛出错误。

ORA-00932: inconsistent datatypes: expected NUMBER got DATE

文档中描述了这一点:find out more

在第一个场景中,第一个结果参数为NULL,Oracle决定将其视为VARCHAR2。如果我们更改它以使第一个结果参数为数字且默认值为null,则DECODE()语句将返回NUMBER; DUMP()证明了这一点。

而CASE坚持所有返回的值都具有相同的数据类型,如果不是这样,则会抛出编译错误。它不会应用隐式转换。这也包含在文档中。 Read it here

差异归结为此。将运行以下DECODE语句,CASE语句将不会:

select decode(1, 1, 1, '1') from dual;

select case 1 when 1 then 1 else '1' end from dual;

Obligatory SQL Fiddle.

答案 2 :(得分:-3)

我知道我为时已晚,但在此发帖是因为如果有人在搜索,希望它可以提供帮助。 我为此创建了一个MsSql脚本 -

Declare @Var varchar(399)='DECODE(MyColumnName,''A'',''Auto'',''M'',''Manual'')'

Begin
Declare @Count int, @Counter int=1
Declare @TempTable table (ID int identity(1,1),Items varchar(500))
Declare @SqlText varchar(max)
Select @Var=Replace(Replace(@Var,'DECODE(',''),')','')

Insert Into @TempTable
Select * FROM [dbo].[Split] ( @Var ,',')
--Select * from @TempTable
Select @Count=Count(ID) from @TempTable

While(@Counter<=@Count)
Begin
    If(@Counter=1)
    Begin
    Select @SqlText='Case ' +Items from @TempTable Where ID=1
    End

    Else If(@Counter=@Count)
    Begin
    Select @SqlText+=' Then ' +Items +' End' from @TempTable Where ID=@Counter 
    End

    Else If(@Counter%2=0)
    Begin
    Select @SqlText +=' When ' +Items from @TempTable Where ID=@Counter
    End

    Else If(@Counter%2=1)
    Begin
    Select @SqlText +=' Then ' +Items from @TempTable Where ID=@Counter
    End

    Set @Counter+=1
End

Select @SqlText SqlServerCaseStatement
End

我在上面的脚本中使用了Split函数,如果你需要这个函数你可以参考Romil的答案 - How to split a comma-separated value to columns