1. ホーム
  2. sql

3つ以上のテーブルが関係する場合のJOINの動作を理解する。[SQL]

2023-12-07 19:54:44

質問

SQL の JOIN に関する私の理解を深めるために、誰かが手助けしてくれないかと思います。[それが問題に重要である場合、私は特にMS SQL Serverを考えています]。

3つのテーブルA、B [いくつかのA.AIdによってBに関連するA]、およびC [いくつかのB.BIdによってCに関連する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

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がclosing balance tokensをもたらすまで、物事は私が期待するように動作します - ここで私は結果に重複をもたらすことになります。

[DISTINCTで修正することができますが、なぜこのようなことが起こっているのかを理解しようとしています。]

問題は、Balance と BalanceToken の間の関係が 1:M であるためであり、最後の JOIN を持ち込むと、3番目の JOIN がすでに BalanceIds を複数回 (おそらく) 一時結果セットに持ち込んだため、重複が発生すると言われています。

私は、例のテーブルが良い DB 設計に適合していないことを知っています。

エッセイのために謝罪し、任意の洞察に感謝します:)

Marcの質問に答える形で編集

概念的には、1つのアカウントに対して(AccountingDateごとに)BalanceTokenに重複があるべきではありません - 1つのアカウント / AccountingDatesの終値が翌日のそのアカウントの開始残高になるため、問題が発生すると思います - したがって、開始および終了残高を得るために何度もBalance、BalanceTokenに自己結合すると、残高(BalanceId)が「結果の混合物」に複数回持ち込まれていると考えられます。2番目の例を明確にするのに役立つのであれば、毎日の照合と考え、それゆえ左結合を行います - 開始(および/または)終了バランスは、与えられたアカウント/会計日の組み合わせに対して計算されていないかもしれません。

どのように解決するのですか?

概念的に は、3つのテーブルを結合するとどうなるかを示しています。

  1. オプティマイザは結合順を含む計画を作成します。 A、B、C、またはC、B、A、またはその組み合わせのいずれかになります。
  2. クエリ実行エンジンは任意の述語 ( WHERE 節)を適用します。 それは、述語の中にある JOIN 条件または SELECT のリストか ORDER BY のリストと同じです。 この結果を A と呼びます。
  3. この結果集合を2番目のテーブルに結合する。 各行に対して、2番目のテーブルに適用されるかもしれないあらゆる述語を適用して、2番目のテーブルに結合する。 この結果、別の一時的な結果集合ができる。
  4. そして、最後のテーブルに結合し、述語を適用します。 ORDER BY

これは概念的に起こることです。 実際には、途中で多くの可能な最適化があります。リレーショナルモデルの利点は、健全な数学的基礎があるため、正しさを変えずに計画のさまざまな変換が可能であることです。

例えば、途中で完全な結果セットを生成する必要は本当にありません。 そのため ORDER BY はインデックスを使用してデータにアクセスすることによって行われるかもしれません。 同様に、多くの種類の結合を行うことができます。