使用 FIFO 方法计算 SQL 的销售成本
Calculating Cost of Sales in SQL using FIFO method
根据股票交易 table 我创建了以下排序视图 table 为 FIFO 做好准备:
rowN
date_
stockCode
sign_
amount
unitPrice
1
'2022-01-20'
ABC
in
5
29.20
2
'2022-01-22'
ABC
in
3
32.50
3
'2022-01-23'
ABC
out
7
40.00
4
'2022-01-23'
ABC
out
1
42.00
5
'2022-01-01'
XYZ
in
3
20.50
6
'2022-01-03'
XYZ
out
3
25.00
我想创建一个 select 查询,它看起来像之前的 table,只有“out”行并添加了 cost_of_sales 列,这是该销售的 FIFO 成本。但是我对 SQL 的了解仅限于对分区的连接和求和。
结果table在我看来应该是这样的:
rowN
date_
stockCode
sign_
amount
unitPrice
cost_of_sales_uP
cost_of_sales
3
'2022-01-23'
ABC
out
7
40.00
30.1428
211.00
4
'2022-01-23'
ABC
out
1
42.00
32.50
32.50
6
'2022-01-03'
XYZ
out
3
25.00
20.50
61.50
我不知道如何实现。任何帮助和指导表示赞赏。结果 table 不必完全一样,但主要思想就在那里。
谢谢!
以下方法可能不是最快的。
但它完成了工作。
首先将传入的内容展开为临时 table。
然后,通过循环传出,每个传入单元在 First-In-First-Out 的基础上分配给传出。
最后的查询然后使用 temp table 的结果来计算传入的总数和平均值。
IF OBJECT_ID('tempdb..#tmpStock') IS NOT NULL DROP TABLE #tmpStock;
CREATE TABLE #tmpStock (
id int identity primary key,
code varchar(30),
date_in date,
rowN int,
unitPrice decimal(10,2),
rowN_out int
);
--
-- Using a recursive CTE to unfold the incoming for the temp table
--
with RCTE as (
select stockCode, date_, rowN, amount, unitPrice
, 1 as lvl
from stock_transactions
where sign_ = 'in'
union all
select stockCode, date_, rowN, amount, unitPrice
, lvl + 1
from RCTE
where lvl < amount
)
insert into #tmpStock (code, date_in, rowN, unitPrice)
select stockCode, date_, rowN, unitPrice
from RCTE
order by stockCode, date_, rowN
option (maxrecursion 0);
DECLARE @IdOut INT = 1;
DECLARE @RowsOut INT = 0;
DECLARE @code VARCHAR(30);
DECLARE @amount SMALLINT;
DECLARE @date DATE;
DECLARE @rowN INT;
DECLARE @StockOut TABLE (
id int identity primary key,
code varchar(30),
date_out date,
rowN int,
amount smallint
);
insert into @StockOut (code, date_out, rowN, amount)
select stockCode, date_, rowN, amount
from stock_transactions
where sign_ = 'out'
order by stockCode, date_, rowN;
SELECT @RowsOut = COUNT(*) FROM @StockOut;
WHILE @IdOut <= @RowsOut
BEGIN
SELECT
@code = code
, @amount = amount
, @date = date_out
, @rowN = rowN
FROM @StockOut
WHERE id = @IdOut;
;WITH cte_in as (
select *
, rn = row_number() over (order by date_in, rowN)
from #tmpStock
where code = @code
and date_in <= @date
and rowN_out is null
)
UPDATE cte_in
SET rowN_out = @rowN
WHERE rn <= @amount;
SET @IdOut = @IdOut + 1;
END;
select * from #tmpStock
id | code | date_in | rowN | unitPrice | rowN_out
-: | :--- | :--------- | ---: | --------: | -------:
1 | ABC | 2022-01-20 | 1 | 29.20 | 3
2 | ABC | 2022-01-20 | 1 | 29.20 | 3
3 | ABC | 2022-01-20 | 1 | 29.20 | 3
4 | ABC | 2022-01-20 | 1 | 29.20 | 3
5 | ABC | 2022-01-20 | 1 | 29.20 | 3
6 | ABC | 2022-01-22 | 2 | 32.50 | 3
7 | ABC | 2022-01-22 | 2 | 32.50 | 3
8 | ABC | 2022-01-22 | 2 | 32.50 | 4
9 | XYZ | 2022-01-01 | 5 | 20.50 | 6
10 | XYZ | 2022-01-01 | 5 | 20.50 | 6
11 | XYZ | 2022-01-01 | 5 | 20.50 | 6
SELECT o.*
, CAST(i.AveragePriceIn AS DECIMAL(10,2)) AS cost_of_sales_uP
, i.TotalPriceIn AS cost_of_sales
FROM stock_transactions o
LEFT JOIN (
SELECT rowN_out
, AVG(unitPrice) as AveragePriceIn
, SUM(unitPrice) as TotalPriceIn
FROM #tmpStock
GROUP BY rowN_out
) i on i.rowN_out = o.rowN
WHERE o.sign_ = 'out'
ORDER BY o.rowN;
rowN | date_ | stockCode | sign_ | amount | unitPrice | cost_of_sales_uP | cost_of_sales
---: | :--------- | :-------- | :---- | -----: | --------: | ---------------: | ------------:
3 | 2022-01-23 | ABC | out | 7 | 40.00 | 30.14 | 211.00
4 | 2022-01-23 | ABC | out | 1 | 42.00 | 32.50 | 32.50
6 | 2022-01-03 | XYZ | out | 3 | 25.00 | 20.50 | 61.50
演示 db<>fiddle here
根据股票交易 table 我创建了以下排序视图 table 为 FIFO 做好准备:
rowN | date_ | stockCode | sign_ | amount | unitPrice |
---|---|---|---|---|---|
1 | '2022-01-20' | ABC | in | 5 | 29.20 |
2 | '2022-01-22' | ABC | in | 3 | 32.50 |
3 | '2022-01-23' | ABC | out | 7 | 40.00 |
4 | '2022-01-23' | ABC | out | 1 | 42.00 |
5 | '2022-01-01' | XYZ | in | 3 | 20.50 |
6 | '2022-01-03' | XYZ | out | 3 | 25.00 |
我想创建一个 select 查询,它看起来像之前的 table,只有“out”行并添加了 cost_of_sales 列,这是该销售的 FIFO 成本。但是我对 SQL 的了解仅限于对分区的连接和求和。
结果table在我看来应该是这样的:
rowN | date_ | stockCode | sign_ | amount | unitPrice | cost_of_sales_uP | cost_of_sales |
---|---|---|---|---|---|---|---|
3 | '2022-01-23' | ABC | out | 7 | 40.00 | 30.1428 | 211.00 |
4 | '2022-01-23' | ABC | out | 1 | 42.00 | 32.50 | 32.50 |
6 | '2022-01-03' | XYZ | out | 3 | 25.00 | 20.50 | 61.50 |
我不知道如何实现。任何帮助和指导表示赞赏。结果 table 不必完全一样,但主要思想就在那里。
谢谢!
以下方法可能不是最快的。
但它完成了工作。
首先将传入的内容展开为临时 table。
然后,通过循环传出,每个传入单元在 First-In-First-Out 的基础上分配给传出。
最后的查询然后使用 temp table 的结果来计算传入的总数和平均值。
IF OBJECT_ID('tempdb..#tmpStock') IS NOT NULL DROP TABLE #tmpStock; CREATE TABLE #tmpStock ( id int identity primary key, code varchar(30), date_in date, rowN int, unitPrice decimal(10,2), rowN_out int );
-- -- Using a recursive CTE to unfold the incoming for the temp table -- with RCTE as ( select stockCode, date_, rowN, amount, unitPrice , 1 as lvl from stock_transactions where sign_ = 'in' union all select stockCode, date_, rowN, amount, unitPrice , lvl + 1 from RCTE where lvl < amount ) insert into #tmpStock (code, date_in, rowN, unitPrice) select stockCode, date_, rowN, unitPrice from RCTE order by stockCode, date_, rowN option (maxrecursion 0);
DECLARE @IdOut INT = 1; DECLARE @RowsOut INT = 0; DECLARE @code VARCHAR(30); DECLARE @amount SMALLINT; DECLARE @date DATE; DECLARE @rowN INT; DECLARE @StockOut TABLE ( id int identity primary key, code varchar(30), date_out date, rowN int, amount smallint ); insert into @StockOut (code, date_out, rowN, amount) select stockCode, date_, rowN, amount from stock_transactions where sign_ = 'out' order by stockCode, date_, rowN; SELECT @RowsOut = COUNT(*) FROM @StockOut; WHILE @IdOut <= @RowsOut BEGIN SELECT @code = code , @amount = amount , @date = date_out , @rowN = rowN FROM @StockOut WHERE id = @IdOut; ;WITH cte_in as ( select * , rn = row_number() over (order by date_in, rowN) from #tmpStock where code = @code and date_in <= @date and rowN_out is null ) UPDATE cte_in SET rowN_out = @rowN WHERE rn <= @amount; SET @IdOut = @IdOut + 1; END;
select * from #tmpStock
id | code | date_in | rowN | unitPrice | rowN_out -: | :--- | :--------- | ---: | --------: | -------: 1 | ABC | 2022-01-20 | 1 | 29.20 | 3 2 | ABC | 2022-01-20 | 1 | 29.20 | 3 3 | ABC | 2022-01-20 | 1 | 29.20 | 3 4 | ABC | 2022-01-20 | 1 | 29.20 | 3 5 | ABC | 2022-01-20 | 1 | 29.20 | 3 6 | ABC | 2022-01-22 | 2 | 32.50 | 3 7 | ABC | 2022-01-22 | 2 | 32.50 | 3 8 | ABC | 2022-01-22 | 2 | 32.50 | 4 9 | XYZ | 2022-01-01 | 5 | 20.50 | 6 10 | XYZ | 2022-01-01 | 5 | 20.50 | 6 11 | XYZ | 2022-01-01 | 5 | 20.50 | 6
SELECT o.* , CAST(i.AveragePriceIn AS DECIMAL(10,2)) AS cost_of_sales_uP , i.TotalPriceIn AS cost_of_sales FROM stock_transactions o LEFT JOIN ( SELECT rowN_out , AVG(unitPrice) as AveragePriceIn , SUM(unitPrice) as TotalPriceIn FROM #tmpStock GROUP BY rowN_out ) i on i.rowN_out = o.rowN WHERE o.sign_ = 'out' ORDER BY o.rowN;
rowN | date_ | stockCode | sign_ | amount | unitPrice | cost_of_sales_uP | cost_of_sales ---: | :--------- | :-------- | :---- | -----: | --------: | ---------------: | ------------: 3 | 2022-01-23 | ABC | out | 7 | 40.00 | 30.14 | 211.00 4 | 2022-01-23 | ABC | out | 1 | 42.00 | 32.50 | 32.50 6 | 2022-01-03 | XYZ | out | 3 | 25.00 | 20.50 | 61.50
演示 db<>fiddle here