计算出库单价

Tii*_*mel 5 postgresql window-functions

目前我有这样的数据,

CREATE TABLE foo (id, qty, unit_price, date, action) AS
    VALUES
    (1,  2000, 4.01235, '2015-10-10'::timestamp, 'in'),
    (2,   -30, NULL   , '2015-10-11'::timestamp, 'out'),
    (3,  1800, 4.9    , '2015-10-25'::timestamp, 'in'),
    (4, -1000, NULL   , '2015-11-12'::timestamp, 'out'),
    (5,  -980, NULL   , '2015-11-20'::timestamp, 'out');
Run Code Online (Sandbox Code Playgroud)

我需要计算传出行的平均价格,以便结果看起来像这样。

身份证 | 数量 | unit_price | 日期 | 行动
----------------------------------------------
1 | 2000 | 4.01235 | 2015-10-10 | 在
2 | -30 |    4.01235 | 2015-10-11 | 出去
3 | 1800 | 4.9 | 2015-10-25 | 在
4 | -1000 |    4.01235 | 2015-11-12 | 出去
5 | -980 |    4.02141 | 2015-11-20 | 出去

unit_price 以这种方式计算输出:

  1. for id=2,因为只有一个收入有 2000 未使用,所以结果 unit_price 与第一个收入行相同id=1
  2. 对于id=4,仍有 1970 未使用,并且它比需要的要大(-1000) - 所以结果 unit_price 仍然与第一收入行相同id=1
  3. 因为id=5,第一个在行动中只剩下 970,但在行动中还有更多id=31800。所以基本上我可以使用-970price4.01235-10price 4.9。因此,((-970*4.01235)+(-10*4.9)) / -980 = 4.02141当四舍五入到5小数时,我得到了这一行的平均价格。

所有的价格计算都要考虑date列。基本上它是FIFO计算逻辑。

有谁知道如何进行这种计算?

And*_*y M 6

这里似乎有两个关键问题。

一个是找到一种方法将“出”数量与所有相关的“入”数量相匹配。在您的情况下,第 5 行需要与第 1 行和第 3 行匹配,因为它使用两行的数量,如下所示。

您可以尝试使用这样的方法。取表的两个子集 ins 和 outs,并为每个子集计算两个运行总计,一个包括当前值(称为qty_from),另一个不包括当前值(即所有先前值的运行总计)。让我们称前者qty_to和后者qty_from。您将获得每个子集的这些结果:

现在使用众所周知的范围匹配方法连接两个子集:A.from < B.to AND B.from < A.to。这将为您提供相交范围 - 或者,转化为我们的问题,匹配来龙去脉。对于给出的示例,结果集将如下所示:

ins.qty_from  ins.qty_to  outs.qty_from  outs.qty_to
------------  ----------  -------------  -----------
0             2000        0              30
0             2000        30             1030
0             2000        1030           2010
2000          3800        1030           2010
Run Code Online (Sandbox Code Playgroud)

您可以看到第一个“in”范围重复了 3 次。这是因为第一个“in”行将其数量借给三个“out”行。您还可以看到最后一个“出”范围也重复了,这意味着它从两个“入”行中借用。

现在输入和输出已成功匹配,大问题是正确确定“输出”行从“输入”行借用的数量,以防它匹配多个“输入”。使用的逻辑可能会有所不同,这里是一个:

  • 如果ins.qty_to< outs.qty_to,
    • 如果ins.qty_from> outs.qty_from,
      • 采取当前 ins.qty
    • 除此以外,
      • 采取ins.qty_to - outs.qty_from;
  • 除此以外,
    • 如果outs.qty_from> ins.qty_from,
      • 采取当前 outs.qty
    • 除此以外,
      • outs.qty_to - ins.qty_from

只需将您的公式应用于获得的值,本质上就是SUM(ins.unit_price * borrowed_qty) / outs.qty.

将以上所有内容翻译成 SQL,我们可以得到这样的查询:

SELECT
  outs.id,
  outs.qty,
  ROUND(SUM(x.borrowed_qty * ins.unit_price) / outs.qty, 5) AS unit_price,
  outs.date,
  outs.action
FROM
  (
    SELECT
      *,
      COALESCE(SUM(qty) OVER (ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING), 0) AS qty_from,
               SUM(qty) OVER (ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)     AS qty_to
    FROM
      yourtable
    WHERE
      action = 'in'
  ) AS ins
  INNER JOIN
  (
    SELECT
      *,
      COALESCE(SUM(-qty) OVER (ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING), 0) AS qty_from,
               SUM(-qty) OVER (ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)     AS qty_to
    FROM
      yourtable
    WHERE
      action = 'out'
  ) AS outs
  ON  ins.qty_from < outs.qty_to
  AND outs.qty_from < ins.qty_to
  CROSS JOIN LATERAL
  (
    SELECT
      CASE
        WHEN ins.qty_to < outs.qty_to THEN
          CASE
            WHEN ins.qty_from > outs.qty_from THEN -ins.qty
            ELSE -(ins.qty_to - outs.qty_from)
          END
        ELSE
          CASE
            WHEN outs.qty_from > ins.qty_from THEN outs.qty
            ELSE -(outs.qty_to - ins.qty_from)
          END
      END
  ) AS x (borrowed_qty)
GROUP BY
  outs.id,
  outs.qty,
  outs.date,
  outs.action
;
Run Code Online (Sandbox Code Playgroud)

可以在 Rexester找到此解决方案的演示。