SQL案例分析:窗口函数实现高效分页查询
sinye56 2024-09-22 08:26 8 浏览 0 评论
我们在使用 SQL 语句实现分页查询时,需要知道一些额外的参数信息,例如查询返回的总行数、当前所在的页数、最后一页的页数等。
在传统的实现方法中我们需要执行额外的查询语句获得这些信息,本文介绍一种只需要一个查询语句就可以返回所有数据的方法,也就是通过 SQL 窗口函数实现高效的分页查询功能。
示例表和数据:https://github.com/dongxuyang1985/thinking_in_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|
关于窗口函数的介绍可以参考这篇文章。
总结
本文介绍了如何利用窗口函数在一个语句中返回分页查询的结果和所需的全部参数,这种方法比传统的分页查询实现更加简洁高效。
相关推荐
- RHEL8和CentOS8怎么重启网络
-
本文主要讲解如何重启RHEL8或者CentOS8网络以及如何解决RHEL8和CentOS8系统的网络管理服务报错,当我们安装好RHEL8或者CentOS8,重启启动网络时,会出现以下报错:...
- Linux 内、外网双网卡路由配置
-
1.路由信息的影响Linux系统中如果有多张网卡的情况下,如果路由信息配置不正确,...
- Linux——centos7修改网卡名
-
修改网卡名这个操作可能平时用不太上,可作为了解。修改网卡默认名从ens33改成eth01.首先修改网卡配置文件名(建议将原配置文件进行备份)...
- CentOS7下修改网卡名称为ethX的操作方法
-
?Linux操作系统的网卡设备的传统命名方式是eth0、eth1、eth2等,而CentOS7提供了不同的命名规则,默认是基于固件、拓扑、位置信息来分配。这样做的优点是命名全自动的、可预知的...
- Linux 网卡名称enss33修改为eth0
-
一、CentOS修改/etc/sysconfig/grub文件(修改前先备份)为GRUB_CMDLINE_LINUX变量增加2个参数(net.ifnames=0biosdevname=0),修改完成...
- CentOS下双网卡绑定,实现带宽飞速
-
方式一1.新建/etc/sysconfig/network-scripts/ifcfg-bond0文件DEVICE=bond0IPADDR=191.3.60.1NETMASK=255.255.2...
- linux 双网卡双网段设置路由转发
-
背景网络情况linux双网卡:网卡A(ens3)和网卡B(...
- Linux-VMware设置网卡保持激活
-
Linux系统只有在激活网卡的状态下才能去连接网络,进行网络通讯。修改配置文件(永久激活网卡)...
- VMware虚拟机三种网络模式
-
01.VMware虚拟机三种网络模式由于linux目前很热门,越来越多的人在学习linux,但是买一台服务放家里来学习,实在是很浪费。那么如何解决这个问题?虚拟机软件是很好的选择,常用的虚拟机软件有v...
- 2023年最新版 linux克隆虚拟机 解决网卡uuid重复问题
-
问题描述1、克隆了虚拟机,两台虚拟机里面的ip以及网卡的uuid都是一样的2、ip好改,但是uuid如何改呢?解决问题1、每台主机应该保证网卡的UUID是唯一的,避免后面网络通信有问题...
- Linux网卡的Vlan配置,你可能不了解的玩法
-
如果服务器上连的交换机端口已经预先设置了TRUNK,并允许特定的VLAN可以通过,那么服务器的网卡在配置时就必须指定所属的VLAN,否则就不通了,这种情形在虚拟化部署时较常见。例如在一个办公环境中,办...
- Centos7 网卡绑定
-
1、切换到指定目录#备份网卡数据cd/etc/sysconfig/network-scriptscpifcfg-enp5s0f0ifcfg-enp5s0f0.bak...
- Linux搭建nginx+keepalived 高可用(主备+双主模式)
-
一:keepalived简介反向代理及负载均衡参考:...
- Linux下Route 路由指令使用详解
-
linuxroute命令用于显示和操作IP路由表。要实现两个不同子网之间的通信,需要一台连接两个网络的路由器,或者同时位于两个网络的网关来实现。在Linux系统中,设置路由通常是为了解决以下问题:该...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- oracle忘记用户名密码 (59)
- oracle11gr2安装教程 (55)
- mybatis调用oracle存储过程 (67)
- oracle spool的用法 (57)
- oracle asm 磁盘管理 (67)
- 前端 设计模式 (64)
- 前端面试vue (56)
- linux格式化 (55)
- linux图形界面 (62)
- linux文件压缩 (75)
- Linux设置权限 (53)
- linux服务器配置 (62)
- mysql安装linux (71)
- linux启动命令 (59)
- 查看linux磁盘 (72)
- linux用户组 (74)
- linux多线程 (70)
- linux设备驱动 (53)
- linux自启动 (59)
- linux网络命令 (55)
- linux传文件 (60)
- linux打包文件 (58)
- linux查看数据库 (61)
- linux获取ip (64)
- linux进程通信 (63)