829

看完后,这不是Explicit vs Implicit SQL Joins的重复。答案可能相关(甚至相同),但问题不同。


有什么区别,每个应该做什么?

如果我正确理解了理论,查询优化器应该能够互换使用两者。

4

21 回答 21

1042

它们不是同一件事。

考虑以下查询:

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

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

第一个将为 order number 返回一个订单及其行(如果有)12345。第二个将返回所有订单,但只有订单12345将有与之关联的任何行。

使用INNER JOIN,子句实际上是等效的。然而,仅仅因为它们在功能上是相同的,因为它们产生相同的结果,并不意味着这两种从句具有相同的语义。

于 2008-12-09T20:21:10.613 回答
548
  • 对于内部连接无关紧要

  • 外连接的事项

    一种。WHERE子句:加入。加入后将过滤记录。

    湾。ON子句 -加入之前。加入前将过滤记录(来自右表)。这可能最终在结果中为 null(因为 OUTER join)。

示例:考虑下表:

  1. 文件:

    ID 姓名
    1 文件1
    2 文档2
    3 文件3
    4 文件4
    5 文件5
  2. 下载:

    ID 文档 ID 用户名
    1 1 桑迪普
    2 1 西米
    3 2 桑迪普
    4 2 雷亚
    5 3 西米

a) 内部WHERE子句:

   SELECT documents.name, downloads.id
     FROM documents
     LEFT OUTER JOIN downloads
       ON documents.id = downloads.document_id
     WHERE username = 'sandeep'

对于上述查询,中间连接表将如下所示。

id(来自文档) 姓名 id(来自下载) 文档 ID 用户名
1 文件1 1 1 桑迪普
1 文件1 2 1 西米
2 文档2 3 2 桑迪普
2 文档2 4 2 雷亚
3 文件3 5 3 西米
4 文件4 空值 空值 空值
5 文件5 空值 空值 空值

应用该WHERE子句并选择列出的属性后,结果将是:

姓名 ID
文件1 1
文档2 3

b) 内部JOIN条款

   SELECT documents.name, downloads.id
   FROM documents
     LEFT OUTER JOIN downloads
       ON documents.id = downloads.document_id
         AND username = 'sandeep'

对于上述查询,中间连接表将如下所示。

id(来自文档) 姓名 id(来自下载) 文档 ID 用户名
1 文件1 1 1 桑迪普
2 文档2 3 2 桑迪普
3 文件3 空值 空值 空值
4 文件4 空值 空值 空值
5 文件5 空值 空值 空值

请注意documents,不匹配两个条件的行是如何填充NULL值的。

选择列出的属性后,结果将是:

姓名 ID
文件1 1
文档2 3
文件3 空值
文件4 空值
文件5 空值
于 2014-01-07T20:54:43.167 回答
154

INNER JOINs 上它们是可以互换的,优化器会随意重新排列它们。

OUTER JOINs 上,它们不一定可以互换,这取决于它们依赖于连接的哪一侧。

我根据可读性将它们放在任何一个地方。

于 2008-12-09T20:21:28.227 回答
64

我这样做的方式是:

  • 如果您正在ON执行INNER JOIN. 因此,不要在 ON 子句中添加任何 WHERE 条件,将它们放在WHERE子句中。

  • 如果您正在执行 a LEFT JOIN,请将任何 WHERE 条件添加到联接右侧ON表的子句中。这是必须的,因为添加引用连接右侧的 WHERE 子句会将连接转换为 INNER JOIN。

    例外情况是当您查找不在特定表中的记录时。您可以通过这种方式将 RIGHT JOIN 表中的唯一标识符(永远不是 NULL)的引用添加到 WHERE 子句中WHERE t2.idfield IS NULL:因此,您应该引用连接右侧的表的唯一时间是查找那些不在表中的记录。

于 2008-12-09T20:57:01.303 回答
33

