MySQL: elimine la fila que tiene una restricción de clave externa que hace referencia a sí misma

12

Tengo una tabla en la que almaceno todos los mensajes del foro publicados por los usuarios en mi sitio web. La estructura de la jerarquía de mensajes se implementa utilizando un modelo de conjunto anidado .

La siguiente es una estructura simplificada de la tabla:

  • Id (CLAVE PRIMARIA)
  • Owner_Id (REFERENCIAS CLAVE EXTRANJERAS A Id )
  • Parent_Id (REFERENCIAS CLAVE EXTRANJERAS A Id )
  • nleft
  • bien
  • nivel

Ahora, la mesa se ve así:

+ ------- + ------------- + -------------- + ---------- + ----------- + ----------- +
| 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           |
+ ------- + ------------- + -------------- + ---------- + ----------- + ----------- +

Tenga en cuenta que la primera fila es el mensaje raíz, y el árbol de esta publicación se puede mostrar como:

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

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

Mi problema ocurre cuando intento eliminar todas las filas debajo de la misma Owner_Iden una sola consulta. Ejemplo:

DELETE FROM forumTbl WHERE Owner_Id = 1 ORDER BY nright;

La consulta anterior falla con el siguiente error:

Código de error: 1451. No se puede eliminar o actualizar una fila principal: falla una restricción de clave foránea ( forumTbl, Owner_Id_frgnLLAVE EXTRANJERA CONSTRAINT ( Owner_Id) REFERENCIAS forumTbl( Id) AL BORRAR SIN ACCIÓN AL ACTUALIZAR SIN ACCIÓN)

La razón es que la primera fila , que es el nodo raíz ( Id=1), también tiene el mismo valor en su Owner_Idcampo ( Owner_Id=1), y hace que la consulta falle debido a la restricción de clave externa.

Mi pregunta es: ¿cómo puedo evitar esta circularidad de restricción de clave externa y eliminar una fila que hace referencia a sí misma? ¿Hay alguna manera de hacerlo sin tener que actualizar primero el Owner_Idde la fila raíz NULL?

Creé una demostración de este escenario: http://sqlfiddle.com/#!9/fd1b1

Gracias.

Alon Eitan
fuente

Respuestas:

9
  1. Además de deshabilitar las claves externas, que es peligroso y puede generar inconsistencias, hay otras dos opciones a tener en cuenta:

  2. Modifique las FOREIGN KEYrestricciones con la ON DELETE CASCADEopción. No he probado todos los casos, pero seguramente lo necesita para la (owner_id)clave externa y posiblemente también para la otra.

    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 ;
    

    Si hace esto, entonces eliminar un nodo y todos los descendientes del árbol es más simple. Elimina un nodo y todos los descendientes se eliminan mediante las acciones en cascada:

    DELETE FROM forum
    WHERE id = 1 ;         -- deletes id=1 and all descendants
    
  3. El problema en el que te metiste es en realidad 2 problemas. La primera es que eliminar de una tabla con clave foránea autorreferenciada no es un problema grave para MySQL, siempre que no haya una fila que haga referencia a sí misma. Si hay una fila, como en su ejemplo, las opciones son limitadas. Deshabilite las claves externas o use la CASCADEacción. Pero si no existen tales filas, la eliminación se convierte en un problema menor.

    Por lo tanto, si decidimos almacenar NULLen lugar de la misma iden owner_id, entonces se podría eliminar sin desactivar las claves externas y sin cascadas.

    ¡Entonces tropezarías con el segundo problema! Ejecutar su consulta generaría un error similar:

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

    Error (s), advertencia (s):
    no se puede eliminar o actualizar una fila principal: falla una restricción de clave externa (rextester.forum, CONSTRAINT owner_id_frgn FOREIGN KEY (owner_Id) REFERENCES forum (id))

    Sin embargo, la razón de este error sería diferente a la anterior. Esto se debe a que MySQL verifica cada restricción después de eliminar cada fila y no (como debería) al final de la declaración. Entonces, cuando un padre se elimina antes de que se elimine su hijo, obtenemos un error de restricción de clave externa.

    Afortunadamente, hay una solución simple para esto, gracias al modelo de conjunto anidado y a eso MySQL nos permite establecer un orden para las eliminaciones. Solo tenemos que ordenar por nleft DESCo por nright DESC, lo que asegura que todos los hijos se eliminen antes que un padre:

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

    Nota menor, podríamos (o deberíamos) usar una condición que también considere el modelo anidado. Esto es equivalente (y podría usar un índice (nleft, nright)para encontrar qué nodos eliminar:

    DELETE FROM forum 
    WHERE nleft >= 1 AND nright <= 8 
    ORDER BY nleft DESC ; 
    
ypercubeᵀᴹ
fuente
5
SET FOREIGN_KEY_CHECKS=0;
DELETE FROM forum WHERE Owner_Id = 1 ORDER BY nright;
SET FOREIGN_KEY_CHECKS=1;

simplemente no olvide en este caso Debe analizar manualmente las situaciones cuando parent_id se muestre en 1, porque no usa cascada

a_vlad
fuente