SQL集合运算参考及案例(一):列值分组累计求和
2016-07-29 17:58
411 查看
概述
--预期结果如下(求Balance的值):
创建测试数据的SQL脚本
传统解答:使用游标
推荐解答:集合运算
目前企业应用系统使用的大多数据库都是关系型数据库,关系数据库依赖的理论就是针对集合运算的关系代数。关系代数是一种抽象的查询语言,是关系数据操纵语言的一种传统表达方式。不过我们在工作中发现,很多人在面对复杂的数据库运算逻辑时会采用游标、循环、自定义函数等方式处理,因为游标是一种比较熟悉和舒适的面向过程的编程方式,很符合我们一般的逻辑思维习惯,可很不幸,这会导致糟糕的性能。显然,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 |
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 |
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;
相关文章推荐
- # mysql CREATE TABLE IF NOT EXISTS metadata lock坑
- oracle 10g函数大全--分析函数
- 玩转数据库之 Group by Grouping
- Oracle逐行累加求和
- window 计划任务 备份数据库至备份服务器
- mysql权限问题
- sqlldr用法
- NO ACTION RESTRICT
- dynatrace purepath数据转换到数据库
- SQL Server中的事物
- mysql list 遍历
- mysql缓存
- YII 如何使用MemCache缓存
- MySql按周,按月,按日分组统计数据
- 【SQL】——Oracle之ROWNUM
- log4j配置和mybatis sql打印
- Linux下redis的安装
- oracle 全文检索创建脚本示例
- MySQL中NULL和空值对比
- SQL Server中用While循环替代游标(Cursor)的解决方案