在阅读它之后,这不是显式与隐式SQL连接的副本。 答案可能相关(甚至相同),但问题是不同的。


它们之间有什么不同?每一种都应该有什么不同?

如果我正确地理解了这个理论,那么查询优化器应该能够互换地使用这两种方法。


当前回答

简短的回答

这取决于连接类型是INNER还是OUTER。

对于INNER JOIN,答案是肯定的,因为INNER JOIN语句可以重写为带有WHERE子句的CROSS JOIN,该子句与您在INNER JOIN查询的ON子句中使用的条件相同。

但是,这只适用于INNER JOIN,不适用于OUTER JOIN。

长回答

考虑到我们有如下的post和post_comment表:

该职位有以下记录:

| id | title     |
|----|-----------|
| 1  | Java      |
| 2  | Hibernate |
| 3  | JPA       |

并且post_comment有以下三行:

| id | review    | post_id |
|----|-----------|---------|
| 1  | Good      | 1       |
| 2  | Excellent | 1       |
| 3  | Awesome   | 2       |

SQL内部连接

SQL JOIN子句允许您关联属于不同表的行。例如,CROSS JOIN将创建一个笛卡尔积(Cartesian Product),其中包含两个连接表之间所有可能的行组合。

虽然CROSS JOIN在某些场景中很有用,但大多数情况下,您希望基于特定的条件来连接表。这就是INNER JOIN发挥作用的地方。

SQL INNER JOIN允许我们根据通过on子句指定的条件筛选连接两个表的笛卡尔积。

SQL内部连接-对“始终为真”的条件

如果您提供了一个“always true”条件,INNER JOIN将不会过滤连接的记录,结果集将包含两个连接表的笛卡尔积。

例如,如果我们执行下面的SQL INNER JOIN查询:

SELECT
   p.id AS "p.id",
   pc.id AS "pc.id"
FROM post p
INNER JOIN post_comment pc ON 1 = 1

我们将得到post和post_comment记录的所有组合:

| p.id    | pc.id      |
|---------|------------|
| 1       | 1          |
| 1       | 2          |
| 1       | 3          |
| 2       | 1          |
| 2       | 2          |
| 2       | 3          |
| 3       | 1          |
| 3       | 2          |
| 3       | 3          |

因此,如果ON子句条件为"always true", INNER JOIN就相当于一个CROSS JOIN查询:

SELECT
   p.id AS "p.id",
   pc.id AS "pc.id"
FROM post p
CROSS JOIN post_comment
WHERE 1 = 1
ORDER BY p.id, pc.id

SQL INNER JOIN - ON "always false"条件

另一方面,如果On子句条件为“always false”,则所有连接的记录将被过滤掉,结果集将为空。

因此,如果我们执行下面的SQL INNER JOIN查询:

SELECT
   p.id AS "p.id",
   pc.id AS "pc.id"
FROM post p
INNER JOIN post_comment pc ON 1 = 0
ORDER BY p.id, pc.id

我们不会得到任何结果:

| p.id    | pc.id      |
|---------|------------|

这是因为上面的查询相当于下面的CROSS JOIN查询:

SELECT
   p.id AS "p.id",
   pc.id AS "pc.id"
FROM post p
CROSS JOIN post_comment
WHERE 1 = 0
ORDER BY p.id, pc.id

SQL INNER JOIN - ON子句使用外键和主键列

最常见的ON子句条件是子表中的外键列与父表中的主键列相匹配的条件,如下面的查询所示:

SELECT
   p.id AS "p.id",
   pc.post_id AS "pc.post_id",
   pc.id AS "pc.id",
   p.title AS "p.title",
   pc.review  AS "pc.review"
FROM post p
INNER JOIN post_comment pc ON pc.post_id = p.id
ORDER BY p.id, pc.id

当执行上面的SQL INNER JOIN查询时,我们得到以下结果集:

