Расчет дневного минимального баланса - PullRequest
4 голосов
/ 31 декабря 2010

Допустим, у меня есть таблица с именем транзакция с полями транзакция_дата, депозит, вывод средств. Может быть или не быть транзакции в день, но может иметь несколько транзакций для каждого дня. Итак, что мне нужно сделать, это указать диапазон дат, скажем, с 1 декабря 2010 года по 31 декабря 2010 года, мне нужно вычислить минимальный баланс на каждый день. Предположим, есть транзакции и до 1 декабря 2010 года. Есть ли кто-нибудь, кто может дать мне представление об этом?

Спасибо.

Обновление С примером

 tran_date   withdraw    deposit
2010-11-23       0.00      50.00
2010-12-10       0.00      50.00
2010-12-10       0.00     200.00
2010-12-12     100.00       0.00
2010-12-20       0.00      50.00
2010-12-20      70.00       0.00
2010-12-20       0.00      50.00
2010-12-20       0.00      50.00
2010-12-24     150.00       0.00

В приведенном выше примере минимальный дневной баланс от 1 декабря до 10 декабря будет 50 . На 10 декабря есть два депозита на общую сумму 70 , но минимальный остаток в этот день будет 50 (перенесено с предыдущего дня).

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

Перенос на 20 декабря составляет 200 . Первый депозит составляет 250 , второй - 180 , третий - 230 , а последняя транзакция - 280 . Таким образом, минимальный остаток на этот день будет 180 после снятия 70 на второй транзакции дня. Можно ли сгенерировать это с помощью запроса на PostgreSQL 8.4 или мне следует использовать другой подход?

Ответы [ 7 ]

2 голосов
/ 31 декабря 2010

Игнорируйте все в этом другом ответе. Этот парень, Мальволио, болван и идиот. Попробуйте вместо этого:

SELECT MIN(balance), transaction_date FROM
( SELECT a.transaction_date, IFNULL(sum(b.deposit) - sum(b.withdrawal), 0) balance FROM transaction a
        LEFT JOIN transaction b ON a.seqno > b.seqno GROUP ON a.seqno
   UNION
  SELECT a.transaction_date, IFNULL(sum(b.deposit) - sum(b.withdrawal), 0) balance FROM transaction a
        LEFT JOIN transaction b ON a.seqno >= b.seqno GROUP ON a.seqno  ) x
GROUP BY transaction_date;

Я как раз собирался заснуть, когда это произошло со мной. IFNULL может быть специфичной для MySQL, но вы можете найти эквивалент Postgres.

2 голосов
/ 31 декабря 2010

Edit2 Это полный пример, включая (минимальный) остаток за предыдущий день (насколько я могу судить с таким небольшим набором данных).Он должен работать на 8.4.

Я произвел рефакторинг производных таблиц, чтобы использовать CTE (общее табличное выражение), чтобы сделать его (надеюсь) немного более читабельным:

