É possível que a cláusula SQL Output retorne uma coluna que não está sendo inserida?

123

Fiz algumas modificações no meu banco de dados e preciso migrar os dados antigos para as novas tabelas. Para isso, preciso preencher uma tabela (ReportOptions) usando os dados da tabela original (Practice) e preencher uma segunda tabela intermediária (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...)

Fiz uma consulta para obter todos os dados necessários para passar do Practice para o ReportOptions, mas estou tendo problemas para preencher a tabela intermediária

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

Se eu pudesse fazer referência a um campo que não está na tabela de destino na cláusula OUTPUT, seria ótimo (acho que não posso, mas não tenho certeza). Alguma idéia de como realizar minha necessidade?

Alejandro B.
fonte
1
Você pode retornar qualquer uma das colunas da tabela na qual você inseriu uma linha, na sua OUTPUTcláusula. Portanto, mesmo se você não fornecer um valor para uma determinada coluna em sua INSERTinstrução, ainda poderá especificar essa coluna na OUTPUTcláusula No entanto, você não pode retornar variáveis ​​ou colunas SQL de outras tabelas.
21868 Marc Mar
2
@marc_s obrigado pela sua resposta, mas eu não tenho o campo eu preciso na tabela de destino (que eu preciso PracticeId, que não está no ReportOption)
Alejandro B.

Respostas:

191

Você pode fazer isso usando em MERGEvez de inserir:

então substitua isso

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

com

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

A chave é usar um predicado que nunca será verdadeiro (1 = 0) na condição de pesquisa de mesclagem, para que você sempre execute a inserção, mas tenha acesso aos campos nas tabelas de origem e de destino.


Aqui está o código inteiro que eu usei para testá-lo:

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 

Mais leitura e a fonte de tudo o que sei sobre o assunto está aqui

GarethD
fonte
2
Obrigado, isso faz o truque! Eu usaria um campo temporário falso, mas isso é muito mais elegante.
21311 Alejandro B.Jun
1
Excelente! Esse truque é um grão de ouro! Adicionado à primeira linha da coleção!
Vadim Loboda 24/09/2013
1
Suweet! Eu gostaria que ele não tivesse que usar o comando MERGE ocasionalmente com erros, mas é perfeitamente elegante caso contrário.
Tab Alleman
4
Esteja avisado. Eu usei uma declaração de mesclagem que ao longo do ano passado cresceu com o uso. Começamos a ter tempos limite durante salvamentos e, como a instrução de mesclagem sempre bloqueia as tabelas, tínhamos 35-160 segundos de bloqueio a cada 4 minutos. Estou tendo que reconstruir várias instruções de mesclagem para usar inserções / atualizações e limitar o número de linhas atualizadas para 500 por inserção / atualização para evitar o bloqueio de tabelas. Eu estimo que esta tabela muito importante estava sendo mantida trancada quase duas horas e meia por dia, o que causava de tudo, de lentidão a intervalos.
CubeRoot
3
Além disso, um fator decisivo para muitos é que o MERGE possui muitos bugs não corrigidos, que surgem sob condições estranhas. Por exemplo, consulte este artigo de Aaron Bertrand. A Microsoft se recusa a consertar alguns deles, e minha suspeita secreta é que a MS reprovou todo o serviço MS Connect apenas para tentar nos fazer esquecer todos os bugs na instrução MERGE que eles não desejam corrigir.
Engenheiro reverso
14

Talvez alguém que use o MS SQL Server 2005 ou inferior ache essa resposta útil.


MERGE funcionará apenas para o SQL Server 2008 ou superior. Para descansar, encontrei outra solução alternativa que lhe permitirá criar tipos de tabelas de mapeamento.

Veja como será a resolução do 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

O principal truque é que estamos preenchendo a tabela de mapeamento duas vezes com dados ordenados da tabela de origem e destino. Para obter mais detalhes aqui: Mesclando dados inseridos usando OUTPUT no SQL Server 2005

Val
fonte
1
Isso não resolveria o meu problema. Preciso Outputmeus Insert's para uma saída Tableque inclui um Identityvalor a partir do alvo Tablecom um não- Insertvalor -ED (PK) a partir da fonte Table(aliás, de modo que podia, em seguida, (em um lote diferente) que o uso de saída Tablepara preencher uma Columnem a fonte Tablecom o Identityvalor). Sem a Merge, imagino que eu teria que: a) começar Transaction, b) chegar em seguida Identity, c) Inserir em temp Tablecom computado Identity, d) definir Identity_Insert, e) Insertno alvo a Tablepartir de temp Table, f) limpar Identity_Insert.
Tom