2

I have a table p1 with transactions in Postgres like this:

| id | product_id | transaction_date | quantity |
|----|------------|------------------|----------|
| 1  | 1          | 2015-01-01       | 1        |
| 2  | 1          | 2015-01-02       | 2        |
| 3  | 1          | 2015-01-03       | 3        |

and p2 table with products like this:

| id | product      | stock |
|----|--------------|-------|
| 1  | Product A    | 15    |

stock in p2' has been be reduced for every new record in p1.

How to reconstruct previous states to get this result?

| product   | first_stock | quantity | last_stock |
|-----------|-------------|----------|------------|
| Product A | 21          | 1        | 20         |
| Product A | 20          | 2        | 18         |
| Product A | 18          | 3        | 15         |

I have tried using lead() to get the quantity after the current row.

SELECT p2.product, p1.quantity, lead(p1.quantity) OVER(ORDER BY p1.id DESC)
FROM p1 INNER JOIN p2 ON p1.product_id = p2.id;

But how to calculate leading rows from the current stock?

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
alfa6661
  • 205
  • 1
  • 11
  • A trigger would be a go on this case. – Jorge Campos Jan 12 '15 at 03:17
  • Didn't understand the question at first, but I think I get it after a closer look. Adapted the question to fix inconsistencies - I hope I understood correctly. Also, the short name is ["Postgres", not "Postgre"](https://wiki.postgresql.org/wiki/Identity_Guidelines). – Erwin Brandstetter Jan 12 '15 at 04:11
  • What happens when you get shipments of stock that increase your stock on hand? Surely they should be factored in, no? – Bohemian Jan 12 '15 at 04:46
  • @Bohemian: Negative values in `p1.quantity` would do that - unless I am missing something? – Erwin Brandstetter Jan 12 '15 at 10:29
  • @ErwinBrandstetter Yes, I suppose so. I assumed (probably wrongly) that the column `transaction_date` suggested those rows were for a "sale", and that stock acquisition would be recorded elsewhere. However, there's nothing to suggest such a stock acquisition could not be another "transaction", which would be convenient to solve this question more completely with no extra code. – Bohemian Jan 12 '15 at 10:56
  • @ErwinBrandstetter thank you for correcting my question. oh i feel so bad that i cant speak english as well. i should improve my english. – alfa6661 Jan 12 '15 at 14:05
  • @Bohemian The above example is not the actual case, there are many "transaction" that affect the stock, so i have to determine the "transaction" will reduce or increase the stock. – alfa6661 Jan 12 '15 at 14:16

1 Answers1

4

You don't just need lead() you need the running sum over all rows in between to reconstruct previous states from transaction data:

SELECT p2.product
     , p2.stock + px.sum_quantity            AS first_stock
     , px.quantity
     , p2.stock + px.sum_quantity - quantity AS last_stock
FROM   p2
JOIN (
   SELECT product_id, quantity, transaction_date
        , sum(quantity) OVER (PARTITION BY product_id
                              ORDER BY transaction_date DESC) AS sum_quantity
   FROM   p1
   ) px ON px.product_id = p2.id
ORDER  BY px.transaction_date;

Assuming the course of events actually indicated by transaction_date.

Use the aggregate function sum() as window-aggregate function to get the running sum. Use a subquery, since we use the running sum of quantities (sum_quantity) multiple times.
For last_stock subtract quantity of the current row (after adding it redundantly).

Nitpick

Theoretically, it would be cheaper to use a custom frame definition for the window frame to only sum quantities up to preceding row, so we don't add and subtract the quantity of the current row redundantly. But that's more complex and hardly faster in reality:

SELECT p2.id, p2.product, px.transaction_date  -- plus id and date for context
     , p2.stock + COALESCE(px.pre_sum_q + px.quantity, 0) AS first_stock
     , px.quantity
     , p2.stock + COALESCE(px.pre_sum_q, 0)               AS last_stock
FROM  p2
LEFT JOIN (
   SELECT id, product_id, transaction_date
        , quantity
        , sum(quantity) OVER (PARTITION BY product_id
                              ORDER BY transaction_date DESC
               ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) AS pre_sum_q
   FROM   p1
   ) px ON px.product_id = p2.id
ORDER  BY px.transaction_date, px.id;

Explanation for the frame definition in this related answer:

While being at it, also demonstrating how to prevent missing rows and NUll values with LEFT JOIN and COALESCE for products that don't have any related rows in p1, and a stable sort order if there are multiple transactions for the same product on the same day.

Still assuming all columns to be defined NOT NULL, or you need to do some more for corner cases with NULL values.

Community
  • 1
  • 1
Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
  • 1
    I'm extremely grateful for answering my question so promptly. and thank you for this great explanation. I'll try to understand your answer. thanks. – alfa6661 Jan 12 '15 at 14:27