Copying data from one SQL server table to another SQL Server table


 

Hi Readers,

I had a requirement where I had to copy data from one SQL Server located in U.S. (Source Server) to SQL Server located in India (Destination Server). The copy has to be triggered when data is inserted at Source Server table.

I encountered various issues starting from Linked server connection, collation issues to distributed transaction coordinator issues.

Finally, I have collected all steps that I followed in my project and steps needed to handle all issues. And here is my blog talking about them. Feel free to drop any suggestions or comments for further enhancement of this blog.

 

Scenario:-

Let us assume you have ‘EmployeeDetails’ table present in Source (Local SQL Server) and Destination Server (‘MyDestinationSQLServer’). With every Insert in EmployeeDetails table in local server, we want the records to be inserted in destination server table as well.

clip_image001

 

Steps Followed:-

Majorly, there were below 5 steps that were followed:-

1. Create local login in both source and destination SQL server (Same Credentials to be used).

2. Create Linked Servers. Link your Source Server to Destination Server.

3. Enabling Distributed Transaction Coordinator service and properties

4. Handling collation issues if any.

5. Writing stored procedure and trigger for copying data from source server to destination server.

 

Detailed Steps:-

We will look into each step in details now:-

 

1. Create local login. This step should be followed in both source and destination machines.

a. Login to the SQL Server

b. Go to Server->Security->Logins->New Login (I have used ‘Admin’ and ‘P@ssw0rd’ as credentials.)

clip_image003

c. Give the user ‘sysadmin’ server role from Server Roles tab.

 

2. Create linked server. You will be required to link source server to destination server to copy data.

a. Run below script from source server (Local server in this blog) to create linked server.

b. Make sure to update destination server name and Database name. (Currently used name is ‘MyDestinationSQLServer’ and ‘MyDatabase’)

USE [master]
GO
 
EXEC master.dbo.sp_addlinkedserver @server = N'MyDestinationSQLServer', @srvproduct=N'', @provider=N'SQLNCLI', @datasrc=N'MyDestinationSQLServer ', @catalog=N'MyDatabase'
EXEC master.dbo.sp_addlinkedsrvlogin @rmtsrvname=N'MyDestinationSQLServer ',@useself=N'False',@locallogin=NULL,@rmtuser=NULL,@rmtpassword=NULL
EXEC master.dbo.sp_addlinkedsrvlogin @rmtsrvname=N'MyDestinationSQLServer ',@useself=N'False',@locallogin=N'admin',@rmtuser=N'admin',@rmtpassword='P@ssw0rd'
GO
 
EXEC master.dbo.sp_serveroption @server=N'MyDestinationSQLServer', @optname=N'collation compatible', @optvalue=N'false'
GO
 
EXEC master.dbo.sp_serveroption @server=N'MyDestinationSQLServer', @optname=N'data access', @optvalue=N'true'
GO
 
EXEC master.dbo.sp_serveroption @server=N'MyDestinationSQLServer', @optname=N'dist', @optvalue=N'false'
GO
 
EXEC master.dbo.sp_serveroption @server=N'MyDestinationSQLServer', @optname=N'pub', @optvalue=N'false'
GO
 
EXEC master.dbo.sp_serveroption @server=N'MyDestinationSQLServer', @optname=N'rpc', @optvalue=N'false'
GO
 
EXEC master.dbo.sp_serveroption @server=N'MyDestinationSQLServer', @optname=N'rpc out', @optvalue=N'false'
GO
 
EXEC master.dbo.sp_serveroption @server=N'MyDestinationSQLServer', @optname=N'sub', @optvalue=N'false'
GO
 
EXEC master.dbo.sp_serveroption @server=N'MyDestinationSQLServer', @optname=N'connect timeout', @optvalue=N'0'
GO
 
EXEC master.dbo.sp_serveroption @server=N'MyDestinationSQLServer', @optname=N'collation name', @optvalue=null
GO
 
