Soluciones para actualizar un registro si existe, sino insertar en SQL Server.
Filed under: SQL Server, T-SQL
grimpi @ 2:06 am Muchas veces cuando trabajamos con ABMs o algn proceso de escritura en la base de datos, al actualizar los registros, debemos establecer si vamos a efectuar un I NSERT o un UPDATE. O sea, tenemos que determinar si el registro existe o no, par a saber que operacin se va a efectuar en la base de datos. Generalmente se suele encapsular toda esta lgica dentro de un SP, algo que consid ero una muy buena practica, ya que nos desentendemos del lado de la aplicacin, si se va a efectuar una operacin de insercin o de modificacin. Primera solucin: Ahora bien, dentro del Store Procedure, lo solemos hacer para determinar la oper acin, es el famoso IF EXISTS. Ejemplo: IF EXISTS(SELECT ID FROM TABLA WHERE ID = @ID) INSERT INTO TABLA (Campo1,ID) VALUES (@Valor,@ID) ELSE UPDATE TABLA SET Campo1 = @Valor WHERE ID = @ID No es un mal enfoque, es muy claro. Sin embargo esta solucin tiene dos inconvenie ntes: 1) Estamos pagando el costo de ejecutar un Query. Por mas que la consulta este i ndexada, tiene un costo. 2) No es 100% segura. En entornos muy demandantes, con alta concurrencia, puede darse el caso de que justo luego de ejecutar el IF EXISTS, otro proceso inserte en la tabla un registro con la misma PK y no tendramos forma de darnos cuenta, ge nerando un error de duplicate key. Por lo tanto, esta opcin que es la ms comn, tiene serios inconvenientes. Segunda solucin: Una segunda opcin podra ser esta: UPDATE TABLA SET Campo1 = @Valor WHERE ID = @ID IF @@ROWCOUNT = 0 INSERT INTO TABLA (Campo1,ID) VALUES (@Valor,@ID) En caso, se eliminara el tener que ejecutar una query con el EXISTS. Aunque en ca so de que no exista el registro, se ejecuta el UPDATE innecesariamente. Si la mayora de las operaciones van a ser del tipo INSERT, en realidad no se gana ra performance, pero por el contrario, si la mayora de las operaciones seria del t ipo UPDATE, podra llegar a ser mas performante. De todas maneras esta solucin sigue teniendo el problema de que otro proceso podra insertar un registro con la misma PK en la tabla y no tendramos forma de darnos cuenta. Tercera solucin: En el segundo caso ganamos un poco de performance (no siempre), pero seguimos co n el mismo problema de concurrencia. Pero ahora veamos este ejemplo de cdigo: BEGIN TRY INSERT INTO TABLA (Campo1,ID) VALUES (@Valor,@ID) END TRY BEGIN CATCH
UPDATE TABLA SET Campo1 = @Valor WHERE ID = @ID
END CATCH A nivel perfomance, es similar a las otras soluciones, pero si tenemos muchas ms operaciones de insercin que de actualizacin, vamos a ganar velocidad. Sin embargo, en este caso no tendramos el inconveniente de concurrencia que suced e en los 2 casos anteriores!!. Lo cual lo hace ideal para situaciones de alta de manda. Cuarta solucin (Solo en SQL Server 2008): SQL Server 2008, incorpora el comando MERGE (que ya tenamos en Oracle y otros mot ores), que sirve para resolver de una manera muy eficiente, exactamente este pro blema. MERGE TABLA USING (SELECT @ID AS ID) AS SRC ON SRC.ID = TABLA.ID WHEN MATCHED THEN UPDATE SET Campo1 = @Valor WHEN NOT MATCHED THEN INSERT (Campo1,ID) VALUES (@Valor,@ID) Con este mtodo, tambin solucionamos el problema de concurrencia, y adems evitar ten er que ejecutar consultas innecesarias. Por lo cual, podramos decir que es la opt ima solucin resolver este problema, aunque lamentablemente debemos esperar hasta mitad de ao, cuando Microsoft libere SQL Server 2008. Conclusin: Vimos como un problema en apariencia tonto y trivial, puede causar serios proble mas de performance y peor aun, crear errores de concurrencia y comportamientos n o deseados. Por las pruebas que hicimos en un entorno de TEST, la diferencia de performance que hicimos no son demasiadas. Pero en situaciones de alta concurrencia, las 2 p rimeras soluciones son definitivamente incorrectas. Recomiendo ver estos links, que explican como funcionan los lockeos, en cada una de las distintas soluciones: http://weblogs.sqlteam.com/mladenp/archive/2007/07/30/60273.aspx http://weblogs.sqlteam.com/mladenp/archive/2007/08/03/60277.aspx Y estas soluciones alternativas al mismo problema, tal vez sean un poco ms comple jas, pero en algunos escenarios pueden ser tiles: http://www.samsaffron.com/blog/archive/2007/04/04/14.aspx http://www.sqlteam.com/article/application-locks-or-mutexes-in-sql-server-2005