Большая часть SQL, который генерируют LLM, не падает. Он выполняется. Возвращает строки. И в этом — главная проблема.
Когда AI пишет код, который не компилируется, вы это сразу видите. Когда AI пишет SQL, который возвращает 1000 строк вместо правильных 1043 — никто этого не замечает до тех пор, пока кто-то не сравнит цифры с другим источником.
Пять типов правдоподобно-неправильного SQL
1. Тихие JOIN-ы
LLM знает, что таблицы orders и customers связаны. Спросишь его про «orders by customer name» — он напишет INNER JOIN, и всё выглядит хорошо. Но в данных есть orders с customer_id = NULL (гость-заказы, легаси-импорт). INNER JOIN молча их выбросит. Вы получаете цифру «всех заказов», которая отличается от настоящей на 4%.
Правильный SQL должен быть LEFT JOIN либо явно отфильтрованный inner. LLM по умолчанию выбирает INNER, потому что в учебных примерах он чаще встречается.
2. Неправильные time-buckets
«Покажи продажи по неделям за последний год.» LLM генерирует GROUP BY DATE_TRUNC('week', sale_date). Это работает, но:
-
DATE_TRUNC('week', …)в Postgres начинает неделю с понедельника. В вашей компании — с воскресенья. -
sale_dateхранится в UTC, а отчёт нужен в локальной timezone — заказы после 22:00 каждый день уезжают в следующий день. -
Граница года: первая «неделя» 2026-го может включать дни декабря 2025-го.
Каждый из этих багов даёт график, который выглядит правдоподобно, но смещён.
3. Aggregations поверх дубликатов
SELECT customer_id, SUM(amount) FROM orders JOIN order_items … без понимания структуры связи. Если у одного order несколько items, SUM(amount) посчитает order-level amount несколько раз. Цифры умножаются.
LLM не знает кардинальность ваших таблиц. Он угадывает из имён. И часто угадывает неверно.
4. Игнорирование soft-delete
В вашей схеме у users есть колонка deleted_at. Всем известно, что для активных юзеров нужен WHERE deleted_at IS NULL. LLM этого не знает, если ему не сказать. Запросы возвращают активных + удалённых, ваши метрики растут «на ровном месте».
5. Subtle window-function ошибки
RANK() vs DENSE_RANK() vs ROW_NUMBER() — три разных функции, которые в коротких примерах выглядят как одно и то же. LLM иногда выбирает RANK() там, где смысл — ROW_NUMBER(), и при ничьих ваши топ-10 превращаются в топ-15.
Почему это происходит
LLM учились на publicly visible SQL — то есть туториалы, Stack Overflow, документация. В этом корпусе:
-
Схемы простые, без soft-delete и legacy-полей.
-
JOIN-ы всегда INNER, потому что туториалы не показывают edge cases.
-
Timezones явно не упоминаются.
-
Дубликаты в данных не считаются — потому что в туториалах их нет.
LLM не знает ваши данные. Он знает «типичный SQL», и его типичный SQL — это SQL для несуществующей идеальной схемы.
Что с этим делать архитектурно
1. Source of truth — не AI
Важные метрики (revenue, MAU, churn) должны вычисляться через curated queries, ревьюенные дата-инженерами. LLM-сгенерированный SQL можно использовать для эксплоратики и one-off вопросов, но не для production-дашбордов.
2. Тесты с известным ответом
Для каждого «продакшен-запроса» должна быть пара (запрос, известный правильный результат на тестовых данных). Если AI потом перепишет запрос, тест поймает регрессию.
3. Контекст в промпте
Если вы всё-таки даёте AI генерировать SQL, передавайте ему:
-
схему таблиц с комментариями (soft-delete, nullability, кардинальность);
-
примеры известно-правильных запросов схожего типа;
-
список «грязных» полей (legacy, deprecated).
Это отнимает работу до запроса, но снижает галлюцинации в разы.
4. Read replica + slow query log
LLM-сгенерированные запросы часто неэффективны (full scans, бессмысленные подзапросы). Прогоняйте их через read-replica, а не главную БД, и логируйте всё дольше 1 секунды для ревью.
5. UI, который показывает «AI guessed»
Каждый SQL, сгенерированный AI, должен быть помечен в UI и недоступен для запуска одной кнопкой «execute». Минимальный friction: explain plan + diff с похожим curated-запросом перед запуском.
Главное
LLM не делают явно плохой SQL — они делают правдоподобно плохой SQL. Это хуже: ошибка диффундирует в данные, в отчёты, в принимаемые решения. Чтобы безопасно использовать AI с БД, нужно встроить процесс верификации между генерацией и production-использованием. Иначе ваш data-driven становится LLM-driven, что не одно и то же.