这个 MERGE / INSERT 查询我做错了什么?
What am I doing wrong with this MERGE / INSERT query?
我有一个数据库 table,我想用 SQL 更新它。基本上,它带有一组时间table小册子部分的描述信息,但这并不重要。一些数据已经通过应用程序输入,但它很耗时,而且其中的块 "boiler plate" 需要每周更新。在某些情况下,我可能错过了通过应用程序输入数据,所以如果数据不存在,我也想自动创建。
这导致我使用 MERGE 查询,如下所示:
MERGE INTO TTP_LINE_DESCRIPTION o
USING
(SELECT DISTINCT lv.lv_nv_id TLDE_NV_ID,
lv.lv_id TLDE_LV_ID,
dir.dir_id TLDE_DIR_ID,
8 TLDE_MED_FLAG,
1 TLDE_TYPE,
0 TLDE_SORT_NO,
'Timetable valid from ' || to_char(lv.lv_valid_from,'DD/MM/YYYY') || ' until ' || nvl2(lv.lv_valid_until,to_char(lv.lv_valid_until,'DD/MM/YYYY'),'further notice') TLDE_TEXT,
0 TLDE_ALIGNMENT,
null TLDE_FONT_SIZE,
null TLDE_FONT_STYLE
FROM LINE_VERSION lv
JOIN line_point_sequence lps ON (lv.lv_id = lps.lps_lv_id)
JOIN direction dir ON (dir.dir_id = lps.lps_dir_id)
where lv.lv_nv_id=3799 and lv.lv_id=10455244) n
ON (o.TLDE_NV_ID=n.TLDE_NV_ID
and o.TLDE_LV_ID=n.TLDE_LV_ID
and o.TLDE_DIR_ID=n.TLDE_DIR_ID
and o.TLDE_TYPE=n.TLDE_TYPE
and o.TLDE_SORT_NO=n.TLDE_SORT_NO)
WHEN MATCHED THEN
UPDATE SET o.TLDE_TEXT=n.TLDE_TEXT,
o.TLDE_ALIGNMENT=n.TLDE_ALIGNMENT,
o.TLDE_FONT_SIZE=n.TLDE_FONT_SIZE,
o.TLDE_FONT_STYLE=n.TLDE_FONT_STYLE
WHEN NOT MATCHED THEN
INSERT (o.tlde_id, o.tlde_nv_id, o.tlde_lv_id, o.tlde_dir_id,
o.tlde_med_flag, o.tlde_type, o.tlde_sort_no, o.tlde_text,
o.tlde_alignment, o.tlde_font_size, o.tlde_font_style,
o.updated_by, o.updated_on, o.updated_prog)
VALUES ((select max(tld.tlde_id)+1 from TTP_LINE_DESCRIPTION tld),
n.tlde_nv_id, n.tlde_lv_id, n.tlde_dir_id, n.tlde_med_flag,
n.tlde_type, n.tlde_sort_no, n.tlde_text, n.tlde_alignment,
n.tlde_font_size, n.tlde_font_style, 'STUARTR',
SYSDATE, 'PL/SQL Developer');
这里有一点需要注意,就是SELECT DISTINCT中的WHERE子句。 lv.lv_id=10455244
是一个参考,我知道它会强制 select 到 return 只有一对 SELECT 中的行,这样我就可以限制我的测试。为此,10455244 是当前不在 TTP_LINE_DESCRIPTION table.
中的有效值
当我使用 table 中的值时,WHEN MATCHED 代码正确执行,并在 0.016 秒内更新一对行。
运行 SELECT 语句本身使用我上面显示的值 returns 需要在 0.109s 中添加的两行。
根据最后 VALUES 行中的第一项获取最大 ID 并向其添加一个(这是主键)需要 0s。
最后,如果我编写一个 INSERT INTO 并显式写入我想为其中一行写入的所有值,我可以在 0.016 秒内执行一行的 INSERT。
但是把它们放在一起......什么都没有。处决只是坐在那里,执行着,似乎并没有结束。或者我会紧张地等着看它是否会结束。我给它留了一个合理的时间,似乎什么也没有进去。
这是怎么回事,为什么它没有按照我认为应该的方式运行?
您正在这样做:
create table t (id, nv_id, val) as (select 1, 101, 'A' from dual);
merge into t o
using (
select 102 nv_id, 'P' val from dual union all
select 103 nv_id, 'Q' val from dual ) n
on (o.nv_id = n.nv_id)
when matched then update set val = n.val
when not matched then
insert (o.id, o.nv_id, o.val)
values ((select max(id) + 1 from t), n.nv_id, n.val);
在我的例子中它起作用了,但是插入行的 id
是相同的:2
。当我回滚并添加主键约束时:
alter table t add constraint t_pk primary key(id);
merge
导致错误 - 违反了唯一约束。我怀疑这与您的 tlde_id
有关,也许不是约束而是其他原因。以这种方式产生价值是一个很大的禁忌。如果您的 Oracle 版本是 12c 或更高,您可以将此列更改为自动生成
generated by default on null as identity
或在旧版本中使用序列和触发器(找到最大值 id
,加 1 并将其设置为序列的起始值)
create sequence seq_t_id start with 2;
create or replace trigger t_on_insert
before insert on t for each row
begin
select seq_t_id.nextval into :new.id from dual;
end;
然后修改您的 merge
,通过从插入子句中删除 id
或仅插入 null
,触发器将设置正确的值:
merge into t o
using (
select 102 nv_id, 'P' val from dual union all
select 103 nv_id, 'Q' val from dual ) n
on (o.nv_id = n.nv_id)
when matched then update set val = n.val
when not matched then
insert (o.nv_id, o.val)
values (n.nv_id, n.val);
即使这不能解决您的问题,您也不应将 id
生成为 max() + 1
,这会导致并发会话、提交、多插入等问题。
我有一个数据库 table,我想用 SQL 更新它。基本上,它带有一组时间table小册子部分的描述信息,但这并不重要。一些数据已经通过应用程序输入,但它很耗时,而且其中的块 "boiler plate" 需要每周更新。在某些情况下,我可能错过了通过应用程序输入数据,所以如果数据不存在,我也想自动创建。
这导致我使用 MERGE 查询,如下所示:
MERGE INTO TTP_LINE_DESCRIPTION o
USING
(SELECT DISTINCT lv.lv_nv_id TLDE_NV_ID,
lv.lv_id TLDE_LV_ID,
dir.dir_id TLDE_DIR_ID,
8 TLDE_MED_FLAG,
1 TLDE_TYPE,
0 TLDE_SORT_NO,
'Timetable valid from ' || to_char(lv.lv_valid_from,'DD/MM/YYYY') || ' until ' || nvl2(lv.lv_valid_until,to_char(lv.lv_valid_until,'DD/MM/YYYY'),'further notice') TLDE_TEXT,
0 TLDE_ALIGNMENT,
null TLDE_FONT_SIZE,
null TLDE_FONT_STYLE
FROM LINE_VERSION lv
JOIN line_point_sequence lps ON (lv.lv_id = lps.lps_lv_id)
JOIN direction dir ON (dir.dir_id = lps.lps_dir_id)
where lv.lv_nv_id=3799 and lv.lv_id=10455244) n
ON (o.TLDE_NV_ID=n.TLDE_NV_ID
and o.TLDE_LV_ID=n.TLDE_LV_ID
and o.TLDE_DIR_ID=n.TLDE_DIR_ID
and o.TLDE_TYPE=n.TLDE_TYPE
and o.TLDE_SORT_NO=n.TLDE_SORT_NO)
WHEN MATCHED THEN
UPDATE SET o.TLDE_TEXT=n.TLDE_TEXT,
o.TLDE_ALIGNMENT=n.TLDE_ALIGNMENT,
o.TLDE_FONT_SIZE=n.TLDE_FONT_SIZE,
o.TLDE_FONT_STYLE=n.TLDE_FONT_STYLE
WHEN NOT MATCHED THEN
INSERT (o.tlde_id, o.tlde_nv_id, o.tlde_lv_id, o.tlde_dir_id,
o.tlde_med_flag, o.tlde_type, o.tlde_sort_no, o.tlde_text,
o.tlde_alignment, o.tlde_font_size, o.tlde_font_style,
o.updated_by, o.updated_on, o.updated_prog)
VALUES ((select max(tld.tlde_id)+1 from TTP_LINE_DESCRIPTION tld),
n.tlde_nv_id, n.tlde_lv_id, n.tlde_dir_id, n.tlde_med_flag,
n.tlde_type, n.tlde_sort_no, n.tlde_text, n.tlde_alignment,
n.tlde_font_size, n.tlde_font_style, 'STUARTR',
SYSDATE, 'PL/SQL Developer');
这里有一点需要注意,就是SELECT DISTINCT中的WHERE子句。 lv.lv_id=10455244
是一个参考,我知道它会强制 select 到 return 只有一对 SELECT 中的行,这样我就可以限制我的测试。为此,10455244 是当前不在 TTP_LINE_DESCRIPTION table.
当我使用 table 中的值时,WHEN MATCHED 代码正确执行,并在 0.016 秒内更新一对行。
运行 SELECT 语句本身使用我上面显示的值 returns 需要在 0.109s 中添加的两行。
根据最后 VALUES 行中的第一项获取最大 ID 并向其添加一个(这是主键)需要 0s。
最后,如果我编写一个 INSERT INTO 并显式写入我想为其中一行写入的所有值,我可以在 0.016 秒内执行一行的 INSERT。
但是把它们放在一起......什么都没有。处决只是坐在那里,执行着,似乎并没有结束。或者我会紧张地等着看它是否会结束。我给它留了一个合理的时间,似乎什么也没有进去。
这是怎么回事,为什么它没有按照我认为应该的方式运行?
您正在这样做:
create table t (id, nv_id, val) as (select 1, 101, 'A' from dual);
merge into t o
using (
select 102 nv_id, 'P' val from dual union all
select 103 nv_id, 'Q' val from dual ) n
on (o.nv_id = n.nv_id)
when matched then update set val = n.val
when not matched then
insert (o.id, o.nv_id, o.val)
values ((select max(id) + 1 from t), n.nv_id, n.val);
在我的例子中它起作用了,但是插入行的 id
是相同的:2
。当我回滚并添加主键约束时:
alter table t add constraint t_pk primary key(id);
merge
导致错误 - 违反了唯一约束。我怀疑这与您的 tlde_id
有关,也许不是约束而是其他原因。以这种方式产生价值是一个很大的禁忌。如果您的 Oracle 版本是 12c 或更高,您可以将此列更改为自动生成
generated by default on null as identity
或在旧版本中使用序列和触发器(找到最大值 id
,加 1 并将其设置为序列的起始值)
create sequence seq_t_id start with 2;
create or replace trigger t_on_insert
before insert on t for each row
begin
select seq_t_id.nextval into :new.id from dual;
end;
然后修改您的 merge
,通过从插入子句中删除 id
或仅插入 null
,触发器将设置正确的值:
merge into t o
using (
select 102 nv_id, 'P' val from dual union all
select 103 nv_id, 'Q' val from dual ) n
on (o.nv_id = n.nv_id)
when matched then update set val = n.val
when not matched then
insert (o.nv_id, o.val)
values (n.nv_id, n.val);
即使这不能解决您的问题,您也不应将 id
生成为 max() + 1
,这会导致并发会话、提交、多插入等问题。