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
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
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.