看起来简单的CASE表达式和DECODE函数是等效的,它们返回的结果应该是相同的。是吗?
关于简单的CASE表达式,文档有以下内容:
简单CASE表达式返回第一个结果selector_value与selector匹配。其余表达式不是评估。如果没有selector_value与选择器匹配,则CASE表达式如果存在,则返回else_result,否则返回NULL。
将其与DECODE函数进行比较,说明似乎完全相同。
DECODE将expr与每个搜索值逐一进行比较。如果expr相等则Oracle数据库返回相应的结果。如果未找到匹配项,则Oracle返回默认值。如果省略默认,则Oracle返回null。
由于搜索到的CASE表达式可以等效于simple,因此也可以理解为相同。
这三个语句似乎都返回了相同的结果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函数)是否总是返回相同的结果?
简短回答,无
稍长一点的答案是接近。
只有显示从每个语句中获得的结果是相同的。如果我们使用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的数据类型摘要,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 Fiddle
请注意(在SQL Fiddle中)DATE已使用会话NLS_DATE_FORMAT转换为字符。
将日期隐式转换为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
有趣的是,DECODE仅在其中一个可能的结果为NULL时才将表达式转换为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;您可能不一定会得到所期望的数据类型。引用Tom Kyte:
解码有些晦涩——CASE非常非常清楚。解码中容易做的事情在CASE中很容易做,困难或解码几乎是不可能的,在CASE中很容易做到。CASE,逻辑明智的人会轻而易举地获胜。
为了完整起见,DECODE和CASE之间存在两个功能性差异。
- DECODE不能在PL/SQL中使用
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
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 Fiddle。
我知道我来晚了,但在这里发帖是因为如果有人搜索它,希望它能有所帮助。我已经为相同的创建了一个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的答案——如何将逗号分隔的值拆分到列