Uso de columnas de origen en la cláusula OUTPUT INTO de una instrucción INSERT (SQL Server)

15

Estoy escribiendo una declaración de inserción de procesamiento por lotes y me gustaría utilizar una tabla temporal para realizar un seguimiento de las ID insertadas en lugar de recorrer los elementos yo mismo y llamar a SCOPE_IDENTITY () para cada fila insertada.

Los datos que deben insertarse tienen ID (temporales) que lo vinculan con otros datos que también deberían insertarse en otra tabla, por lo que necesito una referencia cruzada del Id real y el Id temporal.

Este es un ejemplo de lo que tengo hasta ahora:

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

El problema es que no puedo obtener la cláusula OUTPUT INTO para aceptar la ID, lo he intentado @MyInsertData.IDy otros trucos que unen la tabla, pero parece que nada funciona.

Louis Somers
fuente

Respuestas:

27

En realidad, puedes lograr lo mismo cambiando tu INSERTa MERGE. Si bien la MERGEdeclaración es en realidad una forma bastante ordenada de hacer "upserts" en SQL Server, no hay nada que le impida usarla solo con el propósito de insertar:

-- 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 de las cosas buenas de esto MERGEes que le permite acceder a las columnas de origen , así como a las tablas insertedy la deletedtabla incorporada OUTPUT.

Mi código puede contener errores, ya que no lo he probado. Mi publicación de blog de hace unos años entra en un poco más de detalle, incluido el rendimiento de las consultas.

Daniel Hutmacher
fuente
¡Eso es genial! Esta vez tendré que seguir un ciclo porque se requiere soporte para SQL Server 2005. Sin embargo, tendré esto en cuenta para futuros proyectos. ¡Gracias!
Louis Somers
3
Brillante, esta debería ser la respuesta aceptada.
Chris Peacock
3
Ojalá esto estuviera en stackoverflow en lugar de dba stackexchange. Tiene muy poca visibilidad. Respuesta asombrosa
Lordbalmon
3
Funcionó como un encanto desde el primer intento ... ¡respuesta increíble!
BeemerGuy
5

La cláusula de salida solo puede acceder a datos en las filas de destino y constantes / variables, no a datos de otras partes de la fuente SELECT, como si estuviera operando en un disparador.

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

Cualquier referencia a columnas en la tabla que se está modificando debe calificarse con el prefijo INSERTED o DELETED.

Por lo tanto, para obtener la ID original, deberá incluirla en la tabla de destino para que la cláusula de salida pueda repetirla, así:

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

aunque alterar el esquema del objeto de destino puede no ser práctico en su situación, por lo que esto puede no ser aplicable.

David Spillett
fuente
3
Eso es una gran decepción. Eso hace que la cláusula de salida sea inútil para mi escenario a menos que haya una segunda columna que pueda usarse como clave :-( Oh, bueno, al menos puedo dejar de intentarlo y seguir con el bucle. Gracias.
Louis Somers,