了解涉及3个或更多表时JOIN的工作方式。[SQL]


69

我想知道是否有人可以帮助提高我对SQL JOIN的理解。[如果对该问题很重要,那么我正在特别考虑MS SQL Server。]

取3张表A,B [与A.AId有关的B有关的A]和C [与B.BId有关的C有关的B]

如果我撰写查询,例如

SELECT *
FROM A JOIN B 
ON A.AId = B.AId

都很好-我对它的工作方式很满意。

表C(或添加其他D,E,....)时会发生什么

在这种情况下

SELECT *
FROM A JOIN B 
  ON A.AId = B.AId
JOIN C ON C.BId = B.BId

什么是C加入?-是B表(及其中的值)吗?还是将C表联接到A + B联接的结果而得出的其他一些临时结果集?

[这意味着并非B表中的所有值都不一定基于A,B的连接条件而位于临时结果集中A + B中]

我为什么要问的一个特定(且相当人为)的示例是因为我试图理解以下行为:

Tables 
Account (AccountId, AccountBalanceDate, OpeningBalanceId, ClosingBalanceId)
Balance (BalanceId)
BalanceToken (BalanceId, TokenAmount)

Where:
Account->Opening, and Closing Balances are NULLABLE 
(may have opening balance, closing balance, or none)

Balance->BalanceToken is 1:m - a balance could consist of many tokens

从概念上讲,某个日期的期末余额将是明天的期初余额

如果我要查找一个帐户的所有期初和期末余额的列表

我可能会做类似的事情

SELECT AccountId
, AccountBalanceDate
, Sum (openingBalanceAmounts.TokenAmount) AS OpeningBalance
, Sum (closingBalanceAmounts.TokenAmount) AS ClosingBalance
FROM Account A 
   LEFT JOIN BALANCE OpeningBal 
      ON A.OpeningBalanceId = OpeningBal.BalanceId
   LEFT JOIN BALANCE ClosingBal 
      ON A.ClosingBalanceId = ClosingBal.BalanceId
   LEFT JOIN BalanceToken openingBalanceAmounts 
      ON openingBalanceAmounts.BalanceId = OpeningBal.BalanceId
   LEFT JOIN BalanceToken closingBalanceAmounts 
      ON closingBalanceAmounts.BalanceId = ClosingBal.BalanceId
   GROUP BY AccountId, AccountBalanceDate  

事情一直如我所愿,直到最后一个JOIN引入期末余额令牌-我最终在结果中得到重复项。

[我可以通过DISTINCT进行修复-但我试图理解为什么发生了什么]

我被告知问题是因为Balance和BalanceToken之间的关系是1:M-并且当我引入最后一个JOIN时我得到重复,因为第三个JOIN已经多次将BalanceIds引入了(我假设)临时结果集。

我知道示例表不符合良好的数据库设计

为这篇文章道歉,谢谢您的启发:)

根据马克的问题进行编辑

