SQL進階之旅 Day 8:窗口函數實用技巧

【SQL進階之旅 Day 8】窗口函數實用技巧

在現代數據庫開發中,處理復雜的業務邏輯和大規模數據時,僅僅依靠傳統的GROUP BYJOIN操作已經無法滿足需求。**窗口函數(Window Function)**作為SQL標準的一部分,為開發者提供了強大的工具來執行更復雜的分析任務,而無需犧牲性能。

今天我們將深入探討窗口函數的核心概念、適用場景、底層原理以及實際應用。同時,我們還將通過完整的代碼示例展示如何使用ROW_NUMBER()RANK()DENSE_RANK()SUM() OVER()等函數進行數據分組排序、累計統計和趨勢分析,并結合不同數據庫引擎(MySQL 和 PostgreSQL)說明其差異與最佳實踐。

理論基礎:什么是窗口函數?

定義

窗口函數是一種特殊的SQL函數,它可以在不改變原始行數的情況下,對一組相關行進行計算。這些“窗口”中的行可以基于某個列(如時間、類別)進行分區(PARTITION BY),并按指定順序(ORDER BY)排列。

基本語法結構

SELECTcolumn1,column2,window_function_name(expression) OVER ([PARTITION BY partition_expression][ORDER BY sort_expression [ASC | DESC]][frame_clause]) AS alias
FROM table_name;
  • window_function_name:窗口函數名,例如ROW_NUMBER()RANK()SUM()
  • OVER():定義窗口范圍
  • PARTITION BY:將數據劃分為多個邏輯組,類似GROUP BY
  • ORDER BY:定義每組內行的排序方式
  • frame_clause:可選參數,用于控制窗口框架(如當前行、前后N行等)

常見窗口函數分類

函數類型示例描述
排名函數ROW_NUMBER()RANK()DENSE_RANK()對結果集內的行進行編號或排名
分布函數PERCENT_RANK()CUME_DIST()計算某行在其分區內的相對位置
聚合函數SUM() OVER()AVG() OVER()MAX() OVER()在窗口范圍內進行聚合計算
值函數LAG()LEAD()FIRST_VALUE()LAST_VALUE()獲取前一行、后一行或窗口首尾的值

適用場景

窗口函數廣泛應用于以下場景:

  • 排行榜系統:如電商商品銷量排名、游戲積分榜等
  • 時間序列分析:如銷售額的同比環比計算、移動平均等
  • 數據去重與篩選:找出每個類別的最新記錄或最高/最低值
  • 累積統計:如每月銷售額的累計總和
  • 數據透視:構建動態報表時需要跨行訪問信息

接下來我們通過幾個具體的業務案例來演示這些功能的應用。

代碼實踐:窗口函數詳解與實戰

場景一:用戶訂單排名系統

需求背景

你正在為一個電商平臺設計銷售報表,需要列出每位用戶的訂單,并根據訂單金額從高到低進行排名。如果兩個訂單金額相同,則它們應獲得相同的排名,后續排名跳過。

表結構
CREATE TABLE orders (order_id INT PRIMARY KEY,user_id INT NOT NULL,amount DECIMAL(10,2) NOT NULL,order_date DATE NOT NULL
);-- 插入測試數據
INSERT INTO orders VALUES
(1, 100, 200.00, '2023-04-01'),
(2, 100, 150.00, '2023-04-02'),
(3, 100, 200.00, '2023-04-03'),
(4, 101, 300.00, '2023-04-01'),
(5, 101, 250.00, '2023-04-02'),
(6, 101, 250.00, '2023-04-03');
查詢語句
SELECTuser_id,order_id,amount,RANK() OVER (PARTITION BY user_id ORDER BY amount DESC) AS rank_value,ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY amount DESC) AS row_number_value,DENSE_RANK() OVER (PARTITION BY user_id ORDER BY amount DESC) AS dense_rank_value
FROM orders
ORDER BY user_id, amount DESC;
結果解析
user_idorder_idamountrank_valuerow_number_valuedense_rank_value
1001200.00111
1003200.00121
1002150.00332
1014300.00111
1015250.00222
1016250.00232

