无尘阁日记

无尘阁日记

为什么数据库查出 2410 条,而程序只返回 40 条?你可能忽略了这个致命细节!
2025-11-14

问:

$subjects = TJDomainSubjectsSub::find()

            ->alias('s')

            ->leftJoin(TJDomainSheetsSub::tableName() . ' as sh', 's.SheetId = sh.Id and' . ' sh.ProjectId = ' .

                $params['ProjectId'] . ' and sh.ValidMethod = ' . $params['EevaluationMethodType'] . ' and sh.IsDeleted = 0')

            ->where([

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

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

                's.IsDeleted' => 0,

            ])

            ->andWhere(['not', ['sh.SheetType' => 3]]) // Normal=1,OpenFromSubject=2,TemplateSheet=3

            ->select('sh.*,s.Id as SubjectId,s.Name as SubjectName,s.FromSubjectId as SourceSubjectId')

            ->asArray()

            ->all();


为何这个语句我在数据库执行的数量是2410条。但是打印的结果只有40条?

答:

下面从 SQL 逻辑、ORM(Yii2 ActiveRecord)行为、关联条件位置、WHERE/LEFT JOIN 的本质差异,一步一步帮你分析为什么:

数据库执行 SQL 得到 2410 条,但 PHP 打印只有 40 条。

这是一个非常典型的:
LEFT JOIN + WHERE 条件写法位置不当 → LEFT JOIN 实际被等价成 INNER JOIN 导致数据“被过滤掉”的问题。

结论一句话:

因为你把条件写在 WHERE 里,而不是 JOIN 条件里,导致 LEFT JOIN 被强制变成了 INNER JOIN(过滤掉大量没有匹配 sh 的记录),最终只剩 40 条。

详细分析

你的 SQL(抽象后)等价如下:

SELECT sh.*, s.Id AS SubjectId ...
FROM subjects s
LEFT JOIN sheets sh 
    ON s.SheetId = sh.Id
        AND sh.ProjectId = X
        AND sh.ValidMethod = Y
        AND sh.IsDeleted = 0
WHERE 
      s.ProjectId = X
  AND s.ValidMethod = Y
  AND s.IsDeleted = 0
  AND NOT (sh.SheetType = 3)

问题就在这里👇👇👇

关键问题:你在 WHERE 中添加了 NOT (sh.SheetType = 3)

LEFT JOIN 的本意是:

即使 sh 不匹配,也应该保留 s 的记录(sh 字段为 NULL)。

但是 WHERE 有 sh.SheetType 条件:

AND NOT (sh.SheetType = 3)

如果 sh 没匹配到,则 sh.SheetType = NULL。
WHERE 会变成:

NOT (NULL = 3)

这个是 TRUE,所以看起来似乎没问题?

——问题并不在于 NULL = 3,而在于 WHERE 里出现了 sh.X 会让 LEFT JOIN 退化成 INNER JOIN

为什么 LEFT JOIN 会变成 INNER JOIN?

用一个简化版来看:

SELECT * FROM A
LEFT JOIN B ON ...
WHERE B.col != 3

如果 B 没有匹配到,B.col = NULL
WHERE 条件会过滤掉 NULL 吗?

实际上 SQL 逻辑如下:

  • LEFT JOIN 产生 A+B(包括 B=NULL 的行)

  • WHERE 对结果再筛选

  • WHERE 条件涉及 B.col,则 NULL 值不满足任何比较(=、!=、<> 都不 TRUE)

因此所有 B=NULL 的行全部被过滤掉

→ LEFT JOIN ≈ INNER JOIN

于是你的 2410 条中,大部分没有 sh 匹配的行被直接 WHERE 掉了,所以只有 40 条留下来

如何验证?

你只要把那条 WHERE 去掉试试:

->andWhere(['not', ['sh.SheetType' => 3]])

注释掉后数量大概率恢复为 2410。

正确写法(修复方式)

要保留 LEFT JOIN 的行为,必须把条件写到 JOIN 里:

->leftJoin(
    TJDomainSheetsSub::tableName() . ' as sh',
    's.SheetId = sh.Id
         AND sh.ProjectId = :pid
         AND sh.ValidMethod = :method
         AND sh.IsDeleted = 0
         AND sh.SheetType <> 3'
)

然后删除 WHERE 里关于 sh 的条件:

->andWhere(['s.ProjectId' => $params['ProjectId']])
->andWhere(['s.ValidMethod' => $params['EevaluationMethodType']])
->andWhere(['s.IsDeleted' => 0])

为什么 JOIN 条件位置正确?

因为 LEFT JOIN 的 ON 条件不会过滤掉 s 的记录,只会决定 sh 是否匹配。

而 WHERE 是过滤整张结果集,所以:

  • 出现在 JOIN 的条件 → 不会减少左表行数

  • 出现在 WHERE 的条件 → 会过滤掉 NULL,从而减少行数

额外检查:是否 sh.SheetType 本来就很少为非 3?

你可以手动跑 SQL 测试:

SELECT COUNT(*) 
FROM TJDomainSheetsSub 
WHERE ProjectId = X AND ValidMethod = Y AND IsDeleted = 0 AND SheetType <> 3;

如果结果就是 40 左右,那进一步证明你把 LEFT JOIN 变成 INNER JOIN 了。

✔ 总结(一步到位)

你的查询结果从 2410 变成 40,是因为 WHERE 中使用了包含 sh 的条件,破坏了 LEFT JOIN,使其变成 INNER JOIN,从而过滤掉大量记录。

✔ 修复:把所有 sh.xxx 条件移到 JOIN 的 ON 里。