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 相关文章推荐
sqlserver2017共享功能目录路径不可改的解决方法
Apr 16 SQL Server
SqlServer 垂直分表(减少程序改动)
Apr 16 SQL Server
sql字段解析器的实现示例
Jun 23 SQL Server
利用 SQL Server 过滤索引提高查询语句的性能分析
Jul 15 SQL Server
SQL Server2019数据库备份与还原脚本,数据库可批量备份
Nov 20 SQL Server
SQL SERVER存储过程用法详解
Feb 24 SQL Server
通过T-SQL语句创建游标与实现数据库加解密功能
Mar 16 SQL Server
sqlserver连接错误之SQL评估期已过的问题解决
Mar 23 SQL Server
SQL中的连接查询详解
Jun 21 SQL Server
SQL bool盲注和时间盲注详解
Jul 23 SQL Server
mybatis调用sqlserver存储过程返回结果集的方法
SQL Server2019数据库之简单子查询的具有方法
Apr 27 #SQL Server
SQL Server中交叉联接的用法详解
SqlServer 垂直分表(减少程序改动)
Apr 16 #SQL Server
sqlserver2017共享功能目录路径不可改的解决方法
SQLServer2008提示评估期已过解决方案
SQLServer2019 数据库的基本使用之图形化界面操作的实现
You might like
用PHP和ACCESS写聊天室(五)
2006/10/09 PHP
php 生成随机验证码图片代码
2010/02/08 PHP
shell脚本作为保证PHP脚本不挂掉的守护进程实例分享
2013/07/15 PHP
smarty内置函数{loteral}、{ldelim}和{rdelim}用法实例
2015/01/22 PHP
PHP 实现类似js中alert() 提示框
2015/03/18 PHP
php中define用法实例
2015/07/30 PHP
PHP+Apache环境中如何隐藏Apache版本
2017/11/24 PHP
PHP crc32()函数讲解
2019/02/14 PHP
PHP中用Trait封装单例模式的实现
2019/12/18 PHP
jQuery验证Checkbox是否选中的代码 推荐
2011/09/04 Javascript
基于JQuery模仿苹果桌面的Dock效果(初级版)
2012/10/15 Javascript
JavaScript程序中的流程控制语句用法总结
2016/05/23 Javascript
Angular 4依赖注入学习教程之FactoryProvider配置依赖对象(五)
2017/06/04 Javascript
Angularjs按需查询实例代码
2017/10/30 Javascript
浅谈webpack对样式的处理
2018/01/05 Javascript
详解在微信小程序的JS脚本中使用Promise来优化函数处理
2019/03/06 Javascript
layui 实现加载动画以及非真实加载进度的方法
2019/09/23 Javascript
浅谈Vuex的this.$store.commit和在Vue项目中引用公共方法
2020/07/24 Javascript
[09:23]国际邀请赛采访专栏:iG战队VK,Tongfu战队Cu
2013/08/05 DOTA
Python文件和目录操作详解
2015/02/08 Python
python用装饰器自动注册Tornado路由详解
2017/02/14 Python
python 计算两个日期相差多少个月实例代码
2017/05/24 Python
python3实现名片管理系统
2020/11/29 Python
对python修改xml文件的节点值方法详解
2018/12/24 Python
python读写csv文件并增加行列的实例代码
2019/08/01 Python
关于PyTorch源码解读之torchvision.models
2019/08/17 Python
tensorflow 初始化未初始化的变量实例
2020/02/06 Python
CSS3 实现穿梭星空动画
2020/11/13 HTML / CSS
韩国11街:11STREET
2018/03/27 全球购物
P D PAOLA意大利官网:西班牙著名的珠宝首饰品牌
2019/09/24 全球购物
公司新员工的演讲稿注意事项
2014/01/01 职场文书
初中生300字旷课检讨书
2014/11/19 职场文书
可怜妈妈观后感
2015/06/09 职场文书
欠条格式范本
2015/07/03 职场文书
大学生安全教育心得体会
2016/01/15 职场文书
某某幼儿园的教育教学管理调研分析报告
2019/11/29 职场文书