使用先前的行计算值

时间:2016-01-14 14:41:35

标签: sql-server-2012 cursor common-table-expression

我遇到的情况是我需要将查询的种子值设置为等于另一个的第一个值,并使用此值来计算下一行数据(与当前行的数据一起)。每个后续行都需要使用前面的行计算值。

我研究了窗口函数,例如OVER()LAG()我似乎无法在我的情况下使用它。下面是数据样本。

最终,我需要将之前的Inventory乘以当前行的AppliedCalc并添加当前行的Adjustment字段。这是将在下一行计算中使用的新Inventory字段。

  

库存=之前[库存] * [AppliedCalc] + [调整]

@BeginValue是应该提供查询的种子值。此值会更改,并将成为用于将来计算的初始库存。

@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语句提供种子值,但我无法将计算延续到第二行之后的后续行 - 我发现我需要为每个额外的行添加一个额外的子查询。除了使用游标之外,还有解决方案吗?

3 个答案:

答案 0 :(得分:2)

我尝试了几件事,但我无法提出 clean 解决方案。下面的解决方案是" 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;

使用游标(安全方法):

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

答案 1 :(得分:1)

感谢您的帮助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

答案 2 :(得分:0)

看来你正在寻找累计总数,下面是今天已经回答的使用窗口函数并在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