【问题标题】:Get the running average of a column in T-SQL获取 T-SQL 中某列的运行平均值
【发布时间】:2015-06-21 19:45:24
【问题描述】:

好的,我有一个表格,其中一列有一些数据,第二列是数据的平均值。示例

id|Data|avg
1 |20  |20
2 |4   |12
3 |18  |14

如何使用 T-SQL 使用 Data 列的运行平均值填充插入时的 avg 列?

编辑:对不起,这实际上是我犯的一个愚蠢的错误。我以为我有 SQL 2014,但是在尝试了 Stephan 的代码并遇到了一些错误之后,我回去确认并意识到我使用的是 SQL 2008。对于错误信息,我深表歉意。我也更新了标签

【问题讨论】:

  • @Stephan,没有什么说 avg 列是 sql server 意义上的计算列。只是 OP 试图在插入时计算它。
  • 您能告诉我们您使用的是哪个版本的 SQL Server 吗?有办法做到这一点。
  • 不确定如何在插入时执行此操作(如果您的插入阻止其他查询,不确定从 I/O 角度来看这是一个好主意)但是这个问题(特别是接受的第二部分答案)是您试图解决的问题的优雅解决方案:stackoverflow.com/questions/26618353/…

标签: sql sql-server sql-server-2008 tsql


【解决方案1】:

在插入时,假设id 是一个身份,而您只是输入data

insert into table t(id, data, avg)
    select @data, @data * (1.0 / n) + avg * (n - 1.0)/n
    from (select count(*) as cnt, avg(data) as avg
          from t
         ) t;

在 SQL Server 2012+ 中,只需将其输出即可:

select t.*, avg(data) over (order by id) as cume_avg
from table t

在 SQL Server 2012 之前,您可以使用相关子查询或apply

select t.*,
       (select avg(data)
        from table t2
        where t2.id <= t.id
       ) as cume_avg 
from table t;

如果表很大,这里的性能可能会受到影响。但是,id, data 上的索引会有所帮助。

【讨论】:

  • 如果我们只在插入时设置了avg,那么当这些记录的Data列更新时,avg列就会失效。示例:如果 id:2 的数据从 4 更改为 14,则 id:2 的 avg 仍将显示 12 而不是 17 ([20+14]/2)。作为视图的 Gordon 的第二个解决方案将允许您更新源表中的数据,而不会有使 avg 列无效的风险,同时让您可以通过新视图轻松访问 avg。
【解决方案2】:

Gordon Linoff 在插入时有它。如果你想用触发器来做

触发方式

IF OBJECT_ID('myTable') IS NOT NULL
    DROP TABLE myTable;

CREATE TABLE myTable(ID INT, Data INT,[avg] INT);
GO

CREATE TRIGGER trg_running_avg ON myTable
INSTEAD OF INSERT
AS
BEGIN
    INSERT INTO myTable
        SELECT ID,Data,AVG(Data) OVER (ORDER BY ID ROWS UNBOUNDED PRECEDING)
        FROM inserted
END

INSERT INTO myTable(ID,Data)
VALUES  (1,20),(2,4),(3,18)

SELECT *
FROM myTable

查看方法

CREATE VIEW vw_average
AS
SELECT ID,Data,AVG(Data) OVER (ORDER BY ID ROWS UNBOUNDED PRECEDING)
        FROM inserted

使用自联接更新预先插入的值

UPDATE myTable
SET avg = running_avg
FROM myTable A
INNER JOIN (SELECT ID,AVG(Data) OVER (ORDER BY ID ROWS UNBOUNDED PRECEDING) running_avg FROM myTable) B
ON A.ID = B.ID

使用 CTE 更新预先插入的值

WITH CTE_Update
AS 
(
    SELECT  ID,
            [avg] OldAvg,
            AVG(Data) OVER (ORDER BY ID) AS NewAvg
    FROM myTable
) 
UPDATE CTE_Update SET OldAvg = NewAvg

【讨论】:

  • 你的触发方法有缺陷。填充表格后尝试INSERT myTable VALUES (0,-1000000,DEFAULT)ID&gt;0 现在非常错误的所有行的运行平均值。
  • 触发器仅用于插入新ID。不插入之前的值。像旧值这样的问题并且不想每次都更新你的表是我推荐使用视图的原因。
  • 插入的新 ID 的值可能低于现有 ID。您的触发器应该考虑到这种可能性。
  • 另外,您的更新语句有一个不必要的自联接。这做同样的事情要快得多:WITH t(old,new) AS (SELECT ID,AVG(Data) OVER (ORDER BY ID) FROM myTable) UPDATE t SET old = new
  • 实际上,这只会更新您的 CTE "t"。自联接是必要的,因为 windows 函数只允许在 SELECT 和 ORDER BY 子句中使用。您不能在 SET 子句中使用它
【解决方案3】:

SQL Server OVER(ORDER BY ...) 子句。

CREATE TRIGGER trg_running_avg ON myTable
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
  UPDATE old
    SET avg = new_avg
  FROM myTable old
  CROSS APPLY (
    SELECT AVG(Data) AS new_avg FROM myTable WHERE ID <= old.ID
  ) new
  --Skip the full table update. Start from the lowest ID that was changed.
  WHERE id >= (SELECT MIN(id) FROM (SELECT ID FROM inserted UNION ALL SELECT ID FROM deleted) t)
END
GO

如果可以,请为此使用视图。更改一行以使存储在其他行中的数据无效是一种糟糕的设计。行应该代表独立的事实。

【讨论】:

  • 根据他对帖子的评论,他正在使用 SQL 2014
  • 我更新了这个问题,以表明我错了,我确实在使用 2008 年。对于错误信息感到抱歉
【解决方案4】:

我觉得这应该适用于自加入:

select t1.id, t1.data, sum(t2.data)/t1.id as avg
from table t1, table t2
where t1.id>=t2.id group by t1.id

加入将给予:

t1.id|t1.Data|t2.id|t2.Data
1    |  20   |  1  |   20
2    |  4    |  1  |   20
2    |  4    |  2  |   4
3    |  18   |  1  |   20
3    |  18   |  2  |   4
3    |  18   |  3  |   18

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-02-09
    • 1970-01-01
    • 2021-02-14
    • 2018-10-02
    相关资源
    最近更新 更多