WITH days AS (
   -- generate a liste of possible dates spanning 
   -- the whole interval of the transactions
   SELECT min(tran_date) + generate_series(0, max(tran_date) - min(tran_date)) AS some_date
   FROM transaction
),
total_balance AS (
  -- Calculate the running totals for all transactions
  SELECT tran_id,
         days.some_date as tran_date, 
         deposit, 
         withdrawal,
         sum(deposit - withdrawal) 
             OVER (ORDER BY some_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as balance
  FROM days
   LEFT JOIN transaction t ON t.tran_date = days.some_date 
),
min_balance AS (
  -- calculate the minimum balance for each day 
  -- (the smalles balance will have a '1' in the column balance_rank)
  SELECT tran_id, 
         tran_date,
         rank() OVER (PARTITION BY tran_date ORDER BY balance) as balance_rank,
         balance
  FROM total_balance
)
-- Now get everything, including the balance for the previous day
SELECT tran_id,
       tran_date,
       balance,
       lag(balance) over (order by tran_date) as previous_balance
FROM min_balance
WHERE balance_rank = 1;
0 голосов
/ 03 января 2011

Спасибо всем за помощь. Я использовал следующее, чтобы решить это. Я не знаю, насколько эффективен код.

select dt::date, 
coalesce(case when balance<=coAmt then balance else coAmt end, 
(select sum(coalesce(deposit, 0.00))-sum(coalesce(withdraw, 0.00)) 
from  where tran_date<=dt::date and acc_no='3'), 0.00) amt
from (
select tran_date, min(balance) balance, 
coalesce((select sum(coalesce(deposit, 0.00) - coalesce(withdraw, 0.00)) 
from transaction where tran_date<t.tran_date and acc_no=t.acc_no), 0.00) coAmt
from (
select tran_id, acc_no, tran_date, deposit, withdraw,
sum(deposite - withdraw) over (order by tran_id) balance 
from transaction sv group by tran_id, acc_no, tran_date, deposite, withdraw) t 
where acc_no='3' group by tran_date, acc_no order by tran_date ) t1 
right join 
generate_series('2010-12-01', '2010-12-31', interval '1 day') as dt on dt=tran_date 
group by dt, tran_date, balance, coAmt order by dt

Опять же, спасибо за вашу помощь.

0 голосов
/ 02 января 2011

Почему бы вам не добавить в базу данных столбец, который отслеживает текущий баланс (рассчитывается при каждом пополнении / снятии). Таким образом, это будет просто случай возврата минимума для этого столбца в интересующем диапазоне дат.

0 голосов
/ 31 декабря 2010

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

CREATE TABLE transaction (
    tran_date date,
    num       int,
    withdraw  numeric,
    deposit   numeric
);

INSERT INTO transaction VALUES
    ('2010-11-23', 1,      0.00,      50.00),
    ('2010-12-10', 1,      0.00,      50.00),
    ('2010-12-10', 2,      0.00,     200.00),
    ('2010-12-12', 1,    100.00,       0.00),
    ('2010-12-20', 1,      0.00,      50.00),
    ('2010-12-20', 2,     70.00,       0.00),
    ('2010-12-20', 3,      0.00,      50.00),
    ('2010-12-20', 4,      0.00,      50.00),
    ('2010-12-24', 1,    150.00,       0.00);

Затем появится следующий запрос, чтобы дать вам ответ:

WITH dates (tran_date) AS (SELECT date '2010-12-01' + generate_series(0, 30)),    
     transactions AS (SELECT tran_date, num,
                             coalesce(withdraw, 0) AS withdraw, 
                             coalesce(deposit, 0) AS deposit
                      FROM dates FULL OUTER JOIN transaction USING (tran_date)),
     running_totals AS (SELECT tran_date,     
                               sum(deposit - withdraw) OVER (ORDER BY tran_date, num ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) AS lagging_total,
                               sum(deposit - withdraw) OVER (ORDER BY tran_date, num ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS current_total
                        FROM transactions)
SELECT tran_date, min(least(lagging_total, current_total))
FROM running_totals
GROUP BY tran_date
HAVING tran_date IN (SELECT tran_date FROM dates)
ORDER BY tran_date;    

Обратите внимание, однако, что для этого вам нужен PostgreSQL 9.0, потому что предложение 1 PRECEDING не поддерживается в более ранних версиях. Если вы не можете выполнить обновление, вам, вероятно, понадобится какое-то процедурное решение, как предлагают другие ответы.

В любом случае я рекомендую написать для этого модульные тесты. ; -)

0 голосов
/ 31 декабря 2010

Во-первых, я собираюсь предположить, что транзакции последовательно пронумерованы . По определению, транзакции должны быть надлежащим образом упорядочены (поскольку депозит в 50 долларов, за которым следует вывод в 50 долларов в один и тот же день, приведет к совершенно отличному ответу на те же шаги в другом порядке), а последовательная нумерация делает другие вещи намного проще. Затем мы должны сделать некоторые процедурные рукопожатия:

CREATE TABLE running_total (seqno INT, transaction_date DATE, before NUMBER(10,20), after NUMBER(10,20);
SET tot=0;
FOR transaction IN SELECT * FROM transaction ORDER BY seqno ASC LOOP
    SET oldtot = tot;
    SET tot = tot = transaction.deposit - transaction.withdrawal;
    EXECUTE 'INSERT INTO running_total (seqno, transaction_date, before, after) VALUES (' ||
    transaction.seqno || ', ' || transaction.transaction_date || ',' || oldtot || ',' || tot || ')';
END LOOP;

(Простите за любые опечатки - у меня нет PostGres под рукой). Теперь у нас есть таблица со всеми остатками, нам просто нужно ее выкопать.

SELECT MIN(balance), transaction_date FROM
( SELECT before as balance, transaction_date FROM running_total
   UNION
 SELECT after as balance, transaction_date FROM running_total) x 
GROUP BY transaction_date;

Я не могу проверить это здесь, но оно должно работать.

0 голосов
/ 31 декабря 2010

Я предполагаю, что по минимальному балансу, о котором вы говорите, у вас меньше, начало или конец дня?

Полагаю, для каждого дня вы будете делать что-то вроде этого:

Баланс за предыдущий день:

SELECT (SUM(deposit) - SUM(withdrawal)) WHERE date < [date you're after]

(не уверен, как будет проводиться сравнение дат в PostgreSQL

Затем:

SELECT (SUM(deposit) - SUM(withdrawal)) WHERE date = [date you're after]

Затем в зависимости от того, что будетбольше.

Если вы не это имели в виду, нам нужна дополнительная информация.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...