前两周写一条查询语句时不小心踩了一个比较初级的坑,这里用博客的文章表和内容表来举例,文章表中有个 content_id 字段,对应的是内容表的主键 id。需求是需要查询出所有未被删除的文章列表并且查出对应文章的内容,文章内容允许为空。


第一印象就很容想到以文章表为主表然后去外连接内容表进行关联查询,这里先给出正确的 SQL:
SELECT
    t1.id,
    t1.title,
    t2.html_content AS content 
FROM
    t_article AS t1
    LEFT JOIN t_content AS t2 ON t2.id = t1.content_id 
    AND t2.is_deleted = 0 
WHERE
    t1.is_deleted = 0

正确的查询结果
正确的查询结果

可以看出是查出来了四条数据,并且第四条数据的文章内容是空的,符合要求。


再看当时写的错误的 SQL:

SELECT
    t1.id,
    t1.title,
    t2.html_content AS content 
FROM
    t_article AS t1
    LEFT JOIN t_content AS t2 ON t2.id = t1.content_id 
WHERE
    t1.is_deleted = 0
AND t2.is_deleted = 0

错误的 SQL
错误的 SQL

发现这里查出来的数据少了一条,区别就在于 t2.is_deleted 这个过滤条件从 LEFT JOIN ... ON 的后面给放到了 WHERE 后面。


再看另外一种和上面错误 SQL 等效的写法:

SELECT
    t1.id,
    t1.title,
    t2.html_content AS content 
FROM
    t_article AS t1
    INNER JOIN t_content AS t2 ON t2.id = t1.content_id AND t2.is_deleted = 0
WHERE
    t1.is_deleted = 0

错误 SQL 的等效 SQL
错误 SQL 的等效 SQL

这里查出来的数据也是只有三条,内容为 null 的那一条数据没有被查出来。


其实这是因为当关联条件放到 LEFT JOIN ... ON 后面的时候是会先将右表的数据过滤然后再与左表进行关联,放到 WHERE 后面的话实际上前面的 LEFT JOIN 就相当于 INNER JOIN 了,会先关联产生临时表数据再进行筛选。由于id 为 34 的那篇文章的内容对应的数据表的 is_deleted 字段值为 1,不满足 WHERE 条件,所以这条数据就不会被查询出来。


所以说,联表查询数据的时候,要分清楚最终结果集以谁的数据为准,再考虑用什么连接方式以及过滤条件该写在哪个位置,这些都是需要注意的。