Иллюзия магии рассеивается ровно в тот момент, когда сгенерированный нейросетью SQL-запрос кладет базу данных на лопатки. Маркетологи продают нам кодинг-ассистентов как всезнающих оракулов. Нажал Tab — и сложный аналитический отчет готов. На практике же LLM работает не как опытный DBA, а как очень быстрый, но самоуверенный джуниор. Нейросеть не видит вашу базу данных. Она угадывает структуру таблиц, опираясь на паттерны из обучающей выборки, если вы не скормили ей DDL.
Я решил провести жесткий практический тест. Никаких синтетических SELECT * FROM users. Мы возьмем реальную задачу из финтеха, дадим моделям точный контекст и посмотрим, кто сломается на краевых случаях.
Полигон для испытаний: Схема БД
Для теста я подготовил урезанную, но реалистичную схему биллинга. Пять таблиц, которые хранят историю покупок, транзакций, возвратов и курсов валют.
CREATE TABLE users (
id BIGINT PRIMARY KEY,
email VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE orders (
id BIGINT PRIMARY KEY,
user_id BIGINT REFERENCES users(id),
total_amount DECIMAL(15, 2) NOT NULL,
currency VARCHAR(3) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE transactions (
id BIGINT PRIMARY KEY,
order_id BIGINT REFERENCES orders(id),
status VARCHAR(20) NOT NULL, -- 'pending', 'settled', 'failed'
settled_at TIMESTAMP
);
CREATE TABLE refunds (
id BIGINT PRIMARY KEY,
transaction_id BIGINT REFERENCES transactions(id),
amount DECIMAL(15, 2) NOT NULL,
processed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE exchange_rates (
currency VARCHAR(3) NOT NULL,
rate_to_usd DECIMAL(10, 6) NOT NULL,
actual_date DATE NOT NULL,
PRIMARY KEY (currency, actual_date)
);
Задача (Промпт)
Текст промпта, который я отправлял моделям вместе с DDL:
> "Напиши PostgreSQL запрос, который выведет топ-10 пользователей по чистой выручке (Net Revenue) в долларах (USD) за последний год. Чистая выручка — это сумма успешно завершенных транзакций (status = 'settled') минус сумма возвратов по этим транзакциям. Конвертацию в USD нужно делать по курсу на день создания заказа."
Здесь заложено несколько ловушек:
- Не у каждого заказа есть транзакция (например, отвалились на этапе оплаты).
- Не у каждой транзакции есть возврат (нужен
LEFT JOIN, а неINNER JOIN). - Конвертация валюты требует приведения
created_at(TIMESTAMP) кDATEдля джоина сexchange_rates. - Агрегация сумм с учетом
NULLзначений (если возврата не было, вычитаниеNULLдастNULL, нуженCOALESCE).
Интеграция: 10 моделей за 10 минут через RouterAPI
Проверять это вручную через веб-интерфейсы ChatGPT, Claude и других — пустая трата времени. Плюс, контекст в чатах часто "плывет". Мне нужен был чистый эксперимент.
Я набросал простой скрипт на Go, используя единый шлюз RouterAPI. Этот сервис позволяет дергать десятки разных LLM через один OpenAI-совместимый эндпоинт, просто меняя название модели.
models := []string{
"anthropic/claude-3.5-sonnet",
"openai/gpt-4o",
"deepseek/deepseek-coder",
"meta-llama/llama-3-70b-instruct",
// .. еще 6 моделей
}
for _, model := range models {
go fetchSQL(model, promptWithDDL)
}
Скрипт асинхронно отправил промпт во все 10 моделей. Через 10 минут у меня на диске лежали сырые SQL-запросы, готовые к выполнению на тестовой базе с миллионом сгенерированных строк.
Разбор полетов: GPT-4o против Claude 3.5 Sonnet
Большинство опенсорсных моделей (Llama 3 70B, Qwen) посыпались на базовых вещах: забыли сгруппировать данные или попытались сделать джоин по несуществующим полям. Но настоящая битва развернулась между флагманами.
Попытка GPT-4o
GPT-4o выдал визуально красивый, компактный код. Но при запуске он вернул пустой результат для 90% пользователей.
SELECT
u.id,
u.email,
SUM((o.total_amount - r.amount) * er.rate_to_usd) AS net_revenue_usd
FROM users u
JOIN orders o ON u.id = o.user_id
JOIN transactions t ON o.id = t.order_id
LEFT JOIN refunds r ON t.id = r.transaction_id
JOIN exchange_rates er ON o.currency = er.currency
AND DATE(o.created_at) = er.actual_date
WHERE t.status = 'settled'
AND o.created_at >= NOW - INTERVAL '1 year'
GROUP BY u.id, u.email
ORDER BY net_revenue_usd DESC
LIMIT 10;
Где ошибка? Математика с NULL. GPT-4o правильно использовал LEFT JOIN для таблицы refunds. Но выражение (o.total_amount - r.amount) ломает всю логику. Если возврата не было, r.amount равен NULL. В SQL любое арифметическое действие с NULL возвращает NULL. В итоге функция SUM просто проигнорировала все успешные транзакции без возвратов. Выручка посчиталась только для тех заказов, где был хотя бы частичный возврат. Фатальная ошибка для бизнеса.
Попытка Claude 3.5 Sonnet
Claude подошел к задаче основательнее и использовал CTE (Common Table Expressions) для предварительной подготовки данных.
WITH settled_transactions AS (
SELECT
t.id AS transaction_id,
o.user_id,
o.total_amount,
o.currency,
DATE(o.created_at) AS order_date
FROM transactions t
JOIN orders o ON t.order_id = o.id
WHERE t.status = 'settled'
AND o.created_at >= CURRENT_DATE - INTERVAL '1 year'
),
transaction_refunds AS (
SELECT
transaction_id,
SUM(amount) AS total_refund
FROM refunds
GROUP BY transaction_id
)
SELECT
u.id,
u.email,
SUM(
(st.total_amount - COALESCE(tr.total_refund, 0)) * er.rate_to_usd
) AS net_revenue_usd
FROM settled_transactions st
JOIN users u ON st.user_id = u.id
LEFT JOIN transaction_refunds tr ON st.transaction_id = tr.transaction_id
JOIN exchange_rates er ON st.currency = er.currency
AND st.order_date = er.actual_date
GROUP BY u.id, u.email
ORDER BY net_revenue_usd DESC
LIMIT 10;
Claude 3.5 Sonnet блестяще обошел все ловушки.
Во-первых, он применил COALESCE(tr.total_refund, 0). Это спасает вычисления от превращения в NULL.
Во-вторых, он вынес агрегацию возвратов в отдельный CTE transaction_refunds. Это критически важный архитектурный шаг. Если бы у одной транзакции было несколько частичных возвратов, прямой LEFT JOIN (как у GPT-4o) привел бы к дублированию строк транзакций и задвоению total_amount при финальном суммировании. Claude предвидел проблему "веерного" размножения строк (fan-out problem) и заранее схлопнул возвраты до уровня транзакции.
Сюрприз от DeepSeek Coder V2
Ради интереса я посмотрел на результат DeepSeek Coder V2. Модель написала рабочий код, очень похожий на Claude, но попыталась оптимизировать джоин с курсами валют через оконную функцию, чтобы найти "ближайший" курс, если на конкретную дату его нет. Это не требовалось в промпте, но показало глубокое понимание доменной области финтеха, где курсы на выходных могут не обновляться.
Итоги тестирования
Генерация SQL — это не магия, а строгая трансляция бизнес-требований в реляционную алгебру. Тест показал две вещи.
Первое: контекст решает все. Без DDL-схемы ни одна модель не напишет рабочий запрос. Они будут выдумывать колонки price вместо total_amount и user_id прямо в таблице транзакций.
Второе: выбор модели критичен для сложных задач. GPT-4o отлично справляется с написанием бойлерплейта на Python, но в SQL-логике склонен к поверхностным решениям, забывая про поведение NULL и кардинальность связей. Claude 3.5 Sonnet мыслит как Data Engineer: он изолирует логические блоки через CTE и защищает агрегации.
Прогонять такие тесты вручную — боль. Использование единого API-шлюза вроде RouterAPI меняет правила игры. Вы пишете один скрипт, один промпт и за 10 минут получаете срез возможностей всего рынка AI-моделей. Это позволяет быстро переключать бэкенд на ту нейросеть, которая реально решает вашу задачу, а не ту, у которой громче маркетинг.
Кодинг-ассистенты не заменят инженеров в ближайшее время. Они избавят нас от рутины написания JOIN-ов, но обязанность проверять математику, понимать планы выполнения запросов и отлавливать NULL-ловушки все еще лежит на нас.