我們做資料分析的,大部分人都離不開跟資料庫打交道,特別是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 (11) < top_specification> < select_list>
(10) ORDER BY < order_by_list>
(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) 不要寫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 …
(1)合理寫WHERE子句,不要寫沒有WHERE的SQL語句。
(2) SELECT TOP N * — 沒有WHERE條件的用此替代
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'
5.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引入。關於相關子查詢,應該注意:
比如:
SELECT PUB_NAME FROM PUBLISHERSWHERE PUB_ID NOT IN(SELECT PUB_ID FROM TITLESWHERE TYPE = 'BUSINESS')
- 可以改寫成:
SELECT A.PUB_NAME FROM PUBLISHERS ALEFT JOIN TITLES B ON B.TYPE = 'BUSINESS' AND A.PUB_ID=B. PUB_IDWHERE 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
比如:
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
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)
比如有人寫這樣的語句:
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快點的,因為它過濾資料後才進行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
當你已經養成良好的 SQL 撰寫習慣,下一步就是如何把查詢結果「快速轉化為洞察」。這時,一套好用的 BI 工具就能大幅提升效率。FineBI 是一款主打自助式分析的商業智慧工具,可直接串接資料庫,將 SQL 查詢結果快速轉為互動式圖表與儀表板,透過拖拉操作就能完成分析與視覺化,不再反覆手動整理報表。對於需要頻繁分析資料、向主管或團隊溝通數據結論的工作者來說,是非常實用的效率加速器。
➠ 免費試用FineBI