¿Es posible que la cláusula de salida de SQL devuelva una columna que no se inserta?

123

Hice algunas modificaciones en mi base de datos y necesito migrar los datos antiguos a las nuevas tablas. Para eso, necesito llenar una tabla (ReportOptions) tomando los datos de la tabla original (Practice) y llenar una segunda tabla 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...)

Hice una consulta para obtener todos los datos que necesito para pasar de Practice a ReportOptions, pero tengo problemas para llenar la tabla 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

Si pudiera hacer referencia a un campo que no está en la tabla de destino en la cláusula OUTPUT, sería genial (creo que no puedo, pero no estoy seguro). ¿Alguna idea sobre cómo lograr mi necesidad?

Alejandro B.
fuente
1
Puede devolver cualquiera de las columnas de la tabla en la que ha insertado una fila, en su OUTPUTcláusula. Entonces, incluso si no proporciona un valor para una columna determinada en su INSERTdeclaración, aún puede especificar esa columna en la OUTPUTcláusula. Sin embargo, no puede devolver columnas o variables SQL de otras tablas.
marc_s
2
@marc_s gracias por su respuesta, pero no tengo el campo que necesito en la tabla de destino (necesito PracticeId, que no está en ReportOption)
Alejandro B.

Respuestas:

191

Puede hacer esto usando en MERGElugar de insertar:

así que reemplaza esto

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 clave es usar un predicado que nunca será verdadero (1 = 0) en la condición de búsqueda de combinación, por lo que siempre realizará la inserción, pero tendrá acceso a los campos en las tablas de origen y destino.


Aquí está el código completo que usé para probarlo:

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 

Más lectura, y la fuente de todo lo que sé sobre el tema está aquí.

GarethD
fuente
2
¡Gracias, esto funciona! Iba a usar un campo temporal falso, pero este es mucho más elegante.
Alejandro B.
1
¡Excelente! ¡Este truco es un grano de oro! ¡Agregado a la primera fila de la colección!
Vadim Loboda
1
Suweet! Ojalá no tuviera que usar el comando MERGE ocasionalmente con errores, pero de lo contrario es perfectamente elegante.
Tab Alleman
4
Tenga cuidado. Usé una declaración de combinación que durante el año pasado ha crecido con el uso. Comenzamos a tener tiempos de espera durante los guardados y resultó que debido a que la declaración de combinación siempre bloquea las mesas, teníamos entre 35 y 160 segundos de bloqueo de la mesa cada 4 minutos. Tengo que reconstruir varias declaraciones de combinación para usar insertar / actualizaciones y limitar el número de filas que actualizan a 500 por inserción / actualización para evitar el bloqueo de la tabla. Estimo que esta tabla muy importante se mantuvo bloqueada casi 2 1/2 horas por día, lo que provocó todo, desde guardados lentos hasta tiempos de espera.
CubeRoot
3
Además, un factor decisivo para muchos es que MERGE tiene una gran cantidad de errores sin corregir, que surgen en condiciones extrañas. Por ejemplo, consulte este artículo de Aaron Bertrand. Microsoft se niega a arreglar algunos de ellos, y mi sospecha secreta es que MS desaprobó todo su servicio MS Connect solo para tratar de hacernos olvidar todos esos errores en la declaración MERGE que no quieren arreglar.
Ingeniero
14

Quizás alguien que use MS SQL Server 2005 o versiones anteriores encuentre útil esta respuesta.


MERGE funcionará solo para SQL Server 2008 o superior. Para descansar, encontré otra solución que le dará la posibilidad de crear tipos de tablas de mapeo.

Así es como se verá la resolución para 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

El truco principal es que estamos llenando la tabla de mapeo dos veces con datos ordenados de la tabla de origen y destino. Para obtener más detalles aquí: Fusionar datos insertados usando OUTPUT en SQL Server 2005

Val
fuente
1
Esto no resolvería mi problema. Necesito Outputmis Insert's de una salida Tableque incluye un Identityvalor de la meta Tablecon un no- Insertvalor -ed (PK) de la fuente Table(por cierto, por lo que pude entonces (en un lote diferente) el uso que la producción Tablepara poblar una Columnde la fuente Tablecon el Identityvalor). Sin a Merge, imagino que tendría que: a) comenzar Transaction, b) obtener el siguiente Identity, c) Insertar en temp Tablecon calculado Identity, d) establecer Identity_Insert, e) Inserten objetivo Tabledesde temp Table, f) borrar Identity_Insert.
Tom