Utilizzo delle colonne di origine nella clausola OUTPUT INTO di un'istruzione INSERT (SQL Server)


16

Sto scrivendo un'istruzione insert di elaborazione batch e vorrei utilizzare una tabella temporanea per tenere traccia degli ID inseriti anziché eseguire il ciclo degli elementi e chiamare SCOPE_IDENTITY () per ogni riga inserita.

I dati che devono essere inseriti hanno un ID (temporaneo) che li collega ad altri dati che dovrebbero anche essere inseriti in un'altra tabella, quindi ho bisogno di un riferimento incrociato tra l'ID effettivo e l'ID temporaneo.

Questo è un esempio di ciò che ho finora:

-- The existing table 
DECLARE @MyTable TABLE (ID INT IDENTITY(1,1), [Name] NVARCHAR(MAX));

-- My data I want to insert
DECLARE @MyInsertData TABLE (ID INT, [Name] NVARCHAR(MAX));
INSERT INTO @MyInsertData ( ID,Name)
VALUES ( -1 , 'bla'),(-2,'test'),(-3,'last');

DECLARE @MyCrossRef TABLE ([NewId] INT, OldId INT);

INSERT INTO @MyTable ( [Name] )
   OUTPUT Inserted.ID, INS.ID INTO @MyCrossRef
   SELECT [NAME] FROM @MyInsertData INS

-- Check the result
SELECT * FROM @MyCrossRef

Il problema è che non riesco a ottenere la clausola OUTPUT INTO per accettare l'ID, ho provato @MyInsertData.IDe altri trucchi per unire la tabella a se stessa, ma nulla sembra funzionare.

Risposte:


27

In realtà, puoi ottenere la stessa cosa cambiando INSERTin a MERGE. Mentre l' MERGEistruzione è in realtà un modo abbastanza semplice per fare "upsert" in SQL Server, non c'è nulla che ti impedisca di usarlo solo allo scopo di inserire:

-- The existing table 
DECLARE @MyTable TABLE (ID INT IDENTITY(1,1), [Name] NVARCHAR(MAX));

-- My data I want to insert
DECLARE @MyInsertData TABLE (ID INT, [Name] NVARCHAR(MAX));
INSERT INTO @MyInsertData ( ID,Name)
VALUES ( -1 , 'bla'),(-2,'test'),(-3,'last');

DECLARE @MyCrossRef TABLE ([NewId] INT, OldId INT);

MERGE INTO @MyTable AS dest
USING @MyInsertData AS ins ON 1=0   -- always false

WHEN NOT MATCHED BY TARGET          -- happens for every row, because 1 is never 0
    THEN INSERT ([Name])
         VALUES (ins.[NAME])

OUTPUT inserted.ID, ins.ID
INTO @MyCrossRef (NewId, OldId);

-- Check the result
SELECT * FROM @MyCrossRef

Una delle cose belle di questo MERGEè che ti consente di accedere alle colonne di origine , nonché ai built-in insertede alle deletedtabelle nella OUTPUTclausola.

Il mio codice potrebbe contenere errori, in quanto non l'ho ancora testato. Il mio post sul blog di alcuni anni fa è un po 'più dettagliato, incluso sulle prestazioni delle query.


È fantastico! Questa volta dovrò attenermi a un ciclo perché è richiesto il supporto per SQL Server 2005. Tuttavia, lo terrò presente per i progetti futuri. Grazie!
Louis Somers,

3
Fantastico, questa dovrebbe essere la risposta accettata.
Chris Peacock,

3
Vorrei che questo fosse in StackOverflow invece di DBA StackExchange. Ha una visibilità troppo scarsa. Risposta incredibile.
Lordbalmon,

3
Ha funzionato come un fascino dal primo tentativo ... risposta fantastica!
Beemer

5

La clausola di output può accedere solo ai dati nelle righe di destinazione e alle costanti / variabili, non ai dati provenienti da altre parti dell'origine SELECT, come se si stesse operando in un trigger.

https://docs.microsoft.com/en-us/sql/t-sql/queries/output-clause-transact-sql afferma:

Qualsiasi riferimento alle colonne nella tabella da modificare deve essere qualificato con il prefisso INSERTED o DELETED.

Quindi, per ottenere l'ID originale, è necessario includerlo nella tabella di destinazione in modo che la clausola di output possa riecheggiarlo, in questo modo:

-- The existing table 
DECLARE @MyTable TABLE (ID INT IDENTITY(1,1), [Name] NVARCHAR(MAX), SourceID INT);

-- My data I want to insert
DECLARE @MyInsertData TABLE (ID INT, [Name] NVARCHAR(MAX));
INSERT INTO @MyInsertData ( ID,Name)
VALUES ( -1 , 'bla'),(-2,'test'),(-3,'last');

DECLARE @MyCrossRef TABLE ([NewId] INT, OldId INT);

INSERT INTO @MyTable ( [Name], SourceID )
   OUTPUT Inserted.ID, Inserted.SourceID INTO @MyCrossRef
   SELECT [NAME], ID FROM @MyInsertData INS

-- Check the result
SELECT * FROM @MyCrossRef

sebbene la modifica dello schema dell'oggetto target potrebbe non essere pratica nella tua situazione, questo potrebbe non essere applicabile.


3
È una vera delusione. Ciò rende inutile la clausola di output per il mio scenario a meno che non ci sia una seconda colonna che può essere utilizzata come chiave :-( Oh, beh, almeno posso smettere di provare e andare avanti con il loop. Grazie.
Louis Somers,
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.