¡Hola, chicos!
En esta publicación, me gustaría demostrarle cómo usar el comando FUSIONAR, disponible desde SQL Server 2008, para realizar comandos INSERTAR y ACTUALIZAR entre dos tablas en una sola declaración.
Para los ejemplos de esta publicación, usaré una base que crearé usando el siguiente script:
IF (OBJECT_ID('dbo.Venda') IS NOT NULL) DROP TABLE dbo.Venda
CREATE TABLE dbo.Venda (
Id_Venda INT IDENTITY(1,1) NOT NULL,
Dt_Venda DATE NOT NULL,
Id_Produto INT NOT NULL,
Quantidade INT NOT NULL,
Valor NUMERIC(9, 2) NOT NULL
)
INSERT INTO dbo.Venda
(
Dt_Venda,
Id_Produto,
Quantidade,
Valor
)
VALUES ( '2018-09-21', 1, 2, 25.99 ), ( '2018-09-21', 2, 3, 29.99 ), ( '2018-09-21', 1, 1, 15.99 ),
( '2018-09-22', 1, 2, 25.99 ), ( '2018-09-22', 2, 1, 10.00 ), ( '2018-09-22', 9, 1, 35.99 ), ( '2018-09-22', 5, 3, 20.00 ),
( '2018-09-22', 3, 2, 25.87 ), ( '2018-09-22', 2, 1, 11.25 ), ( '2018-09-22', 1, 1, 21.90 ), ( '2018-09-22', 4, 3, 29.99 )
IF (OBJECT_ID('dbo.Dim_Venda') IS NOT NULL) DROP TABLE dbo.Dim_Venda
CREATE TABLE dbo.Dim_Venda (
Id_Venda INT NOT NULL,
Dt_Venda DATE NOT NULL,
Id_Produto INT NOT NULL,
Quantidade INT NOT NULL,
Valor NUMERIC(9, 2) NOT NULL
)
INSERT INTO dbo.Dim_Venda
(
Id_Venda,
Dt_Venda,
Id_Produto,
Quantidade,
Valor
)
SELECT
Id_Venda,
Dt_Venda,
Id_Produto,
Quantidade,
Valor
FROM
dbo.Venda
WHERE
Dt_Venda = '2018-09-21'
Y nuestras tablas de origen y destino se ven así:

Introducción
Una actividad muy común en el día a día de los profesionales que trabajan con datos, especialmente aquellos que trabajan con ETL, la tarea de actualizar una tabla a partir de datos de otra tabla, suele acabar generando 2 operaciones distintas para actualizar los datos:
Método 1: Insertar/Actualizar
- Actividad 1: Actualizar los registros que existen en ambas tablas
- Actividad 2: Insertar registros que solo existen en la tabla fuente
Veamos cómo haríamos esto:
-- Atividade 1: Atualizar os registros que existem nas duas tabelas
UPDATE A
SET
A.Dt_Venda = B.Dt_Venda,
A.Id_Produto = B.Id_Produto,
A.Quantidade = B.Quantidade,
A.Valor = B.Valor
FROM
dbo.Dim_Venda A
JOIN dbo.Venda B ON A.Id_Produto = B.Id_Venda
-- Atividade 2: Inserir os registros que só existem na tabela de origem
INSERT INTO dbo.Dim_Venda
(
Id_Venda,
Dt_Venda,
Id_Produto,
Quantidade,
Valor
)
SELECT
A.Id_Venda,
A.Dt_Venda,
A.Id_Produto,
A.Quantidade,
A.Valor
FROM
dbo.Venda A
LEFT JOIN dbo.Dim_Venda B ON A.Id_Venda = B.Id_Venda
WHERE
B.Id_Venda IS NULL
SELECT * FROM dbo.Dim_Venda
SELECT * FROM dbo.Venda
¿Y cómo quedaron los datos de la tabla?

