无尘阁日记

无尘阁日记

为什么 `LEFT JOIN` 里的条件不能筛选数据?—— 深入解析及正确用法
2025-02-28

prompt:

return self::find()

            ->alias('d')

            ->leftJoin(CSMSubjectRelations::tableName() . ' r', 'd.SubjectId=r.SubjectId AND r.IsDeleted=0 AND r.CSMouldId=:moduleId')

            ->leftJoin(CSMSubjectRelationFormulas::tableName() . ' f', 'r.Id=f.CSMSubjectRelationId AND f.IsDeleted=0')

            ->leftJoin(TJDomainSubjectFormulaConfigsSub::tableName() . ' sf', 'f.FormulaId=sf.Id AND sf.ProjectId=:projectId AND sf.ValidMethod=:validMethod AND sf.IsDeleted=0')

            ->where([

                'd.SubjectId' => $subjectIds,

                'd.ProjectId' => $params['ProjectId'],

                'd.ValidMethod' => $params['EevaluationMethodType'],

                'd.CSOType' => $csoType,

                'd.IsDeleted' => 0

            ])

            ->select('sf.*') // 仅选择所需字段,避免冲突

            ->distinct()

            ->params([

                ':moduleId' => $moduleId,

                ':projectId' => $params['ProjectId'],

                ':validMethod' => $params['EevaluationMethodType']

            ])

            ->asArray()

            ->all();


这个语句能仅仅查询出moduleId为指定id的数据么?


答:

你的查询语句中,moduleId 仅在 LEFT JOIN 关联时使用,但并未在 WHERE 条件中直接筛选,因此 可能会查询出其他 moduleId 的数据,因为 LEFT JOIN 允许即使 r.CSMouldId != :moduleId 的数据也进入最终结果集。

解决方法:

如果你的目标是 确保最终查询结果仅包含 moduleId 相关的数据,需要在 WHERE 条件中添加额外的过滤,如下:

return self::find()
    ->alias('d')
    ->leftJoin(CSMSubjectRelations::tableName() . ' r', 'd.SubjectId=r.SubjectId AND r.IsDeleted=0')
    ->leftJoin(CSMSubjectRelationFormulas::tableName() . ' f', 'r.Id=f.CSMSubjectRelationId AND f.IsDeleted=0')
    ->leftJoin(TJDomainSubjectFormulaConfigsSub::tableName() . ' sf', 'f.FormulaId=sf.Id AND sf.ProjectId=:projectId AND sf.ValidMethod=:validMethod AND sf.IsDeleted=0')
    ->where([
        'd.SubjectId' => $subjectIds,
        'd.ProjectId' => $params['ProjectId'],
        'd.ValidMethod' => $params['EevaluationMethodType'],
        'd.CSOType' => $csoType,
        'd.IsDeleted' => 0,
        'r.CSMouldId' => $moduleId // 确保 `moduleId` 仅匹配指定值
    ])
    ->select('sf.*')
    ->distinct()
    ->params([
        ':projectId' => $params['ProjectId'],
        ':validMethod' => $params['EevaluationMethodType']
    ])
    ->asArray()
    ->all();

关键修正:

  1. 移除 r.CSMouldId=:moduleId 在 LEFT JOIN 里的条件

    • 之前 LEFT JOIN 条件仅用于关联,而不会剔除不匹配的数据,可能会导致 r.CSMouldId != :moduleId 的记录依然出现在 d 过滤的结果中。

    • 现在 WHERE 里 直接添加 r.CSMouldId = $moduleId 过滤,确保查询结果只包含 moduleId 相关数据。

  2. params() 里移除 :moduleId

    • 之前 params() 仅用于 JOIN,但 WHERE 现在已经是直接的数组形式,不需要额外绑定 :moduleId 变量。

结果:

这样修改后,最终返回的 sf.* 结果中,所有数据都确保 moduleId 为指定值,不会出现多余的 moduleId 相关数据。


prompt:

为什么在leftjoin里加的条件不能筛选出其他的?


答:

LEFT JOIN 里面加 r.CSMouldId=:moduleId 作为 ON 条件,并不能确保最终查询结果只包含 moduleId 相关的数据。要理解为什么,我们需要分析 LEFT JOIN 的行为。


