合并两个父 > 子 table 集

Merging two parent > child table sets

我需要将两个父 > 子 table 集 merged/combined 中的数据获取到第三个父 > 子 table 中。

table 看起来像这样:

三组 table 的唯一区别是 TableC 有一个 TableType 列来帮助区分 TableA 记录和 TableB 记录之间的区别。

我的第一个想法是使用游标。下面是创建 table 结构的代码,插入一些记录,然后将数据合并在一起。效果很好,sooooo....

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

现在,我的问题:

最后,如果您想重新运行 上面的查询,这里有一个小脚本可以删除已创建的 table。

DROP TABLE TableAChild
DROP TABLE TableBChild
DROP TABLE TableCChild

DROP TABLE TableA
DROP TABLE TableB
DROP TABLE TableC

正确的结果应该是这样的:

您可以使用 map table 来 link 根据一些键将旧 ID 和新 ID 组合在一起。

在我的示例中,我使用的是插入 TableC 的顺序。

  1. 创建一个带有标识列的映射table。
  2. 根据TableAID的顺序在TableCtable中添加数据,并在map
  3. 中获取插入的id
  4. 使用TableA.id相同的顺序得到一个ROWNUMBER()并将其与映射的标识列匹配table并更新映射中的old_id以匹配TableA.idTableC.id .
  5. 使用地图插入TableCChildtable
  6. 截断地图并冲洗并重复其他 tables。

示例查询

CREATE TABLE  #map(id int identity,new_id int,old_id int);
INSERT INTO TableC
(
    TableType,
    Name
)output inserted.id into #map(new_id)
SELECT 'A',Name
FROM TableA
ORDER BY ID


update m
set m.old_id = ta.id
FROM #map m
inner join 
(
select row_number()OVER(order by id asc) rn,id
from tableA
)ta on ta.rn = m.id

INSERT INTO TableCChild (Name, Parent) 
SELECT Name,M.new_ID
FROM #Map M
INNER JOIN TableAChild TA ON M.old_id = TA.Parent

TRUNCATE TABLE #map

INSERT INTO TableC
(
    TableType,
    Name
)output inserted.id into #map(new_id)
SELECT 'B',Name
FROM TableB
ORDER BY ID

update m
set m.old_id = tb.id
FROM #map m
inner join 
(
select row_number()OVER(order by id asc) rn,id
from tableB
)tb on tb.rn = m.id

INSERT INTO TableCChild (Name, Parent) 
SELECT Name,M.new_ID
FROM #Map M
INNER JOIN TableBChild TB ON M.old_id = TB.Parent

DROP TABLE #Map

这是一种无需游标或其他 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

您可以使用 merge as described by Adam Machanic in Dr. OUTPUT or: How I Learned to Stop Worrying and Love the MERGE and in this 问题来获取 table 变量中新标识值和旧主键值之间的映射,以及在插入 [=17= 时使用该映射] tables.

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;

SQL Fiddle

如果名称在表 A 中是唯一的并且在表 B 中是唯一的

,我只是写了以下内容SQL
INSERT INTO TableCChild
  (
    Parent,
    NAME
  )
SELECT tc.ID,
       ta.Name
FROM   TableAChild  AS ta
       JOIN TableA a
            ON  a.ID = ta.Parent
       JOIN TableC  AS tc
            ON  tc.Name = a.Name
                AND tc.TableType = 'A' 
UNION
SELECT tc.ID,
       tb.Name
FROM   TableBChild  AS tb
       JOIN TableB b
            ON  b.ID = tb.Parent
       JOIN TableC  AS tc
            ON  tc.Name = b.Name
                AND tc.TableType = 'B' 

如果名称不是唯一的,只有 ID 是唯一标识符,那么我会按照建议添加 LegacyId,代码如下

/* Change Table C to Have LegacyId as well and this is used to find the New Key for Inserts
CREATE TABLE TableC
(
    ID            INT NOT NULL IDENTITY PRIMARY KEY,
    TableType     VARCHAR(1),
    LegacyId     INT,
    NAME          VARCHAR(30)
);
*/

INSERT INTO TableC (Name, TableType, LegacyId) 
SELECT DISTINCT NAME,
       'A', 
       Id
FROM   TableA
UNION
SELECT DISTINCT NAME,
       'B',
       Id
FROM   TableB

    INSERT INTO TableCChild
      (
        Parent,
        NAME
      )
    SELECT tc.ID,
           ta.Name
    FROM   TableAChild  AS ta
           JOIN TableA a
                ON  a.ID = ta.Parent
           JOIN TableC  AS tc
                ON  tc.LegacyId = a.Id
                    AND tc.TableType = 'A' 
    UNION
    SELECT tc.ID,
           tb.Name
    FROM   TableBChild  AS tb
           JOIN TableB b
                ON  b.ID = tb.Parent
           JOIN TableC  AS tc
                ON  tc.LegacyId = b.Id
                    AND tc.TableType = 'B' 

我们可以通过关闭 Identity 列来实现这一点,直到我们完成插入,如下例所示。

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

如果您需要在第三个 table TableC 和 TableCChild 中插入记录供以后使用,那么在这些 table 中插入数据就可以了,但是如果您只需要这个 table 数据暂时在您的存储过程中使用它,那么您也可以只使用前两个 table 来获得所需的结果。

select * from (
select a.ID,'A' as TableType,a.Name from TableA a inner join TableAChild b on a.ID=b.ID
union
select a.ID,'B' as TableType,a.Name  from TableB a inner join TableBChild b on a.ID=b.ID) TableC

同样获取TableCChild

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

如果您必须在 TableC 和 TableCChild 中插入,则必须使用 ID 和 TableType 上的主键重新创建 TableC,并关闭 ID 列的标识。