三个表连接除了INNER JOIN之外的连接

时间:2009-08-11 18:28:47

标签: sql sql-server database sql-server-2005 tsql

我正在学习SQL,本周我正在努力学习JOIN。

我已达到可以进行三次表连接的级别,类似于我见过的很多示例。我还在试图弄清楚事情是如何运作的微小细节。我在三个表连接中看到的所有示例仅使用INNER JOINS。 LEFT和RIGHT JOIN怎么样?你有没有在三个表连接中使用它们?这是什么意思?

SELECT ~some columns~ FROM ~table name~
LEFT JOIN ~table 2~ ON ~criteria~
INNER JOIN ~table 3~ ON ~criteria~

SELECT ~some columns~ FROM ~table name~
INNER JOIN ~table 2~ ON ~criteria~
LEFT JOIN ~table 3~ ON ~criteria~

SELECT ~some columns~ FROM ~table name~
LEFT JOIN ~table 2~ ON ~criteria~
LEFT JOIN ~table 3~ ON ~criteria~

???

尽可能地尝试探索空间

8 个答案:

答案 0 :(得分:22)

是的,我确实使用了所有这三个JOIN,尽管我倾向于仅使用LEFT (OUTER) JOIN而不是混合LEFT和RIGHT JOIN。我还使用FULL OUTER JOINCROSS JOIN s。

总之,INNER JOIN仅将结果集限制为JOIN条件满足的记录。请考虑以下表格

编辑:我已重命名了表格名称,并在其前面添加了@,以便表格变量可以用于阅读此答案并希望进行试验的任何人。

如果您还想在浏览器中进行试验, I've set this all up on SQL Fiddle 也可以;

@Table1

id | name
---------
1  | One
2  | Two
3  | Three
4  | Four

@Table2

id | name
---------
1  | Partridge
2  | Turtle Doves
3  | French Hens
5  | Gold Rings

SQL代码

DECLARE @Table1 TABLE (id INT PRIMARY KEY CLUSTERED, [name] VARCHAR(25))

INSERT INTO @Table1 VALUES(1, 'One');
INSERT INTO @Table1 VALUES(2, 'Two');
INSERT INTO @Table1 VALUES(3, 'Three');
INSERT INTO @Table1 VALUES(4, 'Four');

DECLARE @Table2 TABLE (id INT PRIMARY KEY CLUSTERED, [name] VARCHAR(25))

INSERT INTO @Table2 VALUES(1, 'Partridge');
INSERT INTO @Table2 VALUES(2, 'Turtle Doves');
INSERT INTO @Table2 VALUES(3, 'French Hens');
INSERT INTO @Table2 VALUES(5, 'Gold Rings');

INNER JOIN SQL语句,已加入id字段

SELECT 
    t1.id,
    t1.name,
    t2.name
FROM
    @Table1 t1
INNER JOIN
    @Table2 t2
    ON 
        t1.id = t2.id

结果

id | name | name
----------------
1  | One  | Partridge
2  | Two  | Turtle Doves
3  | Three| French Hens

LEFT JOIN将返回一个结果集,其中包含联接左侧表中的所有记录(如果您要将该语句写为一个单行,最先出现的表)和来自的字段连接右侧与连接表达式匹配的表,包含在SELECT子句中。 缺少详细信息将填充NULL

SELECT 
    t1.id,
    t1.name,
    t2.name
FROM
    @Table1 t1
LEFT JOIN
    @Table2 t2
    ON 
        t1.id = t2.id

结果

id | name | name
----------------
1  | One  | Partridge
2  | Two  | Turtle Doves
3  | Three| French Hens
4  | Four | NULL

RIGHT JOINLEFT JOIN的逻辑相同,但会从连接的右侧返回所有记录,并从左侧返回与连接表达式匹配的字段,并包含在SELECT条款。

SELECT 
    t1.id,
    t1.name,
    t2.name
FROM
    @Table1 t1
RIGHT JOIN
    @Table2 t2
    ON 
        t1.id = t2.id

结果

id | name | name
----------------
1  | One  | Partridge
2  | Two  | Turtle Doves
3  | Three| French Hens
NULL| NULL| Gold Rings

当然,还有FULL OUTER JOIN,其中包含来自两个连接表的记录,并使用NULL填充任何缺少的详细信息。

SELECT 
    t1.id,
    t1.name,
    t2.name
FROM
    @Table1 t1
FULL OUTER JOIN
    @Table2 t2
    ON 
        t1.id = t2.id

结果

id | name | name
----------------
1  | One  | Partridge
2  | Two  | Turtle Doves
3  | Three| French Hens
4  | Four | NULL
NULL| NULL| Gold Rings

