È possibile che la clausola SQL Output restituisca una colonna non inserita?


123

Ho apportato alcune modifiche al mio database e ho bisogno di migrare i vecchi dati nelle nuove tabelle. Per questo, ho bisogno di compilare una tabella (ReportOptions) prendendo i dati dalla tabella originale (Practice) e riempire una seconda tabella intermedia (PracticeReportOption).

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

Ho effettuato una query per ottenere tutti i dati di cui ho bisogno per passare da Practice a ReportOptions, ma ho problemi a riempire la tabella intermedia

--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

Se potessi fare riferimento a un campo che non si trova nella tabella di destinazione nella clausola OUTPUT, sarebbe fantastico (penso di non poterlo fare, ma non lo so per certo). Delle idee su come realizzare il mio bisogno?


1
Puoi restituire una qualsiasi delle colonne della tabella in cui hai inserito una riga, nella tua OUTPUTclausola. Quindi, anche se non fornisci un valore per una determinata colonna nella tua INSERTdichiarazione, puoi comunque specificare quella colonna nella OUTPUTclausola. Tuttavia, non è possibile restituire variabili SQL o colonne da altre tabelle.
marc_s

2
@marc_s grazie per la tua risposta, ma non ho il campo che mi serve nella tabella di destinazione (ho bisogno di PracticeId, che non è su ReportOption)
Alejandro B.

Risposte:


191

Puoi farlo usando MERGEinvece di inserire:

quindi sostituiscilo

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

con

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);

La chiave è utilizzare un predicato che non sarà mai vero (1 = 0) nella condizione di ricerca unione, quindi eseguirai sempre l'inserimento, ma avrai accesso ai campi sia nella tabella di origine che in quella di destinazione.


Ecco l'intero codice che ho usato per testarlo:

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 

Altre letture, e la fonte di tutto ciò che so sull'argomento è qui


2
Grazie, questo fa il trucco! Avrei usato un campo temporaneo falso ma questo è molto più elegante.
Alejandro B.

1
Eccellente! Questo trucco è un granello d'oro! Aggiunto alla prima riga della raccolta!
Vadim Loboda

1
Suweet! Vorrei che non dovesse usare il comando MERGE occasionalmente buggy, ma altrimenti è perfettamente elegante.
Tab Alleman

4
Stai attento. Ho usato una dichiarazione di unione che nell'ultimo anno è cresciuta con l'utilizzo. Abbiamo iniziato ad avere timeout durante i salvataggi e si è scoperto che, poiché l'istruzione merge blocca sempre le tabelle, avevamo 35-160 secondi di blocco della tabella ogni 4 minuti. Devo ricostruire diverse istruzioni di unione per utilizzare inserimento / aggiornamenti e limitare il numero di righe che aggiornano a 500 per inserimento / aggiornamento per evitare il blocco della tabella. Stimo che questa tabella molto importante fosse tenuta bloccata per quasi 2 ore e mezza al giorno, il che causava di tutto, dai salvataggi lenti ai timeout.
CubeRoot

3
Inoltre, un problema per molti è che MERGE contiene un sacco di bug non risolti, che emergono in condizioni strane. Ad esempio, vedere questo articolo di Aaron Bertrand. Microsoft si rifiuta di risolverne alcuni, e il mio sospetto segreto è che MS abbia deprecato l'intero servizio MS Connect solo per cercare di farci dimenticare tutti quei bug nell'istruzione MERGE che non vogliono correggere.
Ingegnere inverso

14

Forse qualcuno che utilizza MS SQL Server 2005 o inferiore troverà utile questa risposta.


MERGE funzionerà solo per SQL Server 2008 o versioni successive. Per il resto ho trovato un'altra soluzione alternativa che ti darà la possibilità di creare tipi di tabelle di mappatura.

Ecco come apparirà la risoluzione per SQL 2005:

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

Il trucco principale è che stiamo riempiendo due volte la tabella di mappatura con i dati ordinati dalla tabella di origine e di destinazione. Per maggiori dettagli qui: Unione di dati inseriti utilizzando OUTPUT in SQL Server 2005


1
Questo non risolverebbe il mio problema. Ho bisogno del Outputmio Insertper un output Tableche includa un Identityvalore dalla destinazione Tablecon un Insertvalore non -ed (il PK) dalla sorgente Table(btw, quindi potrei quindi (in un batch diverso) utilizzare quell'output Tableper popolare un Columnin la fonte Tablecon il Identityvalore). Senza a Merge, immagino che dovrei: a) iniziare Transaction, b) ottenere il prossimo Identity, c) inserire in temp Tablecon calcolato Identity, d) set Identity_Insert, e) Insertin target Tableda temp Table, f) clear Identity_Insert.
Tom
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.