理解SQL原理,寫出高效的SQL語句

Sql養成一個好習慣是一筆財富
服務器君一共花費了108.284 ms進行了4次數據庫查詢,努力地為您提供了這個頁面。
試試閱讀模式?希望聽取您的建議

我們做軟件開發的,大部分人都離不開跟數據庫打交道,特別是erp開發的,跟數據庫打交道更是頻繁,存儲過程動不動就是上千行,如果數據量大,人員流動大,那么我們還能保證下一段時間系統還能流暢的運行嗎?我們還能保證下一個人能看懂我們的存儲過程嗎?

要知道sql語句,我想我們有必要知道sqlserver查詢分析器怎么執行我么sql語句的,我么很多人會看執行計劃,或者用profile來監視和調優查詢語句或者存儲過程慢的原因,但是如果我們知道查詢分析器的執行邏輯順序,下手的時候就胸有成竹,那么下手是不是有把握點呢?

查詢的邏輯執行順序

  1. FROM < left_table>
  2. ON < join_condition>
  3. < join_type> JOIN < right_table>
  4. WHERE < where_condition>
  5. GROUP BY < group_by_list>
  6. WITH {cube | rollup}
  7. HAVING < having_condition>
  8. SELECT
  9. DISTINCT
  10. ORDER BY < order_by_list>
  11. < top_specification> < select_list>

標準的SQL 的解析順序為:

  1. .FROM 子句 組裝來自不同數據源的數據
  2. .WHERE 子句 基于指定的條件對記錄進行篩選
  3. .GROUP BY 子句 將數據劃分為多個分組
  4. .使用聚合函數進行計算
  5. .使用HAVING子句篩選分組
  6. .計算所有的表達式
  7. .使用ORDER BY對結果集進行排序

執行順序

  1. FROM:對FROM子句中前兩個表執行笛卡爾積生成虛擬表vt1
  2. ON:對vt1表應用ON篩選器只有滿足< join_condition> 為真的行才被插入vt2
  3. OUTER(join):如果指定了 OUTER JOIN保留表(preserved table)中未找到的行將行作為外部行添加到vt2 生成t3如果from包含兩個以上表則對上一個聯結生成的結果表和下一個表重復執行步驟和步驟直接結束
  4. WHERE:對vt3應用 WHERE 篩選器只有使< where_condition> 為true的行才被插入vt4
  5. GROUP BY:按GROUP BY子句中的列列表對vt4中的行分組生成vt5
  6. CUBE|ROLLUP:把超組(supergroups)插入vt6 生成vt6
  7. HAVING:對vt6應用HAVING篩選器只有使< having_condition> 為true的組才插入vt7
  8. SELECT:處理select列表產生vt8
  9. DISTINCT:將重復的行從vt8中去除產生vt9
  10. ORDER BY:將vt9的行按order by子句中的列列表排序生成一個游標vc10
  11. TOP:從vc10的開始處選擇指定數量或比例的行生成vt11 并返回調用者

看到這里,那么用過linqtosql的語法有點相似啊?如果我們我們了解了sqlserver執行順序,那么我們就接下來進一步養成日常sql好習慣,也就是在實現功能同時有考慮性能的思想,數據庫是能進行集合運算的工具,我們應該盡量的利用這個工具,所謂集合運算實際就是批量運算,就是盡量減少在客戶端進行大數據量的循環操作,而用SQL語句或者存儲過程代替。

只返回需要的數據

返回數據到客戶端至少需要數據庫提取數據、網絡傳輸數據、客戶端接收數據以及客戶端處理數據等環節,如果返回不需要的數據,就會增加服務器、網絡和客戶端的無效勞動,其害處是顯而易見的,避免這類事件需要注意:

1. 橫向來看:

  1. 不要寫SELECT *的語句,而是選擇你需要的字段。
  2. 當在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. 縱向來看:

  1. 合理寫WHERE子句,不要寫沒有WHERE的SQL語句。
  2. SELECT TOP N * --沒有WHERE條件的用此替代

盡量少做重復的工作

  1. 控制同一語句的多次執行,特別是一些基礎數據的多次執行是很多程序員很少注意的。
  2. 減少多次的數據轉換,也許需要數據轉換是設計的問題,但是減少次數是程序員可以做到的。
  3. 杜絕不必要的子查詢和連接表,子查詢在執行計劃一般解釋成外連接,多余的連接表帶來額外的開銷。
  4. 合并對同一表同一條件的多次UPDATE,比如:
  5. 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'
    
  6. UPDATE操作不要拆成DELETE操作+INSERT操作的形式,雖然功能相同,但是性能差別是很大的。

注意臨時表和表變量的用法

