Actualización de la clave principal en cascada para todas las claves externas de referencia

11

¿Es posible actualizar un valor de columna de clave primaria con la actualización en cascada entre todas las claves externas que hacen referencia a ella?

# EDIT 1: cuando ejecuto la consulta followinq

select * from sys.foreign_keys where referenced_object_id=OBJECT_ID('myTable') 

, Veo que update_referential_action se establece en 0. Por lo tanto, NO se toma ninguna acción después de actualizar mis columnas de claves principales. ¿Cómo puedo actualizar las claves foráneas para que estén ACTUALIZADAS EN CASCADA ?

# EDIT 2:
para ejecutar la creación o el descarte de todas las claves foráneas en su esquema, ejecute el siguiente script (tomado de aquí )

DECLARE @schema_name sysname;

DECLARE @table_name sysname;

DECLARE @constraint_name sysname;

DECLARE @constraint_object_id int;

DECLARE @referenced_object_name sysname;

DECLARE @is_disabled bit;

DECLARE @is_not_for_replication bit;

DECLARE @is_not_trusted bit;

DECLARE @delete_referential_action tinyint;

DECLARE @update_referential_action tinyint;

DECLARE @tsql nvarchar(4000);

DECLARE @tsql2 nvarchar(4000);

DECLARE @fkCol sysname;

DECLARE @pkCol sysname;

DECLARE @col1 bit;

DECLARE @action char(6);  

DECLARE @referenced_schema_name sysname;



DECLARE FKcursor CURSOR FOR

     select OBJECT_SCHEMA_NAME(parent_object_id)

         , OBJECT_NAME(parent_object_id), name, OBJECT_NAME(referenced_object_id)

         , object_id

         , is_disabled, is_not_for_replication, is_not_trusted

         , delete_referential_action, update_referential_action, OBJECT_SCHEMA_NAME(referenced_object_id)

    from sys.foreign_keys

    order by 1,2;

OPEN FKcursor;

FETCH NEXT FROM FKcursor INTO @schema_name, @table_name, @constraint_name

    , @referenced_object_name, @constraint_object_id

    , @is_disabled, @is_not_for_replication, @is_not_trusted

    , @delete_referential_action, @update_referential_action, @referenced_schema_name;

WHILE @@FETCH_STATUS = 0

BEGIN



      IF @action <> 'CREATE'

        SET @tsql = 'ALTER TABLE '

                  + QUOTENAME(@schema_name) + '.' + QUOTENAME(@table_name)

                  + ' DROP CONSTRAINT ' + QUOTENAME(@constraint_name) + ';';

    ELSE

        BEGIN

        SET @tsql = 'ALTER TABLE '

                  + QUOTENAME(@schema_name) + '.' + QUOTENAME(@table_name)

                  + CASE @is_not_trusted

                        WHEN 0 THEN ' WITH CHECK '

                        ELSE ' WITH NOCHECK '

                    END

                  + ' ADD CONSTRAINT ' + QUOTENAME(@constraint_name)

                  + ' FOREIGN KEY (';

        SET @tsql2 = '';

        DECLARE ColumnCursor CURSOR FOR

            select COL_NAME(fk.parent_object_id, fkc.parent_column_id)

                 , COL_NAME(fk.referenced_object_id, fkc.referenced_column_id)

            from sys.foreign_keys fk

            inner join sys.foreign_key_columns fkc

            on fk.object_id = fkc.constraint_object_id

            where fkc.constraint_object_id = @constraint_object_id

            order by fkc.constraint_column_id;

        OPEN ColumnCursor;

        SET @col1 = 1;

        FETCH NEXT FROM ColumnCursor INTO @fkCol, @pkCol;

        WHILE @@FETCH_STATUS = 0

        BEGIN

            IF (@col1 = 1)

                SET @col1 = 0;

            ELSE

            BEGIN

                SET @tsql = @tsql + ',';

                SET @tsql2 = @tsql2 + ',';

            END;

            SET @tsql = @tsql + QUOTENAME(@fkCol);

            SET @tsql2 = @tsql2 + QUOTENAME(@pkCol);

            FETCH NEXT FROM ColumnCursor INTO @fkCol, @pkCol;

        END;

        CLOSE ColumnCursor;

        DEALLOCATE ColumnCursor;

       SET @tsql = @tsql + ' ) REFERENCES ' + QUOTENAME(@referenced_schema_name) + '.' + QUOTENAME(@referenced_object_name)

                  + ' (' + @tsql2 + ')';

        SET @tsql = @tsql

                  + ' ON UPDATE ' + CASE @update_referential_action

                                        WHEN 0 THEN 'NO ACTION '

                                        WHEN 1 THEN 'CASCADE '

                                        WHEN 2 THEN 'SET NULL '

                                        ELSE 'SET DEFAULT '

                                    END

                  + ' ON DELETE ' + CASE @delete_referential_action

                                        WHEN 0 THEN 'NO ACTION '

                                        WHEN 1 THEN 'CASCADE '

                                        WHEN 2 THEN 'SET NULL '

                                        ELSE 'SET DEFAULT '

                                    END

                  + CASE @is_not_for_replication

                        WHEN 1 THEN ' NOT FOR REPLICATION '

                        ELSE ''

                    END

                  + ';';

        END;

    PRINT @tsql;

    IF @action = 'CREATE'

        BEGIN

        SET @tsql = 'ALTER TABLE '

                  + QUOTENAME(@schema_name) + '.' + QUOTENAME(@table_name)

                  + CASE @is_disabled

                        WHEN 0 THEN ' CHECK '

                        ELSE ' NOCHECK '

                    END

                  + 'CONSTRAINT ' + QUOTENAME(@constraint_name)

                  + ';';

        PRINT @tsql;

        END;

    FETCH NEXT FROM FKcursor INTO @schema_name, @table_name, @constraint_name

        , @referenced_object_name, @constraint_object_id

        , @is_disabled, @is_not_for_replication, @is_not_trusted

        , @delete_referential_action, @update_referential_action, @referenced_schema_name;

