ИИ-аналитика · PRO
SQL с помощью ИИ: как получать рабочие запросы и не пропустить ошибку
· 13 мин чтения · Редакция ultrathink
Писать SQL с помощью ChatGPT или другой языковой модели можно и нужно, но запрос, который выполнился без ошибки, ещё не значит, что он посчитал правильно. Модель хорошо пишет синтаксис и плохо знает ваши данные. Поэтому результат зависит от двух вещей: насколько точно вы описали задачу и насколько системно проверяете то, что получили.
Ниже — рабочий процесс, которым пользуются аналитики, когда пишут запросы вместе с моделью: как дать контекст, как собирать запрос по шагам, какие ошибки встречаются чаще всего и как поймать каждую из них до того, как цифра попадёт в отчёт. Все примеры построены на условной схеме и служат только иллюстрацией.
Как поставить задачу модели
Модель не видит вашу базу. Всё, чего нет в вашем сообщении, она додумает — и додумает правдоподобно. Названия таблиц, смысл статусов, валюта суммы, часовой пояс дат — если вы это не указали, модель выберет самый вероятный вариант из общего опыта, а не тот, что принят у вас.
Контекст: диалект и схема
- 01Укажите диалект: PostgreSQL, MySQL, ClickHouse, MS SQL и другие СУБД по-разному работают с датами, строками и оконными функциями.
- 02Передайте схему: названия таблиц, поля, типы, первичные и внешние ключи.
- 03Опишите связи словами: у одного заказа может быть несколько платежей, у клиента — несколько заказов.
- 04Объясните смысл неочевидных полей: статусы, флаги, коды, валюты, часовой пояс.
- 05Перечислите фильтры, которые применяются всегда: тестовые записи, внутренние клиенты, отменённые операции.
Определение метрики словами
До того как просить запрос, запишите метрику так, как вы объяснили бы её новому коллеге. Не «выручка за месяц», а «сумма оплаченных заказов без учёта возвратов, по дате оплаты, в местном времени, без тестовых клиентов». Такое определение полезно не только модели: оно заставляет вас самого ответить на вопросы, которые иначе всплыли бы уже после отправки отчёта.
Гранулярность результата
Опишите ожидаемый результат: какие колонки и что означает одна строка. Фраза «одна строка на клиента в месяц» сразу задаёт гранулярность и даёт простой тест: если в результате один клиент встретился в одном месяце дважды, где-то ошибка. Без такой фразы модель может вернуть вполне разумную на вид таблицу с другим уровнем детализации.
Пошаговый процесс: от вопроса до проверенной цифры
Надёжный результат получается не из одного удачного ответа модели, а из процесса, где каждый шаг проверяется. Порядок ниже кажется длинным, но большинство шагов занимает минуты, а сэкономить он может дни разбирательств, почему отчёт не сходится.
- 01Сформулируйте вопрос бизнеса и метрику словами, согласуйте определение с тем, кто будет пользоваться цифрой.
- 02Соберите контекст: диалект, схема, связи, смысл полей, обязательные фильтры.
- 03Попросите модель сначала описать план запроса по шагам, без кода — так видно, правильно ли она поняла задачу.
- 04Попросите код, разбитый на шаги через CTE, с комментарием к каждому шагу.
- 05Выполните шаги по одному, проверяя количество строк, уникальность ключей и суммы.
- 06Сверьте итог с независимым источником или ручным подсчётом на маленьком отрезке.
- 07Проверьте крайние случаи: пустые периоды, объекты без связанных записей, NULL.
- 08Сохраните финальный запрос вместе с определением метрики и выполненными проверками.
Шаг с планом без кода особенно полезен. Если модель пишет «сначала соединяем заказы с платежами, потом суммируем сумму заказа», вы ещё до запуска видите проблему: сумма заказа размножится на число платежей. Поправить план проще, чем найти ошибку в готовом запросе.
Джойны и размножение строк
Самая частая и самая опасная ошибка — джойн, который тихо размножает строки. Запрос выполняется, цифра выглядит разумно, и ошибка уходит в отчёт. Модель допускает её регулярно, потому что не знает, какое отношение между таблицами у вас на самом деле.
Как это выглядит
Возьмём условную схему из примера выше. Допустим, заказ оплачен двумя частями. Если соединить orders с payments и посчитать сумму поля заказа, этот заказ попадёт в сумму дважды. Количество заказов через COUNT(*) тоже завысится. При этом запрос формально верен и ошибок не выдаёт.
Как избежать
- Перед каждым джойном определите отношение: один к одному, один ко многим, многие ко многим.
- Если отношение «ко многим», сначала агрегируйте вторую таблицу до уровня первой, затем присоединяйте.
- Считайте метрику на той таблице, где она живёт: сумму платежей — по платежам, число заказов — по заказам.
- Попросите модель явно указать гранулярность каждого промежуточного шага.
- Будьте осторожны с LEFT JOIN и последующим фильтром по правой таблице в WHERE: он превращает соединение во внутреннее.
Как поймать
Посчитайте количество строк и количество уникальных ключей до джойна и после. Если ключ основной таблицы должен остаться уникальным, а уникальных значений стало меньше, чем строк, — строки размножились. Такую проверку стоит делать после каждого джойна, а не только в конце.
Дубли, NULL и агрегации
Дубли бывают не только от джойнов. В исходных таблицах встречаются повторные загрузки, тестовые записи, отменённые и восстановленные операции, несколько версий одной записи. Модель не знает об этом, пока вы не скажете, поэтому правила исключения нужно сформулировать явно и проверить, что они действительно применились.
Ловушки NULL
NULL ведёт себя не так, как подсказывает интуиция. Это не ноль и не пустая строка, а отсутствие значения, и любое сравнение с ним даёт неопределённый результат. Модели обычно знают это правило, но применяют его не всегда — особенно когда бизнес-смысл пустого значения не объяснён.
- Фильтр status <> 'cancelled' выбросит строки, где статус пустой, — хотя их, возможно, нужно было оставить.
- COUNT(*) и COUNT(поле) дают разные числа, если в поле есть NULL.
- AVG считает среднее только по заполненным значениям; среднее с заменой NULL на ноль — другая метрика.
- Сумма столбца, где все значения пустые, вернёт NULL, а не ноль, и это может сломать дальнейшие расчёты.
- Условие NOT IN со списком, в котором есть NULL, может не вернуть ни одной строки.
- Деление на ноль или на NULL в долях даёт ошибку или пустой результат в зависимости от СУБД.
COUNT DISTINCT как маскировка
Если цифры завышены, модель часто предлагает добавить DISTINCT. Иногда это правильно, но нередко DISTINCT просто прячет размножение строк в одном месте, оставляя его в другом: количество заказов становится верным, а сумма — по-прежнему завышенной. Прежде чем соглашаться на DISTINCT, выясните, откуда взялись повторы.
Даты, периоды и часовые пояса
Границы периода — источник тихих расхождений, которые трудно заметить глазами. Отчёт за месяц может отличаться от учётной системы на небольшую величину, и никто не поймёт почему, пока не разберёт по дням.
- Условие «меньше или равно последнему дню месяца» для поля с датой и временем потеряет все события этого дня после полуночи.
- Надёжнее полуоткрытый интервал: от первого дня месяца включительно до первого дня следующего месяца не включительно.
- Если даты хранятся в UTC, а отчёт нужен по местному времени, граница дня сдвигается, и часть событий переходит в соседний день или месяц.
- Функция усечения даты до месяца или недели работает в часовом поясе сессии, если не указать его явно.
- Неделя в разных СУБД и настройках может начинаться с разного дня.
- «Прошлый месяц» относительно текущей даты зависит от того, когда запрос запущен, — для регулярных отчётов лучше передавать даты явно.
Ещё одна частая неоднозначность — какую дату брать. У заказа есть дата создания, оплаты, отгрузки и возврата. Выручка по дате создания и по дате оплаты — разные цифры, и обе могут быть правильными. Укажите модели нужную дату явно и запишите это в определение метрики.
Сложные запросы: собирайте по шагам
Чем длиннее запрос, тем выше шанс, что ошибка спрячется в середине. Вместо одного огромного запроса попросите модель разбить его на шаги через CTE — общие табличные выражения — и проверяйте каждый шаг отдельно: сколько в нём строк, какая гранулярность, совпадают ли суммы с предыдущим шагом.
Оконные функции
Ранжирование, накопительные итоги, сравнение с предыдущим периодом зависят от того, как задано разбиение и сортировка. Если забыть разбиение по клиенту, накопительная сумма посчитается по всей таблице, и результат будет выглядеть вполне разумно. Если сортировка не однозначна, например несколько событий в одну секунду, порядок может меняться от запуска к запуску.
- Проверяйте PARTITION BY: соответствует ли он сущности, по которой нужен расчёт.
- Проверяйте ORDER BY: однозначен ли порядок, нет ли одинаковых значений.
- Проверяйте рамку окна для накопительных и скользящих расчётов.
- Для «первого» или «последнего» события уточните, что делать при равенстве дат.
Рабочий приём
Просите комментарий к каждому шагу: что он делает и что означает одна строка. Запускайте шаги по одному на небольшом отрезке данных, например за один день или для одного клиента, где результат можно проверить глазами. Сохраняйте финальную версию вместе с промежуточными проверками: проверенные шаги можно переиспользовать, а модель получает их как готовый контекст для следующих задач.
Сверка с источником и контрольные суммы
Главная проверка — сравнить результат с независимым источником. Это может быть отчёт из учётной системы, выгрузка, которой уже доверяют, или ручной подсчёт на маленьком отрезке. Если итог за месяц совпадает с бухгалтерией, а разбивка по дням в сумме даёт тот же итог, запросу уже можно доверять гораздо больше.
- 01Посчитайте общий итог без группировки и сравните с суммой по группам.
- 02Возьмите один конкретный объект — клиента, заказ, день — и проверьте его вручную по сырым данным.
- 03Сравните с известным числом из другой системы.
- 04Проверьте крайние случаи: пустой период, клиент без заказов, заказ без платежа, платёж без заказа.
- 05Сравните результат с прошлой версией отчёта, если она есть, и объясните каждое расхождение.
- 06Попросите модель объяснить запрос по шагам и сравните объяснение со своей задачей.
Полезный приём — попросить модель саму придумать, как её запрос может ошибиться, и написать проверочные запросы. Она хорошо перечисляет типовые риски, а выполнять проверки всё равно будете вы, на реальных данных.
Типичные ошибки и как поймать каждую
Большинство ошибок в запросах, написанных с помощью модели, повторяются. Удобно держать перед глазами короткий список: что может пойти не так и какой проверкой это ловится.
- Выдуманные таблицы или поля — передавайте схему целиком; если запрос упал с ошибкой «нет такого поля», не позволяйте модели угадывать замену.
- Размножение строк в джойне — сравнение COUNT(*) и COUNT(DISTINCT ключ) после каждого соединения.
- Потеря строк во внутреннем джойне — сравнение числа строк основной таблицы до и после.
- Неверная обработка NULL — отдельный подсчёт строк с пустыми значениями в ключевых полях.
- Сдвиг границ периода — сверка дневной разбивки с источником на стыке двух дней или месяцев.
- Подмена определения метрики — сравнение объяснения модели с записанным определением.
- Неверная дата — проверка, по какому полю идёт фильтр и группировка.
- Забытые фильтры по умолчанию — проверка, что тестовые и внутренние записи не попали в результат.
- Неоднозначная сортировка в окне — повторный запуск и сравнение результатов.
- Арифметика в тексте ответа — итоговые числа берите только из результата запроса, а не из пояснения модели.
Когда просить модель, а когда писать самому
Модель не обязательно использовать для каждого запроса. Решение зависит от того, насколько легко проверить результат и насколько дорога ошибка.
- Простой запрос к знакомым таблицам — модель экономит время на наборе, проверка занимает минуты.
- Незнакомый синтаксис или функция другой СУБД — модель полезна как справочник, но проверяйте поведение на тестовых данных.
- Сложная логика с несколькими джойнами и окнами — просите план и шаги, а не готовый запрос целиком.
- Финансовые и регуляторные цифры — модель может написать черновик, но проверка обязательна, а запрос должен пройти ревью коллеги.
- Разовый исследовательский вопрос — допустима быстрая версия, если вывод помечен как предварительный.
Ревью коллеги остаётся полезным и тогда, когда запрос написан с помощью модели. Покажите ему не только код, но и определение метрики, план и результаты проверок: так он оценивает не синтаксис, а логику. Хороший вопрос на ревью — «что здесь означает одна строка и почему сумма не может задвоиться». Если на него нет быстрого ответа, запрос ещё не готов.
Отдельно — вопрос данных. Перед тем как отправлять модели примеры строк, убедитесь, что это разрешено политикой компании. Для написания запроса почти всегда достаточно схемы и нескольких обезличенных строк; персональные данные клиентов, суммы на счетах и зарплаты во внешний сервис без согласования отправлять нельзя.
Термины
- Гранулярность — что означает одна строка результата: клиент, заказ, день, клиент в месяц.
- Джойн (JOIN) — соединение таблиц по ключу; внутренний оставляет только совпадения, левый сохраняет все строки основной таблицы.
- Размножение строк — рост числа строк после джойна из-за отношения «один ко многим».
- CTE — общее табличное выражение, именованный промежуточный шаг запроса.
- Оконная функция — расчёт по группе строк без их схлопывания: ранги, накопительные итоги, сравнение с предыдущей строкой.
- Полуоткрытый интервал — период, где начало включено, а конец нет.
- Контрольная сумма — итог, который считается двумя независимыми способами и должен совпасть.
Чек-лист перед тем, как отправить цифру
- 01Метрика записана словами и согласована с тем, кто будет её использовать.
- 02Модели переданы диалект, схема, связи и смысл неочевидных полей.
- 03Гранулярность результата описана и проверена на уникальность.
- 04Модель сначала описала план, и он соответствует задаче.
- 05Запрос разбит на шаги, каждый шаг выполнен и проверен.
- 06Каждый джойн проверен на размножение и потерю строк.
- 07Правила исключения дублей, тестовых и отменённых записей явные и применились.
- 08Обработка NULL соответствует бизнес-смыслу метрики.
- 09Границы периода — полуоткрытый интервал, часовой пояс и нужная дата учтены.
- 10Оконные функции проверены на разбиение, сортировку и рамку.
- 11Итог сверен с независимым источником, крайние случаи проверены.
- 12Итоговые числа взяты из результата запроса, а не из текста модели.
- 13Запрос сохранён вместе с определением метрики и проверками.
Итог
ИИ снимает с аналитика необходимость помнить синтаксис и заметно ускоряет написание запросов. Но правильность цифры по-прежнему обеспечивает человек: точное определение метрики, контроль гранулярности, проверка джойнов, NULL и периодов, сверка с источником. Сделайте эти проверки привычкой — и модель станет надёжным инструментом, а не источником красивых ошибок.
Частые вопросы
Синтаксису — чаще всего да, логике — только после проверки. Запрос может выполниться без ошибок и при этом посчитать неправильно из-за джойна, NULL или границ периода.
Диалект СУБД, схему таблиц с ключами, связи между таблицами, смысл неочевидных полей, обязательные фильтры, определение метрики и ожидаемую гранулярность результата.
Сравните количество строк и количество уникальных ключей до и после джойна. Если они расходятся там, где отношение должно быть один к одному, есть размножение.
Чаще всего из-за границ периода, часового пояса, выбора даты (создания или оплаты) или разных правил исключения записей. Сверьте дневную разбивку и определение метрики.
Только если это разрешено политикой компании. Для написания запроса обычно достаточно схемы и нескольких обезличенных строк.
Да, на уровне чтения и понимания. Без этого невозможно заметить логическую ошибку в запросе, который выглядит правильным.