了解当涉及3个或更多表时JOIN如何工作.[SQL]

Del*_*ney 68 sql join

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

取3个表A,B [A由某些A.AId相关]和C [B与某些B.BId相关的C]

如果我撰写查询,例如

SELECT *
FROM A JOIN B 
ON A.AId = B.AId
Run Code Online (Sandbox Code Playgroud)

一切都很好 - 我对它的运作方式很满意.

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

在这种情况下

SELECT *
FROM A JOIN B 
  ON A.AId = B.AId
JOIN C ON C.BId = B.BId
Run Code Online (Sandbox Code Playgroud)

什么是C加入? - 是B表(以及B表中的值吗?)或者是C表加入的A + B Join的结果是否是其他临时结果集?

[暗示并非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
Run Code Online (Sandbox Code Playgroud)

从概念上讲,关闭日期的平衡,将是明天的平衡

如果我正在尝试查找帐户的所有期初和期末余额列表

我可能会做类似的事情

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  
Run Code Online (Sandbox Code Playgroud)

事情按照我的预期工作,直到最后一次JOIN带来结束余额令牌 - 我最终在结果中重复.

[我可以用DISTINCT修复 - 但我试图理解为什么发生的事情正在发生]

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

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

为这篇文章道歉,感谢任何提高:)

编辑以回应Marc提出的问题

从概念上来说,账户中的BalanceToken不应该有重复(每个AccountingDate) - 我认为问题是因为1账户/会计日期的期末余额是第二天的账户期初余额 - 所以当自我加入Balance,BalanceToken多次获得期初和期末余额我认为Balances(BalanceId)被多次带入'结果组合'.如果它有助于澄清第二个示例,将其视为每日对帐 - 因此左联接 - 可能尚未计算给定帐户/会计日期组合的期初(和/或)期末余额.

WW.*_*WW. 42

从概念上讲,这是将三个表连接在一起时会发生什么.

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

这在概念上会发生什么.事实上,在此过程中有许多可能的优化.关系模型的优点在于,良好的数学基础使得计划的各种变换成为可能,同时不改变正确性.

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

  • 谢谢WW - 我认为这清除了我想知道的事情 - 后续连接(表3以后)发生在/沿着正在构建的'最后'中间结果集 - 而不是原始表.从第一个例子开始说,... B JOIN C ON B.BId = C.BId - 再次连接A + B中间结果集中的行而不是B表中的行.[概念性] (3认同)
  • 是的,只有两个结果集一次连接在一起并且它会建立起来.这就是加入顺序对计划非常重要的原因. (3认同)
  • @NikosV WHERE 子句的某些部分在联接之前应用,然后在联接后续表时应用其他部分。从概念上讲,可以通过执行所有联接然后应用 WHERE 子句来获得结果,但这会导致不必要的大中间结果。因此,查询引擎会通过应用 WHERE 子句来尽快缩减结果。 (2认同)

Mar*_*ell 5

我们知道来自B(内部)连接的数据将被过滤A(数据输入A也被过滤).因此,如果我们(内部)从加盟BC,这样的设定C通过关系来过滤A.另请注意,将包含来自联接的任何重复项.

然而; 这种情况发生的顺序取决于优化器; 它可以决定先B/ Cjoin然后引入A,或任何其他序列(可能基于每个连接的估计行数和适当的索引).


然而; 在后面的例子中,你使用了一个LEFT OUTER连接; 所以Account根本没有过滤,如果任何其他表有多个匹配,我可能会重复.

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