使用 while 循环创建计算度量值的 SQL 替代方法

SQL alternative to using while loop to create calculated measure

提问人:DataWrangler1980 提问时间:2/8/2023 最后编辑:Bill Tür stands with UkraineDataWrangler1980 更新时间:2/9/2023 访问量:27

问:

我正在通过执行一些计算来创建一个字段。 然后在下一次迭代中,我使用该结果来计算一个新值。目前使用 WHILE 循环执行此操作。还有其他方法吗?

我尝试过 LAG 和 Partition,甚至是递归 CTE,但无法达到相同的结果:

DECLARE @Period INT = 2;
DECLARE @MaxPeriod INT;

SELECT @MaxPeriod = Periods FROM dbo.Engine (NOLOCK);

WHILE (@Period <= @MaxPeriod)
BEGIN

    WITH CTE AS (
        SELECT 
            A.ProjectId, A.TypeId, A.Trial, A.Period, 
            ((B.ValueA * (COALESCE(B.FactorCalculated, 0) + 1)) +
                (A.ValueA * 0)) / A.ValueA AS FactorCalculated
        FROM dbo.PenetrationResults A (NOLOCK)
        INNER JOIN dbo.PenetrationResults B (NOLOCK)
            ON A.ProjectId = B.ProjectId
                AND A.TypeId = B.TypeId
                AND A.Trial = B.Trial
                AND (A.Period - 1) = B.Period 
        WHERE A.[Period] = @Period
        AND A.ProjectId = @ProjectId
    )
    UPDATE [target]
    SET FactorCalculated = CTE.FactorCalculated
    FROM dbo.PenetrationResults AS [target] --(TABLOCK)
    INNER JOIN CTE 
        ON [target].[ProjectId] = CTE.ProjectId AND [target].[TypeId] = CTE.TypeId 
        AND [target].[Trial] = CTE.Trial
        AND [target].[Period] = CTE.Period

    SET @Period = @Period + 1
END ;
t-sql while-loop

评论

1赞 Thom A 2/8/2023
坏习惯:到处都是NOLOCK
0赞 DataWrangler1980 2/9/2023
谢谢,这会改进这个脚本的设计,还是有什么方法可以让我使用递归 CTE 实现类似的结果?
0赞 Thom A 2/9/2023
可以肯定的是,这将提高它的可靠性。至于解决方案,您最好在此处以可消费的格式提供示例数据和预期结果。

答:

0赞 DataWrangler1980 2/9/2023 #1

好吧,我终于找到了一种使用 CTE 递归能力完成这项工作的方法,将性能提高了 4 倍


WITH ncte AS (
    -- number the rows sequentially without gaps
    SELECT 
        ProjectId,
        TypeId,
        Trial,
        [Period],
        FactorWifiCumulative--,
        --FactorConnectedCalc
    FROM dbo.PenetrationResults (NOLOCK) 
    WHERE ProjectId = @ProjectId

), rcte AS (
    -- find first row in each group
    SELECT *, CAST(0  AS DECIMAL(21,14)) AS Calc
    FROM ncte AS base
    WHERE Period = 1   
    UNION ALL
    -- find next row for each group from prev rows
    SELECT curr.*, 

            CAST(
            (
            (   
                prev.FactorWifiCumulative
            *
            (
                COALESCE(prev.Calc,0)
                + 1
            )
            )
            +
            (
                curr.FactorWifiCumulative*0
            )
            )
            /
            curr.FactorWifiCumulative AS DECIMAL(21,14))
            AS Calc    
    
    --CAST(prev.Calc * (1 + curr.FactorWifiCumulative / 100) AS DECIMAL(21, 14))
    
    FROM ncte AS curr
    INNER JOIN rcte AS prev 
    ON  curr.Period = prev.Period + 1
    AND curr.TypeId = prev.TypeId
    AND curr.Trial = prev.Trial
)
UPDATE [target] 
SET [FactorConnectedCalc] = [Calc]
FROM dbo.PenetrationResults AS [target] (NOLOCK) 
INNER JOIN rcte 
ON [target].[ProjectId] = rcte.ProjectId 
AND [target].[TypeId] = rcte.TypeId
AND [target].[Trial] = rcte.Trial
AND [target].[Period] = rcte.Period
OPTION(MAXRECURSION 0);