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 相关文章推荐
SQLServer2019 数据库的基本使用之图形化界面操作的实现
Apr 08 SQL Server
sqlserver2017共享功能目录路径不可改的解决方法
Apr 16 SQL Server
sql字段解析器的实现示例
Jun 23 SQL Server
SQLServer中JSON文档型数据的查询问题解决
Jun 27 SQL Server
sql server 累计求和实现代码
Feb 28 SQL Server
SQL Server内存机制浅探
Apr 06 SQL Server
SQLServer权限之只开启创建表权限
Apr 12 SQL Server
SQL Server 中的事务介绍
May 20 SQL Server
SQL Server使用CROSS APPLY与OUTER APPLY实现连接查询
May 25 SQL Server
SQL Server中的逻辑函数介绍
May 25 SQL Server
SQL使用复合索引实现数据库查询的优化
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学习之PHP运算符
2006/10/09 PHP
深入了解 register_globals (附register_globals=off 网站打不开的解决方法)
2012/06/27 PHP
javascript编程起步(第四课)
2007/01/10 Javascript
js 判断浏览器类型 去全角、半角空格 自动关闭当前窗口
2009/04/10 Javascript
jQuery 动画弹出窗体支持多种展现方式
2010/04/29 Javascript
jquery的$getjson调用并获取远程的JSON字符串问题
2012/12/10 Javascript
Javascript模块化编程(三)require.js的用法及功能介绍
2013/01/17 Javascript
js模拟C#中List的简单实例
2014/03/06 Javascript
javascript结合ajax读取txt文件内容
2014/12/05 Javascript
使用C++为node.js写扩展模块
2015/04/22 Javascript
JQuery日历插件My97DatePicker日期范围限制
2016/01/20 Javascript
JS面向对象编程详解
2016/03/06 Javascript
Javascrip实现文字跳动特效
2016/11/27 Javascript
jquery实现图片列表鼠标移入微动
2016/12/01 Javascript
详解angularjs结合pagination插件实现分页功能
2017/02/10 Javascript
在vue里面设置全局变量或数据的方法
2018/03/09 Javascript
使用Vue.observable()进行状态管理的实例代码详解
2019/05/26 Javascript
vue.js+elementUI实现点击左右箭头切换头像功能(类似轮播图效果)
2019/09/05 Javascript
jquery实现上传图片功能
2020/06/29 jQuery
原生JS实现pc端轮播图效果
2020/12/21 Javascript
python实现用户登陆邮件通知的方法
2015/07/09 Python
Python中list列表的一些进阶使用方法介绍
2015/08/15 Python
Django contenttypes 框架详解(小结)
2018/08/13 Python
Python爬虫常用库的安装及其环境配置
2018/09/19 Python
python3实现带多张图片、附件的邮件发送
2019/08/10 Python
python实现多线程端口扫描
2019/08/31 Python
Python 将json序列化后的字符串转换成字典(推荐)
2020/01/06 Python
python利用百度云接口实现车牌识别的示例
2020/02/21 Python
基于Python生成个性二维码过程详解
2020/03/05 Python
大学生职业生涯规划范文
2014/01/22 职场文书
药剂专业个人求职信范文
2014/04/29 职场文书
应届硕士毕业生自荐信
2014/05/26 职场文书
数据保密承诺书
2014/06/03 职场文书
新年晚会主持词开场白
2015/05/28 职场文书
基于Python实现一个春节倒计时脚本
2022/01/22 Python
MySQL学习必备条件查询数据
2022/03/25 MySQL