在内部连接中,它们的含义相同。但是,根据您是否将连接条件放在 WHERE 与 ON 子句中,您将在外连接中得到不同的结果。看看这个相关的问题这个答案(由我)。

我认为养成始终将连接条件放在 ON 子句中的习惯是最有意义的(除非它是外连接并且您确实希望在 where 子句中使用它),因为它使阅读您的查询的任何人都更清楚连接表的条件是什么,它还有助于防止 WHERE 子句长达数十行。

于 2008-12-09T20:20:19.087 回答
28

表关系

考虑到我们有以下postpost_comment表格:

post 和 post_comment 表

有以下post记录:

| 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将创建一个笛卡尔积,其中包含两个连接表之间所有可能的行组合。

虽然 CROSS JOIN 在某些情况下很有用,但大多数时候,您希望根据特定条件连接表。而且,这就是 INNER JOIN 发挥作用的地方。

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

SQL INNER JOIN - 在“始终为真”条件下

如果您提供“始终为真”条件,则 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

我们将得到所有的组合postpost_comment记录:

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

因此,如果 ON 子句条件为“始终为真”,则 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“总是假”条件

另一方面,如果 ON 子句条件为“始终为假”,则所有连接的记录都将被过滤掉,结果集将为空。

因此,如果我们执行以下 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以及它们的post_comment记录。post没有关联的行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 | 电脑评论 |
|--------|------------|--------|-----------|--------- --|
| 1 | 1 | 1 | 爪哇 | 好 |
| 1 | 1 | 2 | 爪哇 | 优秀 |
| 1 | 2 | 3 | 爪哇 | 真棒| 
| 2 | 1 | 1 | 休眠 | 好 | 
| 2 | 1 | 2 | 休眠 | 优秀 |
| 2 | 2 | 3 | 休眠 | 真棒|
| 3 | 1 | 1 | JPA | 好 | 
| 3 | 1 | 2 | JPA | 优秀 | 
| 3 | 2 | 3 | JPA | 真棒|

结论

INNER JOIN 语句可以重写为 CROSS JOIN,其 WHERE 子句与您在 INNER JOIN 查询的 ON 子句中使用的条件匹配。

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

于 2014-05-25T14:20:50.203 回答
14

当涉及到左连接时,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' 将有任何关联的行。

于 2016-03-13T06:31:29.217 回答
13

让我们考虑这些表:

一种

id | SomeData

id | id_A | SomeOtherData

id_A作为表的外键A

编写此查询:

SELECT *
FROM A
LEFT JOIN B
ON A.id = B.id_A;

将提供此结果:

/ : part of the result
                                       B
                      +---------------------------------+
            A         |                                 |
+---------------------+-------+                         |
|/////////////////////|///////|                         |
|/////////////////////|///////|                         |
|/////////////////////|///////|                         |
|/////////////////////|///////|                         |
|/////////////////////+-------+-------------------------+
|/////////////////////////////|
+-----------------------------+

在 A 中但不在 B 中的内容意味着 B 存在空值。


现在,让我们考虑 中的特定部分B.id_A,并从之前的结果中突出显示它:

/ : part of the result
* : part of the result with the specific B.id_A
                                       B
                      +---------------------------------+
            A         |                                 |
+---------------------+-------+                         |
|/////////////////////|///////|                         |
|/////////////////////|///////|                         |
|/////////////////////+---+///|                         |
|/////////////////////|***|///|                         |
|/////////////////////+---+---+-------------------------+
|/////////////////////////////|
+-----------------------------+

编写此查询:

SELECT *
FROM A
LEFT JOIN B
ON A.id = B.id_A
AND B.id_A = SpecificPart;

将提供此结果:

/ : part of the result
* : part of the result with the specific B.id_A
                                       B
                      +---------------------------------+
            A         |                                 |
+---------------------+-------+                         |
|/////////////////////|       |                         |
|/////////////////////|       |                         |
|/////////////////////+---+   |                         |
|/////////////////////|***|   |                         |
|/////////////////////+---+---+-------------------------+
|/////////////////////////////|
+-----------------------------+

