【问题标题】:Calculate a Recursive Rolling Average in SQL Server在 SQL Server 中计算递归滚动平均值
【发布时间】:2018-01-05 13:30:28
【问题描述】:

我们正在尝试计算滚动平均值,并尝试转换大量 SO 答案来解决问题。至此,我们仍然没有成功。

我们的尝试

以下是我们考虑过的一些 SO 答案。

我们最近的尝试是修改此处找到的解决方案之一(#4)。 https://www.red-gate.com/simple-talk/sql/t-sql-programming/calculating-values-within-a-rolling-window-in-transact-sql/

示例

这是 SQL Fiddle 中的一个示例:http://sqlfiddle.com/#!6/4570a/17

在小提琴中,我们仍在努力使 SUM 正常工作,但最终我们试图获得平均值。

最终目标

使用 Fiddle 示例,我们需要找到 Value1 和 ComparisonValue1 之间的差异,并将其呈现为 Diff1。当一行没有可用的 Value1 时,我们需要通过取最后两个 Diff1 值的平均值来估计它,然后将其添加到该行的 ComparisonValue1。

使用正确的查询,结果将如下所示:

GroupID  Number  ComparisonValue1  Diff1  Value1
5        10      54.78             2.41   57.19
5        11      55.91             2.62   58.53
5        12      55.93             2.78   58.71
5        13      56.54             2.7    59.24
5        14      56.14             2.74   58.88
5        15      55.57             2.72   58.29
5        16      55.26             2.73   57.99

问题:如果该平均值可能会影响后续行的平均值,是否可以计算该平均值?

更新

  • 向 Fiddle 架构添加了一个 VIEW 以简化最终查询。
  • 更新了查询以包含 Diff1 的新滚动平均值(列 Diff1Last2Avg)。这个滚动平均值很有效,直到我们在 Value1 列中遇到空值。这是我们需要插入估算值的地方。
  • 更新了query 以包含在没有Value1 时应该使用的估计值(Value1Estimate 列)。这很好用,如果我们可以在 Value1 列中使用估计值代替 NULL ,那将是完美的。由于 Diff1 列反映了 Value1(或其估计值)和 ComparisonValue1 之间的差异,因此包括 Estimate 将填充 Diff1 中的所有 NULL 值。这反过来将继续允许计算未来行的估计值。在这一点上它变得令人困惑,但仍然在破解它。有什么想法吗?

【问题讨论】:

  • 如果可能的话,减少记录的数量,并以有问题的表格格式添加预期的结果。好问题
  • 什么版本的 SQL?
  • @Xedni SQL Server 2008
  • @Pரதீப் 在问题编辑中查看更新的小提琴和所需结果的示例。

标签: sql-server


【解决方案1】:

这个想法归功于这个答案:来自@JesúsLópez 的https://stackoverflow.com/a/35152131/6305294

我已经在代码中包含了 cmets 来解释它。

更新

  • 我已经更正了基于 cmets 的查询。
  • 我已经交换了被减数和减数中的数字,以获得正数的差值。
  • 删除了 Diff2Ago 列。

查询结果现在与您的示例输出完全匹配。

;WITH cte AS
(
    -- This is similar to your ItemWithComparison view
    SELECT i.Number, i.Value1, i2.Value1 AS ComparisonValue1,
        -- Calculated Differences; NULL will be returned when i.Value1 is NULL
        CONVERT( DECIMAL( 10, 3 ), i.Value1 - i2.Value1 ) AS Diff
    FROM Item AS i
            LEFT JOIN [Group] AS G ON g.ID = i.GroupID
            LEFT JOIN Item AS i2 ON i2.GroupID = g.ComparisonGroupID AND i2.Number = i.Number
    WHERE NOT i2.Id IS NULL
),
cte2 AS(
    /*
    Start with the first number

    Note if you do not have at least 2 consecutive numbers (in cte) with non-NULL Diff value and therefore Diff1Ago or Diff2Ago are NULL then everything else will not work;
    You may need to add additional logic to handle these cases */
    SELECT TOP 1 -- start with the 1st number (see ORDER BY)
            a.Number, a.Value1, a.ComparisonValue1, a.Diff, b.Diff AS Diff1Ago
    FROM cte AS a
            -- "1 number ago"
            LEFT JOIN cte AS b ON a.Number - 1 = b.Number
    WHERE NOT a.Value1 IS NULL
    ORDER BY a.Number
    UNION ALL
    SELECT b.Number, b.Value1, b.ComparisonValue1,
            ( CASE
                WHEN NOT b.Value1 IS NULL THEN b.Diff
                ELSE CONVERT( DECIMAL( 10, 3 ), ( a.Diff + a.Diff1Ago ) / 2.0 )
            END ) AS Diff,
        a.Diff AS Diff1Ago
    FROM cte2 AS a
        INNER JOIN cte AS b ON a.Number + 1 = b.Number
)
SELECT *, ( CASE WHEN Value1 IS NULL THEN ComparisonValue1 + Diff ELSE Value1 END ) AS NewValue1
FROM cte2 OPTION( MAXRECURSION 0 );

限制: 此解决方案仅在您需要考虑少量先前值时才有效。

【讨论】:

  • 哇,非常接近。更新了一些小问题,但它现在在 fiddle 中运行。虽然,结果与问题中的样本结果不匹配。第一个 Value1 空值的估计值应为 59.24。当我将查询移过来时,我是否搞砸了什么?我会仔细检查。
  • 另外,删除了 ABS 函数,因为负值对我们的应用程序很重要。
  • 我认为递归 cte2 查询中的 CASE ELSE 行需要是ELSE CONVERT( DECIMAL( 10, 3 ), ( a.Diff + a.Diff1Ago ) / 2.0 )
  • @RichC - 你是对的,更新我的答案,很抱歉没有检查实际数字
猜你喜欢
  • 1970-01-01
  • 2015-11-22
  • 1970-01-01
  • 2019-09-24
  • 1970-01-01
  • 2021-03-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多