如何简化含重复子查询的SQL语句?PHP执行SQL优化问询
优化重复子查询的SQL语句
原SQL功能正常但效率极低,核心问题是重复执行了三次完全相同的子查询来匹配不同标签值:
SELECT * FROM ttasks t WHERE bComplete = 0 AND ( 1 IN (SELECT iTagKey FROM ttagties WHERE t.taskKey = iLinkKey AND iType = 0) OR 3 IN (SELECT iTagKey FROM ttagties WHERE t.taskKey = iLinkKey AND iType = 0) ) AND 10 NOT IN (SELECT iTagKey FROM ttagties WHERE t.taskKey = iLinkKey AND iType = 0) ORDER BY dCreated;
你的两种尝试不可行的原因
- 第一种写法
((1 OR 3) AND NOT 10) IN (...)逻辑错误:SQL中1 OR 3会被计算为布尔值1,NOT 10计算为0,最终表达式结果为0,实际是判断0是否在标签集合中,完全不符合需求。 - 第二种
ASSIGN SET AS (...)不是标准SQL语法,无法直接在MySQL等常用数据库中运行。
高效优化方案
方案1:使用EXISTS+聚合(兼容性好)
通过一次子查询分组聚合,同时判断目标标签存在性和排除标签不存在性:
SELECT t.* FROM ttasks t WHERE t.bComplete = 0 AND EXISTS ( SELECT 1 FROM ttagties tt WHERE tt.iLinkKey = t.taskKey AND tt.iType = 0 GROUP BY tt.iLinkKey HAVING SUM(CASE WHEN tt.iTagKey IN (1,3) THEN 1 ELSE 0 END) > 0 AND SUM(CASE WHEN tt.iTagKey = 10 THEN 1 ELSE 0 END) = 0 ) ORDER BY t.dCreated;
方案2:使用CTE预计算标签特征(MySQL 8.0+/PostgreSQL等支持)
先一次性扫描ttagties表,计算每个任务的标签特征,再关联查询,大幅减少表扫描次数:
WITH task_tags AS ( SELECT iLinkKey, MAX(CASE WHEN iTagKey IN (1,3) THEN 1 ELSE 0 END) AS has_target_tag, MAX(CASE WHEN iTagKey = 10 THEN 1 ELSE 0 END) AS has_exclude_tag FROM ttagties WHERE iType = 0 GROUP BY iLinkKey ) SELECT t.* FROM ttasks t JOIN task_tags tt ON t.taskKey = tt.iLinkKey WHERE t.bComplete = 0 AND tt.has_target_tag = 1 AND tt.has_exclude_tag = 0 ORDER BY t.dCreated;
方案3:简化EXISTS组合(逻辑直观)
用两个EXISTS子查询替代三次IN查询,EXISTS会在找到匹配后立即停止遍历,比IN更高效:
SELECT t.* FROM ttasks t WHERE t.bComplete = 0 AND EXISTS (SELECT 1 FROM ttagties WHERE iLinkKey = t.taskKey AND iType = 0 AND iTagKey IN (1,3)) AND NOT EXISTS (SELECT 1 FROM ttagties WHERE iLinkKey = t.taskKey AND iType = 0 AND iTagKey = 10) ORDER BY t.dCreated;
关于JOIN的问题说明
之前直接用JOIN无法排除包含10的任务,是因为JOIN会保留所有匹配目标标签的行,即使任务同时存在排除标签。需要通过分组+HAVING过滤,或者结合NOT EXISTS来排除存在10的任务,上述方案1和3都解决了这个问题。
内容的提问来源于stack exchange,提问作者bookworm
相关产品推荐
相关产品推荐

