【问题标题】:TSQL calculate row value based on value in previous row in same columnTSQL根据同一列中前一行的值计算行值
【发布时间】:2017-09-12 12:02:50
【问题描述】:

我有一个数据集,我需要计算一个值,该值的每一行取决于同一列的前一行中的值。或者当没有上一行时最初为 1。我需要在不同的分区上执行此操作。

公式如下所示:factor = (前一个因子,如果不存在则为 1) * (1 + div / nav) 这需要通过 Inst_id 进行分区。

我宁愿避免使用光标。也许 cte 带有递归 - 但我无法理解它 - 或其他方式?

我知道这段代码不起作用,因为我无法引用同一列,但这是显示我正在尝试做的事情的另一种方式:

SELECT Dato, Inst_id, nav, div
     , (1 + div / nav ) * ISNULL(LAG(factor, 1) OVER (PARTITION BY Inst_id ORDER BY Date), 1) AS factor
FROM @tmp

因此,我需要使用我的测试数据在下面的因子列中获得这些结果。 请忽略舍入问题,因为我是在 Excel 中计算的:

date     Inst_id    nav     div     factor
11-04-2012  16  57.5700     5.7500  1.09987841
19-04-2013  16  102.8600    10.2500 1.20948130
29-04-2014  16  65.9300     16.7500 1.51675890
08-04-2013  29  111.2736    17.2500 1.15502333
10-04-2014  29  101.9650    16.3000 1.33966395
15-04-2015  29  109.5400    7.5000  1.43138825
27-04-2016  29  94.2500     0.4000  1.43746311
15-04-2015  34  159.1300    11.4000 1.07163954
27-04-2016  34  124.6100    17.6000 1.22299863
26-04-2017  34  139.7900    9.2000  1.30348784
01-04-2016  38  99.4600     0.1000  1.00100543
26-04-2017  38  102.9200    2.1000  1.02143014

测试数据:

DECLARE @tmp TABLE(Dato DATE, Inst_id INT, nav DECIMAL(26,19), div DECIMAL(26,19), factor DECIMAL(26,19))
INSERT INTO @tmp (Dato, Inst_id, nav, div) VALUES
('2012-04-11', 16, 57.57, 5.75),
('2013-04-19', 16, 102.86, 10.25),
('2014-04-29', 16, 65.93, 16.75),
('2013-04-08', 29, 111.273577, 17.25),
('2014-04-10', 29, 101.964994, 16.3),
('2015-04-15', 29, 109.54, 7.5),
('2016-04-27', 29, 94.25, 0.4),
('2015-04-15', 34, 159.13, 11.4),
('2016-04-27', 34, 124.61, 17.6),
('2017-04-26', 34, 139.79, 9.2)

我使用的是 Microsoft SQL Server Enterprise 2016(并使用 SSMS 2016)。

【问题讨论】:

    标签: sql-server tsql recursion sql-server-2016 multiplication


    【解决方案1】:

    您可以使用(如果 DIV 和 NAV 始终 >0):

    SELECT A.* , EXP(SUM( LOG(1+DIV/NAV) ) OVER (PARTITION BY INST_ID ORDER BY DATO) )AS FACT_NEW
    FROM @tmp A
    

    其实你需要的是一个等价的聚合函数 MULTIPLY() OVER ...。 使用对数定理:LOG(M*N) = LOG(M) + LOG (N) 你可以做到;例如:

    DECLARE @X1 NUMERIC(10,4)=5
    DECLARE @X2 NUMERIC(10,4)=7
    SELECT @x1*@x2 AS S1, EXP(LOG(@X1)+LOG(@X2)) AS S2
    

    输出:

    +------------+---------+-------------------------+------------------------+--------+------------------+
    |    Dato    | Inst_id |           nav           |          div           | factor |     FACT_NEW     |
    +------------+---------+-------------------------+------------------------+--------+------------------+
    | 2012-04-11 |      16 |  57.5700000000000000000 |  5.7500000000000000000 | NULL   |   1.099878408893 |
    | 2013-04-19 |      16 | 102.8600000000000000000 | 10.2500000000000000000 | NULL   | 1.20948130303111 |
    | 2014-04-29 |      16 |  65.9300000000000000000 | 16.7500000000000000000 | NULL   | 1.51675889783963 |
    | 2013-04-08 |      29 | 111.2735770000000000000 | 17.2500000000000000000 | NULL   |   1.155023325977 |
    | 2014-04-10 |      29 | 101.9649940000000000000 | 16.3000000000000000000 | NULL   | 1.33966395090911 |
    | 2015-04-15 |      29 | 109.5400000000000000000 |  7.5000000000000000000 | NULL   | 1.43138824917236 |
    | 2016-04-27 |      29 |  94.2500000000000000000 |  0.4000000000000000000 | NULL   | 1.43746310646293 |
    | 2015-04-15 |      34 | 159.1300000000000000000 | 11.4000000000000000000 | NULL   |   1.071639539998 |
    | 2016-04-27 |      34 | 124.6100000000000000000 | 17.6000000000000000000 | NULL   | 1.22299862758278 |
    | 2017-04-26 |      34 | 139.7900000000000000000 |  9.2000000000000000000 | NULL   | 1.30348784264639 |
    +------------+---------+-------------------------+------------------------+--------+------------------+
    

    【讨论】:

    • 太棒了!是的,div 和 nav 总是 > 0。所以这是一个简短而漂亮的解决方案。使用 LOG 和 EXP 启用 SUM 进行乘法是一个不错的技巧 :)
    【解决方案2】:

    使用递归 CTE:

    WITH DataSource AS
    (
        SELECT *
              ,ROW_NUMBER() OVER (PARTITION BY Inst_id ORDER BY Dato) AS [rowId]
        FROM @tmp
    ),
    RecursiveDataSource AS
    (
        SELECT *
              ,CAST((1 + div / nav ) * 1 AS DECIMAL(26,19)) as [factor_calculated]
        FROM DataSource
        WHERE [rowId] = 1
        UNION ALL
        SELECT A.*
              ,CAST((1 + A.div / A.nav ) * R.factor_calculated AS DECIMAL(26,19)) as [factor_calculated]
        FROM RecursiveDataSource R
        INNER JOIN DataSource A
            ON r.[Inst_id] = A.[Inst_id]
            AND R.[rowId] + 1 = A.[rowId]
    )
    SELECT *
    FROM RecursiveDataSource
    ORDER BY Inst_id, Dato;
    

    我猜你在第 3 行之后在 Excel 中得到了不同的值,因为你没有在那里按Inst_id 进行分区。

    【讨论】:

    • 太好了,这就是我认为我需要的。但是,我只能将一个答案标记为已接受,因此这必须是 etsa 的,因为这是我将在这种情况下使用的解决方案。但是非常感谢你,因为我可以从这个 cte 示例中学到很多东西,并且该技术肯定会再次帮助我。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-11
    • 1970-01-01
    相关资源
    最近更新 更多