Estoy tratando de actualizar una tabla con una matriz de valores. Cada elemento de la matriz contiene información que coincide con una fila en una tabla en la base de datos de SQL Server. Si la fila ya existe en la tabla, actualizamos esa fila con la información en la matriz dada. De lo contrario, insertamos una nueva fila en la tabla. Básicamente he descrito upsert.
Ahora, estoy tratando de lograr esto en un procedimiento almacenado que toma un parámetro XML. La razón por la que estoy usando XML y no un parámetro con valores de tabla es porque, al hacer esto último, tendré que crear un tipo personalizado en SQL y asociar este tipo al procedimiento almacenado. Si alguna vez cambiara algo en mi procedimiento almacenado o mi esquema db en el futuro, tendría que rehacer tanto el procedimiento almacenado como el tipo personalizado. Quiero evitar esta situación. Además, la superioridad que tiene TVP sobre XML no es útil para mi situación porque mi tamaño de matriz de datos nunca excederá 1000. Esto significa que no puedo usar la solución propuesta aquí: Cómo insertar múltiples registros usando XML en SQL Server 2008
Además, una discusión similar aquí ( UPSERT - ¿Hay una mejor alternativa para MERGE o @@ rowcount? ) Es diferente de lo que estoy preguntando porque estoy tratando de insertar varias filas en una tabla.
Tenía la esperanza de que simplemente usaría el siguiente conjunto de consultas para insertar los valores del xml. Pero esto no va a funcionar. Se supone que este enfoque solo funciona cuando la entrada es una sola fila.
begin tran
update table with (serializable) set select * from xml_param
where key = @key
if @@rowcount = 0
begin
insert table (key, ...) values (@key,..)
end
commit tran
La siguiente alternativa es usar un SI exhaustivo EXISTE o una de sus variaciones de la siguiente forma. Pero, rechazo esto por ser de eficiencia subóptima:
IF (SELECT COUNT ... ) > 0
UPDATE
ELSE
INSERT
La siguiente opción fue usar la instrucción Merge como se describe aquí: http://www.databasejournal.com/features/mssql/using-the-merge-statement-to-perform-an-upsert.html . Pero, luego leí sobre problemas con la consulta de fusión aquí: http://www.mssqltips.com/sqlservertip/3074/use-caution-with-sql-servers-merge-statement/ . Por esta razón, estoy tratando de evitar la fusión.
Entonces, mi pregunta es: ¿hay alguna otra opción o una mejor manera de lograr una respuesta múltiple múltiple usando el parámetro XML en el procedimiento almacenado de SQL Server 2008?
Tenga en cuenta que los datos en el parámetro XML pueden contener algunos registros que no se deben UPSERT debido a que son más antiguos que el registro actual. Hay un ModifiedDate
campo tanto en el XML como en la tabla de destino que debe compararse para determinar si el registro debe actualizarse o descartarse.
fuente
MERGE
que Bertrand señala son en su mayoría casos extremos e ineficiencias, no muestran obstáculos: MS no lo habría lanzado si fuera un verdadero campo minado. ¿Estás seguro de que las convoluciones por las que estás pasando para evitarMERGE
no están creando más errores potenciales de los que están guardando?MERGE
. Los pasos INSERTAR y ACTUALIZAR de MERGE aún se procesan por separado. La principal diferencia en mi enfoque es la variable de tabla que contiene los ID de registro actualizados y la consulta DELETE que usa esa variable de tabla para eliminar esos registros de la tabla temporal de los datos entrantes. Y supongo que la FUENTE podría ser directa desde @ XMLparam.nodes () en lugar de volcar a una tabla temporal, pero aún así, eso no es un montón de cosas adicionales para no tener que preocuparse de encontrarse en uno de esos casos extremos; )Respuestas:
Si la fuente es XML o un TVP no hace una gran diferencia. La operación general es esencialmente:
Lo haces en ese orden porque si INSERTAS primero, entonces todas las filas existen para obtener la ACTUALIZACIÓN y harás un trabajo repetido para las filas que acabas de insertar.
Más allá de eso, hay diferentes maneras de lograr esto y varias formas de ajustar algo de eficiencia adicional.
Comencemos con lo mínimo. Dado que es probable que extraer el XML sea una de las partes más caras de esta operación (si no la más costosa), no queremos tener que hacerlo dos veces (ya que tenemos dos operaciones que realizar). Entonces, creamos una tabla temporal y extraemos los datos del XML en ella:
A partir de ahí hacemos la ACTUALIZACIÓN y luego la INSERCIÓN:
Ahora que tenemos la operación básica inactiva, podemos hacer algunas cosas para optimizar:
capture @@ ROWCOUNT de inserción en la tabla temporal y compárelo con @@ ROWCOUNT de la ACTUALIZACIÓN. Si son iguales, entonces podemos omitir el INSERTAR
capturar los valores de ID actualizados a través de la cláusula OUTPUT y ELIMINAR aquellos de la tabla temporal. Entonces el INSERT no necesita el
WHERE NOT EXISTS(...)
Si hay filas en los datos entrantes que no deberían sincronizarse (es decir, ni insertarse ni actualizarse), esos registros deberían eliminarse antes de realizar la ACTUALIZACIÓN
He usado este modelo varias veces en Importaciones / ETL que tienen más de 1000 filas o quizás 500 en un lote de un conjunto total de 20k, más de un millón de filas. Sin embargo, no he probado la diferencia de rendimiento entre ELIMINAR las filas actualizadas fuera de la tabla temporal frente a simplemente actualizar el campo [IsUpdate].
Tenga en cuenta la decisión de usar XML sobre TVP debido a que hay, como máximo, 1000 filas para importar a la vez (mencionado en la pregunta):
Si esto se llama varias veces aquí y allá, entonces posiblemente la menor ganancia de rendimiento en TVP podría no valer el costo de mantenimiento adicional (la necesidad de abandonar el proceso antes de cambiar el Tipo de tabla definida por el usuario, los cambios en el código de la aplicación, etc.) . Pero si está importando 4 millones de filas, enviando 1000 a la vez, es decir, 4000 ejecuciones (y 4 millones de filas de XML para analizar, sin importar cómo se divida), e incluso una pequeña diferencia de rendimiento cuando se ejecuta solo unas pocas veces sumar a una diferencia notable.
Dicho esto, el método que he descrito no cambia fuera de reemplazar SELECT FROM @XmlInputParam para que sea SELECT FROM @TVP. Dado que los TVP son de solo lectura, no podrá eliminarlos. Supongo que simplemente podría agregar un
WHERE NOT EXISTS(SELECT * FROM @UpdateIDs ids WHERE ids.IDField = tmp.IDField)
a ese SELECT final (vinculado al INSERT) en lugar del simpleWHERE IsUpdate = 0
. Si usara la@UpdateIDs
variable de tabla de esta manera, incluso podría salirse con la suya sin tirar las filas entrantes en la tabla temporal.fuente