在復雜系統中,臨時表和表變量很難避免,關于臨時表和表變量的用法,需要注意:

  1. 如果語句很復雜,連接太多,可以考慮用臨時表和表變量分步完成。
  2. 如果需要多次用到一個大表的同一部分數據,考慮用臨時表和表變量暫存這部分數據。
  3. 如果需要綜合多個表的數據,形成一個結果,可以考慮用臨時表和表變量分步匯總這多個表的數據。
  4. 其他情況下,應該控制臨時表和表變量的使用。
  5. 關于臨時表和表變量的選擇,很多說法是表變量在內存,速度快,應該首選表變量,但是在實際使用中發現,主要考慮需要放在臨時表的數據量,在數據量較多的情況下,臨時表的速度反而更快。執行時間段與預計執行時間(多長)。
  6. 關于臨時表產生使用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。

語句的時候盡量使得優化器可以使用索引。為了使得優化器能高效使用索引,寫語句的時候應該注意:

A、不要對索引字段進行運算,而要想辦法做變換,比如

	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

B、 不要對索引字段進行格式轉換

日期字段的例子:
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'不應修改

C、 不要對索引字段使用函數

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'

4. 不要對索引字段進行多字段連接

  比如:
  WHERE FAME+ '. '+LNAME='HAIWEI.YANG'
  應改為:
  WHERE FNAME='HAIWEI' AND LNAME='YANG'

多表連接的連接條件

多表連接的連接條件對索引的選擇有著重要的意義,所以我們在寫連接條件條件的時候需要特別注意。

  1. 多表連接的時候,連接條件必須寫全,寧可重復,不要缺漏。
  2. 連接條件盡量使用聚集索引
  3. 注意ON、WHERE和HAVING部分條件的區別

ON是最先執行, WHERE次之,HAVING最后,因為ON是先把不符合條件的記錄過濾后才進行統計,它就可以減少中間運算要處理的數據,按理說應該速度是最快的,WHERE也應該比 HAVING快點的,因為它過濾數據后才進行SUM,在兩個表聯接時才用ON的,所以在一個表的時候,就剩下WHERE跟HAVING比較了

考慮聯接優先順序:

  1. INNER JOIN
  2. LEFT JOIN (注:RIGHT JOIN 用 LEFT JOIN 替代)
  3. CROSS JOIN

其它注意和了解的地方有:

  1. 在IN后面值的列表中,將出現最頻繁的值放在最前面,出現得最少的放在最后面,減少判斷的次數
  2. 注意UNION和UNION ALL的區別。--允許重復數據用UNION ALL好
  3. 注意使用DISTINCT,在沒有必要時不要用
  4. TRUNCATE TABLE 與 DELETE 區別
  5. 減少訪問數據庫的次數

還有就是我們寫存儲過程,如果比較長的話,最后用標記符標開,因為這樣可讀性很好,即使語句寫的不怎么樣但是語句工整,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      

大概就寫這么多了,有錯誤的地方歡迎大家拍磚,希望交流和共享。

本文地址:http://www.snpmgr.live/librarys/veda/detail/1502,歡迎訪問原出處。

不打個分嗎?

轉載隨意,但請帶上本文地址:

http://www.snpmgr.live/librarys/veda/detail/1502

如果你認為這篇文章值得更多人閱讀,歡迎使用下面的分享功能。
小提示:您可以按快捷鍵 Ctrl + D,或點此 加入收藏

閱讀一百本計算機著作吧,少年

很多人覺得自己技術進步很慢,學習效率低,我覺得一個重要原因是看的書少了。多少是多呢?起碼得看3、4、5、6米吧。給個具體的數量,那就100本書吧。很多人知識結構不好而且不系統,因為在特定領域有一個足夠量的知識量+足夠良好的知識結構,系統化以后就足以應對大量未曾遇到過的問題。

奉勸自學者:構建特定領域的知識結構體系的路徑中再也沒有比學習該專業的專業課程更好的了。如果我的知識結構體系足以囊括面試官的大部分甚至吞并他的知識結構體系的話,讀到他言語中的一個詞我們就已經知道他要表達什么,我們可以讓他坐“上位”畢竟他是面試官,但是在知識結構體系以及心理上我們就居高臨下。

所以,閱讀一百本計算機著作吧,少年!

《C程序設計語言(第2版新版)》 克尼漢 (作者), 等 (作者, 譯者), 徐寶文 (譯者)

《C程序設計語言》(第2版新版)是由C語言的設計者Brian W.Kernighan和Dennis M.Ritchie編寫的一部介紹標準C語言及其程序設計方法的權威性經典著作。全面、系統地講述了C語言的各個特性及程序設計的基本方法,包括基本概念,類型和表達式、控制流、函數與程序結構、指針與數組、結構、輸入與輸出、UNIX系統接口、標準庫等內容。

更多計算機寶庫...

燃烧吧足球登陆