SQL查询指定user_id符合条件的首条pending请求实现与优化
正确SQL写法
推荐写法(支持MySQL 8.0+、PostgreSQL等主流支持窗口函数的数据库)
SELECT id, user_id, status, requested_by_id, position, order_id FROM ( SELECT *, ROW_NUMBER() OVER ( PARTITION BY order_id ORDER BY position ASC ) AS rn FROM requests WHERE status = 'pending' AND user_id = :target_user_id -- 替换为待查询的指定user_id AND requested_by_id IS NOT NULL AND position IS NOT NULL ) t WHERE rn = 1 -- 合并第一类符合规则的空字段记录 UNION ALL SELECT id, user_id, status, requested_by_id, position, order_id FROM requests WHERE status = 'pending' AND user_id = :target_user_id AND requested_by_id IS NULL AND position IS NULL;
逻辑说明:两类符合规则的记录分开查询后用
UNION ALL合并,避免条件混杂导致的逻辑错误:第一类直接过滤两个字段为空的pending记录;第二类先过滤两个字段非空的pending记录,用窗口函数按order_id分组、position升序打行号,只取每个分组行号为1的记录,也就是同order_id下position最小的首条pending请求,完全匹配规则要求。
低版本兼容写法(不支持窗口函数的场景,比如MySQL 5.x)
SELECT * FROM requests r WHERE status = 'pending' AND user_id = :target_user_id AND ( -- 规则1:两个字段均为空直接命中 (requested_by_id IS NULL AND position IS NULL) OR -- 规则2:两个字段非空时,判断是同order_id下position最小的pending记录 ( requested_by_id IS NOT NULL AND position IS NOT NULL AND NOT EXISTS ( SELECT 1 FROM requests r2 WHERE r2.order_id = r.order_id AND r2.status = 'pending' AND r2.requested_by_id IS NOT NULL AND r2.position IS NOT NULL AND r2.position < r.position ) ) );
注意:该写法在大表下性能弱于窗口函数版本,相关子查询需要逐行判断,仅适合数据量小或者数据库版本不支持窗口函数的场景。
结果验证
对照需求给出的示例场景,执行结果完全匹配:
- 指定user_id=1时,返回id=1的记录
- 指定user_id=2时,返回id=3、id=5的记录
- 指定user_id=3时,返回空结果
- 当order_id=1下position=1的记录状态改为approved时,user_id=3会命中order_id=1下position最小的pending记录,返回id=4的记录,符合补充说明的预期。
可落地优化建议
- 索引优化:建立联合索引
idx_status_userid_orderid_position(status, user_id, order_id, position),可以同时覆盖两类查询的过滤、排序逻辑,避免全表扫描和filesort,查询性能可提升一个数量级。 - 数据约束优化:给表添加CHECK约束,保证
requested_by_id和position的空值状态一致(要么同时为NULL,要么同时非NULL),避免脏数据影响查询逻辑,也可减少查询中的冗余判断。 - 状态值规范:统一状态字段的存储格式,不要混用数字(比如之前错误SQL里写的
status=0)和字符串('pending')表示同一状态,避免隐式类型转换导致索引失效。 - 大表场景优化:如果表数据量超过100万,优先使用窗口函数+
UNION ALL的写法,相比相关子查询可以减少60%以上的扫描行数,避免嵌套循环判断的性能损耗。
内容的提问来源于stack exchange,提问作者przbadu
相关产品推荐
相关产品推荐

