MERGE用法

MERGEMERGE是什么,如何使用呢?先看一个简单的需求如下:从T1表更新数据到T2表中,如果T2表的NAME 在T1表中已存在,就将MONEY累加,如果不存在,将T1表的记录插入到T2表中。DROP TABLE T1;CREATE TABLE T1 (NAME VARCHAR2(20),MONEY NUMBER);INSERT INTO T1 VALUES ('A',10);INSERT INTO T1 VALUES ('B',20);DROP TABLE T2;CREATE TABLE T2 (NAME VARCHAR2(20),MONEY NUMBER);INSERT INTO T2 VALUES ('A',30);INSERMERGE
MERGE是什么,如何使用呢?先看一个简单的需求如下:
从T1表更新数据到T2表中,如果T2表的NAME 在T1表中已存在,就将MONEY累加,如果不存在,将T1表的记录插入到T2表中。
DROP TABLE T1;
CREATE TABLE T1 (NAME VARCHAR2(20),MONEY NUMBER);
INSERT INTO T1 VALUES ('A',10);
INSERT INTO T1 VALUES ('B',20);
DROP TABLE T2;
CREATE TABLE T2 (NAME VARCHAR2(20),MONEY NUMBER);
INSERT INTO T2 VALUES ('A',30);
INSERT INTO T2 VALUES ('C',20);
COMMIT;
大家知道,一般逻辑思路,该需要至少要UPDATE和INSERT两条SQL才能完成,如考虑在PL/SQL中用纯编程语言思路实现,则必须要考虑判断的逻辑,这样显得更麻烦了。而MERGE语句可直接用单条SQL简洁明快的实现了此业务逻辑,具体如下:
MERGE INTO T2
USING T1
ON (T1.NAME=T2.NAME)
WHEN MATCHED THEN
UPDATE
SET T2.MONEY=T1.MONEY+T2.MONEY
WHEN NOT MATCHED THEN
INSERT
VALUES (T1.NAME,T1.MONEY);
    此外MERGE语句同样可以分析执行计划,具体如下:
SQL> explain plan for
  2  MERGE INTO T2
  3  USING T1
  4  ON (T1.NAME=T2.NAME)
  5  WHEN MATCHED THEN
  6  UPDATE
  7  SET T2.MONEY=T1.MONEY+T2.MONEY
  8  WHEN NOT MATCHED THEN
  9  INSERT
 10  VALUES (T1.NAME,T1.MONEY);
Explained
SQL> SELECT * FROM table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------
Plan hash value: 2414655244
----------------------------------------------------------------------------
| Id  | Operation                 | Name |  Rows  | Bytes  |  Cost (%CPU)| Time   |
----------------------------------------------------------------------------
|   0 | MERGE STATEMENT         |      |     2 |   152  |     7  (15)| 00:00:01 |
|   1 |  MERGE                  | T2   |       |        |           |        |
|   2 |   VIEW                  |      |       |        |           |        |
|*  3 |    HASH JOIN OUTER       |      |     2 |   124  |    7  (15) | 00:00:01 |
|   4 |     TABLE ACCESS FUL L     | T1   |     2 |     50  |     3   (0)| 00:00:01 |
|   5 |     TABLE ACCESS FULL     | T2    |     2 |    74  |     3   (0)| 00:00:01 |
-------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   3 - access("T1"."NAME"="T2"."NAME"(+))
Note
-----
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------
 - dynamic sampling used for this statement
21   ows selected
 
1.2 MERGE语法简介
语法如下:
MERGE [hint] INTO [schema .] table [t_alias] USING [schema .]{ table | view | subquery } [t_alias] ON ( condition )WHEN MATCHED THEN merge_update_clauseWHEN NOT MATCHED THEN merge_insert_clause;
 
1.3  Oracle10g中MERGE的完善
在Oracle10g以后,Oracle的MERGE发生了改变
1.4  UPDATE和INSERT动作可只出现其一
可选择仅UPDATE目标表
SQL> MERGE INTO T2
  2  USING T1
  3  ON (T1.NAME=T2.NAME)
  4  WHEN MATCHED THEN
  5  UPDATE
  6  SET T2.MONEY=T1.MONEY+T2.MONEY;
Done
 
也可选择仅仅INSERT目标表而不做任何UPDATE动作
SQL> MERGE INTO T2
  2  USING T1
  3  ON (T1.NAME=T2.NAME)
  4  WHEN NOT MATCHED THEN
  5  INSERT
  6  VALUES (T1.NAME,T1.MONEY);
Done
   注:Oracle9i必须同时出现INSERT和UPDATE操作。
1.5  可对MERGE语句加where条件
SQL> MERGE INTO T2
  2  USING T1
  3  ON (T1.NAME=T2.NAME)
  4  WHEN MATCHED THEN
  5  UPDATE
  6  SET T2.MONEY=T1.MONEY+T2.MONEY
  7  WHERE T1.NAME='A' ---此处表示对MERGE的条件进行过滤
  8  ;
Done
 
