简体   繁体   中英

Get the running average of a column in T-SQL

Okay so I have a table and in one column I have some data and the second column the average of the data. Example

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

How do I populate the avg column on insert with the running average of the Data column using T-SQL?

EDIT: Sorry guys, this was actually a stupid mistake I made. I assumed I had SQL 2014 but after trying Stephan's code and getting some errors, I went back to confirm and realize I use SQL 2008. Sorry for the misinformation. I have also updated the tags

On insert, assuming id is an identity and you are just putting in 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;

In SQL Server 2012+, it is easy enough just to get it on output:

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

Prior to SQL Server 2012, you would do this with a correlated subquery or apply :

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

Here performance might suffer if the table is large. However, an index on id, data would help.

Gordon Linoff has it on insert. If you want to do it with a trigger

Trigger Method

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

View method

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

Update Pre-Inserted Values with Self-Join

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

Update Pre-Inserted Values with 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

SQL Server <=2008 doesn't have the OVER(ORDER BY ...) clause for aggregate functions.

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

Use a view for this if you can. It's a bad design for a change in one row to invalidate data stored in other rows. Rows should represent independent facts.

I feel like this should work with a self join:

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

join will give:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM