1. 程式人生 > 實用技巧 >SQL:SQL高階處理

SQL:SQL高階處理

視窗函式

什麼是視窗函式

  • 視窗函式也稱為 OLAP函式
  • OLAP 是 OnLine Analytical Processing 的簡稱,意思是對資料庫資料 進行實時分析處理
  • 如市場分析、建立財務報表、建立計劃等日常性商務工作

視窗函式的語法

  • <視窗函式> OVER ([PARTITION BY <列清單>] ORDER BY <排序用列清單>)

  • 能夠作為視窗函式使用的函式

    • ①能夠作為視窗函式的聚合函式(SUM、AVG、COUNT、MAX、MIN)
    • ②RANK、DENSE_RANK、ROW_NUMBER 等專用視窗函式

語法的基本使用方法——使用RANK函式

  • RANK 是用來計算記錄排序的函式

  • PARTITION BY 能夠設定排序的物件範圍

    • 在橫向上對錶進行分組
  • ORDER BY 能夠指定按照哪一列、何種順序進行排序

    • 決定縱向排序的規則
  • 視窗函式兼具分組和排序兩種功能

    • 通過 PARTITION BY 分組後的記錄集合稱為視窗,是代表範圍
-- 根據不同的商品種類,按照銷售單價從低到高的順序建立排序表
SELECT product_name, product_type, sale_price,
       RANK () OVER (PARTITION BY product_type                         
                         
ORDER BY sale_price) AS ranking FROM Product;

無需指定PARTITION BY

  • 和使用沒有 GROUP BY 的聚合函式時的效果一樣,從上到下沒有分組的全部排序
-- 不指定PARTITION BY
SELECT product_name, product_type, sale_price,
        RANK () OVER (ORDER BY sale_price) AS ranking  
FROM Product;

專用視窗函式的種類

  • RANK函式

    • 計算排序時,如果存在相同位次的記錄,則會跳過之後的位次
    • 例:有 3 條記錄排在第 1 位時:1 位、1 位、1 位、4 位……
  • DENSE_RANK函式

    • 同樣是計算排序,即使存在相同位次的記錄,也不會跳過之後的位次
    • 例:有 3 條記錄排在第 1 位時:1 位、1 位、1 位、2 位……
  • ROW_NUMBER函式

    • 賦予唯一的連續位次
    • 例:有 3 條記錄排在第 1 位時:1 位、2 位、3 位、4 位……
  • 由於專用視窗函式無需引數,因此通常括號中都是空的

-- 比較RANK、DENSE_RANK、ROW_NUMBER的結果
SELECT product_name, product_type, sale_price,  
RANK () OVER (ORDER BY sale_price) AS ranking, 
DENSE_RANK () OVER (ORDER BY sale_price) AS dense_ranking, 
ROW_NUMBER () OVER (ORDER BY sale_price) AS row_num 
FROM Product;

視窗函式的適用範圍

  • 原則上視窗函式只能在SELECT子句中使用
  • 在SELECT 子句之外“使用視窗函式是沒有意義的”

作為視窗函式使用的聚合函式

  • 所有的聚合函式都能用作視窗函式,其語法和專用視窗函式完全相同
-- 將SUM函式作為視窗函式使用
SELECT product_id, product_name, sale_price,
     SUM (sale_price) OVER (ORDER BY product_id) AS current_sum  
FROM Product;

-- 將AVG函式作為視窗函式使用
SELECT product_id, product_name, sale_price,
     AVG (sale_price) OVER (ORDER BY product_id) AS current_avg  
FROM Product;

計算移動平均

  • 視窗函式就是將表以視窗為單位進行分割,並在其中進行排序的函式。

  • 在視窗中指定更加詳細的彙總範圍的備選功能,該備選功 能中的彙總範圍稱為框架

  • 指定框架(彙總範圍)

    • ROWS(“行”)和 PRECEDING(“之前”)

    • “ ROWS 2 PRECEDING”,

      • 就是將框架指定為“截止到之前 2 行”,也就是將作為彙總物件的記錄限 定為如下的“最靠近的 3行”
      • 1.自身(當前記錄) 2.之前1行的記錄 3.之前2行的記錄
    • 由於框架是根據當前記錄來確定的,因此和固定的視窗不同,其範圍會隨著當前記錄的變化而變化

      • 這樣的統計方法稱為移動平均(moving average)
      • 由於這種方法在希 望實時把握“最近狀態”時非常方便,因此常常會應用在對股市趨勢的實時跟蹤當中
    • 使用關鍵字FOLLOWING(“之後”)替換 PRECEDING,就可以指 定“截止到之後 ~ 行”作為框架了

  • 將當前記錄的前後行作為彙總物件

    • 同時使用 PRECEDING(“之前”)和 FOLLOWING(“之後”)關 鍵字來實現
-- 指定“最靠近的3行”作為彙總物件
SELECT product_id, product_name, sale_price,
       AVG (sale_price) OVER (ORDER BY product_id                                    
                              ROWS 2 PRECEDING) AS moving_avg  
FROM Product;

