了解当涉及到3个或更多表时 JOIN 是如何工作的

我想知道是否有人可以帮助我提高对 SQL 中 JOIN 的理解。[如果这对问题很重要,我特别考虑 MSSQLServer。]

取3个表 A,B [ A 与 B 的关系由某个 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 表(和其中的值)吗? 或者是其他临时结果集,是 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 带来结束余额标记——在结果中出现重复项。

[我可以用一个独特的方法来解决问题-但是我试图理解为什么正在发生的事情正在发生]

有人告诉我,这个问题是因为 Balance 和 BalanceToken 之间的关系是1: m-当我引入最后一个 JOIN 时,我得到了重复的内容,因为第3个 JOIN 已经多次引入了 balanceid 到(我假设)临时结果集中。

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

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

编辑回应马克的问题

从概念上讲,一个账户不应该有重复的 BalanceToken (每个 AccountingDate)-我认为问题出现是因为1 Account/AccountingDate 结算余额是第二天的账户开户余额-所以当自我加入 Balance 时,BalanceToken 多次得到开户余额和结算余额,我认为 BalanceId (BalanceId)被多次加入“结果组合”。如果它有助于澄清第二个示例,那么可以把它看作是每日对账——因此是左连接——可能没有计算给定帐户/会计日期组合的期初(和/或)期末余额。

56159 次浏览

我们知道来自 B的数据将通过(内部)到 A的连接进行筛选(A中的数据也将被筛选)。因此,如果我们(内部)从 B连接到 C,那么集合 C就是通过与 A的关系过滤的 还有。还要注意任何来自联接 将包括在内的副本。

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


但是,在您后面的示例中,您使用了 LEFT OUTER联接; 因此 Account没有被过滤 完全没有,如果其他任何表有多个匹配,那么它可能会被复制。

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

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

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

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

这就是概念上发生的事情。事实上,在这个过程中有许多可能的优化。这种关系模型的优势在于,健全的数学基础使得计划的各种变化成为可能,同时不改变计划的正确性。

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

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

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

也许您应该在查询中使用两个单独的临时表来代替多个连接,其中一个表具有 AccountID、 date 和 sum of openingBalance,另一个表具有 AccountID、 date 和 sum of close Balance,然后在 AccountID 和 date 上将这两个表连接起来。

为了了解连接到底发生了什么,同样是在您的特定情况下,我将执行以下操作:

改变初始部分

选择 AccountID AccountBalance,sum (...)作为期初余额, 和(...)作为结算余额

变得简单

“ SELECT * FROM”

研究结果表,您将确切地看到正在复制哪些数据。一个接一个地移除连接,看看会发生什么。这应该给你一个线索,是什么是你的特定数据,是导致欺骗。

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