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 相关文章推荐
SQLServer 日期函数大全(小结)
Apr 08 SQL Server
SQLServer2019 数据库的基本使用之图形化界面操作的实现
Apr 08 SQL Server
在 SQL 语句中处理 NULL 值的方法
Jun 07 SQL Server
解决sql server 数据库,sa用户被锁定的问题
Jun 11 SQL Server
SQL Server作业失败:无法确定所有者是否有服务器访问权限的解决方法
Jun 30 SQL Server
sql server 累计求和实现代码
Feb 28 SQL Server
SQL Server远程连接的设置步骤(图文)
Mar 23 SQL Server
SQL Server 忘记密码以及重新添加新账号
Apr 26 SQL Server
SQL解决未能删除约束问题drop constraint
May 30 SQL Server
一次SQL查询优化原理分析(900W+数据从17s到300ms)
Jun 10 SQL Server
详解SQL报错盲注
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
新52大事件
2020/03/03 欧美动漫
比较strtr, str_replace和preg_replace三个函数的效率
2013/06/26 PHP
PHP cURL初始化和执行方法入门级代码
2015/05/28 PHP
php 使用expat方式解析xml文件操作示例
2019/11/26 PHP
让textarea自动调整大小的js代码
2011/04/12 Javascript
jquery操作checkbox示例分享
2014/07/21 Javascript
javascript自动生成包含数字与字符的随机字符串
2015/02/09 Javascript
js实现Form栏显示全格式时间时钟效果代码
2015/08/19 Javascript
JavaScript 弹出子窗体并返回结果到父窗体的实现代码
2016/05/28 Javascript
js实现图片切换(动画版)
2016/12/25 Javascript
JavaScript计时器用法分析【setTimeout和clearTimeout】
2017/01/18 Javascript
Vue关于数据绑定出错解决办法
2017/05/15 Javascript
Vue声明式渲染详解
2017/05/17 Javascript
Django与Vue语法的冲突问题完美解决方法
2017/12/14 Javascript
关于js对textarea换行符的处理方法浅析
2018/08/03 Javascript
vue如何搭建多页面多系统应用
2020/06/17 Javascript
vue封装自定义指令之动态显示title操作(溢出显示,不溢出不显示)
2020/11/12 Javascript
在Python的列表中利用remove()方法删除元素的教程
2015/05/21 Python
python面向对象多线程爬虫爬取搜狐页面的实例代码
2018/05/31 Python
Python简单过滤字母和数字的方法小结
2019/01/09 Python
pywinauto自动化操作记事本
2019/08/26 Python
Flask框架 CSRF 保护实现方法详解
2019/10/30 Python
你可能不知道的Python 技巧小结
2020/01/29 Python
python 使用递归实现打印一个数字的每一位示例
2020/02/27 Python
Django Form常用功能及代码示例
2020/10/13 Python
Python安装Bs4的多种方法
2020/11/28 Python
CSS3实现10种Loading效果
2016/07/11 HTML / CSS
完美解决IE8下不兼容rgba()的问题
2017/03/31 HTML / CSS
德国街头和运动文化高品质商店:BSTN Store
2017/08/26 全球购物
MATCHESFASHION.COM美国官网:英国奢侈品零售商
2018/10/29 全球购物
行政前台岗位职责
2013/12/04 职场文书
违纪检讨书2000字
2014/02/08 职场文书
会计工作总结范文2014
2014/12/23 职场文书
2015年艾滋病防治工作总结
2015/05/22 职场文书
在校学生证明格式
2015/06/24 职场文书
高一作文之暖冬
2019/11/09 职场文书