当前位置: 首页 > news >正文

优化 SQL 查询:选出每个分组最大值的行并插入 10W 条测试数据

在数据库操作中,经常需要从一个表中选出每个分组(例如按字段 a 分组)中某字段(例如 b)最大值的行。本文以一个实际案例为基础,展示如何设计一个表,插入 10W 条测试数据,并比较多种 SQL 查询写法来实现“选出每个 a 值对应的最大 b 值的所有行”。我们将分析每种写法的优缺点,并提供完整的实践步骤,适用于现代数据库(如 PostgreSQL、MySQL 8.0+、SQL Server 等)。


背景

假设我们有一个表 test_table,包含字段 a(分组依据)、b(要取最大值的字段)和 other_column(其他数据列)。目标是:

  1. 创建表并插入 10W 条测试数据,模拟 100 个分组(a 从 1 到 100),每个分组约有 100 条记录。
  2. 查询每个 a 值对应的最大 b 值的所有行,包含所有列。
  3. 比较不同 SQL 写法的性能、可读性和适用场景。

实践步骤

步骤 1:创建表结构

我们创建一个简单的表 test_table,包含以下字段:

  • a:整数,分组字段。
  • b:整数,要取最大值的字段。
  • other_column:字符串,模拟额外数据列。
CREATE TABLE test_table (a INT,b INT,other_column VARCHAR(50)
);

为优化查询性能,添加复合索引:

CREATE INDEX idx_a_b ON test_table(a, b);

说明:索引 (a, b) 可加速分组和排序操作,尤其对窗口函数和连接查询有效。


步骤 2:插入 10W 条测试数据

我们生成 10W 条随机数据:

  • a:随机整数,范围 1 到 100,模拟 100 个分组。
  • b:随机整数,范围 1 到 1000。
  • other_column:生成格式为 Data_00001Data_10000 的字符串。
PostgreSQL 插入脚本

PostgreSQL 使用 GENERATE_SERIES 生成序列,适合快速插入大量数据:

INSERT INTO test_table (a, b, other_column)
SELECT FLOOR(RANDOM() * 100 + 1)::INT AS a,  -- 随机 a (1到100)FLOOR(RANDOM() * 1000 + 1)::INT AS b, -- 随机 b (1到1000)'Data_' || LPAD(ROW_NUMBER() OVER ()::TEXT, 5, '0') AS other_column
FROM GENERATE_SERIES(1, 100000);
MySQL 插入脚本

MySQL 不支持 GENERATE_SERIES,使用存储过程插入数据:

DELIMITER //
CREATE PROCEDURE insert_test_data()
BEGINDECLARE i INT DEFAULT 1;WHILE i <= 100000 DOINSERT INTO test_table (a, b, other_column)VALUES (FLOOR(RAND() * 100 + 1), -- 随机 a (1到100)FLOOR(RAND() * 1000 + 1), -- 随机 b (1到1000)CONCAT('Data_', LPAD(i, 5, '0')));SET i = i + 1;END WHILE;
END //
DELIMITER ;CALL insert_test_data();
验证数据

插入后,检查数据量和分布:

SELECT COUNT(*) FROM test_table; -- 应返回 100000
SELECT a, COUNT(*) FROM test_table GROUP BY a; -- 检查 a 的分布

步骤 3:查询每个 a 的最大 b 值行

我们需要选出每个 a 值对应的最大 b 值的所有行(包括重复的最大值),返回所有列。以下是四种常见写法,适用于不同场景。

写法 1:窗口函数(推荐)

窗口函数在现代数据库中性能优异,代码简洁,适合大数据量场景。

SELECT a, b, other_column
FROM (SELECT a, b, other_column,RANK() OVER (PARTITION BY a ORDER BY b DESC) AS rnkFROM test_table
) t
WHERE rnk = 1;

优点

  • 性能高:只需扫描表一次,配合 (a, b) 索引效率更高(查看查询计划type为ALL,如果子查询中加入where条件,type为range, 使用索引)。
  • 灵活:RANK() 返回所有最大 b 的行;若需单行,可用 ROW_NUMBER()
  • 可读性好:逻辑清晰,易于扩展(如添加其他排序条件)。

