How to copy parent and child data from one database to another while assigning the newly created parent ids in the foreign key column of child tables? - sql

Suppose,
I have a table tblClasses and tblStudents
Now each class have multiple student.
tblClass
ID Name
1 ClassA
2 ClassB
3 ClassC
tblStudents
ID Name Class_ID
1. john 1
2. Mathew 1
3. Imran 2
4. Jenny 3
now, I have another server having exact same db and tables and I am copying data from server 1 to server 2 from same tables using the Select and Insert e.g.
insert into server2.dbo.tblClass (Name)
select Name from server1.dbo.tblClass
and for tblStudents
insert into server2.dbo.tblStudents (Name, Class_ID)
select Name, Class_ID from server1.dbo.tblStudents
now this is ok but the real problem is that in server2 after copying the data, how to populate the tblStudents fk Class_ID with the actual IDs of tblClass which are generated after inserting the data into tblStudents in server2 since PKs are Identity and autoincremented and cannot change the design.
What to do in this case?
In simple words, when a parent and child data are copied then in the child table the foreign key field needs to be populated with the actual IDs of the parent not the one from where it is copied which would be obviously different.
I am not allowed to change the table design or properties and have to do it using the queries.
Any suggestions?

The way is to create a ClassId mapping table on class records insertion and use this mapping table to translate OldClassId to NewClassId for the new Student table:
declare #ClassIds table (OldClassId int, NewClassId int);
merge into newDB.dbo.tblClasses as target
using
(
select
Id = Id * (-1),
[Name]
from
oldDB.dbo.tblClasses
)
as source on source.Id = target.Id
when not matched by target then
insert ([Name])
values (source.[Name])
output source.Id * (-1), inserted.Id -- ← the trick is here
into #ClassIds (OldClassId, NewClassId);
insert into newDB.dbo.tblStudents
select
s.Id,
s.[Name],
ClassId = ids.NewClassId
from
oldDB.dbo.tblStudents s
inner join #ClassIds ids on ids.OldClassId = s.ClassId;
The major trick is that the MERGE statement may work with not not only inserted and deleted columns (as INSERT, UPDATE, DELETE statements do) but with the source columns as well.

There are two ways to go that I can think:
The first is to use SET IDENTITY_INSERT tblClass ON. This is not a design change, so you should be able to do it. After that, you can insert your own values in tblClass.ID (although you will need the select list parenthesis):
insert tblClass(ID,Name) values (1, 'ClassA')....
Alternatively, you could make a query that connects student ids to class names, and then use that back to create the respective connections:
-- export/save this in temp table
select s.Name,c.Name as className
into #a
from tblStudents s
left join tblClass c on s.Class_ID=c.ID
--now use this to fill db2 tblStudents
insert tblStudents(Name,Class_ID)
select #a.Name,c.ID
from
#a
inner join tblClass c on #a.className=c.Name

Related

What could be the workaround to avoid the MERGE issue i.e. The target of a MERGE statement cannot be a remote table?

