Ejecutar un UPDATE con validación y reversión controlada

Necesitamos aumentar el precio de los productos activos de una categoría. La condición parece sencilla, pero un filtro incorrecto podría modificar más filas de las previstas y dejar el error confirmado antes de detectarlo.

Qué necesitamos distinguir

Una transacción agrupa cambios para confirmarlos con COMMIT o revertirlos con ROLLBACK como una unidad. Un bloqueo coordina el acceso concurrente y puede conservarse mientras la transacción siga abierta.

Separaremos la selección del objetivo, la modificación y la confirmación. Las filas se fijarán dentro de una transacción, se mostrará el antes y el después, y el cambio se revertirá mientras no exista una confirmación explícita.

Consulta

/*
    Antes de ejecutar:
        Revisa la base activa, los nombres de objetos, los parámetros y los permisos.
        Adapta el ejemplo a tu modelo y pruébalo primero en un ambiente controlado.
    Los cambios se revierten de forma predeterminada.
    Mantén @ConfirmarCambios = 0 hasta validar el alcance.
*/

SET NOCOUNT ON;
GO
-- Consulta
DECLARE @IdCategoria SMALLINT = 1;
DECLARE @Porcentaje DECIMAL(5, 2) = 5.00;
DECLARE @MaximoFilas INT = 20;
DECLARE @ConfirmarCambios BIT = 0;

SET XACT_ABORT ON;

BEGIN TRY
    BEGIN TRANSACTION;

    DECLARE @Objetivo TABLE
    (
        IdProducto INT PRIMARY KEY
    );

    INSERT INTO @Objetivo (IdProducto)
    SELECT p.IdProducto
    FROM dbo.Producto AS p WITH (UPDLOCK, HOLDLOCK)
    WHERE p.IdCategoria = @IdCategoria
      AND p.Activo = 1;

    DECLARE @FilasObjetivo INT = @@ROWCOUNT;

    IF @FilasObjetivo > @MaximoFilas
        THROW 50001, N'El UPDATE supera el máximo de filas esperado.', 1;

    SELECT
        p.IdProducto,
        p.Nombre,
        p.Precio AS PrecioAntes
    FROM dbo.Producto AS p
    INNER JOIN @Objetivo AS o
        ON o.IdProducto = p.IdProducto
    ORDER BY p.IdProducto;

    DECLARE @Cambios TABLE
    (
        IdProducto INT,
        PrecioAntes DECIMAL(12, 2),
        PrecioDespues DECIMAL(12, 2)
    );

    UPDATE p
       SET p.Precio = ROUND
           (p.Precio * (1 + @Porcentaje / 100.0), 2),
           p.FechaActualizacion = SYSDATETIME()
    OUTPUT
        deleted.IdProducto,
        deleted.Precio,
        inserted.Precio
    INTO @Cambios (IdProducto, PrecioAntes, PrecioDespues)
    FROM dbo.Producto AS p
    INNER JOIN @Objetivo AS o
        ON o.IdProducto = p.IdProducto;

    IF @@ROWCOUNT <> @FilasObjetivo
        THROW 50002, N'Cambió el número de filas durante el UPDATE.', 1;

    SELECT
        c.IdProducto,
        c.PrecioAntes,
        c.PrecioDespues
    FROM @Cambios AS c
    ORDER BY c.IdProducto;

    IF @ConfirmarCambios = 1
        COMMIT TRANSACTION;
    ELSE
        ROLLBACK TRANSACTION;
END TRY
BEGIN CATCH
    IF XACT_STATE() <> 0
        ROLLBACK TRANSACTION;

    THROW;
END CATCH;
GO

Parámetros o permisos

Adapta la categoría, porcentaje y límite. @ConfirmarCambios permanece en cero para que el ejemplo revierta de forma predeterminada. Cambiarlo a uno solo tiene sentido después de revisar el objetivo, el resultado y los efectos asociados. Se requieren permisos de lectura y actualización. UPDLOCK y HOLDLOCK mantienen bloqueado el conjunto seleccionado hasta terminar la transacción; reducen cambios concurrentes, pero pueden hacer esperar a otras sesiones. Mantén la revisión corta y prueba primero fuera de producción.

Cómo leer el resultado

El primer resultado presenta las filas fijadas como objetivo. El segundo utiliza OUTPUT para comparar el precio anterior y el nuevo. El límite detiene la operación si el alcance supera lo esperado, y la segunda comprobación exige que el número actualizado coincida con el conjunto seleccionado. Si ocurre un error, TRY/CATCH, XACT_ABORT y XACT_STATE llevan la transacción a una reversión controlada. Un valor cero en @ConfirmarCambios también ejecuta ROLLBACK aun cuando todo sea válido.

Qué conclusión sí permite obtener

Permite comprobar el alcance y el efecto inmediato de una actualización dentro de una transacción controlada.

Qué conclusión todavía no permite obtener

No demuestra que el nuevo precio sea correcto para el negocio. Tampoco descarta efectos en triggers, auditorías, replicación, integraciones o procesos que lean las filas mientras permanecen bloqueadas.

Siguiente comprobación razonable

Revisa triggers y dependencias, prueba el script con datos representativos y conserva evidencia del conjunto esperado. Solo entonces decide si corresponde confirmar la misma operación en el ambiente autorizado.

Índice del recorrido

Desarrollo y exploración

22 artículos de Desarrollo y exploración
  1. 01Buscar una tabla o vista por nombre
  2. 02Buscar una columna en toda la base
  3. 03Buscar texto dentro de procedimientos, vistas y funciones
  4. 04Consultar dependencias de un objeto
  5. 05Listar relaciones y llaves foráneas
  6. 06Consultar llaves primarias, restricciones únicas e índices
  7. 07Listar columnas con tipo, longitud, nulabilidad y valor predeterminado
  8. 08Encontrar triggers y sus tablas asociadas
  9. 09Encontrar objetos modificados recientemente
  10. 10Obtener conteos aproximados de todas las tablas
  11. 11Medir el espacio utilizado por cada tabla
  12. 12Revisar columnas IDENTITY y su valor actual
  13. 13Detectar registros duplicados por una clave de negocio
  14. 14Detectar registros huérfanos
  15. 15Comparar dos conjuntos de datos
  16. 16Validar conversiones antes de modificar una columna
  17. 17Ejecutar un UPDATE con validación y reversión controlada
  18. 18Ejecutar un DELETE con respaldo lógico y reversión controlada
  19. 19Comparar la estructura de dos bases de datos
  20. 20Describir el resultado de una consulta o procedimiento
  21. 21Encontrar tablas sin llave primaria
  22. 22Buscar un valor en columnas seleccionadas