gpt4 book ai didi

sql-server - 计算运行总额/运行余额

转载 作者:行者123 更新时间:2023-12-01 17:48:40 25 4
gpt4 key购买 nike

我有一张 table :

create table Transactions(Tid int,amt int)

有 5 行:
insert into Transactions values(1, 100)
insert into Transactions values(2, -50)
insert into Transactions values(3, 100)
insert into Transactions values(4, -100)
insert into Transactions values(5, 200)

期望的输出:
TID  amt  balance
--- ----- -------
1 100 100
2 -50 50
3 100 150
4 -100 50
5 200 250

基本上第一条记录余额将与 amt 相同, 2nd 以后的余额将是以前余额的加法 + 当前余额 amt .我正在寻找一种最佳方法。我可以考虑使用函数或相关子查询,但不确定如何去做。

最佳答案

对于那些不使用 SQL Server 2012 或更高版本的人来说,游标可能是 CLR 之外支持和保证最有效的方法。还有其他方法,例如“古怪的更新”,它可以稍微快一点,但不能保证在 future 工作,当然,随着表变大,具有双曲线性能配置文件的基于集合的方法,以及通常需要直接的递归 CTE 方法#tempdb I/O 或导致产生大致相同影响的溢出。

INNER JOIN - 不要这样做:
缓慢的、基于集合的方法的形式如下:

SELECT t1.TID, t1.amt, RunningTotal = SUM(t2.amt)
FROM dbo.Transactions AS t1
INNER JOIN dbo.Transactions AS t2
ON t1.TID >= t2.TID
GROUP BY t1.TID, t1.amt
ORDER BY t1.TID;
这是慢的原因?随着表变大,每个增量行都需要读取表中的 n-1 行。这是指数级的,必然会出现故障、超时或愤怒的用户。

相关子查询 - 也不要这样做:
出于同样痛苦的原因,子查询表单同样痛苦。
SELECT TID, amt, RunningTotal = amt + COALESCE(
(
SELECT SUM(amt)
FROM dbo.Transactions AS i
WHERE i.TID < o.TID), 0
)
FROM dbo.Transactions AS o
ORDER BY TID;

古怪的更新 - 请自行承担风险:
“古怪的更新”方法比上述方法更有效,但行为没有记录,没有顺序保证,行为今天可能有效但将来可能会中断。我包括这个是因为它是一种流行的方法并且它是有效的,但这并不意味着我认可它。我什至回答这个问题而不是将其关闭的主要原因是因为 the other question has a quirky update as the accepted answer .
DECLARE @t TABLE
(
TID INT PRIMARY KEY,
amt INT,
RunningTotal INT
);

DECLARE @RunningTotal INT = 0;

INSERT @t(TID, amt, RunningTotal)
SELECT TID, amt, RunningTotal = 0
FROM dbo.Transactions
ORDER BY TID;

UPDATE @t
SET @RunningTotal = RunningTotal = @RunningTotal + amt
FROM @t;

SELECT TID, amt, RunningTotal
FROM @t
ORDER BY TID;

递归 CTE
第一个依赖于 TID 是连续的,没有间隙:
;WITH x AS
(
SELECT TID, amt, RunningTotal = amt
FROM dbo.Transactions
WHERE TID = 1
UNION ALL
SELECT y.TID, y.amt, x.RunningTotal + y.amt
FROM x
INNER JOIN dbo.Transactions AS y
ON y.TID = x.TID + 1
)
SELECT TID, amt, RunningTotal
FROM x
ORDER BY TID
OPTION (MAXRECURSION 10000);
如果你不能依赖这个,那么你可以使用这个变体,它简单地使用 ROW_NUMBER() 构建一个连续的序列。 :
;WITH y AS 
(
SELECT TID, amt, rn = ROW_NUMBER() OVER (ORDER BY TID)
FROM dbo.Transactions
), x AS
(
SELECT TID, rn, amt, rt = amt
FROM y
WHERE rn = 1
UNION ALL
SELECT y.TID, y.rn, y.amt, x.rt + y.amt
FROM x INNER JOIN y
ON y.rn = x.rn + 1
)
SELECT TID, amt, RunningTotal = rt
FROM x
ORDER BY x.rn
OPTION (MAXRECURSION 10000);
根据数据的大小(例如,我们不知道的列),您可能会发现通过首先将相关列填充到 #temp 表中,然后针对该列而不是基表进行处理来获得更好的整体性能:
CREATE TABLE #x
(
rn INT PRIMARY KEY,
TID INT,
amt INT
);

