Kann die SQL Output-Klausel eine nicht eingefügte Spalte zurückgeben?

122

Ich habe einige Änderungen an meiner Datenbank vorgenommen und muss die alten Daten in die neuen Tabellen migrieren. Dazu muss ich eine Tabelle (ReportOptions) aus den Originaltabellen (Practice) und eine zweite Zwischentabelle (PracticeReportOption) ausfüllen.

ReportOption (ReportOptionId int PK, field1, field2...)
Practice (PracticeId int PK, field1, field2...)
PracticeReportOption (PracticeReportOptionId int PK, PracticeId int FK, ReportOptionId int FK, field1, field2...)

Ich habe eine Abfrage durchgeführt, um alle Daten abzurufen, die ich zum Üben von Practice zu ReportOptions benötige, aber ich habe Probleme, die Zwischentabelle zu füllen

--Auxiliary tables
DECLARE @ReportOption TABLE (PracticeId int /*This field is not on the actual ReportOption table*/, field1, field2...)
DECLARE @PracticeReportOption TABLE (PracticeId int, ReportOptionId int, field1, field2)

--First I get all the data I need to move
INSERT INTO @ReportOption
SELECT P.practiceId, field1, field2...
  FROM Practice P

--I insert it into the new table, but somehow I need to have the repation PracticeId / ReportOptionId
INSERT INTO ReportOption (field1, field2...)
OUTPUT @ReportOption.PracticeId, --> this is the field I don't know how to get
       inserted.ReportOptionId
  INTO @PracticeReportOption (PracticeId, ReportOptionId)
SELECT field1, field2
  FROM @ReportOption

--This would insert the relationship, If I knew how to get it!
INSERT INTO @PracticeReportOption (PracticeId, ReportOptionId)
SELECT PracticeId, ReportOptionId
  FROM @ReportOption

Wenn ich auf ein Feld verweisen könnte, das sich nicht in der Zieltabelle der OUTPUT-Klausel befindet, wäre das großartig (ich denke, ich kann nicht, aber ich weiß es nicht genau). Irgendwelche Ideen, wie ich meine Bedürfnisse erfüllen kann?

Alejandro B.
quelle
1
Sie können in Ihrer OUTPUTKlausel jede der Spalten der Tabelle zurückgeben, in die Sie eine Zeile eingefügt haben . Selbst wenn Sie in Ihrer INSERTAnweisung keinen Wert für eine bestimmte Spalte angeben, können Sie diese Spalte dennoch in der OUTPUTKlausel angeben . Sie können jedoch keine SQL-Variablen oder -Spalten aus anderen Tabellen zurückgeben.
marc_s
2
@marc_s danke für Ihre Antwort, aber ich habe nicht das Feld, das ich in der Zieltabelle brauche (ich brauche PracticeId, die nicht auf ReportOption ist)
Alejandro B.

Antworten:

191

Sie können dies tun, indem Sie MERGEanstelle von Einfügen Folgendes verwenden:

Also ersetze dies

INSERT INTO ReportOption (field1, field2...)
OUTPUT @ReportOption.PracticeId, --> this is the field I don't know how to get
       inserted.ReportOptionId
  INTO @PracticeReportOption (PracticeId, ReportOptionId)
SELECT field1, field2
  FROM @ReportOption

mit

MERGE INTO ReportOption USING @ReportOption AS temp ON 1 = 0
WHEN NOT MATCHED THEN
    INSERT (field1, field2)
    VALUES (temp.Field1, temp.Field2)
    OUTPUT temp.PracticeId, inserted.ReportOptionId, inserted.Field1, inserted.Field2
    INTO @PracticeReportOption (PracticeId, ReportOptionId, Field1, Field2);

Der Schlüssel besteht darin, ein Prädikat zu verwenden, das in der Zusammenführungssuchbedingung niemals wahr ist (1 = 0). Sie führen also immer die Einfügung durch, haben jedoch Zugriff auf Felder sowohl in der Quell- als auch in der Zieltabelle.


Hier ist der gesamte Code, mit dem ich ihn getestet habe:

CREATE TABLE ReportOption (ReportOptionID INT IDENTITY(1, 1), Field1 INT, Field2 INT)
CREATE TABLE Practice (PracticeID INT IDENTITY(1, 1), Field1 INT, Field2 INT)
CREATE TABLE PracticeReportOption (PracticeReportOptionID INT IDENTITY(1, 1), PracticeID INT, ReportOptionID INT, Field1 INT, Field2 INT)

INSERT INTO Practice VALUES (1, 1), (2, 2), (3, 3), (4, 4)


MERGE INTO ReportOption r USING Practice p ON 1 = 0
WHEN NOT MATCHED THEN
    INSERT (field1, field2)
    VALUES (p.Field1, p.Field2)
    OUTPUT p.PracticeId, inserted.ReportOptionId, inserted.Field1, inserted.Field2
    INTO PracticeReportOption (PracticeId, ReportOptionId, Field1, Field2);

SELECT  *
FROM    PracticeReportOption

