我需要将一些值合并到一个表中,当具有指定键的行已经存在时更新字段,或者如果它不存在则插入新行。
这是我的表:
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}}语句中,那怎么可能呢?
答案 0 :(得分:2)
编辑: 原始帖子询问如何通过SQL或PL / SQL可以将现有数据集处理到已建立的表(名为:PROFILES)中解决它。
再次编辑: 来自OP的最后评论非常微妙。如果您没有直接的SQL访问权限,那么您将需要一个
CURSOR
,一个驱动查询或其他一些构造来处理您提供的每条记录。许多基于JDBC的中间件组件也接受游标作为输入。您可以在一个过程调用中提供所有数据...查看PL / SQL中的REF CURSOR
数据类型。如果是这种情况,这个解决方案仍然可以提供帮助。
使用复合连接键,根据多个条件更新目标表中的数据:
INSERT
源数据(如果它尚不存在)。UPDATE
状态值。我将我的表命名略有不同并修改了列名称" name"这是一个保留的sql / plsql关键字...以防止任何可能的未来冲突。
示例数据插入语句(DML):
*为清楚起见:测试模式中的名称与OP完全匹配。
STACK_PROFILES
=PROFILES
和STACK_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 ......
对于测试用例3和4 ......
有一种更简单的方法可以通过SQL-merge函数应用其他条件逻辑。下面的PL / SQL匿名块使用outer join syntax
来标识要插入和更新的记录。当光标处理循环跳过该定义的记录时,也会观察到第三类(活动且已存在于目标表中)。
处理循环和光标
我们在dml操作中使用FOR UPDATE
和WHERE 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;
以上代码中有两条建议:
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