Método 2: Eliminar/Insertar
- Actividad 1: Elimina los registros que existen en ambas tablas
- Actividad 2: Insertar registros de la tabla fuente
Que podemos entender cómo funciona a través del siguiente script:
-- Atividade 1: Apaga os registros que existem nas duas tabelas
DELETE A
FROM
dbo.Dim_Venda A
JOIN dbo.Venda B ON A.Id_Produto = B.Id_Venda
-- Atividade 2: Inserir os registros que só existem na tabela de origem
INSERT INTO dbo.Dim_Venda
(
Id_Venda,
Dt_Venda,
Id_Produto,
Quantidade,
Valor
)
SELECT
A.Id_Venda,
A.Dt_Venda,
A.Id_Produto,
A.Quantidade,
A.Valor
FROM
dbo.Venda A
LEFT JOIN dbo.Dim_Venda B ON A.Id_Venda = B.Id_Venda
WHERE
B.Id_Venda IS NULL
SELECT * FROM dbo.Dim_Venda
SELECT * FROM dbo.Venda
Usando el comando FUSIONAR para INSERTAR + ACTUALIZAR + ELIMINAR
Hasta ahora no he presentado nada nuevo, ¿verdad? Los comandos BORRAR/INSERT y ACTUALIZAR/INSERT son muy comunes en la vida diaria de los profesionales de datos.
Sin embargo, para solucionar este problema, a partir de SQL Server 2008 podemos utilizar el comando MERGE, que nos permite realizar esta misma actividad en tan solo 1 declaración. Su funcionamiento es sencillo: Una o más columnas de las tablas involucradas se consideran claves (identificadores) por lo que si el valor de la clave existe en la tabla de destino, los valores se actualizarán según la tabla de origen. Si este identificador no existe, este registro se insertará en la tabla de destino.
Su sintaxis funciona así:
MERGE
dbo.Dim_Venda AS Destino
USING
dbo.Venda AS Origem ON (Origem.Id_Venda = Destino.Id_Venda)
-- Registro existe nas 2 tabelas
WHEN MATCHED THEN
UPDATE SET
Destino.Dt_Venda = Origem.Dt_Venda,
Destino.Id_Produto = Origem.Id_Produto,
Destino.Quantidade = Origem.Quantidade,
Destino.Valor = Origem.Valor
-- Registro não existe no destino. Vamos inserir.
WHEN NOT MATCHED THEN
INSERT
VALUES(Origem.Id_Venda, Origem.Dt_Venda, Origem.Id_Produto, Origem.Quantidade, Origem.Valor);
Controlar la cláusula NOT MATCHED en el origen y el destino
También podemos controlar cuando un registro no existe en destino o en origen, y tomar decisiones diferentes para estas 2 situaciones:
MERGE
dbo.Dim_Venda AS Destino
USING
dbo.Venda AS Origem ON (Origem.Id_Venda = Destino.Id_Venda)
-- Registro existe nas 2 tabelas
WHEN MATCHED THEN
UPDATE SET
Destino.Dt_Venda = Origem.Dt_Venda,
Destino.Id_Produto = Origem.Id_Produto,
Destino.Quantidade = Origem.Quantidade,
Destino.Valor = Origem.Valor
-- Registro não existe na origem, apenas no destino. Vamos apagar o dado da origem.
WHEN NOT MATCHED BY SOURCE THEN
DELETE
-- Registro não existe no destino. Vamos inserir.
WHEN NOT MATCHED BY TARGET THEN
INSERT
VALUES(Origem.Id_Venda, Origem.Dt_Venda, Origem.Id_Produto, Origem.Quantidade, Origem.Valor);
Actualización de valores específicos, a través de filtros específicos
Puede ser necesario actualizar sólo la cantidad y el valor, y en los casos en que sean diferentes:
MERGE
dbo.Dim_Venda AS Destino
USING
dbo.Venda AS Origem ON (Origem.Id_Venda = Destino.Id_Venda)
-- Registro existe nas 2 tabelas
WHEN MATCHED AND (Origem.Quantidade <> Destino.Quantidade OR Origem.Valor <> Destino.Valor) THEN
UPDATE SET
Destino.Quantidade = Origem.Quantidade,
Destino.Valor = Origem.Valor
-- Registro não existe no destino. Vamos inserir.
WHEN NOT MATCHED THEN
INSERT
VALUES(Origem.Id_Venda, Origem.Dt_Venda, Origem.Id_Produto, Origem.Quantidade, Origem.Valor);
Mostrando una salida con todos los cambios realizados por MERGE
¿Qué pasa si queremos recopilar los resultados de MERGE y mostrar las operaciones realizadas en la pantalla? ¡Solo usa SALIDA!
MERGE
dbo.Dim_Venda AS Destino
USING
dbo.Venda AS Origem ON (Origem.Id_Venda = Destino.Id_Venda)
-- Registro existe nas 2 tabelas
WHEN MATCHED THEN
UPDATE SET
Destino.Dt_Venda = Origem.Dt_Venda,
Destino.Id_Produto = Origem.Id_Produto,
Destino.Quantidade = Origem.Quantidade,
Destino.Valor = Origem.Valor
-- Registro não existe na origem, apenas no destino. Vamos apagar o dado da origem.
WHEN NOT MATCHED BY SOURCE THEN
DELETE
-- Registro não existe no destino. Vamos inserir.
WHEN NOT MATCHED BY TARGET THEN
INSERT
VALUES(Origem.Id_Venda, Origem.Dt_Venda, Origem.Id_Produto, Origem.Quantidade, Origem.Valor)
OUTPUT $action,
Deleted.Id_Venda,
Deleted.Dt_Venda,
Deleted.Id_Produto,
Deleted.Quantidade,
Deleted.Valor,
Inserted.Id_Venda,
Inserted.Dt_Venda,
Inserted.Id_Produto,
Inserted.Quantidade,
Inserted.Valor;
Almacenamiento de la salida MERGE en una tabla
¿Le gustaría guardar los registros en una tabla para consultar los datos?
CREATE TABLE dbo.Dim_Venda_Log
(
[Id_Log] BIGINT IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
[Acao] NVARCHAR(10),
[Inserted_Id_Venda] INT,
[Inserted_Dt_Venda] DATE,
[Inserted_Id_Produto] INT,
[Inserted_Quantidade] INT,
[Inserted_Valor] DECIMAL(9, 2),
[Deleted_Id_Venda] INT,
[Deleted_Dt_Venda] DATE,
[Deleted_Id_Produto] INT,
[Deleted_Quantidade] INT,
[Deleted_Valor] DECIMAL(9, 2)
) WITH(DATA_COMPRESSION=PAGE)
MERGE
dbo.Dim_Venda AS Destino
USING
dbo.Venda AS Origem ON (Origem.Id_Venda = Destino.Id_Venda)
-- Registro existe nas 2 tabelas
WHEN MATCHED THEN
UPDATE SET
Destino.Dt_Venda = Origem.Dt_Venda,
Destino.Id_Produto = Origem.Id_Produto,
Destino.Quantidade = Origem.Quantidade,
Destino.Valor = Origem.Valor
-- Registro não existe na origem, apenas no destino. Vamos apagar o dado da origem.
WHEN NOT MATCHED BY SOURCE THEN
DELETE
-- Registro não existe no destino. Vamos inserir.
WHEN NOT MATCHED BY TARGET THEN
INSERT
VALUES(Origem.Id_Venda, Origem.Dt_Venda, Origem.Id_Produto, Origem.Quantidade, Origem.Valor)
OUTPUT
$action,
Inserted.*,
Deleted.*
INTO dbo.Dim_Venda_Log;
SELECT * FROM dbo.Dim_Venda_Log
Usar consultas como datos de origen
MERGE también nos permite unir datos de consultas en lugar de tablas fijas, y usar JOINS, si es necesario.
MERGE
dbo.Dim_Venda AS Destino
USING
(SELECT Id_Venda, Dt_Venda, Id_Produto, Quantidade, Valor FROM dbo.Venda WHERE Dt_Venda = '2018-09-22') AS Origem ON (Origem.Id_Venda = Destino.Id_Venda)
-- Registro existe nas 2 tabelas
WHEN MATCHED THEN
UPDATE SET
Destino.Dt_Venda = Origem.Dt_Venda,
Destino.Id_Produto = Origem.Id_Produto,
Destino.Quantidade = Origem.Quantidade,
Destino.Valor = Origem.Valor
-- Registro não existe na origem, apenas no destino. Vamos apagar o dado da origem.
WHEN NOT MATCHED BY SOURCE THEN
DELETE
-- Registro não existe no destino. Vamos inserir.
WHEN NOT MATCHED BY TARGET THEN
INSERT
VALUES(Origem.Id_Venda, Origem.Dt_Venda, Origem.Id_Produto, Origem.Quantidade, Origem.Valor)
OUTPUT
$action,
Inserted.*,
Deleted.*;
Resultado: vea que, como solo incorporé los datos del 22/09 a MERGE, los datos del 21/09 que se cargaron manualmente antes de MERGE se eliminaron de la tabla de destino, ya que no existían en la fuente.

