Промпт: sQL-запрос по описанию
Запрос к базе данных по словесному описанию с проверкой. Готовый промт на русском: скопируйте, подставьте свои данные и вставьте в GigaChat, YandexGPT, ChatGPT, Claude или Gemini.
Текст промпта
Что подставить в промт
| Поле | Пример |
|---|---|
| {dbms} | PostgreSQL |
| {schema} | orders(id, user_id, created_at, amount, status), users(id, city, registered_at) |
| {need} | выручку и число оплаченных заказов по городам за последние 30 дней, только status = paid, отсортировать по выручке |
Ответы нейросетей на этот промпт
Запрос
SELECT
u.city,
COUNT(o.id) AS num_orders,
SUM(o.amount) AS total_revenue
FROM
users u
JOIN
orders o ON u.id = o.user_id
WHERE
o.status = 'paid'
AND
o.created_at >= NOW() - INTERVAL '30 days'
GROUP BY
u.city
ORDER BY
total_revenue DESC;
Объяснение шагов запроса:
- Выборка полей:
Мы выбираем город (u.city), количество оплаченных заказов (COUNT(o.id)), общую сумму выручки (SUM(o.amount)).
- Объединение таблиц (
JOIN):
Соединяем таблицы users и orders, используя условие совпадения идентификаторов пользователей (u.id = o.user_id).
- Фильтрация условий (
WHERE): - Оставляем только заказы со статусом
"paid":o.status = 'paid'. - Ограничиваем выборку последними 30 днями от текущей даты:
o.created_at >= NOW() - INTERVAL '30 days'.
- Группировка результатов (
GROUP BY):
Группируем данные по городу, чтобы подсчитать агрегированные показатели отдельно для каждого города.
- Сортировка итогового результата (
ORDER BY):
Сортируем результаты по убыванию общей суммы выручки (total_revenue DESC).
Индексы для ускорения работы на больших объемах данных:
Для оптимизации данного запроса важно наличие следующих индексов:
- На таблице
orders:
``sql`
CREATE INDEX idx_orders_user_id_created_at_status ON orders(user_id, created_at, status);
user_id
Этот составной индекс позволяет быстро фильтровать по полям , created_at и status`. Именно эти поля используются в условиях объединения и фильтра.
- На таблице
users:
``sql`
CREATE INDEX idx_users_city ON users(city);
city` поможет ускорить сортировку и группировку по этому полю.
Индекс на поле
Крайние случаи для проверки:
1. Обработка пустых значений (NULL) в полях:
- Если поле
amountсодержит значения NULL, нужно убедиться, что такие записи корректно учитываются в суммарную выручку. В данном случаеSUM(NULL)даст значение NULL. Чтобы избежать проблем, используйте конструкцию COALESCE():
``sql``
SUM(COALESCE(o.amount, 0)) AS total_revenue
- Аналогично, если поле
cityимеет NULL-значения, результат будет содержать строки с NULL-городом. Их можно либо исключить дополнительно через фильтр:
``sql``
WHERE u.city IS NOT NULL AND ...
Либо оставить как отдельную категорию.
2. Проверка ситуации с повторяющимися заказами/дубликатами:
Если одна запись заказа случайно продублировалась несколько раз, наш запрос посчитает такую запись дважды. Это приведет к неверному расчету количества заказов и суммированию одинаковых сумм несколько раз. Для защиты от такого сценария рекомендуется проверять уникальные комбинации записей по ключевым полям (id, user_id, created_at):
- Добавьте дополнительную проверку уникальности:
``sql``
SELECT DISTINCT o.id, o.user_id, o.created_at FROM orders;
Или уточните бизнес-правила относительно обработки дублирующихся заказов перед выполнением анализа.
Таким образом, указанные оптимизация запросов и проверка граничных случаев помогут повысить производительность и надежность решения.
SELECT
u.city,
SUM(o.amount) AS revenue,
COUNT(*) AS paid_orders_count
FROM orders o
JOIN users u
ON u.id = o.user_id
WHERE o.status = 'paid'
AND o.created_at >= NOW() - INTERVAL '30 days'
GROUP BY u.city
ORDER BY revenue DESC;
Объяснение по шагам
- Фильтруем заказы
``sql``
WHERE o.status = 'paid'
AND o.created_at >= NOW() - INTERVAL '30 days'
Берём только оплаченные заказы за последние 30 дней.
- Связываем заказы с пользователями
``sql`
JOIN users u ON u.id = o.user_id
orders
Нужен город пользователя, поэтому соединяем с users`.
- Группируем по городам
``sql``
GROUP BY u.city
Считаем показатели отдельно для каждого города.
- Считаем выручку
``sql``
SUM(o.amount) AS revenue
Складываем суммы оплаченных заказов.
- Считаем количество оплаченных заказов
``sql``
COUNT(*) AS paid_orders_count
Считаем число строк заказов после фильтрации.
- Сортируем по выручке
``sql``
ORDER BY revenue DESC
Города с максимальной выручкой будут первыми.
Индексы для ускорения на больших таблицах
1. Частичный индекс по оплаченным заказам
Если status = 'paid' используется часто, полезен partial index:
CREATE INDEX idx_orders_paid_created_at_user_id
ON orders (created_at, user_id)
WHERE status = 'paid';
Он ускорит фильтрацию:
WHERE status = 'paid'
AND created_at >= NOW() - INTERVAL '30 days'
Так как индекс содержит только оплаченные заказы, он будет меньше и эффективнее обычного.
2. Индекс для соединения по пользователю
Если users.id — primary key, то индекс уже есть:
-- Обычно уже создан автоматически:
-- CREATE UNIQUE INDEX users_pkey ON users(id);
Если почему-то первичного ключа нет:
CREATE INDEX idx_users_id
ON users (id);
3. Покрывающий индекс для заказов
Чтобы PostgreSQL мог быстрее получить amount без обращения к таблице, можно добавить amount в INCLUDE:
CREATE INDEX idx_orders_paid_created_at_user_id_include_amount
ON orders (created_at, user_id)
INCLUDE (amount)
WHERE status = 'paid';
Это особенно полезно, если таблица orders большая, а запрос выполняется часто.
4. Индекс по городу обычно не обязателен
CREATE INDEX idx_users_city
ON users (city);
Для данного запроса индекс по city обычно не даст большого выигрыша, потому что группировка происходит уже после соединения с отфильтрованными заказами. Но он может быть полезен для других запросов, где есть фильтр по городу.
2 крайних случая, которые стоит проверить
1. NULL в city или amount
Если у пользователя city IS NULL, такие строки попадут в отдельную группу:
city = NULL
Если amount IS NULL, то SUM(amount) игнорирует NULL. Если все amount в группе NULL, результат SUM тоже будет NULL.
Если нужно заменить NULL-город и NULL-суммы:
SELECT
COALESCE(u.city, 'Unknown') AS city,
COALESCE(SUM(o.amount), 0) AS revenue,
COUNT(*) AS paid_orders_count
FROM orders o
JOIN users u
ON u.id = o.user_id
WHERE o.status = 'paid'
AND o.created_at >= NOW() - INTERVAL '30 days'
GROUP BY COALESCE(u.city, 'Unknown')
ORDER BY revenue DESC;
2. Пустой период без оплаченных заказов
Если за последние 30 дней нет заказов со статусом paid, запрос вернёт пустой результат.
Это нормально для аналитического отчёта, но если нужно показывать все города с нулевой выручкой, тогда нужен LEFT JOIN от users к orders:
SELECT
u.city,
COALESCE(SUM(o.amount), 0) AS revenue,
COUNT(o.id) AS paid_orders_count
FROM users u
LEFT JOIN orders o
ON o.user_id = u.id
AND o.status = 'paid'
AND o.created_at >= NOW() - INTERVAL '30 days'
GROUP BY u.city
ORDER BY revenue DESC;SQL-запрос
SELECT
u.city,
COALESCE(SUM(o.amount), 0) AS revenue,
COUNT(o.id) AS paid_orders_count
FROM orders o
JOIN users u ON u.id = o.user_id
WHERE o.status = 'paid'
AND o.created_at >= now() - INTERVAL '30 days'
AND o.created_at < now() -- верхняя граница на случай будущих дат
GROUP BY u.city
ORDER BY revenue DESC;
Если нужны города без заказов в периоде (нулевая выручка), используйте LEFT JOIN от users и перенесите условия по orders в ON:
SELECT
u.city,
COALESCE(SUM(o.amount), 0) AS revenue,
COUNT(o.id) AS paid_orders_count
FROM users u
LEFT JOIN orders o
ON o.user_id = u.id
AND o.status = 'paid'
AND o.created_at >= now() - INTERVAL '30 days'
AND o.created_at < now()
GROUP BY u.city
ORDER BY revenue DESC;
Объяснение по шагам
- JOIN orders и users — связываем заказы с пользователями, чтобы узнать город, т.к. в
ordersгорода нет. - WHERE status = 'paid' — фильтруем только оплаченные заказы.
- Фильтр по created_at — ограничиваем период последними 30 днями. Верхняя граница
< now()защищает от заказов с датой "из будущего" (ошибки данных). - GROUP BY u.city — агрегируем по городу.
- SUM(amount) — считаем выручку, COUNT(id) — считаем число заказов (а не
COUNT(*), чтобы не зависеть от NULL-строк при LEFT JOIN). - COALESCE(..., 0) — чтобы при LEFT JOIN города без заказов показывали 0, а не NULL.
- ORDER BY revenue DESC — сортировка по убыванию выручки.
Индексы
-- Для фильтрации orders по статусу и дате + покрытие user_id/amount
CREATE INDEX idx_orders_paid_created
ON orders (status, created_at)
INCLUDE (user_id, amount)
WHERE status = 'paid'; -- частичный индекс, экономит место
-- Для JOIN по users.id (если его ещё нет, обычно есть как PK)
-- PRIMARY KEY уже создаёт индекс, доп. не нужен
-- Если фильтр по городу бывает отдельно
CREATE INDEX idx_users_city ON users (city);
Почему так:
- Частичный индекс
WHERE status = 'paid'значительно меньше полного и ускоряет именно этот частый запрос. INCLUDE (user_id, amount)позволяет избежать обращения к куче таблицы (heap) — index-only scan.- Если
orders.user_idчасто используется в JOIN без фильтра по status, может понадобиться отдельный индекс(user_id).
Проверить эффективность:
EXPLAIN (ANALYZE, BUFFERS) <ваш запрос>;
Крайние случаи для проверки
- NULL в amount — если
amountможет бытьNULL,SUM()его просто игнорирует (не превращает в ошибку), но тест нужен на то, что NULL не "пропадает" из количества заказов. Также проверьтеcreated_at IS NULL— такие строки не попадут ни в период, ни за него, что может скрывать баги в данных.
- Пустой период / нет оплаченных заказов за 30 дней — если для какого-то города вообще нет заказов (или все отфильтровались), при
INNER JOINгород просто не появится в выдаче. Нужно явно решить: это ожидаемое поведение или требуется показать город сrevenue = 0(тогда —LEFT JOIN, как показано выше). Также стоит протестировать полностью пустой результат (без паники приложения на пустом наборе строк).
Дополнительно стоит проверить: дубликаты user_id в users (если это не PK) — это может размножить строки заказов при JOIN и искусственно завысить выручку. Убедитесь, что users.id уникален (PK/UNIQUE constraint).
Конечно, вот SQL-запрос для PostgreSQL, объяснение, рекомендации по индексам и крайние случаи.
SQL-запрос
SELECT
u.city,
SUM(o.amount) AS total_revenue,
COUNT(o.id) AS paid_orders_count
FROM
orders o
JOIN
users u ON o.user_id = u.id
WHERE
o.status = 'paid'
AND o.created_at >= NOW() - INTERVAL '30 days'
GROUP BY
u.city
ORDER BY
total_revenue DESC;
Пошаговое объяснение запроса
SELECT u.city, SUM(o.amount) AS total_revenue, COUNT(o.id) AS paid_orders_count:- Выбираем название города (
u.city) из таблицыusers. - Вычисляем сумму всех
amount(выручку) для каждой группы, используя агрегатную функциюSUM(). Результат будет названtotal_revenue. - Считаем количество заказов (
o.id) для каждой группы, используя агрегатную функциюCOUNT(). Результат будет названpaid_orders_count.
FROM orders o JOIN users u ON o.user_id = u.id:- Указываем, что мы работаем с таблицей
orders(псевдонимo) иusers(псевдонимu). - Используем
JOIN(по умолчаниюINNER JOIN), чтобы объединить строки из обеих таблиц. Объединение происходит по условиюo.user_id = u.id, то есть мы связываем каждый заказ с соответствующим пользователем.
WHERE o.status = 'paid' AND o.created_at >= NOW() - INTERVAL '30 days':- Это условие
WHEREфильтрует строки перед группировкой. o.status = 'paid': Включаем в расчет только те заказы, у которых статус'paid'.o.created_at >= NOW() - INTERVAL '30 days': Включаем только те заказы, которые были созданы за последние 30 дней.NOW()возвращает текущую дату и время, аINTERVAL '30 days'вычитает 30 дней из текущей даты.
GROUP BY u.city:- Группируем результаты по полю
u.city. Это означает, что агрегатные функцииSUM()иCOUNT()будут вычисляться для каждой уникальной группы городов.
ORDER BY total_revenue DESC:- Сортируем конечный результат по убыванию
total_revenue, то есть города с наибольшей выручкой будут вверху списка.
Индексы для ускорения запроса на больших таблицах
Для значительного ускорения этого запроса на больших таблицах рекомендуется создать следующие индексы:
- На таблице
orders: CREATE INDEX idx_orders_status_created_at_user_id ON orders (status, created_at, user_id);- Почему: Этот индекс является покрывающим для условий
WHEREиJOIN. status: Первым в индексе, так как это наиболее селективное условие фильтрации (o.status = 'paid').created_at: Вторым, так как используется для фильтрации по диапазону дат.user_id: Третьим, так как используется дляJOINс таблицейusers. PostgreSQL сможет получить все необходимые данные дляWHEREиJOINнепосредственно из индекса, не обращаясь к основной таблицеorders(или минимизируя такие обращения).
- На таблице
users: CREATE INDEX idx_users_id_city ON users (id, city);- Почему: Этот индекс поможет при
JOINи полученииcity. id: Первым, так как это ключ дляJOIN.city: Вторым, чтобы PostgreSQL мог получить значениеcityнепосредственно из индекса послеJOIN, не обращаясь к основной таблицеusers.
Важно: Если user_id в таблице orders уже является внешним ключом, то, скорее всего, на нем уже есть индекс. Аналогично, id в таблице users почти наверняка является первичным ключом и имеет индекс. Однако, создание комбинированных индексов, как предложено выше, часто дает дополнительное преимущество, так как они могут быть покрывающими для конкретного запроса.
2 крайних случая, которые стоит проверить
- Пустой период / Нет оплаченных заказов за 30 дней:
- Сценарий: За последние 30 дней не было ни одного заказа со статусом
'paid', или вообще не было заказов. - Ожидаемый результат: Запрос вернет пустой результат (0 строк). Это корректное поведение, так как нет данных, удовлетворяющих условиям.
- Проверка: Вставьте данные, где все заказы старше 30 дней или имеют другой статус.
``sql``
-- Пример данных для проверки пустого периода
INSERT INTO users (id, city, registered_at) VALUES (101, 'Moscow', NOW());
INSERT INTO orders (id, user_id, created_at, amount, status) VALUES
(1001, 101, NOW() - INTERVAL '31 days', 100.00, 'paid'),
(1002, 101, NOW() - INTERVAL '10 days', 50.00, 'pending');
Запрос должен вернуть 0 строк.
- Пользователи без заказов / Заказы без соответствующего пользователя (если
user_idнеNOT NULLили есть "потерянные" данные): - Сценарий:
- Есть пользователи, у которых нет заказов за последние 30 дней (или вообще нет заказов).
- (Менее вероятно, но возможно) Есть заказы,
user_idкоторых не соответствует ни одномуidв таблицеusers. - Ожидаемый результат:
- Пользователи без заказов не будут включены в результат, так как
INNER JOINтребует совпадения в обеих таблицах. - Заказы без соответствующего пользователя также не будут включены из-за
INNER JOIN. - Проверка:
```sql
-- Пользователь без заказов
INSERT INTO users (id, city, registered_at) VALUES (102, 'London', NOW());
-- Заказ с user_id, которого нет в таблице users (предполагаем, что user_id может быть NULL или ссылаться на несуществующего пользователя)
-- Если user_id - внешний ключ с ON DELETE RESTRICT, то такой INSERT не пройдет.
-- Если user_id допускает NULL, то такой заказ не будет связан.
-- Для проверки, можно временно отключить FK или создать заказ с user_id, который не существует.
-- INSERT INTO orders (id, user_id, created_at, amount, status) VALUES (1003, 999, NOW() - INTERVAL '5 days', 200.00, 'paid');
``user_id=999
В этом случае пользователь 'London' не появится в результате, и заказ с (если бы он был вставлен) также не был бы учтен. Это ожидаемое поведение для INNER JOIN. Если бы требовалось включить города, в которых есть пользователи, но нет оплаченных заказов, пришлось бы использовать LEFT JOIN с users` в качестве левой таблицы. Но в данном запросе это не требуется.
Советы
- Перед запуском на рабочей базе проверьте запрос на копии или с LIMIT.
- Скопируйте промпт кнопкой выше.
- Замените поля в фигурных скобках своими данными.
- Отправьте в нейросеть и сравните ответ с примером на этой странице.
Подробнее о структуре хорошего запроса: гид AI University.
Набор по теме: процесс целиком
Похожие промпты
Промпты работают лучше, когда понимаешь модель
56 официальных курсов Anthropic, OpenAI, Google и других на русском: первые уроки бесплатно, без VPN. Наборы промптов входят в полный доступ.