7
votes

Fusion de deux parents> Set de table enfant

J'ai besoin d'obtenir les données en deux parents> Ensembles de table enfant fusionnés / combinés dans une tiers parent> table enfant.

Les tables ressemblent à ceci: p>

Structure de table P>

La seule différence dans les trois ensembles de tables est que TableC a une colonne code> TableType > pour discerner la différence entre un enregistrement de table et un enregistrement de tableb. P>

Ma première pensée était d'utiliser un curseur. Voici le code pour créer la structure de la table, insérer des enregistrements, puis fusionner les données ensemble. Cela fonctionne très bien, SOOOOO .... P>

DROP TABLE TableAChild
DROP TABLE TableBChild
DROP TABLE TableCChild

DROP TABLE TableA
DROP TABLE TableB
DROP TABLE TableC
  • On m'a toujours dit que les curseurs sont mauvais. Mais je ne pouvais pas trouver une autre façon de le faire. Je me demande s'il y a une façon de le faire avec un CTE? li>
  • Si le curseur est approprié dans cette situation, comment ai-je fait? Y a-t-il une meilleure façon de faire ce que j'ai fait? Cela ne me semble pas très sec, mais je ne suis pas un expert SQL. li> ul>

    enfin, si vous souhaitez réexécuter la requête ci-dessus, voici un petit script pour supprimer les tables créées. P>

    --Create the tables
    
    CREATE TABLE TableA
    (
        ID int not null identity primary key,
        Name VARCHAR(30)
    );
    
    CREATE TABLE TableAChild
    (
        ID int not null identity primary key,
        Parent int not null,
        Name VARCHAR(30),
        CONSTRAINT FK_A FOREIGN KEY (Parent) REFERENCES TableA(ID)
    );
    
    CREATE TABLE TableB
    (
        ID int not null identity primary key,
        Name VARCHAR(30)
    );
    
    CREATE TABLE TableBChild
    (
        ID int not null identity primary key,
        Parent int not null,
        Name VARCHAR(30),
        CONSTRAINT FK_B FOREIGN KEY (Parent) REFERENCES TableB(ID)
    );
    
    CREATE TABLE TableC
    (
        ID int not null identity primary key,
        TableType VARCHAR(1),
        Name VARCHAR(30)
    );
    
    CREATE TABLE TableCChild
    (
        ID int not null identity primary key,
        Parent int not null,
        Name VARCHAR(30),
        CONSTRAINT FK_C FOREIGN KEY (Parent) REFERENCES TableC(ID)
    );
    
    -- Insert some test records.. 
    
    INSERT INTO TableA (Name) Values ('A1')
    INSERT INTO TableAChild (Name, Parent) VALUES ('A1Child', SCOPE_IDENTITY())
    INSERT INTO TableB (Name) Values ('B1')
    INSERT INTO TableBChild (Name, Parent) VALUES ('B1Child', SCOPE_IDENTITY())
    
    -- Needed throughout.. 
    DECLARE @ID INT
    
    -- Merge TableA and TableAChild into TableC and TableCChild
    DECLARE TableACursor CURSOR
        -- Get the primary key from TableA
        FOR SELECT ID FROM TableA
    OPEN TableACursor
        FETCH NEXT FROM TableACursor INTO @ID
    
        WHILE @@FETCH_STATUS = 0
        BEGIN
            -- INSERT INTO SELECT the parent record into TableC, being sure to specify a TableType
            INSERT INTO TableC (Name, TableType) SELECT Name, 'A' FROM TableA WHERE ID = @ID
    
            -- INSERT INTO SELECT the child record into TableCChild using the parent ID of the last row inserted (SCOPE_IDENTITY())
            -- and the current record from the cursor (@ID).
            INSERT INTO TableCChild(Name, Parent) SELECT Name, SCOPE_IDENTITY() FROM TableAChild WHERE Parent = @ID
    
            FETCH NEXT FROM TableACursor INTO @ID
        END;
    
    CLOSE TableACursor
    DEALLOCATE TableACursor
    
    -- Repeat for TableB
    DECLARE TableBCursor CURSOR
        FOR SELECT ID FROM TableB
    OPEN TableBCursor
        FETCH NEXT FROM TableBCursor INTO @ID
    
        WHILE @@FETCH_STATUS = 0
        BEGIN
            INSERT INTO TableC (Name, TableType) SELECT Name, 'B' FROM TableB WHERE ID = @ID
            INSERT INTO TableCChild(Name, Parent) SELECT Name, SCOPE_IDENTITY() FROM TableBChild WHERE Parent = @ID
            FETCH NEXT FROM TableBCursor INTO @ID
        END;
    
    CLOSE TableBCursor
    DEALLOCATE TableBCursor
    