Copying data from one table to another both on different servers but similar structures.
Ended up on this.
declare #ClassIds table (OldClassId int, NewClassId int);
merge into newDB.dbo.tblClasses as target
using
(
select
Id = Id * (-1),
[Name]
from
oldDB.dbo.tblClasses
)
as source on source.Id = target.Id
when not matched by target then
insert ([Name])
values (source.[Name])
output source.Id * (-1), inserted.Id -- ← the trick is here
into #ClassIds (OldClassId, NewClassId);
insert into newDB.dbo.tblStudents
select
s.Id,
s.[Name],
ClassId = ids.NewClassId
from
oldDB.dbo.tblStudents s
inner join #ClassIds ids on ids.OldClassId = s.ClassId;
but error:
The target of a MERGE statement cannot be a remote table, a remote view, or a view over remote tables.
Workaround could be reversing i.e. target and server but that's not ideal in my situation.
What should I do?
Original question:
Original question
Reason to do this:
the reason is I am copying the parent-child data and in the target the references to parent would be lost since the primary keys are auto generated hence in target a new record in parent would generate new Id but child would have the old parent id as of the source hence lost. So to avoid that the merge would make sure tyo update the child record with new parent ids.
edit:
the newDB is on the different server i.e. [192.168.xxx.xxx].newDB.dbo.tblStudents
If you are not able to change the remote DB structure, I would suggest to build the ClassId mapping table right in the target Class table:
drop table if exists #ClassIdMap;
create table #ClassIdMap (SourceClassId int, TargetClassId int);
declare #Prefix varchar(10) = 'MyClassId=';
insert into targetServer.targetDb.dbo.Classes
([Name])
select
-- insert the source class id values with some unique prefix
[Name] = concat(#Prefix, Id)
from
sourceServer.sourceDb.dbo.Classes;
-- then create the ClassId mapping table
-- getting the SourceClassId by from the target Name column
insert #ClassIdMap (
SourceClassId,
TargetClassId)
select
SourceClassId = replace([Name], #Prefix, ''),
TargetClassId = Id
from
targetServer.targetDb.dbo.Class
where
[Name] like #Prefix + '%';
-- replace the source Ids with the Name values
update target set
[Name] = source.[Name]
from
targetServer.targetDb.dbo.Class target
inner join #ClassIdMap map on map.TargetClassId = target.Id
inner join sourceServer.sourceDb.dbo.Classes source on source.Id = map.SourceClassId;
-- and use the ClassId mapping table
-- to insert Students into correct classes
insert into targetServer.targetDb.dbo.Students (
[Name] ,
ClassId )
select
s.[Name],
ClassId = map.TargetClassId
from
sourceServer.sourceDb.dbo.Students s
inner join #ClassIdMap map on map.SourceClassId = s.ClassId;
The problem or risk with this script is that it is not idempotent — being executed twice it creates the duplicates.
To eliminate this risk, it is necessary to somehow remember on the source side what has already been inserted.

Conditionally insert multiple data rows into multiple Postgres tables

I have multiple rows of data. And this is just some made up data in order to make an easy example.
The data has name, age and location.
I want to insert the data into two tables, persons and locations, where locations has a FK to persons.
Nothing should be inserted if there already is a person with that name, or if the age is below 18.
I need to use COPY (in my real world example I'm using NPQSQL for .NET and I think that's the fastest way of inserting a lot of data).
So I'll do the following in a transaction (not 100% sure on the syntax, not on my computer right now):
-- Create a temp table
DROP TABLE IF EXISTS tmp_x;
CREATE TEMPORARY TABLE tmp_x
(
name TEXT,
age INTEGER,
location TEXT
);
-- Read the data into the temp table
COPY tmp_x (name, age) FROM STDIN (FORMAT BINARY);
-- Conditionally insert the data into persons
WITH insertedPersons AS (
INSERT INTO persons (name, age)
SELECT name, age
FROM tmp_x tmp
LEFT JOIN persons p ON p.name = tmp.name
WHERE
p IS NULL
AND tmp.age >= 18
RETURNING id, name
),
-- Use the generated ids to insert the relational data into locations
WITH insertedLocations AS (
INSERT INTO locations (personid, location)
SELECT ip.id, tmp.location
FROM tmp_x tmp
INNER JOIN insertedPersons ip ON ip.name = tmp.name
),
DROP TABLE tmp_x;
Is there a better/easier/more efficient way to do this?
Is there a better way to "link" the inserts instead of INNER JOIN insertedPersons ip ON ip.name = tmp.name. What if name wasn't unique? Can I update tmp_x with the new person ids and use that?

SQL insert data into a table from another table

I'm having a problem trying to insert some values into a table. I made an empty table with the fields
id(primary key)
association_id
resource_id
I have another table with
resource_id
association_id
and another one with
id(coresponding to the association_id in the former one)
image
I want to insert the resource_id and association_id from the first populated table, where the image field of the coresponding id from the last table is not empty.
I tried this:
INSERT IGNORE INTO `logo_associations` (``,`association_id`,`resource_id`)
SELECT
``,
`a`.`association_id`,
`a`.`resource_id`
FROM doc24_associations_have_resources a
Join doc24_associations An on a.association_id = An.id
WHERE An.image<>''
but it does not work
Try this:
INSERT INTO logo_associations (association_id, resource_id)
SELECT a.association_id
,a.resource_id
FROM doc24_associations_have_resources a
LEFT JOIN doc24_associations an ON a.association_id = an.id
WHERE an.image IS NULL -- check for null with left join
This is valid for SQL Server. You do not need to select and insert the first column as it is an identity as you mention.
My experience is based on SQL Server but the SQL may be very similar
INSERT INTO DestinationTable
(association_id, resource_id)
SELECT LNK.assocication_id,
LNK.resource_id
FROM LinkTable AS LNK
INNER JOIN ImageTable AS IMG ON IMG.id = LNK.association_id
AND IMG.image IS NOT NULL
Above I assume the following:
Tables are named DestinationTable, LinkTable, and ImageTable respectively
In DestinationTable the primary key (id) is auto generated

merge statement when not matched by source then insert to another table

I have created two tables customersrc and customertemp with the columns:
customertemp
ID name age addr cityid isactive
34 Gi 24 Chennai 1 1
customersrc
CustomerId CustomerName CustomerAge CustomerAddress
1 Gi 24 madurai
2 Pa 23 Tirupur
3 MI 27 Tirupur
Now I need to insert pa and mi data value to the temp table bcz it is not matched with the rows of customertemp. And the row gi data will be updated which was matched.
I used the following MERGE statement
DECLARE #cityid INT SET #cityid=1
MERGE Temp.dbo.customersrc as src_customer
USING ( SELECT CustomerName,CustomerAge,CustomerAddress FROM customertemp) as temp_customer
ON src_customer.name=temp_customer.CustomerName
AND
src_customer.cityid=#cityid
WHEN MATCHED THEN
UPDATE SET
src_customer.age=temp_customer.CustomerAge,
src_customer.addr=temp_customer.CustomerAddress,
src_customer.isactive=1
WHEN NOT MATCHED BY SOURCE THEN
UPDATE SET src_customer.isactive=0 ; -- here i need the insert statement to insert in another table
Questions:
is it possible to write insert statement inside the when not matched by source query?
if it is not possible then how to achieve this using merge?
in a simple set theory I need to put the customersrc(table_B)-customertemp (table_A). B-A value into the another or temp table.
One of the main usages of the MERGE statement is to perform so called "UPSERTS" (Update matching records, insert new records), so it is definitely possible to do what you want. Just add the following to the last part of your MERGE statement:
WHEN NOT MATCHED BY TARGET THEN
INSERT (name, age, addr, cityid, isactive)
VALUES (CustomerName, CustomerAge, CustomerAddress, #cityid, 1)
If you also need to insert data into a 3rd table, depending on whether rows are updated or inserted, you can use the OUTPUT clause of the merge statement. Check out the documentation: http://technet.microsoft.com/en-us/library/ms177564.aspx
Me: Why do you want to insert to another table?
You: To show the user who are not in the customertemp table.
So your requirement is not to insert into another table. Your requirement is to get the missing users.
You could do that with a dummy UPDATE (SET SomeCol = SomeCol) and OUTPUT. But that is a hack that I would try to avoid.
It is probably easier to do this in two statements. Here's how you'd get the missing rows:
SELECT temp_customer.*
FROM (SELECT CustomerName,CustomerAge,CustomerAddress FROM customertemp) as temp_customer
LEFT JOIN customersrc ON src_customer.name=temp_customer.CustomerName AND src_customer.cityid=#cityid
WHERE customersrc.cityid IS NULL

sql insert into lookup tables' association tables

I'm updating 3 lookup tables' association tables like so:
insert into LookUpTable1_AssociationTable1 ([LookupId],[AssociationId])
select Id as LookupId, 4 as AssociationId from LookupTable1
insert into LookUpTable2_AssociationTable2 ([LookupId],[AssociationId])
select Id as LookupId, 4 as AssociationId from LookupTable2
I have 4 records in my Association master table. So I can run the above replacing the hard-coded '4' with each id in my association master table, but can i do it as a set somehow? Just run a "set based" (not procedural) sql that takes all 4 records in association table and performs the statements above automatically
Not sure if i understand your intent correctly. Check whether below query suits your needs.
insert into LookUpTable1_AssociationTable1 ([LookupId],[AssociationId])
select
l.Id as LookupId
,a.Id as AssociationId
from
LookupTable1 l
cross join Association a