Tengo la siguiente MERGEdeclaración que se emite contra la base de datos:
MERGE "MySchema"."Point" AS t
USING (
SELECT "ObjectId", "PointName", z."Id" AS "LocationId", i."Id" AS "Region"
FROM @p1 AS d
JOIN "MySchema"."Region" AS i ON i."Name" = d."Region"
LEFT JOIN "MySchema"."Location" AS z ON z."Name" = d."Location" AND z."Region" = i."Id"
) AS s
ON s."ObjectId" = t."ObjectId"
WHEN NOT MATCHED BY TARGET
THEN INSERT ("ObjectId", "Name", "LocationId", "Region") VALUES (s."ObjectId", s."PointName", s."LocationId", s."Region")
WHEN MATCHED
THEN UPDATE
SET "Name" = s."PointName"
, "LocationId" = s."LocationId"
, "Region" = s."Region"
OUTPUT $action, inserted.*, deleted.*;
Sin embargo, esto hace que la sesión finalice con el siguiente error:
Mensaje 0, Nivel 11, Estado 0, Línea 67 Se produjo un error grave en el comando actual. Los resultados, si los hay, deben descartarse.
Mensaje 0, Nivel 20, Estado 0, Línea 67 Se produjo un error grave en el comando actual. Los resultados, si los hay, deben descartarse.
He creado un breve script de prueba que produce el error:
USE master;
GO
IF DB_ID('TEST') IS NOT NULL
DROP DATABASE "TEST";
GO
CREATE DATABASE "TEST";
GO
USE "TEST";
GO
SET NOCOUNT ON;
IF SCHEMA_ID('MySchema') IS NULL
EXECUTE('CREATE SCHEMA "MySchema"');
GO
IF OBJECT_ID('MySchema.Region', 'U') IS NULL
CREATE TABLE "MySchema"."Region" (
"Id" TINYINT IDENTITY NOT NULL CONSTRAINT "PK_MySchema_Region" PRIMARY KEY,
"Name" VARCHAR(8) NOT NULL CONSTRAINT "UK_MySchema_Region" UNIQUE
);
GO
INSERT [MySchema].[Region] ([Name])
VALUES (N'A'), (N'B'), (N'C'), (N'D'), (N'E'), ( N'F'), (N'G');
IF OBJECT_ID('MySchema.Location', 'U') IS NULL
CREATE TABLE "MySchema"."Location" (
"Id" SMALLINT IDENTITY NOT NULL CONSTRAINT "PK_MySchema_Location" PRIMARY KEY,
"Region" TINYINT NOT NULL CONSTRAINT "FK_MySchema_Location_Region" FOREIGN KEY REFERENCES "MySchema"."Region" ("Id"),
"Name" VARCHAR(128) NOT NULL,
CONSTRAINT "UK_MySchema_Location" UNIQUE ("Region", "Name")
);
GO
IF OBJECT_ID('MySchema.Point', 'U') IS NULL
CREATE TABLE "MySchema"."Point" (
"ObjectId" BIGINT NOT NULL CONSTRAINT "PK_MySchema_Point" PRIMARY KEY,
"Name" VARCHAR(64) NOT NULL,
"LocationId" SMALLINT NULL CONSTRAINT "FK_MySchema_Point_Location" FOREIGN KEY REFERENCES "MySchema"."Location"("Id"),
"Region" TINYINT NOT NULL CONSTRAINT "FK_MySchema_Point_Region" FOREIGN KEY REFERENCES "MySchema"."Region" ("Id"),
CONSTRAINT "UK_MySchema_Point" UNIQUE ("Name", "Region", "LocationId")
);
GO
-- CONTAINS HISTORIC Point DATA
IF OBJECT_ID('MySchema.PointHistory', 'U') IS NULL
CREATE TABLE "MySchema"."PointHistory" (
"Id" BIGINT IDENTITY NOT NULL CONSTRAINT "PK_MySchema_PointHistory" PRIMARY KEY,
"ObjectId" BIGINT NOT NULL,
"Name" VARCHAR(64) NOT NULL,
"LocationId" SMALLINT NULL,
"Region" TINYINT NOT NULL
);
GO
CREATE TYPE "MySchema"."PointTable" AS TABLE (
"ObjectId" BIGINT NOT NULL PRIMARY KEY,
"PointName" VARCHAR(64) NOT NULL,
"Location" VARCHAR(16) NULL,
"Region" VARCHAR(8) NOT NULL,
UNIQUE ("PointName", "Region", "Location")
);
GO
DECLARE @p1 "MySchema"."PointTable";
insert into @p1 values(10001769996,N'ABCDEFGH',N'N/A',N'E')
MERGE "MySchema"."Point" AS t
USING (
SELECT "ObjectId", "PointName", z."Id" AS "LocationId", i."Id" AS "Region"
FROM @p1 AS d
JOIN "MySchema"."Region" AS i ON i."Name" = d."Region"
LEFT JOIN "MySchema"."Location" AS z ON z."Name" = d."Location" AND z."Region" = i."Id"
) AS s
ON s."ObjectId" = t."ObjectId"
WHEN NOT MATCHED BY TARGET
THEN INSERT ("ObjectId", "Name", "LocationId", "Region") VALUES (s."ObjectId", s."PointName", s."LocationId", s."Region")
WHEN MATCHED
THEN UPDATE
SET "Name" = s."PointName"
, "LocationId" = s."LocationId"
, "Region" = s."Region"
OUTPUT $action, inserted.*, deleted.*;
Si elimino la OUTPUTcláusula, no se produce el error. Además, si elimino la deletedreferencia, no se produce el error. Así que miré los documentos de MSDN para la OUTPUTcláusula que dice:
DELETED no se puede usar con la cláusula OUTPUT en la instrucción INSERT.
Lo que tiene sentido para mí, sin embargo, el punto MERGEes que quizás no lo sepas de antemano.
Además, el siguiente script funciona perfectamente bien independientemente de la acción que se realice:
USE tempdb;
GO
CREATE TABLE dbo.Target(EmployeeID int, EmployeeName varchar(10),
CONSTRAINT Target_PK PRIMARY KEY(EmployeeID));
CREATE TABLE dbo.Source(EmployeeID int, EmployeeName varchar(10),
CONSTRAINT Source_PK PRIMARY KEY(EmployeeID));
GO
INSERT dbo.Target(EmployeeID, EmployeeName) VALUES(100, 'Mary');
INSERT dbo.Target(EmployeeID, EmployeeName) VALUES(101, 'Sara');
INSERT dbo.Target(EmployeeID, EmployeeName) VALUES(102, 'Stefano');
GO
INSERT dbo.Source(EmployeeID, EmployeeName) Values(103, 'Bob');
INSERT dbo.Source(EmployeeID, EmployeeName) Values(104, 'Steve');
GO
-- MERGE statement with the join conditions specified correctly.
USE tempdb;
GO
BEGIN TRAN;
MERGE Target AS T
USING Source AS S
ON (T.EmployeeID = S.EmployeeID)
WHEN NOT MATCHED BY TARGET AND S.EmployeeName LIKE 'S%'
THEN INSERT(EmployeeID, EmployeeName) VALUES(S.EmployeeID, S.EmployeeName)
WHEN MATCHED
THEN UPDATE SET T.EmployeeName = S.EmployeeName
WHEN NOT MATCHED BY SOURCE AND T.EmployeeName LIKE 'S%'
THEN DELETE
OUTPUT $action, inserted.*, deleted.*;
ROLLBACK TRAN;
GO
Además, tengo otras consultas que usan la OUTPUTmisma manera que la que arroja un error y funcionan perfectamente bien: la única diferencia entre ellas son las tablas que participan en el MERGE.
Esto nos está causando problemas importantes en la producción. He reproducido este error en SQL2014 y SQL2016 en VM y física con 128 GB de RAM, 12 núcleos de 2,2 GHz, Windows Server 2012 R2.
El plan de ejecución estimado generado a partir de la consulta se puede encontrar aquí:
fuente