1.6  可用DELETE子句清除行(必须同时满足on后的条件和delete where后的条件才有效)
在这种情况下,首先是要先满足T1.NAME=T2.NAME的记录,如果T2.NAME=’A’并不满足T1.NAME=T2.NAME过滤出的记录集,那这个DELETE是不会生效的,在满足的条件下,可以删除目标表的记录。
MERGE INTO T2
USING T1
ON (T1.NAME=T2.NAME)
WHEN MATCHED THEN
UPDATE
SET T2.MONEY=T1.MONEY+T2.MONEY
DELETE WHERE (T2.NAME = 'A')
;
1.7可采用无条件方式Insert
方法很简单,在语法ON关键字处写上恒不等条件(如1=2)后,MATCHED语句的INSERT就变为无条件INSERT了,同于INSERT...SELECT的写法,具体如下
SQL> MERGE INTO T2
  2  USING T1
  3  ON (1=2)
  4  WHEN NOT MATCHED THEN
  5  INSERT
  6  VALUES (T1.NAME,T1.MONEY);
Done
  
2    MERGE误区探索
2.3  无法在源表中获得一组稳定的行(目标表的一条记录对应源表的多条记录)
MERGE INTO T2 USING T1 ON...的MERGE表达式中,如果一条T2记录被连接到多条T1记录,就产生了ORA-30926错误。构造T1,T2表进行试验如下,此次T1表中增加了('A',30)的记录,如下:
DROP TABLE T1;
CREATE TABLE T1 (NAME VARCHAR2(20),MONEY NUMBER);
INSERT INTO T1 VALUES ('A',10);
INSERT INTO T1 VALUES ('A',30);
INSERT INTO T1 VALUES ('B',20);
DROP TABLE T2;
CREATE TABLE T2 (NAME VARCHAR2(20),MONEY NUMBER);
INSERT INTO T2 VALUES ('A',30);
INSERT INTO T2 VALUES ('C',20);
COMMIT
 此时继续执行如下
SQL> MERGE INTO T2
  2  USING T1
  3  ON (T1.NAME=T2.NAME)
  4  WHEN MATCHED THEN
  5  UPDATE
  6  SET T2.MONEY=T1.MONEY+T2.MONEY
  7  ;
ORA-30926: 无法在源表中获得一组稳定的行
 Oracle中的merge语句应该保证on中的条件的唯一性,T1.NAME=’A’时,T2表记录对应到了T1表的两条记录,所以就出错了。
 解决方法很简单,可对T1表和T2表的关联字段建主键,这样基本上就不可能出现这样的问题,而且一般而言,MERGE语句的关联字段互相有主键,MERGE的效率将比较高!
 或者是将T1表的ID列做一个聚合,这样归并成单条,也能避免此类错误。如:
SQL> MERGE INTO T2
  2  USING (SELECT NAME,SUM(MONEY) AS MONEY FROM T1 GROUP BY NAME)T1
  3  ON (T1.NAME=T2.NAME)
  4  WHEN MATCHED THEN
  5  UPDATE
  6  SET T2.MONEY=T1.MONEY+T2.MONEY
  7  ;
Done
但是这样的改造需要注意,因为有可能改变了最终的需求。此外需要引起注意的是,MERGE INTO T2 USING T1 ON...的MERGE表达式中,如果反过来,一条T1记录被连接到多条T2记录,是可以导致多条T2记录都被更新而不会出错!继续构造T1,T2表进行试验如下,此次是在T2表中增加了('A',40)的记录,如下:
DROP TABLE T1;
CREATE TABLE T1 (NAME VARCHAR2(20),MONEY NUMBER);
INSERT INTO T1 VALUES ('A',10);
INSERT INTO T1 VALUES ('B',20);
DROP TABLE T2;
CREATE TABLE T2 (NAME VARCHAR2(20),MONEY NUMBER);
INSERT INTO T2 VALUES ('A',30);
INSERT INTO T2 VALUES ('A',40);
INSERT INTO T2 VALUES ('C',20);
COMMIT
   此时继续执行如下,发现执行可以成功并没有报无法在源表中获得一组稳定的行的ORA-30926错误
SQL>  MERGE INTO T2
  2      USING T1
  3      ON (T1.NAME=T2.NAME)
  4      WHEN MATCHED THEN
  5      UPDATE
  6      SET T2.MONEY=T1.MONEY+T2.MONEY
  7      ;
Done
SQL> COMMIT;
Commit complete
   查看T2表,发现T2表中NAME=A的2条记录都被更新了
SQL> SELECT * FROM T2;
NAME                      MONEY
-------------------- ----------------------------------
A                            40
A                            50
C                            20
 
2.4  DELETE子句的WHERE顺序必须最后
SQL> MERGE INTO T2
  2  USING T1
  3  ON (T1.NAME=T2.NAME)
  4  WHEN MATCHED THEN
  5  UPDATE
  6  SET T2.MONEY=T1.MONEY+T2.MONEY
  7  DELETE WHERE (T2.NAME = 'A')
  8  WHERE T1.NAME='A';
/
ORA-00933: SQL 命令未正确结束
改为如下即可
SQL> MERGE INTO T2
  2  USING T1
  3  ON (T1.NAME=T2.NAME)
  4  WHEN MATCHED THEN
  5  UPDATE
  6  SET T2.MONEY=T1.MONEY+T2.MONEY
  7  WHERE T1.NAME='A'
  8  DELETE WHERE (T2.NAME = 'A')
  9  ;
Done
    注:只要是MERGE语句,UPDATE和DELETE两者必须要出现其一,所以上面的脚本是不能省略UPDATE而只做DELETE的。另外WHERE (T2.NAME = 'A')的括号可以省略。
2.5  DELETE 子句只可以删除目标表,而无法删除源表
    这里需要引起注意,无论DELETE WHERE (T2.NAME = 'A' )这个写法的T2是否改写为T1,效果都一样,都是对目标表进行删除!
