因此,我有两个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数据库。
答案 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;
当然,无论采用哪种方法,您都可以为ID设置两条历史记录,它们之间的间隔正好是毫秒,其中较新的记录仍在当前记录的前面;在这种情况下,您仍然会遇到约束冲突。不过,由于涉及的差距很小,因此您遇到这种情况的可能性似乎较小。
答案 1 :(得分:0)
needoriginalname,从错误中您正在生成现有的键值,因此我将看一下Alex Poole的建议。两种情况下的PK是否也都标识为单个列?如果不是这样,则需要在历史记录与当前记录的匹配中更具选择性。