Рекомендации, описанные в этом руководстве, во многом пересекаются с описанными в книге Джо Селко «Стиль программирования Джо Селко на SQL» (оригинал: SQL Programming Style). Это, в частности, найдут полезным те, кто уже знаком с этой книгой. Тем не менее автор этого руководства в некоторых аспектах более категоричен, нежели Джо Селко, а в других, напротив, более гибок. И, конечно, нельзя не отметить, что это руководство значительно короче и лаконичнее книги Селко — здесь вы не встретите ни весёлых историй из жизни, наглядно объясняющих, как и почему лучше не делать, ни длинных повествований, мотивирующих на использование той или иной рекомендации.

Руководство написано в формате Markdown, что позволяет легко включить его в проект или просто сослаться на него оттуда, что гораздо удобнее, нежели работать с большой бумажной книгой.

Основные положения

Хороший стиль

  • Идентификаторы и имена. Осмысленные и в едином стиле.
  • Пробелы и отступы. Логично расставленные для лучшей читаемости кода.
  • Дата и время. Соответствующие стандарту ISO 8601: YYYY-MM-DD HH:MM:SS.SSSSS.
  • Функции SQL. Стандартные вместо специфичных (определяемых поставщиком) с целью лучшей переносимости.
  • Код. Лаконичный и без излишеств, как например: ненужные кавычки или скобки или неуместное использование оператора WHERE.
  • Комментарии. Предпочтительно в стиле C  /* (начало) и */ (конец). Либо -- перед комментарием, тогда окончанием будет новая строка.
SELECT file_hash -- stored ssdeep hash
  FROM file_system
 WHERE file_name = '.vimrc';

/* Updating the file record after writing to the file */
UPDATE file_system
   SET file_modified_date = '1980-02-22 13:19:01.00000',
       file_size = 209732
 WHERE file_name = '.vimrc';

Плохой стиль

  • CamelCase. Неудобочитаем.
  • Префиксы и венгерская нотация. Префиксы наподобие sp_ или tbl_ избыточны.
  • Множественное число. Лучше использовать более естественно звучащие собирательные понятия. Например, staff вместо employees или people вместо individuals.
  • Идентификаторы в кавычках. Если они обязательно нужны, тогда используйте двойные кавычки, определённые в стандарте SQL-92 с целью лучшей переносимости в дальнейшем.
  • Принципы объектно-ориентированного проектирования. Не нужно применять к SQL или структуре базы данных.

Соглашения о наименовании

Общее

  • Убедитесь в том, что имя уникально и его нет в списке зарезервированных ключевых слов.
  • Ограничивайте длину имени 30 байтами (это 30 символов, если не используется многобайтный набор символов).
  • Начинайте имена с буквы и не заканчивайте их символом подчёркивания.
  • Используйте в именах только буквы, цифры и символ подчёркивания.
  • Избегайте нескольких подряд идущих символов подчёркивания.
  • Используйте символ подчёркивания там, где вы бы поставили пробел в реальной жизни (например, first name станет first_name).
  • Избегайте сокращений. Если их всё же нужно использовать, убедитесь в том, что они общепонятны.
SELECT first_name
  FROM staff;

Таблицы

  • Используйте собирательные имена или, что менее предпочтительно, форму множественного числа. Например, staff и employees (в порядке убывания предпочтения).
  • Не используйте описательные префиксы вида tbl_ и венгерскую нотацию в целом.
  • Не допускайте совпадений названия таблицы с названием любого из её столбцов.
  • По возможности избегайте объединения названий двух таблиц для построения таблицы отношений. Например, вместо названия cars_mechanics лучше подойдёт services.

Столбцы

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

Псевдонимы/корреляции

  • Должны так или иначе быть связаны с объектами или выражениями, псевдонимом которых они являются.
  • Имя корреляции обычно составляется из первых букв каждого слова в имени объекта.
  • Добавьте цифру к имени, если такое уже существует.
  • Всегда используйте ключевое слово AS для лучшей читаемости.
  • Для вычислимых данных (SUM() или AVG()) используйте такие имена, которые вы бы дали, будь они столбцами в таблице.
SELECT first_name AS fn
  FROM staff AS s1
  JOIN students AS s2
    ON s2.mentor_id = s1.staff_num;
SELECT SUM(s.monitor_tally) AS monitor_total
  FROM staff AS s;

Хранимые процедуры

  • Имя должно содержать глагол.
  • Не используйте описательные префиксы вида sp_ и венгерскую нотацию в целом.

Универсальные суффиксы

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

  • _id — уникальный идентификатор, например первичный ключ.
  • _status — флаг или любой статус, например publication_status.
  • _total — общее количество или сумма значений.
  • _num — поле, содержащее число.
  • _name — любое имя, например first_name.
  • _seq — непрерывная последовательность значений.
  • _date — колонка, содержащая дату.
  • _tally — счётчик.
  • _size — размер или величина чего-либо, например размер файла.
  • _addr — физический или абстрактный адрес, например ip_addr.

