用于计算运行平均列的 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 语句的主要内容,如果未能解决你的问题,请参考以下文章