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

时间:2009-07-05 08:24:16

标签: sql join

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

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

如果我撰写查询,例如

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表(以及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

从概念上讲,结束日期的平衡,将是明天的平衡

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

我可能会做类似

的事情
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时,我得到重复,因为第3个JOIN已经多次将BalanceIds引入(我假设)临时结果集。

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

为这篇文章道歉,感谢任何一种解脱:)

编辑以回应Marc提出的问题

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

4 个答案:

答案 0 :(得分:39)

从概念上来说就是将三个表连接在一起时会发生的事情。

  1. 优化器提出了一个计划,其中包含一个连接顺序。它可以是A,B,C或C,B,A或任何组合
  2. 查询执行引擎将任何谓词(WHERE子句)应用于不涉及任何其他表的第一个表。它会选择JOIN条件或SELECT列表或ORDER BY列表中提到的列。将此结果称为A
  3. 它将此结果集连接到第二个表。对于每一行,它连接到第二个表,应用可能适用于第二个表的任何谓词。这导致另一个临时结果集。
  4. 然后它加入决赛桌并应用ORDER BY
  5. 这在概念上会发生什么。事实上,在此过程中有许多可能的优化。关系模型的优点在于,良好的数学基础使得计划的各种变换成为可能,同时又不改变正确性。

    例如,实际上不需要在整个过程中生成完整的结果集。 ORDER BY可以通过首先使用索引访问数据来完成。有许多类型的连接也可以完成。

答案 1 :(得分:5)

我们知道来自B的数据将被(内部)联接过滤到AA中的数据也会被过滤)。因此,如果我们(内部)从B加入C,那么集合C 通过与A的关系进行过滤。另请注意,加入的任何重复内容都将包括在内

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


无论其;在后面的示例中,您使用LEFT OUTER联接;所以Account未完全过滤,如果其他任何表都有多个匹配项,那么我的副本可能会重复。

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

答案 2 :(得分:1)

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

答案 3 :(得分:1)

联接可能很棘手,而且大部分行为当然取决于数据如何存储在实际表格中。

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

或许不是多个连接,您应该在查询中创建两个单独的临时表,一个包含accountID,开放平衡的日期和总和,第二个包含accountID,结束余额的日期和总和,然后在AccountID上加入这两个和日期。

为了准确了解联接的情况,在您的具体情况下,我会执行以下操作:

更改初始部分

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

简单地

“SELECT * FROM”

研究结果表,您将确切地看到正在复制的数据。逐个删除连接,看看会发生什么。这应该可以为您提供有关导致欺骗的特定数据的线索。

如果在SQL Server管理工作室中打开查询(存在免费版本),则可以在设计器中编辑查询。表格如何连接的直观视图也可以帮助您了解正在发生的事情。