Oracle MERGE - 如果不匹配,则在条件通过时更新

时间:2014-06-25 13:39:34

标签: sql oracle conditional-statements sql-merge

我需要将一些值合并到一个表中,当具有指定键的行已经存在时更新字段,或者如果它不存在则插入新行。

这是我的表:

profiles(name, surname, active);

其中:

name    VARCHAR2(30)
surname VARCHAR2(30)
active  NUMBER(1)

name and surname -> composite primary key

我正在使用此查询:

MERGE INTO profiles USING (
    SELECT
        'Mark' myName,
        'Zibi' mySurname,
        '1'    myActive
   FROM DUAL
) ON (
   name = myName
   AND surname = mySurname
)
WHEN MATCHED THEN
    UPDATE SET
        active = myActive
WHEN NOT MATCHED THEN
    INSERT (
        name,
        surname,
        active
    ) VALUES (
        myName,
        mySurname,
        myActive
    );

虽然有效,但即使active已设置为1,它也会更新记录。

我想做的是这样的事情:

WHEN MATCHED THEN
    IF(active != myActive)
        UPDATE SET
            active = myActive
    ELSE
        RAISE CUSTOM EXCEPTION
WHEN NOT MATCHED THEN
    INSERT [...]

这可能吗? AFAIK我不能将if这样的MERGE放入{{1}}语句中,那怎么可能呢?

4 个答案:

答案 0 :(得分:2)

使用PL / SQL运行条件合并操作

  

编辑: 原始帖子询问如何通过SQL或PL / SQL可以将现有数据集处理到已建立的表(名为:PROFILES)中解决它。

     

再次编辑: 来自OP的最后评论非常微妙。如果您没有直接的SQL访问权限,那么您将需要一个CURSOR,一个驱动查询或其他一些构造来处理您提供的每条记录。许多基于JDBC的中间件组件也接受游标作为输入。您可以在一个过程调用中提供所有数据...查看PL / SQL中的REF CURSOR数据类型。如果是这种情况,这个解决方案仍然可以提供帮助。

