MySQL 8.0 新增SQL語法對視窗函數和CTE的支持

来源:https://www.cnblogs.com/wy123/archive/2018/03/14/8570011.html
-Advertisement-
Play Games

嘗試了一下MySQL 8.0的部分新特性。 如果用過MSSQL或者是Oracle中的視窗函數(Oracle中叫分析函數), 然後再使用MySQL 8.0之前的時候,就知道需要在使用視窗函數處理邏輯的痛苦了,雖然純SQL也能實現類似於視窗函數的功能,但是這種SQL在可讀性和以及使用方式上大打折扣,看起 ...


 嘗試了一下MySQL 8.0的部分新特性。

 

  如果用過MSSQL或者是Oracle中的視窗函數(Oracle中叫分析函數),
  然後再使用MySQL 8.0之前的時候,就知道需要在使用視窗函數處理邏輯的痛苦了,雖然純SQL也能實現類似於視窗函數的功能,但是這種SQL在可讀性和以及使用方式上大打折扣,看起來寫起了都比較難受。

  在MSSQL和Oracle以及PostgreSQL都已經完整支持視窗函數的情況下,MySQL 8.0中也加入了視窗函數的功能,這一點實實在在方便了sql的編碼,可以說是MySQL8.0的亮點之一。

  對於視窗函數,比如row_number(),rank(),dense_rank(),NTILE(),PERCENT_RANK()等等,在MSSQL和Oracle以及PostgreSQL,使用的語法和表達的邏輯,基本上完全一致。
  這一點,幾個資料庫廠商做的還是比較統一的,如果熟悉任何一種關係數據中的視窗函數(分析函數),在MySQL 8.0之後就放心的用吧。

  通過一個case來體驗一下視窗函數的方便性,熟悉MSSQL或者Oracle或者PostgreSQL的老司機就不用看了。
  測試case,簡單模擬一個訂單表,欄位分別是訂單號,用戶編號,金額,創建時間

drop table  if exists order_info

create table order_info
(
    order_id int primary key,
    user_no varchar(10),
    amount int,
    create_date datetime
);

insert into order_info values (1,'u0001',100,'2018-1-1');
insert into order_info values (2,'u0001',300,'2018-1-2');
insert into order_info values (3,'u0001',300,'2018-1-2');
insert into order_info values (4,'u0001',800,'2018-1-10');
insert into order_info values (5,'u0001',900,'2018-1-20');

insert into order_info values (6,'u0002',500,'2018-1-5');
insert into order_info values (7,'u0002',600,'2018-1-6');
insert into order_info values (8,'u0002',300,'2018-1-10');
insert into order_info values (9,'u0002',800,'2018-1-16');
insert into order_info values (10,'u0002',800,'2018-1-22');

要求sql查詢求每個用戶的最新的一個訂單。

傳統的方式,儘量格式化的好讀一點的情況下,說實話,這句sql咋一看有點莫名其妙,不知所以。

SELECT * FROM 
(
    SELECT 
   IF(@y=a.user_no, @x:=@x+1, @x:=1) X , IF(@y=a.user_no, @y, @y:=a.user_no) Y, a.* FROM order_info a, (SELECT @x:=0, @y:=NULL) b ORDER BY a.user_no, a.create_date desc ) a WHERE X <= 1;

 如下是執行結果,當然執行結果是可以滿足需求的。

  如果採用新的視窗函數的方法,
  就是使用row_number()over(partition by user_no order by create_date desc) as row_num 給原始記錄編一個號,
  然後取第一個編號的數據,自然就是“用戶的最新的一條訂單”,實現邏輯上清晰了很多,代碼也簡潔,可讀了很多。

select * from 
(
    select row_number()over(partition by user_no order by create_date desc) as row_num,
    order_id,user_no,amount,create_date
    from order_info
)t where row_num=1;

  需要註意的是,MySQL中的使用視窗函數的時候,是不允許使用*的,必須顯式指定每一個欄位。

 

 row_number()

  (分組)排序編號,正如上面的例子, row_number()over(partition by user_no order by create_date desc) as row_num,按照用戶分組,按照create_date排序,對已有數據生成一個編號。
  當然也可以不分組,對整體進行排序。任何一個視窗函數,都可以分組統計或者不分組統計(也即可以不要partition by ***都可以,看你的需求了)

  

