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 相关文章推荐
2021-4-3课程——SQL Server查询【2】
Apr 05 SQL Server
sql查询结果列拼接成逗号分隔的字符串方法
May 25 SQL Server
SQLServer中JSON文档型数据的查询问题解决
Jun 27 SQL Server
SQLServer 错误: 15404,无法获取有关 Windows NT 组/用户 WIN-8IVSNAQS8T7\Administrator 的信息
Jun 30 SQL Server
SQL Server代理:理解SQL代理错误日志处理方法
Jun 30 SQL Server
Sql Server之数据类型详解
Feb 28 SQL Server
SQL Server使用导出向导功能
Apr 08 SQL Server
SQL Server的存储过程与触发器以及系统函数和自定义函数
Apr 10 SQL Server
SQL Server使用T-SQL语句批处理
May 20 SQL Server
SQL SERVER中的流程控制语句
May 25 SQL Server
SQL Server使用PIVOT与unPIVOT实现行列转换
May 25 SQL Server
mybatis调用sqlserver存储过程返回结果集的方法
SQL Server2019数据库之简单子查询的具有方法
Apr 27 #SQL Server
SQL Server中交叉联接的用法详解
SqlServer 垂直分表(减少程序改动)
Apr 16 #SQL Server
sqlserver2017共享功能目录路径不可改的解决方法
SQLServer2008提示评估期已过解决方案
SQLServer2019 数据库的基本使用之图形化界面操作的实现
You might like
用PHP将网址字符串转换成超链接(网址或email)
2010/05/25 PHP
深入PHP许愿墙模块功能分析
2013/06/25 PHP
PHP调试函数和日志记录函数分享
2015/01/31 PHP
THINKPHP支持YAML配置文件的设置方法
2015/03/17 PHP
PHPStrom中实用的功能和快捷键大全
2015/09/23 PHP
PHP对象链式操作实现原理分析
2016/10/09 PHP
thinkphp 5框架实现登陆,登出及session登陆状态检测功能示例
2019/10/10 PHP
JavaScript表单常用验证集合
2008/01/16 Javascript
jQuery:节点(插入,复制,替换,删除)操作
2013/03/04 Javascript
详解Bootstrap创建表单的三种格式(一)
2016/01/04 Javascript
JQuery学习总结【一】
2016/12/01 Javascript
jQuery ajax的功能实现方法详解
2017/01/06 Javascript
从0到1构建vueSSR项目之路由的构建
2019/03/07 Javascript
详解vue的数据劫持以及操作数组的坑
2019/04/18 Javascript
微信小程序云开发之云函数详解
2019/05/16 Javascript
微信小程序 确认框的实现(附代码)
2019/07/23 Javascript
Vue 数组和对象更新,但是页面没有刷新的解决方式
2019/11/09 Javascript
vue-router 路由传参用法实例分析
2020/03/06 Javascript
antd 表格列宽自适应方法以及错误处理操作
2020/10/27 Javascript
[01:47]2018年度DOTA2最具人气解说-完美盛典
2018/12/16 DOTA
Python获取当前函数名称方法实例分享
2018/01/18 Python
对Python3 序列解包详解
2019/02/16 Python
对PyQt5中树结构的实现方法详解
2019/06/17 Python
余弦相似性计算及python代码实现过程解析
2019/09/18 Python
pytorch中tensor张量数据类型的转化方式
2019/12/31 Python
Python使用Socket实现简单聊天程序
2020/02/28 Python
python图片合成的示例
2020/11/09 Python
关于python中remove的一些坑小结
2021/01/04 Python
N:Philanthropy官网:美国洛杉矶基础款服装
2020/06/09 全球购物
校园报刊亭的创业计划书
2014/01/02 职场文书
置业顾问岗位职责
2014/03/02 职场文书
库房管理员岗位职责
2014/03/09 职场文书
家长对学生的评语
2014/04/18 职场文书
2015年妇产科工作总结
2015/05/18 职场文书
2019年七夕情人节浪漫祝福语大全!
2019/08/08 职场文书
Redis实战之Lettuce的使用技巧详解
2022/12/24 Redis