Синтаксис запросов

Зарезервированные слова

Зарезервированные ключевые слова всегда пишите прописными буквами, например SELECT, WHERE.

Не используйте сокращённый вариант ключевого слова, если имеется полный. Например, используйте ABSOLUTE вместо ABS.

Не используйте специфичные для какого-либо поставщика СУБД ключевые слова, если в ANSI SQL есть ключевые слова, выполняющие такие же функции. Это сделает ваш код более переносимым.

SELECT model_num
  FROM phones AS p
 WHERE p.release_date > '2014-09-30';

Пробельные символы

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

Пробелы

Можно и нужно использовать пробелы для выравнивания основных ключевых слов по их правому краю. В типографике получающиеся таким образом «коридоры» стараются избегать, в то же время в нашем случае они, напротив, помогают лучше вычленять важные ключевые слова.

(SELECT f.species_name,
        AVG(f.height) AS average_height, AVG(f.diameter) AS average_diameter
   FROM flora AS f
  WHERE f.species_name = 'Banksia'
     OR f.species_name = 'Sheoak'
     OR f.species_name = 'Wattle'
  GROUP BY f.species_name, f.observation_date)

  UNION ALL

(SELECT b.species_name,
        AVG(b.height) AS average_height, AVG(b.diameter) AS average_diameter
   FROM botanic_garden_flora AS b
  WHERE b.species_name = 'Banksia'
     OR b.species_name = 'Sheoak'
     OR b.species_name = 'Wattle'
  GROUP BY b.species_name, b.observation_date);

Обратите внимание, что ключевые слова SELECT, FROM и т.д. выровнены по правому краю, при этом названия столбцов и различные условия — по левому.

