視窗函式(window function)提供了另一種在 SQL 中實作分頁的方式。這個方法很有彈性,而且最重要的是——符合標準

但只有 SQL Server 與 Oracle 能把它用於管線化的 top-N 查詢。

  • PostgreSQL:抓夠列數後不會中止索引掃描,因此執行這類查詢效率極差。
  • MySQL:完全不支援視窗函式。

用 ROW_NUMBER 分頁#

SELECT *
  FROM ( SELECT sales.*
              , ROW_NUMBER() OVER (ORDER BY sale_date DESC
                                          , sale_id   DESC) rn
           FROM sales
       ) tmp
 WHERE rn between 11 and 20
 ORDER BY sale_date DESC, sale_id DESC;

ROW_NUMBERover 子句定義的排序為列編號,外層 where 子句再用這個編號把結果限制在第二頁(第 11 到 20 列)。

Oracle 的執行計畫#

Oracle 認得這個中止條件,並使用 SALE_DATESALE_ID 上的索引產生管線化的 top-N 行為:

---------------------------------------------------------------
|Id | Operation                       | Name    | Rows | Cost |
---------------------------------------------------------------
| 0 | SELECT STATEMENT                |         |1004K | 36877|
|*1 | VIEW                            |         |1004K | 36877|
|*2 | WINDOW NOSORT STOPKEY           |         |1004K | 36877|
| 3 |    TABLE ACCESS BY INDEX ROWID  | SALES   |1004K | 36877|
| 4 |     INDEX FULL SCAN DESCENDING  | SL_DTID |1004K |  2955|
---------------------------------------------------------------
Predicate Information:
1 - filter("RN">=11 AND "RN"<=20)
2 - filter(ROW_NUMBER() OVER (
           ORDER BY "SALE_DATE" DESC, "SALE_ID" DESC )<=20)

WINDOW NOSORT STOPKEY 這個操作說明了兩件事:

  • NOSORT:沒有排序操作。
  • STOPKEY:抵達上界時中止執行。

考慮到這些被中止的操作是以管線化方式執行的,這代表這段查詢與前一節的偏移法一樣有效率

不過,視窗函式的真正強項並不是分頁,而是分析型計算。如果你從未用過視窗函式,絕對值得花幾個小時研讀相關文件。