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