Как я могу временно сократить ссылочную целостность, в рамках транзакции, не отключая ограничение внешнего ключа?
У меня есть таблица с тремя столбцами:
ID, PARENT_ID, NAME
PARENT_ID
имеет отношение внешнего ключа с ID
в той же таблице. Эта таблица моделирует иерархию.
Иногда меняется ID
записи. Я хочу иметь возможность обновить запись ID
, а затем обновить зависимые записи PARENT_ID
, чтобы указать на новый ID
.
Проблема заключается в том, что когда я пытаюсь обновить ID
записи, она нарушает целостность и немедленно сбой.
Я понимаю, что могу вставить новую запись с новым ID
, а затем обновить дочерние элементы, а затем удалить старую запись, но у нас есть много триггеров, которые могут быть испорчены, если я это сделаю.
Есть ли способ временно обновить родителя с обещанием обновить дочерние элементы (очевидно, что это не сработает при фиксации) без кратковременного отключения внешнего ключа?
Ответы
Ответ 1
То, что вы хотите, это " отложенное ограничение ".
Вы можете выбрать между двумя типами отложенных ограничений, "INITIALLY IMMEDIATE" и "INITIALLY DEFERRED", чтобы управлять поведением по умолчанию - должна ли база данных по умолчанию проверять ограничение после каждого оператора, или если по умолчанию должна использоваться проверка только ограничений в конце сделки.
Ответ 2
Отвечало медленнее, чем Чи, но считал, что было бы неплохо включить образец кода, чтобы ответ можно найти в SO.
Как ответил Чи, отложенные ограничения делают это возможным.
SQL> drop table t;
Table dropped.
SQL> create table T (ID number
2 , parent_ID number null
3 , name varchar2(40) not null
4 , constraint T_PK primary key (ID)
5 , constraint T_HIREARCHY_FK foreign key (parent_ID)
6 references T(ID) deferrable initially immediate);
Table created.
SQL> insert into T values (1, null, 'Big Boss');
1 row created.
SQL> insert into T values (2, 1, 'Worker Bee');
1 row created.
SQL> commit;
Commit complete.
SQL> -- Since initially immediate, the following statement will fail:
SQL> update T
2 set ID = 1000
3 where ID = 1;
update T
*
ERROR at line 1:
ORA-02292: integrity constraint (S.T_HIREARCHY_FK) violated - child record found
SQL> set constraints all deferred;
Constraint set.
SQL> update T
2 set ID = 1000
3 where ID = 1;
1 row updated.
SQL> update T
2 set parent_ID = 1000
3 where parent_ID = 1;
1 row updated.
SQL> commit;
Commit complete.
SQL> select * from T;
ID PARENT_ID NAME
---------- ---------- ----------------------------------------
1000 Big Boss
2 1000 Worker Bee
SQL> -- set constraints all deferred during that transaction
SQL> -- and the transaction has commited, the next
SQL> -- statement will fail
SQL> update T
2 set ID = 1
3 where ID = 1000;
update T
*
ERROR at line 1:
ORA-02292: integrity constraint S.T_HIREARCHY_FK) violated - child record found
Я верю, но не смог найти ссылку, что отсрочка определяется во время создания ограничения и не может быть изменена позже. Значение по умолчанию не откладывается. Чтобы перейти к отложенным ограничениям, вам нужно сделать однократное падение и добавить ограничение. (Правильно запланированное, контролируемое и т.д.)
SQL> drop table t;
Table dropped.
SQL> create table T (ID number
2 , parent_ID number null
3 , name varchar2(40) not null
4 , constraint T_PK primary key (ID)
5 , constraint T_HIREARCHY_FK foreign key (parent_ID)
6 references T(ID));
Table created.
SQL> alter table T drop constraint T_HIREARCHY_FK;
Table altered.
SQL> alter table T add constraint T_HIREARCHY_FK foreign key (parent_ID)
2 references T(ID) deferrable initially deferred;
Table altered.
Ответ 3
Общий совет со сценариями, подобными этому, заключается в использовании отложенных ограничений. Тем не менее, я думаю, что эти ситуации почти всегда являются провалом логики приложения или модели данных. Например, вставка дочерней записи и родительской записи в одной транзакции может быть проблемой, если мы выполним ее как два оператора:
Мои тестовые данные:
SQL> select * from t23 order by id, parent_id
2 /
ID PARENT_ID NAME
---------- ---------- ------------------------------
110 parent 1
111 parent 2
210 110 child 0
220 111 child 1
221 111 child 2
222 111 child 3
6 rows selected.
SQL>
Неправильный способ сделать что-то:
SQL> insert into t23 (id, parent_id, name) values (444, 333, 'new child')
2 /
insert into t23 (id, parent_id, name) values (444, 333, 'new child')
*
ERROR at line 1:
ORA-02291: integrity constraint (APC.T23_T23_FK) violated - parent key not
found
SQL> insert into t23 (id, parent_id, name) values (333, null, 'new parent')
2 /
1 row created.
SQL>
Однако Oracle поддерживает многозадачный INSERT synatx, который позволяет нам вставлять родительские и дочерние записи в один и тот же оператор, тем самым устраняя необходимость в отложенных ограничениях:
SQL> rollback
2 /
Rollback complete.
SQL> insert all
2 into t23 (id, parent_id, name)
3 values (child_id, parent_id, child_name)
4 into t23 (id, name)
5 values (parent_id, parent_name)
6 select 333 as parent_id
7 , 'new parent' as parent_name
8 , 444 as child_id
9 , 'new child' as child_name
10 from dual
11 /
2 rows created.
SQL>
Ситуация, в которой вы находитесь, похожа: вы хотите обновить первичный ключ родительской записи, но не можете из-за существования дочерних записей: и вы не можете обновлять дочерние записи, потому что нет родительского ключа, Catch-22:
SQL> update t23
2 set id = 555
3 where id = 111
4 /
update t23
*
ERROR at line 1:
ORA-02292: integrity constraint (APC.T23_T23_FK) violated - child record found
SQL> update t23
2 set parent_id = 555
3 where parent_id = 111
4 /
update t23
*
ERROR at line 1:
ORA-02291: integrity constraint (APC.T23_T23_FK) violated - parent key not
found
SQL>
Вновь решение должно сделать это в одном выражении:
SQL> update t23
2 set id = decode(id, 111, 555, id)
3 , parent_id = decode(parent_id, 111, 555, parent_id)
4 where id = 111
5 or parent_id = 111
6 /
4 rows updated.
SQL> select * from t23 order by id, parent_id
2 /
ID PARENT_ID NAME
---------- ---------- ------------------------------
110 parent 1
210 110 child 0
220 555 child 1
221 555 child 2
222 555 child 3
333 new parent
444 333 new child
555 parent 2
8 rows selected.
SQL>
Синтаксис в инструкции UPDATE немного неуклюжие, но обычно kludges. Дело в том, что нам не нужно часто обновлять столбцы первичного ключа. Действительно, поскольку неизменность является одной из характеристик "первичного ключа", нам не нужно действительно обновлять их вообще. Необходимость этого - отказ модели данных. Один из способов избежать таких сбоев - использовать синтетический (суррогатный) первичный ключ и просто обеспечить уникальность естественного ключа (aka business) с уникальным ограничением.
Итак, почему Oracle предлагает отложенные ограничения? Они полезны, когда мы проводим миграцию данных или загружаем массовые данные. Они позволяют нам очищать данные в базе данных без промежуточных таблиц. Нам действительно не нужны они для обычных задач приложения.
Ответ 4
Рекомендации по использованию суррогатного ключа превосходны, ИМО.
В целом проблема с этой таблицей заключается в том, что ей не хватает первичного ключа. Напомним, что первичным ключом должно быть три вещи:
- Уникальный
- ненулевыми
- неизменная
Базы данных Я знаком с соблюдением (1) и (2), но я не верю, что они соблюдают (3), что является неудачным. И то, что пинает вас в прикладе - если вы меняете свой "первичный ключ", вам нужно преследовать все ссылки на это ключевое поле и делать эквивалентные изменения, если вы не хотите нарушать целостность. Решение, как говорили другие, состоит в том, чтобы иметь истинный первичный ключ - тот, который является уникальным, не равным нулю, и который не изменяется.
Есть причины для всех этих маленьких правил. Это отличная возможность понять "неизменную" часть правил первичных ключей.
Поделитесь и наслаждайтесь.
Ответ 5
Вам нужно использовать отложенное ограничение (см. ответ Chi).
В противном случае, чтобы добавить значение, которое приведет к сбою ограничения внешнего ключа, вам необходимо либо отключить, либо сбросить и заново создать ограничение внешнего ключа.
Ситуации, подобные этим, используют суррогатный ключ, который может быть изменен пользователями по мере необходимости, не влияя на ссылочную целостность. Чтобы расширить эту идею, в настоящее время настройка:
- ID (pk)
- PARENT_ID (внешний ключ, столбец идентификаторов ссылок - делает его самореляционным)
.. и бизнес-правила - это идентификатор, который может измениться. Что принципиально плохо с точки зрения дизайна - первичный ключ неизменен, уникален и не может быть нулевым. Таким образом, решение ситуации, когда вы строите свою модель данных, заключается в использовании:
- ID (pk)
- PARENT_ID (внешний ключ, столбец идентификаторов ссылок - делает его самореляционным)
- SURROGATE_KEY (уникальное ограничение)
SURROGATE_KEY - это столбец, который поддерживает изменения, не затрагивая ссылочную целостность - родительское и дочернее отношения не повреждены. Это означает, что пользователь может настраивать суррогатный ключ в их сердцах без необходимости отложенных ограничений, включать/отключать или отбрасывать/воссоздавать ограничения внешнего ключа, Вкл. ОБНОВЛЕНИЕ CASCADE...
Как правило, при моделировании данных вы НИКОГДА отображаете значения первичного ключа для пользователя из-за подобных ситуаций. Например, у меня есть клиент, который хочет, чтобы их номер задания изменился в начале года, с годом в начале номера (IE: 201000001 будет первым заданием, созданным в 2010 году). Что происходит, когда клиент продает компанию, а новому владельцу нужна другая схема для их учета? Или, что, если нумерация не может быть сохранена при переходе к другому поставщику базы данных?
Ответ 6
Если бы это была другая база данных, кроме Oracle, вы могли бы объявить внешний ключ с помощью ON UPDATE CASCADE
. Затем, если вы измените родительский идентификатор, он будет распространять изменение атомарно на родительский родитель.
К сожалению, Oracle реализует каскадные удаления, но не каскадные обновления.
(Этот ответ предназначен только для информационных целей, поскольку он фактически не решает вашу проблему.)