17

看起来简单的 CASE 表达式和 DECODE 函数是等价的,它们返回的结果应该是相同的。他们是吗?

该文档对简单的 CASE 表达式有以下说明:

简单的 CASE 表达式返回 selector_value 匹配选择器的第一个结果。剩余的表达式不被计算。如果没有 selector_value 匹配 selector,则 CASE 表达式如果存在则返回 else_result,否则返回 NULL。

将此与DECODE 功能进行比较,描述似乎相同。

DECODE 将 expr 与每个搜索值一一进行比较。如果 expr 等于一次搜索,则 Oracle 数据库返回相应的结果。如果未找到匹配项,则 Oracle 返回默认值。如果省略默认值,则 Oracle 返回 null。

由于搜索到的 CASE 表达式可以等同于简单的,这也可以解释为相同。

这三个语句似乎都返回相同的结果,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 表达式)是否总是返回相同的结果?

4

3 回答 3

25

简短的回答,不。

稍微长一点的答案是差不多。

似乎从每个语句获得的结果是相同的如果我们使用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小提琴

您可以看到 DECODE 的数据类型是 1,而两个 CASE 语句“返回”数据类型为 2。使用 Oracle 的数据类型摘要,DECODE 返回一个 VARCHAR2(数据类型 1),而 CASE 语句“返回” " 数字(数据类型 2)。

我认为这是因为,顾名思义,DECODE 是一个函数,而 CASE 不是,这意味着它们在内部的实现方式不同。没有真正的方法可以证明这一点。

你可能认为这并没有真正影响任何事情。如果你需要它是一个数字,Oracle会在隐式转换规则下将字符隐式转换为数字,对吗?这也不是真的,它不会在 UNION 中工作,因为数据类型必须相同;Oracle 不会进行任何隐式转换以方便您。其次,这是 Oracle 关于隐式转换的说法:

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

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

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

  • 隐式转换取决于它发生的上下文,并且可能不会在每种情况下都以相同的方式工作。例如,从日期时间值到 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小提琴

请注意(在 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小提琴

同样,日期算术不再起作用:

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小提琴

有趣的是,如果可能的结果之一为 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小提琴

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

简而言之,尽可能避免 DECODE;您可能不一定会得到您期望的数据类型。引用汤姆凯特的话:

解码有些晦涩——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小提琴

于 2012-12-04T22:04:02.377 回答
13

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

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

ORA-00932: inconsistent datatypes: expected NUMBER got DATE

这在文档中有所描述:了解更多

在第一种情况下,第一个结果参数是 NULL,Oracle 决定将其视为 VARCHAR2。如果我们将其更改为第一个结果参数为数字且默认值为 null,则 DECODE() 语句将返回一个 NUMBER;一个 DUMP() 证明是这样的。

而 CASE 坚持所有返回的值都具有相同的数据类型,如果不是这种情况,将引发编译错误。它不会应用隐式转换。这也包含在文档中。在这里阅读

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

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

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

强制性的 SQL 小提琴。

于 2012-12-05T04:07:22.010 回答
-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

于 2014-05-27T11:49:50.917 回答