| p.id    | pc.post_id | pc.id      | p.title    | pc.review |
|---------|------------|------------|------------|-----------|
| 1       | 1          | 1          | Java       | Good      |
| 1       | 1          | 2          | Java       | Excellent |
| 2       | 2          | 3          | Hibernate  | Awesome   |

因此,只有匹配ON子句条件的记录才包含在查询结果集中。在我们的例子中,结果集包含了所有的帖子及其post_comment记录。没有关联post_comment的发布行将被排除,因为它们不能满足ON子句条件。

同样,上面的SQL INNER JOIN查询等价于下面的CROSS JOIN查询:

SELECT
   p.id AS "p.id",
   pc.post_id AS "pc.post_id",
   pc.id AS "pc.id",
   p.title AS "p.title",
   pc.review  AS "pc.review"
FROM post p, post_comment pc
WHERE pc.post_id = p.id

未取消的行是满足WHERE子句的行,结果集中只包括这些记录。这是可视化INNER JOIN子句如何工作的最好方法。

| p.id | pc.post_id | pc.id | p.title   | pc.review |
|------|------------|-------|-----------|-----------|
| 1    | 1          | 1     | Java      | Good      |
| 1    | 1          | 2     | Java      | Excellent |
| 1    | 2          | 3     | Java      | Awesome   |
| 2    | 1          | 1     | Hibernate | Good      |
| 2    | 1          | 2     | Hibernate | Excellent |
| 2    | 2          | 3     | Hibernate | Awesome   |
| 3    | 1          | 1     | JPA       | Good      |
| 3    | 1          | 2     | JPA       | Excellent |
| 3    | 2          | 3     | JPA       | Awesome   |

结论

INNER JOIN语句可以重写为带有WHERE子句的CROSS JOIN,该子句与在INNER JOIN查询的ON子句中使用的条件相同。

并不是说这只适用于INNER JOIN,而不适用于OUTER JOIN。

其他回答

我认为这种区别可以通过SQL中操作的逻辑顺序来最好地解释,这是简化的:

FROM(包括连接) 在哪里 集团 聚合 有 窗口 选择 截然不同的 并,相交,除 命令 抵消 获取

联接不是select语句的子句,而是FROM语句中的操作符。因此,当逻辑处理到达WHERE子句时,属于相应JOIN运算符的所有ON子句在逻辑上“已经发生”。这意味着在LEFT JOIN的情况下,例如,在应用WHERE子句时,外部连接的语义已经发生。

我已经在这篇博文中更深入地解释了下面的例子。运行此查询时:

SELECT a.actor_id, a.first_name, a.last_name, count(fa.film_id)
FROM actor a
LEFT JOIN film_actor fa ON a.actor_id = fa.actor_id
WHERE film_id < 10
GROUP BY a.actor_id, a.first_name, a.last_name
ORDER BY count(fa.film_id) ASC;

LEFT JOIN实际上没有任何有用的效果,因为即使演员没有在电影中演出,演员也会被过滤,因为它的FILM_ID将为NULL, WHERE子句将过滤这样的一行。结果是这样的:

ACTOR_ID  FIRST_NAME  LAST_NAME  COUNT
--------------------------------------
194       MERYL       ALLEN      1
198       MARY        KEITEL     1
30        SANDRA      PECK       1
85        MINNIE      ZELLWEGER  1
123       JULIANNE    DENCH      1

也就是说,就好像我们内部连接了两个表。如果我们移动ON子句中的筛选谓词,它现在成为外部连接的标准:

SELECT a.actor_id, a.first_name, a.last_name, count(fa.film_id)
FROM actor a
LEFT JOIN film_actor fa ON a.actor_id = fa.actor_id
  AND film_id < 10
GROUP BY a.actor_id, a.first_name, a.last_name
ORDER BY count(fa.film_id) ASC;

这意味着结果将包含没有任何影片的演员,或者没有任何影片且FILM_ID < 10的演员

ACTOR_ID  FIRST_NAME  LAST_NAME     COUNT
-----------------------------------------
3         ED          CHASE         0
4         JENNIFER    DAVIS         0
5         JOHNNY      LOLLOBRIGIDA  0
6         BETTE       NICHOLSON     0
...
1         PENELOPE    GUINESS       1
200       THORA       TEMPLE        1
2         NICK        WAHLBERG      1
198       MARY        KEITEL        1

