How to copy tables avoiding cursors in SQL?
I want to write script in SQL that will copy these 2 tables(A,B) to other 2 tables(C,D) with the same structure as A,B accordingly.
IMPORTANT:
- Tables C,D are NOT necessary empty
- Several processes may call script simultaneously
Table A has foreign开发者_StackOverflow key(fk_a_b) of table B
________________________ _________________
| Table A | | Table B |
|______________________| |_______________|
| id FK_A_B name | | id visible |
| ----- -------- ------| | ----- --------|
| 1 21 n1 | | 21 true |
| 5 32 n2 | | 32 false |
------------------------ -----------------
Let say that after copying table B to D this is what I get
________________
| Table D |
|______________|
| id visible |
| ----- -------|
| 51 true |
| 52 false |
----------------
Now, when I'll copy table A to C I need to know, somehow, that ID=21 maps now to ID=51, and ID=32 to ID=52. Finally, the table C will be:
________________________
| Table C |
|______________________|
| id FK_C_D name |
| ----- -------- ------|
| 61 51 n1 |
| 62 52 n2 |
------------------------
Because several processes may call script simultaneously, I CAN'T alter table A,B to add some helper columns. So, to achieve this I used CURSOR. I copied row by row of table B and managed temp table to map OldId to NewId(21->51,32->52) and then used this temp table to copy table A.
I've read that CURSOR is bad practice. So, is there another way to do it?
Thank you
You can use the output clause with the merge statement to get a mapping between source id and target id. Described in this question. Using merge..output to get mapping between source.id and target.id
Here is some code that you can test. I use table variables instead of real tables.
Setup sample data:
-- @A and @B is the source tables
declare @A as table
(
id int,
FK_A_B int,
name varchar(10)
)
declare @B as table
(
id int,
visible bit
)
-- Sample data in @A and @B
insert into @B values (21, 1),(32, 0)
insert into @A values (1, 21, 'n1'),(5, 32, 'n2')
-- @C and @D is the target tables with id as identity columns
declare @C as table
(
id int identity,
FK_C_D int not null,
name varchar(10)
)
declare @D as table
(
id int identity,
visible bit
)
-- Sample data already in @C and @D
insert into @D values (1),(0)
insert into @C values (1, 'x1'),(1, 'x2'),(2, 'x3')
Copy data:
-- The @IdMap is a table that holds the mapping between
-- the @B.id and @D.id (@D.id is an identity column)
declare @IdMap table(TargetID int, SourceID int)
-- Merge from @B to @D.
merge @D as D -- Target table
using @B as B -- Source table
on 0=1 -- 0=1 means that there are no matches for merge
when not matched then
insert (visible) values(visible) -- Insert to @D
output inserted.id, B.id into @IdMap; -- Capture the newly created inserted.id and
-- map that to the source (@B.id)
-- Add rows to @C from @A with a join to
-- @IdMap to get the new id for the FK relation
insert into @C(FK_C_D, name)
select I.TargetID, A.name
from @A as A
inner join @IdMap as I
on A.FK_A_B = I.SourceID
Result:
select *
from @D as D
inner join @C as C
on D.id = C.FK_C_D
id visible id FK_C_D name
----------- ------- ----------- ----------- ----------
1 1 1 1 x1
1 1 2 1 x2
2 0 3 2 x3
3 1 4 3 n1
4 0 5 4 n2
You can test the code here: https://data.stackexchange.com/stackoverflow/q/101643/using-merge-to-map-source-id-to-target-id
E.g. SQL Server adds rowguid fields to tables that included in some publications for merge replication. I think, such approach can be used in your task. The idea is to add a couple of GUID fields that will play the role pf global identifiers, so we can use them in both pairs of master-datails tables
You can do something like this:
if object_id('tempdb..#TableB') is not null
drop table #TableB
select identity(int) RowId, *
into #TableB
from TableB
if object_id('tempdb..#TableDIds') is not null
drop table #TableDIds
create table #TableDIds (RowId int identity(1,1), Id int)
insert TableD
output inserted.Id into #TableDIds
select Visible
from #TableB
order by RowId
insert TableC
select tdi.Id, ta.name
from TableA ta
join #TableB tb on
ta.FK_A_B = tb.Id
join #TableDIds tdi on
tdi.RowId = tb.RowId
I used the following setup:
create table TableB
(
Id int not null primary key,
Visible bit not null
)
create table TableA
(
Id int not null,
FK_A_B int not null foreign key references TableB(Id),
Name varchar(10) not null
)
create table TableD
(
Id int identity(1,1) primary key,
Visible bit not null
)
create table TableC
(
Id int identity(1,1),
FK_C_D int not null references TableD(Id),
Name varchar(10) not null
)
insert TableB
values
(21, 1),
(32, 0)
insert TableA
values
(1, 21, 'n1'),
(5, 32, 'n2')
I hope this helps
http://pastebin.com/embed_iframe.php?i=fkUd4RPY
精彩评论