使用复合连接键,根据多个条件更新目标表中的数据:

  1. INSERT源数据(如果它尚不存在)。
  2. 如果人员标识符(姓名+姓氏)存在,则切换或UPDATE状态值。
  3. 如果人员已经存在于目标表中并且具有“活跃”状态。状态已经跳过了。
  4. 样本数据

    我将我的表命名略有不同并修改了列名称" name"这是一个保留的sql / plsql关键字...以防止任何可能的未来冲突。

    示例数据插入语句(DML):

      

    *为清楚起见:测试模式中的名称与OP完全匹配。 STACK_PROFILES = PROFILESSTACK_PROFILE_MERGE_SOURCE表示"某些来源" ...这可能是xml Feed,csv文本文件等等。

    from: load_profile_data.sql...
    

    CREATE TABLE "STACK_PROFILES" ( "PROFILE_NAME" VARCHAR2(40), "SURNAME" VARCHAR2(40), "ACTIVE" NUMBER(1,0), CONSTRAINT "STACK_PROFILES_PK" PRIMARY KEY ("PROFILE_NAME", "SURNAME") ENABLE )

    INSERT INTO STACK_PROFILES (profile_name, surname, active) VALUES ('LOIS' , 'LAINE', 0); INSERT INTO STACK_PROFILES (profile_name, surname, active) VALUES ('MARTIN', 'SHORT', 1); INSERT INTO STACK_PROFILES (profile_name, surname, active) VALUES ('ROBIN' , 'WILLIAMS', 0); INSERT INTO STACK_PROFILES (profile_name, surname, active) VALUES ('GRACE' , 'HOPPER', 0); INSERT INTO STACK_PROFILES (profile_name, surname, active) VALUES ('LOIS' , 'LAINE-KENT', 0);

    commit; ...

    CREATE TABLE "STACK_PROFILE_MERGE_SOURCE" ( "PROFILE_NAME" VARCHAR2(40), "SURNAME" VARCHAR2(40), CONSTRAINT "STACK_PROFILE_MERGE_SOURCE_PK" PRIMARY KEY ("PROFILE_NAME", "SURNAME") ENABLE ) /

    INSERT INTO STACK_PROFILE_MERGE_SOURCE (profile_name, surname) VALUES ('BRUCE' , 'WAYNE'); INSERT INTO STACK_PROFILE_MERGE_SOURCE (profile_name, surname) VALUES ('SPONGE' , 'ROBERT'); INSERT INTO STACK_PROFILE_MERGE_SOURCE (profile_name, surname) VALUES ('CLARK' , 'KENT'); INSERT INTO STACK_PROFILE_MERGE_SOURCE (profile_name, surname) VALUES ('LOIS' , 'LAINE'); INSERT INTO STACK_PROFILE_MERGE_SOURCE (profile_name, surname) VALUES ('MARTIN' , 'SHORT'); INSERT INTO STACK_PROFILE_MERGE_SOURCE (profile_name, surname) VALUES ('DAMON' , 'WAYANS'); INSERT INTO STACK_PROFILE_MERGE_SOURCE (profile_name, surname) VALUES ('ROBIN' , 'WILLIAMS'); INSERT INTO STACK_PROFILE_MERGE_SOURCE (profile_name, surname) VALUES ('BRUCE' , 'WILLIS'); INSERT INTO STACK_PROFILE_MERGE_SOURCE (profile_name, surname) VALUES ('DENNIS' , 'HOPPER'); INSERT INTO STACK_PROFILE_MERGE_SOURCE (profile_name, surname) VALUES ('WHOOPI' , 'GOLDBERG'); INSERT INTO STACK_PROFILE_MERGE_SOURCE (profile_name, surname) VALUES ('GRACE' , 'HOPPER'); INSERT INTO STACK_PROFILE_MERGE_SOURCE (profile_name, surname) VALUES ('JERI' , 'RYAN');

    测试用例

    了解所提出的要求很有帮助。编写一些测试用例可以让我们更接近。

    对于测试用例1和2 ......

    Conditional Merge Test Cases 1,2

    对于测试用例3和4 ......

    Conditional Merge Test Cases 3,4

    PL / SQL源代码

    有一种更简单的方法可以通过SQL-merge函数应用其他条件逻辑。下面的PL / SQL匿名块使用outer join syntax来标识要插入和更新的记录。当光标处理循环跳过该定义的记录时,也会观察到第三类(活动且已存在于目标表中)。

    处理循环和光标

    我们在dml操作中使用FOR UPDATEWHERE CURRENT OF语法,因为此查询中引用的数据状态在其使用的生命周期内发生了更改。

     declare
        c_default_status_active   constant number:= 1;
        c_status_inactive         constant number:= 0;
    
        cursor profile_cur is
           select sp.profile_name as target_name, 
                  sp.surname as target_surname, sp.active as original_status,
                  spm.profile_name as source_name, spm.surname as source_surname
    
             from stack_profiles sp, stack_profile_merge_source spm
            where spm.profile_name = sp.profile_name(+)
              and spm.surname = sp.surname(+)
            order by spm.profile_name asc nulls last, 
              spm.surname asc
              for update of sp.profile_name, sp.surname, sp.active;
    
            v_rec_profile  profile_cur%ROWTYPE;
    
         begin
    
          open profile_cur;
         fetch profile_cur into v_rec_profile;
    
         while profile_cur%found loop
           -- insert condition (no match in outer join...)
           if v_rec_profile.original_status is null
           then
           insert into stack_profiles (profile_name, surname, active)
           values (v_rec_profile.source_name, v_rec_profile.source_surname, 
               c_default_status_active);
    
           elsif
           -- flip status from inactive to active for existing but 
           -- inactive records.
           v_rec_profile.original_status = c_status_inactive then
           update stack_profiles
              set active = c_default_status_active
            where current of profile_cur;
              end if;
    
          fetch profile_cur into v_rec_profile;
          end loop;
          close profile_cur;
    
    commit;
    
    end;
    

    讨论

    我已经注意到这种问题的许多不同方法。这里使用的具体方法是证明所涉及的概念。结果可能会有所不同,具体取决于数据库配置,使用情况和设置。

答案 1 :(得分:0)

