No estoy completamente seguro, pero me da la impresión de que esta pregunta es realmente acerca de upsert, que es la siguiente operación atómica:
- Si la fila existe tanto en el origen y el destino, la
UPDATE
objetivo;
- Si la fila solo existe en la fuente,
INSERT
la fila en el destino;
- (Opcional) Si la fila existe en el destino pero no la fuente,
DELETE
la fila del destino.
Desarrolladores convertido en administradores de bases menudo ingenuamente escriben fila por fila, de esta manera:
-- For each row in source
IF EXISTS(<target_expression>)
IF @delete_flag = 1
DELETE <target_expression>
ELSE
UPDATE target
SET <target_columns> = <source_values>
WHERE <target_expression>
ELSE
INSERT target (<target_columns>)
VALUES (<source_values>)
Esto es casi lo peor que se puede hacer, por varias razones:
Tiene una condición de carrera. La fila puede desaparecer entre IF EXISTS
y DELETE
o UPDATE
.
Es un desperdicio. Para cada transacción tiene una operación adicional que se realiza; tal vez es trivial, pero eso depende completamente de lo bien que hayas indexado.
Lo peor de todo, es seguir un modelo iterativo, pensando en estos problemas en el nivel de una sola fila. Esto tendrá el mayor (peor) impacto de todos en el rendimiento general.
Una muy pequeña (y subrayo menor) de optimización es simplemente intentar la UPDATE
de todos modos; si la fila no existe, @@ROWCOUNT
será 0 y se puede entonces "segura" de inserción:
-- For each row in source
BEGIN TRAN
UPDATE target
SET <target_columns> = <source_values>
WHERE <target_expression>
IF (@@ROWCOUNT = 0)
INSERT target (<target_columns>)
VALUES (<source_values>)
COMMIT
peor de los casos, esto va a seguir realizando dos operaciones para cada transacción, pero al menos hay una posibilidad de de solo realizar uno, y también elimina la condición de raza (tipo de).
Pero el problema real es que esto todavía se está haciendo para cada fila en la fuente.
Antes de SQL Server 2008, se tenía que utilizar un modelo 3-etapa difícil hacer frente a este en el nivel establecido (aún mejor que la fila por fila): El rendimiento
BEGIN TRAN
INSERT target (<target_columns>)
SELECT <source_columns> FROM source s
WHERE s.id NOT IN (SELECT id FROM target)
UPDATE t SET <target_columns> = <source_columns>
FROM target t
INNER JOIN source s ON t.d = s.id
DELETE t
FROM target t
WHERE t.id NOT IN (SELECT id FROM source)
COMMIT
Como ya he dicho, fue bastante pésimo en esto, pero aún mucho mejor que el enfoque de una fila a la vez.SQL Server 2008, sin embargo, finalmente se introdujo MERGE sintaxis, por lo que ahora todo lo que tiene que hacer es lo siguiente:
MERGE target
USING source ON target.id = source.id
WHEN MATCHED THEN UPDATE <target_columns> = <source_columns>
WHEN NOT MATCHED THEN INSERT (<target_columns>) VALUES (<source_columns>)
WHEN NOT MATCHED BY SOURCE THEN DELETE;
eso es todo. Una declaración. Si está utilizando SQL Server 2008 y la necesidad de llevar a cabo cualquier secuencia de INSERT
, UPDATE
y DELETE
dependiendo de si o no la fila ya existe - incluso si es sólo una fila - hay sin excusa para no estar usando MERGE
.
Puede incluso OUTPUT
las filas afectadas por un MERGE
en una variable de tabla si necesita saber más tarde lo que se hizo. Simple, rápido y libre de riesgos. Hazlo.
¿Por qué no mira el plan de ejecución? – RichardOD