通过 TSQL 使用 LIFO 方法计算结果
Calculate results with LIFO method via TSQL
我想用 TSQL 通过 LIFO(后进先出)方法进行计算。
使用后进先出法需要您通过卖出最后一笔交易来计算profit/loss。
工作原理示例:
- 交易于 3 月 1 日结束,我们以每只 5 美元的价格购买 10 只股票
- 交易于 3 月 2 日结束,我们以每只 6 美元的价格购买 15 只股票
- 交易于 3 月 3 日结束,我们以每只 4 美元的价格购买 5 只股票
- 交易于 3 月 4 日结束,我们以每只 7 美元的价格出售 17 只股票
到第 4 笔交易时,我们现在已经卖出了 3 月 3 日的 5 只股票,每只 4 美元,以及 3 月 2 日的 12 只股票,每只 6 美元。
所以现在我们留下了以下内容:
10只股票,每只5元,3月1日成交
3 只股票 6 美元起 3 月 2 日交易 (17-5-15 = -3).
剩下 13 只股票,平均价格为 (10*5 + 3*6) / 13 = 5.23076923
这里是测试数据生成脚本:
use TestTask
go
IF OBJECT_ID('testtable','U')IS NOT NULL
DROP TABLE testtable
go
create table testtable
(
stockid int not null,
dealid int identity (1,1) not null,
dealtype char(1) not null,
stockdate datetime not null,
stockamount int not null,
priceperstock int not null
)
insert into testtable(stockid,dealtype,stockdate,stockamount,priceperstock)
VALUES
(111,'B','01.03.2016',10,5),
(111,'B','02.03.2016',15,6),
(111,'B','03.03.2016',5,4),
(111,'S','04.03.2016',17,7)
我想计算财务状况和许多其他参数,这些参数需要我知道还有多少股票价格合适。到目前为止我已经做到了:
select
stockid,
dealid,
dealtype,
stockdate,
priceperstock,
case dealtype
when 'B' then stockamount
when 'S' then -stockamount
end as stockamount,
sum(
case dealtype
when 'B' then stockamount
when 'S' then -stockamount
end
) over (partition by
stockid order by dealid ROWS UNBOUNDED PRECEDING)
as poistion
from testtable
输出:
stockid dealid dealtype stockdate priceperstock stockamount poistion
111 1 B 2016-01-03 00:00:00.000 5 10 10
111 2 B 2016-02-03 00:00:00.000 6 15 25
111 3 B 2016-03-03 00:00:00.000 4 5 30
111 4 S 2016-04-03 00:00:00.000 7 -17 13
期望的输出:
stockid dealid dealtype stockdate priceperstock stockamount poistion stocksleft
111 1 B 2016-01-03 00:00:00.000 5 10 10 10
111 2 B 2016-02-03 00:00:00.000 6 15 25 3
111 3 B 2016-03-03 00:00:00.000 4 5 30 0
111 4 S 2016-04-03 00:00:00.000 7 -17 13 0
最好的方法是什么?
由于您的示例非常狭窄,因此很难组合出一个万无一失的解决方案。但这应该让您走上正确的轨道,或者至少是一条轨道。它使用一种反向 运行 总数,然后从库存量中减去。使用您的数据集稍作改动:
create table #testtable
(
stockid int not null,
dealid int identity (1,1) not null,
dealtype char(1) not null,
stockdate datetime not null,
stockamount int not null,
priceperstock int not null
)
insert into #testtable(stockid,dealtype,stockdate,stockamount,priceperstock)
VALUES
(111,'B','01.03.2016',10,5),
(111,'B','02.03.2016',15,6),
(111,'B','03.03.2016',5,4),
(111,'S','04.03.2016',-17,7) --signed int
----Add this to see another level
--insert into #testtable(stockid,dealtype,stockdate,stockamount,priceperstock)
-- VALUES
-- (111,'S','05.03.2016',-12,5)
;WITH CTE
AS (
SELECT stockid
, dealid
, dealtype
, stockdate
, priceperstock
, stockamount
, sum(stockamount) OVER (
ORDER BY dealid DESC
) AS runningtotal
, sum(stockamount) OVER (
ORDER BY dealid) AS position
FROM #testtable
)
SELECT stockid
, dealid
, dealtype
, stockdate
, priceperstock
, stockamount
--, runningtotal
, position
, CASE
WHEN dealtype = 'S'
THEN 0
WHEN stockamount > runningtotal AND runningtotal < 0
THEN 0
WHEN stockamount > runningtotal AND runningtotal >= 0
THEN runningtotal
WHEN stockamount < runningtotal
THEN stockamount
END AS StockRemaining
FROM cte
ORDER BY dealid
我怀疑您可能想要交错买卖,所以虽然我认为另一个答案是一个很好的起点,但它不能完全处理整个情况。
基本上我认为您必须使用某种迭代机制来处理这个问题。我试图用递归来做,但不幸的是,分析函数不能正确地使用该方法。所以我回到了临时 table 和 while 循环。
create table #R (
lvl int not null, stockId int not null, dealId int not null,
stockDate datetime not null, stockAmount int not null, pricePerStock int not null,
stockRemaining int not null, amountDeducted int not null
);
insert into #R (
lvl, stockId, dealId, stockDate, stockAmount,
pricePerStock, stockRemaining, amountDeducted
)
select 0, stockId, dealId, stockDate, stockAmount, pricePerStock, stockAmount, 0
from <T> where dealtype = 'B' /* <--- your table is <T> */
declare @lvl int = 0;
declare @rowCount int = 1;
while @rowCount > 0
begin
set @lvl = @lvl + 1;
with sells as (
select stockId, dealId as saleId,
row_number() over (order by dealId) as sellNum, stockAmount as sellAmount
from <T> where dealType = 'S'
)
update #R
set stockRemaining = (
select stockRemaining
from (
select dealId,
case
when r.stockRemaining + s.sellAmount
< sum(stockRemaining) over (order by dealId desc)
then r.stockRemaining
when sum(stockRemaining) over (order by dealId desc)
< s.sellAmount
then 0
else sum(stockRemaining) over (order by dealId desc)
- s.sellAmount
end as stockremaining
from sells s inner join #R r
on r.stockId = s.stockId and r.dealId < s.saleId
where s.stockId = #R.stockId and s.sellNum = @lvl
) data
where dealId = #R.dealId
)
where dealId < (select saleId from sells where sellNum = @lvl);
set @rowCount = @@rowCount;
end
我已将其删减以便发布。在这里看到它的实际效果,输出更多以更好地遵循逻辑:http://rextester.com/WPLKLJ95730
我想用 TSQL 通过 LIFO(后进先出)方法进行计算。
使用后进先出法需要您通过卖出最后一笔交易来计算profit/loss。
工作原理示例:
- 交易于 3 月 1 日结束,我们以每只 5 美元的价格购买 10 只股票
- 交易于 3 月 2 日结束,我们以每只 6 美元的价格购买 15 只股票
- 交易于 3 月 3 日结束,我们以每只 4 美元的价格购买 5 只股票
- 交易于 3 月 4 日结束,我们以每只 7 美元的价格出售 17 只股票
到第 4 笔交易时,我们现在已经卖出了 3 月 3 日的 5 只股票,每只 4 美元,以及 3 月 2 日的 12 只股票,每只 6 美元。
所以现在我们留下了以下内容: 10只股票,每只5元,3月1日成交 3 只股票 6 美元起 3 月 2 日交易 (17-5-15 = -3).
剩下 13 只股票,平均价格为 (10*5 + 3*6) / 13 = 5.23076923
这里是测试数据生成脚本:
use TestTask
go
IF OBJECT_ID('testtable','U')IS NOT NULL
DROP TABLE testtable
go
create table testtable
(
stockid int not null,
dealid int identity (1,1) not null,
dealtype char(1) not null,
stockdate datetime not null,
stockamount int not null,
priceperstock int not null
)
insert into testtable(stockid,dealtype,stockdate,stockamount,priceperstock)
VALUES
(111,'B','01.03.2016',10,5),
(111,'B','02.03.2016',15,6),
(111,'B','03.03.2016',5,4),
(111,'S','04.03.2016',17,7)
我想计算财务状况和许多其他参数,这些参数需要我知道还有多少股票价格合适。到目前为止我已经做到了:
select
stockid,
dealid,
dealtype,
stockdate,
priceperstock,
case dealtype
when 'B' then stockamount
when 'S' then -stockamount
end as stockamount,
sum(
case dealtype
when 'B' then stockamount
when 'S' then -stockamount
end
) over (partition by
stockid order by dealid ROWS UNBOUNDED PRECEDING)
as poistion
from testtable
输出:
stockid dealid dealtype stockdate priceperstock stockamount poistion
111 1 B 2016-01-03 00:00:00.000 5 10 10
111 2 B 2016-02-03 00:00:00.000 6 15 25
111 3 B 2016-03-03 00:00:00.000 4 5 30
111 4 S 2016-04-03 00:00:00.000 7 -17 13
期望的输出:
stockid dealid dealtype stockdate priceperstock stockamount poistion stocksleft
111 1 B 2016-01-03 00:00:00.000 5 10 10 10
111 2 B 2016-02-03 00:00:00.000 6 15 25 3
111 3 B 2016-03-03 00:00:00.000 4 5 30 0
111 4 S 2016-04-03 00:00:00.000 7 -17 13 0
最好的方法是什么?
由于您的示例非常狭窄,因此很难组合出一个万无一失的解决方案。但这应该让您走上正确的轨道,或者至少是一条轨道。它使用一种反向 运行 总数,然后从库存量中减去。使用您的数据集稍作改动:
create table #testtable
(
stockid int not null,
dealid int identity (1,1) not null,
dealtype char(1) not null,
stockdate datetime not null,
stockamount int not null,
priceperstock int not null
)
insert into #testtable(stockid,dealtype,stockdate,stockamount,priceperstock)
VALUES
(111,'B','01.03.2016',10,5),
(111,'B','02.03.2016',15,6),
(111,'B','03.03.2016',5,4),
(111,'S','04.03.2016',-17,7) --signed int
----Add this to see another level
--insert into #testtable(stockid,dealtype,stockdate,stockamount,priceperstock)
-- VALUES
-- (111,'S','05.03.2016',-12,5)
;WITH CTE
AS (
SELECT stockid
, dealid
, dealtype
, stockdate
, priceperstock
, stockamount
, sum(stockamount) OVER (
ORDER BY dealid DESC
) AS runningtotal
, sum(stockamount) OVER (
ORDER BY dealid) AS position
FROM #testtable
)
SELECT stockid
, dealid
, dealtype
, stockdate
, priceperstock
, stockamount
--, runningtotal
, position
, CASE
WHEN dealtype = 'S'
THEN 0
WHEN stockamount > runningtotal AND runningtotal < 0
THEN 0
WHEN stockamount > runningtotal AND runningtotal >= 0
THEN runningtotal
WHEN stockamount < runningtotal
THEN stockamount
END AS StockRemaining
FROM cte
ORDER BY dealid
我怀疑您可能想要交错买卖,所以虽然我认为另一个答案是一个很好的起点,但它不能完全处理整个情况。
基本上我认为您必须使用某种迭代机制来处理这个问题。我试图用递归来做,但不幸的是,分析函数不能正确地使用该方法。所以我回到了临时 table 和 while 循环。
create table #R (
lvl int not null, stockId int not null, dealId int not null,
stockDate datetime not null, stockAmount int not null, pricePerStock int not null,
stockRemaining int not null, amountDeducted int not null
);
insert into #R (
lvl, stockId, dealId, stockDate, stockAmount,
pricePerStock, stockRemaining, amountDeducted
)
select 0, stockId, dealId, stockDate, stockAmount, pricePerStock, stockAmount, 0
from <T> where dealtype = 'B' /* <--- your table is <T> */
declare @lvl int = 0;
declare @rowCount int = 1;
while @rowCount > 0
begin
set @lvl = @lvl + 1;
with sells as (
select stockId, dealId as saleId,
row_number() over (order by dealId) as sellNum, stockAmount as sellAmount
from <T> where dealType = 'S'
)
update #R
set stockRemaining = (
select stockRemaining
from (
select dealId,
case
when r.stockRemaining + s.sellAmount
< sum(stockRemaining) over (order by dealId desc)
then r.stockRemaining
when sum(stockRemaining) over (order by dealId desc)
< s.sellAmount
then 0
else sum(stockRemaining) over (order by dealId desc)
- s.sellAmount
end as stockremaining
from sells s inner join #R r
on r.stockId = s.stockId and r.dealId < s.saleId
where s.stockId = #R.stockId and s.sellNum = @lvl
) data
where dealId = #R.dealId
)
where dealId < (select saleId from sells where sellNum = @lvl);
set @rowCount = @@rowCount;
end
我已将其删减以便发布。在这里看到它的实际效果,输出更多以更好地遵循逻辑:http://rextester.com/WPLKLJ95730