SQL> SELECT * FROM T1;
NAME                    MONEY
-------------------- ------------------------------
A                            10
B                            20
SQL> SELECT * FROM T2;
NAME                      MONEY
-------------------- --------------------------------
A                            30
C                            20
SQL> MERGE INTO T2
  2  USING T1
  3  ON (T1.NAME=T2.NAME)
  4  WHEN MATCHED THEN
  5  UPDATE
  6  SET T2.MONEY=T1.MONEY+T2.MONEY
  7  DELETE WHERE (T2.NAME = 'A' )
  8  ;
Done
SQL> SELECT * FROM T1;
NAME                      MONEY
-------------------------------------------------------
A                            10
B                            20
SQL> SELECT * FROM T2;
NAME                      MONEY
-------------------- ---------------------------------
C                            20
可以看出目标表的A记录被删除了,但是如果把DELETE WHERE (T2.NAME = 'A' )修改为DELETE WHERE (T1.NAME = 'A' ),是否就会把源表的T1记录给删除了呢?试验如下:
Rollback complete
SQL> MERGE INTO T2
  2  USING T1
  3  ON (T1.NAME=T2.NAME)
  4  WHEN MATCHED THEN
  5  UPDATE
  6  SET T2.MONEY=T1.MONEY+T2.MONEY
  7  DELETE WHERE (T2.NAME = 'A' )
  8  ;
Done
SQL> SELECT * FROM T1;
NAME                      MONEY
-------------------- ---------------------------------
A                            10
B                            20
SQL> SELECT * FROM T2;
NAME                      MONEY
-------------------- --------------------------------
C                            20
    发现其实T1源表的记录根本还是保留着,还只是目标表被删除了。
2.6  更新同一张表的数据,需担心USING的空值
SQL> SELECT * FROM T2;
NAME                      MONEY
-------------------- ---------------------------------
A                            30
C                            20
需求为对T2表进行自我更新,如果在T2表中发现NAME=’D’的记录,就将该记录的MONEY字段更新为100,如果NAME=D的记录不存在,则自动增加NAME=’D’的记录。
    根据语法完成如下代码
SQL> MERGE INTO T2
  2  USING (SELECT * FROM t2 WHERE NAME='D') T
  3  ON (T.NAME=T2.NAME)
  4  WHEN MATCHED THEN
  5  UPDATE
  6  SET T2.MONEY=100
  7  WHEN NOT MATCHED THEN
  8  INSERT
  9  VALUES ('D',200)
 10  ;
Done
但是查询发现,本来T表应该因为NAME=D不存在而要增加记录,但是实际却根本无变化。
SQL> SELECT * FROM T2;
NAME                      MONEY
-------------------------------------------------------
A                            30
C                            20
原因是USING后面必须包含要更新或插入的行。而第一个USING (SELECT * FROM t2 WHERE NAME='D') T 根本没有这一行,可改造如下巧妙实现需求:
SQL> MERGE INTO T2
  2  USING (SELECT COUNT(*) CNT FROM t2 WHERE NAME='D') T
  3  ON (T.CNT<>0)
  4  WHEN MATCHED THEN
  5  UPDATE
  6  SET T2.MONEY=100
  7  WHEN NOT MATCHED THEN
  8  INSERT
  9  VALUES ('D',100)
 10  ;
Done
SQL> SELECT * FROM T2;
NAME                      MONEY
-------------------- -----------------------------------
A                            30
C                            20
D                           100
 
3    MERGE的巧妙运用
案例1:
需求为:将如下TEST记录的ID=1的NAME改为ID=2的NAME的值,把ID=2的NAME改为ID=1的NAME的值。
drop table test;
create table test (id number,name varchar2(20));
insert into test values (1,'a');
insert into test values (2,'b');
COMMIT;
SQL> SELECT * FROM test;
        ID NAME
---------- --------------------
         1 a
         2 b
如果执行如下:
UPDATE TEST SET NAME =(SELECT NAME FROM TEST WHERE ID=2) WHERE ID=1;
此时ID=1的NAME值已改变了,就不可能用如下来更新了
UPDATE TEST SET NAME =(SELECT NAME FROM TEST WHERE ID=1) WHERE ID=2;
如果是过程就很简单了,可以把原先的值先存储起来。但是是否单条SQL一定不行呢?
其实单条SQL是可以解决的,可考虑灵活利用MERGE特性!思路可考虑构造出一个虚拟表T,然后再根据此虚拟T表和真实的TEST表进行MERGE更新,就方便快捷的完成了。构造的虚拟表方法类似如下:
SQL> SELECT 1 id,(SELECT name FROM test WHERE id=2) name FROM DUAL
  2         UNION ALL
  3         SELECT 2,(SELECT name FROM test WHERE id=1) FROM DUAL
  4  ;
        ID NAME
---------- --------------------
         1 b
         2 a
   有了此思路,结合前面所学的MERGE知识,可以通过如下简洁优雅定的代码完成更新。
SQL> MERGE INTO TEST
  2  USING (SELECT 1 id,(SELECT name FROM test WHERE id=2) name FROM DUAL
  3         UNION ALL
  4         SELECT 2,(SELECT name FROM test WHERE id=1) FROM DUAL
  5         ) t
  6  ON (test.id = t.id)
  7  WHEN MATCHED THEN UPDATE set TEST.name = t.name
  8  ;
