为什么数据库查出 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 里。
发表评论: