使用前几行计算值

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