1. 程式人生 > >Oracle函式介紹:decode

Oracle函式介紹:decode

1、Oracle函式介紹:decode

Sql程式碼 

select sum(DECODE(C810000125,'是',1,0))/COUNT(1) 合格率 FROM t581 

統計合格率,如果 C810000125這個欄位為“是”結果1,不為是結果為0 

還可以這樣寫:

Sql程式碼 

select sum(case when C810000125 = '是' then 1 else 0 end)/COUNT(1) 合格率 FROM t581 

含義解釋: 

decode(條件,值1,返回值1,值2,返回值2,...值n,返回值n,預設值) 

該函式的含義如下: 

IF 條件=值1 THEN 

RETURN(翻譯值1) 

ELSIF 條件=值2 THEN 

RETURN(翻譯值2) 

...... 

ELSIF 條件=值n THEN 

RETURN(翻譯值n) 

ELSE 

RETURN(預設值) 

END IF 

decode(欄位或欄位的運算,值1,值2,值3) 

這個函式執行的結果是,當欄位或欄位的運算的值等於值1時,該函式返回值2,否則返回值3 

當然值1,值2,值3也可以是表示式,這個函式使得某些sql語句簡單了許多 

使用方法: 

1、比較大小 

select decode(sign(變數1-變數2),-1,變數1,變數2) from dual; --取較小值 

sign()函式根據某個值是0、正數還是負數,分別返回0、1、-1 

例如: 

變數1=10,變數2=20 

則sign(變數1-變數2)返回-1,decode解碼結果為“變數1”,達到了取較小值的目的。 

2、此函式用在SQL語句中,功能介紹如下: 

Decode函式與一系列巢狀的 IF-THEN-ELSE語句相似。base_exp與compare1,compare2等等依次進行比較。

如果base_exp和 第i個compare項匹配,就返回第i 個對應的value 。如果base_exp與任何的compare值都不匹配,則返回default。

每個compare值順次求值,如果發現一個匹配,則剩下的 compare值(如果還有的話)就都不再求值。

一個為NULL的base_exp被認為和NULL compare值等價。如果需要的話,每一個compare值都被轉換成和第一個compare 值相同的資料型別,這個資料型別也是返回值的型別。 

Decode函式在實際開發中非常的有用 

結合Lpad函式,如何使主鍵的值自動加1並在前面補0 

select LPAD(decode(count(記錄編號),0,1,max(to_number(記錄編號)+1)),14,'0') 記錄編號 from tetdmis 

eg: 

select decode(dir,1,0,1) from a1_interval 

dir 的值是1變為0,是0則變為1 

比如我要查詢某班男生和女生的數量分別是多少? 

通常我們這麼寫: 

select count(*) from 表 where 性別 = 男; 

select count(*) from 表 where 性別 = 女; 

要想顯示到一起還要union一下,太麻煩了 

用decode呢,只需要一句話 

select sum(decode(性別,男,1,0)),sum(decode(性別,女,1,0)) from 表 

補充:同事遇到一個問題,分組的條件是動態變化的,比如:一組資料最多按A、B、C三種條件分組,但是根據情況這三個條件會動態的參與到分組中,有8中情況,例如:按null,按A,按B,按C,按A、B,按A、C,按B、C,按A、B、C。

Sql如下

select t.e

       ,t.f

      ,decode(param1,'A',t.a,null) as A

      ,decode(param2,'B',t.b,null) as B

,decode(param3,'C',t.c,null)as C

  from test t

 group by

       t.e,

       t.f,

       decode(param1,'A',t.a,null),

       decode(param2,'B',t.b,null),

decode(param3,'C',t.c,null)

 order by t.e

2、詭異的DECODE函式

今天同事遇到下面一個問題: 

order by decode(column_id,1,null,2,null,3,null,column_id);

有個問題,就是當列數大於10列時,column_id 的順序成10,11,12,13,4,5,6,7,8,9了

這個排序的主要目的是讓前3列排在後面,這3列的順序無所謂。

對於小於10列的表是沒問題的:

SQL> create table t(c1 number,c2 number,c3 number,c4number,c5 number);

表已建立。 

SQL> col column_name format a20

SQL> select column_name,column_id

  2  from  user_tab_columns

  3  where table_name='T'

  4  order bydecode(column_id,1,null,2,null,3,null,column_id);

COLUMN_NAME           COLUMN_ID

-------------------- ----------

C4                           4

C5                           5

C2                           2

C1                           1

C3                           3

但是當表的列數大於10的時候就會混亂了。 

SQL> select column_name,column_id,decode(column_id,1,null,2,null,3,null,column_id) sortcolumn

  2  from  user_tab_columns

  3  where table_name='T'

  4  order bydecode(column_id,1,null,2,null,3,null,column_id)

  5  /