因为这会在内部联接中删除不在其中的值B.id_A = SpecificPart


现在,让我们将查询更改为:

SELECT *
FROM A
LEFT JOIN B
ON A.id = B.id_A
WHERE B.id_A = SpecificPart;

结果现在是:

/ : part of the result
* : part of the result with the specific B.id_A
                                       B
                      +---------------------------------+
            A         |                                 |
+---------------------+-------+                         |
|                     |       |                         |
|                     |       |                         |
|                     +---+   |                         |
|                     |***|   |                         |
|                     +---+---+-------------------------+
|                             |
+-----------------------------+

因为整个结果都被过滤掉了B.id_A = SpecificPartB.id_A IS NULL所以在A 中没有在 B 中的部分

于 2019-04-12T10:03:00.340 回答
11

就优化器而言,是否使用 ON 或 WHERE 定义连接子句应该没有区别。

但是,恕我直言,我认为在执行连接时使用 ON 子句要清楚得多。这样,您就有了查询的特定部分,该部分指示如何处理连接,而不是与其余的 WHERE 子句混合。

于 2008-12-09T20:21:36.713 回答
6

您是要加入数据还是过滤数据?

为了可读性,将这些用例分别隔离到 ON 和 WHERE 是最有意义的。

  • 在 ON 中加入数据
  • 在 WHERE 中过滤数据

读取 WHERE 子句中存在 JOIN 条件和过滤条件的查询会变得非常困难。

性能方面您应该看不到差异,尽管不同类型的 SQL 有时会以不同方式处理查询计划,因此值得尝试¯\_(ツ)_/¯(请注意缓存会影响查询速度)

同样正如其他人所指出的,如果您使用外连接,如果您将过滤条件放在 ON 子句中,则会得到不同的结果,因为它只影响其中一个表。

我在这里写了一篇更深入的文章: https ://dataschool.com/learn/difference-between-where-and-on-in-sql

于 2019-04-29T23:17:48.683 回答
2

在 SQL 中,'WHERE' 和 'ON' 子句是一种条件语句,但它们之间的主要区别是,'Where' 子句在 Select/Update 语句中用于指定条件,而 'ON' 子句用于联接,在联接表之前验证或检查目标表和源表中的记录是否匹配

例如: - 'WHERE'

SELECT * FROM employee WHERE employee_id=101

例如: - '开'

有两个表employee 和employee_details,匹配的列是employee_id。

SELECT * FROM employee 
INNER JOIN employee_details 
ON employee.employee_id = employee_details.employee_id

希望我已经回答了你的问题。恢复任何澄清。

于 2014-02-05T10:57:46.477 回答
1

我认为这是连接序列效应。在左上连接的情况下,SQL 先做左连接,再做 where 过滤。在较弱的情况下,先找到 Orders.ID=12345,然后再加入。

于 2014-01-07T03:49:04.017 回答
1

对于内部连接,WHERE可以ON互换使用。事实上,可以ON在相关子查询中使用。例如:

update mytable
set myscore=100
where exists (
select 1 from table1
inner join table2
on (table2.key = mytable.key)
inner join table3
on (table3.key = table2.key and table3.key = table1.key)
...
)

这(恕我直言)对人类来说是完全混乱的,而且很容易忘记链接table1到任何东西(因为“驱动程序”表没有“开”子句),但它是合法的。

于 2014-05-16T22:40:07.833 回答
1

为了获得更好的性能,表应该有一个特殊的索引列用于 JOINS 。

因此,如果您条件的列不是那些索引列之一,那么我怀疑最好将其保留在 WHERE 中。

所以你 JOIN 使用索引列,然后在 JOIN 之后在无索引列上运行条件。

于 2014-12-12T21:10:14.643 回答
1

