八個(gè)不可不知的SQL高級(jí)方法
結(jié)構(gòu)化查詢(xún)語(yǔ)言(SQL)是一種廣泛使用的工具,用于管理和操作數(shù)據(jù)庫(kù)。基本的SQL查詢(xún)簡(jiǎn)單易學(xué),但掌握高級(jí)SQL技術(shù)可以將您的數(shù)據(jù)分析和管理能力提升到新的高度。
高級(jí)SQL技術(shù)是指一系列功能和函數(shù),使您能夠?qū)?shù)據(jù)執(zhí)行復(fù)雜操作,例如聚合、連接、子查詢(xún)、窗口函數(shù)和遞歸查詢(xún)。
通過(guò)深入了解SQL的高級(jí)特性和技巧,您可以更有效地進(jìn)行數(shù)據(jù)分析和管理,為您的工作帶來(lái)更大的價(jià)值。
本文將詳細(xì)介紹以下技術(shù),并使用具體且易于理解的示例。
1. 窗口函數(shù)
窗口函數(shù)支持在與當(dāng)前行相關(guān)的一組行上執(zhí)行計(jì)算,可以根據(jù)指定的窗口定義進(jìn)行聚合、排序和分析操作。這種計(jì)算方式可以提供更靈活和精確的數(shù)據(jù)分析能力。
例如;有一個(gè)名為orders的表,其中包含以下列:order_id、customer_id、order_date和order_amount。您想要計(jì)算每個(gè)客戶(hù)的銷(xiāo)售總額,按其訂單日期排序。您可以使用SUM窗口函數(shù)來(lái)實(shí)現(xiàn)這一點(diǎn):
SELECT order_id, customer_id, order_date, order_amount,
SUM(order_amount) OVER (
PARTITION BY customer_id
ORDER BY order_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS running_total
FROM orders;
在此示例中,SUM函數(shù)應(yīng)用于order_amount列,并按customer_id列進(jìn)行分區(qū)。這意味著每個(gè)客戶(hù)的累計(jì)銷(xiāo)售額將分別計(jì)算。
ORDER BY子句指定應(yīng)使用訂單日期來(lái)確定每個(gè)分區(qū)內(nèi)行的順序。這意味著將按客戶(hù)訂單的順序計(jì)算累計(jì)銷(xiāo)售額。
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW子句指定計(jì)算的窗口框架應(yīng)包括從分區(qū)開(kāi)始到當(dāng)前行為止的所有行。這意味著將從每個(gè)客戶(hù)的第一筆訂單開(kāi)始計(jì)算累計(jì)銷(xiāo)售額,直到包括當(dāng)前訂單。
查詢(xún)的結(jié)果將是一個(gè)包含與orders表相同列的表,以及一個(gè)名為running_total的附加列,其中包含每個(gè)客戶(hù)的累計(jì)銷(xiāo)售額,按其訂單日期排序。
通過(guò)在SQL中使用窗口函數(shù),您可以對(duì)數(shù)據(jù)執(zhí)行復(fù)雜計(jì)算,并深入了解業(yè)務(wù)。此示例演示了如何為每個(gè)客戶(hù)計(jì)算累計(jì)銷(xiāo)售額,但是您可以使用窗口函數(shù)執(zhí)行許多其他類(lèi)型的計(jì)算,例如計(jì)算移動(dòng)平均值、排名數(shù)據(jù)等。
2. 公共表達(dá)式(CTEs)
公共表達(dá)式(CTEs)支持您在SQL查詢(xún)中定義一個(gè)臨時(shí)結(jié)果集,并將其命名為一個(gè)表,以便在后續(xù)的SQL語(yǔ)句中引用和使用該臨時(shí)結(jié)果集。這種方式可以提高查詢(xún)的可讀性和可維護(hù)性,并使查詢(xún)邏輯更加模塊化和靈活。
假設(shè)有一個(gè)名為employees的表,其中包含以下列:employee_id、employee_name、department_id和salary。您想要計(jì)算每個(gè)部門(mén)的平均工資,然后找到其工資高于部門(mén)平均工資的員工。為此,您可以使用CTE首先計(jì)算每個(gè)部門(mén)的平均工資,然后在第二個(gè)查詢(xún)中使用該CTE查找其工資高于部門(mén)平均工資的員工。
WITH department_avg_salary AS (
SELECT department_id, AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id
)
SELECT employee_id, employee_name, salary, department_avg_salary.avg_salary
FROM employees
INNER JOIN department_avg_salary ON employees.department_id = department_avg_salary.department_id
WHERE salary > department_avg_salary.avg_salary;
在此示例中,第一個(gè)查詢(xún)定義了一個(gè)名為department_avg_salary的CTE。該CTE使用AVG函數(shù)和GROUP BY子句計(jì)算每個(gè)部門(mén)的平均工資,將員工按其部門(mén)分組。
然后,第二個(gè)查詢(xún)將department_avg_salary CTE視為表格,并將其與employees表格在department_id列上連接。結(jié)果由WHERE子句過(guò)濾,僅包括其工資高于其部門(mén)平均工資的員工。在這種情況下使用CTE的優(yōu)點(diǎn)在于它允許您將問(wèn)題分解為兩個(gè)步驟:首先計(jì)算每個(gè)部門(mén)的平均工資,然后選擇其工資高于其部門(mén)平均工資的員工。通過(guò)將計(jì)算分成兩個(gè)步驟,查詢(xún)更易于閱讀和維護(hù)。
CTEs也可以用于許多其他情況,例如遞歸查詢(xún)、復(fù)雜連接等。通過(guò)使用CTE,您可以使SQL查詢(xún)更易于閱讀和理解。
3. 聚合函數(shù)
聚合函數(shù)是用于對(duì)一組值進(jìn)行計(jì)算并返回單個(gè)結(jié)果值的函數(shù)。它們可以在表的多行或多列之間執(zhí)行計(jì)算,并且能夠以有意義的方式對(duì)數(shù)據(jù)進(jìn)行匯總。在SQL中,最常見(jiàn)的聚合函數(shù)包括SUM(求和)、AVG(平均值)、MIN(最小值)、MAX(最大值)和COUNT(計(jì)數(shù))。這些函數(shù)在數(shù)據(jù)分析和報(bào)告中非常實(shí)用,可以幫助我們快速獲得對(duì)數(shù)據(jù)集的總結(jié)統(tǒng)計(jì)信息。
例如,有一個(gè)名為sales的表,其中包含以下列:sale_id、product_id、sale_date、sale_amount和region。您想要計(jì)算每個(gè)產(chǎn)品的總銷(xiāo)售額和平均銷(xiāo)售額,以及每個(gè)地區(qū)的暢銷(xiāo)產(chǎn)品。為此,您可以使用聚合函數(shù)按產(chǎn)品和地區(qū)分組銷(xiāo)售,并計(jì)算總銷(xiāo)售額和平均銷(xiāo)售額,以及找到每個(gè)地區(qū)的暢銷(xiāo)產(chǎn)品。
SELECT
product_id,
AVG(sale_amount) AS avg_sale_amount,
SUM(sale_amount) AS total_sale_amount,
region,
RANK() OVER (PARTITION BY region ORDER BY SUM(sale_amount) DESC) AS rank
FROM sales
GROUP BY product_id, region;
在此示例中,查詢(xún)有三個(gè)聚合函數(shù):AVG、SUM和RANK。
AVG函數(shù)計(jì)算每個(gè)產(chǎn)品和地區(qū)的平均銷(xiāo)售額,而SUM函數(shù)計(jì)算每個(gè)產(chǎn)品和地區(qū)的總銷(xiāo)售額。GROUP BY子句按產(chǎn)品和地區(qū)分組銷(xiāo)售。
RANK函數(shù)用于查找每個(gè)地區(qū)的暢銷(xiāo)產(chǎn)品。OVER子句指定應(yīng)單獨(dú)對(duì)每個(gè)地區(qū)進(jìn)行排名,而PARTITION BY子句指定要對(duì)數(shù)據(jù)進(jìn)行分區(qū)的列(在本例中為region)。ORDER BY子句指定排名應(yīng)基于每個(gè)地區(qū)中每種產(chǎn)品的銷(xiāo)售金額總和,并按降序排列。
查詢(xún)結(jié)果包括product_id、region、total_sale_amount、avg_sale_amount和rank列。rank列指示每個(gè)地區(qū)中每種產(chǎn)品基于總銷(xiāo)售額的排名,排名第一的暢銷(xiāo)產(chǎn)品在每個(gè)地區(qū)都是1。
使用聚合函數(shù),在這種情況下,優(yōu)點(diǎn)在于它們?cè)试S您對(duì)數(shù)據(jù)進(jìn)行分組和匯總,并計(jì)算有用的指標(biāo),例如總銷(xiāo)售額和平均銷(xiāo)售額。RANK函數(shù)還允許您查找每個(gè)地區(qū)的暢銷(xiāo)產(chǎn)品,這對(duì)于識(shí)別趨勢(shì)和改進(jìn)機(jī)會(huì)非常有用。
聚合函數(shù)也可以用于許多其他情況,例如計(jì)算最小值和最大值、計(jì)數(shù)記錄等。通過(guò)使用聚合函數(shù),您可以使SQL查詢(xún)更加強(qiáng)大和靈活。
4. 透視表
透視表是一種用于從較大的表格中進(jìn)行數(shù)據(jù)匯總和聚合,以便更方便進(jìn)行分析的表格。它可以將數(shù)據(jù)從行轉(zhuǎn)換為列,并以更有意義的方式展示數(shù)據(jù)。
在SQL中,我們可以使用PIVOT運(yùn)算符創(chuàng)建透視表。該運(yùn)算符可以根據(jù)指定的列對(duì)數(shù)據(jù)進(jìn)行匯總,并以表格的形式呈現(xiàn)結(jié)果,使數(shù)據(jù)更易于理解和分析。透視表為我們提供了一種靈活且直觀(guān)的方式來(lái)匯總和展示數(shù)據(jù),從而幫助我們更好地理解數(shù)據(jù)的關(guān)系和趨勢(shì)。
例如:
SELECT
customer_id,
[1] AS Product1,
[2] AS Product2,
[3] AS Product3,
[4] AS Product4,
[5] AS Product5
FROM (
SELECT
customer_id,
product_id,
order_quantity
FROM orders
) p
PIVOT (
SUM(order_quantity)
FOR product_id IN ([1], [2], [3], [4], [5])
) AS pvt;
在上面的示例中,查詢(xún)使用PIVOT運(yùn)算符按產(chǎn)品ID旋轉(zhuǎn)數(shù)據(jù),每個(gè)客戶(hù)都有一個(gè)列。SUM函數(shù)用于計(jì)算每個(gè)客戶(hù)訂購(gòu)的每種產(chǎn)品的總數(shù)量。
子查詢(xún)p用于從orders表中提取必要的列。然后將PIVOT運(yùn)算符應(yīng)用于子查詢(xún),使用SUM函數(shù)計(jì)算每個(gè)客戶(hù)訂購(gòu)的每種產(chǎn)品的總數(shù)量。FOR子句指定了透視列(在本例中為product_id),而IN子句指定了要透視的值(在本例中為[1]、[2]、[3]、[4]、[5])。
查詢(xún)的結(jié)果是一個(gè)透視表,顯示了每個(gè)客戶(hù)訂購(gòu)的每種產(chǎn)品的總數(shù)量,每種產(chǎn)品都有一列,每個(gè)客戶(hù)都有一行。
透視表也可以用于許多其他情況,例如匯總銷(xiāo)售數(shù)據(jù)、分析調(diào)查結(jié)果等。通過(guò)使用透視表,您可以使SQL查詢(xún)更加強(qiáng)大和靈活。
5. 子查詢(xún)
SQL中的子查詢(xún)是用于從一個(gè)或多個(gè)表中檢索數(shù)據(jù)的嵌套查詢(xún),子查詢(xún)的結(jié)果在主查詢(xún)中使用。它們可用于過(guò)濾、排序和分組數(shù)據(jù),并可分類(lèi)為單行或多行子查詢(xún)。子查詢(xún)括在括號(hào)中,可以在SQL語(yǔ)句的各個(gè)部分中使用,例如SELECT、FROM、WHERE和HAVING子句。
例如;有兩個(gè)名為employees和salaries的表。employees表包含以下列:employee_id、first_name、last_name和department_id。salaries表包含以下列:employee_id、salary和salary_date。您想要找到每個(gè)部門(mén)工資最高的員工的姓名。為此,您可以使用子查詢(xún)查找每個(gè)部門(mén)的最高工資,然后將結(jié)果與employees和salaries表連接以獲取具有該工資的員工的姓名。
下面的查詢(xún)使用子查詢(xún)查找每個(gè)部門(mén)的最高工資。首先執(zhí)行子查詢(xún)并返回包含每個(gè)部門(mén)最高工資的結(jié)果集。然后,主查詢(xún)將employees和salaries表與子查詢(xún)的結(jié)果連接,以獲取每個(gè)部門(mén)工資最高的員工的姓名。
SELECT
e.first_name,
e.last_name,
e.department_id,
s.salary
FROM
employees e
INNER JOIN salaries s ON e.employee_id = s.employee_id
INNER JOIN (
SELECT
department_id,
MAX(salary) AS max_salary
FROM
salaries
GROUP BY
department_id
) m ON s.department_id = m.department_id AND s.salary = m.max_salary;
使用INNER JOIN子句將employees和salaries表連接起來(lái),使用employee_id列作為連接鍵。使用department_id列將子查詢(xún)連接到主查詢(xún),并使用salary列匹配每個(gè)部門(mén)的最高工資。
查詢(xún)的結(jié)果是一個(gè)表格,顯示每個(gè)部門(mén)工資最高的員工的姓名及其部門(mén)ID和工資。
6. 交叉連接
交叉連接是一種連接操作,用于返回兩個(gè)或多個(gè)表的所有可能行組合,而不需要連接條件。它在生成測(cè)試數(shù)據(jù)或需要獲取多個(gè)表格所有可能組合的計(jì)算時(shí)非常有用。然而,由于交叉連接可能會(huì)產(chǎn)生高計(jì)算成本和龐大的結(jié)果集,因此在使用時(shí)需要謹(jǐn)慎考慮其影響,并確保結(jié)果集的大小符合預(yù)期。通常情況下,應(yīng)優(yōu)先考慮使用其他類(lèi)型的連接操作,如內(nèi)連接、外連接或等值連接,以更有效地獲取所需的數(shù)據(jù)。
在下面的示例中,有兩個(gè)名為customers和orders的表。customers表包含以下列:customer_id、customer_name和city。orders表包含以下列:order_id、customer_id和order_date。您想要找到每個(gè)客戶(hù)在每個(gè)城市下單的總數(shù)。為此,您可以使用交叉連接生成一個(gè)結(jié)果集,將每個(gè)客戶(hù)與每個(gè)城市組合,然后將結(jié)果與orders表連接以獲取每種組合的訂單數(shù)量。
SELECT
c.customer_id,
c.customer_name,
c.city,
COUNT(o.order_id) AS order_count
FROM
customers c
CROSS JOIN (
SELECT DISTINCT
city
FROM
customers
) cities
LEFT JOIN orders o ON c.customer_id = o.customer_id
WHERE
c.city = cities.city
GROUP BY
c.customer_id,
c.customer_name,
c.city;
示例中,查詢(xún)使用交叉連接生成一個(gè)結(jié)果集,該結(jié)果集將每個(gè)客戶(hù)與每個(gè)城市組合在一起。交叉連接首先執(zhí)行,返回一個(gè)包含每個(gè)客戶(hù)和城市的每個(gè)組合的結(jié)果集。然后,主查詢(xún)使用左連接將交叉連接的結(jié)果與orders表連接,以確保即使客戶(hù)沒(méi)有下訂單也包括所有客戶(hù)在結(jié)果中。
WHERE子句用于過(guò)濾結(jié)果,僅包括客戶(hù)所在城市與交叉連接中的城市匹配的行。這確保了結(jié)果僅顯示每個(gè)客戶(hù)在其各自城市中的訂單數(shù)量。
GROUP BY子句用于按客戶(hù)ID、客戶(hù)名稱(chēng)和城市分組結(jié)果。COUNT()函數(shù)用于計(jì)算每個(gè)客戶(hù)在每個(gè)城市中的訂單數(shù)量。
查詢(xún)的結(jié)果是一個(gè)表格,顯示了每個(gè)客戶(hù)在每個(gè)城市中下達(dá)的訂單總數(shù)。
7. 臨時(shí)表
SQL中的臨時(shí)表是在執(zhí)行SQL語(yǔ)句或事務(wù)期間創(chuàng)建和使用的表。它們存儲(chǔ)在內(nèi)存或磁盤(pán)上,并在創(chuàng)建它們的會(huì)話(huà)結(jié)束或不再需要時(shí)自動(dòng)刪除。臨時(shí)表通常用于存儲(chǔ)中間結(jié)果,或?qū)?fù)雜查詢(xún)分解為更小、更易管理的部分。
它們可以使用CREATE TEMPORARY TABLE語(yǔ)句創(chuàng)建,并像常規(guī)表一樣使用SQL命令(如SELECT、INSERT、UPDATE和DELETE)進(jìn)行操作。臨時(shí)表可以非常有用,用于優(yōu)化復(fù)雜查詢(xún)并提高性能,因?yàn)樗鼈兛梢詭椭鷾p少需要在任何給定時(shí)間處理的數(shù)據(jù)量。
假設(shè)有一個(gè)名為sales的表,其中包含以下列:date、product、category和sales_amount。您想創(chuàng)建一個(gè)報(bào)告,顯示過(guò)去一年每個(gè)月每個(gè)類(lèi)別的總銷(xiāo)售額。為此,您可以使用一個(gè)臨時(shí)表來(lái)創(chuàng)建每個(gè)月銷(xiāo)售數(shù)據(jù)的摘要,然后將臨時(shí)表與sales表連接,以獲取每個(gè)類(lèi)別的總銷(xiāo)售額。
首先,您可以使用CREATE TEMPORARY TABLE語(yǔ)句創(chuàng)建臨時(shí)表:
CREATE TEMPORARY TABLE monthly_sales_summary (
month DATE,
category VARCHAR(50),
total_sales DECIMAL(10,2)
);
此語(yǔ)句創(chuàng)建了一個(gè)名為monthly_sales_summary的臨時(shí)表,其中包含三列:month、category和total_sales。month列的類(lèi)型為DATE,category列的類(lèi)型為VARCHAR(50),total_sales列的類(lèi)型為DECIMAL(10,2)。
接下來(lái),使用INSERT INTO語(yǔ)句將摘要數(shù)據(jù)填充到臨時(shí)表中:
INSERT INTO monthly_sales_summary (month, category, total_sales)
SELECT
DATE_TRUNC('month', date) AS month,
category,
SUM(sales_amount) AS total_sales
FROM
sales
WHERE
date >= DATE_TRUNC('year', CURRENT_DATE) -- sales from the past year
GROUP BY
DATE_TRUNC('month', date),
category;
此語(yǔ)句使用DATE_TRUNC函數(shù)將date列截?cái)嗟皆路菁?jí)別,按月份和類(lèi)別分組銷(xiāo)售數(shù)據(jù)。此查詢(xún)的結(jié)果插入到monthly_sales_summary表中,該表現(xiàn)在包含每個(gè)月銷(xiāo)售數(shù)據(jù)的摘要。
最后,可以將臨時(shí)表與sales表連接起來(lái),以獲取每個(gè)類(lèi)別的總銷(xiāo)售額:
SELECT
s.category,
mss.month,
mss.total_sales
FROM
sales s
JOIN monthly_sales_summary mss
ON s.category = mss.category
AND DATE_TRUNC('month', s.date) = mss.month
WHERE
s.date >= DATE_TRUNC('year', CURRENT_DATE) -- sales from the past year
ORDER BY
s.category,
mss.month;
此語(yǔ)句將sales表與monthly_sales_summary表連接在category和month列上,并從臨時(shí)表中選擇category、month和total_sales列。WHERE子句用于過(guò)濾結(jié)果,僅包括過(guò)去一年的銷(xiāo)售數(shù)據(jù),ORDER BY子句用于按類(lèi)別和月份對(duì)結(jié)果進(jìn)行排序。
查詢(xún)的結(jié)果是一個(gè)表格,顯示了過(guò)去一年每個(gè)月每個(gè)類(lèi)別的總銷(xiāo)售額。
8. 具體化視圖
SQL中的具體化視圖是存儲(chǔ)為物理表的預(yù)計(jì)算結(jié)果集。它們基于SQL查詢(xún)創(chuàng)建和維護(hù),并用于提高頻繁執(zhí)行查詢(xún)的性能。具體化視圖可以按計(jì)劃或按需刷新,以確保數(shù)據(jù)是最新的。當(dāng)針對(duì)具體化視圖執(zhí)行查詢(xún)時(shí),結(jié)果集從物理表中檢索,而不是從原始表中計(jì)算。
這可以提升性能,特別是對(duì)于涉及聯(lián)接或聚合函數(shù)的復(fù)雜查詢(xún)。具體化視圖通常用于數(shù)據(jù)倉(cāng)庫(kù)和業(yè)務(wù)智能應(yīng)用程序中,在這些應(yīng)用程序中,它們可以幫助加速報(bào)告和儀表板。
例如,有一個(gè)名為sales的大型表,其中包含以下列:date、product、category和sales_amount。您想創(chuàng)建一個(gè)報(bào)告,顯示過(guò)去一年每個(gè)月每個(gè)類(lèi)別的總銷(xiāo)售額。但是,直接在sales表上運(yùn)行此查詢(xún)會(huì)很慢,因?yàn)樗瑪?shù)百萬(wàn)行。為了加快查詢(xún)速度,可以創(chuàng)建一個(gè)物化視圖,按月份和類(lèi)別匯總銷(xiāo)售數(shù)據(jù)。
要?jiǎng)?chuàng)建物化視圖,可以使用CREATE MATERIALIZED VIEW語(yǔ)句,如下所示:
CREATE MATERIALIZED VIEW monthly_sales_summary AS
SELECT
DATE_TRUNC('month', date) AS month,
category,
SUM(sales_amount) AS total_sales
FROM
sales
WHERE
date >= DATE_TRUNC('year', CURRENT_DATE) -- sales from the past year
GROUP BY
DATE_TRUNC('month', date),
category;
此語(yǔ)句創(chuàng)建了一個(gè)名為monthly_sales_summary的物化視圖,其中包含每個(gè)月和類(lèi)別的銷(xiāo)售數(shù)據(jù)摘要。SELECT語(yǔ)句與前面示例中用于創(chuàng)建臨時(shí)表的語(yǔ)句相同,但是不使用臨時(shí)表,而是將結(jié)果存儲(chǔ)在物化視圖中。
物化視圖與表類(lèi)似,因?yàn)樗鼈儗?shù)據(jù)存儲(chǔ)在磁盤(pán)上,但是在基礎(chǔ)數(shù)據(jù)更改時(shí)會(huì)自動(dòng)更新。您可以使用REFRESH MATERIALIZED VIEW語(yǔ)句手動(dòng)刷新物化視圖,也可以使用cron作業(yè)或其他調(diào)度工具設(shè)置定期刷新。
創(chuàng)建物化視圖后,可以像查詢(xún)其他表一樣查詢(xún)它:
SELECT
category,
month,
total_sales
FROM
monthly_sales_summary
ORDER BY
category,
month;
此語(yǔ)句從monthly_sales_summary物化視圖中選擇category、month和total_sales列,并按類(lèi)別和月份對(duì)結(jié)果進(jìn)行排序。
在這種情況下使用物化視圖的優(yōu)點(diǎn)在于,它允許您預(yù)計(jì)算和存儲(chǔ)摘要數(shù)據(jù),從而減少運(yùn)行查詢(xún)所需的時(shí)間。物化視圖特別適用于經(jīng)常運(yùn)行并需要對(duì)大型數(shù)據(jù)集進(jìn)行復(fù)雜計(jì)算的報(bào)告。但是,它們也有一些限制,例如它們可能占用大量磁盤(pán)空間,并且在基礎(chǔ)數(shù)據(jù)更改時(shí)可能無(wú)法立即更新。
結(jié)語(yǔ)
掌握高級(jí)SQL技術(shù),如窗口函數(shù)、CTE、聚合函數(shù)、透視表、子查詢(xún)、交叉連接、臨時(shí)表和物化視圖,可以幫助您更有效地處理復(fù)雜的數(shù)據(jù)分析任務(wù)。