从概念上讲,对于一个帐户(每个AccountingDate),BalanceToken中不应存在重复项-我认为问题是由于1个Account / AccountingDates的期末余额是第二天的帐户期初余额-因此,当我自动加入Balance时,BalanceToken多次获得期初和期末余额我认为Balances(BalanceId's)被多次引入“结果组合”中。如果有助于阐明第二个示例,则可以将其视为每日对帐-因此是左联接-对于给定的帐户/会计日期组合,可能尚未计算期初(和/或)期末余额。


14
+1详细问题和自己的推理。
PatrikAkerstrand

Answers:


43

从概念上讲,这是将三个表连接在一起时发生的情况。

  1. 优化器提出了一个计划,其中包括一个连接顺序。可以是A,B,C或C,B,A或任何组合
  2. 查询执行引擎将任何谓词(WHERE子句)应用于不涉及任何其他表的第一个表。它选择JOIN条件中提到的列或SELECT列表或ORDER BY列表。将此结果称为A
  3. 它将结果集连接到第二个表。对于每一行,它连接到第二个表,并应用可能适用于第二个表的任何谓词。这将导致另一个临时结果集。
  4. 然后将其加入决赛桌并应用 ORDER BY

从概念上讲,这就是发生的情况。实际上,在此过程中有许多可能的优化。关系模型的优点在于,可靠的数学基础使计划的各种转换成为可能,而不会改变正确性。

例如,实际上并不需要一路生成完整的结果集。在ORDER BY可以替代地经由访问利用在第一位置的索引数据来完成。也可以执行许多类型的联接。


3
谢谢WW-我认为这可以清除我想知道的内容-随后的联接(从表3开始)针对/沿途构建的“最后”临时结果集(而不是原始表)进行。因此,从第一个示例说起,... B JOIN C ON B.BId = C.BId-将A + B中间结果集中的再次行与B表中的行连接在一起。[从概念上讲]
Delaney

3
是的,一次只能将两个结果集结合在一起,然后就可以建立起来。这就是加入顺序对计划非常重要的原因。
WW。

嗨,WW,根据您的回答,第1点表示什么?优化器可以按任何顺序连接表吗?这不会影响最终数据吗?
Vaibhav

1
是的,它可以按任何顺序加入他们。它会改变性能,但不会改变正确性。优化器所做的最基本的事情是为联接选择最佳顺序。
WW。

1
@NikosV在联接之前先应用WHERE子句的某些部分,然后在联接后续表时应用其他部分。从概念上讲,可以通过执行所有联接,然后应用WHERE子句来获得结果,但这将导致产生不必要的较大中间结果。因此,查询引擎可以通过应用WHERE子句来尽快缩小结果。
WW。

5

我们知道,B(内部)联接将过滤来自A的数据(其中的数据A也将被过滤)。因此,如果我们(内部)从加盟BC,这样的设定C通过关系来过滤A。还要注意,连接中的所有重复项都将包括在内

然而; 发生的顺序取决于优化程序;它可以决定先执行B/C连接,然后引入A,或其他任何顺序(可能基于每个连接的估计行数和适当的索引)。


然而; 在后面的示例中,使用LEFT OUTER联接;所以Account根本不进行过滤如果其他任何表有多个匹配项,则可能会重复。

(每个帐户中)是否有重复项BalanceToken


你好马克-感谢您的答复,我已编辑的信息最初的问题在针对您的问题
德莱尼

您确定那部分是什么,说优化器将决定他首先执行哪个联接?我认为第二个连接使用第一个连接的结果,因此,第一个连接必须在第二个连接之前执行,对吗?
蒂姆·布斯(TimBüthe)09年

最终结果需要尊重查询的语义-但是优化程序可以在几乎任何地方启动(尽管结果必须相同)。例如,如果我们将具有1M行的表内部联接到具有10行的表,则将10加上星号可能是有意义的。显然,这对于任何类型的外部联接都是不同的-但是优化器仍然有很多选择只要结果是一样的
马克·格雷夫

1

我经常发现它有助于查看实际的执行计划。在查询分析器/管理工作室中,您可以从“查询”菜单中将其打开以进行查询,或者使用Ctrl + M。运行查询后,已执行的计划将显示在另一个结果选项卡中。从中您将看到C和B首先连接,然后结果与A连接。该计划可能会因DBMS所拥有的信息而异,因为这两个连接都是内部的,因此使其成为A和B和C 。我的意思是,无论先加入哪个,结果都是一样的,但是花费的时间可能相差很大,这就是优化器和提示起作用的地方。


1

联接可能很棘手,并且许多行为当然取决于数据在实际表中的存储方式。

在没有看到表格的情况下,很难针对您的具体情况给出明确的答案,但是我认为基本问题是您正在汇总多个结果集,这些结果集被合并为一个。

也许应该在查询中创建两个单独的临时表,而不是多个联接,一个具有accountID,期初余额和期初余额之和,另一个创建具有accountID,日期及期末余额之和的临时表,然后在AccountID和date上将这两个临时表联接。

为了准确查明联接所发生的情况(在您的特定情况下),我将执行以下操作:

更改初始部分

SELECT accountID Accountbalancedate,sum(...)作为期初余额,sum(...)作为期末余额FROM

简单地

“选择*来自”

研究结果表,您将确切看到要复制的数据。逐个删除联接,然后看看会发生什么。这应该为您提供线索,说明造成重复的特定数据到底是什么。

如果您在SQL Server Management Studio中打开查询(存在免费版本),则可以在设计器中编辑查询。有关如何将表连接起来的直观视图也可以帮助您了解正在发生的事情。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.