你真的會玩SQL嗎?系列目錄html
最近要對數據庫進行優化,但因爲工做項目中已經不多親自寫SQL並且用的都不是很複雜的語句,因此有些生疏了,因而翻翻N年前的筆記資料,想以此來記錄回顧總結一些實用的SQL乾貨讓你們來學習,如有不對之處可提出。
記得剛出來行走江湖的時候也是隻會增、刪、改、查四大法寶,通常公司沒有多少複雜的業務,因此就夠用了。但後來看着大神會寫個幾百行的SQL存儲過程就感受本身是否是弱爆了。
現在是大數據的時代,對數據的處理要求愈來愈重視,要出各類數據報表,所以百萬數據處理速度,數據庫明顯比後臺邏輯處理的優點不是一個別。
下面進入正題,寫了多年的SQL,你真的玩會了SQL嗎?
在此我想再次提示一個數據處理的中心思想,SQL數據處理是集合思惟,不要用邏輯思惟來思考。
文中的示例來自本身的積累和TSQL2008技術內幕。
對於教條式的定義請本身去查,此處不會涉及到文鄒鄒的知識,但仍是強調一下基礎的重要性,即便你理解了全部的概念,但當組合起來用時也會一頭霧水。
邏輯查詢處理階段
在以上的10個處理步驟中, 每一步的處理都生成一個虛擬表來做爲下一步的輸入. 虛擬表對於調用者或輸出查詢來講是不存在的, 僅在最後步驟生成的表纔會返回給調用者或者輸出查詢. 若是某一子句沒有出如今SQL語句中, 這一步就被簡單跳過..
這10個具體步驟是:
1.FROM: from子句中的兩個表首先進行交叉鏈接(笛卡爾積), 生成虛擬表VT1。
2.ON: on條件做用在VT1上, 將條件爲True的行生成VT2。
3.OUTER: 若是outer join被指定, 則根據外鏈接條件, 將左表or右表or多表的未出如今VT2查詢結果中的行加入到VT2後生成VT3。
4.WHERE: VT3表中應用Where條件, 結果爲真的行用來生成VT4。
5.GROUP BY: 根據Group by指定的列, 將VT4的行組織到不一樣的組中, 生成VT5。
6.CLUB|ROLLUP: 超級組(分組以後的分組)被添加到VT5中, 生成VT6。
7.HAVING: Having用來篩選組, VT6上符合條件的組將用來生成VT7。
8.SELECT: select子句用來選擇指定的列, 並生成VT8。
9.DISTINCT: 從VT8中刪除重複的行後, VT9被生成。
10.ORDER BY: 根據Order by子句, VT9中的行被排序, 生成遊標10。
注意事項:
第一步中FROM: 須要對兩表同時存在的列添加前綴, 以避免混淆.
第二步中ON: 在SQL特有的三值邏輯(true,false,unknown)中, unkown的值也是肯定的, 只是在不一樣狀況下有時爲true, 有時爲false. 一個總的原則是: unknown的值非真即假, 非假即真. 也就是時說, unknown只能取true和false裏面的一個值, 可是unknown的相反仍是unknown.如:
在ON、WHERE和HAVING中作過濾條件時, unknown看作false;
在CHECK約束中, unknown被看作是true;
在條件中, 兩個NULL的比較結果仍是Unknown.
在UNIQUE和PRIMARY KEY約束、排序和分組中, NULL被看作是相等的. 例如Group by 將null分爲一組, 而order by將全部null排在一塊兒.
第三步中OUTER: 若是多餘兩張表, 則將VT3和FROM中的下一張表再次執行從第一步到第三步的過程.
第四步中WHERE: 因爲此刻沒有分組, 也沒有執行select因此, where子句中不能寫分組函數, 也不能使用表的別名. 而且, 只有在外鏈接時, on和where的邏輯纔是不一樣的, 所以建議鏈接條件放在on中.
第五步中GROUP BY: 若是查詢中包含Group by 子句, 那麼全部的後續操做(having, select等)都是對每一組的結果進行操做.
Group by子句中可使用組函數, 在Sql 2000中一旦使用組函數, 其後面的步驟將都不能處理, 而在
Sql2005中沒有這個限制.
第六步不經常使用, 略過.
第七步中HAVING: having表達式是僅有的分組條件. 注意: count(*)不會忽略掉null, 而count(field)會; 此外分組函數中不支持子查詢作輸入.
第八步中SELECT: 若是包含Group By子句, 那麼在第5步後將只能使用Group By子句中出現的列, 若是要使用其餘原始列則, 只能使用組函數.
另外, select在第八步才執行, 所以別名只能第八步以後才能使用, 而且只能在order by中使用.
第九步中DISTINCT: 當使用Group By子句時, 使用Distinct是多餘的, 他不會刪除任何記錄.
第十步中ORDER BY: 按Order by子句指定的列排序後, 返回遊標VC10.
別名只能在Order by子句中使用.
若是定義了Distinct子句, 則只能排序上一步中返回的表VT9, 若是沒有指定Distinct子句, 則能夠排序再也不最終結果集中的列. 例如: 若是不加Distinct則Order by能夠訪問VT7和VT8中的內容.
這一步最不一樣的是它返回的是遊標而不是表, Sql是基於集合論的, 集合中的元素師沒有順序的, 一個在表上引用Order by排序的查詢返回一個按照特定特定物理順序組織的對象—遊標. 因此對於視圖、子查詢、派生表等均不能將order by結果做爲其數據來源.
建議: 使用表的表達式時, 不容許使用order by子句的查詢, 所以除非你真的要對行排序, 不然不要使用order by 子句.
內容爲 RJ 寫的,邏輯很是清楚,值得花點時間理解,再次強調是由於複雜的集合數據處理過程當中會獲得不是你想要的結果,這時就要你本身腦殼當SQL處理器來推出結果查出問題,可能大多數寫了幾年的SQL都還沒弄明白,但到了用時仍是提早理解下,很是重要。
練習
此後用到的用例數據庫是SQL2008裏面的
用例數據庫文件:連接:http://pan.baidu.com/s/1qW1QxA0 密碼:dqxx
/*1.返回來自美國的客戶,併爲每一個客戶返回其訂單總數和商品交易總數量。 涉及到表:Sales.Customers表、Sales.Orders表,以及Sales.OrderDetails表。 指望的輸出: */ custid numorders totalqty ----------- ----------- ----------- 32 11 345 36 5 122 43 2 20 45 4 181 48 8 134 55 10 603 65 18 1383 71 31 4958 75 9 327 77 4 46 78 3 59 82 3 89 89 14 1063
1參考SQL:
--answer: select c.custid,count(distinct o.orderid) as 'numorders',sum(od.qty) as 'totalqty' from Sales.Customers as c join Sales.Orders as o on c.custid=o.custid join Sales.OrderDetails as od on o.orderid=od.orderid where c.country='USA' group by c.custid /* 1.將表Sales.Customers別名爲c和表Sales.Orders別名爲o應用ON篩選器以custid爲條件內鏈接,生成虛擬表VT1, 2.將虛擬表VT1和表Sales.OrderDetails應用ON篩選器以orderid爲條件內鏈接,生成虛擬表VT2, 3.對上一步返回的虛擬表中的全部行應用where篩選器返回知足條件c.country='USA'的虛擬表VT3, 4.應用group by子句將數據以c.custid列分組 5.處理select列表,去掉重複o.orderid再用count統計個數返回別名爲numorders的列,統計od.qty列別名totalqty */
/*2:返回客戶及其訂單信息,包括沒有下過任何訂單的客戶。 涉及到表:Sales.Customers和Sales.Orders表。 指望的輸出(按簡略的格式顯示): */ custid companyname orderid orderdate ----------- --------------- ----------- ------------------------ 85 Customer ENQZT 10248 2006-07-04 00:00:00.000 79 Customer FAPSM 10249 2006-07-05 00:00:00.000 34 Customer IBVRG 10250 2006-07-08 00:00:00.000 84 Customer NRCSK 10251 2006-07-08 00:00:00.000 ... 73 Customer JMIKW 11074 2008-05-06 00:00:00.000 68 Customer CCKOT 11075 2008-05-06 00:00:00.000 9 Customer RTXGC 11076 2008-05-06 00:00:00.000 65 Customer NYUHS 11077 2008-05-06 00:00:00.000 22 Customer DTDMN NULL NULL 57 Customer WVAXS NULL NULL
2參考SQL:
--answer: select c.custid,c.companyname,o.orderid,o.orderdate from Sales.Customers as c left join Sales.Orders as o on c.custid=o.custid /* 1.將表Sales.Customers別名爲c和表Sales.Orders別名爲o應用ON篩選器以custid爲條件左外鏈接,生成虛擬表VT1, 2.添加外部行,外部行中非保留表中的屬性被賦值爲NULL,生成虛擬表VT2 3.處理select列表,查找出c.custid,c.companyname,o.orderid,o.orderdate生成虛擬表VT3 */
/*3:返回值2007年2月12日下過訂單的客戶,以及他們的訂單。同時也返回在2007年2月12日沒有下過訂單的客戶。 涉及到表:Sales.Customers表和Sales.Orders表。 指望的輸出(按簡略格式顯示): */ custid companyname orderid orderdate ----------- --------------- ----------- ----------------------- 72 Customer AHPOP NULL NULL 58 Customer AHXHT NULL NULL 25 Customer AZJED NULL NULL 18 Customer BSVAR NULL NULL 91 Customer CCFIZ NULL NULL ... 33 Customer FVXPQ NULL NULL 53 Customer GCJSG NULL NULL 39 Customer GLLAG NULL NULL 16 Customer GYBBY NULL NULL 4 Customer HFBZG NULL NULL 5 Customer HGVLZ 10444 2007-02-12 00:00:00.000 42 Customer IAIJK NULL NULL 34 Customer IBVRG NULL NULL 63 Customer IRRVL NULL NULL 73 Customer JMIKW NULL NULL 15 Customer JUWXK NULL NULL ... 21 Customer KIDPX NULL NULL 30 Customer KSLQF NULL NULL 55 Customer KZQZT NULL NULL 71 Customer LCOUJ NULL NULL 77 Customer LCYBZ NULL NULL 66 Customer LHANT 10443 2007-02-12 00:00:00.000 38 Customer LJUCA NULL NULL 59 Customer LOLJO NULL NULL 36 Customer LVJSO NULL NULL 64 Customer LWGMD NULL NULL 29 Customer MDLWA NULL NULL ...
3參考SQL:
--answer: select c.custid,c.companyname,o.orderid,o.orderdate from Sales.Customers as c left join Sales.Orders as o on c.custid=o.custid and o.orderdate='2007-2-12' /* 1.將表Sales.Customers別名爲c和表Sales.Orders別名爲o應用ON篩選器以custid和o.orderdate='2007-2-12'爲條件左外鏈接,生成虛擬表VT1, 2.添加外部行,外部行中非保留表中的屬性被賦值爲NULL,生成虛擬表VT2 3.處理select列表,從虛擬表VT2中查找出c.custid,c.companyname,o.orderid,o.orderdate生成虛擬表VT3 */
其它乾貨下載資源已放入微信公衆號【一個碼農的平常】