Как я могу узнать причину этого уникального нарушения ограничения? - PullRequest
0 голосов
/ 03 июня 2019

Итак, у меня есть две таблицы sql, tableCurrent и tableHistory.Оба имеют одинаковые столбцы: id, lst_updt_ts (последняя обновленная отметка времени) и данные.Отличие состоит в том, что tableCurrent является его ключом в качестве идентификатора, в то время как tableHistory имеет идентификатор (id, lst_updt_ts)

Обычно случается так, что всякий раз, когда строка обновляется в tableCurrent, перед этим она помещает копию этогострока в таблице истории.Тем не менее, я заметил, что кто-то при копировании данных в tableHistory не копировал предыдущий lst_updt_ts и вместо этого использовал SYS_TIMESTAMP.

Это вызывает проблему с системой вниз по течению, поскольку в tableHistory есть записи из отметки времени, которая большетогда что в таблице текущего?

Я предложил решение для поддержания порядка с этим вызовом sql.По сути, он принимает значения, у которых lst_updt_ts больше в tableHistory, чем tableCurrent, и устанавливает lst_updt_ts равным 1 миллисекунде перед соответствующей записью в tableCurrent.

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,обнаружено, что в нем есть строка, у которой lst_updt_ts больше, чем ее запись в tableCurrent.

Так почему же возникает уникальное нарушение ограничения?Не этот запуск базы данных Oracle.

Ответы [ 2 ]

0 голосов
/ 03 июня 2019

needoriginalname, из-за ошибки вы генерируете существующее значение ключа, поэтому я посмотрю на предложение Алекса Пула. Также в обоих случаях идентифицируется ли PK? Если нет, вам нужно быть более избирательным при сопоставлении истории с текущей.

0 голосов
/ 03 июня 2019

Я думаю, что проблема, вероятно, заключается в следующем:

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;

дб <> скрипка

Конечно, при любом подходе также возможно, что у вас есть две записи истории для идентификатора, которые находятся на расстоянии ровно миллисекунды, где более новая все еще опережает текущую запись; в этом случае вы все равно получите нарушение ограничения. Кажется менее вероятным, что вы воспользуетесь этим сценарием, поскольку связанные с этим пробелы намного меньше.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...