Программы
Вредные привычки в SQL: старомодные JOINы

Вредные привычки в SQL: старомодные JOINы

Как лучше писать много-табличные запросы: просто перечисляя таблицы в SELECT через запятую, или же использовать JOIN?

Перевод заметки Bad Habits to Kick : Using old-style JOINs.

Уверен, многие ветераны SQL знают, что есть способ лучше, чем соединять таблицы в стиле ANSI-89 (через запятую):

SELECT o.OrderID, od.ProductID
  FROM dbo.Orders AS o, dbo.OrderDetails AS od
  WHERE o.OrderDate >= '20091001'
  AND o.OrderID = od.ProductID;

Одна из причин избегать данного синтаксиса в том, что подобные запросы сложнее читать. Если бы мы разделили критерии соединения таблиц и критерии фильтрации результата соединения, было бы проще понимать запрос. Ниже мы перепишем данный запрос, но в этот раз используем JOIN-ы:

SELECT o.OrderID, od.ProductID
  FROM dbo.Orders AS o
  INNER JOIN dbo.OrderDetails AS od
    ON o.OrderID = od.ProductID
  WHERE o.OrderDate >= '20091001';

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

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

Версия данного синтаксиса с OUTER JOIN (через *= и =*) во многих SQL серверах упразднена, так что это ещё одна причина избегать использования данного синтаксиса (через перечисление таблиц в SELECT). Многие помнят, как работают данные условия, но пометка "устаревшее" или отсутствие синтаксиса *= и =* – всё же важный фактор. Опять же, лучше ключевыми словами описывать условия соединения во избежание двусмысленности:

SELECT p.ProductName, p.ProductID
  FROM dbo.Products AS p
  LEFT OUTER JOIN dbo.OrderDetails AS od
    ON p.ProductID = od.ProductID
  WHERE od.ProductID IS NULL;

Также есть задокументированные случаи, когда старомодные внешние соединения (аналоги * OUTER JOIN) выдавали неверные результаты в зависимости от порядка исполнения фильтрующих условий. Да, они сейчас объявлены устаревшими, где-то упразднены, но данный факт всё же показывает, что лучше не использовать старомодные соединения.

Если вам ещё не достаточно причин отказаться от старых соединений... Вот ещё большая проблема в использовании неявного синтаксиса, когда условия соединения описываются там же, где и условия фильтрации. А что если вы просто забыли в списке прочих критериев указать критерий объединения? Вы неумышленно получаете CROSS JOIN (Декартово Произведение нескольких таблиц). Рассмотрим эту ситуацию на первом примере:

SELECT o.OrderID, od.ProductID
  FROM dbo.Orders AS o, dbo.OrderDetails AS od
  WHERE o.OrderDate >= '20091001';

Для каждого заказа из Orders с 1-го октября 2009-го года вы получите копию каждой строки из таблицы OrderDetails за всё время. Вы потеряли критерий соединения, поэтому для каждой строки из левой таблицы вы присоедините все строки из правой таблицы. Да, БД оптимизирует по критерию фильтрации, поэтому из левой таблицы вы возьмёте только лишь все заказы с октября 2009-го...

Это может быть ужасная ошибка на боевом сервере – память от такого соединения быстро закончится, а сам сервер на некоторое время превратится в "тыкву".

С другой стороны, вы могли узнать об этой ошибке ещё на моменте первого написания / тестирования данного кода. Если использовать явное указание соединения таблиц через INNER JOIN, база данных от вас потребует условие, так как синтаксис INNER JOIN требует указания условия соединения ON.

Конечно, вы всё ещё можете "выстрелить себе в ногу", указав, например ON o.OrderID = o.OrderID. Но согласитесь, хорошо, когда синтаксис уменьшает количество вариантов ошибиться!

Моя основная цель – помочь людям избавиться от вредных привычек, которые Майкрософт им прививает, публикуя примеры кода, использующие данный синтаксис. Это не проблема для ветеранов SQL (кроме тех, что не желают развиваться), тех, кто понимает, как соединения работают, и знает историю ANSI-89 и ANSI-92. Однако, новички в SQL могут встретить этот синтаксис в книжках или заметках в интернете.