SQL Server How can I copy a set of records in a table and map their references across?

ruyhziif  于 2023-08-02  发布在  其他
关注(0)|答案(2)|浏览(80)

I have a requirement to allow a user to create a copy of some records in the database. These are not exact duplicates but rather the idea is that some of the data can be copied over into another group and maybe the user can then edit that data if necessary. So I have a set of IDs I want to look up and copy from. I do something like this:

insert into Thing (Name, Desc, Group_id)
select Name, Desc, <new_group_id>
from Thing
where Thing_id in (<existing_thing_ids>);

This would create a copy of each selected thing in the newly created group.

I also need to copy over related data into other tables for each thing. So I then want to do something like this:

insert into Attribute (Thing_id, Att_Name, Att_Desc)
select t.New_Thing_id, Att_Name, Att_Desc
from Attribute a
inner join @thing_map t on a.Thing_id = t.Old_Thing_id;

What I'm trying to do here is give each new thing the equivalent attributes of the 'old' thing that it is a copy of. That is, I want to copy the attributes as well as the things.

The Thing_id is an identity column, so I don't know the IDs until after they are inserted. I need to somehow map the IDs from the old things to the new things.

I thought I might be able to use output with insert:

declare @thing_map table (Old_Thing_id int, New_Thing_id int);
insert into Thing (Name, Desc, Group_id)
output Thing_id, inserted.Thing_id into @thing_map
from Thing
where Thing_id in (<existing_thing_ids>);

However, it seems that I cannot use a non-inserted value in the output clause.

The other option is I could join on the name and description instead, which will probably work in most cases but there is no guarantee that these fields will be unique. e.g.

insert into Attribute (Thing_id, Att_Name, Att_Desc)
select new_thing.Thing_id, Att_Name, Att_Desc
from Attribute a
inner join Thing old_thing on a.Thing_id = old_thing.Thing_id
inner join Thing new_thing on new_thing.Name = old_thing.Name and new_thing.Desc = old_thing.desc;

I wonder if anyone has a good method for handling something like this?

epfja78i

epfja78i1#

Thanks to Martin Smith for pointing me towards MERGE which I had not heard of before but is just what I was looking for.

I'm now using this merge statement in place of the insert:

declare @thing_map table (Old_Thing_id int, New_Thing_id int);

merge into Thing as tgt
using (select * from Thing where Thing_id in (<existing_thing_ids>)) as src
on 1=0
when not matched
insert (Name, Desc, Group_id)
values (src.Name, src.Desc, <new_group_id>)
output src.Thing_id, inserted.Thing_id into @thing_map
bfhwhh0e

bfhwhh0e2#

You could filter based on <new_group_id> , but that's only good for the whole group and won't map specific old ID to specific new ID. For this to work, probably you'll have to enforce unique Desc values within a group.

If you can do that, ensuring Descriptions are unique with the new group, then you could do this to map IDs:

SELECT t_old.Thing_ID as Old_thing_ID, t_new.Thing_ID As New_thing_ID
FROM Thing t_new
INNER JOIN Thing t_old ON t_old.Desc = t_new.Desc 
    and t_old.thing_id IN (<existing_thing_ids>)
WHERE t_new.Group_ID = <new_group_id>

This can then act as the @thing_map , for which you've already posted a solution.

相关问题