简而言之

总是把谓词放在逻辑上最有意义的地方。

对于内连接,它们的含义相同。但是,在外部连接中将得到不同的结果,这取决于您将连接条件放在WHERE子句中还是on子句中。看看这个相关的问题和答案(由我)。

我认为习惯总是把连接条件放在ON子句中是最有意义的(除非它是一个外部连接,而且你确实想把它放在where子句中),因为它使阅读你的查询的任何人都更清楚表是在什么条件下被连接的,而且它还有助于防止where子句长几十行。

为了补充Joel Coehoorn的回答,我将添加一些特定于sqlite的优化信息(其他SQL风格的行为可能不同)。在原来的例子中,LEFT JOIN有不同的结果,这取决于你是否使用JOIN on…在哪里或加入…和。下面是一个稍微修改过的例子:

SELECT *
FROM Orders
LEFT JOIN OrderLines ON Orders.ID = OrderLines.OrderID
    WHERE Orders.Username = OrderLines.Username

SELECT *
FROM Orders
LEFT JOIN OrderLines ON Orders.ID = OrderLines.OrderID 
    AND Orders.Username = OrderLines.Username

现在,最初的答案指出,如果使用普通的内部连接而不是左连接,两个查询的结果将是相同的,但执行计划将有所不同。我最近意识到,两者之间的语义差异是,前者强制查询优化器使用与ON子句关联的索引,而后者允许优化器选择ON子句中的任何索引……AND子句,这取决于它认为最有效的方式。

有时,优化器会猜错,您会想强制执行某个执行计划。在本例中,假设SQLite优化器错误地得出结论,认为执行此连接的最快方法是使用Orders上的索引。用户名,当您从经验测试中知道,对订单的索引。ID可以更快地传递查询。

在这种情况下,前者JOIN ON…WHERE语法实际上允许您强制对ID参数执行主连接操作,只有在主连接完成之后才对Username执行辅助过滤。相反,JOIN ON…AND语法允许优化器选择是否在Orders上使用索引。ID或订单。用户名,理论上有可能它会选择一个速度较慢的。

a. WHERE子句:加入后,记录将被过滤。

b. ON子句-在加入之前,记录(来自右边表格)将被过滤。

当涉及到左连接时,where子句和on子句之间有很大的区别。

这里有一个例子:

mysql> desc t1; 
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id    | int(11)     | NO   |     | NULL    |       |
| fid   | int(11)     | NO   |     | NULL    |       |
| v     | varchar(20) | NO   |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+

fid是表t2的id。

mysql> desc t2;
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id    | int(11)     | NO   |     | NULL    |       |
| v     | varchar(10) | NO   |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.00 sec)

查询“on子句”:

mysql> SELECT * FROM `t1` left join t2 on fid = t2.id AND t1.v = 'K' 
    -> ;
+----+-----+---+------+------+
| id | fid | v | id   | v    |
+----+-----+---+------+------+
|  1 |   1 | H | NULL | NULL |
|  2 |   1 | B | NULL | NULL |
|  3 |   2 | H | NULL | NULL |
|  4 |   7 | K | NULL | NULL |
|  5 |   5 | L | NULL | NULL |
+----+-----+---+------+------+
5 rows in set (0.00 sec)

where子句查询:

mysql> SELECT * FROM `t1` left join t2 on fid = t2.id where t1.v = 'K';
+----+-----+---+------+------+
| id | fid | v | id   | v    |
+----+-----+---+------+------+
|  4 |   7 | K | NULL | NULL |
+----+-----+---+------+------+
1 row in set (0.00 sec)

很明显, 第一个查询从t1返回一条记录,从t2返回与t1相关的行(如果有的话)。v = 'K'。

第二个查询返回来自t1的行,但只针对t1。v = 'K'将有任何与它相关的行。