1. LEFT JOIN 的本质

LEFT JOIN 主要作用是:

  • 基表(FROM 后面的表,即 d)的所有记录都会被保留

  • 右表(JOIN 关联的表,例如 rfsf)如果有匹配的记录,就关联;如果没有匹配的记录,就填充 NULL

当你写:

LEFT JOIN CSMSubjectRelations r 
ON d.SubjectId = r.SubjectId 
AND r.IsDeleted = 0 
AND r.CSMouldId = :moduleId

这个 ON 条件 只是控制 LEFT JOIN 关联的数据,并不会影响 d 表的数据是否被保留。


2. 为什么 LEFT JOIN 里的 ON 不能筛选 moduleId

错误的理解

"在 LEFT JOIN 里加 r.CSMouldId = :moduleId,就只会查询 moduleId 相关的数据。"

正确的理解

LEFT JOINON 只是用来 定义如何匹配数据,不会 筛掉不匹配的数据。如果 r.CSMouldId != :moduleId,则:

  • r 的记录不会被匹配到,r 相关字段会填充 NULL

  • 但是 d 的记录仍然会保留。


3. 示例分析

假设 d 表里有数据:

d.SubjectIdd.ProjectIdd.ValidMethodd.CSOTyped.IsDeleted
110012A0
210012B0

CSMSubjectRelations 表数据如下:

r.Idr.SubjectIdr.CSMouldIdr.IsDeleted
1019990
1118880
1227770

假设 :moduleId = 888,你的查询:

LEFT JOIN CSMSubjectRelations r 
ON d.SubjectId = r.SubjectId 
AND r.IsDeleted = 0 
AND r.CSMouldId = 888

会导致:

d.SubjectIdr.Idr.CSMouldId
111888
2NULLNULL
  • SubjectId = 1moduleId = 888 的匹配,正常关联。

  • SubjectId = 2 没有 moduleId = 888,所以 r 变成 NULL,但 d 仍然保留!

问题:SubjectId = 2 本来和 moduleId = 888 无关,但仍然在结果里出现了!
LEFT JOIN 不能筛掉这些 d 的数据,只是让 r 为空。


4. 正确的方式

真正筛选出 moduleId = 888 的数据,必须在 WHERE 里加:

->where(['r.CSMouldId' => $moduleId])

这样,SQL 变成:

WHERE r.CSMouldId = 888

这会彻底 排除 r 为空的记录,确保只有 moduleId = 888 相关的数据。


5. 最终正确的查询

return self::find()
    ->alias('d')
    ->leftJoin(CSMSubjectRelations::tableName() . ' r', 'd.SubjectId=r.SubjectId AND r.IsDeleted=0')
    ->leftJoin(CSMSubjectRelationFormulas::tableName() . ' f', 'r.Id=f.CSMSubjectRelationId AND f.IsDeleted=0')
    ->leftJoin(TJDomainSubjectFormulaConfigsSub::tableName() . ' sf', 'f.FormulaId=sf.Id AND sf.ProjectId=:projectId AND sf.ValidMethod=:validMethod AND sf.IsDeleted=0')
    ->where([
        'd.SubjectId' => $subjectIds,
        'd.ProjectId' => $params['ProjectId'],
        'd.ValidMethod' => $params['EevaluationMethodType'],
        'd.CSOType' => $csoType,
        'd.IsDeleted' => 0,
        'r.CSMouldId' => $moduleId  // **关键修正**
    ])
    ->select('sf.*')
    ->distinct()
    ->params([
        ':projectId' => $params['ProjectId'],
        ':validMethod' => $params['EevaluationMethodType']
    ])
    ->asArray()
    ->all();

这样,WHERE r.CSMouldId = $moduleId 会保证: ✅ 只有 moduleId = 888d 记录才会被保留
不会有 r 为空的情况
最终数据集都是和 moduleId 相关的


6. 总结

  • LEFT JOIN 不能过滤 moduleId,只能影响关联逻辑。

  • LEFT JOIN 失败时,仍然会保留 d 的数据,只是 r 为空。

  • 要完全筛掉非 moduleId 相关的数据,必须在 WHERE 里加 r.CSMouldId = :moduleId

这样,你的查询才会真正返回 moduleId 相关的数据。