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'
Introdução
Método 1: Insert/Update
-- 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
Método 2: Delete/Insert
-- 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
Utilizando o comando MERGE para INSERT + UPDATE + DELETE
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);
Controlando a cláusula NOT MATCHED na Origem e Destino
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);
Atualizando valores específicos, através de filtros específicos
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);
Exibindo uma saída com todas as alterações realizadas pelo MERGE
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;
Armazenando a saída do MERGE em uma tabela
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
Utilizando consultas como dados de origem
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.*;
Utilizando CTE com JOIN para gerar dados da origem
;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.*;
Cuidados ao utilizar o MERGE
Utilizando a Stored Procedure stpETL_Upsert
Exemplo 1 – Uso simples
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
Exemplo 2 – Utilizando várias colunas para o JOIN
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
Exemplo 3 – Utilizando JOINS com colunas diferentes
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-fonte
CREATE PROCEDURE [dbo].[stpETL_Upsert] (
@Nm_Source VARCHAR(MAX),
@Nm_Target VARCHAR(MAX),
@Cd_Join VARCHAR(MAX),
@Cd_Chave VARCHAR(MAX) = '',
@Fl_Update BIT = 1,
@Fl_Debug BIT = 0
)
AS
BEGIN
-- DECLARE @Nm_Source varchar(max) = 'dirceuresende.dbo.Venda', @Nm_Target varchar(max) = 'dirceuresende.dbo.Dim_Venda', @Cd_Join varchar(max) = 'Id_Venda2:Cod_Venda, Dt_Venda:Data', @Cd_Chave VARCHAR(MAX) = '', @Fl_Update bit = 1, @Fl_Debug bit = 0
SET NOCOUNT ON
IF (OBJECT_ID(@Nm_Source) IS NULL)
RAISERROR('A tabela de ORIGEM não existe!', 16, 1) WITH NOWAIT
IF (OBJECT_ID(@Nm_Target) IS NULL)
RAISERROR('A tabela de DESTINO não existe!', 16, 1) WITH NOWAIT
DECLARE
@Source_Banco varchar(max) = dbo.fncSplit(@Nm_Source,'.',1), -- https://www.dirceuresende.com/blog/quebrando-strings-em-sub-strings-utilizando-separador-no-sql-server/
@Source_Schema varchar(max) = dbo.fncSplit(@Nm_Source,'.',2),
@Source_Tabela varchar(max) = dbo.fncSplit(@Nm_Source,'.',3),
@Target_Banco varchar(max) = dbo.fncSplit(@Nm_Target,'.',1),
@Target_Schema varchar(max) = dbo.fncSplit(@Nm_Target,'.',2),
@Target_Tabela varchar(max) = dbo.fncSplit(@Nm_Target,'.',3),
@cmdJoin varchar(max) = '',
@cmdInsert varchar(max) = '',
@cmdValues varchar(max) = '',
@cmdUpdate VARCHAR(MAX) = '',
@matched varchar(MAX) = '',
@usingFrom varchar(MAX) = @Nm_Source,
@cmdMerge varchar(MAX) = ''
DECLARE @ColunasSource AS TABLE (
Nm_Coluna VARCHAR(MAX)
)
DECLARE @ColunasTarget AS TABLE (
Nm_Coluna VARCHAR(MAX)
)
DECLARE @Joins as TABLE (
Id_Join int identity(1,1),
Pt_Source varchar(max),
Fl_IsNull_Source int,
Pt_Target varchar(max),
Fl_IsNull_Target int
)
INSERT INTO @ColunasSource EXEC('SELECT name Nm_Coluna FROM ' + @Source_Banco + '..syscolumns WHERE id = OBJECT_ID(''' + @Nm_Source + ''')')
INSERT INTO @ColunasTarget EXEC('SELECT name Nm_Coluna FROM ' + @Target_Banco + '..syscolumns WHERE id = OBJECT_ID(''' + @Nm_Target + ''')')
INSERT INTO @Joins (Pt_Source)
SELECT Palavra
FROM dbo.fncSplitTexto(@Cd_Join,',') -- https://www.dirceuresende.com/blog/como-quebrar-uma-string-tabela-substrings-utilizando-delimitador-sql-server
UPDATE @Joins
SET
Pt_Source = dbo.fncSplit(Pt_Source,':',1),
Pt_Target = dbo.fncSplit(Pt_Source,':',2)
UPDATE @Joins
SET
Pt_Target = Pt_Source
WHERE
Pt_Target IS NULL
UPDATE @Joins
SET
Fl_IsNull_Source = (CASE WHEN RIGHT(Pt_Source,1) = '*' THEN 1 ELSE 0 END),
Fl_IsNull_Target = (CASE WHEN RIGHT(Pt_Target,1) = '*' THEN 1 ELSE 0 END)
UPDATE @Joins
SET
Pt_Source = REPLACE(Pt_Source,'*',''),
Pt_Target = REPLACE(Pt_Target,'*','')
SELECT
@cmdJoin += '
(SOURCE.[' + Pt_Source +'] = TARGET.[' + Pt_Target + ']' + (CASE WHEN Fl_IsNull_Source = 1 THEN ' OR (SOURCE.[' + Pt_Source + '] IS NULL AND TARGET.[' + Pt_Target + '] IS NULL)' ELSE '' END) + ')'
+ ' AND '
FROM
@Joins
SET @cmdJoin = SUBSTRING(@cmdJoin, 0, LEN(@cmdJoin) - 3)
SELECT
@cmdInsert += '[' + A.Nm_Coluna + '], ',
@cmdValues += 'SOURCE.[' + A.Nm_Coluna + '], '
FROM
@ColunasSource A
JOIN @ColunasTarget B ON A.Nm_Coluna = B.Nm_Coluna
WHERE
A.Nm_Coluna <> @Cd_Chave
SELECT
@cmdInsert += + '[' + Pt_Target + '], ',
@cmdValues += 'SOURCE.[' + Pt_Source + '], '
FROM
@Joins
WHERE
Pt_Source <> Pt_Target
AND Pt_Source <> @Cd_Chave
SELECT
@cmdInsert = SUBSTRING(@cmdInsert, 0, LEN(@cmdInsert)),
@cmdValues = SUBSTRING(@cmdValues, 0, LEN(@cmdValues))
IF (@Fl_Update = 1)
BEGIN
SELECT
@cmdUpdate += '[' + A.Nm_Coluna + '] = SOURCE.[' + B.Nm_Coluna + '], ' + CHAR(10) + CHAR(9) + CHAR(9)
FROM
@ColunasSource A
JOIN @ColunasTarget B ON A.Nm_Coluna = B.Nm_Coluna
WHERE
A.Nm_Coluna NOT IN (SELECT Pt_Source FROM @Joins)
AND B.Nm_Coluna NOT IN (SELECT Pt_Target FROM @Joins)
AND A.Nm_Coluna <> @Cd_Chave
SET @cmdUpdate = SUBSTRING(@cmdUpdate, 0, LEN(@cmdUpdate) - 4)
END
IF (@cmdUpdate <> '') SET @matched = 'WHEN MATCHED THEN UPDATE SET ' + @cmdUpdate
IF (@Source_Banco = 'TempDB') SET @usingFrom = @Source_Tabela
SET @cmdMerge = '
MERGE ' + @Nm_Target + ' as TARGET
USING (SELECT * FROM ' + @usingFrom + ') AS SOURCE ON (
' + @cmdJoin + '
)
' + @matched + '
WHEN NOT MATCHED THEN
INSERT (' + @cmdInsert + ')
VALUES (' + @cmdValues + ');
'
IF (@Fl_Debug = 1)
PRINT @cmdMerge
BEGIN TRY
EXEC(@cmdMerge)
END TRY
BEGIN CATCH
DECLARE @Erro varchar(max) = ERROR_MESSAGE()
PRINT @cmdMerge
RAISERROR(@Erro,16,1)
END CATCH
END
GO