Done
SQL> SELECT * FROM test;
        ID NAME
---------- --------------------
         1 b
         2 a
注:如果是9I固定需要INSERT,所以需要随便加上如下内容
WHEN NOT MATCHED THEN
INSERT   VALUES (1,'a')
 
本案例用的是MERGE的方法,当然,其中的构造虚拟表也是一个非常重要的思路,如果只是查询出改变后的结果而不是真实的进行更新,就可以不采用MERGE,直接可以采用如下方式取出结果
SQL> rollback;
Rollback complete
SQL>
SQL> WITH T AS
  2  (SELECT 1 id,(SELECT name FROM test WHERE id=2) name FROM DUAL
  3         UNION ALL
  4         SELECT 2,(SELECT name FROM test WHERE id=1) FROM DUAL
  5         )
  6  SELECT test.id,t.name FROM test ,t
  7  WHERE test.id=t.id;
        ID NAME
---------- --------------------
         1 b
         2 a


在这里了解一下with as的语法:


create table t (x number(10), y number(10));
insert into t values (1,110);
insert into t values (2,120);
insert into t values (2,80);
insert into t values (3,150);
insert into t values (3,30);
insert into t values (3,60);
commit;
select * from t;
需求描述
按照x列分组后统计y列的总值,最终目标是选出比y列总值的三分之一大的那些分组统计信息

使用子查询方式实现
最容易想到的方法
SELECT x, SUM (y) AS total_y
     FROM t
   GROUP BY x
   HAVING SUM (y) > (SELECT SUM(y) / 3 FROM t)
   ORDER BY total_y



WITH Clause方法闪亮登场
WITH secooler_sum AS (SELECT x, SUM (y) total_y
                          FROM t
                        GROUP BY x)
  SELECT x, total_y
    FROM secooler_sum
   WHERE total_y > (SELECT SUM (total_y) / 3 FROM secooler_sum)
  ORDER BY total_y

查询语句不是以select开始的,而是以“WITH”关键字开头
可认为在真正进行查询之前预先构造了一个临时表secooler_sum,之后便可多次使用它做进一步的分析和处理
WITH Clause方法的优点
增加了SQL的易读性,如果构造了多个子查询,结构会更清晰;
更重要的是:“一次分析,多次使用”,这也是为什么会提供性能的地方,达到了“少读”的目标
知其所以然
为什么WITH Clause方法会提高效率?通过查看上面两种方法的执行计划便可略知一二
1)使用子查询的执行计划
set autot trace exp

第一种使用子查询的方法T表被扫描了两次,而使用WITH Clause方法,T表仅被扫描一次
这也是为什么在大型数据仓库系统中推荐使用WITH Clause方法进行查询统计的原因,这样可以大大的提高数据分析和查询的效率

另外,观察WITH Clause方法执行计划,其中“SYS_TEMP_XXXX”便是在运行过程中构造的中间统计结果临时表。

案例2
通过MERGE可以得到一个非常有用的思想,就是如下:只要能查出更新后的结果集,就可利用该结果集来更新原表记录,即MERGE+ROWID方式。感谢NEWKID给予的指点,他精于使用此类方法,下文案例3中的复杂MERGE更新例子即来自NEWKID的精彩脚本。
本案例2来源于案例1的延伸,改变了案例1的处理思路,不再采用构造虚拟表T来关联TEST表的方式,而是直接把真实结果用SELECT的方式取出,然后利用这个结果集更新回原表中。
SQL> merge into  test using
  2  (
  3  WITH T AS
  4   (SELECT 1 id,(SELECT name FROM test WHERE id=2) name FROM DUAL
  5          UNION ALL
  6          SELECT 2,(SELECT name FROM test WHERE id=1) FROM DUAL
  7          )
  8   SELECT test.id,test.rowid as rn ,t.name FROM test ,t
  9   WHERE test.id=t.id
10  )      n
 11 on(test.rowid=n.rn)
 12  when matched then  update
 13  set test.name=n.name;
SQL> SELECT * FROM test;
        ID NAME
---------- --------------------
         1 a
         2 b
 
注:直接UPDATE一个子查询的写法也可行,但是却有很多限制,稍微复杂的查询都易出错。此时用MERGE是最好的办法,结合ROWID的方式,可快速准确的利用一个已查询出的结果集来更新自己,是一个非常好的思路的扩展,希望对大家有借鉴。
案例3
在文章的最后,举一个USING里面有复杂的连接、聚合、分析函数的综合性例子来加深读者的印象,从而更深入的理解MERGE的强大功能!脚本选自NEWKID在答网友提问的一次精彩回复,提问需求如下:
declarecursor c1 isSELECT art_no,stock
FROM  tb_fin_art_stockWHERE  run_date=to_date('&日期','yyyymmdd')-1  and art_no in (158756);t_art tb_fin_art_stock.art_no%type;t_stock tb_fin_art_STOCK.stock%type;beginopen c1;loop        fetch c1 into t_art,t_stock;        exit when c1%notfound; update tb_fin_art_stock set stock=t_stock+gor_qty+return_qty-sale_qty+stock_corr+DEL_CORR                 WHERE run_date=to_date('&日期','yyyymmdd')                        and art_no=t_art;        commit;end loop;close c1;end;    以上代码,我想更新tb_fin_art_stock 这个表中某个货号某一天往后所有记录的 stock(库存)字段的值,每天的stock是根据前一天的stock字段的值加进货减销售得出来的。现在只能一天一天更新。我想问的是如何能输入日期范围,比如10号到20号的记录,根据9号更改10号,根据10号再改11号,。。。。以此类推,一次就更新了。
MERGE精彩解决方案如下,有兴趣的读者自行研究,相信必有收获!
MERGE INTO tb_fin_art_stock tUSING (SELECT t.ROWID rid             ,t2.stock+SUM(gor_qty+return_qty-sale_qty+stock_corr+DEL_CORR) OVER(PARTITION BY art_no ORDER BY run_date) AS stock         FROM tb_fin_art_stock t             ,(SELECT art_no,stock FROM tb_fin_art_stock WHERE run_date = lv_start_date-1) t2        WHERE t.run_date BETWEEN lv_start_date AND lv_end_date              AND t.art_no = t2.art_no       ) nUSING (t.ROWID = n.rid)WHEN MATCHED THEN UPDATE SET t.stock = n.stock;
 
