Понимание того, как JOIN работает, когда задействовано 3 или более таблиц. [язык SQL]


интересно, может ли кто-нибудь помочь улучшить мое понимание соединений в SQL. [Если это важно для проблемы, я думаю, что MS SQL Server специально.]

принимать по 3 таблиц А, Б [а, связанные с каким-либо помощи А.], и 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 (и значения в таблице B?) Или это какой-то другой временный результирующий набор, который является результатом соединения A+B, к которому присоединена таблица C?

[подразумевается, что не все значения, которые находятся в таблице 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  

все работает так, как я ожидал бы, пока последнее соединение не приведет к закрытию токенов баланса - где я в конечном итоге получаю дубликаты в результате.

[Я могу исправить с Отчетливо - но я пытаюсь понять, почему то, что происходит происходит]

мне сказали, что проблема заключается в том, что отношение между Balance и BalanceToken составляет 1:M - и что когда я привожу последнее соединение, я получаю дубликаты, потому что 3-е соединение уже несколько раз приводило BalanceIds в (Я предполагаю) временный результирующий набор.

Я знаю, что примеры таблиц не соответствуют хорошему дизайну БД

извиняюсь за эссе, спасибо для любого просветления:)

редактировать в ответ на вопрос Марка

концептуально для счета не должно быть дубликатов в BalanceToken для счета (на AccountingDate) - я думаю, что проблема возникает из-за того, что 1 Счет / AccountingDates закрывающий баланс - это баланс открытия счетов на следующий день-поэтому, когда само присоединение к балансу, BalanceToken несколько раз, чтобы получить Открытие и закрытие балансов я думаю, что балансы (BalanceId) вводятся в баланс "результат смешивания" несколько раз. Если это помогает прояснить второй пример, подумайте об этом как о ежедневной выверке - следовательно, левые соединения - Открытие (и/или) заключительный баланс, возможно, не был рассчитан для данной комбинации счета / даты учета.

4 61

4 ответа:

принципиально вот что происходит, когда вы соединяете три таблицы вместе.

  1. оптимизатор придумывает план, который включает в себя порядок соединения. Это может быть A, B, C или C, B, A или любая из комбинаций
  2. механизм выполнения запросов применяет любые предикаты (WHERE предложение) к первой таблице, которая не включает в себя ни одну из других таблиц. Он выбирает из столбцов, упомянутых в JOIN или SELECT списка или ORDER BY список. Назвать этот результат
  3. он присоединяет этот результирующий набор ко второй таблице. Для каждой строки он присоединяется ко второй таблице, применяя любые предикаты, которые могут применяться ко второй таблице. Это приводит к другому временному результирующему набору.
  4. затем он присоединяется к финальной таблице и применяет ORDER BY

это концептуально то, что происходит. На самом деле есть много возможных оптимизаций на этом пути. Преимущество реляционной модели заключается в том, что здравая математическая основа делает возможными различные преобразования плана, не меняя при этом правильности.

например, на самом деле нет необходимости генерировать полные результирующие наборы по пути. Элемент ORDER BY вместо этого может быть сделано через доступ к данным с помощью индекса в первую очередь. Есть много типов соединений, которые можно сделать, а также.

мы знаем, что данные от B будет фильтроваться (внутренним) соединением с A (данные в A тоже фильтруют). Так что если мы (внутренние) присоединяемся от B до C, таким образом, набор C и и фильтруется по отношению к A. И обратите внимание также, что любые дубликаты из join будет включен.

однако; в каком порядке это происходит, зависит от оптимизатора; он может решить сделать B/C Регистрация сначала тогда представьте A, или любая другая последовательность (вероятно, на основе предполагаемого количества строк из каждого соединения и соответствующих индексов).


однако; в вашем более позднем примере вы используете LEFT OUTER join; so Account не фильтруется на всех, и вполне может мой дублированный, если любая из других таблиц имеет несколько совпадений.

есть ли дубликаты (для каждой учетной записи) в BalanceToken?

Я часто нахожу, что это помогает просматривать фактический план выполнения. В query analyser / management studio это можно включить для запросов из меню запрос или использовать Ctrl+M. После выполнения запроса выполненный план отображается на другой вкладке результат. Из этого вы увидите, что сначала соединяются C и B, а затем результат соединяется с A. план может варьироваться в зависимости от информации, которую имеет СУБД, потому что оба соединения являются внутренними, что делает его A-и-B-и-C. Я имею в виду, что результат будет то же самое независимо от того, что присоединяется первым, но время, которое требуется, может сильно отличаться, и именно здесь в игру вступают оптимизатор и подсказки.

соединения могут быть сложными, и большая часть поведения, конечно, диктуется тем, как данные хранятся в реальных таблицах.

Не видя таблиц, трудно дать четкий ответ в вашем конкретном случае, но я думаю, что основная проблема заключается в том, что вы суммируете несколько результирующих наборов, которые объединяются в один.

возможно, вместо нескольких соединений вы должны сделать две отдельные временные таблицы в своем запросе, один с accountID, датой и суммой открытие балансов, второй со счетом, датой и суммой заключительных остатков, а затем присоединение этих двух по счету и дате.

чтобы точно узнать, что происходит с соединениями, также в вашем конкретном случае, я бы сделал следующее:

изменить начальную часть

выберите accountID Accountbalancedate, sum(...) как openingbalance, сумма.(..) как закрыть баланс от

просто

"выберите * От"

изучите полученную таблицу, и вы увидите, какие именно данные дублируются. Удалите соединения по одному и посмотрите, что произойдет. Это должно дать вам ключ к тому, что именно о ваших конкретных данных, которые вызывают обманы.

Если вы открываете запрос в среде SQL server management studio (существует бесплатная версия), вы можете изменить запрос в конструкторе. Визуальное представление того, как объединяются таблицы, также может помочь вам понять, что происходит.