#sql #postgresql #aggregate-functions
#sql #postgresql #агрегатные функции
Вопрос:
У меня есть таблица платежей, в которой есть идентификатор пользователя и сумма. Минимальный вывод средств пользователями составляет $ 5,00, и им может быть выплачено любое количество платежей. Их общий баланс нигде не сохраняется (чтобы упростить его и избежать проблем с синхронизацией), вместо этого он определяется путем выполнения запроса типа:
SELECT SUM(amount)
FROM payment
WHERE user_id = 5;
Я хочу узнать общую сумму всех остатков, которые соответствуют требованию вывода средств. Я хотел бы сделать что-то вроде:
SELECT SUM(SUM(amount))
FROM payment
WHERE sum(amount) >= 5.00
GROUP BY user_id;
К сожалению, я получаю сообщение об ошибке
ОШИБКА: агрегаты не разрешены в предложении WHERE
Как я могу написать этот запрос? Я использую postgres 8.4.
Часть 2:
Я не уверен, возможно ли это вообще, но в эту таблицу платежей также включены платежи, сделанные пользователем: таблица платежей включает сумму, from_user_id и to_user_id. Это система учета с одной записью. Было бы здорово, если бы я мог также вычесть платежи, произведенные пользователем. Псевдо-sql является:
SELECT (sum of all amounts)
WHERE (sum(amount where to_user_id = 5) - sum(amount where from_user_id = 5)) > 5.00
Ответ №1:
Вам нужно использовать предложение HAVING
Никогда не слышал, чтобы кто-нибудь пытался выполнить вложенную агрегацию. Я предлагаю вам выполнить внутренний бит отдельно, либо с with
статментом, либо используя что-то вроде
select sum(foo) from (select sum(amount) as foo ...having ...)
Я полагаю, что вы хотите что-то подобное для своей второй части, хотя я могу сомневаться.
with a as(
select sum(amount) as credit, from_user from payment group by from_user
), b as (
select sum(amount) as debit, to_user from payment group by to_user
)
-- glossing over any null values with coalesce:
select to_user, coalesce(credit,0) - coalesce(debit,0) -- coalesce users perhaps?
from a outer join b on from_user = to_user
where coalesce(credit,0) - coalesce(debit,0) > limit -- or vice-versa
Комментарии:
1. Спасибо, это дает мне часть этого. Я все еще не могу суммировать (sum(сумма)) — в нем говорится, что вы не можете вложить агрегацию. Есть ли способ выполнить эту часть?
2. Я никогда не слышал, чтобы кто-нибудь пытался выполнить вложенную агрегацию. Я предлагаю вам выполнить внутренний бит отдельно, либо с
with
статментом, либо с чем-то вродеselect sum(foo) from (select sum(amount) as foo ...having ...)
3. Отлично, спасибо. Есть идеи, как выполнить часть 2? Я понятия не имею, как разделить две суммы (amount) (одна сумма для to_user минус сумма для from_user)
4. Спасибо, это работает — одно исправление: это должно быть внешнее объединение, иначе у пользователей, которые никогда не отправляли деньги, не было бы баланса, но в остальном это здорово.
Ответ №2:
Вы захотите использовать предложение HAVING.
Ниже приведены все пользователи, у которых баланс превышает 5,00. Затем вы могли бы выполнить цикл на своем языке программирования, чтобы получить общий баланс. SELECT sum(amount) FROM payment GROUP BY user_id HAVING sum(amount) >= 5.00 ;
Я не могу придумать способ объединить все это в одну эффективную инструкцию SQL, но я продолжу думать.
Я также не думаю, что у меня есть ответ на вашу часть 2, я приношу извинения.
Ответ №3:
Часть 1: Агрегированные данные и HAVING
предложение
В SQL WHERE
предложение не поддерживает использование агрегированного значения (т. Е. MIN, MAX, SUM, COUNT и т.д.), Которого нет в подзапросе или производной таблице / встроенном представлении. Такие случаи могут быть обработаны только в HAVING
предложении:
SELECT SUM(p.amount)
FROM PAYMENT p
GROUP BY p.to_user_id
HAVING SUM(p.amount) >= 5.00
Часть 2:
Поскольку пользователь может получать платежи только на свою учетную запись, или наоборот — вам необходимо получить список пользователей для внешнего присоединения к:
SELECT COALESCE(x.debit, 0) - COALESCE(y.credit, 0) AS balance
FROM (SELECT a.from_user_id AS user
FROM PAYMENT a
UNION
SELECT b.to_user_id
FROM PAYMENT b) z
LEFT JOIN (SELECT t.to_user_id,
SUM(t.payment) AS debit
FROM PAYMENT t
GROUP BY t.to_user_id) x ON x.to_user_id = z.user
LEFT JOIN (SELECT t.from_user_id,
SUM(t.payment) AS credit
FROM PAYMENT t
GROUP BY t.from_user_id) y ON y.from_user_id = z.user
WHERE COALESCE(x.debit, 0) - COALESCE(y.credit, 0) >= 5.00
GROUP BY z.user