COLUMN_NAME           COLUMN_IDSORTCOLUMN

-------------------- ---------- --------------------

C10                         10 10

C11                         11 11

C12                         12 12

C4                           4 4

C5                           5 5

C6                           6 6

C7                           7 7

C8                           8 8

C9                           9 9

C3                           3

C2                           2

C1                           1

已選擇12行。

顯然ORACLE把SORTCOLUMN列作為為字元型別排序了。

加個TO_NUMBER即可解決這個問題。 

SQL> select column_name,column_id,decode(column_id,1,null,2,null,3,null,column_id) sortcolumn

  2  from  user_tab_columns

  3  where table_name='T'

  4  order byto_number(decode(column_id,1,null,2,null,3,null,column_id));

COLUMN_NAME           COLUMN_IDSORTCOLUMN

-------------------- ---------- --------------------

C4                           4 4

C5                           5 5

C6                           6 6

C7                           7 7

C8                           8 8

C9                           9 9

C10                         10 10

C11                         11 11

C12                         12 12

C1                           1

C3                           3

C2                           2

已選擇12行。

但是為什麼會導致這個問題,DECODE函式為何返回了字元型別。

這個問題yangtingkun大師專門寫個幾篇文章介紹。

有興趣的可以找找看看。

在這裡我借花獻佛簡單稍微說一下:

對於NULL 型別,ORACLE的預設返回型別是VARCHAR。

對於DECODE函式 ORACLE返回的型別依賴於第一個值。

如下所示: 

SQL> CREATE TABLE A AS SELECTDECODE(DUMMY,'X',1,'Y','2',DUMMY) C1, <---由於第一個返回的值1是整數型別,因此整個表示式返回整數型別

  2  DECODE(DUMMY,'X','1','Y',2,DUMMY) C2 ,<---由於第一個返回的值'1'是字元型別,因此整個表示式返回CHAR型別

  3  DECODE(DUMMY,'X',NULL,'Y','HUATENG',DUMMY)C3 FROM DUAL; <---由於第一個返回的值是NULL,因此整個表示式返回CHAR型別

表已建立。

SQL> DESC A

 名稱                                    是否為空? 型別

 ----------------------------------------- ------------------------------------

 C1                                               NUMBER

 C2                                               VARCHAR2(1)

 C3                                               VARCHAR2(7)

也正是因為DECODE函式的這種依賴於第一次的值型別作為返回型別,對於其他返回的值如果和第一個型別不匹配,可能會讓你遇到很蛋疼的問題:

SQL> DESC A;

 名稱                                    是否為空? 型別

 ----------------------------------------- ------------------------------------

 C1                                               NUMBER

 C2                                               VARCHAR2(1)

 C3                                               VARCHAR2(7)

SQL> INSERT INTO A VALUES(2,2,2);

已建立 1 行。

SQL> SELECT * FROM A;

        C1 C2 C3

---------- -- --------------

         1 1

         2 2  2

SQL> SELECT DECODE(C1,1,1,2,'E',C1) FROM A;

ERROR:

ORA-01722: 無效數字

未選定行

SQL> SELECT DECODE(C1,1,1,2,'E',C1) FROM A WHERE C1=1;

DECODE(C1,1,1,2,'E',C1)

-----------------------

                     1

SQL> SELECT DECODE(C1,1,1,2,'E',C1) FROM A WHERE C1=2;

SELECT DECODE(C1,1,1,2,'E',C1) FROM A WHERE C1=2

                      *

第 1 行出現錯誤:

ORA-01722: 無效數字

上面的問題主要是字元'E'無法轉為整數型別導致的。

3、Decode函式返回型別的確定

今天在QQ上一個朋友發出問題,說min函式返回錯誤的取值。詳細如下:一個數據表列型別為number(6,2),其中有三行記錄,分別為0,0.6和1。用min獲取最小值,得到0.6。

min是Oracle SQL的一個基礎函式,理論上不會出現這樣的Bug之類的。下面一起來模擬下實驗環境。

1、環境構建

在實驗資料庫Oracle11g環境下,構建實驗資料表t。填入實驗資料。

SQL> create table t (num number(6,2));

Table created

SQL> insert into t values (0);

1 row inserted

SQL> insert into t values (0.6);

1 row inserted

SQL> insert into t values (1);

1 row inserted

SQL> commit;

Commit complete

SQL> select * from t;

    NUM

--------

   0.00

   0.60

1.00

實驗那位兄弟的說法。

SQL> select min(num) from t;

 MIN(NUM)

----------

        0

SQL> select min(to_number(num)) from t;

MIN(TO_NUMBER(NUM))

-------------------

                 0

沒有什麼問題,詳細問了一下,獲取到了SQL結構如下。

