7

¿Cuál es la forma recomendada de manejar restricciones de clave externa autorreferenciadas en SQL-Server?Referenciar restricciones de clave externa y eliminar

Tabla-modelo:

enter image description here

fiData referencia a un registro previo en tabData. Si elimino un registro al que hace referencia fiData, la base de datos se produce una excepción:

"La instrucción DELETE en conflicto con la misma tabla de referencia restricción 'FK_tabDataPrev_tabDataNext'. Se ha producido el conflicto en base de datos 'MyDatabase', mesa "dbo.tabData", columna "fiData" "

si Enforce Foreignkey Constraint está establecido en" Sí ".

No necesito eliminar en cascada los registros a los que se hace referencia, pero tendría que establecer fiData=NULL donde se hace referencia. Mi idea es establecer Enforce Foreignkey Constraint en "No" y crear un desencadenador de eliminación. ¿Es esto recomendable o hay mejores formas?

Gracias.

Respuesta

7

A diferencia Andomar, yo estaría feliz usando un disparador - pero no me quite el control de la restricción. Si se implementa como un disparador instead of, puede restablecer las otras filas a cero antes de realizar el actual Eliminar:

CREATE TRIGGER T_tabData_D 
on tabData 
instead of delete 
as 
    set nocount on 
    update tabData set fiData = null where fiData in (select idData from deleted) 
    delete from tabData where idData in (select idData from deleted) 

Es corto, es sucinta, no sería necesario si SQL Server podía manejar cascadas de clave externa a la misma tabla (en otro RDBMS ', usted puede simplemente especificar ON DELETE SET NULL para la restricción de clave externa, YMMV).

+1

Gracias. Pero recibo una excepción al crear el disparador: "Los desencadenadores INSTEAD OF DELETE/UPDATE no se pueden definir en una tabla que tiene una clave externa con una cascada en acción DELETE/UPDATE definida". –

+0

Sé que es demasiado tarde para comentar, pero para alguien que está buscando como yo. Esto solo borrará una entrada. no cascada en forma recursiva. – Arif

+0

@Arif - esto fue "ON DELETE SET NULL", no "ON DELETE CASCADE". El primero nunca necesita recurse. Para "ON DELETE CASCADE", recomendaría un CTE que calcule el cierre de todos los valores de 'ID' primero, y luego realice la eliminación. –

2

Los disparadores agregan complejidad implícita. En una base de datos con desencadenadores, no sabrá qué hace una instrucción SQL al observarla. En mi experiencia, los factores desencadenantes son una mala idea, sin excepciones.

En su ejemplo, si establece el forzado restringido en "No", significa que puede agregar una ID inexistente. Y el optimizador de consultas será menos efectivo porque no puede asumir que la clave es válida.

considerar la creación de un procedimiento almacenado en su lugar:

create procedure dbo.NukeTabData(
    @idData int) 
as 
begin transaction 
update tabData set fiData = null where fiData = @idData 
delete from tabData where idData = @idData 
commit transaction 
go 
+3

Ahora su código de llamada tiene 'exec NukeTabData (ID)', y no tiene idea de lo que hace sin examinar el contenido del procedimiento. ¿Por qué está bien, cuando un gatillo no es así? –

+2

@Damien_The_Unbeliever: El procedimiento almacenado es explícito: me dice dónde debo mirar si estoy interesado en los detalles. Un disparador es implícito: nada en 'delete from tabData where idData = 42' da pistas sobre la presencia de actualizaciones adicionales que se ejecutarán. Por ejemplo, después de ejecutar la eliminación, un desencadenador podría hacer que '@@ rowcount' sea 4 en lugar de 1. Esto da como resultado errores difíciles, especialmente para los desarrolladores que son nuevos en la base de códigos. – Andomar

+0

@@ ROWCOUNT está basado en el alcance: no se ve afectado por ninguna actividad dentro del desencadenador. Posiblemente esté pensando en @@ IDENTIDAD, pero la mayoría de la gente sabe para evitar que en estos días –

0

Esto muy tarde para responder.

Pero para alguien que está buscando como yo.

y quieren cascade

aquí es muy buena explicación

http://devio.wordpress.com/2008/05/23/recursive-delete-in-sql-server/

El problema Aunque se puede definir una clave externa con la eliminación en cascada en SQL Server, eliminaciones en cascada recursivos no son compatibles (es decir, eliminar en cascada en la misma tabla).

Si crea un desencadenador INSTEAD OF DELETE, este activador solo se activa para la primera instrucción DELETE y no se activa para los registros eliminados recursivamente de este desencadenador.

Este comportamiento está documentado en MSDN para SQL Server 2000 y SQL Server 2005.

La solución Suponga que tiene una tabla definida así:

CREATE TABLE MyTable (
    OID INT,  -- primary key 
    OID_Parent INT, -- recursion 
    ... other columns 
) 

entonces el desencadenador de eliminación se ve así:

CREATE TRIGGER del_MyTable ON MyTable INSTEAD OF DELETE 
AS 
    CREATE TABLE #Table(
     OID INT 
    ) 
INSERT INTO #Table (OID) 
SELECT OID 
FROM deleted 

DECLARE @c INT 
SET @c = 0 

WHILE @c <> (SELECT COUNT(OID) FROM #Table) BEGIN 
    SELECT @c = COUNT(OID) FROM #Table 

    INSERT INTO #Table (OID) 
    SELECT MyTable.OID 
    FROM MyTable 
    LEFT OUTER JOIN #Table ON MyTable.OID = #Table.OID 
    WHERE MyTable.OID_Parent IN (SELECT OID FROM #Table) 
    AND  #Table.OID IS NULL 
END 

DELETE MyTable 
FROM MyTable 
INNER JOIN #Table ON MyTable.OID = #Table.OID 

GO 
+0

Esta es una buena respuesta para ON DELETE CASCADE, pero la pregunta es sobre ON DELETE SET NULL – dajoropo

Cuestiones relacionadas