EXEC master.dbo.sp_serveroption @server=N'MyDestinationSQLServer', @optname=N'lazy schema validation', @optvalue=N'false'
GO
 
EXEC master.dbo.sp_serveroption @server=N'MyDestinationSQLServer', @optname=N'query timeout', @optvalue=N'0'
GO
 
EXEC master.dbo.sp_serveroption @server=N'MyDestinationSQLServer', @optname=N'use remote collation', @optvalue=N'true'
GO
 
EXEC master.dbo.sp_serveroption @server=N'MyDestinationSQLServer', @optname=N'remote proc transaction promotion', @optvalue=N'true'
GO

c. After the linked server is created successfully. Test the Connection

Go to Server -> Server Objects -> Linked Servers -> MyDestinationSQLServer -> Right Click -> Test Connection.

You will see below popup when linked server connection test is successful.

clip_image005

 

3. Distributed Transaction Coordinator service should be running. This step should be followed in both source and destination server machines.

a. Go To Services.msc.

b. Start DTC Service

c. Set Startup Type to Automatic

clip_image007

 

4. Allow DTC in windows firewall. This step should be followed in both source and destination server machines.

a. Go to control panel -> Window Firewall -> Allow an app or feature through Windows Firewall

b. Scroll down to DTC feature

c. Enable domain and private communication

clip_image008

 

5. Update local DTC security settings. This step should be followed in both source and destination server machines.

a. Go to Control Panel -> Administrative Tools -> Component Services

b. Expand Component Services -> Computers -> My Computer -> Distributed Transaction Coordinator -> Local DTC.

clip_image009

c. Right click -> Properties -> Security tab

d. Enable network DTC access, allow inbound and outbound communication with no authentication required as shown in screenshot below:-

clip_image011

 

6. Resolve DNS Issue

Both source and destination server should be able to ping each other by name. If machines are not able to ping each other by names but ping by IP Address is working then try resolving the issue by adding server details in host file.

7 . Resolve Collation issues.

I had different collations in columns of source table and destination table in actual production scenario. Source and Destination Servers were having different collations as mentioned below

Source Server Collation: SQL_Latin1_General_CP1_CI_AI

Destination Server Collation: Latin1_General_CI_AI

So while creating stored procedure, it was failing on where clause in equal to operator with below error:-

Cannot resolve the collation conflict between "SQL_Latin1_General_CP1_CI_AS" and "Latin1_General_CI_AI" in the equal to operation.

To resolve issue on specific columns ‘COLLATE DATABASE_DEFAULT’ has to be added when comparing columns. Something like shown below

INNER JOIN [Mydatabase].[dbo].EmployeeDetails SourceTable ON 
DestTable.EmpId COLLATE DATABASE_DEFAULT = SourceTable.EmpId COLLATE 
DATABASE_DEFAULT where SourceTable.EmpId = @SourceEmpId

8. Write Script to copy data from one server to another.

In this blog, we are considering a simple example of ‘EmployeeDetails’ table with majorly 5 columns – Unique Id, Employee Id, Name, Address and Phone Number

clip_image001[1]

