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 相关文章推荐
SQL Server 数据库实验课第五周——常用查询条件
Apr 05 SQL Server
SQLServer2019 数据库的基本使用之图形化界面操作的实现
Apr 08 SQL Server
如何有效防止sql注入的方法
May 25 SQL Server
SQL 尚未定义空闲 CPU 条件 - OnIdle 作业计划将不起任何作用
Jun 30 SQL Server
SQL写法--行行比较
Aug 23 SQL Server
SQL SERVER存储过程用法详解
Feb 24 SQL Server
SQL Server数据库查询出现阻塞之性能调优
Apr 10 SQL Server
SQL Server #{}可以防止SQL注入
May 11 SQL Server
SqlServer常用函数及时间处理小结
May 08 SQL Server
mybatis调用sqlserver存储过程返回结果集的方法
SQL Server2019数据库之简单子查询的具有方法
Apr 27 #SQL Server
SQL Server中交叉联接的用法详解
SqlServer 垂直分表(减少程序改动)
Apr 16 #SQL Server
sqlserver2017共享功能目录路径不可改的解决方法
SQLServer2008提示评估期已过解决方案
SQLServer2019 数据库的基本使用之图形化界面操作的实现
You might like
smarty模板引擎从php中获取数据的方法
2015/01/22 PHP
PHP+shell实现多线程的方法
2015/07/01 PHP
PHP中Session和Cookie是如何操作的
2015/10/10 PHP
PHP实现 APP端微信支付功能
2018/06/22 PHP
php中字符串和整数比较的操作方法
2019/06/06 PHP
如何用js控制css中的float的代码
2007/08/16 Javascript
js获取dom的高度和宽度(可见区域及部分等等)
2013/06/13 Javascript
JavaScript中继承用法实例分析
2015/05/16 Javascript
jQuery实现点击行选中或取消CheckBox的方法
2016/08/01 Javascript
jQuery 常见小例汇总
2016/12/14 Javascript
JS获得多个同name 的input输入框的值的实现方法
2017/01/09 Javascript
微信小程序 本地存储及登录页面处理实例详解
2017/01/11 Javascript
遍历json获得数据的几种方法小结
2017/01/21 Javascript
深入理解Node.js中通用基础设计模式
2017/09/19 Javascript
JQuery Ajax执行跨域请求数据的解决方案
2018/12/10 jQuery
JavaScript数值类型知识汇总
2019/11/17 Javascript
Vue实现商品飞入购物车效果(电商项目)
2019/11/26 Javascript
js实现图片粘贴到网页
2019/12/06 Javascript
el-form 多层级表单的实现示例
2020/09/10 Javascript
[10:24]郎朗助力完美“圣”典,天籁交织奏响序曲
2016/12/18 DOTA
一个超级简单的python web程序
2014/09/11 Python
使用GitHub和Python实现持续部署的方法
2019/05/09 Python
Python&&GDAL实现NDVI的计算方式
2020/01/09 Python
在python tkinter界面中添加按钮的实例
2020/03/04 Python
python让函数不返回结果的方法
2020/06/22 Python
英国最大的网上药品商店:Chemist Direct
2017/12/16 全球购物
英国二手iPhone、音乐、电影和游戏商店:musicMagpie
2018/10/26 全球购物
美国艺术和工艺品商店:Hobby Lobby
2020/12/09 全球购物
J2EE面试题集锦(附答案)
2013/08/16 面试题
旅游与酒店管理的自我评价分享
2013/11/03 职场文书
竞聘演讲稿范文
2014/01/12 职场文书
2014县政府领导班子对照检查材料思想汇报
2014/09/25 职场文书
走群众路线学习笔记
2014/11/06 职场文书
小学生纪律委员竞选稿
2015/11/19 职场文书
pytorch锁死在dataloader(训练时卡死)
2021/05/28 Python
react使用antd的上传组件实现文件表单一起提交功能(完整代码)
2021/06/29 Javascript