缺点

  • 兼容性:仅适用于支持窗口函数的数据库(MySQL 8.0+、PostgreSQL、SQL Server、Oracle)。

适用场景:现代数据库、数据量较大(万行以上)、需要处理重复值。


写法 2:相关子查询

使用子查询找出每个 a 的最大 b,然后匹配行。

SELECT *
FROM test_table t1
WHERE b = (SELECT MAX(b) FROM test_table t2 WHERE t2.a = t1.a);

优点

  • 直观:逻辑简单,易于理解。
  • 通用:适用于所有 SQL 数据库,包括老版本(如 MySQL 5.x)。
  • 正确处理重复值。

缺点

  • 性能较差:相关子查询为每行执行一次子查询,大数据量时可能导致全表扫描。
  • 依赖索引:需要 (a, b) 索引,否则效率低。

适用场景:小数据量(千行以内)、不支持窗口函数的数据库、快速开发。


写法 3:自连接

通过自连接检查是否存在更大的 b 值。

SELECT t1.*
FROM test_table t1
LEFT JOIN test_table t2
ON t1.a = t2.a AND t2.b > t1.b
WHERE t2.b IS NULL;

优点

  • 兼容性强:适用于所有数据库。
  • 索引优化:配合 (a, b) 索引,性能可接受。
  • 处理重复值正确。

缺点

  • 性能:可能不如窗口函数,连接操作开销较大。
  • 可读性:逻辑稍复杂。

适用场景:不支持窗口函数的数据库、中等数据量、需要兼容性。


写法 4:分组后连接

先计算每个 a 的最大 b,再连接回原表。

SELECT t1.*
FROM test_table t1
INNER JOIN (SELECT a, MAX(b) AS max_bFROM test_tableGROUP BY a
) t2
ON t1.a = t2.a AND t1.b = t2.max_b;

优点

  • 性能好:GROUP BY 扫描一次表,配合索引效率高。
  • 兼容性:适用于所有数据库。
  • 正确处理重复值。

缺点

  • 代码稍长:需要子查询和连接。
  • 索引依赖:需要 (a, b) 索引。

适用场景:不支持窗口函数的数据库、大数据量、需要兼容性。


步骤 4:示例数据与结果

假设插入的部分数据如下:

INSERT INTO test_table (a, b, other_column) VALUES
(1, 200, 'Data_00001'),
(1, 800, 'Data_00002'),
(1, 500, 'Data_00003'),
(2, 300, 'Data_00004'),
(2, 900, 'Data_00005'),
(2, 900, 'Data_00006'),
(3, 100, 'Data_00007'),
(3, 600, 'Data_00008');

运行窗口函数查询:

SELECT a, b, other_column
FROM (SELECT a, b, other_column,RANK() OVER (PARTITION BY a ORDER BY b DESC) AS rnkFROM test_table
) t
WHERE rnk = 1;

结果

| a | b   | other_column |
|---|-----|--------------|
| 1 | 800 | Data_00002   |
| 2 | 900 | Data_00005   |
| 2 | 900 | Data_00006   |
| 3 | 600 | Data_00008   |

说明

  • a = 1:最大 b 是 800,返回 1 行。
  • a = 2:最大 b 是 900,有 2 行(重复值),都返回。
  • a = 3:最大 b 是 600,返回 1 行。
  • 对于10W 条数据,假设 a 分布在 1 到 100,结果约为 100 行(可能更多,若有重复)。

其他写法(相关子查询、自连接、分组后连接)会返回相同结果,但性能和可读性有所差异。


步骤 5:性能优化与验证

  1. 检查索引:确保 (a, b) 索引存在:
    CREATE INDEX idx_a_b ON test_table(a, b);
    
  2. 分析查询计划:使用 EXPLAINEXPLAIN ANALYZE 检查查询是否利用索引:
    EXPLAIN SELECT a, b, other_column
    FROM (SELECT a, b, other_column,RANK() OVER (PARTITION BY a ORDER BY b DESC) AS rnkFROM test_table
    ) t
    WHERE rnk = 1;
    
  3. 性能测试
    • 10W 条数据:窗口函数通常最快,其次是分组后连接,相关子查询较慢。
    • 若数据量增至百万级,窗口函数或分组后连接配合索引表现更优。
  4. 处理特殊情况
    • 重复值:RANK() 确保返回所有最大 b 的行;若需单行,用 ROW_NUMBER()
    • 空值:若 b 可能为 NULL,可在查询中添加 COALESCE(b, 0)WHERE b IS NOT NULL

