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
以这种方式计算输出:
id=2
,因为只有一个收入有 2000 未使用,所以结果 unit_price 与第一个收入行相同id=1
id=4
,仍有 1970 未使用,并且它比需要的要大(-1000) - 所以结果 unit_price 仍然与第一收入行相同id=1
id=5
,第一个在行动中只剩下 970,但在行动中还有更多id=3
1800。所以基本上我可以使用-970
price4.01235
和-10
price 4.9
。因此,((-970*4.01235)+(-10*4.9)) / -980 = 4.02141
当四舍五入到5
小数时,我得到了这一行的平均价格。所有的价格计算都要考虑date
列。基本上它是FIFO计算逻辑。
有谁知道如何进行这种计算?
这里似乎有两个关键问题。
一个是找到一种方法将“出”数量与所有相关的“入”数量相匹配。在您的情况下,第 5 行需要与第 1 行和第 3 行匹配,因为它使用两行的数量,如下所示。
您可以尝试使用这样的方法。取表的两个子集 ins 和 outs,并为每个子集计算两个运行总计,一个包括当前值(称为qty_from
),另一个不包括当前值(即所有先前值的运行总计)。让我们称前者qty_to
和后者qty_from
。您将获得每个子集的这些结果:
对于ins:
qty_from qty_to
-------- ------
0 2000
2000 3800
Run Code Online (Sandbox Code Playgroud)出局:
qty_from qty_to
-------- ------
0 30
30 1030
1030 2010
Run Code Online (Sandbox Code Playgroud)现在使用众所周知的范围匹配方法连接两个子集: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找到此解决方案的演示。
归档时间: |
|
查看次数: |
465 次 |
最近记录: |