Est-il possible pour la clause SQL Output de renvoyer une colonne non insérée?

123

J'ai apporté quelques modifications à ma base de données et je dois migrer les anciennes données vers les nouvelles tables. Pour cela, je dois remplir un tableau (ReportOptions) en prenant les données du tableau d'origine (Practice), et remplir un second tableau intermédiaire (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...)

J'ai fait une requête pour obtenir toutes les données dont j'ai besoin pour passer de Practice à ReportOptions, mais j'ai du mal à remplir le tableau intermédiaire

--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 je pouvais référencer un champ qui n'est pas sur la table de destination sur la clause OUTPUT, ce serait génial (je pense que je ne peux pas, mais je ne sais pas avec certitude). Des idées pour répondre à mon besoin?

Alejandro B.
la source
1
Vous pouvez renvoyer l'une des colonnes de la table dans laquelle vous avez inséré une ligne, dans votre OUTPUTclause. Ainsi, même si vous ne fournissez pas de valeur pour une colonne donnée dans votre INSERTinstruction, vous pouvez toujours spécifier cette colonne dans la OUTPUTclause. Vous ne pouvez cependant pas renvoyer de variables SQL ou de colonnes d'autres tables.
marc_s
2
@marc_s merci pour votre réponse, mais je n'ai pas le champ dont j'ai besoin dans la table de destination (j'ai besoin de PracticeId, qui n'est pas sur ReportOption)
Alejandro B.

Réponses:

191

Vous pouvez le faire en utilisant MERGEau lieu d'insérer:

alors remplacez ça

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

avec

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 clé est d'utiliser un prédicat qui ne sera jamais vrai (1 = 0) dans la condition de recherche de fusion, donc vous effectuerez toujours l'insertion, mais aurez accès aux champs des tables source et de destination.


Voici le code complet que j'ai utilisé pour le tester:

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 

Plus de lecture, et la source de tout ce que je sais sur le sujet est ici

GarethD
la source
2
Merci, ça fait l'affaire! J'allais utiliser un faux champ temporaire mais c'est beaucoup plus élégant.
Alejandro B.
1
Excellent! Cette astuce est un grain d'or! Ajouté à la première ligne de la collection!
Vadim Loboda
1
Suweet! J'aurais aimé qu'il n'ait pas besoin d'utiliser la commande MERGE parfois boguée, mais c'est parfaitement élégant autrement.
Tab Alleman
4
Être averti. J'ai utilisé une déclaration de fusion qui, au cours de l'année écoulée, a augmenté avec l'utilisation. Nous avons commencé à avoir des délais d'expiration pendant les sauvegardes et il s'est avéré que, parce que l'instruction de fusion verrouille toujours les tables, nous avions 35 à 160 secondes de verrouillage de table toutes les 4 minutes. Je dois reconstruire plusieurs instructions de fusion pour utiliser insert / updates et limiter le nombre de lignes qu'elles mettent à jour à 500 par insertion / mise à jour pour éviter le verrouillage de table. J'estime que cette table très importante était maintenue verrouillée près de 2 heures et demie par jour, ce qui causait tout, des sauvegardes lentes aux délais d'attente.
CubeRoot
3
En outre, un facteur décisif pour beaucoup est que MERGE contient de nombreux bogues non résolus, qui apparaissent dans des conditions étranges. Par exemple, voir cet article d'Aaron Bertrand. Microsoft refuse de corriger certains d'entre eux, et je soupçonne secrètement que MS a déprécié tout son service MS Connect juste pour essayer de nous faire oublier tous ces bogues dans l'instruction MERGE qu'ils ne veulent pas corriger.
Ingénieur inversé le
14

Peut-être que quelqu'un qui utilise MS SQL Server 2005 ou une version antérieure trouvera cette réponse utile.


MERGE fonctionnera uniquement pour SQL Server 2008 ou version ultérieure. Pour le reste, j'ai trouvé une autre solution de contournement qui vous donnera la possibilité de créer des types de tables de mappage.

Voici à quoi ressemblera la résolution pour 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

L'astuce principale est que nous remplissons la table de mappage deux fois avec des données ordonnées de la table source et de destination. Pour plus de détails ici: Fusion de données insérées à l'aide de OUTPUT dans SQL Server 2005

Val
la source
1
Cela ne résoudrait pas mon problème. J'ai besoin de Outputmon Insert's vers une sortie Tablequi inclut une Identityvaleur de la cible Tableavec une Insertvaleur non -ed (le PK) de la source Table(btw, donc je pourrais alors (dans un autre lot) utiliser cette sortie Tablepour remplir un Columnin la source Tableavec la Identityvaleur). Sans a Merge, j'imagine que je devrais: a) démarrer Transaction, b) passer au suivant Identity, c) insérer dans temp Tableavec calculé Identity, d) définir Identity_Insert, e) Insertdans la cible Tablede temp Table, f) effacer Identity_Insert.
Tom