Ich habe die folgende MERGE
Anweisung, die gegen die Datenbank ausgegeben wird:
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.*;
Dies führt jedoch dazu, dass die Sitzung mit dem folgenden Fehler beendet wird:
Meldung 0, Ebene 11, Status 0, Zeile 67 Beim aktuellen Befehl ist ein schwerwiegender Fehler aufgetreten. Die Ergebnisse sollten, falls vorhanden, verworfen werden.
Meldung 0, Ebene 20, Status 0, Zeile 67 Beim aktuellen Befehl ist ein schwerwiegender Fehler aufgetreten. Die Ergebnisse sollten, falls vorhanden, verworfen werden.
Ich habe ein kurzes Testskript zusammengestellt, das den Fehler erzeugt:
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.*;
Wenn ich die OUTPUT
Klausel entferne , tritt der Fehler nicht auf. Auch wenn ich den deleted
Verweis entferne, tritt der Fehler nicht auf. Also habe ich in den MSDN-Dokumenten nach der OUTPUT
Klausel gesucht, die besagt :
DELETED kann nicht mit der OUTPUT-Klausel in der INSERT-Anweisung verwendet werden.
Was für mich jedoch Sinn macht, MERGE
ist, dass Sie es möglicherweise nicht im Voraus wissen.
Darüber hinaus funktioniert das folgende Skript unabhängig von der ausgeführten Aktion einwandfrei:
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
Ich habe auch andere Abfragen, die OUTPUT
die gleiche Methode verwenden wie die, die einen Fehler auslöst, und die einwandfrei funktionieren - der einzige Unterschied zwischen ihnen besteht in den Tabellen, die an der teilnehmen MERGE
.
Dies führt bei uns zu großen Produktionsproblemen. Ich habe diesen Fehler in SQL2014 und SQL2016 sowohl auf virtuellen als auch auf physischen Computern mit 128 GB RAM, 12 x 2,2 GHz-Kernen und Windows Server 2012 R2 reproduziert.
Den geschätzten Ausführungsplan, der aus der Abfrage generiert wurde, finden Sie hier:
quelle
MERGE
nicht überHOLDLOCK
, für ein, so dass es nicht immun gegen Rennbedingungen ist, und es gibt noch andere Fehler auch zu prüfen , Nachdem Sie das Problem behoben oder gemeldet haben (was auch immer das Problem verursacht)deleted.ObjectId
das Problem zu verursachen.OUTPUT $action, inserted.*, deleted.Name, deleted.LocationId, deleted.Region
funktioniert gut.MySchema.PointTable
Typ nicht verwenden und einfach eine Naked-VALUES()
Klausel oder eine #temp-Tabelle oder eine Tabellenvariable innerhalb von verwendenUSING
. Könnte helfen, beitragende Faktoren zu isolieren.Antworten:
Das ist ein Fehler.
Es bezieht sich auf bestimmte
MERGE
Optimierungen zum Füllen von Löchern, die verwendet werden, um expliziten Halloween-Schutz zu vermeiden und einen Join zu beseitigen, und wie diese mit anderen Funktionen des Aktualisierungsplans interagieren.Details zu diesen Optimierungen finden Sie in meinem Artikel The Halloween Problem - Part 3 .
Das Werbegeschenk ist das Einfügen, gefolgt von einem Zusammenführen in derselben Tabelle :
Problemumgehungen
Es gibt verschiedene Möglichkeiten, diese Optimierung zu umgehen und so den Fehler zu vermeiden.
Verwenden Sie ein nicht dokumentiertes Ablaufverfolgungsflag, um den expliziten Halloween-Schutz zu erzwingen:
Ändern Sie die
ON
Klausel in:Ändern Sie den Tabellentyp
PointTable
, um den Primärschlüssel durch Folgendes zu ersetzen:Der
CHECK
Einschränkungsteil ist optional und wird eingefügt, um die ursprüngliche Eigenschaft eines Primärschlüssels beizubehalten, bei der keine Zurückweisung erfolgt.Die "einfache" Verarbeitung von Aktualisierungsabfragen (Fremdschlüsselprüfungen, eindeutige Indexpflege und Ausgabespalten) ist anfangs komplex genug. Durch Verwenden von werden dem
MERGE
mehrere zusätzliche Ebenen hinzugefügt. Kombinieren Sie dies mit der oben genannten speziellen Optimierung, und Sie haben eine großartige Möglichkeit, Fehler wie diese zu finden.Eine weitere, um die lange Reihe von Fehlern zu ergänzen, die gemeldet wurden
MERGE
.quelle