如何根据PostgreSQL中的先前值计算计算列的值

时间:2018-01-24 20:27:35

标签: postgresql

我试图根据给定的表格数据计算调整后的成本基数,但无法弄清楚如何使用当前行中的先前计算值。

<script>

使用数据:

CREATE TABLE transactions (
  datetime timestamp NOT NULL,
  type varchar(25) NOT NULL,
  amount INT NOT NULL,
  shares INT NOT NULL,
  symbol VARCHAR(20) NOT NULL
);

据我所知:

INSERT INTO transactions(amount, type, shares, datetime, symbol) VALUES (100, 'Buy', 10, now() - INTERVAL '14 days', 'XYZ');
INSERT INTO transactions(amount, type, shares, datetime, symbol) VALUES (330, 'Buy', 30, now() - INTERVAL '11 days', 'XYZ');
INSERT INTO transactions(amount, type, shares, datetime, symbol) VALUES (222, 'Buy', 22, now() - INTERVAL '10 days', 'XYZ');
INSERT INTO transactions(amount, type, shares, datetime, symbol) VALUES (245, 'Buy', 24, now() - INTERVAL '8 days', 'XYZ');
INSERT INTO transactions(amount, type, shares, datetime, symbol) VALUES (150, 'Sell', 15, now() - INTERVAL '7 days', 'XYZ');
INSERT INTO transactions(amount, type, shares, datetime, symbol) VALUES (210, 'Buy', 20, now() - INTERVAL '6 days', 'XYZ');
INSERT INTO transactions(amount, type, shares, datetime, symbol) VALUES (235, 'Buy', 22, now() - INTERVAL '5 days', 'XYZ');
INSERT INTO transactions(amount, type, shares, datetime, symbol) VALUES (110, 'Sell', 10, now() - INTERVAL '4 days', 'XYZ');

预期结果(total_acb是我尝试计算的值):

WITH cte AS (
  WITH shares AS (
      SELECT transactions.*,
        sum(CASE WHEN transactions.type = 'Sell'
          THEN transactions.shares * -1 --reduction of shares
            ELSE transactions.shares END)
        OVER (
          PARTITION BY transactions.symbol
      ORDER BY transactions.symbol, transactions.datetime ROWS UNBOUNDED PRECEDING ) AS total_shares
  FROM transactions)
SELECT shares.*, coalesce(lag(shares.total_shares) OVER(ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING), 0) as previous_shares FROM shares)
SELECT cte.*,
  CASE WHEN cte.type = 'Buy' THEN
    -- [Previous total_acb] + cte.amount
    ELSE
    -- [Previous total_acb] x ((cte.previous_shares – shares) / cte.previous_shares)
END
    AS total_acb
FROM cte

1 个答案:

答案 0 :(得分:0)

进行这种递归计算的最简单方法是使用plpgsql函数。

create or replace function calculate_totals()
returns table (
    datetime timestamp, 
    type text, 
    amount dec, 
    shares dec, 
    symbol text, 
    total_shares dec, 
    total_acb dec)
language plpgsql as $$
declare
    rec record;
    curr_symbol text = '';
begin
    for rec in 
        select *
        from transactions
        order by symbol, datetime
    loop
        if rec.symbol <> curr_symbol then
            curr_symbol = rec.symbol;
            total_acb = 0;
            total_shares = 0;
        end if;
        if rec.type = 'Buy' then
            total_acb = round(total_acb + rec.amount, 2);
            total_shares = total_shares + rec.shares;
        else
            total_acb = round(total_acb * (total_shares - rec.shares) / total_shares, 2);
            total_shares = total_shares - rec.shares;
        end if;
        select rec.datetime, rec.type, rec.amount, rec.shares, rec.symbol
        into datetime, type, amount, shares, symbol;
        return next;
    end loop;
end $$;

结果与问题中给出的结果略有不同(由于作者的错误):

select *
from calculate_totals();

         datetime          | type | amount | shares | symbol | total_shares | total_acb 
---------------------------+------+--------+--------+--------+--------------+-----------
 2018-01-10 23:28:56.66738 | Buy  |    100 |     10 | XYZ    |           10 |    100.00
 2018-01-13 23:28:56.66738 | Buy  |    330 |     30 | XYZ    |           40 |    430.00
 2018-01-14 23:28:56.66738 | Buy  |    222 |     22 | XYZ    |           62 |    652.00
 2018-01-16 23:28:56.66738 | Buy  |    245 |     24 | XYZ    |           86 |    897.00
 2018-01-17 23:28:56.66738 | Sell |    150 |     15 | XYZ    |           71 |    740.55
 2018-01-18 23:28:56.66738 | Buy  |    210 |     20 | XYZ    |           91 |    950.55
 2018-01-19 23:28:56.66738 | Buy  |    235 |     22 | XYZ    |          113 |   1185.55
 2018-01-20 23:28:56.66738 | Sell |    110 |     10 | XYZ    |          103 |   1080.63
(8 rows)