8 способов сделать SQL запросы понятнее


Any fool can write code that a computer can understand. Good programmers write code that humans can understand. — Martin Fowler

Вступление

За последние пять лет мне довелось поработать в трёх разных компаниях, но ни в одной из них я не встречал SQL запросы, которые выглядели бы опрятно и легко читались (не считая редких исключений). Как правило попадаются запросы написанные как попало: в них случайным образом скачут отступы и меняется регистр, они плохо структурированы и непоследовательны, зачастую ещё и написаны не очень эффективно. Открывая такой запрос приходится потратить немалое время, чтобы начать хоть что-то в нём понимать. А через месяц, встретив этот же запрос снова, придётся опять в нём разбираться. Многие люди вообще относятся к сиквелу как к второсорному языку, не проявляя к нему никакого уважения. Но уважения они не проявляют не только к языку, но и к другим разработчикам, которым в будущем приходится читать и поддерживать такие запросы.

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

1. Никакого капса

В давние времена, когда редакторы кода не имели возможности подсвечивать синтаксис, было принято писать ключевые слова заглавными буквами. С тех пор эта привычка крепко укоренилась в головах некоторых разработчиков и они продолжают следовать этой традиции. Некоторые пошли ещё дальше, и стали писать капсом вообще всё: имена таблиц, стобцов и пр. На деле же, любой современный редактор кода имеет подстветку ситнаксиса (в т. ч. и для встроенных языков, если вы пишете запрос внутри другого языка). SELECT, FROM, WHERE навряд ли помогут вам понять суть запроса, а вот внимание на себя отвлекать однозначно будут. За 5 лет что я пишу SQL запросы, я редко встречал те которые можно назвать «образцовыми»: где все ключевые слова выделены капсом, а не ключевые нет. Зато смешивание этих стилей попадается сплошь и рядом.

2. Перенос строк

Тут я выделяю понятия остновных ключевых слов и второстепенных (вложенных). Так, например, select, from и where являются основными, join, on, and второстепенными. Основные слова выравнены по левому краю, второстепенные в зависимости от уровня вложенности сдвигаются вправо:

select ...
from table1
join table2
  on
  and
where ...
and ...

3. Отступы

В SQL предпочтительней использовать отступы из 2 пробелов. Запросы с такими отсупами выглядят опрятней и компактней в сравнении с другими вариантами.

4. Перечисления

В тех случая когда столбцов в запросе несколько, предпочтительней писать каждый из них с новой строки:

select
  id,
  name,
  type_id
  ...

5. Соединения

Тема соединений таблиц всегда была очень болезненной. Когда-то и я перечислял имена таблиц через запятую, а все соединения наряду с предикатами делал в блоке where используя (+) вместо left join. Но, такая запись трудна для восприятия человеком, читающим этот запрос. Основных аргументов в её пользу, которые мне доводилось слышать, два: 1) ANSI соединения в оракле работают медленнее (на данный момент это уже не актуально); 2) глазам не приходится бегать по всему запросу, т. к. все условия находятся в одном месте. Аргумент 2 не выдерживает никакой критики, скорее это закостенелая привычка от которой сложно изавиться людям давно использующим такой синтаксис. Когда все условия собраны в where, это больше похоже на месиво в котором чёрт ногу сломит. Напротив, при использовании ANSI соединений, запрос выглядит опрятным, каждое такое соединение и его уточнение сосредоточено сразу под именем таблицы, а в блоке where находится лишь окончательный предикат глядя на который становится видно саму суть.

...
from objects file
  join parameters path
    on path.object_id = file.id
    and path.attr_id = 123
where file.type_id = 404;

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

Нередко авторы запросов заключают в скобки условия соединений:

...
from objects file
  join parameters path
    on (path.object_id = file.id
    and path.attr_id = 123)
where file.type_id = 404;

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

6. Алиасы для таблиц

Они должны обозначать, то что выбирается из таблицы. Алиасы лучше чем комментарии рядом с идентификаторами, т. к. они позволяют в любом месте запроса сразу понять о чём идёт речь, в случае с комментарием вам придётся искать его и держать в уме связь между id и именем.

Плохо:

select
  obj.name,
  p.value as message
from objects obj
  join params p
    on p.object_id = obj.id
    and p.attr_id = 995 -- content
where obj.type_id = 110; -- mail

Хорошо:

select
  mail.name,
  content.value as message
from objects mail
  join params content
    on content.object_id = mail.id
    and content.attr_id = 995
where mail.type_id = 110;

Во втором случае также можно добавить комментарии рядом с идентификатороми, но как по мне, это излишне.

7. Запятые

Кто-то переносит запятую в перечислении на новую строку:

select
    field1
  , field2
  , field3

Я не сторонник такого подхода. Конечно, это вносит определённое удобство при добавлении новых стобцов в запрос, но выглядит уродско.

8. Скобки

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

select name
from table1
where field1 in (..., ..., ...,)
  and field2 in (
    ...,
    ...,
    ...
  )

Также обратите внимание, что если в where сразу же идёт какое-то перечисление, то такой предикат следует перенести на новую строчку, чтобы правило скобок не было нарушено:

select
  ...
from table1
where
  field1 in (
    ...
  )
  and ...;

Ещё вариант:

select
  ...,
  (
    select foo
    from table
    where ...
  ) as bar,
  ...

Ещё несколько примеров

select
  id,
  name,
  parent_id
from objects descriptor
where type_id = 666
order by name;
select
  id,
  name,
  type_id
from objects descriptor
where
  type_id in (
    ...
  )
  and name like 'zek%';
with
  table1 as (
    ...
  ),
  table2 as (
    ...
  )
select
  ...
from table1
  join table2
    on table2.fieldA = table1.fieldA
where table1.fieldB = 'blah';

Вывод

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