T INTO T2 VALUES ('C',20);COMMIT;大家知道,一般逻辑思路,该需要至少要UPDATE和INSERT两条SQL才能完成,如考虑在PL/SQL中用纯编程语言思路实现,则必须要考虑判断的逻辑,这样显得更麻烦了。而MERGE语句可直接用单条SQL简洁明快的实现了此业务逻辑,具体如下:MERGE INTO T2USING T1ON (T1.NAME=T2.NAME)WHEN MATCHED THENUPDATESET T2.MONEY=T1.MONEY+T2.MONEYWHEN NOT MATCHED THENINSERTVALUES (T1.NAME,T1.MONEY);    此外MERGE语句同样可以分析执行计划,具体如下:SQL> explain plan for  2  MERGE INTO T2  3  USING T1  4  ON (T1.NAME=T2.NAME)  5  WHEN MATCHED THEN  6  UPDATE  7  SET T2.MONEY=T1.MONEY+T2.MONEY  8  WHEN NOT MATCHED THEN  9  INSERT 10  VALUES (T1.NAME,T1.MONEY);ExplainedSQL> SELECT * FROM table(dbms_xplan.display);PLAN_TABLE_OUTPUT---------------------------------------------------------------------------Plan hash value: 2414655244----------------------------------------------------------------------------| Id  | Operation                 | Name |  Rows  | Bytes  |  Cost (%CPU)| Time   |----------------------------------------------------------------------------|   0 | MERGE STATEMENT         |      |     2 |   152  |     7  (15)| 00:00:01 ||   1 |  MERGE                  | T2   |       |        |           |        ||   2 |   VIEW                  |      |       |        |           |        ||*  3 |    HASH JOIN OUTER       |      |     2 |   124  |    7  (15) | 00:00:01 ||   4 |     TABLE ACCESS FUL L     | T1   |     2 |     50  |     3   (0)| 00:00:01 ||   5 |     TABLE ACCESS FULL     | T2    |     2 |    74  |     3   (0)| 00:00:01 |-------------------------------------------------------------------------------------Predicate Information (identified by operation id):---------------------------------------------------   3 - access("T1"."NAME"="T2"."NAME"(+))Note-----PLAN_TABLE_OUTPUT---------------------------------------------------------------------------- - dynamic sampling used for this statement21   ows selected 1.2 MERGE语法简介语法如下:MERGE [hint] INTO [schema .] table [t_alias] USING [schema .]{ table | view | subquery } [t_alias] ON ( condition )WHEN MATCHED THEN merge_update_clauseWHEN NOT MATCHED THEN merge_insert_clause; 1.3  Oracle10g中MERGE的完善在Oracle10g以后,Oracle的MERGE发生了改变1.4  UPDATE和INSERT动作可只出现其一可选择仅UPDATE目标表SQL> MERGE INTO T2  2  USING T1  3  ON (T1.NAME=T2.NAME)  4  WHEN MATCHED THEN  5  UPDATE  6  SET T2.MONEY=T1.MONEY+T2.MONEY;Done 也可选择仅仅INSERT目标表而不做任何UPDATE动作SQL> MERGE INTO T2  2  USING T1  3  ON (T1.NAME=T2.NAME)  4  WHEN NOT MATCHED THEN  5  INSERT  6  VALUES (T1.NAME,T1.MONEY);Done   注:Oracle9i必须同时出现INSERT和UPDATE操作。1.5  可对MERGE语句加where条件SQL> MERGE INTO T2  2  USING T1  3  ON (T1.NAME=T2.NAME)  4  WHEN MATCHED THEN  5  UPDATE  6  SET T2.MONEY=T1.MONEY+T2.MONEY  7  WHERE T1.NAME='A' ---此处表示对MERGE的条件进行过滤  8  ;Done 1.6  可用DELETE子句清除行(必须同时满足on后的条件和delete where后的条件才有效)在这种情况下,首先是要先满足T1.NAME=T2.NAME的记录,如果T2.NAME=’A’并不满足T1.NAME=T2.NAME过滤出的记录集,那这个DELETE是不会生效的,在满足的条件下,可以删除目标表的记录。MERGE INTO T2USING T1ON (T1.NAME=T2.NAME)WHEN MATCHED THENUPDATESET T2.MONEY=T1.MONEY+T2.MONEYDELETE WHERE (T2.NAME = 'A');1.7可采用无条件方式Insert方法很简单,在语法ON关键字处写上恒不等条件(如1=2)后,MATCHED语句的INSERT就变为无条件INSERT了,同于INSERT...SELECT的写法,具体如下SQL> MERGE INTO T2  2  USING T1  3  ON (1=2)  4  WHEN NOT MATCHED THEN  5  INSERT  6  VALUES (T1.NAME,T1.MONEY);Done  2    MERGE误区探索2.3  无法在源表中获得一组稳定的行(目标表的一条记录对应源表的多条记录)MERGE INTO T2 USING T1 ON...的MERGE表达式中,如果一条T2记录被连接到多条T1记录,就产生了ORA-30926错误。构造T1,T2表进行试验如下,此次T1表中增加了('A',30)的记录,如下:DROP TABLE T1;CREATE TABLE T1 (NAME VARCHAR2(20),MONEY NUMBER);INSERT INTO T1 VALUES ('A',10);INSERT INTO T1 VALUES ('A',30);INSERT INTO T1 VALUES ('B',20);DROP TABLE T2;CREATE TABLE T2 (NAME VARCHAR2(20),MONEY NUMBER);INSERT INTO T2 VALUES ('A',30);INSERT INTO T2 VALUES ('C',20);COMMIT 此时继续执行如下SQL> MERGE INTO T2  2  USING T1  3  ON (T1.NAME=T2.NAME)  4  WHEN MATCHED THEN  5  UPDATE  6  SET T2.MONEY=T1.MONEY+T2.MONEY  7  ;ORA-30926: 无法在源表中获得一组稳定的行 Oracle中的merge语句应该保证on中的条件的唯一性,T1.NAME=’A’时,T2表记录对应到了T1表的两条记录,所以就出错了。 解决方法很简单,可对T1表和T2表的关联字段建主键,这样基本上就不可能出现这样的问题,而且一般而言,MERGE语句的关联字段互相有主键,MERGE的效率将比较高! 或者是将T1表的ID列做一个聚合,这样归并成单条,也能避免此类错误。如:SQL> MERGE INTO T2  2  USING (SELECT NAME,SUM(MONEY) AS MONEY FROM T1 GROUP BY NAME)T1  3  ON (T1.NAME=T2.NAME)  4  WHEN MATCHED THEN  5  UPDATE  6  SET T2.MONEY=T1.MONEY+T2.MONEY  7  ;Done但是这样的改造需要注意,因为有可能改变了最终的需求。此外需要引起注意的是,MERGE INTO T2 USING T1 ON...的MERGE表达式中,如果反过来,一条T1记录被连接到多条T2记录,是可以导致多条T2记录都被更新而不会出错!继续构造T1,T2表进行试验如下,此次是在T2表中增加了('A',40)的记录,如下:DROP TABLE T1;CREATE TABLE T1 (NAME VARCHAR2(20),MONEY NUMBER);INSERT INTO T1 VALUES ('A',10);INSERT INTO T1 VALUES ('B',20);DROP TABLE T2;CREATE TABLE T2 (NAME VARCHAR2(20),MONEY NUMBER);INSERT INTO T2 VALUES ('A',30);INSERT INTO T2 VALUES ('A',40);INSERT INTO T2 VALUES ('C',20);COMMIT   此时继续执行如下,发现执行可以成功并没有报无法在源表中获得一组稳定的行的ORA-30926错误SQL>  MERGE INTO T2  2      USING T1  3      ON (T1.NAME=T2.NAME)  4      WHEN MATCHED THEN  5      UPDATE  6      SET T2.MONEY=T1.MONEY+T2.MONEY  7      ;DoneSQL> COMMIT;Commit complete   查看T2表,发现T2表中NAME=A的2条记录都被更新了SQL> SELECT * FROM T2;NAME                      MONEY-------------------- ----------------------------------A                            40A                            50C                            20 2.4  DELETE子句的WHERE顺序必须最后SQL> MERGE INTO T2  2  USING T1  3  ON (T1.NAME=T2.NAME)  4  WHEN MATCHED THEN  5  UPDATE  6  SET T2.MONEY=T1.MONEY+T2.MONEY  7  DELETE WHERE (T2.NAME = 'A')  8  WHERE T1.NAME='A';/ORA-00933: SQL 命令未正确结束改为如下即可SQL> MERGE INTO T2  2  USING T1  3  ON (T1.NAME=T2.NAME)  4  WHEN MATCHED THEN  5  UPDATE  6  SET T2.MONEY=T1.MONEY+T2.MONEY  7  WHERE T1.NAME='A'  8  DELETE WHERE (T2.NAME = 'A')  9  ;Done    注:只要是MERGE语句,UPDATE和DELETE两者必须要出现其一,所以上面的脚本是不能省略UPDATE而只做DELETE的。另外WHERE (T2.NAME = 'A')的括号可以省略。2.5  DELETE 子句只可以删除目标表,而无法删除源表    这里需要引起注意,无论DELETE WHERE (T2.NAME = 'A' )这个写法的T2是否改写为T1,效果都一样,都是对目标表进行删除!SQL> SELECT * FROM T1;NAME                    MONEY-------------------- ------------------------------A                            10B                            20SQL> SELECT * FROM T2;NAME                      MONEY-------------------- --------------------------------A                            30C                            20SQL> MERGE INTO T2  2  USING T1  3  ON (T1.NAME=T2.NAME)  4  WHEN MATCHED THEN  5  UPDATE  6  SET T2.MONEY=T1.MONEY+T2.MONEY  7  DELETE WHERE (T2.NAME = 'A' )  8  ;DoneSQL> SELECT * FROM T1;NAME                      MONEY-------------------------------------------------------A                            10B                            20SQL> SELECT * FROM T2;NAME                      MONEY-------------------- ---------------------------------C                            20可以看出目标表的A记录被删除了,但是如果把DELETE WHERE (T2.NAME = 'A' )修改为DELETE WHERE (T1.NAME = 'A' ),是否就会把源表的T1记录给删除了呢?试验如下:Rollback completeSQL> MERGE INTO T2  2  USING T1  3  ON (T1.NAME=T2.NAME)  4  WHEN MATCHED THEN  5  UPDATE  6  SET T2.MONEY=T1.MONEY+T2.MONEY  7  DELETE WHERE (T2.NAME = 'A' )  8  ;DoneSQL> SELECT * FROM T1;NAME                      MONEY-------------------- ---------------------------------A                            10B                            20SQL> SELECT * FROM T2;NAME                      MONEY-------------------- --------------------------------C                            20    发现其实T1源表的记录根本还是保留着,还只是目标表被删除了。2.6  更新同一张表的数据,需担心USING的空值SQL> SELECT * FROM T2;NAME                      MONEY-------------------- ---------------------------------A                            30C                            20需求为对T2表进行自我更新,如果在T2表中发现NAME=’D’的记录,就将该记录的MONEY字段更新为100,如果NAME=D的记录不存在,则自动增加NAME=’D’的记录。    根据语法完成如下代码SQL> MERGE INTO T2  2  USING (SELECT * FROM t2 WHERE NAME='D') T  3  ON (T.NAME=T2.NAME)  4  WHEN MATCHED THEN  5  UPDATE  6  SET T2.MONEY=100  7  WHEN NOT MATCHED THEN  8  INSERT  9  VALUES ('D',200) 10  ;Done但是查询发现,本来T表应该因为NAME=D不存在而要增加记录,但是实际却根本无变化。SQL> SELECT * FROM T2;NAME                      MONEY-------------------------------------------------------A                            30C                            20原因是USING后面必须包含要更新或插入的行。而第一个USING (SELECT * FROM t2 WHERE NAME='D') T 根本没有这一行,可改造如下巧妙实现需求:SQL> MERGE INTO T2  2  USING (SELECT COUNT(*) CNT FROM t2 WHERE NAME='D') T  3  ON (T.CNT<>0)  4  WHEN MATCHED THEN  5  UPDATE  6  SET T2.MONEY=100  7  WHEN NOT MATCHED THEN  8  INSERT  9  VALUES ('D',100) 10  ;DoneSQL> SELECT * FROM T2;NAME                      MONEY-------------------- -----------------------------------A                            30C                            20D                           100 3    MERGE的巧妙运用案例1:需求为:将如下TEST记录的ID=1的NAME改为ID=2的NAME的值,把ID=2的NAME改为ID=1的NAME的值。drop table test;create table test (id number,name varchar2(20));insert into test values (1,'a');insert into test values (2,'b');COMMIT;SQL> SELECT * FROM test;        ID NAME---------- --------------------         1 a         2 b如果执行如下:UPDATE TEST SET NAME =(SELECT NAME FROM TEST WHERE ID=2) WHERE ID=1;此时ID=1的NAME值已改变了,就不可能用如下来更新了UPDATE TEST SET NAME =(SELECT NAME FROM TEST WHERE ID=1) WHERE ID=2;如果是过程就很简单了,可以把原先的值先存储起来。但是是否单条SQL一定不行呢?其实单条SQL是可以解决的,可考虑灵活利用MERGE特性!思路可考虑构造出一个虚拟表T,然后再根据此虚拟T表和真实的TEST表进行MERGE更新,就方便快捷的完成了。构造的虚拟表方法类似如下:SQL> SELECT 1 id,(SELECT name FROM test WHERE id=2) name FROM DUAL  2         UNION ALL  3         SELECT 2,(SELECT name FROM test WHERE id=1) FROM DUAL  4  ;        ID NAME---------- --------------------         1 b         2 a   有了此思路,结合前面所学的MERGE知识,可以通过如下简洁优雅定的代码完成更新。SQL> MERGE INTO TEST  2  USING (SELECT 1 id,(SELECT name FROM test WHERE id=2) name FROM DUAL  3         UNION ALL  4         SELECT 2,(SELECT name FROM test WHERE id=1) FROM DUAL  5         ) t  6  ON (test.id = t.id)  7  WHEN MATCHED THEN UPDATE set TEST.name = t.name  8  ;DoneSQL> SELECT * FROM test;        ID NAME---------- --------------------         1 b         2 a注:如果是9I固定需要INSERT,所以需要随便加上如下内容WHEN NOT MATCHED THENINSERT   VALUES (1,'a') 本案例用的是MERGE的方法,当然,其中的构造虚拟表也是一个非常重要的思路,如果只是查询出改变后的结果而不是真实的进行更新,就可以不采用MERGE,直接可以采用如下方式取出结果SQL> rollback;Rollback completeSQL>SQL> WITH T AS  2  (SELECT 1 id,(SELECT name FROM test WHERE id=2) name FROM DUAL  3         UNION ALL  4         SELECT 2,(SELECT name FROM test WHERE id=1) FROM DUAL  5         )  6  SELECT test.id,t.name FROM test ,t  7  WHERE test.id=t.id;        ID NAME---------- --------------------         1 b         2 a在这里了解一下with as的语法:create table t (x number(10), y number(10)); insert into t values (1,110); insert into t values (2,120); insert into t values (2,80); insert into t values (3,150); insert into t values (3,30); insert into t values (3,60); commit;select * from t; 需求描述 按照x列分组后统计y列的总值,最终目标是选出比y列总值的三分之一大的那些分组统计信息 使用子查询方式实现 最容易想到的方法 SELECT x, SUM (y) AS total_y      FROM t    GROUP BY x    HAVING SUM (y) > (SELECT SUM(y) / 3 FROM t)    ORDER BY total_y WITH Clause方法闪亮登场 WITH secooler_sum AS (SELECT x, SUM (y) total_y                           FROM t                         GROUP BY x)   SELECT x, total_y     FROM secooler_sum    WHERE total_y > (SELECT SUM (total_y) / 3 FROM secooler_sum)   ORDER BY total_y 查询语句不是以select开始的,而是以“WITH”关键字开头 可认为在真正进行查询之前预先构造了一个临时表secooler_sum,之后便可多次使用它做进一步的分析和处理 WITH Clause方法的优点 增加了SQL的易读性,如果构造了多个子查询,结构会更清晰; 更重要的是:“一次分析,多次使用”,这也是为什么会提供性能的地方,达到了“少读”的目标 知其所以然 为什么WITH Clause方法会提高效率?通过查看上面两种方法的执行计划便可略知一二 1)使用子查询的执行计划 set autot trace exp 第一种使用子查询的方法T表被扫描了两次,而使用WITH Clause方法,T表仅被扫描一次 这也是为什么在大型数据仓库系统中推荐使用WITH Clause方法进行查询统计的原因,这样可以大大的提高数据分析和查询的效率 另外,观察WITH Clause方法执行计划,其中“SYS_TEMP_XXXX”便是在运行过程中构造的中间统计结果临时表。 案例2通过MERGE可以得到一个非常有用的思想,就是如下:只要能查出更新后的结果集,就可利用该结果集来更新原表记录,即MERGE+ROWID方式。感谢NEWKID给予的指点,他精于使用此类方法,下文案例3中的复杂MERGE更新例子即来自NEWKID的精彩脚本。本案例2来源于案例1的延伸,改变了案例1的处理思路,不再采用构造虚拟表T来关联TEST表的方式,而是直接把真实结果用SELECT的方式取出,然后利用这个结果集更新回原表中。SQL> merge into  test using  2  (  3  WITH T AS  4   (SELECT 1 id,(SELECT name FROM test WHERE id=2) name FROM DUAL  5          UNION ALL  6          SELECT 2,(SELECT name FROM test WHERE id=1) FROM DUAL  7          )  8   SELECT test.id,test.rowid as rn ,t.name FROM test ,t  9   WHERE test.id=t.id10  )      n 11 on(test.rowid=n.rn) 12  when matched then  update 13  set test.name=n.name;SQL> SELECT * FROM test;        ID NAME---------- --------------------         1 a         2 b 注:直接UPDATE一个子查询的写法也可行,但是却有很多限制,稍微复杂的查询都易出错。此时用MERGE是最好的办法,结合ROWID的方式,可快速准确的利用一个已查询出的结果集来更新自己,是一个非常好的思路的扩展,希望对大家有借鉴。案例3在文章的最后,举一个USING里面有复杂的连接、聚合、分析函数的综合性例子来加深读者的印象,从而更深入的理解MERGE的强大功能!脚本选自NEWKID在答网友提问的一次精彩回复,提问需求如下:declarecursor c1 isSELECT art_no,stockFROM  tb_fin_art_stockWHERE  run_date=to_date('&日期','yyyymmdd')-1  and art_no in (158756);t_art tb_fin_art_stock.art_no%type;t_stock tb_fin_art_STOCK.stock%type;beginopen c1;loop        fetch c1 into t_art,t_stock;        exit when c1%notfound; update tb_fin_art_stock set stock=t_stock+gor_qty+return_qty-sale_qty+stock_corr+DEL_CORR                 WHERE run_date=to_date('&日期','yyyymmdd')                        and art_no=t_art;        commit;end loop;close c1;end;    以上代码,我想更新tb_fin_art_stock 这个表中某个货号某一天往后所有记录的 stock(库存)字段的值,每天的stock是根据前一天的stock字段的值加进货减销售得出来的。现在只能一天一天更新。我想问的是如何能输入日期范围,比如10号到20号的记录,根据9号更改10号,根据10号再改11号,。。。。以此类推,一次就更新了。MERGE精彩解决方案如下,有兴趣的读者自行研究,相信必有收获!MERGE INTO tb_fin_art_stock tUSING (SELECT t.ROWID rid             ,t2.stock+SUM(gor_qty+return_qty-sale_qty+stock_corr+DEL_CORR) OVER(PARTITION BY art_no ORDER BY run_date) AS stock         FROM tb_fin_art_stock t             ,(SELECT art_no,stock FROM tb_fin_art_stock WHERE run_date = lv_start_date-1) t2        WHERE t.run_date BETWEEN lv_start_date AND lv_end_date              AND t.art_no = t2.art_no       ) nUSING (t.ROWID = n.rid)WHEN MATCHED THEN UPDATE SET t.stock = n.stock; 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值