这个 MERGE / INSERT 查询我做错了啥?

Posted

技术标签:

【中文标题】这个 MERGE / INSERT 查询我做错了啥?【英文标题】:What am I doing wrong with this MERGE / INSERT query?这个 MERGE / INSERT 查询我做错了什么? 【发布时间】:2019-01-22 22:38:06 【问题描述】:

我有一个要使用 SQL 更新的数据库表。基本上,它带有一组时间表小册子部分的描述信息,但这并不重要。一些数据已经通过应用程序输入,但它很耗时,而且其中的一部分是“样板”,需要每周更新。在某些情况下,我可能错过了通过应用程序输入数据,因此如果数据不存在,我也想自动创建数据。

这导致我使用 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 中的一对行,这样我就可以限制我的测试。为此,10455244 是当前不在 TTP_LINE_DESCRIPTION 表中的有效值。

当我使用表中的值时,WHEN MATCHED 代码正确执行,并在 0.016 秒内更新一对行。

使用我上面显示的值自行运行 SELECT 语句会返回需要添加 0.109 秒的两行。

根据最后 VALUES 行中的第一项获取最大 id 并向其添加一个(这是主键)需要 0s。

最后,如果我写一个 INSERT INTO 并显式写下我想为其中一行写的所有值,我可以在 0.016 秒内完成一行的 INSERT。

但是把它们放在一起,然后……什么都没有。执行只是坐在那里,执行,并且似乎没有结束。或者我很紧张,等着看它是否会结束。我已经离开了一段合理的时间,但似乎没有任何东西进入。

那么发生了什么事,为什么它不做我认为它应该做的事?

【问题讨论】:

您是否:检查计划,您是否计划/时间:选择 * TTP_LINE_DESCRIPTION o 内部连接 ​​(SELECT DISTINCT ... ) n ON (o.TLDE_NV_ID=n.TLDE_NV_ID 和 o.TLDE_LV_ID=n. TLDE_LV_ID 和 o.TLDE_DIR_ID=n.TLDE_DIR_ID 和 o.TLDE_TYPE=n.TLDE_TYPE 和 o.TLDE_SORT_NO=n.TLDE_SORT_NO) 不,但是现在这样做需要 0.016 秒才能返回 0 行(因为我正在检查 NOT MATCHED 条件),或者如果我删除了我知道不存在的 lv_id,我会返回0.905 秒内 1049 行,都不是特别慢。 另外,为什么这个问题被否决了?我对很多 SQL 还是陌生的,所以我可能在做一些愚蠢的事情,这个问题可以得到回答,我已经通过分解和测试不同的部分来证明我一直在尝试不同的方法来让我的代码工作。 【参考方案1】:

你正在这样做:

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

即使这不能解决您的问题,您也不应该将ids 生成为max() + 1,这会导致并发会话、提交、多插入等问题。

【讨论】:

好的,明白了——尤其是关于max() + 1 并发问题。不幸的是,数据库模式不是我可以处理的,所以我需要回去与提供者讨论这个问题。我会将问题标记为已回答,但非常感谢。

以上是关于这个 MERGE / INSERT 查询我做错了啥?的主要内容,如果未能解决你的问题,请参考以下文章

使用`setVariables`后中继生成无效查询-我做错了啥吗?

AngularJS POST 与 $resource 在查询字符串中发送,我做错了啥?

这个Lua代码我做错了啥

这个简单的模板类我做错了啥?

这个 AFNetworking JSON 帖子我做错了啥?

在这个非常基本的 UIScrollView 中我做错了啥