Обща сума. АКТУАЛИЗИРАНЕ на временна таблица спрямо CTE
create table Test(
OrderID int primary key,
Qty int not null
);
declare @i int = 1;
while @i <= 5000 begin
insert into Test(OrderID, Qty) values (@i * 2,rand() * 10);
set @i = @i + 1;
end;
Рекурсивно решение отнема 9 секунди:
with T AS
(
select ROW_NUMBER() over(order by OrderID) as rn, * from test
)
,R(Rn, OrderId, Qty, RunningTotal) as
(
select Rn, OrderID, Qty, Qty
from t
where rn = 1
union all
select t.Rn, t.OrderId, t.Qty, p.RunningTotal + t.Qty
from t t
join r p on t.rn = p.rn + 1
)
select R.OrderId, R.Qty, R.RunningTotal from r
option(maxrecursion 0);
АКТУАЛИЗАЦИЯ на таблицата отнема 0 секунди:
create function TestRunningTotal()
returns @ReturnTable table(
OrderId int, Qty int, RunningTotal int
)
as begin
insert into @ReturnTable(OrderID, Qty, RunningTotal)
select OrderID, Qty, 0 from Test
order by OrderID;
declare @RunningTotal int = 0;
update @ReturnTable set
RunningTotal = @RunningTotal,
@RunningTotal = @RunningTotal + Qty;
return;
end;
Тези два подхода биха могли поне да ви дадат рамка, върху която да изградите заявката си.
Между другото в SQL Server, за разлика от MySQL, редът на присвояване на променливи няма значение. Това:
update @ReturnTable set
RunningTotal = @RunningTotal,
@RunningTotal = @RunningTotal + Qty;
И следното:
update @ReturnTable set
@RunningTotal = @RunningTotal + Qty,
RunningTotal = @RunningTotal;
И двете се изпълняват по един и същи начин, т.е. присвояването на променливи се извършва първо, независимо от позицията на присвояването на променливата в оператора. И двете заявки имат същия резултат:
OrderId Qty RunningTotal
----------- ----------- ------------
2 4 4
4 8 12
6 4 16
8 5 21
10 3 24
12 8 32
14 2 34
16 9 43
18 1 44
20 2 46
22 0 46
24 2 48
26 6 54
На вашата точна таблица просто открийте Buy/Sell, можете или да го умножите съответно по 1 и -1, или просто да подпишете полетата, напр. :
update @ReturnTable set
@RunningTotal = @RunningTotal +
CASE WHEN BuySell = 'Buy' THEN Qty ELSE -Qty END,
RunningTotal = @RunningTotal;
Ако се случи да надстроите до SQL Server 2012, ето директната реализация на текущата обща сума:
select OrderID, Qty, sum(Qty) over(order by OrderID) as RunningTotal
from Test
За точния ви проблем:
select OrderID, Qty,
sum(CASE WHEN BuySell = 'Buy' THEN Qty ELSE -Qty END)
over(order by OrderID) as RunningTotal
from Test;
АКТУАЛИЗАЦИЯ
Ако се чувствате неспокойни с странна актуализация , можете да поставите предпазна клауза, за да проверите дали редът на редовете, които трябва да бъдат актуализирани, съвпада с оригиналния ред (подпомогнат от identity(1,1)):
create function TestRunningTotalGuarded()
returns @ReturnTable table(
OrderId int, Qty int,
RunningTotal int not null,
RN int identity(1,1) not null
)
as begin
insert into @ReturnTable(OrderID, Qty, RunningTotal)
select OrderID, Qty, 0 from Test
order by OrderID;
declare @RunningTotal int = 0;
declare @RN_check INT = 0;
update @ReturnTable set
@RN_check = @RN_check + 1,
@RunningTotal =
(case when RN = @RN_check then @RunningTotal + Qty else 1/0 end),
RunningTotal = @RunningTotal;
return;
end;
Ако UPDATE наистина актуализира редове в непредсказуем ред (или случайно ще го направи), @RN_Check вече няма да бъде равен на RN(identity order), кодът ще предизвика грешка при деление на нула тогава. Използвайки предпазна клауза, непредсказуемият ред на актуализиране ще откаже бързо
; ако това се случи тогава, ще е време да подадете бъг петиция до Microsoft да направи странната актуализация не толкова странна :-)
Хеджирането на предпазната клауза на присъщата императивна операция (присвояване на променлива) е наистина последователно.