还有一个CROSS JOIN(也称为CARTESIAN PRODUCT),它只是一个表中SELECT语句中交叉应用字段的产物,其中{{1}中的字段来自另一个表的语句。请注意,SELECT

中没有连接表达式
CROSS JOIN

结果

SELECT 
    t1.id,
    t1.name,
    t2.name
FROM
    @Table1 t1
CROSS JOIN
    @Table2 t2

修改

想象一下,现在有一个Table3

id | name  | name
------------------
1  | One   | Partridge
2  | Two   | Partridge
3  | Three | Partridge
4  | Four  | Partridge
1  | One   | Turtle Doves
2  | Two   | Turtle Doves
3  | Three | Turtle Doves
4  | Four  | Turtle Doves
1  | One   | French Hens
2  | Two   | French Hens
3  | Three | French Hens
4  | Four  | French Hens
1  | One   | Gold Rings
2  | Two   | Gold Rings
3  | Three | Gold Rings
4  | Four  | Gold Rings

SQL代码

@Table3

id | name
---------
2  | Prime 1
3  | Prime 2
5  | Prime 3

现在所有三个表都加入了DECLARE @Table3 TABLE (id INT PRIMARY KEY CLUSTERED, [name] VARCHAR(25)) INSERT INTO @Table3 VALUES(2, 'Prime 1'); INSERT INTO @Table3 VALUES(3, 'Prime 2'); INSERT INTO @Table3 VALUES(5, 'Prime 3');

INNER JOINS

结果

SELECT 
    t1.id,
    t1.name,
    t2.name,
    t3.name
FROM
    @Table1 t1
INNER JOIN
    @Table2 t2
    ON 
        t1.id = t2.id
INNER JOIN
    @Table3 t3
    ON 
        t1.id = t3.id

通过认为id为2和3的记录是所有3个表共有的记录并且也是我们加入每个表的字段,可能有助于理解这个结果。

现在这三个人都有id | name | name | name ------------------------------- 2 | Two | Turtle Doves | Prime 1 3 | Three| French Hens | Prime 2

LEFT JOINS

结果

SELECT 
    t1.id,
    t1.name,
    t2.name,
    t3.name
FROM
    @Table1 t1
LEFT JOIN
    @Table2 t2
    ON 
        t1.id = t2.id
LEFT JOIN
    @Table3 t3
    ON 
        t1.id = t3.id

Joel's answer是解释此结果集的一个很好的解释(Table1是基础/原始表)。

现在有id | name | name | name ------------------------------- 1 | One | Partridge | NULL 2 | Two | Turtle Doves | Prime 1 3 | Three| French Hens | Prime 2 4 | Four | NULL | NULL INNER JOIN

LEFT JOIN

结果

SELECT 
    t1.id,
    t1.name,
    t2.name,
    t3.name
FROM
    @Table1 t1
INNER JOIN
    @Table2 t2
    ON 
        t1.id = t2.id
LEFT JOIN
    @Table3 t3
    ON 
        t1.id = t3.id

虽然我们不知道查询优化器执行操作的顺序,但我们将从上到下查看此查询以了解结果集。 Table1和Table2之间的id | name | name | name ------------------------------- 1 | One | Partridge | NULL 2 | Two | Turtle Doves | Prime 1 3 | Three| French Hens | Prime 2 个id将结果集限制为仅由连接条件满足的那些记录,即我们在第一个示例中看到的三行。然后,此临时结果集将INNER JOIN添加到表1和表之间的ID上的表3;表3中有记录,ID为2和3,但不是id 1,因此t3.name字段的详细信息为2和3但不是1。

答案 1 :(得分:6)

联接只是组合表格的方式。加入三个表与加入2 ...或200没什么不同。您可以根据需要混合和匹配INNER,[LEFT / RIGHT / FULL] OUTER,甚至CROSS联接。唯一的区别是保留了哪些结果:INNER连接仅保留两侧与表达式匹配的行。 OUTER连接根据LEFT / RIGHT / FULL规范选择“origin”表,始终保留origin表中的所有行,并为来自另一端的与表达式不匹配的行提供NULL值。 CROSS联接返回双方的所有可能组合。

诀窍在于,因为你正在使用声明性代码而不是更熟悉的迭代,所以诱惑就是试着把它想象成一切都发生了。当你这样做时,你试图绕过整个查询,这会让人感到困惑。

相反,您希望将其视为连接按顺序发生,从列出的第一个表到最后一个表。这实际上不是它的工作原理,因为查询优化器可以重新排序以使它们运行得更快。但它使开发人员更容易构建查询。

因此,对于三个表,从基表开始,然后从下一个表和下一个表中加入所需的值,依此类推,就像向函数添加代码行以生成所需的输出一样。

至于使用不同的连接类型,我使用了我在这里列出的所有不同类型:INNER,LEFT OUTER,RIGHT OUTER,FULL OUTER和CROSS。但大多数你只需要偶尔使用。 INNER JOIN和LEFT JOIN可能会覆盖你想要做的95%或更多。

现在让我们谈谈性能。通常情况下,您列出表格的顺序取决于您:从TableA开始,您需要先列出TableB才能访问加入TableC所需的列。但有时TableBTableC只依赖于TableA,您可以按任意顺序列出它们。当发生这种情况时,查询优化器通常会为您选择最佳订单,但有时它不知道如何。即使它确实如此,它也有助于建立一个良好的列表表系统,这样你就可以随时查看查询并知道它是“正确的”。

考虑到这一点,您应该在查询构建时根据当前内存中的working set来考虑查询。当您从TableA开始时,数据库将查看选择列表中TableA的所有列或查询中的其他任何列(如WHERE或ORDER BY子句或潜在索引),相关条件中的因子从WHERE子句,并将该表的最小部分加载到它可以逃脱的内存中。它依次为每个表执行此操作,始终尽可能少地加载。这就是关键:你希望尽可能长时间地保持这个工作集尽可能小。

因此,回到我们的三表连接,我们希望按顺序列出表,以使工作集更小更长。这意味着将较小的表列在较大的表上方。另一个好的经验法则是INNER连接倾向于收缩结果集,而OUTER连接则倾向于增长结果集,因此您希望首先列出INNER连接。但是,对于查询来说,这不是要求,也不总是如此;有时反过来也会发生。

最后,我想再次指出,这不是它真正起作用的方式。查询优化器和执行计划是一个非常复杂的主题,数据库可以采取许多技巧来不时打破这个模型。它只是您作为开发人员可以用来帮助理解服务器正在做什么的一个模型,并帮助您编写更好的查询。

答案 2 :(得分:2)

从三个表中进行选择与仅从两个表中选择(或多达一百个,尽管这将是一个相当难看的查询)。

对于您编写的每个连接,INNER表示您只需要将这两个表成功连接在一起的行。如果在查询的早期加入了其他表,则这些结果现在完全无关紧要,除非您自己的连接条件调用它们。

例如:

SELECT person.*
FROM person
LEFT JOIN vehicle ON (person.person_id = vehicle.owner_id)
LEFT JOIN house ON (person.person_id = house.owner_id)

在这里,我想要一份所有人的清单,以及他们拥有的所有车辆和房屋(如果有的话)。

可替换地:

SELECT person.*
FROM person
INNER JOIN vehicle ON (person.person_id = vehicle.owner_id)
LEFT JOIN house ON (person.person_id = house.owner_id)

我希望所有拥有车辆的人(他们必须拥有车辆才能在我的查询中获得结果),以及(如果有的话)他们拥有的所有房屋。

每个联接在这里完全分开。

当然,通过改变你在ON子句中的内容,你可以以任何你想要的方式使连接相互关联。

答案 3 :(得分:0)

这实际上取决于你在做什么。我写了很多3+表查询,其中有一个外连接。它只取决于您查询的数据以及您要遵循的内容。

当您有多个连接时选择连接类型时,相同的一般逻辑适用于单个连接查询。

答案 4 :(得分:0)

为了这个例子,假设我们有一个表“employees”,其中包含ID,NAME和MANAGER_ID字段。

这是一个简单的查询:

SELECT E.ID, E.NAME, M.NAME AS MANAGER
FROM EMPLOYEES E
JOIN EMPLOYEE M ON E.MANAGER_ID = M.ID

这将返回所有员工及其经理姓名。但老板会怎么样?谁没有经理?数据库null实际上会阻止该行返回,因为它找不到要加入的匹配记录。因此,您将使用OUTER连接(向左或向右,具体取决于您编写查询的方式)。

使用2 + n连接编写查询时,同样的逻辑也适用。如果你可能会在你的join子句中拥有没有匹配的行,并希望这些行返回(尽管是空值),那么你就是金色的。

答案 5 :(得分:0)

答案 6 :(得分:0)

在某些sql引擎上存在使用左连接加入的问题。 如果您加入A-> B-> C并且B中的行不存在,则B中的连接列为NULL。 我使用的一些要求如果来自A-> B的连接是左连接,则来自B-> C的连接必须是左连接。

这没关系

select a.*, b.*, c.*
 from a
 left join b on b.id = a.id
 left join c on c.id = b.id

这不是

 select a.*, b.*, c.*
 from a
 left join b on b.id = a.id
 inner join c on c.id = b.id

答案 7 :(得分:0)

为了完整性和标准的evangelics,我将使用ansi-92嵌套连接语法:

select t1.*
    ,t2.*
    ,t3.*
from table1 t1
    left outer join (
        table2 t2 left outer join table3 t3 on (t2.b = t3.b)
    ) on (t1.a = t2.a)

您选择的SQL引擎可以为它们进行优化。