SQL 案例分析之月度銷(xiāo)量冠軍
作者: 不剪發(fā)的Tony老師
畢業(yè)于北京航空航天大學(xué),十多年數(shù)據(jù)庫(kù)管理與開(kāi)發(fā)經(jīng)驗(yàn),目前在一家全球性的金融公司從事數(shù)據(jù)庫(kù)架構(gòu)設(shè)計(jì)。CSDN學(xué)院簽約講師以及GitChat專(zhuān)欄作者。csdn上的博客收藏于以下地址:https://tonydong.blog.csdn.net
文章目錄
問(wèn)題描述
Oracle
MySQL
Microsoft SQL Server
PostgreSQL
SQLite
總結(jié)
大家好,我是只談技術(shù)不剪發(fā)的 Tony 老師。
今天給大家介紹一個(gè) SQL 實(shí)戰(zhàn)案例:通過(guò)橫向子查詢(xún)(LATERAL subquery)實(shí)現(xiàn)銷(xiāo)量冠軍分析。文章描述了 Oracle、MySQL、Microsoft SQL Server、PostgreSQL 以及 SQLite 中的實(shí)現(xiàn)方法。
如果覺(jué)得文章有用,歡迎評(píng)論??、點(diǎn)贊??、推薦??
??本文使用的示例表可以點(diǎn)此下載。
問(wèn)題描述
公司的銷(xiāo)售人員負(fù)責(zé)各種產(chǎn)品的銷(xiāo)售,emp_sales 表中記錄了每個(gè)銷(xiāo)售人員每個(gè)月份的銷(xiāo)量數(shù)據(jù)。以下是該表中的一些示例數(shù)據(jù):
SELECT * FROM emp_sales;
emp_id|sale_year|sale_month|amount |
------|---------|----------|--------|
19| 2021| 1|15672.53|
20| 2021| 1|11160.46|
21| 2021| 1|13763.75|
22| 2021| 1|11210.34|
23| 2021| 1|14610.88|
24| 2021| 1|13747.64|
25| 2021| 1|12816.20|
19| 2021| 2|14413.77|
20| 2021| 2|14266.04|
21| 2021| 2|16984.42|
...
emp_sales 表中的字段分別表示員工編號(hào)、銷(xiāo)售年份、銷(xiāo)售月份以及銷(xiāo)售金額。
現(xiàn)在我們想要知道每個(gè)月份的銷(xiāo)售冠軍,也就是每個(gè)月份銷(xiāo)售金額最高的員工。請(qǐng)問(wèn)應(yīng)該如何使用 SQL 查詢(xún)實(shí)現(xiàn)?
從原理上來(lái)說(shuō),我們可以分別通過(guò)查詢(xún)找出每個(gè)月份銷(xiāo)量最高的員工。例如 2021 年 1 月份的銷(xiāo)量冠軍可以使用一下查詢(xún)語(yǔ)句:
-- MySQL、PostgreSQL 以及 SQLite
SELECT s.sale_year, s.sale_month, e.emp_name, s.amount
FROM emp_sales s
JOIN employee e ON (e.emp_id = s.emp_id)
WHERE s.sale_year = 2021
AND s.sale_month = 1
ORDER BY s.amount DESC
LIMIT 1;
-- Oracle 和 SQL Server
SELECT s.sale_year, s.sale_month, e.emp_name, s.amount
FROM emp_sales s
JOIN employee e ON (e.emp_id = s.emp_id)
WHERE s.sale_year = 2021
AND s.sale_month = 1
ORDER BY s.amount DESC
offset 0 ROWS
FETCH FIRST 1 ROWS ONLY;
sale_year|sale_month|emp_name|amount |
---------|----------|--------|--------|
2021| 1|龐統(tǒng) |15672.53|
然后使用同樣的方法獲取其他月份分銷(xiāo)量冠軍。
顯然,這種方法不夠智能,我們需要一種查詢(xún)語(yǔ)句可以為每個(gè)月份返回一個(gè)最高銷(xiāo)量的員工。下面我們就來(lái)介紹如何通過(guò) SQL 橫向子查詢(xún)實(shí)現(xiàn)這一功能。
Oracle
Oracle 提供了橫向子查詢(xún),子查詢(xún)可以使用 FROM 子句中出現(xiàn)在它之前的表或者查詢(xún)結(jié)果中的字段。例如:
SELECT d.sale_year, d.sale_month, e.emp_name, s.amount
FROM (SELECT DISTINCT sale_year, sale_month FROM emp_sales) d
CROSS JOIN
LATERAL (SELECT emp_id, amount
FROM emp_sales
WHERE sale_year = d.sale_year
AND sale_month = d.sale_month
ORDER BY amount DESC
FETCH FIRST 1 ROWS ONLY) s
JOIN employee e
ON (e.emp_id = s.emp_id)
ORDER BY d.sale_year, d.sale_month;
其中子查詢(xún) d 的作用是獲取銷(xiāo)售數(shù)據(jù)中的所有年度和月份信息。LATERAL 關(guān)鍵字表示橫向子查詢(xún),子查詢(xún) s 通過(guò)使用左側(cè)查詢(xún)結(jié)果中的年度和月份數(shù)據(jù)返回了每個(gè)月銷(xiāo)售金額最高的員工和相應(yīng)的金額,最后連接員工表獲得員工的姓名。查詢(xún)返回的結(jié)果如下。
SALE_YEAR|SALE_MONTH|EMP_NAME|AMOUNT |
---------|----------|--------|--------|
2021| 1|龐統(tǒng) |15672.53|
2021| 2|黃權(quán) |16984.42|
2021| 3|鄧芝 |16377.44|
2021| 4|簡(jiǎn)雍 |18744.78|
2021| 5|蔣琬 |19466.56|
2021| 6|龐統(tǒng) |20154.83|
除了 LATERAL 關(guān)鍵字之外,Oracle 還提供了另一種實(shí)現(xiàn)橫向子查詢(xún)功能的 CROSS APPLY 語(yǔ)法。例如:
SELECT d.sale_year, d.sale_month, e.emp_name, s.amount
FROM (SELECT DISTINCT sale_year, sale_month FROM emp_sales) d
CROSS APPLY (SELECT emp_id, amount
FROM emp_sales
WHERE sale_year = d.sale_year
AND sale_month = d.sale_month
ORDER BY amount DESC
OFFSET 0 ROWS
FETCH FIRST 1 ROWS ONLY) s
JOIN employee e
ON (e.emp_id = s.emp_id)
ORDER BY d.sale_year, d.sale_month;
該語(yǔ)句和上面的示例等價(jià),返回的結(jié)果也相同。
??關(guān)于 Oracle 橫向子查詢(xún)和 CROSS APPLY 以及 OUTER APPLY 子句的詳細(xì)信息可以參考官方文檔。
另一種實(shí)現(xiàn)方法就是使用窗口函數(shù),參考下文中的 SQLite 部分。
MySQL
MySQL 8.0 提供了橫向子查詢(xún),子查詢(xún)可以使用 FROM 子句中出現(xiàn)在它之前的表或者查詢(xún)結(jié)果中的字段。例如:
SELECT d.sale_year, d.sale_month, e.emp_name, s.amount
FROM (SELECT DISTINCT sale_year, sale_month FROM emp_sales) d
CROSS JOIN
LATERAL (SELECT emp_id, amount
FROM emp_sales
WHERE sale_year = d.sale_year
AND sale_month = d.sale_month
ORDER BY amount DESC
LIMIT 1) s
JOIN employee e
ON (e.emp_id = s.emp_id)
ORDER BY d.sale_year, d.sale_month;
sale_year|sale_month|emp_name|amount |
---------|----------|--------|--------|
2021| 1|龐統(tǒng) |15672.53|
2021| 2|黃權(quán) |16984.42|
2021| 3|鄧芝 |16377.44|
2021| 4|簡(jiǎn)雍 |18744.78|
2021| 5|蔣琬 |19466.56|
2021| 6|龐統(tǒng) |20154.83|
其中子查詢(xún) d 的作用是獲取銷(xiāo)售數(shù)據(jù)中的所有年度和月份信息。LATERAL 關(guān)鍵字表示橫向子查詢(xún),子查詢(xún) s 可以使用 FROM 子句中出現(xiàn)在它之前的表或者查詢(xún)結(jié)果中的字段。
??關(guān)于 MySQL 橫向子查詢(xún)的詳細(xì)信息,可以參考官方文檔。
另一種實(shí)現(xiàn)方法就是使用窗口函數(shù),參考下文中的 SQLite 部分。
Microsoft SQL Server
Microsoft SQL Server 沒(méi)有實(shí)現(xiàn) LATERAL 關(guān)鍵字,但是可以使用和 Oracle 相同的 CROSS APPLY 子句實(shí)現(xiàn)橫向子查詢(xún)語(yǔ)句。例如:
SELECT d.sale_year, d.sale_month, e.emp_name, s.amount
FROM (SELECT DISTINCT sale_year, sale_month
FROM emp_sales) d
CROSS APPLY (SELECT emp_id, amount
FROM emp_sales
WHERE sale_year = d.sale_year
AND sale_month = d.sale_month
ORDER BY amount DESC
OFFSET 0 ROWS
FETCH FIRST 1 ROWS ONLY) s
JOIN employee e
ON (e.emp_id = s.emp_id)
ORDER BY d.sale_year, d.sale_month;
其中子查詢(xún) d 的作用是獲取銷(xiāo)售數(shù)據(jù)中的所有年度和月份信息。CROSS APPLY 關(guān)鍵字表示子查詢(xún) s 通過(guò)使用左側(cè)查詢(xún)結(jié)果中的年度和月份數(shù)據(jù)返回了每個(gè)月銷(xiāo)售金額最高的員工和相應(yīng)的金額。以上查詢(xún)通過(guò)使用左側(cè)查詢(xún)結(jié)果(d)中的年度和月份數(shù)據(jù)返回了每個(gè)月銷(xiāo)售金額最高的員工和相應(yīng)的金額:
sale_year|sale_month|emp_name|amount |
---------|----------|--------|--------|
2021| 1|龐統(tǒng) |15672.53|
2021| 2|黃權(quán) |16984.42|
2021| 3|鄧芝 |16377.44|
2021| 4|簡(jiǎn)雍 |18744.78|
2021| 5|蔣琬 |19466.56|
2021| 6|龐統(tǒng) |20154.83|
??關(guān)于 Microsoft SQL Server 中的 CROSS APPLY 和 OUTER APPLY 子句的詳細(xì)介紹,可以參考官方文檔。
另一種實(shí)現(xiàn)方法就是使用窗口函數(shù),參考下文中的 SQLite 部分。
PostgreSQL
PostgreSQL 實(shí)現(xiàn)了橫向子查詢(xún),子查詢(xún)可以使用 FROM 子句中出現(xiàn)在它之前的表或者查詢(xún)結(jié)果中的字段。例如:
SELECT d.sale_year, d.sale_month, e.emp_name, s.amount
FROM (SELECT DISTINCT sale_year, sale_month FROM emp_sales) d
CROSS JOIN
LATERAL (SELECT emp_id, amount
FROM emp_sales
WHERE sale_year = d.sale_year
AND sale_month = d.sale_month
ORDER BY amount DESC
LIMIT 1) s
JOIN employee e
ON (e.emp_id = s.emp_id)
ORDER BY d.sale_year, d.sale_month;
其中子查詢(xún) d 的作用是獲取銷(xiāo)售數(shù)據(jù)中的所有年度和月份信息,橫向子查詢(xún) s 通過(guò)使用左側(cè)查詢(xún)結(jié)果中的年度和月份數(shù)據(jù)返回了每個(gè)月銷(xiāo)售金額最高的員工和相應(yīng)的金額,最后連接員工表獲得員工的姓名。查詢(xún)返回的結(jié)果如下。
sale_year|sale_month|emp_name|amount |
---------|----------|--------|--------|
2021| 1|龐統(tǒng) |15672.53|
2021| 2|黃權(quán) |16984.42|
2021| 3|鄧芝 |16377.44|
2021| 4|簡(jiǎn)雍 |18744.78|
2021| 5|蔣琬 |19466.56|
2021| 6|龐統(tǒng) |20154.83|
??關(guān)于 PostgreSQL 橫向子查詢(xún)的詳細(xì)介紹,可以參考官方文檔。
另一種實(shí)現(xiàn)方法就是使用窗口函數(shù),參考下文中的 SQLite 部分。
SQLite
SQLite 目前沒(méi)有提供橫向子查詢(xún),因此不能通過(guò)這種方式返回月度銷(xiāo)量冠軍。我們可以利用窗口函數(shù)實(shí)現(xiàn)同樣的功能,例如:
SELECT s.sale_year, s.sale_month, e.emp_name, s.amount
FROM (SELECT emp_id, sale_year, sale_month, amount,
RANK() OVER (PARTITION BY sale_year, sale_month ORDER BY amount desc) AS rk
FROM emp_sales) s
JOIN employee e
ON (e.emp_id = s.emp_id)
WHERE rk = 1
ORDER BY s.sale_year, s.sale_month;
窗口函數(shù) RANK 返回了按照月份分組后的銷(xiāo)量排名,然后我們返回了排名第一的數(shù)據(jù),也就是月度銷(xiāo)量冠軍。
以上窗口函數(shù)的實(shí)現(xiàn)語(yǔ)法也適用于其他數(shù)據(jù)庫(kù),具體介紹可以參考這篇文章。
總結(jié)
本文通過(guò)一個(gè)月度銷(xiāo)量冠軍分析的案例,介紹了如何利用橫向子查詢(xún)引用子查詢(xún)左側(cè)表中的字段。另外,我們還給出了窗口函數(shù)實(shí)現(xiàn)相同功能的語(yǔ)法。