我想知道是否有人可以帮助提高我对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)被多次带入'结果组合'。如果有助于澄清第二个例子,可以将其视为每日对账 - 因此留下联接 - 可能没有为给定的账户/会计日期组合计算期初和/或期末余额。
答案 0 :(得分:39)
从概念上来说就是将三个表连接在一起时会发生的事情。
WHERE
子句)应用于不涉及任何其他表的第一个表。它会选择JOIN
条件或SELECT
列表或ORDER BY
列表中提到的列。将此结果称为A ORDER BY
这在概念上会发生什么。事实上,在此过程中有许多可能的优化。关系模型的优点在于,良好的数学基础使得计划的各种变换成为可能,同时又不改变正确性。
例如,实际上不需要在整个过程中生成完整的结果集。 ORDER BY
可以通过首先使用索引访问数据来完成。有许多类型的连接也可以完成。
答案 1 :(得分:5)
我们知道来自B
的数据将被(内部)联接过滤到A
(A
中的数据也会被过滤)。因此,如果我们(内部)从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管理工作室中打开查询(存在免费版本),则可以在设计器中编辑查询。表格如何连接的直观视图也可以帮助您了解正在发生的事情。