INSERT INTO #x (rn, TID, amt)
SELECT ROW_NUMBER() OVER (ORDER BY TID),
TID, amt
FROM dbo.Transactions;

;WITH x AS
(
SELECT TID, rn, amt, rt = amt
FROM #x
WHERE rn = 1
UNION ALL
SELECT y.TID, y.rn, y.amt, x.rt + y.amt
FROM x INNER JOIN #x AS y
ON y.rn = x.rn + 1
)
SELECT TID, amt, RunningTotal = rt
FROM x
ORDER BY TID
OPTION (MAXRECURSION 10000);

DROP TABLE #x;
只有第一个 CTE 方法将提供与古怪更新相媲美的性能,但它对数据的性质做出了很大的假设(没有间隙)。其他两种方法将回退,在这些情况下,您也可以使用游标(如果您不能使用 CLR 并且您尚未使用 SQL Server 2012 或更高版本)。

光标
每个人都被告知游标是邪恶的,应该不惜一切代价避免它们,但这实际上击败了大多数其他受支持方法的性能,并且比古怪的更新更安全。与游标解决方案相比,我唯一喜欢的是 2012 和 CLR 方法(如下):
CREATE TABLE #x
(
TID INT PRIMARY KEY,
amt INT,
rt INT
);

INSERT #x(TID, amt)
SELECT TID, amt
FROM dbo.Transactions
ORDER BY TID;

DECLARE @rt INT, @tid INT, @amt INT;
SET @rt = 0;

DECLARE c CURSOR LOCAL STATIC READ_ONLY FORWARD_ONLY
FOR SELECT TID, amt FROM #x ORDER BY TID;

OPEN c;

FETCH c INTO @tid, @amt;

WHILE @@FETCH_STATUS = 0
BEGIN
SET @rt = @rt + @amt;
UPDATE #x SET rt = @rt WHERE TID = @tid;
FETCH c INTO @tid, @amt;
END

CLOSE c; DEALLOCATE c;

SELECT TID, amt, RunningTotal = rt
FROM #x
ORDER BY TID;

DROP TABLE #x;

SQL Server 2012 或以上
SQL Server 2012 中引入的新窗口函数使这项任务变得更加容易(并且它的性能也比上述所有方法都要好):
SELECT TID, amt, 
RunningTotal = SUM(amt) OVER (ORDER BY TID ROWS UNBOUNDED PRECEDING)
FROM dbo.Transactions
ORDER BY TID;
请注意,在较大的数据集上,您会发现上述方法的性能比以下两个选项中的任何一个都要好得多,因为 RANGE 使用磁盘假脱机(并且默认使用 RANGE)。然而,同样重要的是要注意行为和结果可能不同,因此在根据这种差异做出决定之前,请确保它们都返回正确的结果。
SELECT TID, amt, 
RunningTotal = SUM(amt) OVER (ORDER BY TID)
FROM dbo.Transactions
ORDER BY TID;

SELECT TID, amt,
RunningTotal = SUM(amt) OVER (ORDER BY TID RANGE UNBOUNDED PRECEDING)
FROM dbo.Transactions
ORDER BY TID;

CLR
为了完整起见,我提供了 Pavel Pawlowski 的 CLR 方法的链接,这是迄今为止 SQL Server 2012(但显然不是 2000)之前版本的首选方法。
http://www.pawlowski.cz/2010/09/sql-server-and-fastest-running-totals-using-clr/

结论
如果您使用的是 SQL Server 2012 或更高版本,那么选择是显而易见的——使用新的 SUM() OVER()构造(使用 ROWSRANGE )。对于早期版本,您需要比较替代方法在您的架构、数据上的性能,并考虑到与性能无关的因素,确定哪种方法适合您。这很可能是 CLR 方法。以下是我的建议,按优先顺序排列:
  • SUM() OVER() ... ROWS , 如果在 2012 年或以上
  • 如果可能,CLR 方法
  • 如果可能,第一个递归 CTE 方法
  • 光标
  • 其他递归 CTE 方法
  • 古怪的更新
  • 加入和/或相关子查询

  • 有关这些方法的性能比较的更多信息,请参阅 http://dba.stackexchange.com 上的此问题。 :
    https://dba.stackexchange.com/questions/19507/running-total-with-count

    我还在博客上发布了有关这些比较的更多详细信息:
    http://www.sqlperformance.com/2012/07/t-sql-queries/running-totals

    另外对于分组/分区运行总计,请参阅以下帖子:
    http://sqlperformance.com/2014/01/t-sql-queries/grouped-running-totals
    Partitioning results in a running totals query
    Multiple Running Totals with Group By

    关于sql-server - 计算运行总额/运行余额,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11310877/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com