END;

CLOSE FKcursor;

DEALLOCATE FKcursor;  

Para generar el script de claves externas DROP, modifique el valor de @action para que sea igual a 'DROP' en la cláusula de declaración:

DECLARE @action char(6) = 'DROP';
mounaim
fuente

Respuestas:

9

Si ha definido las restricciones de la clave externa como ON UPDATE CASCADEentonces, el valor de la clave primaria que se modificó debería caer en cascada a todas las claves externas con esa restricción.

Si no tiene la ON UPDATE CASCADErestricción, necesitará crear scripts para completar la actualización.

EDITAR: Dado que no tiene la ON UPDATE CASCADErestricción, pero desea configurarla, es un poco de trabajo. SQL Server no admite alterar las restricciones a una nueva configuración.

Es necesario recorrer cada tabla que tenga una restricción FK a la tabla PK. Para cada mesa con el FK:

  1. ALTER TABLE para eliminar la restricción FK existente.
  2. ALTER TABLE nuevamente para crear la restricción ON UPDATE CASCADE para el FK en cuestión.

Esto requiere un poco de esfuerzo, pero daría como resultado que su restricción se establezca correctamente para su caso.

EDITAR 2: la información que necesita se encuentra en sys.foreign_keys. Puede seleccionar de esa tabla para obtener toda la información que necesita.

Una publicación de John Paul Cook se puede encontrar aquí:

( http://social.technet.microsoft.com/wiki/contents/articles/2958.script-to-create-all-foreign-keys.aspx )

Este código caerá y creará TODAS las restricciones FK en una base de datos. Debería poder trabajar a partir de eso para hacer solo los cambios que desee en su base de datos.

RLF
fuente
ver mi edición para más información
mounaim
¿Sabes cómo escribir todas las claves foráneas @RLF?
mounaim
@mounaim: actualizado con una nota sobre la creación del script.
RLF
Estaba trabajando en lo mismo y en el mismo enlace. Vea mi edición @RLF
mounaim
1
es mejor incluir bloques de código aquí en DBA SE porque los enlaces a otros sitios web pueden romperse más tarde :)
mounaim 05 de
4

Seguro que puedes. ON UPDATE CASCADEes lo que buscas.

Aquí hay un pequeño tutorial: http://sqlandme.com/2011/08/08/sql-server-how-to-cascade-updates-and-deletes-to-related-tables/

Básicamente, cuando modificas la PK, la cascada se apagará y actualizará todos los FK que hacen referencia a ella. Esto se puede hacer en su CREATEdeclaración, igual que si estuviera haciendo unCASCADE DELETE

Esté atento a las cosas cuando haga esto porque, según tengo entendido, CASCADE realmente se ejecuta en el nivel de aislamiento SERIALIZABLE(normalmente, SQL se ejecuta READ COMMITTEDpor defecto) detrás de escena, así que esté atento a cualquier problema de bloqueo.

Puede encontrar información adicional sobre los niveles de aislamiento en este artículo: http://msdn.microsoft.com/en-us/library/ms173763.aspx

Kris Gruttemeyer
fuente
3

Defina todas las claves foráneas como ACTUALIZACIÓN EN CASCADA

Si no has hecho esto, entonces tendrás que

  1. Crear una nueva fila con nueva clave principal
  2. Actualizar todas las tablas secundarias
  3. Eliminar fila anterior

.. en una transacción, por supuesto, y vigilando otras restricciones que podrían fallar

gbn
fuente
gracias @gbn. ¿Es posible actualizar mis claves foráneas o solo tengo que soltarlas y recrearlas con la cláusula ACTUALIZACIÓN EN CASCADA?
mounaim
Soltar y recrear ...
gbn