只要你点击进入这篇文章,我相当确信你应该已经理解了 SQL。你也必须理解,在 SELECT 查询中,我们应该将条件放在 WHERE 子句中。然而,让我问你一个问题,看看你能否立即回答。
如果我们在 LEFT JOIN … ON …子句中放置过滤条件会发生什么?
SELECT *
FROM Employee e LEFT JOIN Department d
ON e.dept_id = d.id
AND e.name = 'Chris'
如果您对上述查询的行为不确定,或者认为它与以下查询等效,请阅读我的文章,我会告诉您它们为什么不同。
SELECT *
FROM Employee e LEFT JOIN Department d
ON e.dept_id = d.id
WHERE e.name = 'Chris'
1. 验证结果
为了演示目的,我创建了两个具有简单虚拟数据的表,如下所示。
员工表
部门表
现在,让我们运行介绍中的第一个查询。为了方便参考,我再次将查询内容放在这里。
SELECT *
FROM Employee e LEFT JOIN Department d
ON e.dept_id = d.id
AND e.name = 'Chris'
这就是答案。你答对了吗? 🙂
仅为了比较目的,我还会将第二个查询及其结果放在这里。当然,没有什么值得惊讶的,过滤条件最终会过滤结果。
SELECT *
FROM Employee e LEFT JOIN Department d
ON e.dept_id = d.id
WHERE e.name = 'Chris';
为什么会是这种情况?让我们在下一节深入探讨。
2. 连接的逻辑
图片由 Gerd Altmann 来自 Pixabay
在我们能够理解为什么行为是这样的之前,我们需要回顾 LEFT JOIN 和 RIGHT JOIN 的确切逻辑,并确保我们完全理解它。
让我向您展示带有条件的 RIGHT JOIN 是什么样的。与上面的查询相同,但使用 RIGHT JOIN 会导致以下结果。
SELECT *
FROM Employee e RIGHT JOIN Department d
ON e.dept_id = d.id
AND e.name = 'Chris'
现在,让我们看看工作流程,以便我们理解为什么会发生这种情况。
带条件的 LEFT JOIN
SQL 中的 JOIN 操作用于将两个表(左表和右表)结合起来。当我们使用 LEFT JOIN 时,将保证左表中的所有行都被保留。只有右表将根据连接条件进行过滤。
以下示例适用于我们只有 e.dept_id = d.id 作为连接条件的场景。
SELECT *
FROM Employee e LEFT JOIN Department d
ON e.dept_id = d.id
https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/19250de1566154fd3f3dcfb290eef0a3.pnghttps://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/0f53c7707b45b1fb2bf8a96f0907f810.png
在上述查询中,我们可以这样想。左表 Employee 已经写在纸上,然后我们逐行查看它。对于左表上的每一行,我们将逐行遍历右表 Department,以查看哪一行符合条件。如果它符合条件,我们将在左边的行后面写下右边的行。
例如,当我们查看 “Alice” 时,dept_id=1。右边的 “Sales” 部门的第一个行具有相同的 id。因此,此行将跟随左边的 “Alice” 行写下。
现在,让我们看看带有额外条件 e.name = 'Chris' 的查询。
SELECT *
FROM Employee e LEFT JOIN Department d
ON e.dept_id = d.id
AND e.name = 'Chris'
我仍然使用相同的箭头。然而,这次虚线表示只满足第一个条件 e.dept_id = d.id,但不满足第二个条件 e.name = 'Chris'。例如,下面的图示显示,尽管两个 ID 都是 1,但 e.name 是 Alice,而不是 Chris。
当然,右侧的其他两行也无法满足这两个条件。因此:
左侧的行将被保留,而右侧的行将不会选择与之连接。
在此示例中,唯一匹配的配对如下。
因此,我们只会看到具有 “Chris” 的行有一个有效的从右表连接的行。因为需要右表中的 id 和 name 列,所以所有其他行都将显示 null 值来填充这两个列的空缺。
带条件的 RIGHT JOIN
现在,让我们看看 RIGHT JOIN 的场景。它将正好相反。右表将保证被保留,并且它将尝试根据条件从左表中找到匹配的行。
因此,具有上述条件的 RIGHT JOIN 将如下所示。
由于左表中唯一满足两个条件的行是 “Chris” 这一行,结果将如下所示。
3. 何时使用它?
图片由Rudy and Peter Skitterians在Pixabay提供
基本上,我们很少会在 ON 子句中放置条件,因为它实际上并不过滤结果。然而,这也意味着当我们想要保留左表(或右表)的所有记录,无论条件如何时,我们可以使用它。
实际应用案例:活动销售报告
假设我们有两个表:活动和销售。
“活动”表包含了所有的活动,每个活动只对应一个产品。当然,每个活动也有开始和结束日期。
CREATE TABLE Campaigns (
campaign_id INT PRIMARY KEY,
product_id INT,
start_date DATE,
end_date DATE
);
“销售”表包含了所有的交易。每笔交易包含一个或多个产品以及销售日期。
CREATE TABLE Sales (
sale_id INT,
product_id INT,
quantity INT,
sale_date DATE,
PRIMARY KEY (sale_id, product_id)
);
现在,我们需要解决的问题是要列出所有的活动,以及在这些活动期间售出的产品数量。同时,我们不想错过任何活动,因为即使在这些活动期间没有售出任何产品,我们仍然想了解它。
SQL 查询如下。我们可以将销售日期过滤条件放入 LEFT JOIN … ON 子句中。这确保了连接的有效性,以及即使没有售出任何产品,我们也不会丢失任何活动。
SELECT C.campaign_id,
C.product_id,
C.start_date,
C.end_date,
SUM(S.quantity) AS TotalCampaignSales
FROM Campaigns C LEFT JOIN Sales S
ON C.product_id = S.product_id
AND S.sale_date >= C.start_date
AND S.sale_date <= C.end_date
GROUP BY C.campaign_id,
C.product_id,
C.start_date,
C.end_date
ORDER BY SUM(S.quantity) DESC;
结果显示,活动编号#1 是最成功的,而活动编号#4 则完全没有效果。
如果我们将条件放在 WHERE 子句中会怎样呢?
结果仍然有效,但我们无法看到活动编号#4。
SELECT C.campaign_id,
C.product_id,
C.start_date,
C.end_date,
SUM(S.quantity) AS TotalCampaignSales
FROM Campaigns C LEFT JOIN Sales S
ON C.product_id = S.product_id
WHERE S.sale_date >= C.start_date
AND S.sale_date <= C.end_date
GROUP BY C.campaign_id,
C.product_id,
C.start_date,
C.end_date
ORDER BY SUM(S.quantity) DESC;
因此,我们根本看不到活动编号#4,因为在活动中没有售出任何产品。假设我们在“活动”表中还有更多的列,比如活动预算,如果我们能返回所有活动,那就更有用了。例如,我们在没有售出任何产品的活动中浪费了多少预算?
摘要
图片由SplitShire在Pixabay提供
在这篇文章中,我专注于 SQL 中的一个非常小的语法陷阱,即 LEFT/RIGHT JOIN … ON 子句中的条件。通常,我们会将过滤条件放在 WHERE 子句中。然而,ON 子句中的条件不会减少行数,但它会影响另一边的表是否会被连接。如果条件不满足,它们将保留为 null 值。
我已尽最大努力解释了这种行为。此外,还提供了实际的应用案例。希望这会有所帮助。
除非另有说明,所有图片均为作者所有

被折叠的 条评论
为什么被折叠?



