Sql Oracle合并-如果不匹配,则在条件通过时更新
我需要将一些值合并到一个表中,当具有指定键的行已经存在时更新字段,或者在不存在时插入新行 这是我的桌子:Sql Oracle合并-如果不匹配,则在条件通过时更新,sql,oracle,conditional-statements,sql-merge,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 我正在使用此查询: 使用合并到配置文件中 选择 “马克”我的名字, “紫碧”我的名字, “1”myAct
profiles(name, surname, active);
其中:
name VARCHAR2(30)
surname VARCHAR2(30)
active NUMBER(1)
name and surname -> composite primary key
我正在使用此查询:
使用合并到配置文件中
选择
“马克”我的名字,
“紫碧”我的名字,
“1”myActive
来自双重
在…上
name=myName
姓氏=mySurname
当匹配时
更新集
活动=我的活动
当不匹配时
插入
名称
姓
忙碌的
价值观
我的名字,
我的名字,
myActive
;
它可以工作,但即使active已设置为1,它也会更新记录
我想做的是这样的:
WHEN MATCHED THEN
IF(active != myActive)
UPDATE SET
active = myActive
ELSE
RAISE CUSTOM EXCEPTION
WHEN NOT MATCHED THEN
INSERT [...]
可能吗?好吧,我不能将这样的if放入MERGE语句中,那么怎么做呢?好吧,我想这不是一个好的做法,但由于活动列的类型为NUMBER1,您可以通过简单地尝试将其值更新为更大的值来轻松生成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
);
使用PL/SQL运行条件合并操作
编辑:原始帖子询问如何通过SQL或PL/SQL可以解决的方法将一组现有数据处理到一个名为PROFILES的已建立表中
再次编辑:OP的最后一条评论非常微妙。如果您没有直接的SQL访问权限,那么您将需要一个游标、一个驱动查询或其他构造来处理您输入的每个记录。许多基于JDBC的中间件组件也接受游标作为输入。您可以在一个过程调用中输入所有数据。。。看看PL/SQL中的REF-CURSOR数据类型。如果是这样,这个解决方案仍然有帮助
使用复合联接键,根据多个条件更新目标表中的数据:
如果源数据不存在,请插入源数据。
如果存在人员标识符名称+姓氏,则切换或更新状态值。
如果目标表中已存在人员且其状态为“活动”,则跳过该人员。
样本数据
我给我的表命名略有不同,并修改了列名,这是一个保留的sql/plsql关键字。。。防止将来可能发生的任何冲突
示例数据插入语句DML:
*为清楚起见:测试模式中的名称与OP不完全匹配。STACK_PROFILES=PROFILES和STACK_PROFILE_MERGE_SOURCE表示某些源。。。这可能是xml提要、csv文本文件等
创建表堆栈配置文件
配置文件名称VARCHAR240,
姓VARCHAR240,
活动编号1,0,
约束堆栈\配置文件\主键配置文件\名称、姓氏启用
在堆栈配置文件中插入名称、姓氏、活动值“LOIS”、“LAINE”、0;
在堆栈配置文件中插入配置文件名称、姓氏、活动值“MARTIN”、“SHORT”1;
在堆栈配置文件中插入配置文件名称、姓氏、活动值“ROBIN”、“WILLIAMS”,0;
在堆栈配置文件中插入配置文件名称、姓氏、活动值“GRACE”、“HOPPER”、0;
在堆栈配置文件中插入配置文件名称、姓氏、活动值“LOIS”、“LAINE-KENT”,0
承诺。。。
创建表堆栈\u配置文件\u合并\u源
配置文件名称VARCHAR240,
姓VARCHAR240,
约束堆栈\配置文件\合并\源\主键配置文件\名称、姓氏
使可能
/
插入堆栈_配置文件_合并_源配置文件_名称,姓氏值“BRUCE”,“WAYNE”;
插入堆栈_配置文件_合并_源配置文件_名称,姓氏值“海绵”,“罗伯特”;
插入堆栈_PROFILE_MERGE_SOURCE PROFILE_name,姓氏值'CLARK','KENT';
插入堆栈_配置文件_合并_源配置文件_名称,姓氏值'LOIS','LAINE';
插入堆栈_PROFILE_MERGE_SOURCE PROFILE_name,姓氏值'MARTIN','SHORT';
插入堆栈_配置文件_合并_源配置文件_名称,姓氏值'DAMON','WAYANS';
插入堆栈_配置文件_合并_源配置文件_名称,姓氏值‘ROBIN’、‘WILLIAMS’;
插入堆栈_PROFILE_MERGE_SOURCE PROFILE_name,姓氏值'BRUCE','WILLIS';
插入堆栈_配置文件_合并_源配置文件_名称,姓氏值‘DENNIS’、‘HOPPER’;
插入堆栈_配置文件_合并_源配置文件_名称,姓氏值'WHOOPI','GOLDBERG';
插入堆栈_PROFILE_MERGE_SOURCE PROFILE_name,姓氏值'GRACE','HOPPER';
插入堆栈_PROFILE_MERGE_SOURCE PROFILE_name,姓氏值'JERI','RYAN';
测试用例
理解所提出的要求是很有帮助的。写几个测试用例可以让我们更接近
对于测试用例1和2
对于测试用例3和4
PL/SQL源代码
有一种更简单的方法可以通过类似SQL合并的函数应用附加的条件逻辑。下面的PL/SQL匿名块使用外部连接语法来标识要插入的记录和要更新的记录。第三个c
当光标处理循环跳过该定义的记录时,还可以观察到目标表中已存在且处于活动状态的类别
处理循环和游标
我们在dml操作中使用FOR UPDATE和WHERE CURRENT语法,因为此查询中引用的数据的状态在其使用寿命期间发生变化
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;
讨论
我注意到对这类问题有许多不同的处理方法。这里使用的具体方法是演示所涉及的概念。结果可能因数据库配置、使用和设置而异 在这种情况下,最好通过存储过程使用PL/SQL,或者只从客户端执行匿名SQL块,而不是执行单个MERGE 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.姓名、姓氏对是一个主键,所以总是选择单行或不选;
2.活动字段不能为空,例如,使用not null约束创建。
如果这个建议失败了,代码会稍微复杂一点。该变体可在中找到
我从未使用过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>
您可以通过在源代码上添加where条件(使用--subquery--)来完成此操作,以便在匹配命令时进行筛选,或者在不匹配命令后添加where条件
在下面的示例中,我将合并从ID520到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:您可以在此处添加WHERE子句,但不能添加异常。你真的需要一个例外,还是仅仅更新记录不够?您可以添加一个触发器来捕获更改活动标志的代码实例,如果是,则引发一个异常。@Ben如果可能的话,我希望有一个异常。.您是仅限于SQL还是可以在此处使用PL/SQL?例如,执行开始。。。终止block?@ThinkJet我想我可以使用PL/SQL。我从一个java应用程序调用数据库,在这个应用程序中我使用MyBatis处理数据库,它应该支持PL/SQL。在MyBatis映射XML中使用PL/SQL,因此它应该是完美的。对于此任务,不需要外部连接、显式游标和游标循环。将commit合并到SQL代码中是一种糟糕的做法。另外,请举例说明。我将编辑您的建议+ThinkJet,但其他内容似乎不正确。。。我想我记得您和OP也认为PL/SQL,即外部连接、显式游标和游标循环可能是一种可能的方法…>您是仅限于SQL还是可以在这里使用PL/SQL?例如,执行开始。。。终止区块?–ThinkJet 10小时前我也喜欢SQLfiddle,但我也失去了使用他们系统的工作。。。如果他们真的出缺了,我在这里的职位还有意义吗?称我为老派,但我将永远把我的工作签入scm,或者发布完整的SQLDDL和DML,以便其他人可以跟随。我们不仅仅是在解决一个人的问题。。。我们正在为未来数不清的搜索提供有意义的指南。。。让你的工作尽可能长。无论如何,开发人员应该可以自由地试验和使用那些似乎适合表达他们观点的工具。你的方法是可能的,但对于这个简单的查询来说太复杂了。关于PL/SQL没有异议,在我自己的回答中,我也使用PL/SQL。我所有的建议都是关于PL/SQL代码的风格。我的建议:一,。关于显式游标,请查看。2.如果只存在一条记录,则不需要循环。3.不需要任何连接,查询中的堆栈\配置文件\合并\源是什么?4.不需要在中输入个人资料\姓名和姓氏来更新。。。记录锁定可能有用,但选择取决于应用程序逻辑。我们现在没有足够的信息来做出这样的决定。我只是假设将用户状态更新到所需状态并不重要。即使在您的变体中,如果未找到所需的配置文件,在插入重复主键的情况下也可能引发本机Oracle错误。所以从我的观点来看,这个解决方案过于复杂了。
--------
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;