Помимо этого, старайтесь расставлять пробелы:

  • до и после знака равно (=)
  • после запятых (,)
  • до открывающего и после закрывающего апострофов ('), если последний не внутри скобок, или без последующих запятой или точки с запятой, или не в конце строки
SELECT a.title, a.release_date, a.recording_date
  FROM albums AS a
 WHERE a.title = 'Charcoal Lane'
    OR a.title = 'The New Danger';

Переводы строки

Всегда делайте перенос строки:

  • перед AND или OR
  • после точки с запятой (для разделения запросов)
  • после каждого основного ключевого слова
  • после запятой (при выделении логических групп столбцов)

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

INSERT INTO albums (title, release_date, recording_date)
VALUES ('Charcoal Lane', '1990-01-01 01:01:01.00000', '1990-01-01 01:01:01.00000'),
       ('The New Danger', '2008-01-01 01:01:01.00000', '1990-01-01 01:01:01.00000');
UPDATE albums
   SET release_date = '1990-01-01 01:01:01.00000'
 WHERE title = 'The New Danger';
SELECT a.title,
       a.release_date, a.recording_date, a.production_date -- grouped dates together
  FROM albums AS a
 WHERE a.title = 'Charcoal Lane'
    OR a.title = 'The New Danger';

Отступы

Для того, чтобы SQL был удобочитаем, важно также следовать стандартам расстановки отступов.

JOIN

Объединения (JOIN) должны располагаться по правую часть «коридора». При необходимости между ними можно добавить пустую строку.

SELECT r.last_name
  FROM riders AS r
       INNER JOIN bikes AS b
       ON r.bike_vin_num = b.vin_num
          AND b.engine_tally > 2

       INNER JOIN crew AS c
       ON r.crew_chief_last_name = c.last_name
          AND c.chief = 'Y';

Подзапросы

Подзапросы тоже должны быть выровнены по правому краю «коридора», а внутри них самих применяются те же правила форматирования, что и в любом другом запросе. Если используются вложенные подзапросы, может иметь смысл поставить закрывающую скобку на новой строке ровно под парной ей открывающей скобкой.

SELECT r.last_name,
       (SELECT MAX(YEAR(championship_date))
          FROM champions AS c
         WHERE c.last_name = r.last_name
           AND c.confirmed = 'Y') AS last_championship_year
  FROM riders AS r
 WHERE r.last_name IN
       (SELECT c.last_name
          FROM champions AS c
         WHERE YEAR(championship_date) > '2008'
           AND c.confirmed = 'Y');

Формальные тонкости

  • Используйте BETWEEN, где возможно, вместо нагромождения условий AND.
  • Таким же образом старайтесь использовать IN() вместо OR.
  • Используйте CASE, если значение должно быть интерпретировано до окончания выполнения запроса. С помощью CASE можно также формировать сложные логические структуры.
  • По возможности избегайте использования UNION и временных таблиц.
SELECT CASE postcode
       WHEN 'BN1' THEN 'Brighton'
       WHEN 'EH1' THEN 'Edinburgh'
       END AS city
  FROM office_locations
 WHERE country = 'United Kingdom'
   AND opening_time BETWEEN 8 AND 9
   AND postcode IN ('EH1', 'BN1', 'NN1', 'KW1');

Синтаксис CREATE

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

Внутри объявления CREATE делайте отступ, равный 4 пробелам.

Типы данных

  • По возможности не используйте специфичные для той или иной СУБД типы данных. Это может негативно сказаться на переносимости, а также этих типов может не оказаться в старых версиях этих же СУБД.
  • Для работы с плавающей точкой используйте только REAL или FLOAT, но где нет необходимости в подобных вычислениях, всегда используйте NUMERIC и DECIMAL. Ошибки округления в операциях с плавающей точкой могут оказаться очень некстати.

Значения по умолчанию

  • Значение по умолчанию всегда должно совпадать по типу со столбцом. Если, скажем, столбец объявлен как DECIMAL, не нужно в качестве умолчания указывать значение типа INTEGER.
  • Значения по умолчанию должны располагаться после объявления типа столбца и перед пометкой NOT NULL.

Ограничения и ключи

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

Ключи

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

  1. Ключ должен быть в какой-то степени уникальным.
  2. Должна быть согласованность по типу данных для значения во всей схеме, а также чем ниже вероятность того, что это изменится в будущем, тем лучше.
  3. Можно ли проверить значение на соответствие стандарту (например, ISO)?
  4. Ключ должен быть как можно проще, чтобы можно было без трудностей использовать составные ключи.

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

Ограничения

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

Общее
  • У каждой таблицы должен быть хотя бы один ключ.
  • Ограничениям нужно присваивать вразумительные имена. Для UNIQUE, PRIMARY KEY и FOREIGN KEY подобные имена создаются автоматически, поэтому нужно позаботиться об остальных ограничениях.
Расположение и порядок
  • Первичный ключ должен быть объявлен в самом начале, сразу после оператора CREATE TABLE.
  • Ограничения должны быть объявлены строго ниже столбца, с которым они связаны. Расставьте отступы так, чтобы объявление ограничения начиналось после названия столбца.
  • В случае ограничений, затрагивающих несколько столбцов, старайтесь объявлять их как можно ближе к описанию последнего из них. В крайнем случае объявляйте ограничение в конце тела CREATE TABLE.
  • Ограничения целостности уровня таблицы должны располагаться в конце.
  • Используйте алфавитный порядок там, где ON DELETE предшествует ON UPDATE.
  • Внутри запроса можно выравнивать каждый уровень по-своему. Например, можно добавить отступы после названия столбцов, чтобы типы данных начинались с одной позиции, а затем ещё добавить отступов в нужном количестве, чтобы все объявления NOT NULL тоже были выровнены по левому краю. Подобное форматирование позволит быстрее ориентироваться в коде.
Валидация
  • Используйте LIKE и SIMILAR TO для обеспечения целостности строк с известным форматом.
  • Если диапазон числовых значений для столбца известен, используйте CHECK() для предотвращения внесения в базу некорректных данных или скрытого отсечения части значения слишком больших данных. Обычно проверка делается на то, что значение больше нуля.
  • CHECK() должен быть объявлен как отдельное ограничение для упрощения последующей отладки.
Пример
CREATE TABLE staff (
    PRIMARY KEY (staff_num),
    staff_num      INT(5)       NOT NULL,
    first_name     VARCHAR(100) NOT NULL,
    pens_in_drawer INT(2)       NOT NULL,
                   CONSTRAINT pens_in_drawer_range
                   CHECK(pens_in_drawer BETWEEN 1 AND 99)
);

Чего следует избегать

  • Не применяйте объектно-ориентированные принципы, поскольку они далеко не всегда оптимально ложатся на реляционную модель баз данных.
  • Не разносите по разным столбцам значения и единицы измерения. Нужно создавать столбцы так, чтобы единицы измерения были чем-то самим собой разумеющимся. Для проверки корректности вставляемых в столбец данных используйте CHECK().
  • Избегайте паттерна EAV (Entity Attribute Value). Вместо него используйте специальные продукты, предназначенные для работы с неструктурированными данными.
  • Не разбивайте данные, логически принадлежащие одной таблице, по разным таблицам на основании условностей, например архивации по времени или географическим атрибутам. Впоследствии для работы с несколькими подобными таблицам придётся часто использовать UNION вместо простых запросов к одной таблице.

 

By Ruslan Novikov

Интернет-предприниматель. Фулстек разработчик. Маркетолог. Наставник.