通常,一旦两个表已经连接,过滤就会在 WHERE 子句中处理。有可能,但您可能希望在加入其中一个或两个表之前对其进行过滤。即,where 子句适用于整个结果集,而 on 子句仅适用于相关连接。

于 2018-02-16T05:29:45.450 回答
1

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

  • FROM(包括连接)
  • WHERE
  • GROUP BY
  • 聚合
  • HAVING
  • WINDOW
  • SELECT
  • DISTINCT
  • UNION, INTERSECT,EXCEPT
  • ORDER BY
  • OFFSET
  • FETCH

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

我在这篇博文中更深入地解释了以下示例。运行此查询时:

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

简而言之

总是把你的谓词放在最合乎逻辑的地方。

于 2019-04-09T13:37:42.380 回答
1

从字面上看,它们是等价的。

在大多数开源数据库中(最著名的例子,在MySqlpostgresql中),查询计划是出现在关系数据库管理系统中的访问路径选择中的经典算法的变体(Selinger 等人,1979 年)。在这种方法中,条件有两种类型

  • 引用单个表的条件(用于过滤)
  • 引用两个表的条​​件(视为连接条件,无论它们出现在哪里

特别是在 MySql 中,您可以通过跟踪优化器看到自己join .. on的条件在解析过程中被等效where条件替换。在 postgresql 中也会发生类似的事情(虽然无法通过日志查看,但您必须阅读源描述)。

无论如何,重点是,两种语法变体之间的差异在解析/查询重写阶段丢失了,甚至没有到达查询计划和执行阶段。因此,毫无疑问它们在性能方面是否相同,它们在到达执行阶段之前就变得相同了

您可以使用explain, 来验证它们是否产生相同的计划。例如,在 postgres 中,计划将包含一个join子句,即使您没有在join..on任何地方使用该语法

Oracle 和 SQL Server 不是开源的,但据我所知,它们是基于等价规则的(类似于关系代数中的规则),并且它们在两种情况下也产生相同的执行计划。

显然,这两种语法风格对于外连接来说是不等价的,对于那些你必须使用join ... on语法的人

于 2020-11-14T17:42:25.397 回答
0

关于你的问题,

只要您的服务器可以获取它,内部连接上的“on”或“where”都是相同的:

select * from a inner join b on a.c = b.c

select * from a inner join b where a.c = b.c

并非所有口译员都知道的“where”选项,因此可能应该避免使用。当然,“on”子句更加清晰。

于 2020-01-21T13:23:37.043 回答
0

为了添加到 Joel Coehoorn 的响应中,我将添加一些特定于 sqlite 的优化信息(其他 SQL 风格可能表现不同)。在原始示例中,左连接具有不同的结果,具体取决于您使用JOIN ON ... WHEREJOIN ON ... AND。这是一个稍微修改的示例来说明:

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 优化器错误地得出执行此连接的最快方法是使用索引 on 的结论,而Orders.Username您从经验测试中知道索引 onOrders.ID会更快地传递您的查询。

在这种情况下,前一种JOIN ON ... WHERE语法本质上允许您强制对参数进行主连接操作,只有在主连接完成后才执行ID辅助过滤。Username相比之下,JOIN ON ... AND语法允许优化器选择是否在Orders.IDor上使用索引Orders.Username,理论上它可能会选择最终速度较慢的那个。

于 2021-06-09T01:44:34.183 回答
-1

一种。WHERE 子句:加入后,会过滤记录。

湾。ON 子句 - 在加入之前,将过滤记录(来自右表)。

于 2021-06-06T15:17:45.923 回答
-5

这是我的解决方案。

SELECT song_ID,songs.fullname, singers.fullname
FROM music JOIN songs ON songs.ID = music.song_ID  
JOIN singers ON singers.ID = music.singer_ID
GROUP BY songs.fullname

必须拥有GROUP BY才能让它工作。

希望这有帮助。

于 2010-04-26T07:53:50.890 回答