MERGEno es asíHOLDLOCK, por lo que no es inmune a las condiciones de carrera, y aún hay otros errores que considerar incluso después de resolver - o el informe -. lo que está causando este problema)deleted.ObjectIdque está causando el problema.OUTPUT $action, inserted.*, deleted.Name, deleted.LocationId, deleted.Regionfunciona bien.MySchema.PointTabletipo, y simplemente usando unaVALUES()cláusula desnuda , o #temp table, o table variable, dentro deUSING. Podría ayudar a aislar los factores contribuyentes.Respuestas:
Esto es un error
Está relacionado con
MERGEoptimizaciones específicas para rellenar huecos que se utilizan para evitar la protección explícita de Halloween y eliminar una unión, y cómo interactúan con otras características del plan de actualización.Hay detalles sobre esas optimizaciones en mi artículo, The Halloween Problem - Part 3 .
El sorteo es la inserción seguida de una combinación en la misma tabla :
Soluciones alternativas
Hay varias formas de vencer esta optimización, y así evitar el error.
Use una marca de rastreo indocumentada para forzar la protección explícita de Halloween:
Cambie la
ONcláusula a:Cambie el tipo de tabla
PointTablepara reemplazar la clave primaria con:La
CHECKparte de restricción es opcional, incluida para preservar la propiedad original de rechazo nulo de una clave primaria.El procesamiento de consultas de actualización 'simple' (comprobaciones de clave externa, mantenimiento de índice único y columnas de salida) es lo suficientemente complejo para empezar. El uso
MERGEagrega varias capas adicionales a eso. Combine eso con la optimización específica mencionada anteriormente, y tiene una excelente manera de encontrar errores de borde de caso como este.Uno más para agregar a la larga línea de errores con los que se ha informado
MERGE.fuente