Я думаю, что проблема, вероятно, заключается в следующем:
select (h.lst_updt_ts)-(1/86400000) --removes 1 milisecond.
Комментарий не совсем корректен, поскольку выполнение такой арифметики приводит к неявному преобразованию значения метки времени в дату. (Подробнее о арифметике даты / времени в документации). Затем 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
- это простая дата, как вы можете сказать по ней, не показывая дробных секунд. Возвращение к временной метке оставляет дробные секунды равными нулю.
Таким образом, если у вас есть идентификатор с существующей исторической записью, который оказывается именно в это настроенное время, даже если это не самое последнее время записи истории для этого идентификатора и до текущего времени для этого идентификатора, то вы будете в конечном итоге столкновение. С очень простой настройкой этого сценария:
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;
дб <> скрипка
Конечно, при любом подходе также возможно, что у вас есть две записи истории для идентификатора, которые находятся на расстоянии ровно миллисекунды, где более новая все еще опережает текущую запись; в этом случае вы все равно получите нарушение ограничения. Кажется менее вероятным, что вы воспользуетесь этим сценарием, поскольку связанные с этим пробелы намного меньше.