-- 將當前記錄的前後行作為彙總物件
SELECT product_id, product_name, sale_price,
       AVG (sale_price) OVER (ORDER BY product_id                               
                              ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS moving_avg  
FROM Product;

兩個ORDER BY

  • 使用視窗函式時必須要在 OVER 子句中使用 ORDER BY
  • OVER 子句中的 ORDER BY 只是用來決定 視窗函式按照什麼樣的順序進行計算的,對結果的排列順序並沒有影響
  • 在SELECT 語句的最後,使用ORDER BY 子句進行指定。這樣就能保證SELECT 語句的結果中記錄的排列順序了
  • 將聚合函式作為視窗函式使用時,會以當前記錄為基準來決定彙總物件的記錄。
-- 無法保證如下SELECT語句的結果的排列順序
SELECT product_name, product_type, sale_price,
        RANK () OVER (ORDER BY sale_price) AS ranking  
FROM Product;

-- 在語句末尾使用ORDER BY子句對結果進行排序
SELECT product_name, product_type, sale_price,
        RANK () OVER (ORDER BY sale_price) AS ranking  
FROM Product 
ORDER BY ranking;

GROUPING運算子

同時得到合計行

  • UNION ALL
-- 使用GROUP BY無法得到合計行 
SELECT product_type, SUM(sale_price)  
FROM Product 
GROUP BY product_type;

-- 分別計算出合計行和彙總結果再通過UNION ALL進行連線 
SELECT '合計' AS product_type, SUM(sale_price)  
FROM Product 
UNION ALL 
SELECT product_type, SUM(sale_price)  
FROM Product 
GROUP BY product_type;

GROUPING 運算子包含以下 3種:ROLLUP,CUBE,GROUPING SETS

ROLLUP

  • 同時得出合計和小計

  • 使用方法

    • 從語法上來說,就是將 GROUP BY 子句中的聚合鍵清單像 ROLLUP (<列1>,< 列2>,...)這樣使用
    • 該運算子的作用,“一次計算出不同聚合鍵組合的結果”
  • 超級分組記錄(super group row)

    • GROUP BY () 表示沒有聚合鍵,也就相當於沒有 GROUP BY 子句(這時會得到全部資料的合計行的記錄)
  • 將“登記日期”新增到聚合鍵當中

-- 使用ROLLUP同時得出合計和小計 。
SELECT product_type, SUM(sale_price) AS sum_price  
FROM Product 
GROUP BY ROLLUP(product_type); 

-- 在GROUP BY中新增“登記日期”(不使用ROLLUP) 
SELECT product_type, regist_date, SUM(sale_price) AS sum_price  
FROM Product 
GROUP BY product_type, regist_date;

-- 在GROUP BY中新增“登記日期”(使用ROLLUP)
SELECT product_type, regist_date, SUM(sale_price) AS sum_price  
FROM Product 
GROUP BY ROLLUP(product_type, regist_date); 

GROUPING函式——讓NULL更加容易分辨

  • 用來判斷超級分組記錄的 NULL 的 特定函式 —— GROUPING 函式
  • 該函式在其引數列的值為超級分組記錄 所產生的 NULL 時返回 1,其他情況返回0
  • 使用GROUPING函式能夠簡單地分辨出原始資料中的NULL和超級分組記錄中的NULL
-- 使用GROUPING函式來判斷NULL
SELECT GROUPING(product_type) AS product_type,             
                GROUPING(regist_date) AS regist_date, SUM(sale_price) AS sum_price  
FROM Product 
GROUP BY ROLLUP(product_type, regist_date);

-- 在超級分組記錄的鍵值中插入恰當的字串
SELECT CASE WHEN GROUPING(product_type) = 1
             THEN '商品種類 合計'
             ELSE product_type END AS product_type,
       CASE WHEN GROUPING(regist_date) = 1
             THEN '登記日期 合計'
             ELSE CAST(regist_date AS VARCHAR(16)) END AS regist_date,
       SUM(sale_price) AS sum_price  
FROM Product GROUP BY ROLLUP(product_type, regist_date);

CUBE——用資料來搭積木

  • 所謂 CUBE,就是將 GROUP BY 子句中聚合鍵的“所有可能的組合” 的彙總結果集中到一個結果中
  • 組合的個數就是 2n(n 是聚合鍵的 個數)
  • 可以把CUBE理解為將使用聚合鍵進行切割的模組堆積成一個立方體
-- 使用CUBE取得全部組合的結果
SELECT CASE WHEN GROUPING(product_type) = 1
             THEN '商品種類 合計'
            ELSE product_type END AS product_type,
       CASE WHEN GROUPING(regist_date) = 1
             THEN '登記日期 合計'
            ELSE CAST(regist_date AS VARCHAR(16)) END AS regist_date,
       SUM(sale_price) AS sum_price  
FROM Product 
GROUP BY CUBE(product_type, regist_date);

GROUPING SETS——取得期望的積木

  • 該運算子可以用於從 ROLLUP 或者 CUBE 的結果中取出部分記錄
-- 使用GROUPING SETS取得部分組合的結果
SELECT CASE WHEN GROUPING(product_type) = 1
            THEN '商品種類 合計'
            ELSE product_type END AS product_type,
       CASE WHEN GROUPING(regist_date) = 1
            THEN '登記日期 合計'
            ELSE CAST(regist_date AS VARCHAR(16)) END AS regist_date, 
       SUM(sale_price) AS sum_price  
FROM Product 
GROUP BY GROUPING SETS (product_type, regist_date);