Usando CTE con JOIN para generar datos desde la fuente
Merge también le permite usar CTE para generar el conjunto de resultados de la fuente de datos y usar JOINS, si es necesario.
;WITH cteVenda AS (
SELECT
A.Id_Venda,
A.Dt_Venda,
A.Id_Produto,
A.Quantidade,
A.Valor,
B.Id_Erro,
B.Dt_Erro,
B.Ds_Erro
FROM
dbo.Venda A
LEFT JOIN Consultoria.dbo.Log_Erro B ON A.Id_Produto = B.Id_Erro
WHERE
A.Dt_Venda = '2018-09-21'
)
MERGE
dbo.Dim_Venda AS Destino
USING
cteVenda AS Origem ON (Origem.Id_Venda = Destino.Id_Venda)
-- Registro existe nas 2 tabelas
WHEN MATCHED THEN
UPDATE SET
Destino.Dt_Venda = Origem.Dt_Venda,
Destino.Id_Produto = Origem.Id_Produto,
Destino.Quantidade = Origem.Quantidade,
Destino.Valor = Origem.Valor
-- Registro não existe na origem, apenas no destino. Vamos apagar o dado da origem.
WHEN NOT MATCHED BY SOURCE THEN
DELETE
-- Registro não existe no destino. Vamos inserir.
WHEN NOT MATCHED BY TARGET THEN
INSERT
VALUES(Origem.Id_Venda, Origem.Dt_Venda, Origem.Id_Produto, Origem.Quantidade, Origem.Valor)
OUTPUT
$action,
Inserted.*,
Deleted.*;
Precauciones al utilizar MERGE
Ahora que he demostrado varias formas de usar MERGE en SQL Server, me gustaría advertirle sobre la existencia de algunos errores al usar MERGE en SQL Server 2008, que van desde resultados incorrectos al usar OUTPUT hasta errores graves como el que se muestra a continuación:
Mensaje 0, Nivel 11, Estado 0, Línea 0
Se produjo un error grave en el comando actual. Los resultados, si los hubiera, deben descartarse.
Mensaje 0, Nivel 20, Estado 0, Línea 0
Se produjo un error grave en el comando actual. Los resultados, si los hubiera, deben descartarse.
Si está utilizando la versión 2008 (AÚN), actualice su SQL con la última versión de SP y Cumulative Update disponible y lea atentamente los artículos que pondré a disposición a continuación, ya que tienen varios escenarios que pueden causar dolores de cabeza al usar MERGE en SQL Server 2008 (en las versiones 2012+ estos errores ya no existen):
- http://sqlblog.com/blogs/paul_white/archive/2010/08/04/another-interesting-merge-bug.aspx
- http://andreyzavadskiy.com/2015/07/23/sql-server-2008r2-causes-severe-error-in-merge-output/
- https://www.mssqltips.com/sqlservertip/3074/use-caution-with-sql-servers-merge-statement/
Usando el procedimiento almacenado stpETL_Upsert
Para facilitar el uso de MERGE, especialmente en tablas con muchas columnas, compartiré con ustedes el Procedimiento Almacenado stpETL_Upsert, creado originalmente por Murilo Mielke y adaptado por mí, que tiene como objetivo facilitar el uso de MERGE en situaciones cotidianas.
Parámetros del SP:
- @Nm_Fuente: Parámetro de tipo VARCHAR(MAX), contiene el nombre de la tabla fuente de datos (fuente). El nombre de la tabla debe ser específico en formato completo (base de datos.esquema.tabla)
- @Nm_Target: Parámetro de tipo VARCHAR(MAX), contiene el nombre de la tabla de destino de los datos (target), es decir, donde se insertarán/actualizarán los datos. El nombre de la tabla debe ser específico en formato completo (base de datos.esquema.tabla)
- @Cd_Join: Parámetro de tipo VARCHAR(MAX), se debe informar la columna o columnas del JOIN para vincular los datos de las tablas origen y destino. Para un uso simplificado, puede especificar solo los nombres de las columnas, si son iguales entre el origen y el destino, separando con una coma “,” si hay más de una columna para realizar el JOIN. Si el nombre de las columnas entre origen y destino son diferentes, se debe utilizar el formato nombre_columna_origen:nombre_columna_destino, utilizando también el separador de coma “,” en caso de varias columnas.
- @Cd_Chave: Tipo de parámetro VARCHAR(MAX), se debe informar el nombre de la columna secuencial (IDENTITY) de la tabla de destino, para evitar que el SP intente realizar un UPDATE o INSERT usando esa columna. Si no hay una columna IDENTIDAD en el destino, puede ignorar este parámetro, ya que su valor predeterminado ya está vacío.
- @Fl_Actualización: Parámetro de tipo BIT, con valor predeterminado = 1, esta columna informa al SP si los registros en la tabla de destino (destino) deben actualizarse si existen en el origen y el destino (@Fl_Update = 1) o no.
- @Fl_Debug: Parámetro de tipo BIT que hace que el SP muestre el comando MERGE generado al final de su ejecución en la ventana de salida
Ejemplo 1: uso sencillo
Utilizando el mismo escenario que los ejemplos de este artículo, usaremos stpETL_Upset para realizar nuestra FUSIÓN:
SELECT * FROM dbo.Dim_Venda
EXEC dbo.stpETL_Upsert
@Nm_Source = 'dirceuresende.dbo.Venda', -- varchar(max)
@Nm_Target = 'dirceuresende.dbo.Dim_Venda', -- varchar(max)
@Cd_Join = 'Id_Venda', -- varchar(max)
@Cd_Chave = '', -- varchar(max)
@Fl_Update = 1, -- bit
@Fl_Debug = 1 -- bit
SELECT * FROM dbo.Dim_Venda
Ejemplo 2: uso de varias columnas para JOIN
Si necesita utilizar varias columnas para UNIR los datos, simplemente sepárelas con una coma “,” en la llamada al SP:
EXEC dbo.stpETL_Upsert
@Nm_Source = 'dirceuresende.dbo.Venda', -- varchar(max)
@Nm_Target = 'dirceuresende.dbo.Dim_Venda', -- varchar(max)
@Cd_Join = 'Id_Venda, Id_Produto, Dt_Venda', -- varchar(max)
@Cd_Chave = '', -- varchar(max)
@Fl_Update = 1, -- bit
@Fl_Debug = 1 -- bit
Ejemplo 3: uso de JOINS con diferentes columnas
Si las columnas JOIN no tienen exactamente el mismo nombre o desea usar más de una cláusula JOIN, puede usar SP como se muestra a continuación, usando la sintaxis nombre_coluna_table_origin:nombre_coluna_table_destination, y también puede usar el carácter de coma “,” para usar múltiples asignaciones.
En el siguiente ejemplo, la tabla de origen “Venda” tiene un campo llamado Id_Venda, que realizará el JOIN con la columna Cod_Venda en la tabla de destino “Dim_Venda”.
EXEC dbo.stpETL_Upsert
@Nm_Source = 'dirceuresende.dbo.Venda', -- varchar(max)
@Nm_Target = 'dirceuresende.dbo.Dim_Venda', -- varchar(max)
@Cd_Join = 'Id_Venda:Cod_Venda', -- varchar(max)
@Cd_Chave = '', -- varchar(max)
@Fl_Update = 1, -- bit
@Fl_Debug = 1 -- bit
código fuente
¿Te gustó stpETL_Upset? Bastante práctico, ¿verdad? Haré que el código fuente de este Procedimiento almacenado esté disponible a continuación para que pueda comenzar a usarlo en su vida diaria. No olvides crear los objetos fncSplit y fncSplitTexto que están en los requisitos, ¿eh?, de lo contrario será error y me quejaras en los comentarios.. jajaja
Ver el código fuente de stpETL_Upsert
Requisitos:
Referencias
- https://docs.microsoft.com/pt-br/sql/t-sql/statements/merge-transact-sql?view=sql-server-2017
- https://www.mssqltips.com/sqlservertip/1704/using-merge-in-sql-server-to-insert-update-and-delete-at-the-same-time/
Bueno chicos!
Espero que hayas disfrutado de esta publicación y nos vemos la próxima.
¡Gran abrazo!








Comentários (0)
Carregando comentários…