a. Script to create table is given below

 
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[EmployeeDetails](
[Id] [int] IDENTITY(1,1) NOT NULL,
[EmpId] [nvarchar](50) NOT NULL,
[Name] [nvarchar](50) NOT NULL,
[Address] [nvarchar](50) NOT NULL,
[Phone] [nvarchar](50) NOT NULL,
CONSTRAINT [PK_EmployeeDetails] PRIMARY KEY CLUSTERED
(
[EmpId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, 
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

b. Stored Procedure to copy data from Source Server table to Destination Server EmployeeDetails table.

Below stored procedure will give you collation issue while running as collation of column EmpId in Source and Destination table was different

 
Create Procedure [dbo].[InsertEmployeeDetails]
@SourceEmpId nvarchar(10)
AS
declare @DestEmpExists int
Select @DestEmpExists = Count(*) from 
[MyDestinationSQLServer].[MyDatabase].[dbo].EmployeeDetails where 
EmpId = @SourceEmpId
If @DestEmpExists = 0
BEGIN
PRINT 'Inserting Employee Information in Destination Employee Table'
Insert Into 
[MyDestinationSQLServer].[MyDatabase].[dbo].EmployeeDetails (EmpId, 
Name, Address, Phone)
Select EmpId, Name, Address, Phone from 
[MyDatabase].[dbo].EmployeeDetails where EmpId = @SourceEmpId
END
ELSE If @DestEmpExists = 1
BEGIN
PRINT 'Updating Employee Information in Destination Employee Table'
Update [MyDestinationSQLServer].[MyDatabase].[dbo].EmployeeDetails
SET Name = SourceTable.Name, Address = SourceTable.Address, Phone = 
SourceTable.Phone
FROM [MyDestinationSQLServer].[MyDatabase].[dbo].EmployeeDetails DestTable
INNER JOIN [MyDatabase].[dbo].EmployeeDetails SourceTable ON 
DestTable.EmpId = SourceTable.EmpId where SourceTable.EmpId = @SourceEmpId
END

Error received was

Msg 468, Level 16, State 9, Procedure InsertEmployeeDetails, Line 28

Cannot resolve the collation conflict between "SQL_Latin1_General_CP1_CI_AS" and "Latin1_General_CI_AI" in the equal to operation.

clip_image013

To resolve this issue, ‘COLLATE DATABASE_DEFAULT’ was added when comparing columns. Below is the updated stored procedure with collation issues fixed.

 
Create Procedure [dbo].[InsertEmployeeDetails]
@SourceEmpId nvarchar(10)
AS
declare @DestEmpExists int
Select @DestEmpExists = Count(*) from 
[MyDestinationSQLServer].[MyDatabase].[dbo].EmployeeDetails where EmpId = 
@SourceEmpId
If @DestEmpExists = 0
BEGIN
PRINT 'Inserting Employee Information in Destination Employee Table'
Insert Into [MyDestinationSQLServer].[MyDatabase].[dbo].EmployeeDetails 
(EmpId, Name, Address, Phone)
Select EmpId, Name, Address, Phone from 
[MyDatabase].[dbo].EmployeeDetails where EmpId = @SourceEmpId
END
ELSE If @DestEmpExists = 1
BEGIN
PRINT 'Updating Employee Information in Destination Employee Table'
Update [MyDestinationSQLServer].[MyDatabase].[dbo].EmployeeDetails
SET Name = SourceTable.Name, Address = SourceTable.Address, Phone = 
SourceTable.Phone
FROM [MyDestinationSQLServer].[MyDatabase].[dbo].EmployeeDetails DestTable
INNER JOIN [MyDatabase].[dbo].EmployeeDetails SourceTable ON 
DestTable.EmpId COLLATE DATABASE_DEFAULT = SourceTable.EmpId COLLATE 
DATABASE_DEFAULT where SourceTable.EmpId = @SourceEmpId
END

c. Trigger created on EmployeeDetails table in Source server.

 
Create TRIGGER [dbo].[Trg_InsertEmployeeDetails] 
ON [dbo].[EmployeeDetails]
AFTER INSERT, UPDATE
AS
BEGIN
SET XACT_ABORT ON
BEGIN DISTRIBUTED TRANSACTION
--1. Declare variables
declare @InsertedEmpId nvarchar(50)
--2. Fetch values from Table
Select @InsertedEmpId = EmpId from inserted
exec InsertEmployeeDetails @SourceEmpId = @InsertedEmpId
COMMIT TRANSACTION
SET XACT_ABORT OFF
END

Hope this will help you. Suggestion and Feedback is welcome.

Happy Coding!


Comments (2)

  1. Naren says:

    Thank you Sonam !!

Skip to main content