rank()

  類似於 row_number(),也是排序功能,但是rank()有什麼不一樣?新的事物的出現必然是為瞭解決潛在的問題。
  如果再往測試表中寫入一條數據:insert into order_info values (11,'u0002',800,'2018-1-22');
  對於測試表中的U002用戶來說,有兩條create_date完全一樣的數據(假設有這樣的數據),那麼在row_number()編號的時候,這兩條數據卻被編了兩個不同的號
  理論上講,這兩條的數據的排名是併列最新的。因此rank()就是為瞭解決這個問題的,也即:排序條件一樣的情況下,其編號也一樣。

  

dense_rank()

  dense_rank()的出現是為瞭解決rank()編號存在的問題的,
  rank()編號的時候存在跳號的問題,如果有兩個併列第1,那麼下一個名次的編號就是3,結果就是沒有編號為2的數據。
  如果不想跳號,可以使用dense_rank()替代。

  

avg,sum等聚合函數在視窗函數中的的增強

  可以在聚合函數中使用視窗功能,比如sum(amount)over(partition by user_no order by create_date) as sum_amont,達到一個累積計算sum的功能
  這種需求在沒有視窗函數的情況下,用純sql寫起來,也夠蛋疼的了,就不舉例了。

  

NTILE(N) 將數據按照某些排序分成N組

  舉個簡單的例子,按照分數線的倒序排列,將學生成績分成上中下3組,可以得到哪個程式數據上中下三個組中哪一部分,就可以使用NTILE(3) 來實現。這種需求倒是用的不是非常多。
  如下還是使用上面的表,按照時間將user_no = 'u0002'的訂單按照時間的緯度,劃分為3組,看每一行數據數據哪一組。

  

 

first_value(column_name) and last_value(column_name)

  first_value和last_value基本上見名知意了,就是取某一組數據,按照某種方式排序的,最早的和最新的某一個欄位的值。
  看結果體會一下。

  

nth_value(column_name,n)

  從排序的第n行還是返回nth_value欄位中的值,這個函數用的不多,要表達的這種邏輯,說實話,很難用語言表達出來,看個例子體會一下就行。

  n = 3

  

  n = 4

 

cume_dist

  在某種排序條件下,小於等於當前行值的行數/總行數,得到的是數據在某一個緯度的分佈百分比情況。
  比如如下示例
  第1行數據的日期(create_date)是2018-01-05 00:00:00,小於等於2018-01-05 00:00:00的數據是1行,計算方式是:1/6 = 0.166666666
  第2行數據的日期(create_date)是2018-01-06 00:00:00,小於等於2018-01-06 00:00:00的數據是2行,計算方式是:2/6 = 0.333333333
  依次類推
  第4行數據的日期(create_date)是2018-01-16 00:00:00,小於等於2018-01-16 00:00:00的數據是4行,計算方式是:4/6 = 0.6666666666
  第一行數據的0.6666666666 意味著,小於第四行日期(create_date)的數據占了符合條件數據的66.66666666666%

  

 

percent_rank()

  同樣是數據分佈的計算方式,只不過演算法變成了:當前RANK值-1/總行數-1 。
  具體演算法不細說,這個實際中用的也不多。

  

 

lag以及lead

  lag(column,n)獲取當前數據行按照某種排序規則的上n行數據的某個欄位,lead(column,n)獲取當前數據行按照某種排序規則的下n行數據的某個欄位,
  確實很拗口。
  舉個實際例子,按照時間排序,獲取當前訂單的上一筆訂單發生時間和下一筆訂單發生時間,(可以計算訂單的時間上的間隔度或者說買買買的頻繁程度)

select order_id,
         user_no,
         amount,
         create_date,
       lag(create_date,1) over (partition by user_no order by create_date asc) 'last_transaction_time',
       lead(create_date,1) over (partition by user_no order by create_date asc) 'next_transaction_time'
from order_info ;

  

 

