Insertar Actualización de proc almacenado en SQL Server


He escrito un proc almacenado que hará una actualización si existe un registro, de lo contrario hará un insert. Se ve algo como esto:

update myTable set Col1=@col1, Col2=@col2 where ID=@ID
if @@rowcount = 0
insert into myTable (Col1, Col2) values (@col1, @col2)

Mi lógica detrás de escribirlo de esta manera es que la actualización realizará una selección implícita usando la cláusula where y si eso devuelve 0, entonces la inserción tendrá lugar.

La alternativa para hacerlo de esta manera sería hacer una selección y luego, en función del número de filas devueltas, hacer una actualización o insertar. Esto lo consideré ineficiente porque si va a hacer una actualización causará 2 selects (la primera llamada explícita a select y la segunda implícita en el lugar de la actualización). Si el proc hiciera un inserto, entonces no habría diferencia en eficiencia.

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

Author: Brannon, 2008-08-17

9 answers

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

Importancia de UPSERT-de sqlservercentral.com :

Por cada actualización en el caso mencionado anteriormente estamos eliminando una lectura adicional de la tabla si utilice el UPSERT en lugar de EXISTS. Desafortunadamente para un Inserto, tanto el Los métodos UPSERT y IF EXISTS utilizan el el mismo número de lecturas sobre la mesa. Por lo tanto, el control de la existencia deber solo se hará cuando haya un razón muy válida para justificar la E / S adicionales. La forma optimizada de hacer las cosas es asegurarse de que tener pequeñas lecturas como sea posible en el DB.

La mejor estrategia es intentar actualizar. Si no hay filas afectadas por el actualizar y luego insertar. En la mayoría circunstancias, la fila ya existe y solo una E/S será requerir.

Editar: Por favor, echa un vistazo esta respuesta y la entrada de blog vinculada a aprenda sobre los problemas con este patrón y cómo hacer que funcione de manera segura.

 56
Author: binOr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:34:33

Por favor, lea el post en mi blog para un patrón bueno y seguro que puede usar. Hay muchas consideraciones, y la respuesta aceptada a esta pregunta está lejos de ser segura.

Para una respuesta rápida intente el siguiente patrón. Funcionará bien en SQL 2000 y superiores. SQL 2005 le da el 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
 48
Author: Sam Saffron,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-06-06 08:18:37

Si se va a usar con SQL Server 2000/2005, el código original debe estar incluido en la transacción para asegurarse de que los datos permanezcan consistentes en el escenario concurrente.

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

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

Add, como ya se ha sugerido, se debe usar MERGE cuando esté disponible.

 10
Author: Dima Malenko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-08-24 20:21:28

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

 6
Author: Jon Galloway,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-08-17 07:24:48

No solo necesita ejecutarlo en transacción, también necesita un alto nivel de aislamiento. I hecho nivel de aislamiento por defecto se lee Commited y este código necesita Serializable.

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
COMMIT TRANSACTION Upsert

Tal vez agregar también la comprobación de error @@y la reversión podría ser una buena idea.

 6
Author: Tomas Tintera,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-25 10:15:53

Si no está haciendo una fusión en SQL 2008, debe cambiarla a:

Si @ @ rowcount = 0 y @ @ error = 0

De lo contrario, si la actualización falla por alguna razón, intentará insertar después porque el rowcount en una instrucción fallida es 0

 4
Author: Simon Munro,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-02 20:13:39

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

 3
Author: Natron,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-08-29 21:41:33

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

De lo contrario, si siempre está haciendo una inserción si la actualización no afectó a ningún registro, ¿qué sucede cuando alguien elimina el registro antes de que se ejecute "UPSERT"? Ahora el registro que estaba tratando de actualizar no existe, por lo que creará un registro en su lugar. Probablemente ese no es el comportamiento que estabas buscando.

 1
Author: Kevin Fairchild,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-16 20:31:28

Modificado Dima Malenko post:

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 atrapar el error y enviar el registro a una tabla de inserción fallida.
Necesitaba hacer esto porque estamos tomando cualquier dato que se envíe a través de WSDL y si es posible arreglarlo internamente.

 1
Author: thughes78013,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-10-21 07:26:35