.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.包 ...
随机推荐
- 史上最强php生成pdf文件,html转pdf文件方法
body{ font-family: "Microsoft YaHei UI","Microsoft YaHei",SimSun,"Segoe UI& ...
- FZU 2087 统计树边
这题第一直觉就是和CF第三次教育场的E题是一样的, http://codeforces.com/contest/609/problem/E 然后直接拉过来代码改了改,提交返回MLE.FZU内存开的小, ...
- 必须熟悉的vim快捷键操作
转载请表明出处http://www.dabu.info/?p=801 Vim/Vi 没有菜单,只有命令 Vim/Vi 工作模式介绍:插入模式 和 命令模式 是vi的两个基本模式.——插入模式 ,是用 ...
- js执行js字符串函数的方法
<script> var jsText = 'return function(){alert(1+1)}' var jscode = new Function(jsText)(); jsc ...
- Java编译时出现No enclosing instance of type XXX is accessible.
今天在编译Java程序的时候出现以下错误: No enclosing instance of type Main is accessible. Must qualify the allocation ...
- 统计学常用概念:T检验、F检验、卡方检验、P值、自由度
1,T检验和F检验的由来 一般而言,为了确定从样本(sample)统计结果推论至总体时所犯错的概率,我们会利用统计学家所开发的一些统计方法,进行统计检定. 通过把所得到的统计检定值,与统计学家建立了一 ...
- UVa 11790 - Murcia's Skyline
题目大意:给一个建筑的序列,建筑用高度和宽度描述,找出按高度的LIS和LDS,最长XX子序列的长度按照序列中建筑的宽度和进行计算. 其实就是带权的最长XX子序列问题,原来是按个数计算,每个数权都是1, ...
- Python爬虫框架Scrapy安装使用步骤
一.爬虫框架Scarpy简介Scrapy 是一个快速的高层次的屏幕抓取和网页爬虫框架,爬取网站,从网站页面得到结构化的数据,它有着广泛的用途,从数据挖掘到监测和自动测试,Scrapy完全用Python ...
- C#键盘事件处理(来源网上)
C#键盘事件处理 如果你希望用户按F1弹出chm帮助,代码如下: private void FrmMain_Load(object sender, EventArgs e) { this.KeyPre ...
- thinkPHP 模板中变量的使用
一.变量输出 1.标量输出(普通) 2.数组输出 {$name[1]} {$name['k2'] ...