Как Рэймонд Бойс помог сделать SQL практичным: читаемость запросов, JOIN и подзапросы, NULL, представления, оптимизация и внедрение в компаниях.

Реляционная теория дала красивую, строгую модель данных, но организациям был нужен не учебник, а работающий инструмент: чтобы можно было быстро задавать вопросы к данным, получать отчёты и поддерживать систему годами. Ранний SQL стал мостом между идеалом и реальностью — языком, который можно внедрить, обучить ему сотрудников и встроить в процессы.
Теоретически можно стремиться к полной формальной чистоте, но внедрение базы данных упирается в повседневные ограничения: разные источники данных, неполные записи, необходимость разграничения прав доступа, давление сроков. Если язык запросов слишком сложен или слишком «академичен», им будут пользоваться только специалисты — а бизнесу нужна массовая применимость.
Для компаний критичны четыре вещи:
Ранние решения — декларативный стиль запросов, соединения таблиц, подзапросы, представления и работа с отсутствующими значениями — сформировали «ядро», которое позже закрепилось как де-факто норма. Во многом именно такие инженерные выборы (в развитии которых участвовал Рэймонд Бойс) сделали SQL не просто идеей, а практическим языком для реальных информационных систем.
Эта статья — про компромиссы и последствия решений в дизайне SQL, а не про полную биографию Бойса: важнее понять, какие требования организаций направляли эволюцию языка и почему это оказалось долговечным.
Рэймонд Бойс — один из ключевых инженеров и исследователей IBM, благодаря которым ранний SQL (тогда ещё SEQUEL) перестал быть красивой концепцией на бумаге и стал языком, на котором реально можно было работать в организациях. Его вклад обычно описывают не как «изобретение с нуля», а как доводку идей до состояния, когда ими удобно пользоваться каждый день — и пользователям, и командам разработки.
Промышленные языки почти никогда не рождаются усилиями одного человека. Рядом с Бойсом работали Дональд Чемберлин и другие участники проекта System R: одни формулировали принципы, другие проверяли их на реальных сценариях, третьи внедряли в прототипы и измеряли эффект.
Сильная сторона Бойса — умение связывать теорию реляционных моделей с инженерной практикой: как будет выглядеть запрос в документации, насколько легко его читать коллегам, какие ошибки допускают пользователи, как ведёт себя система на больших объёмах данных.
Ранний SQL решал насущную проблему: дать аналитикам и разработчикам общий способ задавать вопросы к данным без написания процедурных программ на каждый отчёт.
Аналитикам нужна была выразительность (фильтрация, группировки, соединения таблиц), а разработчикам — чтобы запросы можно было встраивать в приложения и поддерживать годами. Отсюда внимание к понятным конструкциям и к тому, чтобы результат был воспроизводимым.
Если язык допускает слишком много «магии», он быстро становится источником споров: почему один и тот же запрос в разных руках даёт разные результаты? Поэтому в дизайне раннего SQL важны были ясные правила — что считается строкой результата, как применяются условия, когда выполняются группировки.
Ставка на декларативность и читаемость сделала SQL общим рабочим инструментом: запрос можно передать коллеге, обсудить на ревью и повторить через месяц — без зависимости от личного стиля автора.
Реляционная модель предлагала простую, но строгую картину: данные хранятся в таблицах (отношениях), строки — это кортежи, а работа с данными сводится к операциям над этими отношениями. В теории это выглядело элегантно: можно формально описать, какие строки должны попасть в результат, и получить корректный ответ без ручной «процедуры» перебора.
Ключевая идея: не писать пошаговый алгоритм, а задавать преобразование набора данных. Проекции «выбирают» столбцы, выборки «фильтруют» строки, объединения и пересечения работают как операции над множествами. Такая математика хорошо подходит для проверки корректности и для оптимизации.
Проблема была в том, что чистая реляционная алгебра и исчисление — это язык для исследователей, а не для сотрудников компаний. Ранний SQL стал переводчиком между теорией и практикой: он сохранил смысл операций, но выразил их в привычной форме «выбрать–откуда–где». Вместо того чтобы думать категориями алгебраических выражений, пользователь описывал результат, который хочет увидеть.
Для бизнеса это означало предсказуемость и скорость работы: один и тот же запрос можно повторять, отдавать коллегам, встраивать в отчёты. Пользователям было важно, чтобы язык:
Именно эти ожидания — читаемость, переносимость запросов между командами и минимальный порог входа — сильно повлияли на то, каким SQL стал в реальных организациях.
Декларативность — один из тех выборов раннего SQL, который сделал язык пригодным для больших организаций. Пользователь формулирует что именно нужно получить из данных, а не расписывает как это вычислять шаг за шагом.
В процедурном подходе запрос легко превращается в мини-программу: где пройти циклом, в каком порядке фильтровать, как хранить промежуточные результаты. SQL же описывает результат: откуда берём строки, какие условия применяем, как агрегируем.
SELECT department_id, COUNT(*) AS employees
FROM employees
WHERE status = 'active'
GROUP BY department_id;
В этом запросе нет указаний, какой индекс использовать или в каком порядке читать таблицу — только логика результата.
Декларативные запросы обычно короче, ближе к бизнес-формулировкам и легче читаются коллегами. Это снижает зависимость команды от «авторов-героев», которые помнят все оптимизационные хитрости. Когда сотрудник уходит или проект переезжает между командами, важнее сохранить смысл вычислений, чем набор низкоуровневых приёмов.
Кроме того, декларативность помогает строить единые практики: ревью SQL сводится к проверке корректности условий и соответствия бизнес-правилам, а не к спору о том, «как быстрее пройтись по данным».
Раз запрос не фиксирует способ выполнения, СУБД может выбирать план под текущие объёмы, индексы и статистику: перестраивать порядок соединений, проталкивать фильтры, менять стратегии агрегации. Один и тот же SQL переживает рост данных и изменения схемы заметно спокойнее.
Цена удобства — необходимость явно задавать намерения. Если забыть условие соединения, можно получить «взрыв» строк (декартово произведение). Если не указать порядок сортировки, нельзя полагаться на «как получилось». А при неоднозначной логике фильтрации легко пропустить крайние случаи — запрос останется валидным, но даст другой смысл. Декларативность не про магию, а про дисциплину формулировок.
Нормализация делает схемы чище: меньше повторов, проще обновления, понятнее ответственность каждой таблицы. Но «разнесённые» данные нужно снова собирать в отчётах и экранах приложений — и тут JOIN стал ключевым практичным решением раннего SQL.
В реальной организации данные о клиентах, заказах и оплатах почти всегда живут отдельно. Это не прихоть архитектора, а способ избежать расхождений: имя клиента меняется в одном месте, а не в десяти копиях.
JOIN позволяет собрать «целую картину» на чтение, не разрушая аккуратную структуру хранения.
Соединение опирается на связи «ключ–ссылка»: обычно это первичный ключ в одной таблице и внешний ключ в другой. По смыслу JOIN отвечает на простой вопрос: какие строки относятся друг к другу.
Минимальный шаблон выглядит так:
SELECT o.id, c.name
FROM orders o
JOIN customers c ON c.id = o.customer_id;
Алиасы (o, c) делают запросы короче и уменьшают риск перепутать столбцы.
Самая частая проблема — неправильная кардинальность связи. Если вы думаете, что связь «один-к-одному», а она «один-ко-многим», JOIN умножит строки и отчёт внезапно «раздуется».
Другой класс ошибок — неверное условие соединения (или его отсутствие). Тогда возникает декартово произведение: каждая строка первой таблицы соединяется с каждой строкой второй.
... ON ... и избегайте «скрытых» соединений в WHERE.COUNT(*)) и сравните ожидания.Подзапросы дали SQL редкую для языков запросов способность мыслить «в два шага», не превращая запрос в программирование с переменными и циклами. Пользователь может сначала определить набор подходящих строк, а затем использовать его как фильтр, источник данных или условие — оставаясь в декларативной модели: «что нужно получить», а не «как пройти по данным».
Классический сценарий — выбрать сотрудников, чьи отделы удовлетворяют условию. Это похоже на рассуждение: «найди отделы X, затем возьми людей из этих отделов». Такой стиль особенно понятен бизнес-пользователям.
SELECT e.*
FROM employees e
WHERE e.dept_id IN (
SELECT d.id
FROM departments d
WHERE d.region = 'West'
);
Коррелированный подзапрос ссылается на строку внешнего запроса и выполняет проверку «для каждой строки». Это удобно для условий вида «показать заказы, где есть хотя бы одна позиция дороже N».
SELECT o.*
FROM orders o
WHERE EXISTS (
SELECT 1
FROM order_items i
WHERE i.order_id = o.id AND i.price > 100
);
Цена — потенциальная нагрузка: наивное выполнение может дать много повторных обращений к таблицам. Оптимизатор часто преобразует такие конструкции в соединения, но рассчитывать на это вслепую не стоит.
Подзапрос бывает читабельнее, когда нужен лишь факт существования (EXISTS) или один агрегированный результат. JOIN обычно лучше, когда надо вернуть поля из связанных таблиц или избежать многократных вычислений.
IN чувствителен к NULL: если подзапрос возвращает NULL среди значений, сравнение может перейти в «неизвестно» и отфильтровать больше строк, чем ожидается. EXISTS надёжнее для проверок наличия, потому что не сравнивает значения напрямую. Для предсказуемости важно помнить про трёхзначную логику и явно обрабатывать NULL (например, через IS NULL / IS NOT NULL).
Корпоративные данные почти никогда не бывают «идеально заполненными». У сотрудника может отсутствовать номер внутреннего телефона, у клиента — дата рождения, у поставки — фактическая дата прибытия (ещё не наступила), а у исторических записей — часть полей потеряна при миграции. Для таких ситуаций раннему SQL нужен был честный маркер «значение неизвестно или неприменимо» — так появился NULL.
NULL — не «пустая строка» и не ноль. Это именно отсутствие значения. Поэтому обычная логика сравнения ломается: выражение NULL = 5 не может быть ни истинным, ни ложным — оно неизвестно.
Отсюда следует трёхзначная логика в SQL: TRUE, FALSE, UNKNOWN. Самое важное проявление — в WHERE: строки проходят фильтр только если условие даёт TRUE. И UNKNOWN ведёт себя как «не пропускать».
-- вернёт 0 строк, потому что сравнение даёт UNKNOWN
SELECT *
FROM employees
WHERE manager_id = NULL;
-- правильный вариант
SELECT *
FROM employees
WHERE manager_id IS NULL;
-- пример с UNKNOWN в фильтре
SELECT *
FROM orders
WHERE shipped_at > '2025-01-01';
-- строки с shipped_at = NULL не попадут в результат
Проверяйте отсутствующие значения только через IS NULL / IS NOT NULL.
Чтобы подставлять значение по умолчанию, используйте COALESCE:
SELECT COALESCE(phone_ext, 'не задан') AS phone_ext
FROM employees;
Осторожнее с NOT IN: если внутри списка (или подзапроса) есть NULL, результат может стать UNKNOWN, и вы получите пустую выборку.
SELECT *
FROM customers
WHERE customer_id NOT IN (SELECT customer_id FROM black_list);
Если black_list.customer_id допускает NULL, лучше использовать NOT EXISTS.
Наконец, агрегаты: COUNT(col) не считает NULL, а COUNT(*) — считает строки. Это удобно, но легко ошибиться при подсчёте «сколько заполнено». Подробнее о практиках запросов см. /blog/sql-query-habits.
Представления (VIEW) — это «виртуальные таблицы», которые выглядят как обычные данные, но на самом деле собираются из запроса. Их сила в том, что они дают единый, понятный интерфейс к данным и позволяют переиспользовать бизнес-логику без копирования запросов по всем отчётам и приложениям.
Как только в организации появляется несколько команд и десятки отчётов, одна и та же логика начинает дублироваться: «выручка по оплаченным заказам», «активные клиенты», «последняя цена». VIEW фиксирует эту логику в одном месте.
Плюс это удобный «контракт»: аналитики и продуктовые команды работают с согласованными полями, а инженеры могут менять внутреннюю схему и связи таблиц, не ломая потребителей — достаточно сохранить структуру представления.
Нормализованные модели часто требуют нескольких JOIN и фильтров, чтобы получить «простой» набор полей. Представление убирает эту сложность с глаз пользователей: вместо пяти таблиц — один понятный объект с говорящими колонками.
Это особенно полезно для новичков и для регулярной отчётности: меньше шансов ошибиться в соединениях, фильтрах и трактовке статусов.
VIEW — не волшебная ускорялка. Обычно это сохранённый запрос, и СУБД всё равно должна его выполнить. Если поверх VIEW строить ещё VIEW, появляются «слои», из‑за которых труднее понять, где именно возникла ошибка или почему запрос стал медленным.
Также сложнее отлаживать: пользователь видит представление, но корень проблемы может быть в исходных таблицах, фильтрах или условиях соединения.
Полезно заранее договориться об именовании (например, vw_sales_daily), описывать назначение и ключевые поля в документации, и иметь «контрольные выборки» — короткие запросы, которые проверяют, что VIEW возвращает ожидаемые значения на известных примерах.
Так представления становятся не только удобством, но и инструментом командной дисциплины: меньше хаоса, больше повторяемости и доверия к данным.
Практичность раннего SQL заключалась не только в том, чтобы «удобно спрашивать данные», но и в том, чтобы безопасно делиться ими внутри компании. Как только база данных становится общей для бухгалтерии, продаж, аналитики и поддержки, вопрос доступа превращается в ежедневную операционную задачу. Поэтому механизмы безопасности в SQL — это не декоративное дополнение, а часть жизнеспособности решения.
Идея прав доступа на уровне объектов (таблиц и представлений) помогает выстроить понятное разделение ролей: одни команды отвечают за хранение и качество данных, другие — только читают нужные срезы, третьи — обновляют ограниченный набор полей. Это снижает риск случайных изменений и упрощает контроль: «кто может читать», «кто может менять», «кто может удалять».
На практике это выражается в простых действиях админа или владельца схемы:
GRANT SELECT ON orders TO analyst_role;
GRANT SELECT, UPDATE ON customers TO crm_role;
REVOKE DELETE ON customers FROM crm_role;
Представления (VIEW) особенно полезны, когда людям нужно работать с данными, но не видеть лишнего. VIEW может скрыть чувствительные столбцы (например, персональные контакты) и одновременно ограничить строки (например, только «свои» регионы или только активные договоры). Пользователь получает простую «витрину», а исходные таблицы остаются закрытыми.
Важно, что VIEW поддерживает управляемость: меняется внутренняя структура таблиц — можно сохранить прежний интерфейс доступа через представление и не ломать отчёты.
Хорошая практика в компаниях обычно сводится к нескольким правилам: использовать роли вместо раздачи прав отдельным людям; придерживаться принципа минимальных прав (только то, что нужно для работы); регулярно пересматривать доступы при смене задач; вести аудит изменений схемы и критичных операций. В сумме это делает SQL-среду предсказуемой: меньше инцидентов, проще расследования и понятнее ответственность.
Ранний SQL задумывался не как академическое упражнение, а как инструмент для компаний, где базы работали на ограниченных по памяти и диску машинах, а отчёты нужно было получать «здесь и сейчас». Поэтому удобство декларативного запроса быстро столкнулось с вопросом: как сделать так, чтобы один и тот же SELECT выполнялся приемлемо и на небольшом наборе данных, и после многократного роста таблиц.
Ключевой ход — декларативность. Пользователь описывает что нужно получить, а не как это вычислять. Благодаря этому оптимизатор может выбирать план выполнения: менять порядок соединений, использовать индексы, перестраивать вычисления агрегаций.
Предсказуемый набор операций (сканирование, фильтрация, соединение, группировка) создаёт «пространство» для оптимизации. Если бы запрос был набором пошаговых инструкций, у системы было бы меньше свободы для улучшений.
Оптимизация в повседневной работе сводится к нескольким рычагам:
GROUP BY и DISTINCT могут становиться узким местом; иногда выгодно агрегировать раньше, иногда — после соединения, в зависимости от кардинальности.Важно, что эти решения не видны в тексте запроса, но радикально меняют скорость.
Для команд полезно смотреть сразу на несколько показателей: время выполнения, план запроса (и как он меняется со временем), а также стабильность при росте данных. Запрос, который сегодня работает за секунды, может «поплыть» после увеличения таблиц в 10 раз — и именно здесь связка декларативного SQL и оптимизатора превращает удобство языка в масштабируемость.
Когда SQL из исследовательского проекта превратился в рабочий инструмент компаний, выяснилось простое: язык — это не только синтаксис, но и «социальный контракт». Чем больше людей могут читать и писать запросы одинаково, тем дешевле обучение, проще найм и легче обмен опытом между командами.
Стандартный SQL уменьшает зависимость от конкретного вендора и конкретных людей.
Даже при наличии ANSI/ISO SQL реальность остаётся такой: разные СУБД поддерживают стандарт неравномерно, а ещё добавляют расширения (свои типы данных, функции, синтаксический сахар). Итог — запрос «по учебнику» обычно работает везде, а запрос «максимально удобный» может внезапно стать непереносимым.
Практичный подход: считать стандарт базовым уровнем, а расширения — осознанным выбором, который нужно оправдать выгодой (скорость, функциональность, стоимость владения).
Сведите «особенности платформы» к контролируемому минимуму:
Полезно иметь короткий внутренний документ: какой диалект SQL считается основным, какие функции разрешены, как оформляются имена, где обязательны комментарии. Это может быть страница в базе знаний и чек-лист для ревью (например, /blog/sql-style-guide). Тогда стандарт становится не абстракцией, а рабочим соглашением команды.
Ранний SQL ценили не за «идеальность», а за то, что он помогал людям в организациях быстро получать ответы из данных. Этот прагматичный подход (к которому приложил руку и Рэймонд Бойс) хорошо переносится в современную работу: выигрывают не самые хитрые запросы, а те, которые легко читать, проверять и сопровождать.
Во‑первых — простота и предсказуемость. Договоритесь, что запрос должен быть понятен человеку, который видит его впервые: явные JOIN, осмысленные имена алиасов, одинаковый порядок полей.
Во‑вторых — инструменты важнее героизма. Лучше иметь набор повторяемых практик (шаблоны, тесты, ревью), чем полагаться на «звёздного» автора одного большого запроса.
В‑третьих — скорость цикла «идея → прототип → проверка». Например, когда команда собирает внутренний сервис поверх данных (витрины, отчёты, формы), удобно использовать подходы, которые уменьшают время на рутину. В TakProsto.AI можно в формате чата собрать прототип веб‑ или серверного приложения, быстро оформить модели данных и типовые выборки, а затем выгрузить исходники и доработать их под ваш стандарт SQL и требования к безопасности.
Отдельно это полезно для дисциплины изменений: снапшоты, откат и «planning mode» помогают сначала согласовать структуру и логику запросов, а уже потом вносить правки — ровно тот тип предсказуемости, за который организации ценили SQL с первых лет.
SELECT * в прод‑отчётах.«Магические» запросы, которые работают только «потому что так исторически сложилось»: скрытые допущения (например, «в таблице всегда одна запись на клиента»), неявные преобразования типов (строка в число), фильтрация в WHERE вместо условий в JOIN, неочевидная логика с NULL (когда результат меняется от появления пропусков).
Инвестируйте в обучение аналитиков не только синтаксису, но и мышлению: как читать план выполнения, как оценивать кардинальность, как интерпретировать NULL.
Соберите библиотеку примеров (лучшие практики JOIN, шаблоны витрин, типовые проверки качества) и поддерживайте внутреннюю базу знаний с решениями частых ошибок. Такой «прагматичный SQL» масштабируется вместе с командой — так же, как когда-то масштабировалась сама идея языка запросов.
SQL встраивался в организационные процессы, где важны скорость внедрения, обучение и долгосрочная поддержка. Поэтому выиграли решения, которые:
NULL);GRANT/REVOKE);Декларативный запрос описывает результат, а не алгоритм. Практические плюсы:
JOIN нужен, потому что в нормализованных схемах данные разнесены по таблицам, а в отчётах их нужно собрать обратно.
Базовый шаблон:
SELECT o.id, c.name
FROM orders o
JOIN customers c ON c.id = o.customer_id;
Всегда соединяйте по ключам (PK/FK), а не по текстовым «похожим» полям.
Самые частые причины:
ON → лишние совпадения;Практика: сначала сравните COUNT(*) до/после JOIN и проверьте уникальность ключей, на которые опираетесь.
Подзапрос удобен, когда мыслите «в два шага»: сначала находите набор, потом фильтруете по нему. JOIN удобнее, когда нужно вернуть поля из связанной таблицы.
Частые ориентиры:
EXISTS;Если важна производительность, сравните планы выполнения для обоих вариантов.
Потому что NULL означает «значение неизвестно/не применимо», и сравнение с ним даёт UNKNOWN.
Правила:
NOT IN чувствителен к NULL: если в списке/подзапросе есть NULL, результат может стать UNKNOWN, и вы получите пустую выборку.
Надёжная альтернатива:
VIEW помогает зафиксировать повторяемую бизнес-логику и дать командам понятный интерфейс к данным.
Практические сценарии:
Помните: VIEW обычно не ускоряет сам по себе — это сохранённый запрос.
Типовая схема управления доступом:
Примеры:
Сосредоточьтесь на измеряемых и управляемых вещах:
GROUP BY/DISTINCT.Для командной дисциплины полезны чек-листы и правила стиля (например, запрет в прод-отчётах) — см. /blog/sql-style-guide и практики из /blog/sql-query-habits.
col = NULL → используйте col IS NULL;COALESCE(col, '...');WHERE проходят только строки, где условие даёт TRUE (а UNKNOWN отфильтруется).SELECT c.*
FROM customers c
WHERE NOT EXISTS (
SELECT 1
FROM black_list b
WHERE b.customer_id = c.customer_id
);
Дополнительно: следите, допускает ли поле в подзапросе NULL, и фиксируйте это ограничением/проверкой.
GRANT SELECT ON orders TO analyst_role;
REVOKE DELETE ON customers FROM crm_role;
Регулярно пересматривайте доступы при смене задач и команд.
SELECT *