【转】SQL 语句执行顺序
From:http://www.jellythink.com/archives/924
Oracle-SQL語句執行原理和完整過程詳解:https://wenku.baidu.com/view/398bc427964bcf84b8d57b00.html
詳解一條 SQL?語句的執行過程:http://www.cnblogs.com/cdf-opensource-007/p/6502556.html
步步深入:MySQL架構總覽->查詢執行流程->SQL解析順序:http://www.cnblogs.com/annsshadow/p/5037667.html
sql語句查詢執行順序:http://blog.csdn.net/bitcarmanlee/article/details/51004767
?
準備工作
先來一段偽代碼,首先你能看懂么?
SELECT DISTINCT <select_list>
FROM <left_table>
<join_type> JOIN <right_table>
ON <join_condition>
WHERE <where_condition>
GROUP BY <group_by_list>
HAVING <having_condition>
ORDER BY <order_by_condition>
LIMIT <limit_number>
如果你知道每個關鍵字的意思和作用,并且你還用過的話,那再好不過了。但是,你知道這些語句,它們的執行順序你清楚么?
下面一切測試操作都是在MySQL數據庫上完成,關于MySQL數據庫的一些簡單操作,請閱讀一下文章:
- 《MySQL掃盲篇》
- 《MySQL存儲引擎介紹》
- 《MySQL數據類型和屬性》
- 《MySQL處理數據庫和表的常用命令》
繼續做以下的前期準備工作:
CREATE TABLE table1
(
customer_id VARCHAR(10) NOT NULL,
city VARCHAR(10) NOT NULL,
PRIMARY KEY(customer_id)
)ENGINE=INNODB DEFAULT CHARSET=UTF8;
CREATE TABLE table2
(
order_id INT NOT NULL auto_increment,
customer_id VARCHAR(10),
PRIMARY KEY(order_id)
)ENGINE=INNODB DEFAULT CHARSET=UTF8;
INSERT INTO table1(customer_id,city) VALUES('163','hangzhou');
INSERT INTO table1(customer_id,city) VALUES('9you','shanghai');
INSERT INTO table1(customer_id,city) VALUES('tx','hangzhou');
INSERT INTO table1(customer_id,city) VALUES('baidu','hangzhou');
INSERT INTO table2(customer_id) VALUES('163');
INSERT INTO table2(customer_id) VALUES('163');
INSERT INTO table2(customer_id) VALUES('9you');
INSERT INTO table2(customer_id) VALUES('9you');
INSERT INTO table2(customer_id) VALUES('9you');
INSERT INTO table2(customer_id) VALUES('tx');
INSERT INTO table2(customer_id) VALUES(NULL);
準備工作做完以后,table1和table2看起來應該像下面這樣:
mysql> select * from table1;
+-------------+----------+
| customer_id | city |
+-------------+----------+
| 163 | hangzhou |
| 9you | shanghai |
| baidu | hangzhou |
| tx | hangzhou |
+-------------+----------+
4 rows in set (0.00 sec)
mysql> select * from table2;
+----------+-------------+
| order_id | customer_id |
+----------+-------------+
| 1 | 163 |
| 2 | 163 |
| 3 | 9you |
| 4 | 9you |
| 5 | 9you |
| 6 | tx |
| 7 | NULL |
+----------+-------------+
7 rows in set (0.00 sec)
SELECT a.customer_id, COUNT(b.order_id) as total_orders
FROM table1 AS a
LEFT JOIN table2 AS b
ON a.customer_id = b.customer_id
WHERE a.city = 'hangzhou'
GROUP BY a.customer_id
HAVING count(b.order_id) < 2
ORDER BY total_orders DESC;
使用上述SQL查詢語句來獲得來自杭州,并且訂單數少于2的客戶。
好吧,這些測試表和測試數據均來自《MySQL技術內幕:SQL編程》,這應該不算抄襲吧,借鑒借鑒啊。
萬事俱備,只欠東風。接下來開始這篇文章最正式的部分吧。
SQL邏輯查詢語句執行順序
?
還記得上面給出的那一長串的SQL邏輯查詢規則么?那么,到底哪個先執行,哪個后執行呢?現在,我先給出一個查詢語句的執行順序:
(7) SELECT /* 處理SELECT列表,產生 VT7 */
(8) DISTINCT <select_list> /* 將重復的行從 VT7 中刪除,產品 VT8 */
(1) FROM <left_table> /* 對FROM子句中的表執行笛卡爾積(交叉聯接),生成虛擬表 VT1。 */
(3) <join_type> JOIN <right_table> /* 如果指定了OUTER JOIN(相對于CROSS JOIN或INNER JOIN),
保留表中未找到匹配的行將作為外部行添加到 VT2,生成 VT3。
如果FROM子句包含兩個以上的表,
則對上一個聯接生成的結果表和下一個表重復執行步驟1到步驟3,
直到處理完所有的表位置。 */
(2) ON <join_condition> /* 對 VT1 應用 ON 篩選器,只有那些使為真才被插入到 VT2。 */
(4) WHERE <where_condition> /* 對 VT3 應用 WHERE 篩選器,只有使為true的行才插入VT4。 */
(5) GROUP BY <group_by_list> /* 按 GROUP BY子句中的列列表對 VT4 中的行進行分組,生成 VT5 */
(6) HAVING <having_condition> /* 對 VT5 應用 HAVING 篩選器,只有使為true的組插入到 VT6 */
(9) ORDER BY <order_by_condition> /* 將 VT8 中的行按 ORDER BY子句中的列列表順序,生成一個游標(VC10),
生成表TV11,并返回給調用者。 */
(10) LIMIT <limit_number>
上面在每條語句的前面都標明了執行順序號,不要問我怎么知道這個順序的。我也是讀各種“武林秘籍”才得知的,如果你有功夫,去閱讀一下MySQL的源碼,也會得出這個結果的。
Oracle SQL語句執行順序
(8)SELECT?(9)DISTINCT??(11)<Top?Num>?<select?list>
(1)FROM?[left_table]
(3)<join_type>?JOIN?<right_table>
(2)ON?<join_condition>
(4)WHERE?<where_condition>
(5)GROUP?BY?<group_by_list>
(6)WITH?<CUBE?|?RollUP>
(7)HAVING?<having_condition>
(10)ORDER?BY?<order_by_list>
?
? ? ? ? 以上每個步驟都會產生一個虛擬表,該虛擬表被用作下一個步驟的輸入。這些虛擬表對調用者(客戶端應用程序或者外部查詢)不可用。只有最后一步生成的表才會會給調用者。如果沒有在查詢中指定某一個子句,將跳過相應的步驟。
?
邏輯查詢處理階段簡介
注:步驟10,按ORDER BY子句中的列列表排序上步返回的行,返回游標VC10.這一步是第一步也是唯一 一步可以使用SELECT列表中的列別名的步驟。這一步不同于其它步驟的 是,它不返回有效的表,而是返回一個游標。SQL是基于集合理論的。集合不會預先對它的行排序,它只是成員的邏輯集合,成員的順序無關緊要。對表進行排序 的查詢可以返回一個對象,包含按特定物理順序組織的行。ANSI把這種對象稱為游標。理解這一步是正確理解SQL的基礎。
因為這一步不返回表(而是返回游標),使用了ORDER BY子句的查詢不能用作表表達式。表表達式包括:視圖、內聯表值函數、子查詢、派生表和共用表達式。它的結果必須返回給期望得到物理記錄的客戶端應用程序。例如,下面的派生表查詢無效,并產生一個錯誤:
select * from(select orderid,customerid from orders order by orderid) as d下面的視圖也會產生錯誤
create view my_view as select * from orders order by orderid????? 在SQL中,表表達式中不允許使用帶有ORDER BY子句的查詢,而在T—SQL中卻有一個例外(應用TOP選項)。
????? 所以要記住,不要為表中的行假設任何特定的順序。換句話說,除非你確定要有序行,否則不要指定ORDER BY 子句。排序是需要成本的,SQL Server需要執行有序索引掃描或使用排序運行符。
?
?
以上就是一條sql的執行過程,同時我們在書寫查詢sql的時候應當遵守以下順序。
SELECT XXX FROM XXX WHERE XXX GROUP BY XXX HAVING XXX ORDER BY XXX LIMIT XXX;上面標出了各條查詢規則的執行先后順序,那么各條查詢語句是如何執行的呢?
?
執行FROM語句
?
在這些SQL語句的執行過程中,都會產生一個虛擬表,用來保存SQL語句的執行結果(這是重點),我現在就來跟蹤這個虛擬表的變化,得到最終的查詢結果的過程,來分析整個SQL邏輯查詢的執行順序和過程。
第一步,執行FROM語句。我們首先需要知道最開始從哪個表開始的,這就是FROM告訴我們的?,F在有了<left_table>和<right_table>兩個表,我們到底從哪個表開始,還是從兩個表進行某種聯系以后再開始呢?它們之間如何產生聯系呢?——笛卡爾積
關于什么是笛卡爾積,請自行Google補腦。經過FROM語句對兩個表執行笛卡爾積,會得到一個虛擬表,暫且叫VT1(vitual table 1),內容如下:
+-------------+----------+----------+-------------+
| customer_id | city | order_id | customer_id |
+-------------+----------+----------+-------------+
| 163 | hangzhou | 1 | 163 |
| 9you | shanghai | 1 | 163 |
| baidu | hangzhou | 1 | 163 |
| tx | hangzhou | 1 | 163 |
| 163 | hangzhou | 2 | 163 |
| 9you | shanghai | 2 | 163 |
| baidu | hangzhou | 2 | 163 |
| tx | hangzhou | 2 | 163 |
| 163 | hangzhou | 3 | 9you |
| 9you | shanghai | 3 | 9you |
| baidu | hangzhou | 3 | 9you |
| tx | hangzhou | 3 | 9you |
| 163 | hangzhou | 4 | 9you |
| 9you | shanghai | 4 | 9you |
| baidu | hangzhou | 4 | 9you |
| tx | hangzhou | 4 | 9you |
| 163 | hangzhou | 5 | 9you |
| 9you | shanghai | 5 | 9you |
| baidu | hangzhou | 5 | 9you |
| tx | hangzhou | 5 | 9you |
| 163 | hangzhou | 6 | tx |
| 9you | shanghai | 6 | tx |
| baidu | hangzhou | 6 | tx |
| tx | hangzhou | 6 | tx |
| 163 | hangzhou | 7 | NULL |
| 9you | shanghai | 7 | NULL |
| baidu | hangzhou | 7 | NULL |
| tx | hangzhou | 7 | NULL |
+-------------+----------+----------+-------------+
總共有28(table1的記錄條數 * table2的記錄條數)條記錄。這就是VT1的結果,接下來的操作就在VT1的基礎上進行。
執行ON過濾
?
執行完笛卡爾積以后,接著就進行ON a.customer_id = b.customer_id條件過濾,根據ON中指定的條件,去掉那些不符合條件的數據,得到VT2表,內容如下:
+-------------+----------+----------+-------------+
| customer_id | city | order_id | customer_id |
+-------------+----------+----------+-------------+
| 163 | hangzhou | 1 | 163 |
| 163 | hangzhou | 2 | 163 |
| 9you | shanghai | 3 | 9you |
| 9you | shanghai | 4 | 9you |
| 9you | shanghai | 5 | 9you |
| tx | hangzhou | 6 | tx |
+-------------+----------+----------+-------------+
VT2就是經過ON條件篩選以后得到的有用數據,而接下來的操作將在VT2的基礎上繼續進行。
添加外部行
?
這一步只有在連接類型為OUTER JOIN時才發生,如LEFT OUTER JOIN、RIGHT OUTER JOIN和FULL OUTER JOIN。在大多數的時候,我們都是會省略掉OUTER關鍵字的,但OUTER表示的就是外部行的概念。
下面從網上找到一張很形象的關于‘SQL JOINS'的解釋圖
LEFT OUTER JOIN把左表記為保留表,得到的結果為:
+-------------+----------+----------+-------------+
| customer_id | city | order_id | customer_id |
+-------------+----------+----------+-------------+
| 163 | hangzhou | 1 | 163 |
| 163 | hangzhou | 2 | 163 |
| 9you | shanghai | 3 | 9you |
| 9you | shanghai | 4 | 9you |
| 9you | shanghai | 5 | 9you |
| tx | hangzhou | 6 | tx |
| baidu | hangzhou | NULL | NULL |
+-------------+----------+----------+-------------+
RIGHT OUTER JOIN把右表記為保留表,得到的結果為:
+-------------+----------+----------+-------------+
| customer_id | city | order_id | customer_id |
+-------------+----------+----------+-------------+
| 163 | hangzhou | 1 | 163 |
| 163 | hangzhou | 2 | 163 |
| 9you | shanghai | 3 | 9you |
| 9you | shanghai | 4 | 9you |
| 9you | shanghai | 5 | 9you |
| tx | hangzhou | 6 | tx |
| NULL | NULL | 7 | NULL |
+-------------+----------+----------+-------------+
FULL OUTER JOIN把左右表都作為保留表,得到的結果為:
+-------------+----------+----------+-------------+
| customer_id | city | order_id | customer_id |
+-------------+----------+----------+-------------+
| 163 | hangzhou | 1 | 163 |
| 163 | hangzhou | 2 | 163 |
| 9you | shanghai | 3 | 9you |
| 9you | shanghai | 4 | 9you |
| 9you | shanghai | 5 | 9you |
| tx | hangzhou | 6 | tx |
| baidu | hangzhou | NULL | NULL |
| NULL | NULL | 7 | NULL |
+-------------+----------+----------+-------------+
添加外部行的工作就是在VT2表的基礎上添加保留表中被過濾條件過濾掉的數據,非保留表中的數據被賦予NULL值,最后生成虛擬表VT3。
由于我在準備的測試SQL查詢邏輯語句中使用的是LEFT JOIN,過濾掉了以下這條數據:
| baidu | hangzhou | NULL | NULL |現在就把這條數據添加到VT2表中,得到的VT3表如下:
+-------------+----------+----------+-------------+
| customer_id | city | order_id | customer_id |
+-------------+----------+----------+-------------+
| 163 | hangzhou | 1 | 163 |
| 163 | hangzhou | 2 | 163 |
| 9you | shanghai | 3 | 9you |
| 9you | shanghai | 4 | 9you |
| 9you | shanghai | 5 | 9you |
| tx | hangzhou | 6 | tx |
| baidu | hangzhou | NULL | NULL |
+-------------+----------+----------+-------------+
接下來的操作都會在該VT3表上進行。
執行WHERE過濾
?
對添加外部行得到的VT3進行WHERE過濾,只有符合<where_condition>的記錄才會輸出到虛擬表VT4中。當我們執行WHERE a.city = 'hangzhou'的時候,就會得到以下內容,并存在虛擬表VT4中:
+-------------+----------+----------+-------------+
| customer_id | city | order_id | customer_id |
+-------------+----------+----------+-------------+
| 163 | hangzhou | 1 | 163 |
| 163 | hangzhou | 2 | 163 |
| tx | hangzhou | 6 | tx |
| baidu | hangzhou | NULL | NULL |
+-------------+----------+----------+-------------+
但是在使用WHERE子句時,需要注意以下兩點:
執行GROUP BY分組
?
GROU BY子句主要是對使用WHERE子句得到的虛擬表進行分組操作。我們執行測試語句中的GROUP BY a.customer_id,就會得到以下內容:
+-------------+----------+----------+-------------+
| customer_id | city | order_id | customer_id |
+-------------+----------+----------+-------------+
| 163 | hangzhou | 1 | 163 |
| baidu | hangzhou | NULL | NULL |
| tx | hangzhou | 6 | tx |
+-------------+----------+----------+-------------+
得到的內容會存入虛擬表VT5中,此時,我們就得到了一個VT5虛擬表,接下來的操作都會在該表上完成。
執行HAVING過濾
?
HAVING子句主要和GROUP BY子句配合使用,對分組得到的VT5虛擬表進行條件過濾。當我執行測試語句中的HAVING count(b.order_id) < 2時,將得到以下內容:
+-------------+----------+----------+-------------+
| customer_id | city | order_id | customer_id |
+-------------+----------+----------+-------------+
| baidu | hangzhou | NULL | NULL |
| tx | hangzhou | 6 | tx |
+-------------+----------+----------+-------------+
這就是虛擬表VT6。
SELECT列表
?
現在才會執行到SELECT子句,不要以為SELECT子句被寫在第一行,就是第一個被執行的。
我們執行測試語句中的SELECT a.customer_id, COUNT(b.order_id) as total_orders,從虛擬表VT6中選擇出我們需要的內容。我們將得到以下內容:
+-------------+--------------+
| customer_id | total_orders |
+-------------+--------------+
| baidu | 0 |
| tx | 1 |
+-------------+--------------+
不,還沒有完,這只是虛擬表VT7。
執行DISTINCT子句
?
如果在查詢中指定了DISTINCT子句,則會創建一張內存臨時表(如果內存放不下,就需要存放在硬盤了)。這張臨時表的表結構和上一步產生的虛擬表VT7是一樣的,不同的是對進行DISTINCT操作的列增加了一個唯一索引,以此來除重復數據。
由于我的測試SQL語句中并沒有使用DISTINCT,所以,在該查詢中,這一步不會生成一個虛擬表。
執行ORDER BY子句
?
對虛擬表中的內容按照指定的列進行排序,然后返回一個新的虛擬表,我們執行測試SQL語句中的ORDER BY total_orders DESC,就會得到以下內容:
+-------------+--------------+
| customer_id | total_orders |
+-------------+--------------+
| tx | 1 |
| baidu | 0 |
+-------------+--------------+
可以看到這是對total_orders列進行降序排列的。上述結果會存儲在VT8中。
執行LIMIT子句
?
LIMIT子句從上一步得到的VT8虛擬表中選出從指定位置開始的指定行數據。對于沒有應用ORDER BY的LIMIT子句,得到的結果同樣是無序的,所以,很多時候,我們都會看到LIMIT子句會和ORDER BY子句一起使用。
MySQL數據庫的LIMIT支持如下形式的選擇:
LIMIT n, m表示從第n條記錄開始選擇m條記錄。而很多開發人員喜歡使用該語句來解決分頁問題。對于小數據,使用LIMIT子句沒有任何問題,當數據量非常大的時候,使用LIMIT n, m是非常低效的。因為LIMIT的機制是每次都是從頭開始掃描,如果需要從第60萬行開始,讀取3條數據,就需要先掃描定位到60萬行,然后再進行讀取,而掃描的過程是一個非常低效的過程。所以,對于大數據處理時,是非常有必要在應用層建立一定的緩存機制(貌似現在的大數據處理,都有緩存哦)。各位,請期待我的緩存方面的文章哦。
?
至此SQL的解析之旅就結束了,上圖總結一下:
?
習慣養成
了解了 SQL 執行順序,那么我們就接下來進一步養成日常 sql好習慣,也就是在實現功能同時有考慮性能的思想,數據庫是能進行集合運算的工具,我們應該盡量的利用這個工具,所謂集合運算實際就是批量運算,就是盡量減少在客戶端進行大數據量的循環操作,而用SQL語句或者存儲過程代替。
?
只返回需要的數據
返回數據到客戶端至少需要數據庫提取數據、網絡傳輸數據、客戶端接收數據以及客戶端處理數據等環節。
如果返回不需要的數據,就會增加服務器、網絡和客戶端的無效勞動,其害處是顯而易見的,避免這類事件需要注意:
1. 橫向來看:
不要寫SELECT * 的語句,而是選擇你需要的字段。
當在SQL語句中連接多個表時, 請使用表的別名并把別名前綴于每個Column上。這樣一來,就可以減少解析的時間并減少那些由Column歧義引起的語法錯誤。
如有表table1(ID,col1)和table2 (ID,col2)
?
Select A.ID, A.col1, B.col2
-- Select A.ID, col1, col2 –不要這么寫,不利于將來程序擴展
from table1 A inner join table2 B on A.ID=B.ID Where …
?
2. 縱向來看
合理寫WHERE子句,不要寫沒有WHERE的SQL語句。SELECT TOP N * --沒有WHERE條件的用此替代
盡量少做重復的工作。控制同一語句的多次執行,特別是一些基礎數據的多次執行是很多程序員很少注意的。
減少多次的數據轉換,也許需要數據轉換是設計的問題,但是減少次數是程序員可以做到的。
杜絕不必要的子查詢和連接表,子查詢在執行計劃一般解釋成外連接,多余的連接表帶來額外的開銷。
合并對同一表同一條件的多次UPDATE,比如:
?
UPDATE EMPLOYEE SET FNAME='HAIWER'
WHERE EMP_ID=' VPA30890F' UPDATE EMPLOYEE SET LNAME='YANG'
WHERE EMP_ID=' VPA30890F'
-- 這兩個語句應該合并成以下一個語句
UPDATE EMPLOYEE SET FNAME='HAIWER',LNAME='YANG' WHERE EMP_ID=' VPA30890F'
?
UPDATE操作不要拆成DELETE操作+INSERT操作的形式,雖然功能相同,但是性能差別是很大的。
?
注意 臨時表 和 表變量 的用法
在復雜系統中,臨時表和表變量很難避免,關于臨時表和表變量的用法,需要注意:
如果語句很復雜,連接太多,可以考慮用臨時表和表變量分步完成。
如果需要多次用到一個大表的同一部分數據,考慮用臨時表和表變量暫存這部分數據。
如果需要綜合多個表的數據,形成一個結果,可以考慮用臨時表和表變量分步匯總這多個表的數據。
其他情況下,應該控制臨時表和表變量的使用。
關于臨時表和表變量的選擇,很多說法是表變量在內存,速度快,應該首選表變量,
但是在實際使用中發現,主要考慮需要放在臨時表的數據量,在數據量較多的情況下,臨時表的速度反而更快。執行時間段與預計執行時間(多長)。
關于臨時表產生使用SELECT INTO 和 CREATE TABLE + INSERT INTO 的選擇。
一般情況下,SELECT INTO會比CREATE TABLE + INSERT INTO的方法快很多,
但是SELECT INTO會鎖定TEMPDB的系統表SYSOBJECTS、SYSINDEXES、SYSCOLUMNS,
在多用戶并發環境下,容易阻塞其他進程,
所以我的建議是,在并發系統中,盡量使用CREATE TABLE + INSERT INTO,而大數據量的單個語句使用中,使用SELECT INTO。
?
子查詢的用法
子查詢是一個 SELECT 查詢,它嵌套在 SELECT、INSERT、UPDATE、DELETE 語句或其它子查詢中。
任何允許使用表達式的地方都可以使用子查詢,子查詢可以使我們的編程靈活多樣,可以用來實現一些特殊的功能。
但是在性能上,往往一個不合適的子查詢用法會形成一個性能瓶頸。
如果子查詢的條件中使用了其外層的表的字段,這種子查詢就叫作相關子查詢。
相關子查詢可以用IN、NOT IN、EXISTS、NOT EXISTS引入。
關于相關子查詢,應該注意:
1. NOT IN、NOT EXISTS的相關子查詢可以改用LEFT JOIN代替寫法。
?
比如:SELECT PUB_NAME FROM PUBLISHERS WHERE PUB_ID NOT IN (SELECT PUB_ID FROM TITLES WHERE TYPE = 'BUSINESS') 可以改寫成:SELECT A.PUB_NAME FROM PUBLISHERS A LEFT JOIN TITLES B ON B.TYPE = 'BUSINESS' AND A.PUB_ID=B. PUB_ID WHERE B.PUB_ID IS NULL 又比如:SELECT TITLE FROM TITLESWHERE NOT EXISTS(SELECT TITLE_ID FROM SALESWHERE TITLE_ID = TITLES.TITLE_ID) 可以改寫成:SELECT TITLEFROM TITLES LEFT JOIN SALESON SALES.TITLE_ID = TITLES.TITLE_IDWHERE SALES.TITLE_ID IS NULL?
2. 如果保證子查詢沒有重復 ,IN、EXISTS的相關子查詢可以用INNER JOIN 代替。
比如:
?
SELECT PUB_NAMEFROM PUBLISHERSWHERE PUB_ID IN(SELECT PUB_IDFROM TITLESWHERE TYPE = 'BUSINESS') 可以改寫成:SELECT A.PUB_NAME --SELECT DISTINCT A.PUB_NAMEFROM PUBLISHERS A INNER JOIN TITLES BON B.TYPE = 'BUSINESS' ANDA.PUB_ID=B. PUB_ID?
3. IN的相關子查詢用EXISTS代替
比如
?
SELECT PUB_NAME FROM PUBLISHERSWHERE PUB_ID IN(SELECT PUB_ID FROM TITLES WHERE TYPE = 'BUSINESS') 可以用下面語句代替:SELECT PUB_NAME FROM PUBLISHERS WHERE EXISTS(SELECT 1 FROM TITLES WHERE TYPE = 'BUSINESS' ANDPUB_ID= PUBLISHERS.PUB_ID)?
4. 不要用COUNT(*)的子查詢判斷是否存在記錄,最好用LEFT JOIN或者EXISTS
?
比如有人寫這樣的語句:SELECT JOB_DESC FROM JOBSWHERE (SELECT COUNT(*) FROM EMPLOYEE WHERE JOB_ID=JOBS.JOB_ID)=0 應該寫成:SELECT JOBS.JOB_DESC FROM JOBS LEFT JOIN EMPLOYEE ON EMPLOYEE.JOB_ID=JOBS.JOB_IDWHERE EMPLOYEE.EMP_ID IS NULL 還有SELECT JOB_DESC FROM JOBSWHERE (SELECT COUNT(*) FROM EMPLOYEE WHERE JOB_ID=JOBS.JOB_ID)<>0 應該寫成:SELECT JOB_DESC FROM JOBSWHERE EXISTS (SELECT 1 FROM EMPLOYEE WHERE JOB_ID=JOBS.JOB_ID)?
?
盡量使用索引
建立索引后,并不是每個查詢都會使用索引,在使用索引的情況下,索引的使用效率也會有很大的差別。只要我們在查詢語句中沒有強制指定索引,索引的選擇和使用方法是SQLSERVER的優化器自動作的選擇,而它選擇的根據是查詢語句的條件以及相關表的統計信息,這就要求我們在寫SQL語句的時候盡量使得優化器可以使用索引。為了使得優化器能高效使用索引,寫語句的時候應該注意:
不要對索引字段進行運算,而要想辦法做變換
?
SELECT ID FROM T WHERE NUM/2=100應改為:SELECT ID FROM T WHERE NUM=100*2SELECT ID FROM T WHERE NUM/2=NUM1如果NUM有索引應改為:SELECT ID FROM T WHERE NUM=NUM1*2如果NUM1有索引則不應該改。發現過這樣的語句:SELECT 年,月,金額 FROM 結余表 WHERE 100*年+月=2010*100+10應該改為:SELECT 年,月,金額 FROM 結余表 WHERE 年=2010 AND月=10?
不要對索引字段進行格式轉換
?
日期字段的例子:WHERE CONVERT(VARCHAR(10), 日期字段,120)='2010-07-15'應該改為WHERE日期字段〉='2010-07-15' AND 日期字段<'2010-07-16'ISNULL轉換的例子:WHERE ISNULL(字段,'')<>''應改為:WHERE字段<>''WHERE ISNULL(字段,'')=''不應修改WHERE ISNULL(字段,'F') ='T'應改為: WHERE字段='T'WHERE ISNULL(字段,'F')<>'T'不應修改?
不要對索引字段使用函數
?
WHERE LEFT(NAME, 3)='ABC' 或者WHERE SUBSTRING(NAME,1, 3)='ABC'應改為: WHERE NAME LIKE 'ABC%'日期查詢的例子:WHERE DATEDIFF(DAY, 日期,'2010-06-30')=0應改為:WHERE 日期>='2010-06-30' AND 日期 <'2010-07-01'WHERE DATEDIFF(DAY, 日期,'2010-06-30')>0應改為:WHERE 日期 <'2010-06-30'WHERE DATEDIFF(DAY, 日期,'2010-06-30')>=0應改為:WHERE 日期 <'2010-07-01'WHERE DATEDIFF(DAY, 日期,'2010-06-30')<0應改為:WHERE 日期>='2010-07-01'WHERE DATEDIFF(DAY, 日期,'2010-06-30')<=0應改為:WHERE 日期>='2010-06-30'?
不要對索引字段進行多字段連接
?
比如:WHERE FAME+ '. '+LNAME='HAIWEI.YANG'應改為:WHERE FNAME='HAIWEI' AND LNAME='YANG'?
?
多表連接的連接條件
多表連接的連接條件對索引的選擇有著重要的意義,所以我們在寫連接條件的時候需要特別注意。
多表連接的時候,連接條件必須寫全,寧可重復,不要缺漏。
連接條件盡量使用聚集索引
注意ON、WHERE和HAVING部分條件的區別
ON是最先執行, WHERE次之,HAVING最后。因為ON是先把不符合條件的記錄過濾后才進行統計,它就可以減少中間運算要處理的數據,按理說應該速度是最快的,
WHERE也應該比 HAVING快點的,因為它過濾數據后才進行SUM,在兩個表聯接時才用ON的,所以在一個表的時候,就剩下WHERE跟HAVING比較了
?
考慮聯接優先順序
INNER JOIN
LEFT JOIN (注:RIGHT JOIN 用 LEFT JOIN 替代)
CROSS JOIN
其它注意和了解的地方有
在IN后面值的列表中,將出現最頻繁的值放在最前面,出現得最少的放在最后面,減少判斷的次數
注意UNION和UNION ALL的區別。--允許重復數據用UNION ALL好
注意使用DISTINCT,在沒有必要時不要用
?
TRUNCATE TABLE 與 DELETE 區別
減少訪問數據庫的次數
還有就是我們寫存儲過程,如果比較長的話,最后用標記符標開,因為這樣可讀性很好,即使語句寫的不怎么樣但是語句工整,C# 有region,sql我比較喜歡用的就是:
--startof 查詢在職人數
sql語句
--end of
正式機器上我們一般不能隨便調試程序,但是很多時候程序在我們本機上沒問題,
但是進正式系統就有問題,但是我們又不能隨便在正式機器上操作,那么怎么辦呢?
我們可以用回滾來調試我們的存儲過程或者是sql語句,從而排錯。
BEGIN TRAN
UPDATE a SET 字段=''
ROLLBACK
作業存儲過程我一般會加上下面這段,這樣檢查錯誤可以放在存儲過程,如果執行錯誤回滾操作。
但是如果程序里面已經有了事務回滾,那么存儲過程就不要寫事務了,這樣會導致事務回滾嵌套降低執行效率,
但是我們很多時候可以把檢查放在存儲過程里,這樣有利于我們解讀這個存儲過程,和排錯。
BEGIN TRANSACTION
--事務回滾開始
--檢查報錯
IF ( @@ERROR > 0 )
BEGIN
--回滾操作
ROLLBACK TRANSACTION
RAISERROR('刪除工作報告錯誤', 16, 3)
RETURN
END
--結束事務
COMMIT TRANSACTION?
總結
以上是生活随笔為你收集整理的【转】SQL 语句执行顺序的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 支付宝还信用卡超过还款日才到账怎么办
- 下一篇: 唐人转债什么时候上市? 唐人转债申购指南