Insira no select em várias tabelas relacionadas usando INSERT_IDENTITY

10

Ok, definindo a cena. Eu tenho três tabelas, ( Table1, Table2e DataTable) e eu quero inserir Table1e Table2usar DataTablecomo fonte. Portanto, para cada linha em que DataTableeu quero uma linha Table1e Table2, e Table2precisa ter o id(PK) inserido de Table1...

Se eu fosse fazer isso ...

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

Eu obteria IDo último registro inserido Table1.

A CURSORou WHILEloop é a única maneira de fazer isso?

m4rc
fonte

Respostas:

10

Uma solução que pode funcionar para você é usar a cláusula OUTPUT, que cospe todas as linhas inseridas, para que você possa inseri-las novamente em uma tabela diferente. No entanto, isso limita as restrições de chave estrangeira na Tabela2, se a memória servir.

De qualquer forma, a solução seria mais ou menos assim:

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, ao contrário de outras instruções DML, pode fazer referência a outras tabelas além de just insertede deleted, o que é útil aqui.

Mais: http://sqlsunday.com/2013/08/04/cool-merge-features/

Daniel Hutmacher
fonte
4

Se isso é algo que você planeja fazer regularmente (isto é, faz parte da lógica do aplicativo e não um exercício único de transformação de dados), você pode usar uma visualização na Tabela1 e na Tabela2 com um INSTEAD OF INSERTgatilho para gerenciar a divisão dos dados (e organizar as chaves / relacionamentos) - então você faria:

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

e o gatilho pode ser tão simples quanto:

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

assumindo que a visualização é algo como:

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

ou se houver linhas em cada tabela sem as linhas correspondentes na outra:

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;

(é claro que linhas são exibidas quando você, SELECTna visualização, não é importante se você não pretende SELECTe só existe para fornecer um modelo INSERTpara o gatilho fazer sua mágica)

Isso pressupõe que você pretenda usar um tipo de UUID para sua chave primária nesse caso - se você estiver usando uma chave inteira de incremento automático na tabela1, há um pouco mais de trabalho a fazer. Algo como o seguinte pode 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

e, de fato, esse par de INSERTinstruções pode funcionar diretamente como um caso único (se você está usando um INT IDENTITYou UNIQUEIDENTIFIER DEFAULT NEWID()tipo para a chave):

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 a necessidade da visão e acionar completamente, embora, se esta for uma operação que você esteja realizando com frequência em seu código, o mecanismo de visão + ainda valha a pena considerar para abstrair a necessidade de várias instruções a cada vez.

CAVEAT: todo o SQL acima foi digitado por pensamento e não foi testado; ele precisará de trabalho antes que haja qualquer garantia de que ele funcionará conforme necessário.

David Spillett
fonte
3

Parece que você quer:

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

Ou talvez apenas use uma tabela, se você sempre terá uma linha em cada tabela ... você tem um bom motivo para dividi-las em várias tabelas?

Aaron Bertrand
fonte
11
O sistema estava em vigor antes de eu trabalhar no projeto e o SE responsável queria experimentar a herança da tabela, o que é bom se você estiver usando o Entity Framework e fazendo coisas com o código, porque ele esconde tudo de você, mas quando você precisa mudar para a ADO por causa do fraco desempenho, é um pesadelo!
M4rc
1

Ao ler sua pergunta e os comentários das outras respostas, parece que você está tentando resolver um problema DataTable, dividindo-o em duas novas tabelas.

Suponho que DataTableainda não tenha um único campo único, como um IDENTITY(1,1)? Caso contrário, talvez você deva adicionar um que possa ser usado para inserir dados no Table1e Table2.

A título de exemplo; Criei um esquema de amostra, inseri dados de teste DataTable, modifiquei DataTablepara ter uma IDENTITY(1,1)coluna e usei isso para inserir dados em ambos Table1e 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
fonte
-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

Isso funcionou para mim, eu sei que é uma resposta muito tardia, mas pode ajudar os outros. Eu costumava IDENT_CURRENTobter a identidade da linha da inserção anterior, mas para mim é sempre uma linha.

Muhammad Waqas Aziz
fonte