简体   繁体   English

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

[英]Oracle MERGE - if not matched then update if condition passes

I need to merge some values into a table, updating a field when a row with the specified key already exists, or inserting a new row if it doesn't exist. 我需要将一些值合并到表中,当具有指定键的行已经存在时更新字段,或者如果不存在则插入新行。

This is my table: 这是我的桌子:

profiles(name, surname, active);

where: 哪里:

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

name and surname -> composite primary key

I'm using this query: 我正在使用此查询:

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
    );

It works, but it updates a record even if active is already set to 1 . 它可以工作,但是即使active已设置为1 ,它也会更新一条记录。

What I would like to do is something like this: 我想做的是这样的:

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

Is that possible? 那可能吗? AFAIK I cannot put an if like this into a MERGE statement, so how could it be done? AFAIK我不能将这样的if放入MERGE语句中,那怎么办呢?

Using PL/SQL to Run a Conditional Merge Operation 使用PL / SQL运行条件合并操作

Edit: The original post asks how to process an existing set of data into an established table (named: PROFILES) through an approach that SQL or PL/SQL can solve it. 编辑:原始帖子询问如何通过SQL或PL / SQL可以解决的方法将现有数据集处理到已建立的表(名为:PROFILES)中。

Edit Again: The last comment from OP was pretty subtle. 再次编辑: OP的最后评论非常微妙。 If you don't have direct SQL access, then you will need a CURSOR , a driving query or some other construct to process each of the records your feeding in anyways. 如果您没有直接的SQL访问权限,那么您将需要CURSOR ,驾驶查询或其他构造来以任何方式处理您的提要中的每条记录。 Many JDBC based middle-ware components also accept cursors as inputs. 许多基于JDBC的中间件组件也接受游标作为输入。 You could feed in all your data in one procedure call... take a look at REF CURSOR data types in PL/SQL. 您可以在一个过程调用中提供所有数据...查看PL / SQL中的REF CURSOR数据类型。 If that is the case, this solution can still help. 如果是这样,此解决方案仍然可以提供帮助。

Using a composite join key, update data in a target table based on multiple criteria: 使用复合联接键,基于多个条件更新目标表中的数据:

  1. INSERT source data if it does not exist already. INSERT源数据(如果尚不存在)。
  2. Toggle or UPDATE a status value if the person identifier (name + surname) exists. 如果个人标识符(姓名+姓氏)存在,则切换或UPDATE状态值。
  3. If person already exists in the target table and has an 'active' status already, skip it. 如果人员已经存在于目标表中并且已经具有“活动”状态,请跳过它。

Sample Data 样本数据

I named my tables slightly different and modified the column name "name" which is a reserved sql/plsql keyword... to prevent any possible future conflicts. 我为表命名略有不同,并修改了列名“名称”,这是一个保留的sql / plsql关键字...以防止将来可能发生任何冲突。

The sample data insert statements (DML): 样本数据插入语句(DML):

*For clarity: The names in the test schema are not an exact match to the OP. *为清楚起见:测试架构中的名称与OP不完全匹配。 STACK_PROFILES = PROFILES and STACK_PROFILE_MERGE_SOURCE represents "some source"... this could have been an xml feed, a csv text file, etc.etc. 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);

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');

Test Cases 测试用例

It's helpful to understand the requirements presented. 了解提出的要求将很有帮助。 Writing up a few test cases gets us closer. 编写一些测试用例使我们更加接近。

For test cases 1 and 2... 对于测试用例1和2 ...

条件合并测试用例1,2

For test cases 3 and 4... 对于测试用例3和4 ...

条件合并测试用例3,4

The PL/SQL Source Code PL / SQL源代码

There is a simpler way to apply additional conditional logic through a SQL-merge like function. 有一种更简单的方法可以通过类似SQL-merge的函数来应用其他条件逻辑。 The PL/SQL Anonymous block following uses outer join syntax to identify records to be inserted vs. updated. 后面的PL / SQL匿名块使用outer join syntax联接outer join syntax来标识要插入和更新的记录。 The third category (active and already present in the target table) is also observed as the cursor processing loop skips records of that definition. 当游标处理循环跳过该定义的记录时,也会观察到第三类(活动的并且已经存在于目标表中)。

The processing loop and cursor 处理循环和游标

We use the FOR UPDATE and WHERE CURRENT OF syntax in the dml operations because the state of data referenced within this query changes during the lifespan of its use. 我们在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; 

Discussion 讨论

I have noted many different approaches to this type of problem. 我已经注意到许多解决此类问题的方法。 The specific approach used here is to demonstrate the concept involved. 这里使用的特定方法是演示所涉及的概念。 Results may vary depending on the database configuration, its usage and set up. 结果可能会有所不同,具体取决于数据库配置,其用法和设置。

Ok, this is not good practice, i suppose, but since your ACTIVE column has type NUMBER(1) you can easily generate ORA-01438 exception by simply trying to update it's value to bigger one. 好的,我想这不是一个好习惯,但是由于您的ACTIVE列的类型为NUMBER(1),因此您可以通过简单地尝试将其值更新为更大的值来轻松生成ORA-01438异常。 For example, something like this will throw an exception, if new and old values of active are equal: 例如,如果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
    );

It's better to use PL/SQL in such a case via the stored procedure or just by executing anonymous SQL block from a client side instead of single MERGE SQL statement. 在这种情况下,最好通过存储过程或仅从客户端执行匿名SQL块而不是单个MERGE SQL语句来使用PL / SQL。

Anonymous PL/SQL block may look like: 匿名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 SQLFiddle

There are two suggestions in code above: 上面的代码中有两个建议:
1. (name, surname) pair is a primary key, so always selected single row or nothing; 1. (name, surname)对是主键,因此总是选择单行或什么都不选;
2. active field can't be null (eg created with not null constraint). 2. active字段不能为null(例如,使用not null约束创建)。
Code would be a little bit more complicated if this suggestions fails. 如果此建议失败,则代码会稍微复杂一些。 This variant may be found in this SQLFiddle . 可以在此SQLFiddle中找到此变体。

I never used MyBatis but based on answer from your comment XML description for such query may look like that: 我从未使用MyBatis但基于您的注释 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>

You can do this by adding where condition either on source -using (---- subquery ---), to filter when matched command, or add where condition after when not matched. 您可以通过在源-using(----子查询---)上添加where条件来进行过滤,以在匹配命令时进行过滤,或者在不匹配之后添加where条件。

In the following example, I will merge records from id 520 up to 530, at the same time I will not insert record where id =525 在下面的示例中,我将合并从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 参考: https : //docs.oracle.com/cd/B28359_01/server.111/b28286/statements_9016.htm#SQLRF01606

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM