用于计算运行平均列的 SQL Select 语句

Posted

技术标签:

【中文标题】用于计算运行平均列的 SQL Select 语句【英文标题】:SQL Select Statement For Calculating A Running Average Column 【发布时间】:2009-05-26 15:32:07 【问题描述】:

我试图在 SELECT 语句中根据同一 SELECT 语句中前 n 行中的一列创建一个运行平均列。我需要的平均值基于结果集中的前 n 行。

让我解释一下

Id        Number       Average
 1             1          NULL
 2             3          NULL
 3             2          NULL
 4             4             2 <----- Average of (1, 3, 2),Numbers from previous 3 rows
 5             6             3 <----- Average of (3, 2, 4),Numbers from previous 3 rows
 .             .             .
 .             .             .

Average 列的前 3 行为空,因为没有之前的行。 Average 列中的第 4 行显示前 3 行的 Number 列的平均值。

我需要一些帮助来尝试构建一个可以执行此操作的 SQL Select 语句。

【问题讨论】:

你用的是什么SQL数据库? 我认为这是游标最快的极少数情况之一......只需将最后 3 行保留在 vars 中...... @sambo99 - 实际上,有很多方法可以以基于集合的方式执行此操作,对于任何大型数据集,它们通常比使用光标更快 任何基于集合的解决方案都必须进行自连接,如果您只想在我怀疑光标是唯一方法时运行数据。 游标在这里可能不是一个好的选择,因为需要“前 n 行的平均值”。这里,n 可以是任意数字。 【参考方案1】:

应该这样做:

--Test Data
CREATE TABLE    RowsToAverage
    (
    ID int NOT NULL,
    Number int NOT NULL
    )

INSERT  RowsToAverage(ID, Number)
SELECT  1, 1
UNION ALL
SELECT  2, 3
UNION ALL
SELECT  3, 2
UNION ALL
SELECT  4, 4
UNION ALL
SELECT  5, 6
UNION ALL
SELECT  6, 8
UNION ALL
SELECT  7, 10

--The query
;WITH   NumberedRows
AS
(
SELECT  rta.*, row_number() OVER (ORDER BY rta.ID ASC) AS RowNumber
FROM    RowsToAverage rta
)

SELECT  nr.ID, nr.Number,
        CASE
            WHEN nr.RowNumber <=3 THEN NULL
            ELSE (  SELECT  avg(Number) 
                    FROM    NumberedRows 
                    WHERE   RowNumber < nr.RowNumber
                    AND     RowNumber >= nr.RowNumber - 3
                )
        END AS MovingAverage
FROM    NumberedRows nr

【讨论】:

非常优雅的解决方案。有 9,000 行,在我的开发服务器上大约需要 45 秒。有什么方法可以更有效地使用这种技术。【参考方案2】:

假设 Id 列是连续的,下面是对名为“MyTable”的表的简化查询:

SELECT 
    b.Id,
    b.Number,
    (
      SELECT 
       AVG(a.Number) 
      FROM 
       MyTable a 
     WHERE 
       a.id >= (b.Id - 3) 
       AND a.id < b.Id
       AND b.Id > 3 
     ) as Average
FROM 
    MyTable b;

【讨论】:

如果表中没有删除任何行,这也可以工作。我接受了 Aaron Alton 的解决方案,因为 row_number() OVER (ORDER BY rta.ID ASC) 适用于所有情况。【参考方案3】:

简单的自联接似乎比引用子查询的行执行得更好

生成一万行测试数据:

drop table test10k
create table test10k (Id int, Number int, constraint test10k_cpk primary key clustered (id))

;WITH digits AS (
    SELECT 0 as Number
    UNION SELECT 1
    UNION SELECT 2
    UNION SELECT 3
    UNION SELECT 4
    UNION SELECT 5
    UNION SELECT 6
    UNION SELECT 7
    UNION SELECT 8
    UNION SELECT 9
)
,numbers as (
    SELECT 
        (thousands.Number * 1000) 
        + (hundreds.Number * 100) 
        + (tens.Number * 10) 
        + ones.Number AS Number
    FROM digits AS ones 
    CROSS JOIN digits AS tens
    CROSS JOIN digits AS hundreds
    CROSS JOIN digits AS thousands
)
insert test10k (Id, Number)
select Number, Number
from numbers 

