2008-08-17 12 views
89

He escrito un proceso almacenado que hará una actualización si existe un registro, de lo contrario, hará una inserción. Se ve algo como esto:Insertar proceso almacenado de actualización en SQL Server

update myTable set [email protected], [email protected] where [email protected] 
if @@rowcount = 0 
insert into myTable (Col1, Col2) values (@col1, @col2) 

Mi lógica detrás de escribir de esta manera es que la actualización se realice una implícita seleccione mediante la cláusula dónde y si devuelve 0, entonces el inserto se llevará a cabo.

La alternativa para hacerlo de esta manera sería hacer una selección y luego, en función del número de filas devueltas, realice una actualización o inserte. Esto lo considero ineficiente porque si va a hacer una actualización, causará 2 selecciones (la primera llamada de selección explícita y la segunda implícita en el lugar de la actualización). Si el proceso fuera a hacer una inserción, entonces no habría diferencia en la eficiencia.

¿Mi lógica suena aquí? ¿Es así como combinaría una inserción y una actualización en un proceso almacenado?

Respuesta

56

Su suposición es correcta, esta es la forma óptima de hacerlo y se llama upsert/merge.

Importance of UPSERT - from sqlservercentral.com:

Por cada actualización en el caso mencionado anteriormente vamos a eliminar uno lectura adicional de la tabla si utilizamos el lugar de UPSERT existe. Desafortunadamente para un inserto, tanto el métodoUPSERT como el IF EXISTS usan el mismo número de lecturas en la tabla . Por lo tanto, la comprobación de existencia solo debe realizarse cuando existe un motivo muy válido para justificar la E/S adicional . La forma optimizada para hacer las cosas es asegurarse de que tenga pocas lecturas como sea posible en el DB.

La mejor estrategia es intentar la actualización . Si ninguna fila se ve afectada por la actualización , inserte. En la mayoría de las circunstancias , la fila ya habrá y solo se requerirá una E/S .

Editar: Por favor, echa un vistazo a this answer y la entrada de blog vinculado a aprender acerca de los problemas con este patrón y cómo hacer que funcione segura.

+1

Bueno, al menos respondió una pregunta, creo. Y no agregué el código porque el código en la pregunta ya me parecía correcto. Aunque lo pondría en una transacción, no tomé en cuenta el nivel de aislamiento para la actualización. ¡Gracias por señalar eso en tu respuesta! – binOr

6

Por cierto, MERGE es una de las características nuevas de SQL Server 2008.

+0

y deberías usarlo en lugar de esta tontería homebrew difícil de leer. Buen ejemplo está aquí - https://www.mssqltips.com/sqlservertip/1704/using-merge-in-sql-server-to-insert-update-and-delete-at-the-same-time/ –

8

Si se va a utilizar con SQL Server 2000/2005, el código original debe incluirse en la transacción para asegurarse de que los datos permanecen constantes en el escenario concurrente.

BEGIN TRANSACTION Upsert 
update myTable set [email protected], [email protected] where [email protected] 
if @@rowcount = 0 
insert into myTable (Col1, Col2) values (@col1, @col2) 
COMMIT TRANSACTION Upsert 

Esto tendrá un costo de rendimiento adicional, pero garantizará la integridad de los datos.

Agregue, como ya se ha sugerido, MERGE debería usarse donde esté disponible.

3

Gran admirador de UPSERT, realmente reduce el código para administrar. Aquí hay otra forma en que lo hago: uno de los parámetros de entrada es ID, si el ID es NULL o 0, usted sabe que es un INSERT, de lo contrario es una actualización.Asume que la aplicación sabe si hay una identificación, por lo que no funcionará en todas las situaciones, pero cortará los ejecutables a la mitad si lo hace.

1

Su lógica parece sólida, pero es posible que desee considerar agregar algún código para evitar la inserción si hubiera pasado una clave primaria específica.

De lo contrario, si siempre está haciendo una inserción si la actualización no afectó ningún registro, ¿qué sucede cuando alguien borra el registro antes de ejecutar "UPSERT"? Ahora el registro que intentabas actualizar no existe, por lo que creará un registro. Ese probablemente no es el comportamiento que estabas buscando.

3

Si usted no está haciendo una mezcla en SQL 2008, debe cambiarlo a:

si @@ recuento de filas = 0 y @@ error = 0

de lo contrario si la actualización falla por alguna razón, entonces lo intentará y luego lo insertará porque el recuento de filas en una declaración fallida es 0

5

No solo necesita ejecutarlo en una transacción, sino que también necesita un alto nivel de aislamiento. De hecho, el nivel de aislamiento predeterminado es Read Commited y este código necesita Serializable.

SET transaction isolation level SERIALIZABLE 
BEGIN TRANSACTION Upsert 
UPDATE myTable set [email protected], [email protected] where [email protected] 
if @@rowcount = 0 
    begin 
    INSERT into myTable (ID, Col1, Col2) values (@ID @col1, @col2) 
    end 
COMMIT TRANSACTION Upsert 

Quizás agregar también la comprobación @@ error y la reversión sean una buena idea.

+0

@Munish Goyal Porque en la base de datos, múltiples comandos y procedimientos se ejecutan en paralelo. Luego, otro subproceso puede insertar una fila justo después de que se ejecutó la actualización y antes de que se ejecute. –

46

Lea el post on my blog para obtener un patrón bueno y seguro que puede usar. Hay muchas consideraciones, y la respuesta aceptada sobre esta cuestión está lejos de ser segura.

Para obtener una respuesta rápida, pruebe el siguiente patrón. Funcionará bien en SQL 2000 y superior. SQL 2005 le proporciona un manejo de errores que abre otras opciones y SQL 2008 le da un comando MERGE.

begin tran 
    update t with (serializable) 
    set hitCount = hitCount + 1 
    where pk = @id 
    if @@rowcount = 0 
    begin 
     insert t (pk, hitCount) 
     values (@id,1) 
    end 
commit tran 
+0

En su publicación de blog concluye con el uso de la sugerencia WITH (updlock, serializable) en la comprobación de presencia. Sin embargo, leer MSDN es el siguiente: "UPDLOCK: especifica que los bloqueos de actualización deben tomarse y retenerse hasta que finalice la transacción". ¿Esto significa que la pista serializable es superflua ya que el bloqueo de actualización se mantendrá para el resto de la transacción de todos modos, o he entendido mal algo? –

1

Modificado posterior Dima Malenko:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE 

BEGIN TRANSACTION UPSERT 

UPDATE MYTABLE 
SET COL1 = @col1, 
     COL2 = @col2 
WHERE ID = @ID 

IF @@rowcount = 0 
    BEGIN 
     INSERT INTO MYTABLE 
        (ID, 
        COL1, 
        COL2) 
     VALUES  (@ID, 
        @col1, 
        @col2) 
    END 

IF @@Error > 0 
    BEGIN 
     INSERT INTO MYERRORTABLE 
        (ID, 
        COL1, 
        COL2) 
     VALUES  (@ID, 
        @col1, 
        @col2) 
    END 

COMMIT TRANSACTION UPSERT 

Puede interceptar el error y enviar el registro a una tabla de inserción fallado.
Necesito hacer esto porque estamos tomando cualquier información que se envíe a través de WSDL y, si es posible, solucionándola internamente.

Cuestiones relacionadas