Как я могу на короткое время нарушить ссылочную целостность внутри транзакции, не отключая ограничение внешнего ключа? - PullRequest
17 голосов
/ 24 июня 2010

У меня есть таблица с 3 столбцами:

ID, PARENT_ID, NAME

PARENT_ID имеет отношение внешнего ключа с ID в той же таблице. Эта таблица моделирует иерархию.

Иногда ID записи будет меняться. Я хочу иметь возможность обновить ID записи, а затем обновить зависимые записи 'PARENT_ID, чтобы они указывали на новый ID.

Проблема в том, что когда я пытаюсь обновить ID записи, она нарушает целостность и немедленно завершается ошибкой.

Я понимаю, что мог бы вставить новую запись с новым ID, затем обновить дочерние элементы, затем удалить старую запись, но у нас есть много триггеров, которые могут испортиться, если я сделаю это.

Есть ли способ временно обновить родительский элемент с обещанием обновить дочерние элементы (очевидно, он не сможет выполнить коммит) без кратковременного отключения внешнего ключа?

Ответы [ 6 ]

19 голосов
/ 24 июня 2010

То, что вы хотите, это ' отложенное ограничение '.

Вы можете выбрать между двумя типами отложенных ограничений, «INITIALLY IMMEDIATE» и «INITIALLY DEFERRED», чтобы управлять поведением по умолчанию -должна ли база данных по умолчанию проверять ограничение после каждого оператора или должна по умолчанию проверять только ограничения в конце транзакции.

10 голосов
/ 24 июня 2010

Ответил медленнее, чем Chi, но чувствовал, что было бы неплохо включить пример кода, чтобы найти ответ на 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.
7 голосов
/ 24 июня 2010

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

Мои тестовые данные:

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 немного неуклюж, но, как правило, кладжи.Дело в том, что нам не нужно обновлять столбцы первичного ключа очень часто.Действительно, поскольку неизменность является одной из характеристик «первичной ключности», нам вообще не нужно их обновлять.Это необходимо для сбоя модели данных.Один из способов избежать таких сбоев - использовать синтетический (суррогатный) первичный ключ и просто обеспечить уникальность естественного (бизнес-) ключа с помощью уникального ограничения.

Так почему же Oracle предлагает отложенные ограничения?Они полезны, когда мы проводим миграцию данных или массовую загрузку данных.Они позволяют нам очищать данные в базе данных без промежуточных таблиц.Нам действительно не нужны они для обычных прикладных задач.

3 голосов
/ 24 июня 2010

Рекомендации по использованию суррогатного ключа превосходны, ИМО.

В целом, проблема с этой таблицей состоит в том, что в ней отсутствует первичный ключ.Напомним, что первичный ключ должен состоять из трех вещей:

  1. Уникальный
  2. Не нулевой
  3. Неизменяемые

Базы данных, с которыми я знакомс принудительным исполнением (1) и (2), но я не верю, что они приводят в исполнение (3), что вызывает сожаление.И это то, что пинает вас в задницу - если вы измените свой «первичный ключ», вам придется выследить все ссылки на это ключевое поле и сделать эквивалентные изменения, если вы не хотите нарушать целостность.Решение, как говорили другие, состоит в том, чтобы иметь истинный первичный ключ - уникальный, ненулевой и не изменяющийся.

Есть причины для всех этих маленьких правил.Это прекрасная возможность понять «неизменную» часть правил первичного ключа.

Делитесь и наслаждайтесь.

1 голос
/ 24 июня 2010

Если бы это была любая другая база данных, кроме Oracle, вы могли бы объявить внешний ключ с помощью ON UPDATE CASCADE.Затем, если вы измените идентификатор родителя, он будет атомарно распространять это изменение на parent_id ребенка.

К сожалению, Oracle реализует каскадные удаления, но не каскадные обновления.

(Этот ответ только для информационных целей, поскольку он фактически не решает вашу проблему.)

1 голос
/ 24 июня 2010

Вам нужно использовать отложенное ограничение (см. Ответ Чи).
В противном случае, чтобы добавить значение, которое не пройдет ограничение внешнего ключа, необходимо либо отключить, либо удалить и заново создать ограничение внешнего ключа.

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

  • ID (pk)
  • PARENT_ID (внешний ключ, столбец идентификатора ссылок - делает его самоссылочным)

.. и бизнес-правила таковы, что ID может измениться. Что в принципе плохо с точки зрения дизайна - первичные ключи неизменны, уникальны и не могут быть нулевыми. Таким образом, решение ситуации, когда вы строите свою модель данных, заключается в использовании:

  • ID (pk)
  • PARENT_ID (внешний ключ, столбец идентификатора ссылок - делает его самоссылочным)
  • SURROGATE_KEY (уникальное ограничение)

SURROGATE_KEY - это столбец, который поддерживает изменение, не затрагивая ссылочную целостность - отношения между родителями и дочерними элементами не повреждены. Это означает, что пользователь может настроить суррогатный ключ для удовольствия своего сердца, не нуждаясь в отложенных ограничениях, включить / отключить или удалить / восстановить ограничения внешнего ключа, ON UPDATE CASCADE ...

Как правило, при моделировании данных НИКОГДА не отображают значения первичного ключа для пользователя из-за подобных ситуаций. Например, у меня есть клиент, который хочет, чтобы номер его работы изменился в начале года, а год - в начале номера (IE: 201000001 будет первым заданием, созданным в 2010 году). Что происходит, когда клиент продает компанию, а новому владельцу нужна другая схема для их учета? Или что делать, если нумерация не может быть сохранена при переходе к другому поставщику базы данных?

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