You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多字段组合匹配时重复SQL IN子查询的优化方法

SQL多字段关联匹配查询优化

问题背景

核心业务需求为筛选会话记录时,保证metrics_user_training_cohort表中存在同一行记录,同时匹配会话对应的user_id和training_id。当前使用两个独立IN子查询的写法存在逻辑冗余,且无法完全保证同记录匹配的逻辑严谨性。
原有完整查询语句如下:

SELECT date(metrics_session.created_at) as day, COUNT(metrics_session.user_id) as total_logins,  
            sum(TIMESTAMPDIFF(MINUTE,metrics_session.created_at,metrics_session.completed_at)) as total_time_spent  
            FROM metrics_session 
            inner join metrics_training on metrics_training.id = metrics_session.training_id 
            inner join metrics_course on metrics_course.id = metrics_training.course_id
            inner join metrics_user_training_cohort on  metrics_training.id = metrics_user_training_cohort.training_id
            inner join auth_user on auth_user.id = metrics_user_training_cohort.user_id

            WHERE metrics_session.created_at >= '2021-01-15'
            AND metrics_session.created_at <= '2022-10-15' 
            AND metrics_session.completed_at IS NOT NULL
            AND metrics_session.user_id In (SELECT user_id from metrics_user_training_cohort  where user_id = 44 and training_id = 4)
            AND metrics_session.training_id In (SELECT training_id from metrics_user_training_cohort  where user_id = 44 and training_id = 4)
                           
            #AND EXISTS(SELECT user_id,training_id from metrics_user_training_cohort  where user_id = 44 and training_id = 4)
            GROUP BY date(metrics_session.created_at) ORDER BY date(metrics_session.created_at)

可选优化方案

方案1:多列IN匹配(最小改动)

SQL原生支持多字段同时做IN匹配,不需要拆成两个独立子查询。该写法可以保证两个字段来自子查询的同一行记录,逻辑严谨,且只需要执行一次子查询:

-- 替换原有两个独立IN条件即可
AND (metrics_session.user_id, metrics_session.training_id) IN (
    SELECT user_id, training_id
    FROM metrics_user_training_cohort
    WHERE user_id = 44 AND training_id = 4
)

方案2:关联EXISTS(索引场景下性能更优)

之前注释的EXISTS写法不生效,核心原因是没有将子查询与外层会话表做字段关联,正确的关联EXISTS写法如下:

AND EXISTS (
    SELECT 1
    FROM metrics_user_training_cohort mutc
    WHERE mutc.user_id = metrics_session.user_id
      AND mutc.training_id = metrics_session.training_id
      AND mutc.user_id = 44
      AND mutc.training_id = 4
)

该写法在metrics_user_training_cohort表的(user_id, training_id)联合索引存在时,查询效率远高于IN写法,数据库不需要全量扫描子查询结果,匹配到符合条件的记录就会终止当前行的校验。

方案3:复用现有JOIN逻辑(性能最优,无额外子查询)

原查询已经对metrics_user_training_cohort做了INNER JOIN,完全不需要额外写子查询过滤,只需要补全缺失的JOIN关联条件,直接在JOIN层面做过滤即可,避免重复扫描表:

注意:原JOIN metrics_user_training_cohort时只关联了training_id,缺少user_id的关联条件,会产生不必要的笛卡尔积,导致统计结果重复计算。

优化后的完整SQL:

SELECT 
    DATE(ms.created_at) AS day,
    COUNT(ms.user_id) AS total_logins,
    SUM(TIMESTAMPDIFF(MINUTE, ms.created_at, ms.completed_at)) AS total_time_spent
FROM metrics_session ms
INNER JOIN metrics_training mt ON mt.id = ms.training_id
INNER JOIN metrics_course mc ON mc.id = mt.course_id
INNER JOIN metrics_user_training_cohort mutc
    ON mt.id = mutc.training_id
    AND ms.user_id = mutc.user_id -- 补全user_id关联,避免笛卡尔积
INNER JOIN auth_user au ON au.id = mutc.user_id
WHERE
    ms.created_at >= '2021-01-15'
    AND ms.created_at <= '2022-10-15'
    AND ms.completed_at IS NOT NULL
    AND mutc.user_id = 44
    AND mutc.training_id = 4
GROUP BY DATE(ms.created_at)
ORDER BY DATE(ms.created_at)

方案选择建议

  • 如果只做最小改动快速修复,选方案1
  • 如果后续需要动态匹配多组user_id和training_id,且表有对应联合索引,选方案2
  • 如果追求最优性能,选方案3,同时建议给关联字段加上联合索引进一步提速。

内容的提问来源于stack exchange,提问作者Rafael Santos

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.03 02:57:33