合并两个父母>儿童餐桌套

时间:2015-05-13 13:28:56

标签: sql sql-server cursor common-table-expression

我需要在两个父母>中获取数据子表集合并/组合成第三父表格>儿童表。

表格如下:

Table structure

三组表的唯一区别是TableC有一个TableType列,可帮助识别TableA记录和TableB记录之间的差异。

我的第一个想法是使用游标..这是创建表结构,插入一些记录,然后将数据合并在一起的代码。它工作得非常好,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

现在,我的问题:

  • 我总是被告知游标很糟糕。但我找不到另一种方法。我想知道CTE是否有某种方法可以做到这一点?
  • 如果光标在这种情况下合适,我该怎么办?做我做的更好的方法是什么?它对我来说看起来不太干,但我不是SQL专家。

最后,如果你想重新运行上面的查询,这里有一个小脚本来删除创建的表。

DROP TABLE TableAChild
DROP TABLE TableBChild
DROP TABLE TableCChild

DROP TABLE TableA
DROP TABLE TableB
DROP TABLE TableC

正确的结果应如下所示:

Desired result

6 个答案:

答案 0 :(得分:4)

您可以在mergeDr. OUTPUT or: How I Learned to Stop Worrying and Love the MERGE问题中使用Adam Machanic所述的this来获取表格变量中新标识值和旧主键值之间的映射关系插入子表时的用法。

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

答案 1 :(得分:1)

这是一种没有光标或其他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

答案 2 :(得分:0)

您可以使用map表根据某些键将旧ID和新ID链接在一起。

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

  1. 使用标识列创建一个映射表。
  2. 根据TableC ID的顺序在TableA表中添加数据,并在地图中插入ID
  3. 使用TableA.id的相同顺序获取ROWNUMBER()并将其与地图表的标识列相匹配,并更新地图中的old_id以匹配TableA.id TableC.id
  4. 使用地图插入TableCChild表格
  5. 截断地图并冲洗并重复其他表格。
  6. 示例查询

    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
    

答案 3 :(得分:0)

如果Name在TableA中是唯一的并且在TableB中是唯一的,那么我只是编写了以下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' 

如果Name不是唯一的,只有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' 

答案 4 :(得分:0)

我们可以通过关闭标识列直到我们完成插入来达到此目的。如下例所示。

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

答案 5 :(得分:0)

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

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列的标识。