Промпт для SQL-запроса и проверка двойного подсчёта

Проверка SQL-запроса: стеклянные таблицы, повторяющиеся маркеры и увеличительное стекло

SQL-запрос может выполниться без ошибки и всё равно завысить итог. Например, если у заказа две товарные строки и две оплаты, прямое соединение обеих таблиц даст четыре комбинации. Сумма повторится, хотя названия столбцов и синтаксис выглядят правильно.

Поэтому запрос к нейросети лучше начинать не с «сделай отчёт о продажах», а со схемы таблиц, правил расчёта и ожидаемого результата на небольшом наборе данных. Ниже готовый промпт и воспроизводимый пример для SQLite. Редакционные запросы действительно выполнены в SQLite 3.53.1; это проверка примера, а не сравнение чат-сервисов.

Что передать нейросети кроме названий таблиц

Укажите диалект SQL. Синтаксис дат, приведения типов и некоторые функции отличаются между SQLite, PostgreSQL и другими СУБД. Добавьте ключи и связи: один заказ может иметь несколько товаров и несколько оплат. Отдельно объясните, что считается одной строкой итогового отчёта.

Следующий слой — правила предметной области. «Оплачено» может означать наличие успешного платежа, сумму поступлений за месяц или сумму всех оплат по заказам, созданным в этом месяце. Это разные отчёты. Если в схеме нет даты платежа, запрос не сможет честно посчитать месячный денежный поток.

Передавайте учебные или разрешённые обезличенные данные. Пароль подключения, токены и доступ к рабочей базе для подготовки SELECT не нужны. Выполняйте результат сначала на тестовой копии с правами чтения; текстовая просьба «не меняй данные» не заменяет реальные ограничения доступа.

Готовый промпт для отчёта по заказам

Подготовь три SELECT-запроса для SQLite. Не меняй данные.
Сначала проверь, достаточно ли схемы и правил. Если нет,
задай конкретный вопрос, а не выбирай бизнес-правило за меня.

Схема:
orders(id INTEGER PRIMARY KEY, created_at TEXT NOT NULL,
       status TEXT NOT NULL, customer_id TEXT);
order_items(id INTEGER PRIMARY KEY, order_id INTEGER NOT NULL,
            qty INTEGER NOT NULL, price_cents INTEGER NOT NULL);
payments(id INTEGER PRIMARY KEY, order_id INTEGER NOT NULL,
         amount_cents INTEGER NOT NULL, status TEXT NOT NULL).

order_id в обеих дочерних таблицах ссылается на orders.id.
У заказа может быть несколько товаров и несколько платежей.
Деньги хранятся целыми копейками. Даты — UTC, текст одинакового
формата YYYY-MM-DD HH:MM:SS, без смещений и долей секунды.

Выбери только completed-заказы, созданные с 2026-10-01 00:00:00
включительно до 2026-11-01 00:00:00 не включительно.
Стоимость заказа = SUM(qty * price_cents).
Оплачено = сумма всех платежей status='posted' этого заказа.
Отрицательная сумма — возврат. Включай её в SUM со знаком
минус; повторно знак не меняй.
failed не учитывать. Отсутствие posted-платежей даёт ноль.
customer_id=NULL не исключает заказ.
Отсутствие товарных строк даёт стоимость 0.
Если подходящих заказов нет, общий итог — одна строка
с тремя нулями.
Это НЕ денежный поток за октябрь: даты платежа в схеме нет.

1. По одной строке на заказ: id, goods_cents, paid_cents.
2. Общий итог: order_count, goods_cents, paid_cents.
3. Заказы без единой строки posted-платежа: id, customer_id.
   Нулевая сумма оплат не равна отсутствию платежей.

Не допускай размножения сумм при JOIN двух дочерних таблиц.
Объясни уровень агрегации и обработку отсутствующих строк.
Сортируй списки по id. После SQL предложи граничные проверки.

К этому запросу приложите небольшой набор INSERT из тестового файла. Для собственной базы замените названия и правила, но не убирайте пояснения про связи и значение итоговых сумм.

Проверяем результат на семи заказах

В учебном наборе семь заказов. Четыре завершённых попадают в октябрь; ещё один создан за секунду до начала месяца, один — точно в начале ноября, один отменён. У заказа 101 две товарные строки и две оплаты, у 102 есть возврат, у 103 неизвестен клиент и есть только неуспешный платёж. У 107 платежей нет вовсе.