写法比较

写法性能(10,000 行)兼容性可读性重复值处理适用场景
窗口函数MySQL 8.0+、PostgreSQL 等支持大数据量、现代数据库
相关子查询中低所有数据库支持小数据量、快速开发
自连接所有数据库支持不支持窗口函数、中等数据量
分组后连接所有数据库支持不支持窗口函数、大数据量

推荐:优先使用窗口函数(RANK()),因为:

  • 性能最佳:单次表扫描,索引利用率高。
  • 代码简洁:逻辑清晰,易于维护。
  • 灵活:可处理重复值或扩展排序条件。

若数据库不支持窗口函数(如 MySQL 5.x),推荐分组后连接。


扩展与注意事项

  1. 数据库兼容性
    • PostgreSQL、SQL Server、Oracle:支持所有写法,窗口函数最优。
    • MySQL 5.x:不支持窗口函数,使用分组后连接或自连接。
  2. 数据量扩展
    • 百万级数据:窗口函数或分组后连接配合索引仍高效。
    • 更大规模:考虑分区表或分布式数据库。
  3. 其他需求
    • b 有 NULL 值,添加 WHERE b IS NOT NULLCOALESCE
    • 若需按其他字段排序(如 other_column),在窗口函数中调整 ORDER BY
  4. 性能监控
    • 定期检查索引使用情况。
    • 对于生产环境,测试查询性能并调整。

结论

通过插入 10W 条测试数据并比较四种 SQL 写法,我们发现窗口函数(RANK())在现代数据库中通常是最优选择,兼顾性能、可读性和灵活性。对于不支持窗口函数的数据库,分组后连接是次优选择。实践时,确保创建适当索引,并根据数据量和数据库版本选择合适写法。

http://www.dtcms.com/a/330855.html

相关文章:

  • 云计算-Docker Compose 实战:从OwnCloud、WordPress、SkyWalking、Redis ,Rabbitmq等服务配置实例轻松搞定
  • 【代码随想录day 20】 力扣 108.将有序数组转换为二叉搜索树
  • 老生常谈之引用计数:《More Effective C++》条款29
  • 炎热的夏天
  • SQL181 第二快/慢用时之差大于试卷时长一半的试卷
  • 掌握MATLAB三维可视化:从基础到实战技巧
  • Redis 从入门到生产:数据结构、持久化、集群、工程实践与避坑(含 Node.js/Python 示例)
  • jenkins在windows配置sshpass
  • 构建Node.js单可执行应用(SEA)的方法
  • 【前端工具】使用 Node.js 脚本实现项目打包后自动压缩
  • Go语言defer机制详解与应用
  • 机器学习介绍
  • 预训练模型在机器翻译中的应用:迁移学习的优势详解
  • 华为实验WLAN 基础配置随练
  • dkms安装nvidia驱动和多内核支持
  • 【motion】GIF 转mp4及ubuntu的VLC播放
  • 数据结构初阶(14)排序算法—交换排序(冒泡)(动图演示)
  • 基于SpringBoot+Vue的房屋匹配系统(WebSocket实时通讯、协同过滤算法、地图API、Echarts图形化分析)
  • iOS App TestFlight 上架全流程案例,从 0 到 1 完成内测分发
  • C#通过TCP_IP与PLC通信
  • vue部署正式环境上传nginx后遇到的问题
  • 分享10个ai生成ppt网站(附ai生成ppt入口)
  • ZigBee入门与提高(3)—— ZigBee协议初识
  • Wireshark中常见协议
  • 重学JS-002 --- JavaScript算法与数据结构(二)JavaScript 基础知识
  • MFT 在零售行业的实践案例与场景:加速文件集成与业务协作的高效方案
  • day30 TCP通信
  • 财务自动化软件敏感数据泄露风险评估与防护措施
  • B站 韩顺平 笔记 (Day 18)
  • C++ 仿RabbitMQ实现消息队列项目