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 |
-- 预期结果如下(求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脚本
CREATE TABLE tPeriod
(
OID INT IDENTITY PRIMARY KEY
, Period NVARCHAR(20)
, Amount DECIMAL(18, 2) DEFAULT 0
, Balance DECIMAL(18, 2) DEFAULT 0
, Balance2 DECIMAL(18, 2) DEFAULT 0
, Balance3 DECIMAL(18, 2) DEFAULT 0
)
GO DECLARE @i INT
SET @i = 1900
WHILE @i <= 2013
BEGIN INSERT INTO tPeriod(Period, Amount)
SELECT CAST(@i AS NVARCHAR), ROUND(RAND() * 10000, -2)
UNION ALL SELECT CAST(@i AS NVARCHAR), ROUND(RAND() * 10000, -2)
UNION ALL SELECT CAST(@i AS NVARCHAR), ROUND(RAND() * 10000, -2)
UNION ALL SELECT CAST(@i AS NVARCHAR), ROUND(RAND() * 10000, -2)
UNION ALL SELECT CAST(@i AS NVARCHAR), ROUND(RAND() * 10000, -2)
UNION ALL SELECT CAST(@i AS NVARCHAR), ROUND(RAND() * 10000, -2)
UNION ALL SELECT CAST(@i AS NVARCHAR), ROUND(RAND() * 10000, -2)
UNION ALL SELECT CAST(@i AS NVARCHAR), ROUND(RAND() * 10000, -2)
UNION ALL SELECT CAST(@i AS NVARCHAR), ROUND(RAND() * 10000, -2)
UNION ALL SELECT CAST(@i AS NVARCHAR), ROUND(RAND() * 10000, -2)
UNION ALL SELECT CAST(@i AS NVARCHAR), ROUND(RAND() * 10000, -2)
UNION ALL SELECT CAST(@i AS NVARCHAR), ROUND(RAND() * 10000, -2) SET @i = @i + 1
END INSERT INTO tPeriod(Period, Amount)
SELECT CAST(@i AS NVARCHAR), ROUND(RAND() * 10000, -2)
UNION ALL SELECT CAST(@i AS NVARCHAR), ROUND(RAND() * 10000, -2)
UNION ALL SELECT CAST(@i AS NVARCHAR), ROUND(RAND() * 10000, -2)
UNION ALL SELECT CAST(@i AS NVARCHAR), ROUND(RAND() * 10000, -2)
UNION ALL SELECT CAST(@i AS NVARCHAR), ROUND(RAND() * 10000, -2)
UNION ALL SELECT CAST(@i AS NVARCHAR), ROUND(RAND() * 10000, -2)
GO SELECT * FROM tPeriod;
GO
传统解答:使用游标
DECLARE @OID INT
, @vPeriod_Pre NVARCHAR(20)
, @vPeriod_Current NVARCHAR(20)
, @dcAmount DECIMAL(18, 2)
, @dcBalance DECIMAL(18, 2)
DECLARE cursor1 CURSOR FOR
SELECT t.OID, t.Period, t.Amount from tPeriod AS t
OPEN cursor1 FETCH NEXT FROM cursor1 INTO @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 UPDATE tPeriod
SET Balance = @dcBalance
WHERE OID = @OID FETCH NEXT FROM cursor1 INTO @OID, @vPeriod_Current, @dcAmount
END CLOSE cursor1
DEALLOCATE cursor1
推荐解答:集合运算
-- 参考答案2
UPDATE tPeriod
SET Balance3 = ( SELECT SUM(Amount)
FROM tPeriod AS t
WHERE t.Period = tPeriod.Period AND t.OID <= tPeriod.OID
)
GO -- 参考答案3(SQLSERVER)
DECLARE @dcAmt DECIMAL(18, 2), @period CHAR(4) UPDATE T1
SET @dcAmt = CASE WHEN Period = @period THEN @dcAmt + Amount ELSE Amount END,
@Period = Period,
Balance2 = @dcAmt
FROM tPeriod AS T1
GO
-- 参考答案3(Oracle)
SELECT t.*, sum(t.amount) over(partition BY t.Period order by t.OID) as acc
FROM tPeriod t;
SQL集合运算参考及案例(一):列值分组累计求和的更多相关文章
- SQL集合运算参考及案例(二):树形节点数量逐级累计汇总
问题描述: 我们经常遇到这样一个问题,类似于面对一个树形结构的物料数据,需要将库存中每一种物料数量汇总到物料上展示出来:或者说组织机构是一棵树,我们需要统计每一个节点上的人员数量(含下级节点的累计数量 ...
- SQL将一个表中的某一列值全部插入到另一个表中
1. SQL将一个表中的某一列值全部插入到另一个表中 插入的话: insert into a(col) select col from b; 更新的话: update a set col=selec ...
- 详解SQL集合运算
以前总是追求新东西,发现基础才是最重要的,今年主要的目标是精通SQL查询和SQL性能优化. 本系列[T-SQL基础]主要是针对T-SQL基础的总结. [T-SQL基础]01.单表查询-几道sql查询题 ...
- 7 SQL 集合运算
7 集合运算 7-1 表的加减法 本章将会和大家一起学习“集合运算”操作.在数学领域,“集合”表示“(各种各样的)事物的总和”:在数据库领域,表示“记录的集合”.具体来说,表.视图和查询的执行结果都是 ...
- SQL集合运算 差集 并集 交
SQL-3标准中提供了三种对检索结果进行集合运算的命令:并集UNION:交集INTERSECT:差集EXCEPT(在Oracle中叫做 MINUS).在有些数据库中对此的支持不够充分,如MySql中只 ...
- mybatis关联集合List&分布查询传递多列值
场景:查询部门的同时,要求查询此部门下的所有用户. 部门(Department) private Integer id; private String departmentName; private ...
- sql 集合运算
UNION 并运算 UNION 操作符用于合并两个或多个 SELECT 语句的结果集. 请注意,UNION 内部的 SELECT 语句必须拥有相同数量的列.列也必须拥有相似的数据类型.同时,每条 SE ...
- SQL集合运算:差集、交集、并集
1.差集( except ) select a from t_a except select a from t_b -- 也可写作: select a from t_a where a not in ...
- SQL集合运算
注:UserInfo一共29条记录 select * from UserInfo union --并集(29条记录)(相同的只出现一次) select * from UserInfo select * ...
随机推荐
- 1、SQL基础整理(基本查询)
分离.附加.备份.还原 --去重 select distinct 列名from表名 --更新 update fenshu set name = ‘李四’where code = 9 --更改表名 sp ...
- magento 去掉index.php .html
首先应该确保你的服务器环境,达到了magento的运行需求,若你是使用的是magento专用空间就不必担心了,若不是请查看我以前的文章,检测你的运行环境(http://www.hellokeykey. ...
- 《JS高程》实现继承的6种方式(完整版)
许多OO语言都支持 两种继承方式: (1)接口继承:只继承方法签名: (2)实现继承:继承实际的方法. ECMAScript 由于函数没有签名,无法实现接口继承,因此只支持实现继承,而且主要是依靠原型 ...
- 《JS高程》对象&原型学习笔记
ECMA-262 把对象定义为:”无序属性的集合,其属性可以包含基本值.对象或者函数.”可以把 ECMAScript 的对象想象成散列表:无非就是一组名值对,其中值可以是数据或函数. 6.1.理解 ...
- Windows 10下Chrome不能启动的问题
不能启动了 Chrome突然不能启动了,点击图标也没反应,打开任务管理器,发现点击图标后,Google Ghrome短暂地出现,随即消失. 如何解决? 解决方案 打开安装目录: C:\Program ...
- 解决SimpleCursorAdapter不能自动更新的问题
假设场景是这样的:你使用SimpleCursorAdapter显示数据,并监听数据的变化:在数据发生变化的时候,调用cursor的requery,期待UI显示也跟着变化. 但是,你可能会发现,UI并没 ...
- LCS (nlogn)
最长上升子序列的O(n*logn)算法分析如下: 先回顾经典的O(n^2)的动态规划算法,设a[t]表示序列中的第t个数,dp[t]表示从1到t这一段中以t结尾的最长上升子序列的长度,初始时设dp [ ...
- Response.AddHeader使用实例
1.文件下载,指定默认名Response.AddHeader("content-type","application/x-msdownload"); // 限制 ...
- AspxGridView
转:出处 http://i.cnblogs.com/EditPosts.aspx?opt=1 AspxGridView点滴 1:页码设置 <SettingsPager> & ...
- keil uvision看厌了么?试试Sublime Text吧!
之前用 Sublime Text(以下简称 ST )配置了 C/C++ 开发环境,感觉相当不错,作为编辑器的 ST,编辑代码的功能当然是相当棒的,美中不足的是目前只能编译单个文件,但是用来做些小练习也 ...