SQL 窗口函数实现高效分页查询的案例分析


Posted in SQL Server onMay 21, 2021

SQL 窗口函数实现高效分页查询的案例分析

 

?不闻不若闻之,闻之不若见之,见之不若知之,知之不若行之。学至于行之而止矣。——荀子

大家好!我是只谈技术不剪发的 Tony 老师。

在使用 SQL 语句实现分页查询时,我们需要知道一些额外的参数信息,例如查询返回的总行数、当前所在的页数、最后一页的页数等。在传统的实现方法中我们需要执行额外的查询语句获得这些信息,本文介绍一种只需要一个查询语句就可以返回所有数据的方法,也就是通过 SQL 窗口函数实现高效的分页查询功能。

传统方法实现分页查询

在 SQL 中实现分页查询的传统方法就是利用标准的 OFFSET … FETCH 语句或者许多数据库支持的 LIMIT … OFFSET 语句,例如:

-- Oracle、SQL Server、PostgreSQL
SELECT emp_name, sex, email 
FROM employee
ORDER BY emp_id
OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY;

-- MySQL、PostgreSQL、SQLite
SELECT emp_name, sex, email 
FROM employee
ORDER BY emp_id
LIMIT 10 OFFSET 10;

以上语句非常容易理解,返回的是第 2 页中的 10 条记录。但是问题在于我们如何知道总共包含多少页数据(或者总的记录数),显然在此之前我们需要执行另一个查询:

SELECT COUNT(*)
FROM employee;

COUNT(*)|
--------+
      25|

有了总的记录数 25 之后,我们可以计算出数据总共有 3 页,每页 10 条。

这种方法要求我们每次进行分页查询时都需要执行 2 个查询语句,使用起来不是很方便。下面我们介绍更加高效的窗口函数分页查询。

?关于分页查询的实现,OFFSET 分页对于大量数据的分页可能存在性能问题,另一种方法就是采用键集分页(keyset pagination)。

窗口函数实现分页查询

首先让我们考虑一下使用 OFFSET 分页查询时需要哪些参数:

  • TOTAL_ROWS,总记录数;
  • CURRENT_PAGE,当前所在页码;
  • MAX_PAGE_SIZE,每一页最多显示的记录数,例如 10、20、50;
  • ACTUAL_PAGE_SIZE,当前页实际包含的记录数;
  • ROW_NBR,每条记录的实际偏移量;
  • LAST_PAGE,当前页是否是最后一页。

每一页最多显示的记录数(MAX_PAGE_SIZE)是我们传递给数据库的参数,其他则是查询返回的结果,我们可以通过下面的查询语句实现所有的功能:

-- Oracle、SQL Server、PostgreSQL
WITH e AS ( -- 初始查询
  SELECT emp_id, emp_name, sex, email
  FROM employee
),
t AS (
  SELECT emp_id, emp_name, sex, email, 
         COUNT(*) OVER () AS total_rows, -- 总记录数
         ROW_NUMBER () OVER (ORDER BY e.emp_id) AS row_nbr -- 偏移量,ORDER BY和初始查询相同
  FROM e
  ORDER BY e.emp_id -- 排序
  OFFSET 10 ROWS -- 分页
  FETCH NEXT 10 ROWS ONLY
)
SELECT
  emp_id, emp_name, sex, email,
  COUNT(*) OVER () AS actual_page_size, -- 当前页实际记录数
  CASE MAX(row_nbr) OVER () 
    WHEN total_rows THEN 'Y' 
    ELSE 'N' 
  END AS last_page, -- 是否最后一页
  total_rows, -- 总记录数
  row_nbr, -- 每一条数据的偏移量
  ((row_nbr - 1) / 10) + 1 AS current_page -- 当前所在页码
FROM t
ORDER BY emp_id;


-- MySQL、PostgreSQL、SQLite
WITH e AS ( -- 初始查询
  SELECT emp_id, emp_name, sex, email
  FROM employee
),
t AS (
  SELECT emp_id, emp_name, sex, email, 
         COUNT(*) OVER () AS total_rows, -- 总记录数
         ROW_NUMBER () OVER (ORDER BY e.emp_id) AS row_nbr -- 偏移量,ORDER BY和初始查询相同
  FROM e
  ORDER BY e.emp_id -- 排序
  LIMIT 10
  OFFSET 10 ROWS -- 分页
)
SELECT
  emp_id, emp_name, sex, email,
  COUNT(*) OVER () AS actual_page_size, -- 当前页实际记录数
  CASE MAX(row_nbr) OVER () 
    WHEN total_rows THEN 'Y' 
    ELSE 'N' 
  END AS last_page, -- 是否最后一页
  total_rows, -- 总记录数
  row_nbr, -- 每一条数据的偏移量
  ((row_nbr - 1) / 10) + 1 AS current_page -- 当前所在页码
FROM t
ORDER BY emp_id;

首先,我们定义了通用表表达式 e,它是返回数据的初始查询,可以增加其他的过滤条件。

然后,我们基于 e 定义了另一个通用表表达式 t,在定义中进行了排序和分页,并且利用窗口函数 COUNT(*) 计算总的记录数,利用窗口函数 ROW_NUMBER () 计算每条数据的偏移量(行号)。

接下来,我们基于 t 返回了更多的参数,利用窗口函数 COUNT(*) 返回了当前页的实际记录数,通过窗口函数 MAX(row_nbr) 返回的当前页最大偏移量和总记录数的比较判断是否最后一页,以及当前所在的页码。

