使用前几行计算值
Using previous rows calculated value
我有一种情况,我需要将一个查询的种子值设置为另一个查询的第一个值,并使用这个值来计算下一行数据(结合当前行的数据) .每个后续行都需要使用前几行的计算值。
我研究过窗口函数,例如OVER()
和 LAG()
但我似乎无法让它在我的情况下工作。下面是数据示例。
最终我需要将前一个 Inventory
乘以当前行的 AppliedCalc
并加上当前行的 Adjustment
字段。这是将在下一行的计算中使用的新 Inventory
字段。
Inventory = previous [inventory] * [AppliedCalc] + [Adjustment]
@BeginValue
是查询应提供的 seed 值。此值会更改,并将成为用于未来计算的初始库存。
@BeginValue=1,000,000
DateValue Inventory Adjustment AppliedCalc
1/31/2001 1000000 0.00 0.00
2/28/2001 1100125 125.00 1.10
3/31/2001 1133529 400.00 1.03
...
虽然我可以为 SQL 语句提供种子值,但我无法将计算延续到第二行之后的后续行 - 我发现我需要为每个额外的行添加一个额外的子查询。除了使用游标之外,还有其他解决方案吗?
看来你在寻找累计总数,下面是今天已经回答的问题,它使用 window 函数并在 SQL 2012 年有效,我的答案在下面,适用于所有版本
CREATE TABLE #temp (TypeA int , TypeSize int )
INSERT INTO #temp (TypeA , TypeSize)
VALUES ( 110 , 2),
( 110 , 2),
( 110 , 6),
( 200 , 5),
( 200 , 7),
( 301 , 1),
( 301 , 2),
( 301 , 5),
( 301 , 1)
---accepted answer
SELECT TypeA , TypeSize, SUM(CAST(TypeSize AS bigint))
OVER(PARTITION BY TypeA ORDER BY TypeA ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS Csize FROM #temp AS A
--my answer
with cte
as
(
select *,row_number() over (partition by typea order by typea) as rn
from #temp
)
select typea,typesize,(select sum(typesize) from cte t1 where t1.rn<t2.rn+1 and t1.typea=t2.typea
group by typea
) as cszie
from cte t2
我尝试了几种方法,但无法想出 干净 解决方案。下面的解决方案是"quirky update"。这依赖于聚集索引之后发生的更新。这没有记录,但对于至少 SQL Server 2005 和高达 SQL Server 2012 SP2(我刚刚测试过)它是有效的。然而,普遍的共识是:不要将这种工作方式用于生产代码(尽管我从未见过它不工作)。
除此之外,我认为您只能使用 CURSOR 进行计算(参见第二个脚本)。这是保证安全的方法。
可能存在其他方法可以做到这一点,无需古怪的更新或使用 CURSOR。但我现在想不出一个。 HTH!
古怪更新(使用风险自负):
CREATE TABLE #tt(
date_value DATE CONSTRAINT PK_tt_date_value PRIMARY KEY CLUSTERED,
adjustment DECIMAL(28,2),
applied_calc DECIMAL(28,2),
inventory NUMERIC(28,0) CONSTRAINT DF_tt_inventory DEFAULT(0)
);
INSERT INTO #tt(date_value,adjustment,applied_calc)
VALUES
('2001-01-31',0.00,1.00),
('2001-02-28',125.00,1.10),
('2001-03-31',400.00,1.03),
('2001-04-30',500,1.05),
('2001-05-31',100,1),
('2001-06-30',125,1.03);
DECLARE @inventory NUMERIC(28,0) = 100000;
UPDATE
t1
SET
@inventory=t1.inventory=@inventory*t1.applied_calc+t1.adjustment
FROM
#tt AS t1 WITH (INDEX=PK_tt_date_value);
SELECT
*
FROM
#tt
ORDER BY
date_value;
DROP TABLE #tt;
使用 CURSOR(安全方法):
CREATE TABLE #tt(
date_value DATE CONSTRAINT PK_tt_date_value PRIMARY KEY CLUSTERED,
adjustment DECIMAL(28,2),
applied_calc DECIMAL(28,2),
inventory NUMERIC(28,0) CONSTRAINT DF_tt_inventory DEFAULT(0)
);
INSERT INTO #tt(date_value,adjustment,applied_calc)
VALUES
('2001-01-31',0.00,1.00),
('2001-02-28',125.00,1.10),
('2001-03-31',400.00,1.03),
('2001-04-30',500,1.05),
('2001-05-31',100,1),
('2001-06-30',125,1.03);
DECLARE c_uv CURSOR
FOR SELECT adjustment,applied_calc FROM #tt ORDER BY date_value
FOR UPDATE OF inventory;
OPEN c_uv;
DECLARE @adjustment DECIMAL(28,2);
DECLARE @applied_calc DECIMAL(28,2);
DECLARE @inventory NUMERIC(28,0) = 100000;
WHILE 1=1
BEGIN
FETCH NEXT FROM
c_uv
INTO
@adjustment,
@applied_calc;
IF @@FETCH_STATUS<>0
BREAK;
SET @inventory=@inventory*@applied_calc+@adjustment;
UPDATE
#tt
SET
inventory=@inventory
WHERE
CURRENT OF c_uv;
END
CLOSE c_uv;
DEALLOCATE c_uv;
SELECT
*
FROM
#tt
ORDER BY
date_value;
DROP TABLE #tt;
两个脚本都给出以下结果:
date_value adjustment applied_calc inventory
2001-01-31 0.00 1.00 100000
2001-02-28 125.00 1.10 110125
2001-03-31 400.00 1.03 113829
2001-04-30 500.00 1.05 120020
2001-05-31 100.00 1.00 120120
2001-06-30 125.00 1.03 123849
谢谢TT的帮助!
我尝试使用 CTE 来回答问题 - 您的建议很可能是更好的选择。这是我想出的:
CREATE TABLE #tt(
RowID INT,
date_value DATE,
Adjustment DECIMAL(28,2),
AppliedCalc DECIMAL(28,2)
);
DECLARE @BeginValue Decimal(28,2)=100000;
INSERT INTO #tt(RowID,date_value,Adjustment,AppliedCalc)
VALUES
(1,'2001-01-31',0.00,1.00),
(2,'2001-02-28',125.00,1.10),
(3,'2001-03-31',400.00,1.03),
(4,'2001-04-30',500.00,1.05),
(5,'2001-05-31',0.00,1),
(6,'2001-06-30',1.25,1.03);
;with cteOutput as (
SELECT t.RowID, date_value, isnull(t.AppliedCalc,1) as AppliedCalc, Adjustment,Cast(@BeginValue as Decimal(28,2)) as AdjMV
FROM #tt t
WHERE t.RowID = 1
UNION ALL
SELECT t.RowID, t.date_value, t.AppliedCalc, t.Adjustment,cast((t.AppliedCalc)*cte.Adjmv+isnull(t.Adjustment,0) as decimal(28,2)) as AdjMV
FROM #tt t
INNER JOIN cteOutput cte
ON t.RowID-1 = cte.RowID)
SELECT cte.RowID, cte.date_value,cte.AppliedCalc, cte.Adjustment, cte.AdjMV
FROM cteOutput cte
ORDER BY rowid
OPTION(maxrecursion 0)
DROP TABLE #tt;
和输出:
RowID date_value Inventory AppliedCalc Adjustment
1 2001-01-31 100000 1.00 0.00
2 2001-02-28 110125 1.10 125.00
3 2001-03-31 113828 1.03 400.00
4 2001-04-30 120020 1.05 500.00
5 2001-05-31 120120 1.00 100.00
6 2001-06-30 123848 1.03 125.00
我有一种情况,我需要将一个查询的种子值设置为另一个查询的第一个值,并使用这个值来计算下一行数据(结合当前行的数据) .每个后续行都需要使用前几行的计算值。
我研究过窗口函数,例如OVER()
和 LAG()
但我似乎无法让它在我的情况下工作。下面是数据示例。
最终我需要将前一个 Inventory
乘以当前行的 AppliedCalc
并加上当前行的 Adjustment
字段。这是将在下一行的计算中使用的新 Inventory
字段。
Inventory = previous [inventory] * [AppliedCalc] + [Adjustment]
@BeginValue
是查询应提供的 seed 值。此值会更改,并将成为用于未来计算的初始库存。
@BeginValue=1,000,000
DateValue Inventory Adjustment AppliedCalc
1/31/2001 1000000 0.00 0.00
2/28/2001 1100125 125.00 1.10
3/31/2001 1133529 400.00 1.03
...
虽然我可以为 SQL 语句提供种子值,但我无法将计算延续到第二行之后的后续行 - 我发现我需要为每个额外的行添加一个额外的子查询。除了使用游标之外,还有其他解决方案吗?
看来你在寻找累计总数,下面是今天已经回答的问题,它使用 window 函数并在 SQL 2012 年有效,我的答案在下面,适用于所有版本
CREATE TABLE #temp (TypeA int , TypeSize int )
INSERT INTO #temp (TypeA , TypeSize)
VALUES ( 110 , 2),
( 110 , 2),
( 110 , 6),
( 200 , 5),
( 200 , 7),
( 301 , 1),
( 301 , 2),
( 301 , 5),
( 301 , 1)
---accepted answer
SELECT TypeA , TypeSize, SUM(CAST(TypeSize AS bigint))
OVER(PARTITION BY TypeA ORDER BY TypeA ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS Csize FROM #temp AS A
--my answer
with cte
as
(
select *,row_number() over (partition by typea order by typea) as rn
from #temp
)
select typea,typesize,(select sum(typesize) from cte t1 where t1.rn<t2.rn+1 and t1.typea=t2.typea
group by typea
) as cszie
from cte t2
我尝试了几种方法,但无法想出 干净 解决方案。下面的解决方案是"quirky update"。这依赖于聚集索引之后发生的更新。这没有记录,但对于至少 SQL Server 2005 和高达 SQL Server 2012 SP2(我刚刚测试过)它是有效的。然而,普遍的共识是:不要将这种工作方式用于生产代码(尽管我从未见过它不工作)。
除此之外,我认为您只能使用 CURSOR 进行计算(参见第二个脚本)。这是保证安全的方法。
可能存在其他方法可以做到这一点,无需古怪的更新或使用 CURSOR。但我现在想不出一个。 HTH!
古怪更新(使用风险自负):
CREATE TABLE #tt(
date_value DATE CONSTRAINT PK_tt_date_value PRIMARY KEY CLUSTERED,
adjustment DECIMAL(28,2),
applied_calc DECIMAL(28,2),
inventory NUMERIC(28,0) CONSTRAINT DF_tt_inventory DEFAULT(0)
);
INSERT INTO #tt(date_value,adjustment,applied_calc)
VALUES
('2001-01-31',0.00,1.00),
('2001-02-28',125.00,1.10),
('2001-03-31',400.00,1.03),
('2001-04-30',500,1.05),
('2001-05-31',100,1),
('2001-06-30',125,1.03);
DECLARE @inventory NUMERIC(28,0) = 100000;
UPDATE
t1
SET
@inventory=t1.inventory=@inventory*t1.applied_calc+t1.adjustment
FROM
#tt AS t1 WITH (INDEX=PK_tt_date_value);
SELECT
*
FROM
#tt
ORDER BY
date_value;
DROP TABLE #tt;
使用 CURSOR(安全方法):
CREATE TABLE #tt(
date_value DATE CONSTRAINT PK_tt_date_value PRIMARY KEY CLUSTERED,
adjustment DECIMAL(28,2),
applied_calc DECIMAL(28,2),
inventory NUMERIC(28,0) CONSTRAINT DF_tt_inventory DEFAULT(0)
);
INSERT INTO #tt(date_value,adjustment,applied_calc)
VALUES
('2001-01-31',0.00,1.00),
('2001-02-28',125.00,1.10),
('2001-03-31',400.00,1.03),
('2001-04-30',500,1.05),
('2001-05-31',100,1),
('2001-06-30',125,1.03);
DECLARE c_uv CURSOR
FOR SELECT adjustment,applied_calc FROM #tt ORDER BY date_value
FOR UPDATE OF inventory;
OPEN c_uv;
DECLARE @adjustment DECIMAL(28,2);
DECLARE @applied_calc DECIMAL(28,2);
DECLARE @inventory NUMERIC(28,0) = 100000;
WHILE 1=1
BEGIN
FETCH NEXT FROM
c_uv
INTO
@adjustment,
@applied_calc;
IF @@FETCH_STATUS<>0
BREAK;
SET @inventory=@inventory*@applied_calc+@adjustment;
UPDATE
#tt
SET
inventory=@inventory
WHERE
CURRENT OF c_uv;
END
CLOSE c_uv;
DEALLOCATE c_uv;
SELECT
*
FROM
#tt
ORDER BY
date_value;
DROP TABLE #tt;
两个脚本都给出以下结果:
date_value adjustment applied_calc inventory
2001-01-31 0.00 1.00 100000
2001-02-28 125.00 1.10 110125
2001-03-31 400.00 1.03 113829
2001-04-30 500.00 1.05 120020
2001-05-31 100.00 1.00 120120
2001-06-30 125.00 1.03 123849
谢谢TT的帮助!
我尝试使用 CTE 来回答问题 - 您的建议很可能是更好的选择。这是我想出的:
CREATE TABLE #tt(
RowID INT,
date_value DATE,
Adjustment DECIMAL(28,2),
AppliedCalc DECIMAL(28,2)
);
DECLARE @BeginValue Decimal(28,2)=100000;
INSERT INTO #tt(RowID,date_value,Adjustment,AppliedCalc)
VALUES
(1,'2001-01-31',0.00,1.00),
(2,'2001-02-28',125.00,1.10),
(3,'2001-03-31',400.00,1.03),
(4,'2001-04-30',500.00,1.05),
(5,'2001-05-31',0.00,1),
(6,'2001-06-30',1.25,1.03);
;with cteOutput as (
SELECT t.RowID, date_value, isnull(t.AppliedCalc,1) as AppliedCalc, Adjustment,Cast(@BeginValue as Decimal(28,2)) as AdjMV
FROM #tt t
WHERE t.RowID = 1
UNION ALL
SELECT t.RowID, t.date_value, t.AppliedCalc, t.Adjustment,cast((t.AppliedCalc)*cte.Adjmv+isnull(t.Adjustment,0) as decimal(28,2)) as AdjMV
FROM #tt t
INNER JOIN cteOutput cte
ON t.RowID-1 = cte.RowID)
SELECT cte.RowID, cte.date_value,cte.AppliedCalc, cte.Adjustment, cte.AdjMV
FROM cteOutput cte
ORDER BY rowid
OPTION(maxrecursion 0)
DROP TABLE #tt;
和输出:
RowID date_value Inventory AppliedCalc Adjustment
1 2001-01-31 100000 1.00 0.00
2 2001-02-28 110125 1.10 125.00
3 2001-03-31 113828 1.03 400.00
4 2001-04-30 120020 1.05 500.00
5 2001-05-31 120120 1.00 100.00
6 2001-06-30 123848 1.03 125.00