DROP TABLE ReportOption
DROP TABLE Practice
DROP TABLE PracticeReportOption 

Weitere Lektüre und die Quelle von allem, was ich zu diesem Thema weiß, ist hier

GarethD
quelle
2
Danke, das macht den Trick! Ich wollte ein falsches Temperaturfeld verwenden, aber das ist viel eleganter.
Alejandro B.
1
Ausgezeichnet! Dieser Trick ist ein Goldkorn! Zur ersten Zeile der Sammlung hinzugefügt!
Vadim Loboda
1
Süß! Ich wünschte, es müsste nicht den gelegentlich fehlerhaften MERGE-Befehl verwenden, aber ansonsten ist es vollkommen elegant.
Tab Alleman
4
Sei gewarnt. Ich habe eine Zusammenführungsanweisung verwendet, die im letzten Jahr mit der Nutzung gewachsen ist. Während des Speichervorgangs kam es zu Zeitüberschreitungen, und es stellte sich heraus, dass wir aufgrund der Zusammenführungsanweisung, die die Tabellen immer sperrt, alle 4 Minuten eine Tabellensperre von 35 bis 160 Sekunden hatten. Ich muss mehrere Zusammenführungsanweisungen rekonstruieren, um Einfügen / Aktualisieren zu verwenden und die Anzahl der Zeilen, die aktualisiert werden, auf 500 pro Einfügen / Aktualisieren zu begrenzen, um das Sperren von Tabellen zu vermeiden. Ich schätze, dass dieser sehr wichtige Tisch fast 2 1/2 Stunden pro Tag gesperrt war, was alles von langsamen Speichern bis zu Zeitüberschreitungen verursachte.
CubeRoot
3
Ein Deal Breaker für viele ist auch, dass MERGE eine ganze Reihe von nicht behobenen Fehlern enthält, die unter seltsamen Bedingungen auftauchen. ZB sehen diesen Artikel von Aaron Bertrand. Microsoft weigert sich, einige von ihnen zu beheben, und mein geheimer Verdacht ist, dass MS ihren gesamten MS Connect- Dienst abgelehnt hat, nur um zu versuchen, all diese Fehler in der MERGE-Anweisung zu vergessen, die sie nicht beheben möchten.
Umgekehrter Ingenieur
14

Vielleicht findet jemand, der MS SQL Server 2005 oder niedriger verwendet , diese Antwort hilfreich.


MERGE funktioniert nur für SQL Server 2008 oder höher. Für den Rest habe ich eine andere Problemumgehung gefunden, mit der Sie Mapping-Tabellen erstellen können.

So sieht die Auflösung für SQL 2005 aus:

DECLARE @ReportOption TABLE (ReportOptionID INT IDENTITY(1, 1), Field1 INT, Field2 INT)
DECLARE @Practice TABLE(PracticeID INT IDENTITY(1, 1), Field1 INT, Field2 INT)
DECLARE @PracticeReportOption TABLE(PracticeReportOptionID INT IDENTITY(1, 1), PracticeID INT, ReportOptionID INT, Field1 INT, Field2 INT)

INSERT INTO @Practice (Field1, Field2) VALUES (1, 1)
INSERT INTO @Practice (Field1, Field2) VALUES (2, 2)
INSERT INTO @Practice (Field1, Field2) VALUES (3, 3)
INSERT INTO @Practice (Field1, Field2) VALUES (4, 4)

INSERT INTO @ReportOption (field1, field2)
    OUTPUT INSERTED.ReportOptionID, INSERTED.Field1, INSERTED.Field2 INTO @PracticeReportOption (ReportOptionID, Field1, Field2)
    SELECT Field1, Field2 FROM @Practice ORDER BY PracticeID ASC;


WITH CTE AS ( SELECT PracticeID, ROW_NUMBER() OVER ( ORDER BY PracticeID ASC ) AS ROW FROM @Practice )
UPDATE M SET M.PracticeID = S.PracticeID 
    FROM @PracticeReportOption AS M
    JOIN CTE AS S ON S.ROW = M.PracticeReportOptionID

    SELECT * FROM @PracticeReportOption

Der Haupttrick besteht darin, dass wir die Zuordnungstabelle zweimal mit geordneten Daten aus der Quell- und Zieltabelle füllen. Weitere Informationen finden Sie hier: Zusammenführen eingefügter Daten mit OUTPUT in SQL Server 2005

Val
quelle
1
Dies würde mein Problem nicht lösen. Ich muß Outputmeinen Insert‚s mit einem Ausgang Table, der einen enthält IdentityWert von dem Ziel Tablemit einem nicht Insert-ed Wert (PK) von der Quelle Table(btw, so konnte ich dann (in einer anderen Charge) verwenden , die Ausgabe Tableeines bevölkern Columnin die Quelle Tablemit dem IdentityWert). Ohne a Mergemüsste ich wohl: a) starten Transaction, b) als nächstes kommen Identity, c) Tablemit berechnet in temp einfügen Identity, d) setzen Identity_Insert, e) von temp Insertin Ziel , f) löschen . TableTableIdentity_Insert
Tom