INTRODUCCIÓN
La instrucción MERGE de SQL Server es una herramienta increíblemente útil para realizar operaciones DML basadas en la comparación de dos tablas o dos conjuntos de datos. El uso de esta declaración es en realidad como realizar múltiples operaciones en una sola declaración.
Este artículo explorará tres casos de uso que limitan con garantizar que los datos entre una tabla en línea y una tabla de historial estén sincronizados.
Describamos el escenario en algunas declaraciones resumidas que serían familiares para muchos administradores de bases de datos o desarrolladores:
- La tabla de origen llamada Tran captura las transacciones que suceden en tiempo real de forma continua.
- El período de retención acordado para la tabla Tran es de un mes. Tran debe purgarse al final de cada mes.
- Diariamente, los datos de Tran deben enviarse a TranHistory durante el proceso de "Fin del día".
- La TranHistoria table es una tabla histórica que agrega datos de transacciones durante un año.
- Todas las inserciones y actualizaciones en el Trans la tabla debe reflejarse en el TranHistory mesa al final de cada día.
PREPARANDO EL ESCENARIO
El escenario descrito anteriormente implica que los registros en la tabla Tran también existen en la tabla TranHistory hasta que se purgan mensualmente. Cada día, habrá algunos registros nuevos en la tabla Tran pero NO en la tabla TranHistory. Debemos encontrar una manera de insertar estas nuevas filas.
Primero, preparemos las tablas en cuestión (Ver Listado 1 y 2).
-- Listing 1: Create Tran Table
USE AU
GO
CREATE TABLE [Tran] (
responseId int NOT NULL ,
senderId varchar(15) ,
msisdn varchar(15) ,
[message] varbinary ,
status smallint ,
application varchar ,
receivedTime timestamp NULL ,
processedTime datetime2 NULL ,
flag smallint ,
requestDelivery smallint ,
delivered smallint ,
account varchar(20) ,
srcTon smallint ,
srcNpi smallint ,
destTon smallint ,
destNpi smallint ,
errorCode smallint ,
messageId varchar ,
sequenceNo int ,
retries smallint ,
messagePriority int ,
userId varchar(20) ,
bulkId varchar(20)
)
-- Listing 2: Create TranHistory Table
USE AU
GO
CREATE TABLE [TranHistory] (
responseId int NOT NULL ,
senderId varchar(15) ,
msisdn varchar(15) ,
[message] varchar(160) ,
status smallint ,
application varchar ,
receivedTime datetime2 NULL ,
processedTime datetime2 NULL ,
flag smallint ,
requestDelivery smallint ,
delivered smallint ,
account varchar(20) ,
srcTon smallint ,
srcNpi smallint ,
destTon smallint ,
destNpi smallint ,
errorCode smallint ,
messageId varchar ,
sequenceNo int ,
retries smallint ,
messagePriority int ,
userId varchar(20) ,
bulkId varchar(20) ,
archivedTime datetime2 NOT NULL ,
)
CUANDO NO COINCIDE ENTONCES INSERTAR
Utilizando el código del Listado 3, insertamos algunas filas en la tabla Tran para comenzar. Luego, procedemos a usar una declaración MERGE para mover los datos a la tabla TranHistory.
-- Listing 3: Insert Initial Set of Rows in Tran Table
USE [AU]
GO
INSERT INTO [dbo].[Tran]
VALUES
(8000,'0233456789','Wishing you a Happy New Year',1,'K','20201110 15:00:00','20201110 15:10:00',1,1,1,'KAIROS',1,2,3,4,1,1,9789,2,1,'ROUTEMOBILE','9988776')
,(7777,'0233456789','The blessing of the Lord be with you',1,'K','20201110 08:00:00','20201110 08:10:00',1,1,1,'KAIROS',1,2,3,4,1,1,9789,2,1,'ROUTEMOBILE','9988776')
,(7005,'0234876789','Happy Birthday to you',1,'K','20201110 09:00:00','20201110 09:20:00',1,1,1,'KAIROS',1,2,3,4,1,1,9789,2,1,'ROUTEMOBILE','9988776')
,(9002,'0233456789','Merry Christmas',1,'K','20201110 07:00:00','20201110 07:15:00',1,1,1,'KAIROS',1,2,3,4,1,1,9789,2,1,'ROUTEMOBILE','9988776')
,(6789,'0233467889','Buy our brand new cars for less than $8000',1,'K','20201110 14:00:00','20201110 14:20:00',1,1,1,'KAIROS',1,2,3,4,1,1,9789,2,1,'ROUTEMOBILE','9988776')
,(7685,'0244556789','Happy New Month. God bless and increase you',1,'K','20201110 17:00:00','20201110 17:08:00',1,1,1,'KAIROS',1,2,3,4,1,1,9789,2,1,'ROUTEMOBILE','9988776')
,(4983,'0229856789','Help is coming your way today!',1,'K','20201110 19:00:00','20201110 19:20:00',1,1,1,'KAIROS',1,2,3,4,1,1,9789,2,1,'ROUTEMOBILE','9988776')
,(6879,'0239986789','Call us for your next relocation project',1,'K','20201110 19:15:00','20201110 19:20:00',1,1,1,'KAIROS',1,2,3,4,1,1,9789,2,1,'ROUTEMOBILE','9988776')
,(4567,'0233456789','Hard Work Always Pays',1,'K','20201110 22:05:00','20201110 22:20:00',1,1,1,'KAIROS',1,2,3,4,1,1,9789,2,1,'ROUTEMOBILE','9988776')
,(8890,'0244656733','Don''t wait to buy land, buy land and wait',1,'K','20201110 15:05:00','20201110 15:20:00',1,1,1,'KAIROS',1,2,3,4,1,1,9789,2,1,'ROUTEMOBILE','9988776')
,(6789,'0233466734','We are relocating. Call us on 077788997',1,'K','20201110 18:02:00','20201110 18:17:00',1,1,1,'KAIROS',1,2,3,4,1,1,9789,2,1,'ROUTEMOBILE','9988776')
,(9899,'0233456556','Watch out for our latest movie',1,'K','20201110 06:00:00','20201110 06:02:00',1,1,1,'KAIROS',1,2,3,4,1,1,9789,2,1,'ROUTEMOBILE','9988776')
,(6789,'0233456338','We are here to make you happy',1,'K','20201110 12:16:00','20201110 12:20:00',1,1,1,'KAIROS',1,2,3,4,1,1,9789,2,1,'ROUTEMOBILE','9988776')
GO
Podríamos haber hecho esto de una manera mucho más simple, pero queremos mostrar la sintaxis de la instrucción MERGE (consulte el Listado 4):
-- Listing 4: Merge Records in Tran Table to TranHistory Table
MERGE INTO [TranHistory] a USING [Tran] b
ON a.responseId=b.responseID
WHEN NOT MATCHED BY TARGET THEN INSERT
([responseId],[senderId],[msisdn],[message],[status],[application],[receivedTime],[processedTime],[flag],[requestDelivery],[delivered],[account],[srcTon],[srcNpi],[destTon],[destNpi],[errorCode],[messageId],[sequenceNo],[retries],[messagePriority],[userId],[bulkId],[archivedTime])
VALUES
([responseId],[senderId],[msisdn],[message],[status],[application],[receivedTime],[processedTime],[flag],[requestDelivery],[delivered],[account],[srcTon],[srcNpi],[destTon],[destNpi],[errorCode],[messageId],[sequenceNo],[retries],[messagePriority],[userId],[bulkId],getdate());
GO
La instrucción MERGE dice:
- Tome el contenido de la tabla [Tran] y compárelo con la tabla [TranHistory] en función del responseId columna.
- Inserte aquellas filas que encuentre en la tabla de origen pero que no encuentre en la tabla de destino (TranHistory).
Tal como están las cosas, Tran y TranHistory están en per. Pero supongamos que al día siguiente se introducen nuevas filas en la tabla Trans. También debemos enviarlos a la tabla TransHistory mientras retenemos los registros en la tabla Tran hasta que termine el mes.
Nuevamente usamos el script en el listado 4. Esta vez, agregamos una cláusula OUTPUT para indicar qué se introdujo (consulte el Listado 5):
-- Listing 5: Merge Records in Tran Table to TranHistory Table (Add OUTPUT Clause)
USE AU
GO
MERGE INTO [TranHistory] a USING [Tran] b
ON a.responseId=b.responseID
WHEN NOT MATCHED BY TARGET THEN INSERT
([responseId],[senderId],[msisdn],[message],[status],[application],[receivedTime],[processedTime],[flag],[requestDelivery],[delivered],[account],[srcTon],[srcNpi],[destTon],[destNpi],[errorCode],[messageId],[sequenceNo],[retries],[messagePriority],[userId],[bulkId],[archivedTime])
VALUES
([responseId],[senderId],[msisdn],[message],[status],[application],[receivedTime],[processedTime],[flag],[requestDelivery],[delivered],[account],[srcTon],[srcNpi],[destTon],[destNpi],[errorCode],[messageId],[sequenceNo],[retries],[messagePriority],[userId],[bulkId],getdate())
OUTPUT deleted.*, $action, inserted.*;
GO
Podemos repetir este proceso después de introducir filas adicionales en la tabla Tran (Listados 6 y 7), y obtenemos un comportamiento similar:
-- Listing 6: Insert Six New Rows in Tran Table
USE [AU]
GO
INSERT INTO [dbo].[Tran]
VALUES
(6879,'0239986789','Call us for your next relocation project',1,'K','20201110 19:15:00','20201110 19:20:00',1,1,1,'KAIROS',1,2,3,4,1,1,9789,2,1,'ROUTEMOBILE','9988776')
,(4567,'0233456789','Hard Work Always Pays',1,'K','20201110 22:05:00','20201110 22:20:00',1,1,1,'KAIROS',1,2,3,4,1,1,9789,2,1,'ROUTEMOBILE','9988776')
,(8890,'0244656733','Don''t wait to buy land, buy land and wait',1,'K','20201110 15:05:00','20201110 15:20:00',1,1,1,'KAIROS',1,2,3,4,1,1,9789,2,1,'ROUTEMOBILE','9988776')
,(6789,'0233466734','We are relocating. Call us on 077788997',1,'K','20201110 18:02:00','20201110 18:17:00',1,1,1,'KAIROS',1,2,3,4,1,1,9789,2,1,'ROUTEMOBILE','9988776')
,(9899,'0233456556','Watch out for our latest movie',1,'K','20201110 06:00:00','20201110 06:02:00',1,1,1,'KAIROS',1,2,3,4,1,1,9789,2,1,'ROUTEMOBILE','9988776')
,(6789,'0233456338','We are here to make you happy',1,'K','20201110 12:16:00','20201110 12:20:00',1,1,1,'KAIROS',1,2,3,4,1,1,9789,2,1,'ROUTEMOBILE','9988776')
GO
-- Listing 7: Insert Additional Three Rows in Tran Table
USE [AU]
GO
INSERT INTO [dbo].[Tran]
VALUES
(7789,'0233456433','Are you ready for your next level?',1,'K','20201110 14:35:00','20201110 14:40:00',1,1,1,'KAIROS',1,2,3,4,1,1,9789,2,1,'ROUTEMOBILE','9988776')
,(8000,'0233457759','Hutchies Honey, another level of taste',1,'K','20201110 08:00:00','20201110 08:08:00',1,1,1,'KAIROS',1,2,3,4,1,1,9789,2,1,'ROUTEMOBILE','9988776')
,(7777,'0233458909','Make sure you vote tomorrow',1,'K','20201110 09:45:00','20201110 09:50:00',1,1,1,'KAIROS',1,2,3,4,1,1,9789,2,1,'ROUTEMOBILE','9988776')
,(9890,'0233459994','Wishing you a Merry Christmas',1,'K','20201110 10:00:00','20201110 10:05:00',1,1,1,'KAIROS',1,2,3,4,1,1,9789,2,1,'ROUTEMOBILE','9988776')
GO
CUANDO COINCIDE ENTONCES ACTUALIZAR
Otro caso es una actualización de la tabla en vivo (Tran) cuando queremos sincronizar dichas actualizaciones con la tabla TranHistory. Creamos este escenario actualizando filas en la tabla Tran usando el script en el Listado 8.
-- Listing 8: Update and Insert Rows in Tran Table
USE AU
GO
UPDATE [dbo].[Tran] SET account='JUNIPER'
WHERE account='KAIROS';
GO
INSERT INTO [dbo].[Tran]
VALUES
(5578,'0233566933','Newest on the Block!',1,'K','20201110 14:35:00','20201110 14:40:00',1,1,1,'KAIROS',1,2,3,4,1,1,9789,2,1,'ROUTEMOBILE','9988776')
GO
La figura 6 muestra que las filas se actualizan en la tabla de destino. Obtenemos este detalle usando la cláusula OUTPUT.
Debemos usar la cláusula resaltada en el Listado 9. La declaración MERGE identificó filas que coinciden con la condición JOIN y actualiza los datos en la columna especificada (cuenta ). Podemos actualizar varias filas con este enfoque.
-- Listing 9: Sync Data in TranHistory by Updating Rows
USE AU
GO
MERGE INTO [TranHistory] a USING [Tran] b
ON a.responseId=b.responseID
WHEN MATCHED THEN UPDATE
SET a.account=b.account
WHEN NOT MATCHED BY TARGET THEN INSERT
([responseId],[senderId],[msisdn],[message],[status],[application],[receivedTime],[processedTime],[flag],[requestDelivery],[delivered],[account],[srcTon],[srcNpi],[destTon],[destNpi],[errorCode],[messageId],[sequenceNo],[retries],[messagePriority],[userId],[bulkId],[archivedTime])
VALUES
([responseId],[senderId],[msisdn],[message],[status],[application],[receivedTime],[processedTime],[flag],[requestDelivery],[delivered],[account],[srcTon],[srcNpi],[destTon],[destNpi],[errorCode],[messageId],[sequenceNo],[retries],[messagePriority],[userId],[bulkId],getdate())
OUTPUT deleted.*, $action, inserted.*;
GO
CUANDO NO COINCIDE CON LA FUENTE, ELIMINAR
Un escenario más implica la eliminación de filas de la tabla de origen (Listado 10). Cuando suceda, debemos identificar las filas que ya no existen en la tabla de origen y eliminarlas de la tabla de destino.
Logramos esto usando la cláusula resaltada en el Listado 10. Nuevamente, las Figuras 7 y 8 muestran las filas afectadas por esta instrucción MERGE.
-- Listing 10: Update and Insert Rows in Tran Table
USE AU
GO
DELETE FROM [dbo].[Tran]
WHERE account='JUNIPER';
GO
-- Listing 11: Syncing Tables After Deleting from Tran Table
USE AU
GO
MERGE INTO [TranHistory] a USING [Tran] b
ON a.responseId=b.responseID
WHEN NOT MATCHED BY SOURCE THEN DELETE
WHEN MATCHED THEN UPDATE
SET a.account=b.account
WHEN NOT MATCHED BY TARGET THEN INSERT
([responseId],[senderId],[msisdn],[message],[status],[application],[receivedTime],[processedTime],[flag],[requestDelivery],[delivered],[account],[srcTon],[srcNpi],[destTon],[destNpi],[errorCode],[messageId],[sequenceNo],[retries],[messagePriority],[userId],[bulkId],[archivedTime])
VALUES
([responseId],[senderId],[msisdn],[message],[status],[application],[receivedTime],[processedTime],[flag],[requestDelivery],[delivered],[account],[srcTon],[srcNpi],[destTon],[destNpi],[errorCode],[messageId],[sequenceNo],[retries],[messagePriority],[userId],[bulkId],getdate())
OUTPUT deleted.*, $action, inserted.*;
GO
La Figura 7 muestra las filas que se eliminaron y la Figura 8 muestra las que se actualizaron. Es el poder de la instrucción MERGE. Podemos realizar operaciones de eliminación, inserción y actualización, todo en una declaración.
CONCLUSIÓN
Este artículo revisó el uso de la declaración MERGE para sincronizar datos entre una tabla en línea y una tabla de historial mientras se mantiene la retención deseada requerida en ambas tablas.
Hay muchos otros casos de uso para las declaraciones MERGE de SQL Server que no se tratan en este artículo, pero se exploran en la documentación de Microsoft. Los datos de origen especificados en la cláusula USING no se limitan a las tablas. Los conjuntos de resultados de declaraciones SELECT explícitas pueden ser datos de origen. Las expresiones de tabla comunes también pueden ser datos de origen.
REFERENCIAS
COMBINAR en Transact-SQL