7 commentaires

Si les champs de noms dans Tablea et tableb sont uniques par table, vous pouvez réécrire ceci sans curseur et simplement utiliser un joindre . Cependant, s'ils n'étaient pas uniques, le curseur est la seule option que je vois pour obtenir le champ ID du parent.


@sgeddes Faites-vous référence aux valeurs dans la colonne Nom? Si oui, ils ne sont pas uniques.


Pourquoi ne pas simplement ajouter une autre colonne à la table pour l'héritage. Cela vous donnera une valeur pour utiliser une jointure. Il n'est pas nécessaire de faire ce genre de rbar.


@Seanlange hrmmm, tu m'as pensé, mais je ne sais pas comment ça marcherait. Il existe des relations entre la colonne TableX.Id et la colonne TableXchild.Parente dans ma base de données (excuses pour ne pas inclure celle de l'échantillon, car je ne pensais pas que c'était nécessaire). Et je ne veux certainement pas laisser derrière une colonne de la héritabilité indésirable. Pouvez-vous expliquer, éventuellement dans une réponse?


Vous pouvez ajouter cette colonne hégacyid comme une chose temporaire. Cela permettrait de l'utiliser pour rejoindre les lignes enfants au nouveau parent. Une fois que vous avez fini de fusionner les données, vous pouvez supprimer cette colonne car elle n'aurait plus de sens.


@Seanlange Votre suggère doit-elle prendre en compte qu'il existe de nouveaux enregistrements d'enfants qui doivent également être créés? J'ai l'impression d'être proche de la compréhension, mais je manque la connexion aux nouveaux enregistrements d'enfants.


FYI, j'ai mis à jour les scripts de création de table pour être plus spécifiques sur les clés et les relations primaires.


6 Réponses :


0
votes

Vous pouvez utiliser une table mappe pour relier les ID anciens et les nouveaux identifiants en fonction de certaines touches.

Dans mon exemple, j'utilise l'ordre d'insertion dans NAPP .

  1. Créez une table de cartes avec une colonne d'identité.
  2. Ajouter des données dans Table de table basé sur l'ordre de ID de Tablea et obtenez les identifiants insérés dans la carte
  3. Utilisez le même ordre de Tablea.ID pour obtenir un ROWNOMBER () et le correspond à la colonne Identity de la table et met à jour le old_id < / Code> sur la carte pour correspondre Tablea.id avec TableC.Id .
  4. Utilisez la carte pour insérer dans la table Tableau
  5. tronquer la carte et rincer et répéter pour d'autres tables.

    requête d'échantillon xxx


2 commentaires

S'il est possible d'ajouter une colonne supplémentaire à la table Napp , vous pouvez utiliser la table elle-même comme carte comme Sean Lange suggéré, puis supprimez la colonne après.


Cette réponse a fonctionné pour produire le résultat souhaité. Il semble plus compliqué que l'approche du curseur. Je vais devoir l'étudier cependant. Et voyons ce que les autres pensent par vote.



1
votes

Voici une façon de le faire sans curseur ni autre type de type RBAR.

ALTER TABLE TableC ADD LegacyID INT
GO

INSERT INTO TableC (TableType, Name, LegacyID)
SELECT 'A', Name, ID
FROM TableA

INSERT TableCChild
SELECT C.ID, AC.Name
FROM TableAChild AC
JOIN TableA A ON A.Id = AC.ID
JOIN TableC C ON C.LegacyID = A.ID AND C.TableType = 'A'

INSERT INTO TableC (TableType, Name, LegacyID)
SELECT 'B', Name, ID
FROM TableB

INSERT TableCChild
SELECT C.ID, AC.Name
FROM TableBChild AC
JOIN TableB A ON A.Id = AC.ID
JOIN TableC C ON C.LegacyID = A.ID AND C.TableType = 'B'

