martes, 14 de febrero de 2012

Cambio masivo del valor de un campo con T-SQL

Hoy me ha surgido la necesidad de cambiar el valor de un código de producto en toda las tablas de la base de datos. Es decir, donde antes tenía el código X ahora debería de tener el código Y.

Lógicamente podría ejecutar manualmente una sentencia de actualización por cada tabla de la base de datos donde se encuentra el campo, pero coincidiremos ambos en que esto no parece una solución muy elegante. Además, no tengo la seguridad de saber exactamente en que tablas está mi campo y no quiero dejar el proceso a medias.

Como me parece que esta situación se podría repetir en un futuro, tanto para el código del producto como para cualquier otro código, he resuelto escribir un script que automatice la tarea en la medida de lo posible.

A grandes rasgos, el script que te muestro a continuación lleva a cabo los siguientes pasos:

  • Declarar el nombre de campo que queremos actualizar (aquí cabe mencionar que una base de datos normalizada ayudaría al proceso, es decir, si en una tabla has llamado a tu código de producto “ProductID” y en otra “IDProducto”, pues estamos fastidiados…)
  • Declarar el valor que queremos sustituir y el nuevo valor que queremos utilizar.
  • Recorrer las tablas en las que aparece el nombre de campo y deshabilitar todas sus restricciones (imagina que sino las restricciones del tipo ‘foreign key’ impedirían el proceso de actualización masivo)
  • Ejecutar una consulta de actualización por cada tabla donde aparezca el campo.
  • Volver a habilitar las restricciones previamente deshabilitadas.

Lógicamente el script no es perfecto y requiere cambiar algunos ajuste manuales como el tipo de los parámetros, tanto en la declaración inicial como en la sentencia de actualización con sp_executesql.

Además, el script presupone que las ‘foreign key’ declaradas en tus tablas están todas activadas, porque sino y en el último paso, siempre las activará incondicionalmente con independencia de como estuvieran inicialmente.

A continuación el script y espero te sea útil:

 DECLARE @columnName SYSNAME 
 -- Nombre de campo
 SET @columnName = N'ProductID' 
 DECLARE @OldValue NVARCHAR(20)
 -- Valor antiguo
 SET @OldValue = N'1701291'
 DECLARE @NewValue NVARCHAR(20)
 -- Valor nuevo
 SET @NewValue = N'1701291__MODIFICADO'
	
 DECLARE @tableSchema NVARCHAR(128)
 DECLARE @tableName SYSNAME
    
 DECLARE @noCheckStatement NVARCHAR(4000) 
 DECLARE @checkStatement NVARCHAR(4000) 
 SET @noCheckStatement = N''
 SET @checkStatement = N''
 -- Tablas que contienen el nombre de campo
 DECLARE cTables CURSOR READ_ONLY
 FOR
    SELECT  T.TABLE_SCHEMA ,
            T.TABLE_NAME
    FROM    INFORMATION_SCHEMA.TABLES T
            INNER JOIN INFORMATION_SCHEMA.COLUMNS C 
            ON T.TABLE_SCHEMA = C.TABLE_SCHEMA AND T.TABLE_NAME = C.TABLE_NAME
    WHERE   TABLE_TYPE = N'BASE TABLE'
            AND COLUMN_NAME = @columnName
		
 OPEN cTables
 FETCH NEXT FROM cTables INTO @tableSchema, @tableName
 WHILE ( @@fetch_status <> -1 ) 
    BEGIN
        IF ( @@fetch_status <> -2 ) 
            BEGIN          
		-- Generar sentencias para deshabilitar y habilitar restricciones 
                SET @noCheckStatement = @noCheckStatement + N'ALTER TABLE '
                    + @tableSchema + '.' + @tableName
                    + ' NOCHECK CONSTRAINT ALL;'
                SET @checkStatement = @checkStatement + N'ALTER TABLE '
                    + @tableSchema + '.' + @tableName
                    + ' WITH CHECK CHECK CONSTRAINT ALL;'
            END
        FETCH NEXT FROM cTables INTO @tableSchema, @tableName
    END
 CLOSE cTables
 
 -- Deshabilitar restricciones
 EXECUTE sp_executesql @noCheckStatement
 
 DECLARE @statement NVARCHAR(4000) 
 DECLARE @params NVARCHAR(4000)   
		
 OPEN cTables
 FETCH NEXT FROM cTables INTO @tableSchema, @tableName
 WHILE ( @@fetch_status <> -1 ) 
    BEGIN
        IF ( @@fetch_status <> -2 ) 
            BEGIN          
		-- Generar sentencia de actualización
                SET @statement = N'UPDATE ' + @tableSchema + '.' + @tableName
                    + ' SET ' + @columnName + ' = @NewValue WHERE '
                    + @columnName + ' = @OldValue'
                SET @params = N'@NewValue NVARCHAR(20), @OldValue NVARCHAR(20)'                
                -- Actualizar
                EXECUTE sp_executesql @statement, @params, @NewValue,
                    @OldValue
            END
        FETCH NEXT FROM cTables INTO @tableSchema, @tableName
    END
 CLOSE cTables
 DEALLOCATE cTables
 
 -- Habilitar restricciones
 EXECUTE sp_executesql @checkStatement                     

No hay comentarios:

Publicar un comentario