Postgres 中的库存计算

Stock calculation in Postgres

我有一个 table p1,在 Postgres 中有这样的交易:

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

p2 table 产品如下:

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

stock in p2' 已针对 p1 中的每条新记录进行缩减。

如何重建以前的状态得到这个结果?

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

我试过使用 lead() 获取当前行之后的数量。

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;

但是如何计算当前股票的领先行数?

您不仅需要 lead(),您还需要对 所有 行进行 运行 求和,以从交易数据重建之前的状态:

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;

假设事件的进程实际由 transaction_date 指示。

使用聚合函数 sum() 作为 window-聚合函数来获得 运行 总和。使用子查询,因为我们多次使用 运行 数量总和 (sum_quantity)。
对于 last_stock 减去当前行的 quantity(在冗余添加之后)。

吹毛求疵

理论上,为 window 框架使用自定义框架定义来仅将数量相加到 preceding 行会更便宜,因此我们不添加并多余地减去 current 行的数量。但这在现实中更复杂而且几乎没有更快:

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
               <b>ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING</b>) AS pre_sum_q
   FROM   p1
   ) px ON px.product_id = p2.id
ORDER  BY px.transaction_date, px.id;

此相关答案中框架定义的解释:

  • Grouping based on sequence of rows

同时,还演示了如何使用 LEFT JOINCOALESCE 来防止没有 any 的产品丢失行和空值p1 中的相关行,如果同一产品在同一天有多个交易,则排序顺序稳定。

仍然假设所有列都被定义为 NOT NULL,或者您需要为具有 NULL 值的极端情况做更多的事情。