MySQL - удалить строку с ограничением внешнего ключа, которая ссылается на себя

12

У меня есть таблица, в которой я храню все сообщения форума, опубликованные пользователями на моем сайте. Структура иерархии сообщений реализована с использованием модели Nested set .

Ниже приведена упрощенная структура таблицы:

  • Id (ПЕРВИЧНЫЙ КЛЮЧ)
  • Owner_Id (ИНОСТРАННЫЕ КЛЮЧЕВЫЕ ССЫЛКИ НА Id )
  • Parent_Id (ИНОСТРАННЫЕ КЛЮЧЕВЫЕ ССЫЛКИ НА Id )
  • Nleft
  • NRight
  • Nlevel

Теперь таблица выглядит примерно так:

+ ------- + ------------- + -------------- + ---------- + ----------- + ----------- +
| Id      | Owner_Id      | Parent_Id      | nleft      | nright      | nlevel      |
+ ------- + ------------- + -------------- + ---------- + ----------- + ----------- +
| 1       | 1             | NULL           | 1          | 8           | 1           |
| 2       | 1             | 1              | 2          | 5           | 2           |
| 3       | 1             | 2              | 3          | 4           | 3           |
| 4       | 1             | 1              | 6          | 7           | 2           |
+ ------- + ------------- + -------------- + ---------- + ----------- + ----------- +

Обратите внимание, что первая строка является корневым сообщением, и дерево этого сообщения может быть отображено как:

-- SELECT * FROM forumTbl WHERE Owner_Id = 1 ORDER BY nleft;

MESSAGE (Id = 1)
    MESSAGE (Id = 2)
        Message (Id = 3)
    Message (Id = 4)

Моя проблема возникает, когда я пытаюсь удалить все строки под одним и тем же Owner_Idв одном запросе. Пример:

DELETE FROM forumTbl WHERE Owner_Id = 1 ORDER BY nright;

Вышеприведенный запрос завершается с ошибкой:

Код ошибки: 1451. Невозможно удалить или обновить родительскую строку: сбой ограничения внешнего ключа ( forumTbl, CONSTRAINT Owner_Id_frgnFOREIGN KEY ( Owner_Id) REFERENCES forumTbl( Id) ON DELETE NO ACTION ON UPDATE NO ACTION)

Причина в том, что первая строка , которая является корневым узлом ( Id=1), также имеет то же значение в своем Owner_Idполе ( Owner_Id=1), и это вызывает сбой запроса из-за ограничения внешнего ключа.

Мой вопрос: как я могу предотвратить эту цикличность ограничения внешнего ключа и удалить строку, которая ссылается на себя? Есть ли способ сделать это без предварительного обновления Owner_Idкорневого ряда до NULL?

Я создал демо этого сценария: http://sqlfiddle.com/#!9/fd1b1

Спасибо.

Алон Эйтан
источник

Ответы:

9
  1. Помимо отключения внешних ключей, которое является опасным и может привести к несоответствиям, есть еще два варианта:

  2. Измените FOREIGN KEYограничения с помощью ON DELETE CASCADEопции. Я не проверял все случаи, но вам наверняка нужно это для (owner_id)внешнего ключа и, возможно, для другого.

    ALTER TABLE forum
        DROP FOREIGN KEY owner_id_frgn,
        DROP FOREIGN KEY parent_id_frgn ;
    ALTER TABLE forum
        ADD CONSTRAINT owner_id_frgn
            FOREIGN KEY (owner_id) 
            REFERENCES forum (id)
            ON DELETE CASCADE,
        ADD CONSTRAINT parent_id_frgn
            FOREIGN KEY (parent_id) 
            REFERENCES forum (id)
            ON DELETE CASCADE ;
    

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

    DELETE FROM forum
    WHERE id = 1 ;         -- deletes id=1 and all descendants
    
  3. Проблема, с которой вы столкнулись, на самом деле 2 проблемы. Во-первых, удаление из таблицы с внешним ссылочным ключом, который ссылается на себя, не является серьезной проблемой для MySQL, если нет строки, которая ссылается на себя. Если есть строка, как в вашем примере, варианты ограничены. Либо отключите внешние ключи, либо используйте CASCADEдействие. Но если таких строк нет, удаление становится меньшей проблемой.

    Итак, если мы решим хранить NULLвместо того же самого idв owner_id, то вы можете удалить, не отключая внешние ключи и без каскадов.

    Тогда вы наткнетесь на вторую проблему! Выполнение вашего запроса вызовет похожую ошибку:

    DELETE FROM forum 
    WHERE owner_id = 1 OR id = 1 ; 
    

    Ошибка (и), предупреждение (я):
    Невозможно удалить или обновить родительскую строку: ограничение внешнего ключа не выполняется (rextester.forum, CONSTRAINT owner_id_frgn FOREIGN KEY (owner_Id) ССЫЛКИ форум (id))

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

    К счастью, для этого есть простое решение, благодаря модели вложенного набора и тому, что MySQL позволяет нам устанавливать порядок удалений. Мы просто должны сделать заказ by nleft DESCили by nright DESC, что гарантирует, что все дочерние элементы будут удалены перед родительским:

    DELETE FROM forum 
    WHERE owner_id = 1 OR id = 1 
    ORDER BY nleft DESC ; 
    

    Небольшое примечание: мы могли (или должны) использовать условие, которое учитывает и вложенную модель. Это эквивалентно (и может использовать индекс (nleft, nright)для поиска узлов, которые нужно удалить:

    DELETE FROM forum 
    WHERE nleft >= 1 AND nright <= 8 
    ORDER BY nleft DESC ; 
    
ypercubeᵀᴹ
источник
5
SET FOREIGN_KEY_CHECKS=0;
DELETE FROM forum WHERE Owner_Id = 1 ORDER BY nright;
SET FOREIGN_KEY_CHECKS=1;

просто не забудьте в этом случае вы должны вручную разобрать ситуации, когда parent_id показывает 1, потому что вы не используете каскад

a_vlad
источник