Usando colunas de origem na cláusula OUTPUT INTO de uma instrução INSERT (SQL Server)

16

Estou escrevendo uma instrução de inserção de processamento em lote e gostaria de usar uma tabela temporária para controlar os IDs inseridos em vez de fazer um loop entre os itens e chamar SCOPE_IDENTITY () para cada linha inserida.

Os dados que precisam ser inseridos têm IDs (temporários) vinculando-os a outros dados que também devem ser inseridos em outra tabela. Por isso, preciso de uma referência cruzada do ID real e do ID temporário.

Este é um exemplo do que tenho até agora:

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

O problema é que não consigo que a cláusula OUTPUT INTO aceite o ID, tentei @MyInsertData.IDe outros truques unindo a tabela a ela mesma, mas nada parece funcionar.

Louis Somers
fonte

Respostas:

27

Na verdade, você pode conseguir a mesma coisa alterando sua INSERTpara a MERGE. Embora a MERGEdeclaração seja realmente uma maneira bem interessante de fazer "upserts" no SQL Server, não há nada que o impeça de usá-la apenas com a finalidade de inserir:

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

Uma das coisas legais MERGEé que ele permite acessar as colunas de origem , bem como as tabelas insertede as incorporadas deletedna OUTPUTcláusula.

Meu código pode conter erros, pois ainda não o testei. Minha postagem de blog de alguns anos atrás entra em um pouco mais de detalhes, inclusive no desempenho de consultas.

Daniel Hutmacher
fonte
Isso é ótimo! Desta vez, terei que manter um loop, pois é necessário suporte ao SQL Server 2005. No entanto, vou ter isso em mente para projetos futuros. Obrigado!
Louis Somers
3
Brilhante, essa deve ser a resposta aceita.
Chris Peacock
3
Gostaria que isso estivesse no stackoverflow em vez de dba stackexchange. Tem muito pouca visibilidade. Resposta incrível.
Lordbalmon
3
Funcionou como um encanto desde a primeira tentativa ... resposta incrível!
BeemerGuy
5

A cláusula de saída pode acessar apenas dados nas linhas e constantes / variáveis ​​de destino, e não dados de outros lugares na origem SELECT, como se você estivesse operando em um gatilho.

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

Qualquer referência a colunas na tabela que está sendo modificada deve ser qualificada com o prefixo INSERTED ou DELETED.

Portanto, para obter o ID original, você deve incluí-lo na tabela de destino para que a cláusula de saída possa repetir o processo da seguinte forma:

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

embora alterar o esquema do objeto de destino possa não ser prático em sua situação, isso pode não ser aplicável.

David Spillett
fonte
3
Isso é uma decepção. Isso torna a cláusula de saída inútil para o meu cenário, a menos que exista uma segunda coluna que possa ser usada como chave :-( Oh, bem, pelo menos eu posso parar de tentar e continuar com o loop. Obrigado.
Louis Somers