ITPub博客

首页 > 数据库 > 数据库开发技术 > 通用SQL开发的利器——MERGE(中)Merge误区

通用SQL开发的利器——MERGE(中)Merge误区

原创 数据库开发技术 作者:梁敬彬 时间:2025-07-08 08:35:43 0 删除 编辑

上期回顾

    通用SQL开发的利器——MERGE(上)Merge初探 


三.MERGE误区探索

1. 无法在源表中获得一组稳定的行

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. 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')的括号可以省略。


3. 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记录给删除了呢?试验如下:

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源表的记录根本还是保留着,还只是目标表被删除了。

4. 更新同一张表的数据,需担心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


未完待续...

     通用SQL开发的利器——MERGE(下)Merge初探 


公众号:收获不止数据库

微信:ljbyxl1213

系列回顾


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/9091385/viewspace-3090372/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论
历任中电福富研究院副理事长、中盾安信研究院副院长、宁德时代技术顾问等职务,受聘电信集团专家和国家十四五课题负责人,著有《收获,不止Oracle》《收获,不止SQL优化》等多本畅销数据库技术书籍。

注册时间:2024-02-06

  • 博文量
    162
  • 访问量
    198127