Estoy tratando de usar una MERGE
declaración para insertar o eliminar filas de una tabla, pero solo quiero actuar en un subconjunto de esas filas. La documentación para MERGE
tiene una advertencia bastante redactada:
Es importante especificar solo las columnas de la tabla de destino que se utilizan con fines de coincidencia. Es decir, especifique columnas de la tabla de destino que se comparan con la columna correspondiente de la tabla de origen. No intente mejorar el rendimiento de la consulta filtrando las filas de la tabla de destino en la cláusula ON, como especificando AND NOT target_table.column_x = value. Hacerlo puede devolver resultados inesperados e incorrectos.
pero esto es exactamente lo que parece que tengo que hacer para hacer mi MERGE
trabajo.
Los datos que tengo son una tabla estándar de unión de muchos a muchos a categorías (por ejemplo, qué elementos se incluyen en qué categorías) de esta manera:
CategoryId ItemId
========== ======
1 1
1 2
1 3
2 1
2 3
3 5
3 6
4 5
Lo que necesito hacer es reemplazar efectivamente todas las filas en una categoría específica con una nueva lista de elementos. Mi intento inicial de hacer esto se ve así:
MERGE INTO CategoryItem AS TARGET
USING (
SELECT ItemId FROM SomeExternalDataSource WHERE CategoryId = 2
) AS SOURCE
ON SOURCE.ItemId = TARGET.ItemId AND TARGET.CategoryId = 2
WHEN NOT MATCHED BY TARGET THEN
INSERT ( CategoryId, ItemId )
VALUES ( 2, ItemId )
WHEN NOT MATCHED BY SOURCE AND TARGET.CategoryId = 2 THEN
DELETE ;
Esto parece estar funcionando en mis pruebas, pero estoy haciendo exactamente lo que MSDN me advierte explícitamente que no haga. Esto me preocupa de tener problemas inesperados más adelante, pero no puedo ver ninguna otra manera de hacer que mis MERGE
únicas filas afecten con el valor de campo específico ( CategoryId = 2
) e ignore las filas de otras categorías.
¿Existe una forma "más correcta" de lograr este mismo resultado? ¿Y cuáles son los "resultados inesperados o incorrectos" que MSDN me advierte?
fuente
Respuestas:
La
MERGE
declaración tiene una sintaxis compleja y una implementación aún más compleja, pero esencialmente la idea es unir dos tablas, filtrar a filas que deben cambiarse (insertarse, actualizarse o eliminarse) y luego realizar los cambios solicitados. Dados los siguientes datos de muestra:Objetivo
Fuente
El resultado deseado es reemplazar los datos en el destino con datos de la fuente, pero solo para
CategoryId = 2
. Siguiendo la descripción de loMERGE
anterior, deberíamos escribir una consulta que combine el origen y el destino solo en las claves, y filtre las filas solo en lasWHEN
cláusulas:Esto da los siguientes resultados:
El plan de ejecución es:
Observe que ambas tablas se escanean completamente. Podríamos pensar que esto es ineficiente, porque solo las filas
CategoryId = 2
se verán afectadas en la tabla de destino. Aquí es donde entran las advertencias en Books Online. Un intento equivocado de optimizar para tocar solo las filas necesarias en el objetivo es:La lógica en la
ON
cláusula se aplica como parte de la unión. En este caso, la combinación es una combinación externa completa (consulte esta entrada de Libros en línea para saber por qué). Al aplicar la verificación para la categoría 2 en las filas de destino como parte de una unión externa, en última instancia, se eliminan las filas con un valor diferente (porque no coinciden con la fuente):La causa raíz es la misma razón por la que los predicados se comportan de manera diferente en una
ON
cláusula de unión externa que si se especifica en laWHERE
cláusula. LaMERGE
sintaxis (y la implementación de la unión según las cláusulas especificadas) solo hacen que sea más difícil ver que esto es así.La guía en Books Online (ampliada en la entrada Optimizing Performance ) ofrece una guía que garantizará que la semántica correcta se exprese utilizando la
MERGE
sintaxis, sin que el usuario tenga que comprender necesariamente todos los detalles de implementación, o explicar las formas en que el optimizador podría reorganizarse legítimamente cosas por razones de eficiencia de ejecución.La documentación ofrece tres formas potenciales de implementar el filtrado temprano:
La especificación de una condición de filtrado en la
WHEN
cláusula garantiza resultados correctos, pero puede significar que se leen y procesan más filas de las tablas de origen y destino de las estrictamente necesarias (como se ve en el primer ejemplo).La actualización a través de una vista que contiene la condición de filtrado también garantiza resultados correctos (dado que las filas modificadas deben ser accesibles para la actualización a través de la vista) pero esto requiere una vista dedicada y una que siga las condiciones extrañas para actualizar las vistas.
El uso de una expresión de tabla común conlleva riesgos similares al agregar predicados a la
ON
cláusula, pero por razones ligeramente diferentes. En muchos casos será seguro, pero requiere un análisis experto del plan de ejecución para confirmar esto (y pruebas prácticas exhaustivas). Por ejemplo:Esto produce resultados correctos (no repetidos) con un plan más óptimo:
El plan solo lee filas para la categoría 2 de la tabla de destino. Esto podría ser una consideración importante de rendimiento si la tabla de destino es grande, pero es demasiado fácil equivocarse con la
MERGE
sintaxis.A veces, es más fácil escribir
MERGE
como operaciones DML separadas. Este enfoque incluso puede funcionar mejor que uno soloMERGE
, un hecho que a menudo sorprende a las personas.fuente