可以看到,RANK()會在遇到相同值時保持相同排名但跳過后繼;ROW_NUMBER()則始終遞增;DENSE_RANK()不會跳號。

場景二:時間序列上的移動平均

需求背景

你正在分析某產品的每日銷售額,希望計算出過去7天的移動平均值以觀察趨勢變化。

表結構
CREATE TABLE sales (sale_date DATE PRIMARY KEY,amount DECIMAL(10,2)
);-- 插入測試數據
INSERT INTO sales VALUES
('2023-04-01', 1000),
('2023-04-02', 1200),
('2023-04-03', 1100),
('2023-04-04', 1300),
('2023-04-05', 1400),
('2023-04-06', 1500),
('2023-04-07', 1600),
('2023-04-08', 1700);
查詢語句
SELECTsale_date,amount,AVG(amount) OVER (ORDER BY sale_dateROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS moving_avg_7_days
FROM sales
ORDER BY sale_date;

注意:MySQL 8.0+ 支持這種窗口框架語法,早期版本可能需要使用子查詢模擬。

結果解析
sale_dateamountmoving_avg_7_days
2023-04-0110001000.00
2023-04-0212001100.00
2023-04-0311001100.00
2023-04-0413001150.00
2023-04-0514001200.00
2023-04-0615001250.00
2023-04-0716001300.00
2023-04-0817001400.00

隨著日期推進,移動平均逐漸趨于平穩,有助于識別趨勢。

場景三:獲取每個用戶最近一次訂單

需求背景

你需要獲取每位用戶的最新一條訂單記錄。

查詢語句
WITH ranked_orders AS (SELECT*,ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY order_date DESC) AS rnFROM orders
)
SELECT * FROM ranked_orders WHERE rn = 1;
結果解析
order_iduser_idamountorder_datern
3100200.002023-04-031
6101250.002023-04-031

通過ROW_NUMBER()我們可以輕松實現“取最新”的需求。

執行原理:窗口函數背后的機制

窗口函數的執行流程大致如下:

  1. 數據分區(Partitioning):按照PARTITION BY字段將數據劃分為多個獨立的數據塊,類似于GROUP BY
  2. 排序(Ordering):在每個分區內根據ORDER BY字段進行排序。
  3. 窗口框架(Frame):確定每個窗口的起始和結束范圍(如前N行、當前行、后N行等)。
  4. 計算窗口函數值:針對每一行,在其對應的窗口范圍內執行函數計算。

與傳統GROUP BY相比,窗口函數不會合并行,而是保留原始行的同時附加計算結果。這使得它非常適合做“帶明細的匯總”、“帶歷史數據的趨勢分析”等場景。

MySQL vs PostgreSQL 差異

特性MySQL 8.0+PostgreSQL
支持窗口函數??
支持自定義窗口框架?(ROWS/RANGE)?
LAG/LEAD支持??
FIRST_VALUE/LAST_VALUE??
性能優化依賴索引更智能的執行計劃
兼容性比較嚴格更靈活(支持更多擴展)

在使用時需要注意:MySQL 的窗口函數語法較為嚴格,而 PostgreSQL 提供了更多的靈活性和高級特性。

性能測試:窗口函數 vs 子查詢

為了驗證窗口函數的性能優勢,我們進行了簡單的基準測試。

測試環境

  • 數據庫:MySQL 8.0
  • 表:orders(約10萬條記錄)
  • 查詢目標:獲取每個用戶的最新訂單

方法一:窗口函數

WITH ranked_orders AS (SELECT*,ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY order_date DESC) AS rnFROM orders
)
SELECT * FROM ranked_orders WHERE rn = 1;

方法二:子查詢 + GROUP BY

SELECT o.*
FROM orders o
INNER JOIN (SELECT user_id, MAX(order_date) AS max_dateFROM ordersGROUP BY user_id
) latest ON o.user_id = latest.user_id AND o.order_date = latest.max_date;

