如何找出这种独特的约束违反原因?

时间:2019-06-03 16:46:42

标签: sql oracle plsql

因此,我有两个sql表,tableCurrent和tableHistory。 两者具有相同的列:id,lst_updt_ts(最后更新的时间戳)和数据。 区别在于tableCurrent是其键的id,而tableHistory的id是(id,lst_updt_ts)

通常,发生的事情是,每当在tableCurrent中更新一行时,在此之前,它都会将该行的副本放入tableHistory中。但是,我注意到有人在将数据复制到tableHistory时没有复制以前的lst_updt_ts,而是使用SYS_TIMESTAMP。

这会导致下游系统出现问题,因为tableHistory从时间戳中获得的条目大于tableCurrent中的条目。

我通过此sql调用提出了一种维护订单的解决方案。 本质上,它会在tableHistory中获取lst_updt_ts值大于tableCurrent的值,并将lst_updt_ts设置为在tableCurrent中与其关联的条目之前的1毫秒。

Update dbuser.tableHistory lh
set lh.lst_updt_ts = (
-- set to change the history table entry to 1 millisecond behind current table's timestamp
select (h.lst_updt_ts)-(1/86400000) --removes 1 milisecond.
from dbuser.tableCurrent h
where h.id = lh.id
)
WHERE (lh.id, lh.lst_updt_ts) in 
--grab all whose history table's lst_updt_ts is greater then current table
(
Select larh.id, larh.lst_updt_ts
FROM dbuser.tableHistory larh, dbuser.tableCurrent lar
where larh.id = lar.id
and larh.lst_updt_ts >= lar.lst_updt_ts
);

但是,在运行它时。它抱怨唯一约束违反。现在,lst_updt_ts是主键之一,在检查之后,在检查之前和之后应该不可能有冲突的lst_updt_ts。

要注意的一件事是,发现tableHistory中每个id的一个条目只有一行,其lst_updt_ts大于它在tableCurrent中的条目。

那为什么会出现一个独特的约束违规?不是这样运行Oracle数据库。

2 个答案:

答案 0 :(得分:0)

我认为问题可能是这样的:

select (h.lst_updt_ts)-(1/86400000) --removes 1 milisecond.

该注释不太正确,因为执行类似的算法会导致时间戳值隐式转换为日期。 (在文档中详细了解datetime/interval arithmetic)。 update set然后将结果隐式转换回时间戳,但两者之间的差异可能会超过一毫秒:

with t (ts) as (
  select timestamp '2019-06-01 12:34:56.788' from dual
)
select ts, ts - 1/8640000, cast(ts - 1/8640000 as timestamp)
from t;

TS                      TS-1/8640000        CAST(TS-1/8640000ASTIME
----------------------- ------------------- -----------------------
2019-06-01 12:34:56.788 2019-06-01 12:34:56 2019-06-01 12:34:56.000

ts - 1/8640000是一个简单的日期,您可以看出它没有显示任何小数秒。放回时间戳记可使小数秒为零。

因此,如果您有一个ID,而其现有历史记录恰好在调整后的时间,即使该时间不是该ID的最新历史记录时间,也早于该ID的当前时间,那么您将最终发生冲突。通过该场景的非常基本的设置:

create table tablecurrent (
  id number,
  lst_updt_ts timestamp,
  constraint tab_curr_uniq unique (id)
);

insert into tablecurrent
select 1, timestamp '2019-06-01 12:34:56.789' from dual;

create table tablehistory (id number,
  lst_updt_ts timestamp,
  constraint tab_hist_uniq unique (id, lst_updt_ts)
);
insert into tablehistory
select 1, timestamp '2019-06-01 12:45:00.000' from dual
union all
select 1, timestamp '2019-06-01 12:34:56.000' from dual;

然后您的查询为ORA-01001,因为将12:45历史记录更新为12:34:56与较旧的行发生冲突。

如果您使用间隔来调整时间:

select h.lst_updt_ts - interval '0.001' second --removes 1 milisecond.

...然后将其保留为时间戳:

Update tableHistory lh
set lh.lst_updt_ts = (
-- set to change the history table entry to 1 millisecond behind current table's timestamp
select h.lst_updt_ts - interval '0.001' second --removes 1 milisecond.
from tableCurrent h
where h.id = lh.id
)
WHERE (lh.id, lh.lst_updt_ts) in 
--grab all whose history table's lst_updt_ts is greater then current table
(
Select larh.id, larh.lst_updt_ts
FROM tableHistory larh, tableCurrent lar
where larh.id = lar.id
and larh.lst_updt_ts >= lar.lst_updt_ts
);

1 row updated.

select * from tablehistory;

        ID LST_UPDT_TS            
---------- -----------------------
         1 2019-06-01 12:34:56.788
         1 2019-06-01 12:34:56.000

您也可以将其合并:

merge into tablehistory th
using (
  select id, lst_updt_ts
  from tablecurrent
) tc
on (tc.id = th.id)
when matched then
update set th.lst_updt_ts = tc.lst_updt_ts - interval '0.001' second
where th.lst_updt_ts > tc.lst_updt_ts;

db<>fiddle

当然,无论采用哪种方法,您都可以为ID设置两条历史记录,它们之间的间隔正好是毫秒,其中较新的记录仍在当前记录的前面;在这种情况下,您仍然会遇到约束冲突。不过,由于涉及的差距很小,因此您遇到这种情况的可能性似乎较小。

答案 1 :(得分:0)

needoriginalname,从错误中您正在生成现有的键值,因此我将看一下Alex Poole的建议。两种情况下的PK是否也都标识为单个列?如果不是这样,则需要在历史记录与当前记录的匹配中更具选择性。