数据库高级查询与性能优化1,开窗函数与子查询
數據庫高級查詢與性能優化
開窗函數
對于開窗函數一般的解釋是"用于’分區’或’分組’計算的函數".聯想到聚集函數,同樣是為分組而準備的函數(與GROUP BY子句連用),這兩者有什么區別呢?
作為 ISO SQL 支持的開窗函數與聚集函數在使用聚合函數計算上其實差別不大, 其區別主要出現在兩個部分:一是在結果顯示上上面,聚集函數計算出的結果是按組別在一行輸出的,而開窗函數是在每一行上分別輸出;二是可以利用開窗,使用排名開窗函數獲取行的排名值,在OLAP系統和Web應用排序上有較多的應用.另外,兩者還在使用形式上有所不同,在編寫語句時,開窗需要在SELECT子句中針對列聲明,聚集是在主句后使用GROUP BY指定目標列.
使用開窗需要用到OVER和PARTITION BY關鍵字,基本使用方法是在SELECT中使用函數名(計算列) OVER(PARTITION BY 開窗列) AS 別名.
聚合開窗
聚合開窗與聚合函數的使用基本一致,這里使用一個實例來說明,有一張商品表,其中的存儲(商品名, 商品類別, 價格)等數值:
| a | X | 2 |
| b | X | 3 |
| c | X | 4 |
| d | Y | 9 |
| e | Y | 11 |
使用聚合函數獲取同種類別商品價格平均價格,價格之和,最高價格的語句是:
SELECT 商品類別, AVG(價格) AS 平均價格, SUM(價格) AS 價格之和, MAX(價格) AS 最高價格 FROM 商品表 GROUP BY 商品類別結果是:
| X | 3 | 9 | 4 |
| Y | 10 | 20 | 11 |
如果使用開窗函數查詢上述數據,語句應是:
SELECT 商品類別, AVG(價格) OVER(PARTITION BY 商品類別) AS 平均價格, SUM(價格) OVER(PARTITION BY 商品類別) AS 價格之和, MAX(價格) OVER(PARTITION BY 商品類別) AS 最高價格, FROM 商品表結果是:
| X | 3 | 9 | 4 |
| X | 3 | 9 | 4 |
| X | 3 | 9 | 4 |
| Y | 10 | 20 | 11 |
| Y | 10 | 20 | 11 |
上述這些冗余的結果十分令人費解,但我們在查詢的時候將商品名標識出來,就比較好理解了:
SELECT 商品名, 商品類別, AVG(價格) OVER(PARTITION BY 商品類別) AS 平均價格, SUM(價格) OVER(PARTITION BY 商品類別) AS 價格之和, MAX(價格) OVER(PARTITION BY 商品類別) AS 最高價格, FROM 商品表結果是:
| a | X | 3 | 9 | 4 |
| b | X | 3 | 9 | 4 |
| c | X | 3 | 9 | 4 |
| d | Y | 10 | 20 | 11 |
| e | Y | 10 | 20 | 11 |
可以看出,使用開窗函數,原來聚集函數每一組一行的數據顯示方式變成了每一條記錄顯示一行了.
排名開窗
排名開窗是開窗的重要用法,主要排名函數有四個.這一部分使用一列(1, 2, 3, 1)數據來簡要說明.使用排名函數,待排序的列名不是在函數的參數中指定,而是在開窗中說明.需要注意的是,開窗函數的排名方式不是在ORDER BY子句中聲明,而是在帶排名列后標注.
RANK()
RANK()排名時會考慮到值相同的情況,也就是并列,在并列后會按照絕對位置繼續排名,換句話說,排名出來的數字不是連續的.
例如SELCET value, RANK() OVER(PARTITION BY value ASC) AS RANK排名 FROM Values的結果是
| 1 | 1 |
| 1 | 1 |
| 2 | 3 |
| 3 | 4 |
DENSE_RANK()
DENSE_RANK()排名時也會考慮到并列的情況,但其返回值是連續不間斷的.
例如SELCET value, DENSE_RANK() OVER(PARTITION BY value ASC) AS DENSE_RANK排名 FROM Values的結果是
| 1 | 1 |
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
ROW_NUMBER()
此函數排名時不考慮并列的情況,其結果自然也是連續的.
例如SELCET value, ROW_NUMBER() OVER(PARTITION BY value ASC) AS ROW_NUMBER排名 FROM Values的結果是
| 1 | 1 |
| 1 | 2 |
| 2 | 3 |
| 3 | 4 |
NTILE()
NTILE()函數比較特殊,是在有序分區中劃分組來排名,需要在函數參數里指明分組的數目,排名結果是組所排名的結果.
例如將前面表格分為兩組排名,使用SELCET value, NTILE(2) OVER(PARTITION BY value ASC) NTILE排名 FROM Values的結果是
| 1 | 1 |
| 1 | 1 |
| 2 | 2 |
| 3 | 2 |
子查詢
子查詢是在SELECT語句中使用另一個SELECT語句,也被稱作內層查詢,其外層查詢也被稱作主查詢.內外層查詢沒有固定的先后查詢順序,根據不同的查詢語句情況(語義)有不同的查詢順序.
嵌套子查詢
嵌套子查詢,WHERE/HAVING子查詢,這種內層查詢(子查詢)先于外層(主)進行.基于集合的嵌套子查詢,WHERE 表達式 [NOT] IN (子查詢).比較嵌套子查詢,WHERE 表達式 比較運算符 (使用聚合函數返回單值的子查詢).以上查詢又稱作不相關子查詢,測試子查詢.
這種查詢先進行內層查詢,查詢出一個集合(WHERE ... IN ...)或者查出一個數(WHERE 比較條件),然后再進行外部查詢,外部的每一行執行WHERE中的比較語句,檢查結果是否符合條件,若符合,則該記錄進入最終的結果集,不符合則棄用.
例如利用子查詢方式查詢和A商品同類的商品:
SELECT 商品名, 價格, 類別 FROM 商品表 WHERE 類別 IN (SELECT 類別 FROM 商品表 WHERE 商品名 = 'A' ) AND 商品名 != 'A'相關子查詢
相關子查詢雖然是用在WHERE/HAVING中,但執行順序是先執行主查詢,再主查詢中逐行進行子查詢,根據子查詢的值決定主查詢中當前行是否返回在結果集合中.這個子查詢的值是布爾值,形式為WHERE [NOT] EXISTS(子查詢).此種查詢也稱為存在性測試子查詢.
在這種查詢方式中,子查詢不同于嵌套子查詢只執行一次,而是會執行很多次的.執行的次數根據主查詢返回的原始結果集決定.子查詢返回布爾值的邏輯是由子查詢中的WHERE決定的.可以理解為先產生主查詢語句中查出來的行,在主查詢完成后,逐行進行子查詢.
例如利用相關子查詢查找單次消費大于一萬元的客戶姓名:
SELECT DISTINCT 客戶表.姓名 FROM 客戶表 WHERE EXISTS (SELECT * FROM 銷售明細表WHERE 銷售明細表.銷售額 > 10000AND 銷售明細表.客戶編號 = 客戶表.客戶編號 )替代表達式子查詢
替代表達式子查詢(SELECT子查詢)通常利用子查詢中聚合函數返回一個標量值.其作用貌似是如同相關子查詢一樣利用主查詢的每一行進行查找,然而事實上其查詢條件是在查詢之前就寫死再子查詢語句中的,與主查詢無關,通常是只查詢某一個記錄時使用.其查詢結果將作為一列呈現在結果中.
例如查詢客戶編號為10086的客戶名,地址和累計消費金額:
SELECT 姓名, 地址, (SELECT SUM(銷售額) FROM 銷售明細表JOIN 客戶表 ON 客戶表.客戶編號 = 銷售明細表.客戶編號WHERE 客戶表.客戶編號 = 10086 ) FROM 客戶表 WHERE 客戶編號 = 10086派生表子查詢
派生表子查詢(FROM子查詢)作用類似于臨時表,在主查詢進行前先進性子查詢,而后主查詢以子查詢返回的表作為數據源繼續查詢,可以理解為臨時表的性能優化方式和一種方便的用法,其形式為FROM (子查詢)或者FROM (子查詢|表) [各種連接類型] JOIN (子查詢 | 表).其性能優化之處在于不必手動生成臨時表,產生的派生表在內存中用完即焚,避免在SQL Server調用tempdb數據庫,減少I/O帶來的性能損失.
例如查詢客戶編號為10086和10010的兩個用戶都購買了的商品有哪些:
SELECT 商品表.商品編號, 商品表.商品名 FROM (SELECT * FROM 銷售明細表 WHERE 客戶編號 = 10086 ) AS T1 JOIN (SELECT * FROM 銷售明細表 WHERE 客戶編號 = 10010 ) AS T2 ON T1.商品編號 = T2.商品編號 JOIN 商品表 ON T1.商品編號 = 商品表.商品編號子查詢性能問題
需要注意的是,在WHERE子句中使用聚集函數的值不可以直接調用聚集函數,需要使用子查詢的方式調用.聚合函數不能直接出現在WHERE子句中,需要時必須先利用子查詢獲取結果.
讀者會發現,這一部分許多的查詢其實可以使用聯接查詢或修改WHERE條件來實現.的確如此,但在許多復雜地查詢需求中,聯接查詢和WHERE條件并不能高效易懂地完成任務,還是需要子查詢來幫忙的.在數據量大,事務多的情況下,查詢的性能表現十分重要,子查詢和聯接查詢等等查詢方法具體用哪一個,需要結合業務邏輯,數據表結構,索引甚至是物理文件等等因素具體分析.
使用子查詢無法提高連接查詢的性能,放在存儲過程中也無法減少運算量,也無法提升查詢效率.在一定程度上可以說,只要使用到了子查詢,性能一定會有折扣(聯接查詢同理).為了改善查詢性能,就需要在進行數據庫物理設計階段,將符合3NF的關系模式進行適度的合并,人為增加一些冗余,合理地,成本最小化地用空間換時間.
參考
[1]何玉潔, 劉乃嘉. 全國計算機等級考試三級教程-數據庫技術[M]. 高等教育出版社. 2020.
[2]Ben Forta. SQL必知必會[M]. 人民郵電出版社. 2020.
[3]史嘉權. 數據庫系統概論[M]. 清華大學出版社. 2006.
[4]褚華, 霍邱艷. 軟件設計師教程[M]. 清華大學出版社. 2018.
[5]王珊, 陳紅. 數據庫系統原理教程[M]. 清華大學出版社. 1998.
[6]湯小丹, 梁紅兵, 哲鳳屏, 湯子瀛. 計算機操作系統[M]. 西安電子科技大學出版社. 2014.
總結
以上是生活随笔為你收集整理的数据库高级查询与性能优化1,开窗函数与子查询的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 学习阶段总结
- 下一篇: 设计一个串口服务器设备.《需求分析报告》