【问题标题】:Recursive Lag Column Calculation in SQLSQL中的递归滞后列计算
【发布时间】:2015-09-08 07:34:14
【问题描述】:

我正在尝试编写一个将计算表数据插入另一个表的过程。

我遇到的问题是我需要每一行的计算列受到前一行计算列的结果的影响。我试图滞后计算本身,但这不起作用!

如:

(Max 是我创建的一个函数,它返回两个值中的最大值)

Id   Product      Model      Column1    Column2
1    A            1          5          =MAX(Column1*2, Lag(Column2))
2    A            2          2          =MAX(Column1*2, Lag(Column2))
3    B            1          3          =MAX(Column1*2, Lag(Column2))

如果我在 SQL 中尝试上述操作:

SELECT
    Column1, 
    MyMAX(Column1,LAG(Column2, 1, 0) OVER (PARTITION BY Product ORDER BY Model ASC) As Column2 
FROM Source

...它说 column2 是未知的。

如果我 LAG Column2 计算得到的输出:

Select Column1, MyMAX(Column1,LAG(Column1*2, 1, 0) OVER (PARTITION BY Product ORDER BY Model ASC) As Column2

Id   Column1    Column2
1    5          10
2    2          10
3    3          6

为什么第 3 行有 6 个?因为 3*2 > 2*2。

我想要的输出:

Id   Column1    Column2
1    5          10
2    2          10
3    3          10

为什么第 3 行有 10 个?因为之前的结果 10 > 3*2

问题是我不能滞后 Column2 的结果 - 我只能滞后其他列或它们的计算!

是否有使用 LAG 实现此目的的技术,或者我必须使用递归 CTE 吗?我读到 LAG 成功了 CTE,所以我认为这是可能的。如果不是,这个“CTE”会是什么样子?

编辑:或者,我还能做些什么来解决这个计算?

【问题讨论】:

  • 您能否提供您的实际查询。 Lag 需要OVER() 子句,而Sql-Server 的MAX 不像MySql's GREATEST 那样工作
  • 这是一个 CLR 函数还是什么?当内置 MAX 函数已经存在时,您是如何创建一个名为 MAX 的函数的?如何在没有架构的情况下调用它而不出错?
  • 感谢 cmets - 这尚未实施,因为我无法找到解决此初始问题的方法。问题中的示例尽可能简单,以便我理解问题。一旦我找到了解决方案,我会将我学到的知识应用到我的解决方案中。我进行了一些修改以提高清晰度。
  • 您不能在创建它的查询中通过别名来引用计算列。

标签: sql sql-server recursion


【解决方案1】:

编辑

事后看来,这个问题是超过Column1 * 2 的运行分区最大值。它可以像这样简单地完成

SELECT Id, Column1, Model, Product,
       MAX(Column1 * 2) OVER (Partition BY Model, Product Order BY ID ASC) AS Column2
FROM Table1;

Fiddle

原答案

这是一种使用递归 CTE 完成此操作的方法,完全没有 LAG,方法是加入递增的行号。我没有假设您的Id 是连续的,因此添加了一个额外的ROW_NUMBER()。你没有提到任何分区,所以没有应用相同的。查询只是从第一行开始,然后投影当前Column1 * 2 或前面的Column2 中的较大者

WITH IncrementingRowNums AS
(
    SELECT Id, Column1, Column1 * 2 AS Column2, 
           ROW_NUMBER() OVER (Order BY ID ASC) AS RowNum
    FROM Table1
),
lagged AS
(
    SELECT Id, Column1, Column2, RowNum
    FROM IncrementingRowNums
    WHERE RowNum = 1

    UNION ALL

    SELECT i.Id, i.Column1, 
        CASE WHEN (i.Column2 > l.Column2) 
            THEN i.Column2 
            ELSE l.Column2 
        END, 
        i.RowNum
    FROM IncrementingRowNums i
    INNER JOIN lagged l
    ON i.RowNum = l.RowNum + 1
)
SELECT Id, Column1, Column2
FROM lagged;

SqlFiddle here

编辑、重新分区

分区大致相同,只需拖动 Model + Product 列,然后在行编号中按这些列进行分区(即每次 Product 或 Model 重置时从 1 开始),包括 CTE JOIN 条件和也在最终排序中。

WITH IncrementingRowNums AS
(
    SELECT Id, Column1, Column1 * 2 AS Column2, Model, Product,
           ROW_NUMBER() OVER (Partition BY Model, Product Order BY ID ASC) AS RowNum
    FROM Table1
),
lagged AS
(
    SELECT Id, Column1, Column2, Model, Product, RowNum
    FROM IncrementingRowNums
    WHERE RowNum = 1

    UNION ALL

    SELECT i.Id, i.Column1, 
        CASE WHEN (i.Column2 > l.Column2) 
            THEN i.Column2 
            ELSE l.Column2 
        END, 
        i.Model, i.Product,
        i.RowNum
    FROM IncrementingRowNums i
    INNER JOIN lagged l
    ON i.RowNum = l.RowNum + 1 
    AND i.Model = l.Model AND i.Product = l.Product
)
SELECT Id, Column1, Column2, Model, Product
FROM lagged
ORDER BY Model, Product, Id;

Updated Fiddle

【讨论】:

  • 这真的很好——虽然处理速度很慢,但我想我可以让它工作。您介意编辑答案以描述我将如何分区吗?例如,我想与上一行进行比较,但要针对每个产品和型号?
  • @Robert - 我已经更新了。性能应该由 Model、Product 上的索引(如果您的实际分区相反,请切换这些索引)和 Id 驱动。
  • 我不得不对其进行大量编辑以适应我的解决方案(正如预期的那样)。我最终使用现有的分区列作为行 num 以减少递归迭代的次数。我不知道 CTE,并且收到您自己示例的解决方案似乎是一种有效的学习方式。谢谢!!
  • @Robert - 实际上有一种更简单的方法可以做到这一点,使用分区运行的窗口最大值。昨晚我没有注意这个问题 - 根本没有看到模式!已更新。
  • 这似乎工作正常!这种替代方案更容易阅读,并且对我来说处理速度非常快。再次感谢!!
猜你喜欢
  • 2019-09-03
  • 2018-08-11
  • 2015-01-08
  • 2013-05-06
  • 2017-11-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-06
相关资源
最近更新 更多