了解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组合,可能没有计算开放(和/或)结账余额。
从概念上讲 ,当你将三张桌子连在一起时会发生什么。
- 优化器提出了一个包含连接顺序的计划。 它可以是A,B,C或C,B,A或任何组合
- 查询执行引擎将任何谓词(
WHERE
子句)应用于不涉及任何其他表的第一个表。 它select在JOIN
条件或SELECT
列表或ORDER BY
列表中提到的列。 称这个结果为A - 它将这个结果集连接到第二个表。 对于每一行都join到第二个表中,应用可能适用于第二个表的任何谓词。 这导致另一个临时结果集。
- 然后它join到最终表格中并应用
ORDER BY
这在概念上会发生什么。 事实上,沿途有许多可能的优化。 关系模型的优点在于,良好的math基础使得计划的各种转换成为可能,而不会改变正确性。
例如,一路上真的不需要生成完整的结果集。 ORDER BY
可以通过首先使用索引访问数据来完成。 有很多types的连接也可以完成。
我们知道来自B
的数据将被(内部)连接过滤为A
( A
的数据也被过滤)。 因此,如果我们(内部)从B
到C
,那么集合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表格的可视化视图也可以帮助您了解发生了什么事情。