SQL оставил соединение против нескольких таблиц в строке FROM?

256

Большинство диалектов 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

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

jmucchiello
источник
1
Guffa: Как ты это нашел? Хотя мой вопрос - это лучшая практика, чем «как я могу»
jmucchiello
Так как это лучшая практика, пожалуйста, сделайте это вики.
Биной ​​Энтони
1
Я не думаю, что кто-то прокомментировал производительность этих двух. Может ли кто-нибудь подтвердить или процитировать что-либо разумное в отношении каких-либо существенных различий?
ahnbizcad
@ahnbizcad Два заданных запроса не делают одно и то же. Первый возвращает то же самое, что и INNER JOIN ON. Внедрение зависит от версии СУБД и даже в этом случае имеет мало гарантий. Но случаи эквивалентности преобразований СУБД через запятую против INNER JOIN ON / WHERE против CROSS JOIN WHERE тривиальны. Узнайте об оптимизации / реализации запросов к реляционной базе данных.
philipxy
получил рекомендацию ресурса? гигантские, плотные руководства - вот почему я пытаюсь учиться здесь.
Анбизкад

Ответы:

319

Старый синтаксис с простым перечислением таблиц и использованием 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, было похоже на высказывание «вот, пожалуйста, делай как можно лучше». С новым синтаксисом оптимизатору запросов меньше работы, чтобы выяснить, какие части идут вместе.

Так что у вас есть это.

LEFT и INNER JOIN - это волна будущего.

Лассе В. Карлсен
источник
28
«не рекомендуется в большинстве современных баз данных». --- просто любопытно, какие?
zerkms
10
простите, я не знаком с оператором * =, что он делает? Спасибо!
УльтраДжон
9
Звезды = и = Звезды (хорошо были) являются правыми и левыми внешними соединениями, или это слева и справа? Уже давно не рекомендуется, я не использовал их с SQL Server 6.
Тони Хопкинсон
3
Запятая не рекомендуется. Никогда-стандартный OUTER JOINсинтаксис *=/ =*/ *=*устарел.
philipxy
1
Этот ответ даже не отвечает на вопрос, который не касается внешних объединений. Одно утверждение, которое он делает о запятой против INNER JOIN ON, повторная оптимизация, неверно.
philipxy
17

Синтаксис 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)
Andomar
источник
2
Синтаксис * = устарел в MS SQLServer и по уважительной причине: он не только затрудняет чтение, но и не выполняет то, что думают люди, и НЕ является тем же самым, что и ЛЕВОЕ СОЕДИНЕНИЕ. Синтаксис (+) мне незнаком; какая реализация SQL делает это?
Евро Мицелли
2
Другой синтаксис используется Oracle, по крайней мере.
Лассе В. Карлсен
4
Никогда не используйте синтаксис SQL Server * =, он НЕ даст согласованных результатов, поскольку иногда будет интерпретироваться как перекрестное соединение, а не левое соединение. Это верно даже в SQL Server 2000. Если у вас есть код, использующий это, вам нужно исправить.
HLGEM
12

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

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

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

Дуайт Т
источник
,все еще стандарт. onнужно было ввести только для того, чтобы outer joinбыли также введены подвыборы.
Philipxy
12

По сути, когда ваше предложение 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 более читабельным (IMHO, если не использовать объединения, оператор становится беспорядочным). При использовании перекрестного продукта необходимо указать критерии объединения в предложении WHERE, и это проблема с обозначениями. Вы переполняете свое предложение WHERE такими вещами, как

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

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

Петер Перхач
источник
10

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

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

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

HLGEM
источник
6

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

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

Алан Г
источник
5

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

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

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

Евро Мицелли
источник
2

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

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

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

Пабло Санта Круз
источник
SQL-сервер устарел из-за синтаксиса левого соединения, и даже в SQL Server 2000 он не всегда дает правильные результаты (иногда он выполняет перекрестное соединение вместо левого соединения) и никогда не должен использоваться в SQL Server.
HLGEM
@HLGEM: Спасибо за информацию. Я собираюсь ОБНОВИТЬ мой пост, чтобы отразить то, что вы говорите.
Пабло Санта Круз
0

Я слышал, что многие жалуются, что первый слишком сложен для понимания и неясен. Я не вижу проблемы с этим, но после этого обсуждения я использую второе даже для ВНУТРЕННИХ СОЕДИНЕНИЙ для ясности.

kemiller2002
источник
1
Я воспитывался с привычкой не использовать синтаксис JOIN и делать это первым способом. Я должен признать, что часто я все еще застрял в этой привычке только потому, что я думаю, что мой мозг был обусловлен, чтобы следовать этой логике, тогда как иногда мне кажется, что мне сложно обдумать синтаксис соединения
TheTXI
3
Меня тоже так учили. Я изменил свой стиль кодирования, потому что люди смотрят на это и не легко узнают, что происходит. Поскольку нет логической разницы, и я не могу найти причин для выбора первого вместо второго, я чувствовал, что должен приспособиться к тому, чтобы сделать код более понятным, чтобы помочь другим понять, что я пишу.
kemiller2002
0

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

Джефф Ферланд
источник
0

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

Гэвин Н
источник