了解JOIN在涉及3个或更多表时如何工作。

我想知道是否有人能够帮助提高我对SQL中JOIN的理解。 [如果问题很重要,我特意考虑MS SQL Server。]

取3个表A,B [与A有关的一个A.Ad]和C [有关B的一些B.BId相关的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 

什么是Cjoin? – 它是B表(还是B表中的值?)或者是C表join的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 

从概念上讲,一个date的收盘余额,将是明date初余额

如果我试图find一个账户的所有期初和期末余额的清单

我可能会做类似的事情

 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多次带入(我假设)临时结果集。

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

为这篇短文道歉,谢谢你的任何提醒:)

编辑回应马克的问题

从概念上来说,一个账户不应该有重复的帐户BalanceToken(根据AccountingDate) – 我认为问题的出现是因为1帐户/会计结算余额是帐户余额第二天 – 所以当自我join余额,BalanceToken多次获得期初和期末余额我认为余额(BalanceId)被多次纳入“结果组合”。 如果有助于澄清第二个例子,将其视为每日对帐 – 因此,剩下的join – 对于给定的账户/会计date组合,可能没有计算开放(和/或)结账余额。

从概念上讲 ,当你将三张桌子连在一起时会发生什么。

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

这在概念上会发生什么。 事实上,沿途有许多可能的优化。 关系模型的优点在于,良好的math基础使得计划的各种转换成为可能,而不会改变正确性。

例如,一路上真的不需要生成完整的结果集。 ORDER BY可以通过首先使用索引访问数据来完成。 有很多types的连接也可以完成。

我们知道来自B的数据将被(内部)连接过滤为AA的数据也被过滤)。 因此,如果我们(内部)从BC ,那么集合C 通过与A的关系过滤。 并且还要注意, 包含来自连接的任何重复项。

然而; 这种情况发生的顺序是由优化器决定的; 它可以决定首先进行B / C连接,然后引入A或任何其他序列(可能基于每个连接的估计行数和相应的索引)。


然而; 在你以后的例子中你使用了一个LEFT OUTER join; 所以Account根本不被过滤,并且如果其他表中有多个匹配的话,那么我的重复也是可以的。

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

我经常发现有助于查看实际的执行计划。 在查询分析器/pipe理工作室中,您可以打开查询菜单中的查询,或使用Ctrl + M。 运行查询后,执行的计划显示在另一个结果选项卡中。 从这里你可以看到C和B首先被连接,然后结果和A连接在一起。这个计划可能会根据DBMS的信息而有所不同,因为这两个连接都是内在的,使得它与A和B和C 。 我的意思是说,无论首先join的结果是相同的,但是所花的时间可能差别很大,而这正是优化器和提示发挥作用的地方。

联接可能会非常棘手,而且大部分行为当然是由数据如何存储在实际表中来决定的。

如果没有看到表格,在特定情况下很难给出明确的答案,但是我认为最基本的问题是,您正在将多个结果集合在一起。

也许而不是多个连接,你应该在你的查询中创build两个单独的临时表,一个帐户ID,date和总额balancebalances,第二个帐户ID,date和期末余额的总和,然后join这两个AccountID和date。

为了准确找出连接发生了什么,在你的具体情况下,我会做以下几点:

更改初始部分

SELECT accountID Accountbalancedate,sum(…)as openingbalance,sum(…)as closingbalance FROM

简单地说

“select*从”

研究结果表,你将会看到正确的数据被复制。 逐个删除连接,看看会发生什么。 这应该给你一个线索,它是关于你的特定数据是什么造成的欺骗。

如果您在SQL Server Management Studio中打开查询(Free version exists),则可以在devise器中编辑查询。 如何join表格的可视化视图也可以帮助您了解发生了什么事情。