CTE 公用表表達式

  CTE有兩種用法,非遞歸的CTE和遞歸的CTE。
  非遞歸的CTE可以用來增加代碼的可讀性,增加邏輯的結構化表達。
  平時我們比較痛恨一句sql幾十行甚至上上百行,根本不知道其要表達什麼,難以理解,對於這種SQL,可以使用CTE分段解決,
  比如邏輯塊A做成一個CTE,邏輯塊B做成一個CTE,然後在邏輯塊A和邏輯塊B的基礎上繼續進行查詢,這樣與直接一句代碼實現整個查詢,邏輯上就變得相對清晰直觀。
  舉個簡單的例子,當然這裡也不足以說明問題,比如還是第一個需求,查詢每個用戶的最新一條訂單
  第一步是對用戶的訂單按照時間排序編號,做成一個CTE,第二步對上面的CTE查詢,取行號等於1的數據。

  另外一種是遞歸的CTE,遞歸的話,應用的場景也比較多,比如查詢大部門下的子部門,每一個子部門下麵的子部門等等,就需要使用遞歸的方式。
  這裡不做細節演示,僅演示一種遞歸的用法,用遞歸的方式生成連續日期。

  

  當然遞歸不會無限下去,不同的資料庫有不同的遞歸限制,MySQL 8.0中預設限制的最大遞歸次數是1000。
  超過最大低估次數會報錯:Recursive query aborted after 1001 iterations. Try increasing @@cte_max_recursion_depth to a larger value.
  由參數@@cte_max_recursion_depth決定。

  

  關於CTE的限制,跟其他資料庫並無太大差異,比如CTE內部的查詢結果都要有欄位名稱,不允許連續對一個CTE多次查詢等等,相信熟悉CTE的老司機都很清楚。

 

視窗函數和CTE的增加,簡化了SQL代碼的編寫和邏輯的實現,並不是說沒有這些新的特性,這些功能都無法實現,只是新特性的增加,可以用更優雅和可讀性的方式來寫SQL。
不過這都是在MySQL 8.0中實現的新功能,在8.0之前,還是老老實實按照較為複雜的方式實現吧。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 伺服器需要換python環境,手賤重裝了,今天湊巧需要測試資料庫,花了一個小時搞了一下MySQL安裝。 1.刪除原有Mariadb 說明:目前centos預設的MySQL是Mariadb,由於習慣了MySQL(Oracle)加上一堆亂七八糟的原因,還是需要MySQL作資料庫。 Linux終端輸入命令 ...
  • 安裝前要關閉防火牆,防止外網不能訪問,這一點很重要,要不然外網訪問不了; ①關閉防火牆:service iptables stop ②永久關閉防火牆:chkconfig iptables off ③查看防火牆狀態:service iptables status 1.安裝Apache [root@lo ...
  • keepalived使用腳本進行健康檢查時的相關配置項。例如keepalived+haproxy實現haproxy的高可用。 keepalived分為vrrp實例的心跳檢查和後端服務的健康檢查。如果要配置後端服務,則後端服務只能是LVS。但vrrp能獨立與lvs存在,例如keepalive結合hap ...
  • 虛擬記憶體 demand paging 如何判斷是否在記憶體里:valid(legal + in memory)invalid(illegal or legal + not in memory)。 如何處理illegal的情況:page fault 到內核 ; 查看internal table(PCB) ...
  • 在上一篇文章中,分析了haproxy的stick table特性和用法,其中特性之一也是很實用的特性是stick table支持在haproxy多個節點之間進行複製(replication)。 本文僅討論如何配置實現stick table的複製功能,不考慮在什麼環境下實現它,以及它的雙主模型如何配置 ...
  • CentOS 修改用戶密碼 1.普通用戶 ①獲取超級用戶root許可權 命令:su 或者 su- 或者 su -root ②輸入命令: passwd 用戶名 ③輸入新密碼 2.超級用戶 ①打開system-auth文件 命令:vim /etc/pam.d/system-auth ②修改其中一行(設置最 ...
  • 一、問題發現 今天重啟了一下虛擬機之後,出現了一個問題,那就是突然間使用Nat配置的IP地址丟失了,嘗試了重啟網路,重啟虛擬機兩種辦法,發現都沒有用,好吧,那就查看一下發生了什麼問題: 輸入命令: 然後發現瞭如下的錯誤信息: 後來,百度了一下相關的問題,但是都說是虛擬機的mac地址有問題,什麼執行i ...
  • 對於MySQL資料庫中的誤操作刪除數據的恢復問題,可以使用基於MySQL中binlog做到類似於閃回或者生成反向操作的SQL語句來實現,是MySQL中一個非常實用的功能。原理不難理解,基於MySQL的row格式的binlog中,記錄歷史的增刪改SQL信息,基於此解析出來對應的SQL語句(回滾的話就是 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...