ALTER TABLE TableC DROP COLUMN LegacyID
GO


10 commentaires

Malheureusement, c'est un exemple très incomplet. De plus, TABLEX.ID est une colonne d'identité, vous ne pouvez donc pas insérer des enregistrements. Et vous ne pouvez pas activer l'insertion d'identité, car vous auriez des conflits lors de la fusion des enregistrements de tableb et de tablebchild. Je me sens comme si tu as quelque chose d'aller, alors j'aimerais le voir fini.


Vous n'utilisez même pas legacyid. :)


Je pense que ce qu'il veut dire est à partir de la haleture hachuild AC Rejoignez-ci sur A.Legacyid = AC.ID


Oups. J'ai totalement fouillé ça. Veuillez consulter la mise à jour qui utilise maintenant des tables pour récupérer la nouvelle valeur d'identité.


Ne fonctionne pas sans erreurs. Pouvez-vous utiliser le script que j'ai inclus dans l'OP pour générer des tables et des données pour confirmer que votre réponse fonctionne correctement?


Je l'ai fait et ça court juste bien. :) Quelles erreurs voyez-vous?


Je pense que c'était juste qu'il n'y avait pas de Go après le tableau alter . Ou peut-être qu'il avait besoin d'informations de colonne plus spécifiques dans les insertions. J'ai nettoyé votre réponse un peu et j'ai inclus un support pour tableb. Le problème est le résultat maintenant .. Je modifierai mon op pour que vous puissiez voir le résultat que je reçois.


J'ai édité @ la réponse de Seanlange pour produire le résultat correct. Je déteste que je devais faire une grande partie du travail seul pour que cette réponse soit valide, mais il m'a fait au moins me diriger dans la bonne direction. Jusqu'à présent, c'est ma réponse préférée, car c'est le plus simple.


Heureux cela a fonctionné pour vous. Je suppose en quelque sorte que vous devriez faire beaucoup de travail puisque j'espère que la table et les noms de colonne que vous avez fournis n'étaient que des exemples.


Ils ont été des exemples, mais votre dernière réponse n'a définitivement pas produit la sortie souhaitée.



4
votes

Vous pouvez utiliser Fusionner comme décrit par Adam Machanic dans dr. Sortie ou: Comment j'ai appris à cesser de vous inquiéter et d'aimer la fusion et dans Cette question pour obtenir un mappage entre la nouvelle valeur d'identité et l'ancienne valeur principale de la variable de table et l'utilisation que lorsque vous insérez sur vos tables d'enfant.

declare @T table(ID int, IDC int);

merge dbo.TableC as C
using dbo.TableA as A
on 0 = 1
when not matched by target then
  insert (TableType, Name) values('A', A.Name)
output A.ID, inserted.ID into @T(ID, IDC);

insert into dbo.TableCChild(Parent, Name)
select T.IDC, AC.Name
from dbo.TableAChild as AC
  inner join @T as T
    on AC.Parent = T.ID;

delete from @T;

merge dbo.TableC as C
using dbo.TableB as B
on 0 = 1
when not matched by target then
  insert (TableType, Name) values('B', B.Name)
output B.ID, inserted.ID into @T(ID, IDC);

insert into dbo.TableCChild(Parent, Name)
select T.IDC, BC.Name
from dbo.TableBChild as BC
  inner join @T as T
    on BC.Parent = T.ID;


2 commentaires

Ceci est définitivement assez magique. Et étonnamment, je le comprends sans même avoir regardé les articles que vous avez référencés. Le 0 = 1 se sent un peu pirateux cependant. On dirait que vous utilisez fusionnez juste pour que vous puissiez tirer parti de sortie et inséré . En .NET, nous avons tendance à froncer les sourcils sur ce type d'abus. Mais, d'après ce que je peux dire, il semble être une utilisation acceptable de fusionner en ligne. Attendons et voyons s'il y a d'autres soumissions ..


Une utilisation vraiment intelligente de fusion puisque la sortie d'insert normal ne peut pas être a.id dans la variable de table qui est pourquoi, dans ma solution, je devais sauter si des boucles pour correspondre aux deux identifiants



