Insertar en select en múltiples tablas relacionadas usando INSERT_IDENTITY

10

Está bien preparando la escena. Tengo tres tablas, ( Table1, Table2y DataTable) y quiero insertarlas Table1y Table2usarlas DataTablecomo fuente. Entonces, para cada fila en la DataTableque quiero una fila Table1y Table2, y necesito Table2tener el insertado id(PK) de Table1...

Si tuviera que hacer esto ...

INSERT INTO Table1 SELECT A, B, C FROM MyTable
INSERT INTO Table2 SELECT IDENTITY_INSERT(), D, E, F FROM MyTable

Me gustaría obtener el IDúltimo registro insertado Table1.

¿Es un bucle CURSORo WHILElas únicas formas de hacer esto?

m4rc
fuente

Respuestas:

10

Una solución que podría funcionar para usted es usar la cláusula OUTPUT, que escupe todas las filas insertadas, para que pueda volver a insertarlas en una tabla diferente. Sin embargo, esto pone limitaciones a las restricciones de clave externa en Table2, si la memoria sirve.

De todos modos, la solución se vería así:

MERGE INTO Table1 AS t1
USING MyTable ON 1=0 -- always generates "not matched by target"

WHEN NOT MATCHED BY TARGET THEN
    -- INSERT into Table1:
    INSERT (A, B, C) VALUES (t1.A, t1.B, t1.C)

--- .. and INSERT into Table2:
OUTPUT inserted.ID, MyTable.D, MyTable.E, MyTable.F
INTO Table2 (ID, D, E, F);

MERGE, a diferencia de las otras declaraciones DML, puede hacer referencia a otras tablas además de insertedy deleted, lo cual es útil para usted aquí.

Más: http://sqlsunday.com/2013/08/04/cool-merge-features/

Daniel Hutmacher
fuente
4

Si esto es algo que planea hacer regularmente (es decir, es parte de la lógica de la aplicación y no un ejercicio de transformación de datos único), entonces podría usar una vista en Tabla1 y Tabla2 con un INSTEAD OF INSERTdisparador para administrar la división de los datos (y organizar las claves / relaciones), entonces simplemente harías:

INSERT newView SELECT NEWID(), A, B, C, D, E, F FROM MyTable

y el desencadenante podría ser tan simple como:

CREATE trg_newview_insert TRIGGER newView INSTEAD OF UPDATE AS 
    INSERT table1 SELECT ID, A, B, C FROM inserted
    INSERT table2 SELECT ID, D, E, F FROM inserted
GO

asumiendo que la vista es algo como:

CREATE VIEW newView AS 
SELECT table1.ID, A, B, C, D, E, F 
FROM table1 
    JOIN table2 ON table1.ID = table2.ID;

o si puede haber filas en cada tabla sin filas coincidentes en la otra:

CREATE VIEW newView AS 
SELECT ISNULL(table1.ID, table2.ID), A, B, C, D, E, F 
FROM table1 
    FULL OUTER JOIN table2 ON table1.ID = table2.ID;

(por supuesto, qué filas se generan cuando usted SELECTdesde la vista no es importante si no tiene la intención SELECTde hacerlo y solo existe para proporcionar una plantilla INSERTpara que el disparador haga su magia)

Esto supone que tiene la intención de utilizar un tipo de UUID para su clave principal en este caso; si está utilizando una clave entera que se incrementa automáticamente en la tabla 1, hay un poco más de trabajo por hacer. Algo como lo siguiente podría funcionar:

CREATE trg_newview_insert TRIGGER newView INSTEAD OF UPDATE AS 
    INSERT table1 (A, B, C) 
    SELECT A, B, C 
    FROM inserted;
    INSERT table2 (ID, D, E, F) 
    SELECT ID, D, E, F 
    FROM table1 AS t 
        JOIN inserted AS i ON t.A = i.A AND t.B = i.B AND t.C = i.C;
GO

y, de hecho, ese par de INSERTdeclaraciones podría funcionar directamente de manera única (ya sea que esté usando un INT IDENTITYo UNIQUEIDENTIFIER DEFAULT NEWID()escriba la clave):

INSERT table1 (A, B, C) 
SELECT A, B, C 
FROM MyTable;
INSERT table2 (ID, D, E, F) 
SELECT ID, D, E, F 
FROM table1 AS t 
    JOIN MyTable AS i ON t.A = i.A AND t.B = i.B AND t.C = i.C;

negando la necesidad de la vista y el disparador por completo, aunque si esta es una operación que realizará a menudo en su código, vale la pena considerar la vista + disparador para resumir la necesidad de múltiples declaraciones cada vez.

PRECAUCIÓN: todo el SQL anterior se ha escrito de forma pensada y no se ha probado, necesitará trabajo antes de que haya alguna garantía de que funcionará según lo necesite.