Ожидания зафиксированы до выполнения запросов. Суммы ниже переведены из целых копеек в рубли только для чтения.

Заказ Стоимость Оплачено с учётом возврата
101 250 ₽ 250 ₽
102 300 ₽ 250 ₽
103 100 ₽ 0 ₽
107 100 ₽ 0 ₽

Все три запроса дали ожидаемый результат: четыре заказа, стоимость 750 ₽, оплаты 500 ₽. В список без проведённых платежей вошли 103 и 107. Заказ с неизвестным клиентом не потерялся; сентябрьская и ноябрьская границы и отменённый заказ исключены.

Эти цифры проверяют именно сформулированные правила. Они не означают, что магазину «поступило 500 ₽ в октябре»: в учебной таблице нет времени платежей.

Как поймать размножение JOIN

Намеренно ошибочный редакционный пример напрямую соединяет товары и оплаты заказа 101:

SELECT SUM(i.qty * i.price_cents), SUM(p.amount_cents)
FROM orders o
JOIN order_items i ON i.order_id = o.id
JOIN payments p ON p.order_id = o.id
WHERE o.id = 101 AND p.status = 'posted';

Он вернул 500 ₽ и 500 ₽ вместо 250 ₽ и 250 ₽. Причина в сочетании двух товаров с двумя платежами. Это не ошибка, которую мы наблюдали у конкретной модели, а специально составленный контрпример.

Надёжный для этой схемы подход — сначала отдельно получить одну сумму товаров и одну сумму платежей на заказ, затем соединить эти результаты с orders. После этого каждой строке заказа соответствует не больше одной строки из каждой агрегированной таблицы.

WITH item_totals AS (
  SELECT order_id, SUM(qty * price_cents) AS goods_cents
  FROM order_items GROUP BY order_id
), payment_totals AS (
  SELECT order_id, SUM(amount_cents) AS paid_cents
  FROM payments WHERE status = 'posted' GROUP BY order_id
)
SELECT o.id, COALESCE(i.goods_cents, 0) AS goods_cents,
       COALESCE(p.paid_cents, 0) AS paid_cents
FROM orders o
LEFT JOIN item_totals i ON i.order_id = o.id
LEFT JOIN payment_totals p ON p.order_id = o.id
WHERE o.status = 'completed'
  AND o.created_at >= '2026-10-01 00:00:00'
  AND o.created_at < '2026-11-01 00:00:00'
ORDER BY o.id;

Заменять SUM на SUM(DISTINCT …) вслепую нельзя. Две настоящие оплаты по 100 ₽ должны дать 200 ₽, но DISTINCT оставит одну сумму и вернёт 100 ₽. В отдельной контрольной проверке мы получили именно эту разницу.

Промпт для проверки готового SQL

Проверь запрос по указанным схеме и бизнес-правилам.
Не ограничивайся синтаксисом. Для каждого JOIN укажи,
сколько строк он может дать на один заказ.
Проверь возвраты, отсутствие оплат, customer_id=NULL,
границы месяца и отменённые заказы.
Отдельно различай отсутствие платежей и нулевую сумму.
Если предлагаешь исправление, объясни, какой конкретный
контрпример оно исправляет. Не называй запрос проверенным,
если он не выполнялся. Не придумывай результат выполнения.

Ответ модели с объяснением полезен, но не заменяет запуск. Скачать комплект к статье (ZIP): setup.sql, три SELECT, заранее подготовленные ожидания и скрипт проверки. Скрипт создаёт только учебную базу в памяти; перед отчётами включает режим query_only.

Что проверить перед применением к своим данным

Сопоставьте вручную несколько заказов с разным числом товаров и платежей. Добавьте возврат на всю сумму: он даст нулевой итог, но не должен попасть в список «платежей не было». Проверьте пустой период, заказ без товаров и две одинаковые по сумме оплаты.

В примере сравнение дат текстом работает потому, что все значения имеют одинаковый формат и UTC. Для смешанных часовых поясов или другого хранения дат правило нужно изменить. А если отчёт нужен по месяцу фактической оплаты, сначала добавьте соответствующее поле и согласуйте фильтр, а не просите нейросеть угадать отсутствующие данные.

Источники

  • SQLite SELECT: https://www.sqlite.org/lang_select.html
  • SQLite Aggregate Functions: https://www.sqlite.org/lang_aggfunc.html
  • SQLite Date And Time Functions: https://www.sqlite.org/lang_datefunc.html
Понравилась статья? Поделиться с друзьями:
GPT Insider