目錄mysql
先來一段僞代碼,首先你能看懂麼?程序員
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>
繼續作如下的前期準備工做:sql
新建一個測試數據庫TestDB;數據庫
create database TestDB;
建立測試表table1和table2;編程
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)
準備SQL邏輯查詢測試語句服務器
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的客戶。網絡
還記得上面給出的那一長串的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>
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>
以上每一個步驟都會產生一個虛擬表,該虛擬表被用做下一個步驟的輸入。這些虛擬表對調用者(客戶端應用程序或者外部查詢)不可用。只有最後一步生成的表纔會會給調用者。若是沒有在查詢中指定某一個子句,將跳過相應的步驟。
邏輯查詢處理階段簡介:
FROM
:對 FROM 子句中的前兩個表執行笛卡爾積(Cartesian product)(交叉聯接),生成虛擬表VT1
ON
:對VT1應用ON篩選器。只有那些使<join_condition>
爲真的行才被插入VT2。OUTER(JOIN)
:如 果指定了OUTER JOIN
(相對於CROSS JOIN
或(INNER JOIN
),保留表(preserved table
:左外部聯接把左表標記爲保留表,右外部聯接把右表標記爲保留表,徹底外部聯接把兩個表都標記爲保留表)中未找到匹配的行將做爲外部行添加到 VT2,生成VT3.若是FROM子句包含兩個以上的表,則對上一個聯接生成的結果表和下一個表重複執行步驟1到步驟3,直處處理完全部的表爲止。
WHERE
:對VT3應用WHERE篩選器。只有使<where_condition>
爲true的行才被插入VT4.
GROUP BY
:按GROUP BY
子句中的列列表對VT4中的行分組,生成VT5.
CUBE|ROLLUP
:把超組(Suppergroups)插入VT5,生成VT6.
HAVING
:對VT6應用HAVING
篩選器。只有使<having_condition>
爲 true 的組纔會被插入VT7.
SELECT
:處理SELECT列表,產生VT8.
DISTINCT
:將重複的行從VT8中移除,產生VT9.
ORDER BY
:將VT9中的行按RDER BY
子句中的列列表排序,生成遊標(VC10).
TOP
:從VC10的開始處選擇指定數量或比例的行,生成表VT11,並返回調用者。
注:步驟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;
上面標出了各條查詢規則的執行前後順序,那麼各條查詢語句是如何執行的呢?
在這些 SQL 語句的執行過程當中,都會產生一個虛擬表,用來保存 SQL 語句的執行結果(這是重點),我如今就來跟蹤這個虛擬表的變化,獲得最終的查詢結果的過程,來分析整個 SQL 邏輯查詢的執行順序和過程。
第一步,執行FROM語句。咱們首先須要知道最開始從哪一個表開始的,這就是FROM告訴咱們的。如今有了 <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 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表上進行。
對添加外部行獲得的VT3進行WHERE過濾,只有符合
+-------------+----------+----------+-------------+ | customer_id | city | order_id | customer_id | +-------------+----------+----------+-------------+ | 163 | hangzhou | 1 | 163 | | 163 | hangzhou | 2 | 163 | | tx | hangzhou | 6 | tx | | baidu | hangzhou | NULL | NULL | +-------------+----------+----------+-------------+
可是在使用WHERE子句時,須要注意如下兩點:
因爲數據尚未分組,所以如今還不能在WHERE過濾器中使用where_condition=MIN(col)這類對分組統計的過濾;
因爲尚未進行列的選取操做,所以在SELECT中使用列的別名也是不被容許的,
如:SELECT city as c FROM t WHERE c='shanghai';
是不容許出現的。
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
子句主要和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 a.customer_id, COUNT(b.order_id) as total_orders
,從虛擬表VT6中選擇出咱們須要的內容。咱們將獲得如下內容:
+-------------+--------------+ | customer_id | total_orders | +-------------+--------------+ | baidu | 0 | | tx | 1 | +-------------+--------------+
不,尚未完,這只是虛擬表VT7。
若是在查詢中指定了DISTINCT
子句,則會建立一張內存臨時表(若是內存放不下,就須要存放在硬盤了)。這張臨時表的表結構和上一步產生的虛擬表 VT7 是同樣的,不一樣的是對進行DISTINCT
操做的列增長了一個惟一索引,以此來除重複數據。
因爲個人測試SQL語句中並無使用 DISTINCT
,因此,在該查詢中,這一步不會生成一個虛擬表。
對虛擬表中的內容按照指定的列進行排序,而後返回一個新的虛擬表,咱們執行測試SQL語句中的ORDER BY total_orders DESC
,就會獲得如下內容:
+-------------+--------------+ | customer_id | total_orders | +-------------+--------------+ | tx | 1 | | baidu | 0 | +-------------+--------------+
能夠看到這是對 total_orders 列進行降序排列的。上述結果會存儲在VT8中。
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 TITLES WHERE NOT EXISTS (SELECT TITLE_ID FROM SALES WHERE TITLE_ID = TITLES.TITLE_ID) 能夠改寫成: SELECT TITLE FROM TITLES LEFT JOIN SALES ON SALES.TITLE_ID = TITLES.TITLE_ID WHERE SALES.TITLE_ID IS NULL 2. 若是保證子查詢沒有重複 ,IN、EXISTS的相關子查詢能夠用INNER JOIN 代替。 好比: SELECT PUB_NAME FROM PUBLISHERS WHERE PUB_ID IN (SELECT PUB_ID FROM TITLES WHERE TYPE = 'BUSINESS') 能夠改寫成: SELECT A.PUB_NAME --SELECT DISTINCT A.PUB_NAME FROM PUBLISHERS A INNER JOIN TITLES B ON B.TYPE = 'BUSINESS' AND A.PUB_ID=B. PUB_ID 3. IN的相關子查詢用EXISTS代替 好比 SELECT PUB_NAME FROM PUBLISHERS WHERE 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' AND PUB_ID= PUBLISHERS.PUB_ID) 4. 不要用COUNT(*)的子查詢判斷是否存在記錄,最好用LEFT JOIN或者EXISTS 好比有人寫這樣的語句: SELECT JOB_DESC FROM JOBS WHERE (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_ID WHERE EMPLOYEE.EMP_ID IS NULL 還有 SELECT JOB_DESC FROM JOBS WHERE (SELECT COUNT(*) FROM EMPLOYEE WHERE JOB_ID=JOBS.JOB_ID)<>0 應該寫成: SELECT JOB_DESC FROM JOBS WHERE 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*2 SELECT 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
,在沒有必要時不要用
相同點:
1.truncate和不帶where子句的delete、以及drop都會刪除表內的數據。 2.drop、truncate都是DDL語句(數據定義語言),執行後會自動提交。
不一樣點:
1. truncate 和 delete 只刪除數據不刪除表的結構(定義) drop 語句將刪除表的結構被依賴的約束(constrain)、觸發器(trigger)、索引(index);依賴於該表的存儲過程/函數將保留,可是變爲 invalid 狀態。 2. delete 語句是數據庫操做語言(dml),這個操做會放到 rollback segement 中,事務提交以後才生效;若是有相應的 trigger,執行的時候將被觸發。 truncate、drop 是數據庫定義語言(ddl),操做當即生效,原數據不放到 rollback segment 中,不能回滾,操做不觸發 trigger。 3.delete 語句不影響表所佔用的 extent,高水線(high watermark)保持原位置不動 drop 語句將表所佔用的空間所有釋放。 truncate 語句缺省狀況下見空間釋放到 minextents個 extent,除非使用reuse storage;truncate 會將高水線復位(回到最開始)。 4.速度,通常來講: drop> truncate > delete 5.安全性:當心使用 drop 和 truncate,尤爲沒有備份的時候.不然哭都來不及 使用上,想刪除部分數據行用 delete,注意帶上where子句. 回滾段要足夠大. 想刪除表,固然用 drop 想保留表而將全部數據刪除,若是和事務無關,用truncate便可。若是和事務有關,或者想觸發trigger,仍是用delete。 若是是整理表內部的碎片,能夠用truncate跟上reuse stroage,再從新導入/插入數據。 6.delete是DML語句,不會自動提交。drop/truncate都是DDL語句,執行後會自動提交。 七、TRUNCATE TABLE 在功能上與不帶 WHERE 子句的 DELETE 語句相同:兩者均刪除表中的所有行。但 TRUNCATE TABLE 比 DELETE 速度快,且使用的系統和事務日誌資源少。 DELETE 語句每次刪除一行,並在事務日誌中爲所刪除的每行記錄一項。 TRUNCATE TABLE 經過釋放存儲表數據所用的數據頁來刪除數據,而且只在事務日誌中記錄頁的釋放。 八、TRUNCATE TABLE 刪除表中的全部行,但表結構及其列、約束、索引等保持不變。新行標識所用的計數值重置爲該列的種子。若是想保留標識計數值,請改用 DELETE。若是要刪除表定義及其數據,請使用 DROP TABLE 語句。 九、對於由 FOREIGN KEY 約束引用的表,不能使用 TRUNCATE TABLE,而應使用不帶 WHERE 子句的 DELETE 語句。因爲 TRUNCATE TABLE 不記錄在日誌中,因此它不能激活觸發器。 十、TRUNCATE TABLE 不能用於參與了索引視圖的表。