0
votes

Je viens d'écrire le SQL suivant pour le faire si le nom est unique en tablea et unique dans tableb xxx

si le nom n'est pas unique et que seul l'identifiant est l'identifiant unique, puis je voudrais Ajoutez legacyid comme suggéré et le code serait alors comme suit xxx


3 commentaires

N'est-ce pas la même chose que la réponse de Sean, sauf que vous êtes un syndicat tout ensemble? Si oui, quel est le bénéfice de l'Union ici?


La même chose que son oui, il n'y a pas d'avantage réel au syndicat, mais dans la première requête si vos noms sont uniques et séparément en B, vous n'avez pas besoin de l'identifiant Legacy, ce qui signifie une solution plus simple. J'ai testé la mienne et je m'assurai que ça a également manqué d'erreurs aussi :)


En outre, je suis plus un promoteur de garder le héritage héritatif si c'était nécessaire, ou laissez-le tout ensemble, sinon.



0
votes

Nous pouvons y arriver en tournant la colonne d'identité jusqu'à la fin de la fin de l'insertion comme l'exemple suivant.

--Create the tables

CREATE TABLE TableA
(
    ID int not null identity primary key,
    Name VARCHAR(30)
);

CREATE TABLE TableAChild
(
    ID int not null identity primary key,
    Parent int not null,
    Name VARCHAR(30),
    CONSTRAINT FK_A FOREIGN KEY (Parent) REFERENCES TableA(ID)
);

CREATE TABLE TableB
(
    ID int not null identity primary key,
    Name VARCHAR(30)
);

CREATE TABLE TableBChild
(
    ID int not null identity primary key,
    Parent int not null,
    Name VARCHAR(30),
    CONSTRAINT FK_B FOREIGN KEY (Parent) REFERENCES TableB(ID)
);

CREATE TABLE TableC
(
    ID int not null identity primary key,
    TableType VARCHAR(1),
    Name VARCHAR(30)
);

CREATE TABLE TableCChild
(
    ID int not null identity primary key,
    Parent int not null,
    Name VARCHAR(30),
    CONSTRAINT FK_C FOREIGN KEY (Parent) REFERENCES TableC(ID)
);

-- Insert some test records.. 

INSERT INTO TableA (Name) Values ('A1')
INSERT INTO TableAChild (Name, Parent) VALUES ('A1Child', SCOPE_IDENTITY())
INSERT INTO TableB (Name) Values ('B1')
INSERT INTO TableBChild (Name, Parent) VALUES ('B1Child', SCOPE_IDENTITY())

SET IDENTITY_INSERT TableC ON
INSERT INTO TableC(ID, TableType, Name)
SELECT ID, 'A', Name FROM TableA

INSERT INTO TableCChild(Parent, Name)
SELECT Parent, Name FROM TableAChild

DECLARE @MAXID INT
SELECT @MAXID = MAX(ID) FROM TableC
PRINT @MAXID

SET IDENTITY_INSERT TableC ON
INSERT INTO TableC(ID, TableType, Name)
SELECT ID + @MAXID, 'B', Name FROM TableB
SET IDENTITY_INSERT TableC OFF

INSERT INTO TableCChild(Parent, Name)
SELECT Parent + @MAXID, Name FROM TableBChild

SET IDENTITY_INSERT TableC OFF

SELECT * FROM TableC
SELECT * FROM TableCChild

DROP TABLE TableAChild
DROP TABLE TableBChild
DROP TABLE TableCChild

DROP TABLE TableA
DROP TABLE TableB
DROP TABLE TableC


0 commentaires

0
votes

Si vous devez insérer des enregistrements dans la troisième table TableM et TableCChild pour une utilisation ultérieure, il convient d'insérer des données dans ces tables, mais si vous n'avez besoin que de cette table pour l'utiliser dans votre procédure stockée pour le moment où vous pouvez également. Il suffit de travailler avec les deux premières tables pour obtenir le résultat souhaité.

select * from 
(
select b.ID,b.Parent,b.Name  from TableA a inner join TableAChild b on a.ID=b.ID
union
select b.ID,b.Parent,b.Name   from TableB a inner join TableBChild b on a.ID=b.ID) TableCChild


0 commentaires