我会将前 3 行的特殊情况从主查询中提取出来,如果你真的想要在行集中,你可以 UNION ALL 回来。自联接查询:

;WITH   NumberedRows
AS
(
    SELECT  rta.*, row_number() OVER (ORDER BY rta.ID ASC) AS RowNumber
    FROM    test10k rta
)

SELECT  nr.ID, nr.Number,
    avg(trailing.Number) as MovingAverage
FROM    NumberedRows nr
    join NumberedRows as trailing on trailing.RowNumber between nr.RowNumber-3 and nr.RowNumber-1
where nr.Number > 3
group by nr.id, nr.Number

在我的机器上这大约需要 10 秒,Aaron Alton 演示的子查询方法大约需要 45 秒(在我更改它以反映我的测试源表之后):

;WITH   NumberedRows
AS
(
    SELECT  rta.*, row_number() OVER (ORDER BY rta.ID ASC) AS RowNumber
    FROM    test10k rta
)
SELECT  nr.ID, nr.Number,
    CASE
            WHEN nr.RowNumber <=3 THEN NULL
            ELSE (  SELECT  avg(Number) 
                            FROM    NumberedRows 
                            WHERE   RowNumber < nr.RowNumber
                            AND             RowNumber >= nr.RowNumber - 3
                    )
    END AS MovingAverage
FROM    NumberedRows nr

如果您执行 SET STATISTICS PROFILE ON,您可以看到自联接在表假脱机上执行了 10k 次。子查询在过滤器、聚合和其他步骤上执行了 10k 次。

【讨论】:

【参考方案4】:

编辑:我错过了应该平均前三个记录的点......

对于一般的运行平均值,我认为这样的事情会起作用:

SELECT
    id, number, 
    SUM(number) OVER (ORDER BY ID) / 
       ROW_NUMBER() OVER (ORDER BY ID) AS [RunningAverage]
FROM myTable
ORDER BY ID

【讨论】:

在使用@Aaron Alton 的 RowsToAverage 表时(我将 FROM MyTable 更改为 FROM RowsToAverage),我收到错误消息:Msg 102, Level 15, State 1, Line 3 Incorrect syntax near 'order'。跨度> 您使用的是哪个 RDBMS?窗口函数仅在 SQL 2005 及更高版本中可用。 我应该补充一点,OP 提到他们正在使用 SQL 2008。【参考方案5】:

查看一些解决方案here。我相信你可以很容易地适应其中之一。

【讨论】:

虽然理论上这可以回答这个问题,it would be preferable 在这里包含答案的基本部分,并提供链接以供参考。【参考方案6】:

如果您希望它真正具有高性能,并且不怕深入 SQL Server 很少使用的领域,您应该考虑编写自定义聚合函数。 SQL Server 2005 和 2008 将 CLR 集成到表中,包括编写用户聚合函数的能力。到目前为止,自定义运行总聚合将是计算此类运行平均值的最有效方法。

【讨论】:

【参考方案7】:

或者,您可以非规范化并存储预先计算的运行值。此处描述:

http://sqlblog.com/blogs/alexander_kuznetsov/archive/2009/01/23/denormalizing-to-enforce-business-rules-running-totals.aspx

选择的性能尽可能快。当然,修改比较慢。

【讨论】:

以上是关于用于计算运行平均列的 SQL Select 语句的主要内容,如果未能解决你的问题,请参考以下文章

SQL server select语句用于选择另一列的重复条目的ID

数据库SQL语句学习笔记-汇总数据

SQL语句优化-关于分组求平均值

SQL语句汇总——聚合函数分组子查询及组合查询

sql语句中的group by啥意思

8.聚集函数 ---SQL