SQL оставил соединение против нескольких таблиц в строке FROM? - PullRequest
241 голосов
/ 21 мая 2009

Большинство диалектов SQL принимают оба следующих запроса:

SELECT a.foo, b.foo
FROM a, b
WHERE a.x = b.x

SELECT a.foo, b.foo
FROM a
LEFT JOIN b ON a.x = b.x

Теперь, очевидно, когда вам нужно внешнее соединение, необходим второй синтаксис. Но при выполнении внутреннего объединения, почему я должен предпочесть второй синтаксис первому (или наоборот)?

Ответы [ 11 ]

303 голосов
/ 21 мая 2009

Старый синтаксис с простым перечислением таблиц и использованием предложения WHERE для указания критериев объединения в большинстве современных баз данных устарел.

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

Позвольте привести пример.

Предположим, у вас есть 3 таблицы в вашей системе:

Company
Department
Employee

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

Хорошо, теперь вы хотите сделать следующее:

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

Итак, вы делаете это:

SELECT * -- for simplicity
FROM Company, Department, Employee
WHERE Company.ID *= Department.CompanyID
  AND Department.ID = Employee.DepartmentID

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

Хорошо, так что теперь происходит. Проблема в том, что это зависит от механизма базы данных, оптимизатора запросов, индексов и статистики таблиц. Позвольте мне объяснить.

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

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

И в этом случае из-за левого соединения столбец Department.ID будет иметь значение NULL, и поэтому, когда дело доходит до INNER JOIN для Employee, нет никакого способа выполнить это ограничение для строки Employee, и поэтому не появится.

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

Так что старый синтаксис неоднозначен. Невозможно указать, что вы хотите, не обращаясь к подсказкам запросов, а в некоторых базах данных вообще нет возможности.

Введите новый синтаксис, с этим вы можете выбрать.

Например, если вы хотите, чтобы все компании, как указано в описании проблемы, вы бы написали:

SELECT *
FROM Company
     LEFT JOIN (
         Department INNER JOIN Employee ON Department.ID = Employee.DepartmentID
     ) ON Company.ID = Department.CompanyID

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

Кроме того, допустим, вам нужны только те отделы, в названии которых есть буква X. Опять же, при объединении в старом стиле вы рискуете потерять и компанию, если у нее нет отделов с X в названии, но с новым синтаксисом вы можете сделать это:

SELECT *
FROM Company
     LEFT JOIN (
         Department INNER JOIN Employee ON Department.ID = Employee.DepartmentID
     ) ON Company.ID = Department.CompanyID AND Department.Name LIKE '%X%'

Это дополнительное предложение используется для объединения, но не является фильтром для всей строки. Таким образом, строка может отображаться с информацией о компании, но может содержать значения NULL во всех столбцах отдела и сотрудника для этой строки, поскольку для этой компании не существует отдела с X в названии. Это сложно со старым синтаксисом.

Именно поэтому, среди других поставщиков, Microsoft не поддерживает старый синтаксис внешнего соединения, а не старый синтаксис внутреннего соединения, начиная с SQL Server 2005 и выше. Единственный способ общаться с базой данных, работающей на Microsoft SQL Server 2005 или 2008, используя синтаксис внешнего соединения старого стиля, - это установить эту базу данных в режиме совместимости 8.0 (он же SQL Server 2000).

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

Итак, вот оно.

ВЛЕВО и ВНУТРЕННЕЕ СОЕДИНЕНИЕ - волна будущего.

16 голосов
/ 21 мая 2009

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

Кстати, вы можете также выполнить внешнее соединение с первым синтаксисом:

WHERE a.x = b.x(+)

Или

WHERE a.x *= b.x

Или

WHERE a.x = b.x or a.x not in (select x from b)
11 голосов
/ 22 мая 2009

Первый способ - более старый стандарт. Второй метод был введен в SQL-92, http://en.wikipedia.org/wiki/SQL. Полный стандарт можно посмотреть на http://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt.

Потребовалось много лет, чтобы компании, работающие с базами данных, приняли стандарт SQL-92.

Таким образом, причина, по которой второй метод является предпочтительным, это стандарт SQL согласно комитету по стандартам ANSI и ISO.

10 голосов
/ 21 мая 2009

Как правило, когда в предложении FROM перечислены таблицы:

SELECT * FROM
  tableA, tableB, tableC

результатом является перекрестное произведение всех строк в таблицах A, B, C. Затем вы применяете ограничение WHERE tableA.id = tableB.a_id, которое выбрасывает огромное количество строк, затем далее ... AND tableB.id = tableC.b_id затем получите только те строки, которые вам действительно интересны.

СУБД знают, как оптимизировать этот SQL, чтобы разница в производительности при написании этого с использованием JOIN была незначительной (если есть). Использование нотации JOIN делает оператор SQL more читабельным (IMHO, если не использовать объединения, оператор становится беспорядочным). При использовании кросс-продукта необходимо указать критерии объединения в предложении WHERE, и это проблема с нотацией. Вы переполняете предложение WHERE такими вещами, как

    tableA.id = tableB.a_id 
AND tableB.id = tableC.b_id 

, который используется только для ограничения перекрестного произведения. Предложение WHERE должно содержать ОГРАНИЧЕНИЯ только для набора результатов. Если вы смешаете критерии объединения таблиц с ограничениями набора результатов, вам (и другим) будет труднее читать ваш запрос. Вам определенно следует использовать JOIN и оставить предложение FROM предложением FROM, а предложение WHERE - предложением WHERE.

9 голосов
/ 21 мая 2009

Второй вариант предпочтительнее, поскольку он гораздо реже может привести к случайному перекрестному соединению, если забыть вставить предложение where. Соединение с предложением no on не выполнит проверку синтаксиса, соединение старого стиля с предложением no where не завершится неудачей, оно выполнит перекрестное соединение.

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

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

6 голосов
/ 25 июня 2012

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

В действительно сложных операторах SELECT читателю становится намного легче понять, что происходит.

5 голосов
/ 21 мая 2009

Синтаксис SELECT * FROM table1, table2, ... подходит для нескольких таблиц, но он становится экспоненциально ( не обязательно математически точный оператор ) все труднее и труднее читать по мере увеличения количества таблиц.

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

Кроме того, если все соединения являются ВНУТРЕННИМИ, то обе версии эквивалентны. Тем не менее, в тот момент, когда вы выполняете OUTER-соединение где-либо в утверждении, все становится намного сложнее, и практически гарантируется, что то, что вы пишите, не будет запрашивать то, что вы написали.

2 голосов
/ 21 мая 2009

Когда вам нужно внешнее соединение, второй синтаксис будет , а не всегда требуется:

Oracle:

SELECT a.foo, b.foo
  FROM a, b
 WHERE a.x = b.x(+)

MSSQLServer (хотя он был устарел в версии 2000) / Sybase:

SELECT a.foo, b.foo
  FROM a, b
 WHERE a.x *= b.x

Но вернемся к вашему вопросу. Я не знаю ответа, но это, вероятно, связано с тем, что соединение более естественно (по крайней мере, синтаксически), чем добавление выражения к предложению , где , когда вы делают именно это: присоединение .

0 голосов
/ 21 мая 2009

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

0 голосов
/ 21 мая 2009

Для базы данных они заканчиваются тем же. Для вас, однако, вам придется использовать этот второй синтаксис в некоторых ситуациях. Ради редактирования запросов, которые в итоге должны будут использовать его (выяснение, что вам нужно левое соединение, где у вас было прямое соединение), и для согласованности, я бы сделал паттерн только по 2-му методу. Это облегчит чтение запросов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...