您的位置:首页 > 数据库

SQL集合运算参考及案例(一):列值分组累计求和

2016-07-29 17:58 411 查看
概述

目前企业应用系统使用的大多数据库都是关系型数据库,关系数据库依赖的理论就是针对集合运算的关系代数。关系代数是一种抽象的查询语言,是关系数据操纵语言的一种传统表达方式。不过我们在工作中发现,很多人在面对复杂的数据库运算逻辑时会采用游标、循环、自定义函数等方式处理,因为游标是一种比较熟悉和舒适的面向过程的编程方式,很符合我们一般的逻辑思维习惯,可很不幸,这会导致糟糕的性能。显然,SQL的总体目的是你要实现什么,而不是怎样实现。大道至简,我们在工作与学习的过程中经常会发现,更好的解决方案往往是简单的,是高效的,是优雅的。

本人曾经用T-SQL重写了一个基于游标的存储过程,那个表只有100,000条记录,原来的存储过程用了40分钟才执行完毕,而新的存储过程只用了不到1秒。在这里,我想将自己遇到和收集到的关于集合运算与游标操作的对比展现给大家,以供参考。

问题描述

我们有时会遇到这样一个问题,类似于某一列的值累计求和(即本条记录的某个值=前几列该值的合计)。我将解决的核心部分抽取出来。

---原始数据如下:

OID

Period

Amount

Balance

1

2009

3500.00

0.00

2

2009

5100.00

0.00

3

2009

10000.00

0.00

4

2010

2560.00

0.00

5

2010

4700.00

0.00

--预期结果如下(求Balance的值):

OID

Period

Amount

Balance

1

2009

3500.00

3500.00

2

2009

5100.00

8600.00

3

2009

10000.00

18600.00

4

2010

2560.00

2560.00

5

2010

4700.00

7260.00

创建测试数据的SQL脚本

CREATETABLEtPeriod
(
OIDINTIDENTITYPRIMARYKEY
,PeriodNVARCHAR(20)
,AmountDECIMAL(18,2)DEFAULT0
,BalanceDECIMAL(18,2)DEFAULT0
,Balance2DECIMAL(18,2)DEFAULT0
,Balance3DECIMAL(18,2)DEFAULT0
)
GO

DECLARE@iINT
SET@i=1900
WHILE@i<=2013
BEGIN

INSERTINTOtPeriod(Period,Amount)
SELECTCAST(@iASNVARCHAR),ROUND(RAND()*10000,-2)
UNIONALLSELECTCAST(@iASNVARCHAR),ROUND(RAND()*10000,-2)
UNIONALLSELECTCAST(@iASNVARCHAR),ROUND(RAND()*10000,-2)
UNIONALLSELECTCAST(@iASNVARCHAR),ROUND(RAND()*10000,-2)
UNIONALLSELECTCAST(@iASNVARCHAR),ROUND(RAND()*10000,-2)
UNIONALLSELECTCAST(@iASNVARCHAR),ROUND(RAND()*10000,-2)
UNIONALLSELECTCAST(@iASNVARCHAR),ROUND(RAND()*10000,-2)
UNIONALLSELECTCAST(@iASNVARCHAR),ROUND(RAND()*10000,-2)
UNIONALLSELECTCAST(@iASNVARCHAR),ROUND(RAND()*10000,-2)
UNIONALLSELECTCAST(@iASNVARCHAR),ROUND(RAND()*10000,-2)
UNIONALLSELECTCAST(@iASNVARCHAR),ROUND(RAND()*10000,-2)
UNIONALLSELECTCAST(@iASNVARCHAR),ROUND(RAND()*10000,-2)

SET@i=@i+1
END

INSERTINTOtPeriod(Period,Amount)
SELECTCAST(@iASNVARCHAR),ROUND(RAND()*10000,-2)
UNIONALLSELECTCAST(@iASNVARCHAR),ROUND(RAND()*10000,-2)
UNIONALLSELECTCAST(@iASNVARCHAR),ROUND(RAND()*10000,-2)
UNIONALLSELECTCAST(@iASNVARCHAR),ROUND(RAND()*10000,-2)
UNIONALLSELECTCAST(@iASNVARCHAR),ROUND(RAND()*10000,-2)
UNIONALLSELECTCAST(@iASNVARCHAR),ROUND(RAND()*10000,-2)
GO

SELECT*FROMtPeriod;
GO


传统解答:使用游标

DECLARE@OIDINT
,@vPeriod_PreNVARCHAR(20)
,@vPeriod_CurrentNVARCHAR(20)
,@dcAmountDECIMAL(18,2)
,@dcBalanceDECIMAL(18,2)
DECLAREcursor1CURSORFOR
SELECTt.OID,t.Period,t.AmountfromtPeriodASt
OPENcursor1

FETCHNEXTFROMcursor1INTO@OID,@vPeriod_Current,@dcAmount
SELECT@vPeriod_Pre=@vPeriod_Current,@dcBalance=0

WHILE@@FETCH_STATUS=0
BEGIN
IF@vPeriod_Current=@vPeriod_Pre
BEGIN
SET@dcBalance=@dcBalance+@dcAmount
END
ELSE
BEGIN
SELECT@vPeriod_Pre=@vPeriod_Current,@dcBalance=@dcAmount
END

UPDATEtPeriod
SETBalance=@dcBalance
WHEREOID=@OID

FETCHNEXTFROMcursor1INTO@OID,@vPeriod_Current,@dcAmount
END

CLOSEcursor1
DEALLOCATEcursor1


推荐解答:集合运算

--参考答案2
UPDATEtPeriod
SETBalance3=(SELECTSUM(Amount)
FROMtPeriodASt
WHEREt.Period=tPeriod.PeriodANDt.OID<=tPeriod.OID
)
GO

--参考答案3(SQLSERVER)
DECLARE@dcAmtDECIMAL(18,2),@periodCHAR(4)

UPDATET1
SET@dcAmt=CASEWHENPeriod=@periodTHEN@dcAmt+AmountELSEAmountEND,
@Period=Period,
Balance2=@dcAmt
FROMtPeriodAST1
GO


--参考答案3(Oracle)
SELECTt.*,sum(t.amount)over(partitionBYt.Periodorderbyt.OID)asacc
FROMtPeriodt;



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: