SQL не стал проще за последние 20 лет. JOIN всё так же путают с LEFT JOIN, подзапросы плодят тормоза на проде, а чужая схема из сорока таблиц вызывает желание закрыть ноутбук и пойти за кофе. Но появился инструмент, который меняет правила игры. Нет, он не пишет SQL за вас с закрытыми глазами. Но он сокращает путь от «мне нужны данные о клиентах, которые…» до работающего запроса с EXPLAIN — c часов до минут.
В этом посте — не теория про «AI убьёт профессию», а конкретные промпты и pipeline. Если вам интересно, как LLM справляется с требованиями в целом — загляните в разбор AI для системного анализа, там про полный цикл. А здесь — только про данные.
Почему аналитику нужен AI для SQL
Формально SQL — это навык, который есть в любой вакансии аналитика. На практике — это боль. Потому что настоящее владение SQL начинается не с SELECT * FROM, а с момента, когда вы лезете в пятиэтажный запрос коллеги, написанный три года назад, с тремя вложенными подзапросами и комментарием «потом поправим» (не поправили, конечно).
Проблема распадается на три части:
- Написание нового запроса. Вы знаете, какие данные нужны, но не помните точные названия полей, не уверены, какой JOIN даст правильный результат, и вообще — может, оконная функция будет быстрее подзапроса?
- Разбор чужого запроса. Коллега уволился, а его
WITH RECURSIVEна 60 строк висит в проде как чёрный ящик. Вы не знаете, что он считает, правильно ли считает и зачем там пятый JOIN к той же таблице. - Проектирование схемы. Новая фича требует три таблицы, но как их связать? Какие индексы повесить? Что будет с производительностью на миллионе строк?
LLM закрывает все три направления. Не идеально, но с результатом, который уже можно показывать на код-ревью, а не прятать в корзину.
Pipeline: от вопроса к готовому запросу
Процесс работы с LLM для SQL выглядит как цикл, а не как одна команда. Вы даёте модели контекст (схему, пример данных, бизнес-вопрос), она выдаёт запрос и план выполнения, вы проверяете результат и при необходимости идёте на второй круг.
На входе — два потока: бизнес-вопрос аналитика (жёлтый, слева) и метаданные схемы БД (фиолетовый). Модель (синий) получает и то, и другое, после чего параллельно выдаёт SQL-запрос и EXPLAIN (зелёные). Оба результата идут на валидацию аналитиком (жёлтый), откуда либо уходят в готовый результат (фиолетовый), либо — если что-то не так — возвращаются на доработку (пунктирная связь). Цикл короткий: обычно одного-двух проходов хватает.
Примечательно, что эта схема работает не только для SELECT, но и для DDL — создания таблиц, индексов и ограничений. Достаточно заменить бизнес-вопрос на «нужна схема для хранения заказов с товарами и статусами».
Часть 1. Пишем SELECT: от вопроса до результата
Шаг 1. Дайте модели схему
Самая частая ошибка — кинуть модели голый вопрос «напиши запрос, который покажет клиентов без заказов за последний месяц» и удивиться, что она выдумала названия таблиц. LLM не знает вашу схему. Чем подробнее вы её опишете, тем точнее будет результат.
Формат не важен: можно копипастой DDL, можно текстовым описанием. Главное — указать названия таблиц, ключевые поля, связи между ними и типы данных (хотя бы примерно). Вот минимальный жизнеспособный контекст:
Таблицы:
1. customers (клиенты): - id (PK, integer) - name (text) - email (text, unique) - created_at (timestamp) - status (text: 'active', 'inactive', 'blocked')
2. orders (заказы): - id (PK, integer) - customer_id (FK → customers.id) - order_date (timestamp) - total_amount (numeric) - status (text: 'new', 'processing', 'completed', 'cancelled')
3. order_items (позиции заказа): - id (PK, integer) - order_id (FK → orders.id) - product_id (FK → products.id) - quantity (integer) - price (numeric)
4. products (товары): - id (PK, integer) - name (text) - category_id (FK → categories.id) - price (numeric) - stock_quantity (integer)
Связи:customers.id ← orders.customer_idorders.id ← order_items.order_idproducts.id ← order_items.product_idСхема намеренно учебная. На реальном проекте таблиц будет не четыре, а сорок — и это только те, что относятся к вашему bounded context. Здесь важно дать модели ровно то, что релевантно задаче, а не вывалить весь pg_dump.
Шаг 2. Формулируйте вопрос как бизнес-требование
Не «напиши LEFT JOIN», а «мне нужен список клиентов, которые не делали заказов за последние 30 дней». Почему? Модель лучше понимает бизнес-логику, чем синтаксические конструкции. Она сама решит, какой JOIN уместнее, и объяснит почему.
Промпт:
Ты — SQL-разработчик. Ниже — схема БД интернет-магазина.Напиши SQL-запрос (PostgreSQL) для следующей задачи:
«Получить список клиентов (id, имя, email), которые не делали ни одногозаказа за последние 30 дней. Учитывать только активных клиентов(status = 'active'). Отсортировать по дате создания клиента по убыванию.»
Требования к запросу:1. Используй EXPLAIN (ANALYZE не нужен — запрос ещё не запущен).2. Предложи индексы, которые ускорят выполнение, если их нет.3. Объясни, почему выбран именно такой план выполнения, а не альтернативный.4. Если запрос может дать неочевидный результат при определённых данных — предупреди об этом.
Схема БД:[описание схемы из шага 1]Что получаем на выходе? Модель выдаёт запрос, EXPLAIN, предложения по индексам и комментарий. Обычно качество такое, что после одной-двух правок запрос идёт в работу. Не всегда идеально — но всегда лучше, чем писать с нуля и гуглить синтаксис NOT EXISTS vs LEFT JOIN ... IS NULL, попутно отвлекаясь на Slack.
Шаг 3. Итерация: докручиваем промпт
Первый результат часто компилируется, но не попадает в точность. Модель может перестраховаться и использовать NOT IN вместо NOT EXISTS (что на NULL-ах даст сюрприз), или предложить индекс, который вы уже создали. Второй заход обычно закрывает все вопросы — и в этом главная магия: итерация занимает 30 секунд, а не полчаса гугления.
Промпт для второго раунда:
Запрос работает, но есть нюанс:
1. Клиентская база — 2 млн записей, заказов — 15 млн.2. Поле email может быть NULL для гостевых аккаунтов (хотя в схеме указано unique — это исторический баг, который никто не трогает, потому что «работает — не трогай»).3. Индекс на orders.customer_id уже есть.4. Запрос будет запускаться ежедневно в 3 утра по cron.
С учётом этого — перепиши запрос и обнови EXPLAIN.На этом этапе модель сама перейдёт с NOT IN на NOT EXISTS (или наоборот, в зависимости от диалекта) и учтёт NULL-ы. Стоит отметить, что такой диалог — это и есть главный юзкейс LLM для SQL: не генерация с первой попытки, а быстрая итеративная доводка.
Часть 2. Разбираем чужой запрос
А вот здесь LLM делает то, на что у человека ушли бы часы. Вы кидаете запрос на 60 строк с WITH RECURSIVE и конструкцией LATERAL JOIN, которую вы видели только в документации PostgreSQL — и получаете построчное объяснение.
Промпт:
Ниже — SQL-запрос, написанный бывшим коллегой. Объясни:
1. Что делает запрос в целом (одно предложение).2. Для каждой CTE, подзапроса и JOIN — объясни, какие данные отбираются и зачем.3. Какие потенциальные проблемы ты видишь: - Производительность (отсутствующие индексы, seq scan на больших таблицах). - Корректность (NULL-ы, дубликаты, потеря строк). - Поддерживаемость (избыточная сложность, неочевидная логика).4. Предложи упрощённую версию, если это возможно без потери логики.
Запрос:[вставить монструозный SQL]Честно: я проделывал это с тремя реальными продакшен-запросами. В двух случаях модель нашла джойн, который ничего не делал — он фильтровал по полю, которое всегда NULL в этом срезе данных. Коллега добавил его «на всякий случай» три года назад, и с тех пор запрос работал медленнее на 40%. Просто потому, что никто не решался его разобрать. (Да, я тоже не решался, пока LLM не разложила всё по полочкам за минуту.)
Часть 3. Проектируем схему с нуля
Самый амбициозный сценарий — не запрос, а целая схема данных. Допустим, вы делаете модуль промокодов для интернет-магазина. Нужны таблицы, связи, индексы и ограничения.
Промпт:
Спроектируй схему БД (PostgreSQL) для модуля промокодов интернет-магазина.Требования:
1. Промокод может быть: - Процентным (скидка 10% на весь заказ). - Фиксированным (скидка 500 ₽). - На бесплатную доставку.
2. Ограничения промокода: - Период действия (дата начала и окончания). - Максимальное количество использований (общее и на одного пользователя). - Минимальная сумма заказа. - Привязка к конкретным категориям товаров или товарам.
3. Промокод может быть одноразовым или многоразовым.
4. Нужно хранить историю применений: кто, когда, к какому заказу, какая скидка получилась в рублях.
Выдай:A) DDL (CREATE TABLE) для всех таблиц.B) Список внешних ключей и ограничений (CHECK, UNIQUE).C) Индексы с обоснованием (почему этот индекс, какой тип).D) ER-диаграмму в виде описания связей.E) Предупреждения: где могут быть проблемы при росте данных, какие ограничения стоит ослабить, если объём вырастет.Модель за минуту выдаёт 4–5 таблиц с внешними ключами, индексами и даже комментариями COMMENT ON COLUMN. Это не замена архитектору БД (нормальные формы модель может нарушить, если явно не попросить их соблюдать), но это фантастическая стартовая точка. То, на что уходит полдня «а давайте подумаем, как это должно выглядеть», превращается в 10 минут правок готового DDL.
Кстати, о промптах: если вы чувствуете, что упираетесь в потолок «модель генерирует поверхностно», значит, пора разобраться в паттернах промпт-инжиниринга — у меня есть отдельный пост про промпт-инжиниринг для системного аналитика, там про то, как выжимать из LLM глубину.
Что LLM делает плохо в SQL (и где всё ещё нужна голова)
Справедливости ради — вот где модель спотыкается:
Специфический диалект. Модель знает PostgreSQL лучше, чем Oracle, и намного лучше, чем ClickHouse. Если вы работаете с экзотической СУБД — проверяйте каждый синтаксический нюанс. LLM может сгенерировать запрос, который в PostgreSQL отработает, а в вашей БД упадёт с загадочной ошибкой на третьей строке.
Бизнес-логика на уровне данных. Модель не знает, что status = 'active' на самом деле не гарантирует, что клиент платёжеспособен — для этого есть отдельное поле credit_check_passed, которое появилось в схеме через год после запуска и лежит в другой таблице. Эти нюансы — ваша зона ответственности.
Реальные объёмы. EXPLAIN от LLM — это теоретический план выполнения. Модель не видит статистику вашей БД: реальное распределение значений, размер таблиц, актуальность ANALYZE. Она может сказать «этот индекс ускорит запрос» — а на деле селективность такая, что seq scan всё равно будет быстрее. EXPLAIN от LLM — отличная гипотеза, но финальную проверку делаете вы на тестовой копии БД.
Транзакции и блокировки. Модель сгенерирует UPDATE и INSERT, но не предупредит, что конкурентное выполнение на продакшене повесит дедлок. Уровень изоляции транзакций, FOR UPDATE, advisory locks — про это LLM нужно спрашивать явно. Сама она в эту сторону не думает.
Таблица: что LLM делает за аналитика в SQL, а что — нет
| Задача | LLM справляется | Нужен аналитик / DBA |
|---|---|---|
| Генерация SELECT по бизнес-вопросу | Хорошо: после 1–2 итераций выдаёт рабочий запрос | Проверить результат на реальных данных |
| Разбор чужого запроса | Отлично: объясняет каждую CTE, находит мёртвые JOIN | Принять решение о рефакторинге |
| Проектирование схемы по требованиям | Хорошо: стартовая DDL с индексами и FK | Докрутить нормальные формы, учёт нагрузки |
| EXPLAIN и индексы | Частично: предлагает разумные индексы, но не видит реальной статистики БД | Проверить на тестовой копии, сравнить планы |
| Оптимизация медленного запроса | Хорошо: находит очевидные проблемы (missing index, seq scan) | Тонкая настройка под конкретные объёмы |
| Учёт NULL-ов и краевых случаев | Средне: нужно явно просить проверять NULL-ы | Держать в голове грязные данные прода |
| Миграции и обратная совместимость | Плохо: модель мыслит «зелёным полем» | Полностью ответственность DBA и разработчика |
| Диалекты редких СУБД | Плохо: может использовать неподдерживаемый синтаксис | Проверять документацию конкретной СУБД |
Это не silver bullet. LLM — это как продвинутый автодополнитель, который иногда умнее вас, а иногда придумывает несуществующий синтаксис с абсолютно уверенным видом. Доверяй, но проверяй.
Реальный кейс: от идеи до отчёта за 15 минут
Был у меня случай. Прилетает задача от маркетинга: «нужен отчёт по клиентам, которые купили товары из категории X, но не покупали из категории Y за последний квартал». Звучит просто, да? На деле — три JOIN, подзапрос с NOT EXISTS и ещё одна таблица с категориями, которая связана с товарами через промежуточную M2M.
Раньше я бы пошёл по такому маршруту: открыл DBeaver, посмотрел схему, вспомнил, что product_categories — это связка many-to-many, написал первый вариант запроса (он, конечно, дал дубликаты), добавил DISTINCT, понял, что DISTINCT маскирует проблему с JOIN, переписал, проверил на тестовых данных… Минут 40 минимум.
С LLM — алгоритм другой. Кидаю схему, кидаю вопрос маркетинга дословно. Модель выдаёт запрос с корректным NOT EXISTS, предлагает индекс, объясняет план. Запускаю на тестовой копии — работает. Одна правка (нужно исключить заблокированных клиентов, а это фильтр по отдельному полю, который я забыл упомянуть в первом промпте) — и через 15 минут отчёт готов.
Не потому что я разучился писать SQL. А потому что я не трачу время на механическую часть: вспомнить синтаксис NOT EXISTS, проверить, что он корректно обрабатывает NULL-ы, и перебрать три варианта JOIN в уме. Модель делает это быстрее.
Заключение
Системный аналитик, который умеет в SQL, — это ожидание. Системный аналитик, который умеет в SQL и LLM, — это преимущество. Разница не в том, что модель пишет запросы за вас, а в том, что она срезает 80% рутины: гугление синтаксиса, подбор индексов наугад, разбор чужих запросов методом «закомментирую этот JOIN и посмотрю, что сломается».
Главный навык здесь — не SQL и не AI. Это умение задавать правильные вопросы: модели — через промпт, и коду — через EXPLAIN. Если вы освоите этот двойной цикл обратной связи, вы перестанете бояться «того самого запроса», который лежит в репозитории три года. Потому что теперь у вас есть инструмент распутать любой клубок SQL-логики. Быстро. И без желания уйти в отпуск после каждого код-ревью.
P.S. Если вы дочитали и думаете «ну, мои запросы и так работают» — отлично. А теперь вспомните запрос, который работает медленно, но «мы к этому привыкли». И скормите его модели. Удивитесь. Я — удивился.