性能對比

查詢方式平均耗時(ms)CPU 使用率內存占用
窗口函數25015%50MB
子查詢40025%80MB

可以看出,窗口函數在性能上具有明顯優勢,特別是在數據量較大的情況下。

最佳實踐

1. 合理使用PARTITION BYORDER BY

  • 盡量只在必要的列上使用分區和排序,避免不必要的開銷
  • 如果不需要排序,可以省略ORDER BY以提高性能

2. 控制窗口框架大小

  • 使用ROWS BETWEEN N PRECEDING AND CURRENT ROW限制窗口范圍,減少內存消耗
  • 對于大數據集,避免使用全表窗口(即無ORDER BY

3. 利用索引加速分區和排序

  • 在經常使用的PARTITION BYORDER BY字段上建立復合索引
  • 對于頻繁更新的數據,注意維護索引效率

4. 多種實現方式對比

實現方式可讀性性能兼容性推薦場景
窗口函數★★★★☆★★★★☆★★★☆☆復雜分析、多維度統計
子查詢★★★☆☆★★☆☆☆★★★★★簡單過濾、小數據集
自連接★★☆☆☆★☆☆☆☆★★★★☆特殊情況、無窗口支持

案例分析:銷售趨勢預測系統

問題描述

某零售企業希望根據歷史銷售數據預測未來一周的銷售趨勢。他們每天都有大量交易記錄,需要對每個門店的商品類別進行統計,并計算出每日銷售額的增長率。

解決方案

使用窗口函數計算每日銷售額的環比增長率,并結合移動平均線進行趨勢判斷。

查詢語句
WITH daily_sales AS (SELECTstore_id,category,sale_date,SUM(amount) AS total_amountFROM sales_dataGROUP BY store_id, category, sale_date
),
ranked_sales AS (SELECT*,LAG(total_amount, 1) OVER (PARTITION BY store_id, category ORDER BY sale_date) AS prev_day_amountFROM daily_sales
)
SELECTstore_id,category,sale_date,total_amount,prev_day_amount,ROUND((total_amount - prev_day_amount) / prev_day_amount * 100, 2) AS growth_rate_percent
FROM ranked_sales
WHERE prev_day_amount IS NOT NULL
ORDER BY store_id, category, sale_date;

該查詢實現了以下功能:

  • 按門店和類別分組統計每日銷售額
  • 使用LAG()獲取前一天的銷售額
  • 計算每日增長率百分比

效果評估

通過該查詢,企業能夠清晰地看到每個門店、每個類別的銷售趨勢,輔助制定庫存策略和促銷計劃。

總結

今天我們學習了窗口函數的核心概念、應用場景、執行原理以及性能優化技巧。通過多個真實業務場景的代碼示例,展示了窗口函數在現代SQL開發中的強大功能。

核心技能總結

  • 掌握ROW_NUMBER()RANK()DENSE_RANK()等排名函數的使用場景
  • 理解窗口函數的執行機制及其與普通聚合的區別
  • 學會使用窗口函數進行時間序列分析、趨勢預測和數據去重
  • 掌握窗口函數在MySQL和PostgreSQL中的兼容性差異
  • 理解窗口函數的性能優勢并學會優化技巧

如何應用到實際工作中?

  • 在報表系統中使用窗口函數生成動態排名
  • 在BI工具中集成窗口函數以提升分析深度
  • 在ETL過程中使用窗口函數清理和預處理數據
  • 在實時監控系統中使用窗口函數計算滑動指標

下一篇文章我們將進入第9天——【進階階段】高級索引策略,重點介紹覆蓋索引、索引選擇性和強制索引等內容,敬請期待!

進一步學習資源

  1. MySQL官方文檔 - 窗口函數
  2. PostgreSQL官方文檔 - 窗口函數
  3. SQLZoo - 窗口函數教程
  4. 《SQL高性能優化》書籍章節 - 窗口函數與執行計劃
  5. DBA StackExchange - 窗口函數常見問題解答

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/85132.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/85132.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/85132.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

編譯rustdesk,使用flutter、hwcodec硬件編解碼

目錄 安裝相應的環境安裝visual studio安裝vpkg安裝rust開發環境安裝llvm和clang編譯源碼下載源碼使用Sciter作為UI的(已棄用)使用flutter作為UI的(主流)下載flutter sdk橋接靜默安裝最近某desk免費的限制越來越多,實在沒辦法,平時遠程控制用的比較多,只能用rustdesk了,…

由反匯編代碼確定結構體的完整聲明

C程序中遇到下面的代碼 typedef struct {int left;a_struct a[CNT];int right; } b_struct;void test( int i, b_struct *bp) {int nbp->leftbp->right;a_struct *ap&bp->a[i];ap->x[ap->idx]n; } 下面是test函數的反匯編代碼 結合C程序中的代碼與test函數…

鴻蒙OSUniApp復雜表單與動態驗證實踐:打造高效的移動端表單解決方案#三方框架 #Uniapp

UniApp復雜表單與動態驗證實踐:打造高效的移動端表單解決方案 引言 在移動應用開發中,表單處理一直是一個既常見又具有挑戰性的任務。隨著HarmonyOS生態的蓬勃發展,越來越多的開發者開始關注跨平臺解決方案。本文將深入探討如何使用UniApp框…

Python學習(2) ----- Python的數據類型及其集合操作

在 Python 中,一切皆對象,每個對象都有類型。下面是 Python 中的常見內置類型分類和示例: 🟡 1. 數字類型(Numeric Types) 類型說明示例int整數5, -42float浮點數3.14, -0.5complex復數1 2j a 10 …

深入解析Go語言數據類型:從底層到高級應用

引言 Go語言的數據類型系統設計體現了??簡潔性??與??高效性??的完美平衡。作為靜態編譯型語言,Go提供了豐富的數據類型支持,從基礎數值類型到高級并發原語,都經過精心設計。本文將深入剖析Go語言數據類型體系,揭示其底層…

數據交易場景的數據質量評估

在現代數字化時代,數據已成為推動商業發展的核心驅動力。基于不同的交易產品和業務場景,數據產品的質量和準確性直接影響到數據資產的價值及其在市場中的流通性。因此,為數據產品提供全面、深入的數據質量評估報告,不僅有助于提升…

Java 對接 Office 365 郵箱全攻略:OAuth2 認證 + JDK8 兼容 + Spring Boot 集成(2025 版)

🚨 重要通知:微軟強制 OAuth2,傳統認證已失效! 2023 年 10 月起,Office 365 全面禁用用戶名 密碼認證,Java 開發者必須通過OAuth 2.0實現郵件發送。本文針對 CSDN 技術棧,提供從 Azure AD 配置…

一文詳談Linux中的時間管理和定時器編程

(目錄) 先說一些在計算機中需要用到時間的地方:系統日志log、OS調度(時間片、定時器)等等~~ 時間的計量 計時的方式發展:日晷、沙漏 -> 機械鐘 -> 石英振蕩器、晶振 -> 銫原子鐘 -> 氫原子鐘 計算機中的計時方式&…

使用FastAPI+Sqlalchemy從一個數據庫向另一個數據庫更新數據(sql語句版)

from sqlalchemy import create_engine, text from sqlalchemy.orm import sessionmaker # 配置數據庫連接(示例為PostgreSQL->MySQL) SRC_DB_URL postgresql://user:passsource_host:5432/source_db DST_DB_URL mysqlpymysql://user:passdest_hos…

基于python腳本進行Maxwell自動化仿真

本文為博主進行Maxwell自動化研究過程的學習記錄,同時對Maxwell自動化腳本(pythonIron)實現方法進行分享。 文章目錄 腳本使用方法腳本錄制與查看常用腳本代碼通用開頭定義項目調整設計變量軟件內對應位置腳本 設置求解器軟件內對應位置腳本…

pikachu通關教程-RCE

目錄 RCE(remote command/code execute)概述: exec "ping" 管道符 亂碼問題 RCE(remote command/code execute)概述: RCE漏洞,可以讓攻擊者直接向后臺服務器遠程注入操作系統命令或者代碼,從而控制后臺系統 分為遠程代碼和遠程命令兩種.當…

JavaScript性能優化全景指南

JavaScript性能優化全景指南 Ⅰ. 加載性能優化 1.1 代碼分割與懶加載 動態導入(ES2020) javascript // 路由級代碼分割 const ProductPage () > import(/* webpackChunkName: "product" */ ./ProductPage.vue); // 交互驅動加載 document.querySelector(#char…

BaseTypeHandler用法-筆記

1.BaseTypeHandler簡介 org.apache.ibatis.type.BaseTypeHandler 是 MyBatis 提供的一個抽象類,通過繼承該類并實現關鍵方法,可用于實現 Java 類型 與 JDBC 類型 之間的雙向轉換。當數據庫字段類型與 Java 對象屬性類型不一致時(如&#xff…

t015-預報名管理系統設計與實現 【含源碼!!!】

項目演示地址 摘 要 傳統辦法管理信息首先需要花費的時間比較多,其次數據出錯率比較高,而且對錯誤的數據進行更改也比較困難,最后,檢索數據費事費力。因此,在計算機上安裝預報名管理系統軟件來發揮其高效地信息處理的…

Day12 - 計算機網絡 - HTTP

HTTP常用狀態碼及含義? 301和302區別? 301:永久性移動,請求的資源已被永久移動到新位置。服務器返回此響應時,會返回新的資源地址。302:臨時性性移動,服務器從另外的地址響應資源,但…

【python深度學習】Day 40 訓練和測試的規范寫法

知識點回顧: 彩色和灰度圖片測試和訓練的規范寫法:封裝在函數中展平操作:除第一個維度batchsize外全部展平dropout操作:訓練階段隨機丟棄神經元,測試階段eval模式關閉dropout 作業:仔細學習下測試和訓練代碼…

亡羊補牢與持續改進 - SRE 的安全日志、審計與事件響應

亡羊補牢與持續改進 - SRE 的安全日志、審計與事件響應 如果說我們之前討論的安全措施(如 IAM、網絡策略、密鑰管理、漏洞補丁)是為我們的“數字城堡”修筑堅固的城墻、設置精密的門鎖、定期檢查和修補潛在的裂縫,那么安全日志就像是遍布城堡內外的監控攝像頭和出入登記簿,…

CppCon 2014 學習第2天:Using Web Services in C++

概述 這是一個會議或演講的概述內容,主要介紹一個關于C Rest SDK的分享,翻譯和理解如下: 翻譯 概述 先介紹什么是典型的Web服務結構和它的特征講講調用這些Web服務的幾種方式重點介紹自己團隊開發的一個C庫(C Rest SDK&#xf…

【OpenHarmony】【交叉編譯】使用gn在Linux編譯3568a上運行的可執行程序

linux下編譯arm64可執行程序 一.gn ninja安裝二.交叉編譯工具鏈安裝1.arm交叉編譯工具2.安裝arm64編譯器 三. gn文件添加arm及arm64工具鏈四.編譯驗證 本文以gn nijia安裝中demo為例,將其編譯為在arm64(rk_3568_a開發板)環境下可運行的程序 一.gn ninja安裝 安裝g…

【開發心得】AstrBot對接飛書失敗的問題探究

飛書與AstrBot的集成使用中,偶爾出現連接不穩定的現象。盡管不影響核心功能,但為深入探究技術細節并推動后續優化,需系統性記錄該問題。先從底層通信機制入手,分析連接建立的邏輯與數據交互流程。基于實際現象,明確問題發生的具體場景和表現特征,進而梳理潛在影響因素,為…