Подтвердить что ты не робот

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

Интересно, может ли кто-нибудь помочь улучшить мое понимание JOINs в SQL. [Если это важно для проблемы, я думаю, 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?) Или это какой-то другой временный результирующий набор, являющийся результатом объединения 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  

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

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

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

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

Извинения за эссе, спасибо за любое восстание:)

Изменить в ответ на вопрос Marc

Концептуально для учетной записи не должно быть дубликатов в BalanceToken для учетной записи (на учетную дату). Я думаю, проблема возникает из-за того, что 1 баланс счета/счета-счетов-счетов является тем, что баланс счета начинается на следующий день - поэтому, когда я присоединяюсь к Баланс, BalanceToken несколько раз, чтобы получить открывающие и закрывающие балансы. Думаю, балансы (BalanceId's) несколько раз приводятся в "результат". Если это поможет прояснить второй пример, подумайте об этом как о ежедневной сверке - следовательно, о левом соединении - закрытие счета открытия (и/или) может не быть рассчитано для данной учетной записи/accountdate.

4b9b3361

Ответ 1

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

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

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

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

Ответ 2

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

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


ОДНАКО; в следующем примере вы используете соединение LEFT OUTER; поэтому Account вообще не фильтруется, и, вполне возможно, мой дубликат, если какая-либо из других таблиц имеет несколько совпадений.

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

Ответ 3

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

Ответ 4

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

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

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

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

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

ВЫБОР учетной записи Account Account Accountate, sum (...) как балансировка, sum (...) как closbalance FROM

просто

"SELECT * FROM"

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

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