.Net程序员学用Oracle系列(13):合并语句(MERGE)
- 1、[**语法说明**](#segment1)
- 1.1、[UPDATE 和 INSERT 可以只出现一个](#point11)
- 1.2、[UPDATE 后面还可以再跟 WHERE](#point12)
- 1.3、[UPDATE 和 INSERT 同时出现](#point13)
- 1.4、[UPDATE 之后还可以再删除行](#point14)
- 1.5、[将两个列的部分行值互换](#point15)
- 2、[**常见雷区**](#segment2)
- 2.1、[ORA-30926: 无法在源表中获得一组稳定的行](#point21)
- 2.2、[USING 了空行导致 UPDATE 或 INSERT 失败](#point22)
- 3、[**总结**](#segment3)
在开发工作中时常会遇到这样一个问题:要判断一张表中是否存在某条记录,如果不存在则添加该记录,如果存在则修改该记录。按照一般的思路,至少需要 SELECT、INSERT、UPDATE 三条 SQL 语句才能搞定。无论是用程序代码还是存储过程,尽管逻辑不算复杂,但代码会比较长、可维护性也不好,而且经验不足的开发人员往往不会考虑事务,写出来的代码会有逻辑漏洞。
我本人是在一次发现旧代码中存在逻辑漏洞后,不想写太长的事务控制代码,又想不出更简洁的方案时,带着“要是能一条语句同时搞定添加和修改就好了”的幻想开始了漫无边际的百度之旅,终于功夫不负有心人,合并语句 MERGE 就这样被我找到了。于是乎我开始在工作中大量使用 MERGE,由于经验不足,曾多次入坑,我将通过本文把我的使用心得分享给大家。
## 1、语法说明
`MERGE` 语句具有按条件获取要更新或插入到表中的数据行,然后从一个或多个源头对表进行更新或者向表中插入行两方面的能力。它最经常被用在数据仓库中来移动大量的数据,但它的应用不仅限于数据仓库环境下。这个语句提供的一个很大的附加值在于你可以很方便地把多个操作结合成一个。如果你避免去做那些不是必须做的事情,响应的时间可能得到相应的改善。
简单的说 `MERGE` 语句就是用来合并 `UPDATE` 语句和 `INSERT` 语句的。通过 MERGE 语句,可根据一张表或子查询的连接条件对另外一张表进行查询,连接条件匹配上了就执行 UPDATE,无法匹配就执行 INSERT。这个语句仅需要一次全表扫描就完成了全部工作,执行效率要高于 INSERT + UPDATE。语法:
MERGE INTO target_table
USING {table/view/subquery}
ON(condition)
WHEN MATCHED THEN merge_update_clause
WHEN NOT MATCHED THEN merge_insert_clause;
#### 1.1、UPDATE 和 INSERT 可以只出现一个
只出现 INSERT 的示例:
-- 如果课程表中没有“计算机”这门课程则插入这门课程
MERGE INTO demo.t_course t1
USING(SELECT 1 cid,'计算机' cname FROM DUAL) t2
ON(t1.course_id=t2.cid)
WHEN NOT MATCHED THEN
INSERT(t1.course_id,t1.course_name) VALUES(t2.cid,t2.cname);
只出现 UPDATE 的示例:
-- 如果课程表中有“计算机”这门课程则将这门课程的备注改为“工科”
MERGE INTO demo.t_course t1
USING(SELECT 1 cid,'计算机' cname FROM DUAL) t2
ON(t1.course_name=t2.cname)
WHEN MATCHED THEN
UPDATE SET t1.course_desc='工科';
#### 1.2、UPDATE 后面还可以再跟 WHERE
如果课程表中有多个名为“计算机”的课程,而实际上只需要把课程 ID 为 1 的课程备注改为“工科”,上文中只出现 UPDATE 的示例就有逻辑错误了。这种情况的正确示例:
MERGE INTO demo.t_course t1
USING(SELECT 1 cid,'计算机' cname FROM DUAL) t2
ON(t1.course_name=t2.cname)
WHEN MATCHED THEN
UPDATE SET t1.course_desc='工科' WHERE t1.course_id=1;
#### 1.3、UPDATE 和 INSERT 同时出现
如果研发一部有叫“大国”的人,就把他的岗位工资加 200,如果没有就把他添加到研发一部。示例:
MERGE INTO demo.t_staff t1
USING(SELECT '010101' dept_code,'大国' staff_name FROM DUAL) t2
ON(t1.dept_code=t2.dept_code AND t1.staff_name=t2.staff_name)
WHEN MATCHED THEN
UPDATE SET t1.post_salary=t1.post_salary+200
WHEN NOT MATCHED THEN
INSERT(staff_name, dept_code, gender, birthday, edu_bg, base_salary, post_salary, post_code)
VALUES('大国','010101',1,TO_DATE('1992-01-15','yyyy-mm-dd'),2,2500,4000,'P50');
假如说员工中没有叫“大国”的,所以第一次执行上面这条语句时,大国会被插入到研发一部;这时候研发一部就已经有“大国”了,如果再执行第二次,“大国”的岗位工资就会被修改成 4200 了。
#### 1.4、UPDATE 之后还可以再删除行
如要把低工资的员工固定薪资上调 500,然后把工资依然低于 5000 的员工数据删除,示例:
MERGE INTO demo.t_staff_salary t1
USING demo.t_staff_low t2
ON(t1.staff_id=t2.staff_id)
WHEN MATCHED THEN
UPDATE SET t1.fixed_salary=t1.fixed_salary+500
DELETE WHERE t1.fixed_salary
#### 1.5、将两个列的部分行值互换
如果要求将两个列的值互换,那自然是简单,譬如要把 t 表中的 a 列与 b 列值互换,示例:
UPDATE t SET a=b,b=a;
但如果要求将两个列的部分行值互换,譬如要把小强的岗位工资和小明的岗位工资调换一下。假如先把小强的岗位工资更新成小明的岗位工资,那么再更新小明的岗位工资时就不知道小强的岗位工资了。要是在程序中倒是好办,可以先把小强的工资存储起来,那是不是仅用 SQL 语句就无法解决这个问题了呢?答案是否定的!示例:
MERGE INTO demo.t_staff t1
USING(
SELECT 1 staff_id,(SELECT n1.post_salary FROM t_staff n1 WHERE n1.staff_id=2) post_salary FROM DUAL
UNION ALL
SELECT 2 staff_id,(SELECT n2.post_salary FROM t_staff n2 WHERE n2.staff_id=1) post_salary FROM DUAL
) t2
ON(t1.staff_id=t2.staff_id)
WHEN MATCHED THEN
UPDATE SET t1.post_salary=t2.post_salary;
## 2、常见雷区
#### ORA-30926: 无法在源表中获得一组稳定的行
这可能是用 MERGE 语句最常见的报错!先来创建两张测试表,语句如下:
CREATE TABLE t1(cid NUMBER(4),cname VARCHAR2(20));
INSERT ALL
INTO t1 VALUES(1,'A')
INTO t1 VALUES(2,'B')
INTO t1 VALUES(3,'C')
SELECT * FROM DUAL;
COMMIT;
CREATE TABLE t2(cid NUMBER(4),cname VARCHAR2(20));
INSERT ALL
INTO t2 VALUES(1,'甲')
INTO t2 VALUES(2,'乙')
INTO t2 VALUES(2,'乙')
SELECT * FROM DUAL;
COMMIT;
要求把 t1 表中 cid 与 t2 表中 cid 相同的行的 cname 列的值更新为 t2 表中的 cname 列的值,简单的说就是把 t1 表中的 A/B/C 翻译成 t2 表中的甲/乙/丙。按照一般的思路是行不通的,错误示例:
MERGE INTO t1
USING t2
ON(t1.cid=t2.cid)
WHEN MATCHED THEN
UPDATE SET t1.cname=t2.cname;
很遗憾!它会报:“ORA-30926: 无法在源表中获得一组稳定的行”的错。原因是 MERGE 语句要求保证 ON 中条件的唯一性,也就是说目标表中的一条记录不能对应源表中的多条记录。但反过来源表中的一条记录对应目标表中的多条记录却是可以的,示例:
MERGE INTO t2
USING t1
ON(t1.cid=t2.cid)
WHEN MATCHED THEN
UPDATE SET t2.cname=t1.cname;
那是不是说目标表中的一条记录对应源表中的多条记录时就无法通过 MERGE 更新了呢?也不完全是!就拿本例来说,可以通过对源表做聚合,将多行归并成单行,然后再更新。示例:
MERGE INTO t1
USING(SELECT t2.cid,MAX(t2.cname) cname FROM t2 GROUP BY t2.cid) t2
ON(t1.cid=t2.cid)
WHEN MATCHED THEN
UPDATE SET t1.cname=t2.cname;
#### 2.2、USING 了空行导致 UPDATE 或 INSERT 失败
假如要判断上例中的 t2 表里是否有 cid 为 3 的记录,如果有就把它的 cname 改为丙,如果没有就插入cid 为 3,cname 为丙的记录。错误示例:
MERGE INTO t2
USING(SELECT * FROM t2 WHERE t2.cid=3) t
ON(t2.cid=t.cid)
WHEN MATCHED THEN
UPDATE SET t2.cname='丙'
WHEN NOT MATCHED THEN
INSERT VALUES(3,'丙');
上面这个语句是可以执行成功的,但遗憾的是,表中却没有增加记录。原因是 MERGE 语句要求 USING 后面必须包含要更新或插入的行,而上例中 t2.cid=3 的行是不存在的。下面我来介绍两种正确写法。
写法一,示例:
MERGE INTO t2
USING(SELECT 3 cid,'丙' cname FROM DUAL) t
ON(t2.cid=t.cid)
WHEN MATCHED THEN
UPDATE SET t2.cname='丙'
WHEN NOT MATCHED THEN
INSERT VALUES(3,'丙');
写法二,示例:
USING(SELECT COUNT(1) cnt FROM t2 WHERE t2.cid=3) t
ON(t.cnt>0)
WHEN MATCHED THEN
UPDATE SET t2.cname='丙'
WHEN NOT MATCHED THEN
INSERT VALUES(3,'丙');
推荐写法一,写法二相对没有写法一那么好理解,一旦理解反了,后果可能很严重。譬如上例中如果写成 t.cnt
## 3、总结
MERGE 是 SQL 中除 INSERT、DELETE、UPDATE、SELECT 之外又一重要基本语句,不仅 Oracle 中有,MS SQL Server 和 MySQL 等关系型数据库中也都有,所以非常有必要熟练掌握 MERGE 语句。若能在开发过程中合理的运用 MERGE 语句,可有效提高开发效率,增强代码的可维护性和健壮性,最显而易见的好处就是——不用写那么长的代码了。
> **本文链接**:http://www.cnblogs.com/hanzongze/p/Oracle-Merge.html
> **版权声明**:本文为博客园博主 [**韩宗泽**](http://www.cnblogs.com/hanzongze/) 原创,作者保留署名权!欢迎通过转载、演绎或其它传播方式来使用本文,但必须在明显位置给出作者署名和本文链接!本人初写博客,水平有限,若有不当之处,敬请批评指正,谢谢!
.Net程序员学用Oracle系列(13):合并语句(MERGE)的更多相关文章
- .Net程序员学用Oracle系列(1):导航目录
本人从事基于 Oracle 的 .Net 企业级开发近三年,在此之前学习和使用的都是 (MS)SQL Server.未曾系统的了解过 Oracle,所以长时间感到各种不习惯.不方便.怪异和不解,常会遇 ...
- 系列文章----.Net程序员学用Oracle系列
.Net程序员学用Oracle系列(18):PLSQL Developer 攻略 .Net程序员学用Oracle系列(17):数据库管理工具(SQL Plus) .Net程序员学用Oracle系列(1 ...
- .Net程序员学用Oracle系列(9):系统函数(上)
<.Net程序员学用Oracle系列:导航目录> 本文大纲 1.字符函数 1.1.字符函数简介 1.2.语法说明及案例 2.数字函数 2.1.数字函数简介 2.2.语法说明及案例 3.日期 ...
- .Net程序员学用Oracle系列(10):系统函数(下)
<.Net程序员学用Oracle系列:导航目录> 本文大纲 1.转换函数 1.1.TO_CHAR 1.2.TO_NUMBER 1.3.TO_DATE 1.4.CAST 2.近似值函数 2. ...
- .Net程序员学用Oracle系列(11):系统函数(下)
1.聚合函数 1.1.COUNT 函数 1.2.SUM 函数 1.3.MAX 函数 1.4.MIN 函数 1.5.AVG 函数 2.ROWNUM 函数 2.1.ROWNUM 函数简介 2.2.利用 R ...
- .Net程序员学用Oracle系列(30):零碎补充、最后总结(The End)
1.同义词 2.Flashback 技术 3.连接字符串的写法 4.转义字符 & 特殊运算符 5.文件类型 6.查看参数 & 修改参数 7.AWR 工具 8.学习方法 & 学习 ...
- .Net程序员学用Oracle系列(2):准备测试环境
<.Net程序员学用Oracle系列:导航目录> 本文大纲 1.创建说明 1.1.为什么要创建的测试环境? 1.2.了解 Oracle 实例的默认用户 2.创建环境 2.1.创建基本环境 ...
- .Net程序员学用Oracle系列(6):表、字段、注释、约束、索引
<.Net程序员学用Oracle系列:导航目录> 本文大纲 1.表 1.1.创建表 1.2.修改表 & 删除表 2.字段 2.1.添加字段 2.2.修改字段 & 删除字段 ...
- .Net程序员学用Oracle系列(7):视图、函数、过程、包
<.Net程序员学用Oracle系列:导航目录> 本文大纲 1.视图 1.1.创建视图 2.函数 2.1.创建函数 2.2.调用函数 3.过程 3.1.创建过程 3.2.调用过程 4.包 ...
随机推荐
- SpringMVC 接收表单数据的方式 - Samuel - 博客频道 - CSDN.NET
1.@RequestParam @RequestMapping(value = "/xxxx.do") public void create(@RequestParam(value ...
- js获取光标位置
js获取光标位置 var TT = { /* * 获取光标位置 * @Method getCursorPosition * @param t element * @return number */ ...
- [iOS]使用signal让app能够在从容崩溃
前言 虽然大家都不愿意看到程序崩溃,但可能崩溃是每个应用必须面对的现实,既然崩溃已经发生,无法阻挡了,那我们就让它崩也崩得淡定点吧. iOS SDK中提供了一个现成的函数 NSSetUncaughtE ...
- hibernate---一级缓存, 二级缓存, 查询缓存
缓存: 内存开辟一块空间,把本来存在硬盘的东西放在内存里, 以后从内存读取. 一级缓存: session级别的缓存, session.load 二级缓存: 总的缓存.
- 设计模式笔记之四:MVP+Retrofit+RxJava组合使用
本博客转自郭霖公众号:http://mp.weixin.qq.com/s?__biz=MzA5MzI3NjE2MA==&mid=2650236866&idx=1&sn=da66 ...
- HDU 3903 Trigonometric Function
这题真难,并不会推理... #include<cstdio> #include<cstring> #include<cmath> #include<algor ...
- JNI 中文字符串传递(转)
源:JNI 中文字符串传递 因为项目编码中通过JNI传递中文字符时出现乱码问题,特搜集了相关资料,整理如下: java内部是使用16bit的unicode编码(UTF-16)来表示字符串的,无论中文英 ...
- 【转】25个Git用法技巧
Andy Jeffries 给 Git 中级用户总结分享的 25 个小贴士.你不需要去做大量搜索,或许这些小贴士对你就很有帮助的. 我从开始使用git到现在已经差不多18个月了,以为自己已经很懂git ...
- zeromq随笔
ZMQ (以下 ZeroMQ 简称 ZMQ)是一个简单好用的传输层,像框架一样的一个 socket library,他使得 Socket 编程更加简单.简洁和性能更高.是一个消息处理队列库,可在多个线 ...
- 如何备份、还原或迁移 WhatsApp 的信息和资料?
WhatsApp 会在每天清晨 2 点钟自动创建本地备份文件并将备份文件存储在手机系统设置的文件夹中. Google 云端硬盘 您可以备份对话记录和媒体到 Google 云端硬盘以便日后更换手机时仍可 ...