日期轉換函式的格式引數大小寫規則 (自yangtingkun)
首先建立一個簡單的測試環境:
SQL> CREATE TABLE T (ID NUMBER, START_TIME DATE);
Table created.
SQL> INSERT INTO T (ID, START_TIME) SELECT ROWNUM, CREATED FROM DBA_OBJECTS;
36001 rows created.
SQL> COMMIT;
Commit complete.
SQL> CREATE INDEX IND_T_TIME ON T (TO_CHAR(START_TIME, 'YYYY-MM-DD'));
Index created.
SQL> EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'T', METHOD_OPT => 'FOR ALL INDEXED COLUMNS')
PL/SQL procedure successfully completed.
SQL> ALTER SESSION SET NLS_DATE_LANGUAGE = AMERICAN;
Session altered.
SQL> ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS';
Session altered.
SQL> SET AUTOT ON EXP
SQL> SELECT * FROM T WHERE TO_CHAR(START_TIME, 'YYYY-MM-DD') = '2006-01-18';
ID START_TIME
---------- -------------------
33873 2006-01-18 11:20:47
34280 2006-01-18 11:20:48
35990 2006-01-18 14:40:26
35991 2006-01-18 14:45:20
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT ptimizer=CHOOSE (Cost=9 Card=342 Bytes=7524)
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=9 Card=342 Bytes=7524)
2 1 INDEX (RANGE SCAN) OF 'IND_T_TIME' (NON-UNIQUE) (Cost=1 Card=342)
SQL> SELECT * FROM T WHERE TO_CHAR(START_TIME, 'yyyy-mm-dd') = '2006-01-18';
ID START_TIME
---------- -------------------
33873 2006-01-18 11:20:47
34280 2006-01-18 11:20:48
35990 2006-01-18 14:40:26
35991 2006-01-18 14:45:20
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT ptimizer=CHOOSE (Cost=10 Card=360 Bytes=7920)
1 0 TABLE ACCESS (FULL) OF 'T' (Cost=10 Card=360 Bytes=7920)
SQL> SELECT /*+ INDEX (T IND_T_TIME) */ * FROM T
2 WHERE TO_CHAR(START_TIME, 'yyyy-mm-dd') = '2006-01-18';
ID START_TIME
---------- -------------------
33873 2006-01-18 11:20:47
34280 2006-01-18 11:20:48
35990 2006-01-18 14:40:26
35991 2006-01-18 14:45:20
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT ptimizer=CHOOSE (Cost=10 Card=360 Bytes=7920)
1 0 TABLE ACCESS (FULL) OF 'T' (Cost=10 Card=360 Bytes=7920)
問題產生了。由於函式索引一個常數輸入引數的不同,造成了Oracle無法使用函式索引。這時,Oracle認為索引掃描得到的結果是不正確的,因此,即使使用HINT,Oracle也不會採用索引掃描。使這個查詢語句使用索引的辦法就是在增加日期轉換格式為小寫的函式索引。
SQL> CREATE INDEX IND_T_LOWER ON T (TO_CHAR(START_TIME, 'yyyy-mm-dd'));
Index created.
SQL> EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'T', METHOD_OPT => 'FOR ALL INDEXED COLUMNS')
PL/SQL procedure successfully completed.
SQL> SELECT * FROM T WHERE TO_CHAR(START_TIME, 'yyyy-mm-dd') = '2006-01-18';
ID START_TIME
---------- -------------------
33873 2006-01-18 11:20:47
34280 2006-01-18 11:20:48
35990 2006-01-18 14:40:26
35991 2006-01-18 14:45:20
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT ptimizer=CHOOSE (Cost=9 Card=342 Bytes=7524)
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=9 Card=342 Bytes=7524)
2 1 INDEX (RANGE SCAN) OF 'IND_T_LOWER' (NON-UNIQUE) (Cost=1 Card=342)
問題似乎解決了,但是新的問題出現了,如果輸入的日期格式是大小寫混寫怎麼樣。
而且lfree的測試發現了一個更有趣的現象。
SQL> SELECT * FROM T WHERE TO_CHAR(START_TIME, 'YYYY-MM-Dd') = '2006-01-18';
ID START_TIME
---------- -------------------
33873 2006-01-18 11:20:47
34280 2006-01-18 11:20:48
35990 2006-01-18 14:40:26
35991 2006-01-18 14:45:20
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT ptimizer=CHOOSE (Cost=10 Card=360 Bytes=7920)
1 0 TABLE ACCESS (FULL) OF 'T' (Cost=10 Card=360 Bytes=7920)
SQL> SELECT * FROM T WHERE TO_CHAR(START_TIME, 'yyyy-mm-dD') = '2006-01-18';
ID START_TIME
---------- -------------------
33873 2006-01-18 11:20:47
34280 2006-01-18 11:20:48
35990 2006-01-18 14:40:26
35991 2006-01-18 14:45:20
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT ptimizer=CHOOSE (Cost=9 Card=342 Bytes=7524)
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=9 Card=342 Bytes=7524)
2 1 INDEX (RANGE SCAN) OF 'IND_T_LOWER' (NON-UNIQUE) (Cost=1 Card=342)
仔細觀察上面兩個SQL語句,當格式為YYYY-MM-Dd時,無法使用YYYY-MM-DD格式的索引。這個很容易理解,輸入不同嗎。但是格式為yyyy-mm-dD時,卻可以使用yyyy-mm-dd的索引,這是為什麼呢?
經過一系列測試後,總結出了日期轉換函式格式引數的大小寫規則(終於引入正題了)。並發現一開始對CBO函式索引的理解還不算很清楚,對CBO的聰明程度有點低估了(當然,總體來說,還不算太聰明)。
剛開始測試時發現,即使是下面這種查詢也可以使用索引:
SQL> SELECT * FROM T WHERE TO_CHAR(START_TIME, 'yYYY-mM-dD') = '2006-01-18';
ID START_TIME
---------- -------------------
33873 2006-01-18 11:20:47
34280 2006-01-18 11:20:48
35990 2006-01-18 14:40:26
35991 2006-01-18 14:45:20
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT ptimizer=CHOOSE (Cost=9 Card=342 Bytes=7524)
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=9 Card=342 Bytes=7524)
2 1 INDEX (RANGE SCAN) OF 'IND_T_LOWER' (NON-UNIQUE) (Cost=1 Card=342)
似乎Oracle只關係第一個字母。但是綜合考慮YYYY-MM-Dd的情況,似乎又不是這樣。於是得到了Oracle會找到第一個小寫字母為止的推論。
在測試過程中,找到了一個更好的方法來確定Oracle使用字串轉換的格式。
SQL> SET AUTOT OFF
SQL> EXPLAIN PLAN FOR
2 SELECT * FROM T WHERE TO_CHAR(START_TIME, 'yYYY-mM-dD') = '2006-01-18';
Explained.
SQL> SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY());
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------
----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost |
----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 342 | 7524 | 9 |
| 1 | TABLE ACCESS BY INDEX ROWID| T | 342 | 7524 | 9 |
|* 2 | INDEX RANGE SCAN | IND_T_LOWER | 342 | | 1 |
----------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access(TO_CHAR("T"."START_TIME",'yyyy-mm-dd')='2006-01-18')
Note: cpu costing is off
15 rows selected.
透過看執行計劃後面的Predicate Information,可以清楚的看到Oracle使用的格式是yyyy-mm-dd而不是yYYY-mM-dD。
繼續測試:
SQL> EXPLAIN PLAN FOR
2 SELECT * FROM T WHERE TO_CHAR(START_TIME, 'YyYY-MM-DD') = '2006-01-18';
Explained.
SQL> SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY());
PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------
--------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost |
--------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 360 | 7920 | 10 |
|* 1 | TABLE ACCESS FULL | T | 360 | 7920 | 10 |
--------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(TO_CHAR("T"."START_TIME",'Yyyy-MM-DD')='2006-01-18')
Note: cpu costing is off
14 rows selected.
上面這種情況也是對的,但是下面列出的情況又不符合了。
SQL> EXPLAIN PLAN FOR
2 SELECT * FROM T WHERE TO_CHAR(START_TIME, 'YYyY-MM-DD') = '2006-01-18';
Explained.
SQL> SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY());
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 342 | 7524 | 9 |
| 1 | TABLE ACCESS BY INDEX ROWID| T | 342 | 7524 | 9 |
|* 2 | INDEX RANGE SCAN | IND_T_TIME | 342 | | 1 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access(TO_CHAR("T"."START_TIME",'YYYY-MM-DD')='2006-01-18')
Note: cpu costing is off
15 rows selected.
顯然,這種情況又和剛才得到的推論衝突了。似乎這種規律只對前兩位有效。
突然我明白了Oracle所定義的規則。由於英文會出現首字母大寫的情況,所以Oracle在判定的時候首先判斷第一位的大小寫狀態,如果是小寫,就不繼續判斷後面的內容了,而把整個字串都當作小寫。如果第一位是大寫,Oracle繼續判斷第二位,如果也是大寫,那麼Oracle就把整個字串都當作大寫,如果第二位是小寫,則Oracle認為這種情況是首字母大寫,就把第一個字母當作大寫,而剩餘字元做小寫出來。
如果有興趣可以對SELECT TO_CHAR(SYSDATE, 'Month') FROM DUAL;進行測試,不管給出的Month的大小寫如何變,Oracle的輸出結果只有三種,大寫、小寫和首字母大寫。
SQL> SELECT TO_CHAR(SYSDATE, 'Month') FROM DUAL;
TO_CHAR(S
---------
January
SQL> SELECT TO_CHAR(SYSDATE, 'MOnth') FROM DUAL;
TO_CHAR(S
---------
JANUARY
SQL> SELECT TO_CHAR(SYSDATE, 'MoNTH') FROM DUAL;
TO_CHAR(S
---------
January
SQL> SELECT TO_CHAR(SYSDATE, 'mONTH') FROM DUAL;
TO_CHAR(S
---------
january
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/22034023/viewspace-662849/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- SpringMVC的引數繫結-日期格式轉換SpringMVC
- string大小寫轉換函式函式
- EXCEL中日期格式轉換為文字格式-函式TEXTExcel函式
- SQLserver中用convert函式轉換日期格式SQLServer函式
- 函式呼叫的引數規則與解包函式
- MySQL字串函式 字串大小寫轉換MySql字串函式
- 使用正則 轉換大小寫
- 大小寫轉換函式(財務應用)函式
- js日期轉換函式JS函式
- 日期格式的轉換
- 日期轉換為raw的函式函式
- sql 日期格式轉換SQL
- ABAP日期格式轉換
- 在LoadRunner中轉換字串大小寫的C語言函式字串C語言函式
- SimpleDateFormat日期格式轉換的使用ORM
- 日期格式正規表示式
- 大小寫互換-"數字字串"轉換成數字字串
- C#日期格式轉換C#
- 兩個控制顯示格式的函式(自動換行、自動省略) (轉)函式
- 筆記:隱式轉換規則筆記
- numtoyminterval函式——數字轉換函式函式
- SQL 日期格式相關函式SQL函式
- mysql 獲取當前日期函式及時間格式化引數詳解MySql函式
- par函式cex引數-控制文字和點的大小函式
- to_char函式格式轉換參考函式
- 字串-大小寫轉換字串
- JavaScript 時間日期格式轉換JavaScript
- Excel函式應用教程:函式的引數(轉)Excel函式
- oracle資料隱式轉換規則Oracle
- Oracle的日期函式(轉)Oracle函式
- 函式定義、函式的引數、函式的預設引數函式
- js 轉換大小寫的方法JS
- jquery height()和width()函式的引數格式jQuery函式
- JS中使用正規表示式替換物件裡的大小寫JS物件
- 字元函式、數字函式和日期函式字元函式
- SQL SERVER 日期格式化、日期和字串轉換SQLServer字串
- JS日期格式化轉換方法JS
- ORACLE TO_CHAR()函式中日期格式的使用Oracle函式