David Spillett
fuente
3

Parece que quieres:

INSERT dbo.Table1(A,B,C) SELECT A,B,C 
  FROM dbo.DataTable WHERE <identify one row>;

INSERT dbo.Table2(ID,D,E,F) SELECT SCOPE_IDENTITY(),D,E,F
  FROM dbo.DataTable WHERE <identify that same row>;

O tal vez solo use una tabla, si siempre va a tener una fila en cada tabla ... ¿tiene una buena razón para dividirlas en varias tablas?

Aaron Bertrand
fuente
1
El sistema estaba en su lugar antes de que trabajara en el proyecto y el SE a cargo quería probar la herencia de la tabla, lo cual está bien si está usando Entity Framework y haciendo cosas del código porque lo oculta todo, pero cuando tiene que cambiar a ADO por el bajo rendimiento, ¡es una pesadilla!
m4rc
1

Al leer su pregunta y los comentarios sobre las otras respuestas, parece que está intentando solucionar un problema DataTabledividiéndolo en dos tablas nuevas.

Supongo que DataTabletodavía no tiene un único campo único como un IDENTITY(1,1)? De lo contrario, quizás debería agregar uno que podría usar para insertar datos en Table1y Table2.

A modo de ejemplo; Creé un esquema de muestra, inserté datos de prueba DataTable, modifiqué DataTablepara tener una IDENTITY(1,1)columna, luego los usé para insertar datos en ambos Table1y Table2:

USE tempdb;
GO

CREATE TABLE dbo.DataTable
(
    A INT
    , B INT
    , C INT
    , D INT
    , E INT
    , F INT
);

INSERT INTO dbo.DataTable (A, B, C, D, E, F)
VALUES (1, 2, 3, 11, 12, 13)
    , (4, 5, 6, 14, 15, 16)
    , (7, 8, 9, 17, 18, 19);

CREATE TABLE dbo.Table1
(
    Table1PK INT NOT NULL CONSTRAINT PK_Table1 PRIMARY KEY CLUSTERED IDENTITY(1,1)
    , A INT
    , B INT
    , C INT
);

CREATE TABLE dbo.Table2
(
    Table2PK INT NOT NULL CONSTRAINT PK_Table2 PRIMARY KEY CLUSTERED IDENTITY(1,1)
    , Table1PK INT NOT NULL CONSTRAINT FK_Table2_Table1_PK FOREIGN KEY REFERENCES dbo.Table1(Table1PK)
    , D INT
    , E INT
    , F INT
);

ALTER TABLE dbo.DataTable ADD TempCol INT NOT NULL IDENTITY(1,1);

SET IDENTITY_INSERT dbo.Table1 ON;

INSERT INTO Table1 (Table1PK, A, B, C)
SELECT TempCol, A, B, C 
FROM DataTable;

SET IDENTITY_INSERT dbo.Table1 OFF;

INSERT INTO Table2 
SELECT Table1PK, D, E, F 
FROM dbo.DataTable DT
    INNER JOIN dbo.Table1 T ON DT.TempCol = T.Table1PK;

SELECT *
FROM dbo.Table1;

SELECT *
FROM dbo.Table2;
Max Vernon
fuente
-1
INSERT INTO VouchersOtherDetail (
                                 [VouNo],
                                 [Location],
                                 [VouType],
                                 [VouDate],
                                 [InputDate],
                                 [CrossRefGoodsVouNo],
                                 [Reversed],
                                 [ReversalReference],
                                 [UserID]
                                 ) 
SELECT   
                                [VouNo],
                                [Location],
                                [VouType],
                                [VouDate],
                                [InputDate],
                                [CrossRefGoodsVouNo],
                                [Reversed],
                                [ReversalReference],
                                [UserID]
FROM @InsertTableForVoucherDetail           

INSERT INTO VouchersDrCrDetail (
                                [VouID],
                                [AccountCode],
                                [CrossReferAccountCode],
                                [Description],
                                [VouDrAmount],
                                [VouCrAmount],
                                [RunningBalance]
                               )
SELECT  -- IDENT_CURRENT to get the identity of row from previous insert
                                 IDENT_CURRENT('VouchersOtherDetail'), 
                                 [AccountCode],
                                 [CrossReferAccountCode],
                                 [Description],
                                 [VouDrAmount],
                                 [VouCrAmount],
                                 [RunningBalance]
FROM @InsertTableForDrAndCR

Esto funcionó para mí, sé que es una respuesta muy tardía, pero puede ayudar a otros. Solía IDENT_CURRENTobtener la identidad de la fila de la inserción anterior, pero para mí siempre es una fila.

Muhammad Waqas Aziz
fuente