emp_id|emp_name|sex|email              |actual_page_size|last_page|total_rows|row_nbr|current_page|
------+--------+---+-------------------+----------------+---------+----------+-------+------------+
    11|关平    |男 |guanping@shuguo.com|              10|N        |        27|     11|           2|
    12|赵氏    |女 |zhaoshi@shuguo.com |              10|N        |        27|     12|           2|
    13|关兴    |男 |guanxing@shuguo.com|              10|N        |        27|     13|           2|
    14|张苞    |男 |zhangbao@shuguo.com|              10|N        |        27|     14|           2|
    15|赵统    |男 |zhaotong@shuguo.com|              10|N        |        27|     15|           2|
    16|周仓    |男 |zhoucang@shuguo.com|              10|N        |        27|     16|           2|
    17|马岱    |男 |madai@shuguo.com   |              10|N        |        27|     17|           2|
    18|法正    |男 |fazheng@shuguo.com |              10|N        |        27|     18|           2|
    19|庞统    |男 |pangtong@shuguo.com|              10|N        |        27|     19|           2|
    20|蒋琬    |男 |jiangwan@shuguo.com|              10|N        |        27|     20|           2|

总结

本文介绍了如何利用窗口函数在一个语句中返回分页查询的结果和所需的全部参数,这种方法比传统的分页查询实现更加简洁高效。

SQL Server 相关文章推荐
【HBU】数据库第四周 单表查询
Apr 05 SQL Server
如何有效防止sql注入的方法
May 25 SQL Server
Spark SQL 2.4.8 操作 Dataframe的两种方式
Oct 16 SQL Server
SQL Server表分区降低运维和维护成本
Apr 08 SQL Server
sql server偶发出现死锁的解决方法
Apr 10 SQL Server
SQLServer权限之只开启创建表权限
Apr 12 SQL Server
SQL Server中的游标介绍
May 20 SQL Server
SQL Server中锁的用法
May 20 SQL Server
一次SQL查询优化原理分析(900W+数据从17s到300ms)
Jun 10 SQL Server
SQL Server数据库备份和恢复数据库的全过程
Jun 14 SQL Server
mybatis调用sqlserver存储过程返回结果集的方法
SQL Server2019数据库之简单子查询的具有方法
Apr 27 #SQL Server
SQL Server中交叉联接的用法详解
SqlServer 垂直分表(减少程序改动)
Apr 16 #SQL Server
sqlserver2017共享功能目录路径不可改的解决方法
SQLServer2008提示评估期已过解决方案
SQLServer2019 数据库的基本使用之图形化界面操作的实现
You might like
简体中文转换为繁体中文的PHP函数
2006/10/09 PHP
php递归调用删除数组空值元素的方法
2015/04/28 PHP
php插入含有特殊符号数据的处理方法
2016/11/24 PHP
PHP实现单文件、多个单文件、多文件上传函数的封装示例
2019/09/02 PHP
JavaScript高级程序设计 XML、Ajax 学习笔记
2011/09/10 Javascript
js 幻灯片的实现
2011/12/06 Javascript
在ASP.NET中使用JavaScript脚本的方法
2013/11/12 Javascript
最棒的Angular2表格控件
2016/08/10 Javascript
JS图片压缩(pc端和移动端都适用)
2017/01/12 Javascript
原生JS实现垂直手风琴效果
2017/02/19 Javascript
详谈commonjs模块与es6模块的区别
2017/10/18 Javascript
javascript设计模式 ? 策略模式原理与用法实例分析
2020/04/21 Javascript
[42:06]2019国际邀请赛全明星赛 8.23
2019/09/05 DOTA
python实现封装得到virustotal扫描结果
2014/10/05 Python
python的Tqdm模块的使用
2018/01/10 Python
Python打印输出数组中全部元素
2018/03/13 Python
python合并同类型excel表格的方法
2018/04/01 Python
python 通过logging写入日志到文件和控制台的实例
2018/04/28 Python
Python3.6.0+opencv3.3.0人脸检测示例
2018/05/25 Python
python matplotlib绘图,修改坐标轴刻度为文字的实例
2018/05/25 Python
python读取并定位excel数据坐标系详解
2019/06/26 Python
centos 安装Python3 及对应的pip教程详解
2019/06/28 Python
python实现连连看辅助之图像识别延伸
2019/07/17 Python
Python解析命令行读取参数之argparse模块
2019/07/26 Python
关于pytorch中网络loss传播和参数更新的理解
2019/08/20 Python
python numpy 常用随机数的产生方法的实现
2019/08/21 Python
Python如何实现单例模式
2016/06/03 面试题
歌唱比赛获奖感言
2014/01/21 职场文书
民族团结先进个人事迹材料
2014/06/02 职场文书
课程设计的心得体会
2014/09/03 职场文书
中职毕业生自我鉴定
2014/09/13 职场文书
乡党政领导班子群众路线教育实践活动个人对照检查材料
2014/09/20 职场文书
2014年教师德育工作总结
2014/11/10 职场文书
anaconda python3.8安装后降级
2021/06/11 Python
vue配置型表格基于el-table拓展之table-plus组件
2022/04/12 Vue.js
Vue 打包后相对路径的引用问题
2022/06/05 Vue.js