Oracle和MySQL竟然可以這麼寫這樣的SQL?(r12筆記第99天)
今天看到Franck Pachot 發了一個Twitter,意思是Oracle裡的SQL還能這麼寫。猛一看確實讓人有些意外。
禁不住誘惑,自己也嘗試了一番。我現在12cR2的環境中測試了一下。
Oracle Database 12c Enterprise Edition Release 12.2.0.1.0 - 64bit Production
嘗試上面的步驟,先來看看dual表。
SQL> select count(*)from dual;
COUNT(*)
----------
1
開始測試,這樣看起來不規範的SQL照樣能夠正常解析
SQL> select +count(*) from dual;
+COUNT(*)
----------
1
SQL> select -count(*)from dual;
-COUNT(*)
----------
-1SQL> select +dummy from dual;
DU
--
X
SQL> select -dummy from dual;
select -dummy from dual
*
ERROR at line 1:
ORA-01722: invalid number
如果對dual表抱有一絲幻想,那麼我們換一個數據字典cat
SQL> select count(*)from cat;
COUNT(*)
----------
8113
SQL> select +count(*)from cat;
+COUNT(*)
----------
8113SQL> select -count(*)from cat;
-COUNT(*)
----------
-8113
看起來依舊可以,我們換一個堆表。
SQL> create table test_data as select *from user_objects;
Table created.
SQL> select count(*)from test_data;
COUNT(*)
----------
51907SQL> select +count(*)from test_data;
+COUNT(*)
----------
51907SQL> select -count(*)from test_data;
-COUNT(*)
----------
-51907
換成object_id欄位
SQL> select -object_id from test_data where rownum<3;
-OBJECT_ID
----------
-16
-20SQL> select +object_id from test_data where rownum<3;
OBJECT_ID
----------
16
20
對此如果還有一些疑問,我們可以使用10053來解析一下,看看優化器是如何處理的。
SQL> alter session set events '10053 trace name context forever,level 12';
SQL> select -object_id from test_data where rownum<3;
-OBJECT_ID
----------
-16
-20
SQL> alter session set events '10053 trace name context off';
查詢轉換後的SQL如下:
Final query after transformations:******* UNPARSED QUERY IS *******
SELECT (-"TEST_DATA"."OBJECT_ID") "-OBJECT_ID" FROM "SYS"."TEST_DATA" "TEST_DATA" WHERE ROWNUM<3
*************************
而如果是字元型資料,則使用這種方式的減號就不可行了。
SQL> select -object_name from test_data where rownum<2;
select -object_name from test_data where rownum<2
*
ERROR at line 1:
ORA-01722: invalid number
如果你以為是12c裡面的一些新特性這類的,其實在10g也是類似的結果。
MySQL篇
如果你認為這是Oracle優化器的強大,其實不然,我們看看MySQL裡的表現,假設表為test_tab.
> select -count(*) from test_tab;
+-----------+
| -count(*) |
+-----------+
| -548650 |
+-----------+
1 row in set (0.39 sec)
使用運算子,可以看到也是支援的。
> select +count(*)from test_tab;
+-----------+
| +count(*) |
+-----------+
| 548650 |
+-----------+
1 row in set (0.39 sec)
如果查詢兩條資料,進行比對測試。
> select login_account from test_tab limit 2;
+-------------------------------+
| login_account |
+-------------------------------+
| [email protected] |
| [email protected] |
+-------------------------------+
2 rows in set (0.00 sec)
我們繼續使用運算子來處理。
> select -login_account from test_tab limit 2;
+----------------+
| -login_account |
+----------------+
| -180000000 |
| -111000 |
+----------------+
2 rows in set, 2 warnings (0.00 sec)
注意這裡有兩個警告,我們看看警告內容,原來內容都被處理過了。
> show warnings;
+---------+------+-------------------------------------------------------------------+
| Level | Code | Message |
+---------+------+-------------------------------------------------------------------+
| Warning | 1292 | Truncated incorrect DOUBLE value: '[email protected]' |
| Warning | 1292 | Truncated incorrect DOUBLE value: '[email protected]' |
+---------+------+-------------------------------------------------------------------+
2 rows in set (0.00 sec)
如果我們手動換一種形式,寫為0-xxx的形式,結果是一樣的。
> select 0-login_account from test_tab limit 2;
+-----------------+
| 0-login_account |
+-----------------+
| -180000000 |
| -111000 |
+-----------------+
2 rows in set, 2 warnings (0.01 sec)
這裡的結果其實涉及到sql_mode的設定,在此就不再展開了。
回到這個問題,上面的語句竟然可以解析,在優化器中是什麼樣的呢?可以使用explain extended的方式來解析,結果如下:
> explain extended select -count(*) from test_tab;
+----+-------------+----------+-------+---------------+-----------------------+---------+------+--------+----------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+-------+---------------+-----------------------+---------+------+--------+----------+-------------+
| 1 | SIMPLE | test_tab | index | NULL | ind_tmp_login_account | 303 | NULL | 548474 | 100.00 | Using index |
+----+-------------+----------+-------+---------------+-----------------------+---------+------+--------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
解析的結果如下,可以看到原本的count(*)自動轉換為了count(0),然後做了運算處理。
> show warnings;
+-------+------+----------------------------------------------------------+
| Level | Code | Message |
+-------+------+----------------------------------------------------------+
| Note | 1003 | select -(count(0)) AS `-count(*)` from `test`.`test_tab` |
+-------+------+----------------------------------------------------------+
1 row in set (0.00 sec)