SQL> select min(decode(num,-1,null,num)),min(num)from t;

MIN(DECODE(NUM,-1,NULL,NUM))              MIN(NUM)

---------------------------------------- ----------

.6                                               0

果然,詭異的現象發生了。

2、問題分析

一時間還是很唬人的,那麼我們先拋開min函式,單獨看資料列情況。抽絲剝繭吧。

SQL> select decode(num,-1,null,num),num from t;

DECODE(NUM,-1,NULL,NUM)                      NUM

---------------------------------------- --------

0                                           0.00

.6                                          0.60

1                                           1.00

這裡只剩下一個decode函式的使用。從含義上看,當num為-1的時候,返回null值,否則就是原有的num值。但是有兩個疑點,首先是0.60是如何轉變為.6的呢?其次就是decode函式處理列的列對其方式,數字型別預設是右對齊,只有字串是左對齊的。難道說經過decode函式處理之後,返回值變成了字串?

那麼,如果decode處理之後,變成了字串的話,我們調整一個decode的結構,看看是否是由於處理變成字串造成了問題。

SQL> selectmin(to_number(decode(num,-1,null,num))),min(num) from t;

MIN(TO_NUMBER(DECODE(NUM,-1,NU  MIN(NUM)

------------------------------ ----------

                            0         0

看來原因就在於decode函式使用處理之後,返回資料列是一個字串型別。但是decode函式命名指定了num列,返回值是什麼型別呢?

num是數字肯定沒有什麼問題?難道說疑點出現在null的返回值型別上?繼續實驗。

SQL> select decode(num,-1,num,num),num from t;

DECODE(NUM,-1,NUM,NUM)     NUM

---------------------- --------

                    0    0.00

                  0.6    0.60

                    1    1.00

果然,臨時取消掉null,decode返回型別就正常。看來真是受到了null的影響。這個時候,筆者思考一個問題,Oracle Decode函式如何確定返回值型別列呢?

SQL> select decode(num,-1,'d',num),numfrom t;

DECODE(NUM,-1,'D',NUM)                       NUM

---------------------------------------- --------

0                                           0.00

.6                                          0.60

1                                           1.00

SQL> select decode(num,-1,'k',num),to_char(num)from t;

DECODE(NUM,-1,'K',NUM)                  TO_CHAR(NUM)

---------------------------------------- ----------------------------------------

0                                       0

.6                                      .6

1                                       1

上面的實驗,讓我們得出了和null值是相同的效果。這樣,我們對decode有下面猜想:

ü       Oracle在呼叫decode函式的時候,是需要預先確定列的型別,因為畢竟出現在相同的列上;

ü       確定decode返回值型別,是依據引數中第一個條件返回型別。之後所有的返回型別都依據第一個型別進行強制型別轉換;

ü       Oracle在第一個條件返回型別為null的時候,預設將其作為字串處理;

如果三個假設成立,那麼所有問題就得到解釋。

那個朋友的SQL中,decode函式第一個可選返回值是null,Oracle識別返回型別為字元型別。之後對所有的其他返回值均使用了to_char方法類似的轉換邏輯。

那麼,往後想一步,如果Oracle decode函式真是依靠第一條件來確定列型別,其他列進行強行轉換,那麼如果出現不匹配的時候怎麼辦?

SQL> select decode(num,1,num,'k') from t;

select decode(num,1,num,'k') from t

ORA-01722:無效數字

這個案例中的decode函式,根據第一個前條件取值num是數字型別,那麼其他所有都會被強制轉換為數字型別。但是我們寫定的其他條件取值是’k’,不能進行強制型別轉換。於是報錯無效數字。

3、問題解決

瞭解了問題decode的根源,剩下的就好解釋了。min函式可以接受字串和數字。在數字型別時,依據數字型別的比較規則,選擇出0是最小值。當接受字串時,使用的是二進位制對比策略。其中.小數點的排序位最小。於是選擇出.6作為結果也就不奇怪了。

解決問題的方法很多,筆者推薦的方式是對null進行數字化處理。讓Oracle識別為數字型別。

SQL> select min(decode(num,-1,to_number(null),num)),min(num)from t;

MIN(DECODE(NUM,-1,TO_NUMBER(NU  MIN(NUM)

------------------------------ ----------

                            0         0

4、結論

通過這個案例,我們除了重新認識到decode的原理外,還有幾個收穫。

首先是要重視null值,null在Oracle中是一種很特殊的型別。在運算和函式呼叫中,都有很多特殊之處。遇到問題,要注意考慮null的因素,是我們解決問題的思路;

其次就是重視函式的本質。Decode是我們常見的函式,但是我們對一些細節缺乏思考研究。比如Decode返回值型別如何確定?這些都是細節,但是細節也反映了我們的能力和修行。