2015-05-13 159 views
7

我需要获取两个父>子表集合中的数据/合并到第三个父>子表中。合并两个父>子表集合

的表是这样的:

Table structure

在三套表的唯一区别是,表C具有TableType列,以帮助辨别TableA的记录和表B记录之间的差。

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

现在,我的问题(S):

  • 我一直被告知,光标是坏的。但我找不到另一种方式。我想知道是否有某种方式可以用CTE来做到这一点?
  • 如果光标适合这种情况,我是怎么做的?有没有更好的方式去做我所做的事情?它看起来不太干,但我不是SQL专家。

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

DROP TABLE TableAChild 
DROP TABLE TableBChild 
DROP TABLE TableCChild 

DROP TABLE TableA 
DROP TABLE TableB 
DROP TABLE TableC 

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

Desired result

+0

如果'tablea'和'tableb'中的名称字段在每个表中都是唯一的,那么您可以在不使用游标的情况下重写它,并使用'join'。但是,如果它们不是唯一的,那么光标是我看到从父级获得'id'字段的唯一选项。 – sgeddes

+0

@sgeddes您是在引用名称列中的*值*吗?如果是这样,它们不是唯一的。 –

+1

为什么不只是将另一列添加到TableC for LegacyID。这会给你一个使用连接的价值。没有必要做这种RBAR。 –

回答

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

不幸的是,这是一个非常不完整的例子。另外,TableX。ID是一个标识列,所以你不能插入记录。并且你不能打开标识插入,因为当合并来自TableB和TableBChild的记录时你会有冲突。我觉得你已经有了一些东西,所以我很乐意看到它完成。 –

+0

你甚至没有使用LegacyId。 :) –

+0

我认为他的意思是'从TableAChild交流连接A.CleanAID = AC.ID' – ughai

0

可以使用map表新老IDS基于一些关键的链接在一起。

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

  1. 创建具有标识列的映射表。
  2. TableC表基础上的TableAID和订单添加数据得到的地图
  3. 使用TableA.id同样为了得到ROWNUMBER()并与映射表的标识列匹配,并更新所插入的ID的old_id在地图上匹配TableA.idTableC.id
  4. 使用地图插入TableCChild
  5. 截断地图并冲洗并重复其他表格。

示例查询

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

如果可以向表格'TableC'添加额外的列,则可以使用表格本身作为像肖恩朗格建议的映射,然后再删除列。 – ughai

+0

这个答案的工作产生了预期的结果。它看起来比光标方法更复杂。尽管如此,我还得研究一下。让我们看看其他人通过投票的方式思考。 –

4

您可以使用merge作为Dr. OUTPUT or: How I Learned to Stop Worrying and Love the MERGEthis问题亚当Machanic描述来获得新的标识值,并在表变量旧的主键值和使用,当你插入之间的映射你的子表。

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

+0

这绝对是很神奇的。令人惊讶的是,我甚至没有看过你引用的文章就能理解它。 0 = 1虽然感觉有点ha。。好像你正在使用'merge',这样你就可以利用'output'和'inserted'。在.NET中,我们往往不喜欢那种滥用。但是,从我所知道的看来,它似乎是在线合并的可接受使用方式。让我们拭目以待,看看是否有任何其他提交.. –

+2

真正巧妙的使用合并,因为普通插入的输出不能得到A.id到表变量这就是为什么在我的解决方案中,我不得不通过循环跳转来匹配两个ID – ughai

0

我只是写了下面的SQL这样做,如果该名称是在表B

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' 

TableA中独特的和独特的。如果名称不唯一,唯一的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' 
+0

Isn'这跟肖恩的答案一样,除了你是联合起来的吗?如果是这样,UNION在这里有什么好处? –

+0

几乎与他的相同,联合实际上并没有真正的优势,但在第一个查询中,如果您的名字在a中是唯一的且在b中是单独唯一的,则不需要遗留Id,这意味着更简单的解决方案。我测试了我的程序,并确保它没有错误地运行:) – TonyM

+0

另外,如果需要的话,我更支持保留legacyid,或者如果不是,则将它全部关闭。 – TonyM

0

我们可以通过关闭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 
0

如果你需要插入在第三个表表C和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 

如果你有在表C和TableCChild插入,那么你必须重新创建表C与ID和TABLETYPE主键,并关闭了ID列的身份。