好吧,我认为这不是一个好习惯,但由于您的ACTIVE列的类型为NUMBER(1),您只需尝试将其值更新为更大的值就可以轻松生成ORA-01438异常。例如,如果active的旧值和旧值相等,则类似这样的内容将引发异常:

MERGE INTO profiles USING (
    SELECT
        'Mark' myName,
        'Zibi' mySurname,
        1    myActive
   FROM DUAL
) ON (
   name = myName
   AND surname = mySurname
)
WHEN MATCHED THEN
    UPDATE SET
        active =  CASE WHEN active = myActive THEN 11 ELSE myActive END
WHEN NOT MATCHED THEN
    INSERT (
        name,
        surname,
        active
    ) VALUES (
        myName,
        mySurname,
        myActive
    );

答案 2 :(得分:0)

在这种情况下,通过存储过程或仅从客户端执行匿名SQL块而不是单个MERGE SQL语句,最好使用PL / SQL。

匿名PL / SQL块可能如下所示:

declare
  -- Parameters of query, initialization values  
  pName    profiles.name%type    := 'Mark';
  pSurname profiles.surname%type := 'Zibi';
  pActive  profiles.active%type  := 0;

  -- variable used for test against table
  vIsActiveInDb profiles.active%type;
begin

  select 
    max(profs.active) into vIsActiveInDb
  from 
    profiles profs
  where 
    profs.name = pName and profs.surname = pSurname
  ;

  if(vIsActiveInDb is null) then
    -- profile not found, create new one 
    insert into profiles(name, surname, active)
    values(pName, pSurname, pActive);

  elsif(vIsActiveInDb != pActive) then
    -- profile found, activity flag differs 
    update profiles set active = pActive 
    where name = pName and surname = pSurname;

  else
    -- profile found with same activity flag
    raise_application_error(
      -20001, -- custom error code from -20000 to -20999
      'Profile "'||pName||' '||pSurname||'" already exists with same activity flag'
    );  
  end if;

end;

SQLFiddle

以上代码中有两条建议:
1. (name, surname)对是主键,所以总是选择单行或什么都没有;
2. active字段不能为空(例如,使用not null约束创建) 如果这个建议失败,代码会更复杂一些。此变体可在this SQLFiddle中找到。

我从未使用MyBatis但基于answer from your comment此类查询的XML描述可能如下所示:

<update id="UpdateProfileActivity" parameterType="map" statementType="CALLABLE">   
  declare
    -- Parameters of query, initialization values
    pName    profiles.name%type    := #{piName,    mode=IN, jdbcType=VARCHAR};
    pSurname profiles.surname%type := #{piSurname, mode=IN, jdbcType=VARCHAR};
    pActive  profiles.active%type  := #{piActivity,mode=IN, jdbcType=NUMERIC};

    -- variable used for test against table
    vIsActiveInDb profiles.active%type;   begin

    select
      max(profs.active) into vIsActiveInDb
    from
      profiles profs
    where
      profs.name = pName and profs.surname = pSurname
    ;

    if(vIsActiveInDb is null) then
      -- profile not found, create new one
      insert into profiles(name, surname, active)
      values(pName, pSurname, pActive);

    elsif(vIsActiveInDb != pActive) then
      -- profile found, activity flag differs
      update profiles set active = pActive
      where name = pName and surname = pSurname;

    else
      -- profile found with same activity flag
      raise_application_error(
        -20001, -- custom error code from -20000 to -20999
        'Profile "'||pName||' '||pSurname||'" already exists with same activity flag'
      );
    end if;

  end; 
</update>

答案 3 :(得分:0)

您可以通过在源-使用(----子查询---)上添加where条件来过滤匹配的命令,或者在不匹配后添加where条件。

在以下示例中,我将合并ID为520到530的记录,  同时,我不会插入id = 525

的记录
--------
merge into merchant_tmp2 dest
using (select * from merchant where id between 520 and 530) src
on(dest.id=src.id)
when matched then 
update set address=address ||' - updated'
when not matched then 
insert (ID,....)
values (src.ID,....)
where src.id <> 525;

ref:https://docs.oracle.com/cd/B28359_01/server.111/b28286/statements_9016.htm#SQLRF01606