如何为预先数据库创建自定义SQL Server复制

  SyncNavigator是一款功能强大的数据库同步软件,适用于SQL SERVER, MySQL,具有自动/定时同步数据、无人值守、故障自动恢复、同构/异构数据库同步、断点续传和增量同步等功能,支持Windows xp以上所有操作系统,适用于大容量数据库快速同步。

安装包下载地址:https://www.syncnavigator.cn/Setup.zip

帮助文档地址:https://www.syncnavigator.cn/Help_zh-CN.chm

Web文档地址:https://www.syncnavigator.cn/chm/index.htm

SyncNavigator v8.6.2(数据库同步软件)下载地址

SyncNavigator v8.6.2(数据库同步软件)下载地址


创建第一个同步项目


使用 HKROnline SyncNavigator 创建一个数据库同步项目。只需要通过简单的配置,创建完成后您可以随时执行数据库同步任务。

1.点击 “SyncNavigator(Client)” 图标进入系统。

2.在登录界面中输入连接到的服务器地址,点击 “确定” 按钮开始连接。

  • 注意:这里不是登陆您的数据库,而是登陆到本软件的管理端。
  • 默认情况下直接点击 “连接” 按钮即可(本机默认已经安装)。
  • 默认服务器登录用户名为 “admin” 密码为空。
  • 本机服务器地址 = 127.0.0.1 。
  • 您可以使用域名或者IP地址作为服务器地址。如果指定了端口号可以使用 IP:Port 方式填写。
  • 通常,需要创建SQL Server数据库的副本副本。例如,出于分离分析任务和操作任务的目的,可能需要这样做。第一个导致数据库上的高负载,并且为了减少负载,创建了主数据库的副本以执行分析性预期查询。

    通常,可以使用内置DBMS工具创建以下重复副本:

    1. 日志传送
    2. SQL Server复制
    3. AlwaysOn可用组

    但是,如果您不需要的整个数据库,而只需要其中的几个表怎么办?在这种情况下,您可以自己创建复制。只要数据采样是主要目标,那么在一个方向(主到从)的数据库复制就足够了。可以使用包括SSIS和.NET内部的几种方法来执行这种复制。

    在这里中,我们将使用JobEmpl招聘服务数据库来演示如何使用T-SQL在主从方向上创建数据库复制。

    使用T-SQL在一个方向上创建SQL Server复制

    首先,让我们描述此复制的主要原理和算法。在多次中断期间,我们需要比较源数据库和目标数据库之间替换表中的数据。这意味着我们需要输入唯一的代理键来比较表。为了加快比较过程,我们还需要在该键上创建一个索引。并且还需要为每个复制表添加一个计算片段,闸为每一行计算CHECKSUM。

    同样重要的是选择数据的固定部分,例如一次(多次重复)一次特定数量的行。

    因此,我们需要执行以下步骤:

    1. 在源表上,创建一个REPL_GUID列和一个唯一的REPL_GUID索引,以在源表和目标表之间建立一对一的关系。您还应该创建一个计算出的CheckSumVal列,该列将为每一行计算CHECKSUM值。
    2. 创建一个称为Target的新目标数据库。
    3. 跨源数据库和目标数据库同步复制表的架构,并删除对不存在对象的所有引用。
    4. 补充目标数据库的外键。
    5. 运行复制并监视源数据库和目标数据库之间有多少行不同。

    现在,让我们使用为雇用员工而创建的JobEmpl数据库详细查看每个步骤。

    求职者数据库的模式

    图1求职者数据库的架构

    我们只需要复制EmployeeJobHistory表。

    然后,可以在以下脚本的帮助下执行上述算法的第一步。

    USE JobEmpl
    GO
    
    SET QUOTED_IDENTIFIER ON;
       
    DECLARE @src NVARCHAR(255) = N'JobEmpl';
    DECLARE @sch NVARCHAR(255) = N'dbo';
    DECLARE @sql NVARCHAR(MAX);
    DECLARE @name NVARCHAR(255);
    DECLARE @listcols NVARCHAR(MAX);
    CREATE TABLE #cols (
        [Name] NVARCHAR(255)
    );
      
    SELECT
        [Name] INTO #tbl
    FROM sys.tables
    WHERE [Name] IN (
    N'Employee',
    N'JobHistory'
    );
       
    DECLARE sql_cursor CURSOR LOCAL FOR SELECT
        [Name]
    FROM #tbl;
       
    OPEN sql_cursor;
       
    FETCH NEXT FROM sql_cursor
    INTO @name;
       
    WHILE (@@fetch_status = 0)
    BEGIN
        DELETE FROM #cols;
       
        SET @sql = N'SET QUOTED_IDENTIFIER ON; select N''COALESCE(CAST([''+col.[name]+N''] AS NVARCHAR(MAX)), N'''''''')'' ' +
        N'from [' + @src + N'].sys.columns as col ' +
        N'inner join [' + @src + N'].sys.tables as tbl on col.[object_id]=tbl.[object_id] ' +
        N'where tbl.[name]=''' + @name + ''' and col.[is_identity]=0';
       
        INSERT INTO #cols ([Name])
        EXEC sys.sp_executesql @sql;
       
        SET @listcols = N'';
       
        SELECT
            @listcols = @listcols + CAST([Name] AS NVARCHAR(MAX)) + N'+ '
        FROM #cols;
       
        SET @listcols = SUBSTRING(@listcols, 1, LEN(@listcols) - 1);
      
        SET @sql=N'SET QUOTED_IDENTIFIER ON; ALTER TABLE ['+@sch+N'].['+@name+N'] ADD [CheckSumVal] AS CHECKSUM('+@listcols+N');'
       
        --PRINT @sql;
        EXEC sys.sp_executesql @sql;
      
        SET @sql=N'SET QUOTED_IDENTIFIER ON; ALTER TABLE [dbo].['+@name+N'] ADD [REPL_GUID] [uniqueidentifier] ROWGUIDCOL NOT NULL CONSTRAINT ['+@name+N'_DEF_REPL_GUID] DEFAULT (NEWSEQUENTIALID());';
      
        --PRINT @sql;
        EXEC sys.sp_executesql @sql;
      
        SET @sql=N'SET QUOTED_IDENTIFIER ON; CREATE UNIQUE NONCLUSTERED INDEX [indREPL_GUID] ON [dbo].['+@name+N']([REPL_GUID] ASC);';
      
        --PRINT @sql;
        EXEC sys.sp_executesql @sql;
       
        FETCH NEXT FROM sql_cursor
        INTO @name;
    END
       
    CLOSE sql_cursor;
    DEALLOCATE sql_cursor;
       
    DROP TABLE #cols;
       
    DROP TABLE #tbl;

    从脚本中,您可以看到它必须在源JobEmpl数据库上运行,并且应该在@src和@sch变量中相应地指定源数据库和架构。@ sql变量是生成动态SQL所必需的,同时,@ name是保存复制表名称所必需的。

    首先,我们将复制的表名称收集到临时#tbl表中。接下来,我们使用光标遍历每个表名,并将表名提取到@name变量中。之后,对于每个表,将形成非IDENTITY类型的列的列表,并将结果插入带有“ +”号的@listcols变量中。

    值得一提的是,首先,每个表名都使用CAST函数转换为NVACHAR(MAX)类型,然后使用COALESCE函数([<ColumnName>],N'')。从每一行的所有列值中形成一个字符串。

    接下来,将创建计算出的CheckSumVal字段,REPL_GUID字段及其唯一的indREPL_GUID索引。

    在我们的例子中,我们得到了以下脚本。

    SET QUOTED_IDENTIFIER ON;
    ALTER TABLE [dbo].[Employee]
    ADD [CheckSumVal] AS CHECKSUM(COALESCE(CAST([FirstName] AS NVARCHAR(MAX)), N'')+ COALESCE(CAST([LastName] AS NVARCHAR(MAX)), N'')+ COALESCE(CAST([Address] AS NVARCHAR(MAX)), N'')+ COALESCE(CAST([CheckSumVal] AS NVARCHAR(MAX)), N'')+ COALESCE(CAST([REPL_GUID] AS NVARCHAR(MAX)), N''));
    
    SET QUOTED_IDENTIFIER ON;
    ALTER TABLE [dbo].[Employee]
    ADD [REPL_GUID] [uniqueidentifier] ROWGUIDCOL NOT NULL CONSTRAINT [Employee_DEF_REPL_GUID] DEFAULT (NEWSEQUENTIALID());
    
    SET QUOTED_IDENTIFIER ON;
    CREATE UNIQUE NONCLUSTERED INDEX [indREPL_GUID] ON [dbo].[Employee]([REPL_GUID] ASC);
    
    SET QUOTED_IDENTIFIER ON;
    ALTER TABLE [dbo].[JobHistory]
    ADD [CheckSumVal] AS CHECKSUM(COALESCE(CAST([EmployeeID] AS NVARCHAR(MAX)), N'')+ COALESCE(CAST([CompanyID] AS NVARCHAR(MAX)), N'')+ COALESCE(CAST([PositionID] AS NVARCHAR(MAX)), N'')+ COALESCE(CAST([ProjectID] AS NVARCHAR(MAX)), N'')+ COALESCE(CAST([StartDate] AS NVARCHAR(MAX)), N'')+ COALESCE(CAST([FinishDate] AS NVARCHAR(MAX)), N'')+ COALESCE(CAST([Description] AS NVARCHAR(MAX)), N'')+ COALESCE(CAST([Achievements] AS NVARCHAR(MAX)), N'')+ COALESCE(CAST([ReasonsForLeavingTheProject] AS NVARCHAR(MAX)), N'')+ COALESCE(CAST([ReasonsForLeavingTheCompany] AS NVARCHAR(MAX)), N'')+ COALESCE(CAST([CheckSumVal] AS NVARCHAR(MAX)), N'')+ COALESCE(CAST([REPL_GUID] AS NVARCHAR(MAX)), N''));
    
    SET QUOTED_IDENTIFIER ON;
    ALTER TABLE [dbo].[JobHistory] ADD [REPL_GUID] [uniqueidentifier] ROWGUIDCOL NOT NULL CONSTRAINT [JobHistory_DEF_REPL_GUID] DEFAULT (NEWSEQUENTIALID());
    
    SET QUOTED_IDENTIFIER ON;
    CREATE UNIQUE NONCLUSTERED INDEX [indREPL_GUID] ON [dbo].[JobHistory]([REPL_GUID] ASC);

    以后,您可以在以下脚本的帮助下从数据库中删除创建的表和索引。

    DECLARE @name NVARCHAR(255);
    DECLARE @sql NVARCHAR(MAX);
    
    CREATE TABLE #tbl (
    	[name] NVARCHAR(255)
    );
    
    INSERT INTO #tbl ([name])
    	SELECT
    		[name]
    	FROM sys.tables
    	WHERE [name] IN (
    	N'Employee',
    	N'JobHistory'
    	);
    
    DECLARE sql_cursor CURSOR LOCAL FOR SELECT
    	[name]
    FROM #tbl;
    
    OPEN sql_cursor;
    
    FETCH NEXT FROM sql_cursor
    INTO @name;
    
    WHILE (@@fetch_status = 0)
    BEGIN
    	SET @sql = N'DROP INDEX [indREPL_GUID] ON [dbo].[' + @name + N'];';
    	
    	--print @sql
    	EXEC sys.sp_executesql @sql;
    	
    	SET @sql = N'ALTER TABLE [dbo].[' + @name + N'] DROP CONSTRAINT [' + @name + N'_DEF_REPL_GUID], COLUMN [CheckSumVal], COLUMN [REPL_GUID];';
    	
    	--print @sql
    	EXEC sys.sp_executesql @sql;
    	
    	FETCH NEXT FROM sql_cursor
    	INTO @name;
    END
    
    CLOSE sql_cursor;
    DEALLOCATE sql_cursor;
    
    DROP TABLE #tbl;

    复制的表也位于此处,对于每个表,都将删除indREPL_GUID索引以及REPL_GUID和CheckSumVal列。

    在我们的例子中,创建了以下T-SQL代码。

    DROP INDEX [indREPL_GUID] ON [dbo].[Employee];
    ALTER TABLE [dbo].[Employee] DROP CONSTRAINT [Employee_DEF_REPL_GUID], COLUMN [CheckSumVal], COLUMN [REPL_GUID];
    
    DROP INDEX [indREPL_GUID] ON [dbo].[JobHistory];
    ALTER TABLE [dbo].[JobHistory] DROP CONSTRAINT [JobHistory_DEF_REPL_GUID], COLUMN [CheckSumVal], COLUMN [REPL_GUID];

    现在,根据上述算法的第二步,创建一个新的JobEmplRead数据库来接收数据。然后,我们同步复制表的架构。要执行同步,请使用DbForge架构比较工具:选择JobEmpl作为数据源,选择jobEmplRead作为数据目标。

    选择数据库以进行模式同步

    图2选择数据库进行模式同步

    然后按“  比较” 按钮。完成用于比较的元数据创建过程后,选择所需的表并开始配置数据库同步过程。

    选择表以进行模式同步

    图3选择用于模式同步的表

    接下来,我们选择预设值–脚本生成。

    选择脚本生成作为同步输出

    图4选择脚本生成作为同步输出

    现在让我们清除备份创建选项。

    取消选择备份创建选项

    图5取消选择备份创建选项

    接下来,我们稍后将在生成的模式同步脚本中手动删除外键。

    取消选择所有依赖项

    图6取消选择所有依赖项

    现在按“  同步按钮,并忽略“  摘要”选项卡上的警告。

    模式比较警告

    图7警告

    在生成的脚本中删除以下外键:

    • FK_JobHistory_Company_CompanyID
    • FK_JobHistory_Position_PositionID
    • FK_JobHistory_Project_ProjectID

    我们需要这样做,因为我们没有转移CompanyPositionProject表。结果,我们得到了用于移动复制的模式表的脚本。

    SET CONCAT_NULL_YIELDS_NULL, ANSI_NULLS, ANSI_PADDING, QUOTED_IDENTIFIER, ANSI_WARNINGS, ARITHABORT, XACT_ABORT ON
    SET NUMERIC_ROUNDABORT, IMPLICIT_TRANSACTIONS OFF
    GO
    
    USE [JobEmplRead]
    GO
    
    IF DB_NAME() <> N'JobEmplRead' SET NOEXEC ON
    GO
    
    
    --
    -- Set transaction isolation level
    --
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    GO
    
    --
    -- Start Transaction
    --
    BEGIN TRANSACTION
    GO
    
    --
    -- Create table [dbo].[Employee]
    --
    CREATE TABLE [dbo].[Employee] (
      [EmployeeID] [int] IDENTITY,
      [FirstName] [nvarchar](255) NOT NULL,
      [LastName] [nvarchar](255) NOT NULL,
      [Address] [nvarchar](max) NULL,
      [CheckSumVal] AS (checksum((coalesce(CONVERT([nvarchar](max),[FirstName]),N'')+coalesce(CONVERT([nvarchar](max),[LastName]),N''))+coalesce(CONVERT([nvarchar](max),[Address]),N''))),
      [REPL_GUID] [uniqueidentifier] NOT NULL CONSTRAINT [Employee_DEF_REPL_GUID] DEFAULT (newsequentialid()) ROWGUIDCOL,
      CONSTRAINT [PK_Employee_EmployeeID] PRIMARY KEY CLUSTERED ([EmployeeID])
    )
    ON [PRIMARY]
    TEXTIMAGE_ON [PRIMARY]
    GO
    IF @@ERROR<>0 OR @@TRANCOUNT=0 BEGIN IF @@TRANCOUNT>0 ROLLBACK SET NOEXEC ON END
    GO
    
    --
    -- Create index [indREPL_GUID] on table [dbo].[Employee]
    --
    CREATE UNIQUE INDEX [indREPL_GUID]
      ON [dbo].[Employee] ([REPL_GUID])
      WITH (FILLFACTOR = 80)
      ON [PRIMARY]
    GO
    IF @@ERROR<>0 OR @@TRANCOUNT=0 BEGIN IF @@TRANCOUNT>0 ROLLBACK SET NOEXEC ON END
    GO
    
    --
    -- Create table [dbo].[JobHistory]
    --
    CREATE TABLE [dbo].[JobHistory] (
      [EmployeeID] [int] NOT NULL,
      [CompanyID] [int] NOT NULL,
      [PositionID] [int] NOT NULL,
      [ProjectID] [int] NOT NULL,
      [StartDate] [date] NOT NULL,
      [FinishDate] [date] NULL,
      [Description] [nvarchar](max) NOT NULL,
      [Achievements] [nvarchar](max) NULL,
      [ReasonsForLeavingTheProject] [nvarchar](max) NULL,
      [ReasonsForLeavingTheCompany] [nvarchar](max) NULL,
      [CheckSumVal] AS (checksum(((((((((coalesce(CONVERT([nvarchar](max),[EmployeeID]),N'')+coalesce(CONVERT([nvarchar](max),[CompanyID]),N''))+coalesce(CONVERT([nvarchar](max),[PositionID]),N''))+coalesce(CONVERT([nvarchar](max),[ProjectID]),N''))+coalesce(CONVERT([nvarchar](max),[StartDate]),N''))+coalesce(CONVERT([nvarchar](max),[FinishDate]),N''))+coalesce(CONVERT([nvarchar](max),[Description]),N''))+coalesce(CONVERT([nvarchar](max),[Achievements]),N''))+coalesce(CONVERT([nvarchar](max),[ReasonsForLeavingTheProject]),N''))+coalesce(CONVERT([nvarchar](max),[ReasonsForLeavingTheCompany]),N''))),[REPL_GUID][uniqueidentifier] NOT NULL CONSTRAINT [JobHistory_DEF_REPL_GUID] DEFAULT (newsequentialid()) ROWGUIDCOL,
      CONSTRAINT [PK_JobHistory] PRIMARY KEY CLUSTERED ([EmployeeID],[CompanyID],[PositionID],[ProjectID]))
    ON [PRIMARY]
    TEXTIMAGE_ON [PRIMARY]
    GO
    IF @@ERROR<>0 OR @@TRANCOUNT=0BEGIN IF @@TRANCOUNT>0 ROLLBACK SET NOEXEC ON END
    GO
    
    ----Create index [indREPL_GUID] on table [dbo].[JobHistory]--
    CREATE UNIQUE INDEX [indREPL_GUID]
      ON [dbo].[JobHistory]([REPL_GUID])
      WITH (FILLFACTOR =80)
      ON [PRIMARY]
    GO
    IF @@ERROR<>0 OR @@TRANCOUNT=0BEGIN IF @@TRANCOUNT>0 ROLLBACK SET NOEXEC ON END
    GO
    
    ----Create foreign key [FK_JobHistory_Employee_EmployeeID] on table [dbo].[JobHistory]--
    ALTER TABLE [dbo].[JobHistory] WITH NOCHECK
      ADD CONSTRAINT [FK_JobHistory_Employee_EmployeeID] FOREIGN KEY ([EmployeeID]) REFERENCES [dbo].[Employee]([EmployeeID])
    GO
    IF @@ERROR<>0 OR @@TRANCOUNT=0BEGIN IF @@TRANCOUNT>0 ROLLBACK SET NOEXEC ON END
    GO
    
    ----CommitTransaction--
    IF @@TRANCOUNT>0 COMMIT TRANSACTION
    GO
    
    ----Set NOEXEC to off
    --
    SET NOEXEC OFF
    GO

    在JobEmplRead数据库中运行此脚本。

    因此,我们已经完成了算法的第3步:跨JobEmpl和JobEmpl读取数据库的同步表架构,并删除了对不存在的对象的所有引用。

    让我们使用以下脚本进行监视。

    DECLARE @src NVARCHAR(255) = N'JobEmpl';
    DECLARE @trg NVARCHAR(255) = N'JobEmplRead';
    DECLARE @sch NVARCHAR(255) = N'dbo';
    DECLARE @sql NVARCHAR(MAX);
    DECLARE @name NVARCHAR(255);
    
    CREATE TABLE #res (
    	[TblName] NVARCHAR(255)
       ,[Count] INT
    );
    
    SELECT
    	[name] INTO #tbl
    FROM sys.tables
    WHERE [name] IN (
    N'Employee',
    N'JobHistory'
    );
    
    DECLARE sql_cursor CURSOR LOCAL FOR SELECT
    	[name]
    FROM #tbl;
    
    OPEN sql_cursor;
    
    FETCH NEXT FROM sql_cursor
    INTO @name;
    
    WHILE (@@fetch_status = 0)
    BEGIN
    	SET @sql = N'SELECT ''' + @name + N''' AS [TblName], COUNT(*) as [Count] ' +
    	N'FROM [' + @src + N'].[' + @sch + N'].[' + @name + N'] AS src WITH(READUNCOMMITTED) FULL OUTER JOIN ' +
    	N'[' + @trg + N'].[' + @sch + N'].[' + @name + N'] AS trg WITH(READUNCOMMITTED) ON src.[REPL_GUID]=trg.[REPL_GUID] ' +
    	N'WHERE (src.[REPL_GUID] IS NULL) OR (trg.[REPL_GUID] IS NULL) OR (src.[CheckSumVal]<>trg.[CheckSumVal])';
    	
    	--print @sql;
    	
    	INSERT INTO #res ([TblName], [Count])
    	EXEC sys.sp_executesql @sql;
    	
    	FETCH NEXT FROM sql_cursor
    	INTO @name;
    END
    
    CLOSE sql_cursor;
    DEALLOCATE sql_cursor;
    
    DROP TABLE #tbl;
    
    SELECT
    	*
    FROM #res
    ORDER BY [TblName] ASC;
    
    DROP TABLE #res;

    在这里,我们有FULL OUTER JOIN语句的创建,表列表的返回以及与众不同的行数,包括不存在的行和关联的行。

    在我们的例子中,我们得到以下结果。

    复制表中不同行的数量

    图8复制表中不同行的数量

    生成了以下脚本进行比较。

    SELECT 'Employee' AS [TblName], COUNT(*) as [Count] 
    FROM [JobEmpl].[dbo].[Employee] AS src WITH(READUNCOMMITTED) 
    FULL OUTER JOIN [JobEmplRead].[dbo].[Employee] AS trg WITH(READUNCOMMITTED) ON src.[REPL_GUID]=trg.[REPL_GUID] 
    WHERE (src.[REPL_GUID] IS NULL) OR (trg.[REPL_GUID] IS NULL) OR (src.[CheckSumVal]<>trg.[CheckSumVal])
    
    SELECT 'JobHistory' AS [TblName], COUNT(*) as [Count] 
    FROM [JobEmpl].[dbo].[JobHistory] AS src WITH(READUNCOMMITTED) 
    FULL OUTER JOIN [JobEmplRead].[dbo].[JobHistory] AS trg WITH(READUNCOMMITTED) ON src.[REPL_GUID]=trg.[REPL_GUID] 
    WHERE (src.[REPL_GUID] IS NULL) OR (trg.[REPL_GUID] IS NULL) OR (src.[CheckSumVal]<>trg.[CheckSumVal])

    显然是,为了减少双重,事务隔离等级为脏读。

    让我们将算法的第4步和第5步组合为以下简短脚本。

    USE [JobEmplRead]
    GO
    
    SET QUOTED_IDENTIFIER ON;
    
    DECLARE @count INT = 100000;
    DECLARE @src NVARCHAR(255) = N'JobEmpl';
    DECLARE @trg NVARCHAR(255) = N'JobEmplRead';
    DECLARE @sch NVARCHAR(255) = N'dbo';
    DECLARE @sql NVARCHAR(MAX);
    DECLARE @name NVARCHAR(255);
    DECLARE @upd_listcols NVARCHAR(MAX);
    DECLARE @ins_listcols NVARCHAR(MAX);
    DECLARE @listcols NVARCHAR(MAX);
    CREATE TABLE #cols (
    	[Name] NVARCHAR(255)
    );
    CREATE TABLE #fk_list (
    	[TblName] NVARCHAR(255)
       ,[Name] NVARCHAR(255)
    );
    
    DECLARE @ParmDefinition NVARCHAR(500);
    
    SELECT
    	[Name] INTO #tbl
    FROM sys.tables
    WHERE [Name] IN (
    N'Employee',
    N'JobHistory'
    );
    
    INSERT INTO #fk_list ([TblName], [Name])
    	SELECT
    		t.[Name]
    	   ,fk.[Name]
    	FROM sys.foreign_keys AS fk
    	INNER JOIN sys.tables AS tbl
    		ON fk.[parent_object_id] = tbl.[object_id]
    	INNER JOIN #tbl AS t
    		ON t.[Name] = tbl.[Name];
    
    --select * from #fk_list;
    
    IF (EXISTS (SELECT TOP (1)
    			1
    		FROM #fk_list)
    	)
    BEGIN
    	SELECT
    		N'SET QUOTED_IDENTIFIER ON; ALTER TABLE [' + [TblName] + N'] NOCHECK CONSTRAINT [' + [Name] + N']; ' AS [Script] INTO #script_fk_off
    	FROM #fk_list;
    
    	--select *
    	--from #script_fk_off;
    
    	DECLARE sql_cursor0 CURSOR LOCAL FOR SELECT
    		[Script]
    	FROM #script_fk_off;
    
    	OPEN sql_cursor0;
    
    	FETCH NEXT FROM sql_cursor0
    	INTO @sql;
    
    	WHILE (@@fetch_status = 0)
    	BEGIN
    	--print @sql;
    
    	EXEC sys.sp_executesql @sql;
    
    	FETCH NEXT FROM sql_cursor0
    	INTO @sql;
    	END
    
    	CLOSE sql_cursor0;
    	DEALLOCATE sql_cursor0;
    
    	DROP TABLE #script_fk_off;
    END
    
    DECLARE sql_cursor CURSOR LOCAL FOR SELECT
    	[Name]
    FROM #tbl;
    
    OPEN sql_cursor;
    
    FETCH NEXT FROM sql_cursor
    INTO @name;
    
    WHILE (@@fetch_status = 0)
    BEGIN
    	DELETE FROM #cols;
    	
    	SET @sql = N'SET QUOTED_IDENTIFIER ON; select N''[''+col.[name]+N'']'' ' +
    	N'from [' + @src + N'].sys.columns as col ' +
    	N'inner join [' + @src + N'].sys.tables as tbl on col.[object_id]=tbl.[object_id] ' +
    	N'where tbl.[name]=''' + @name + ''' and col.[is_identity]=0 and (col.[name] not in (''CheckSumVal'', ''REPL_GUID''))';--+''' and [is_identity]=0';
    	
    	INSERT INTO #cols ([Name])
    	EXEC sys.sp_executesql @sql;
    	
    	SET @upd_listcols = N'';
    	
    	SELECT
    		@upd_listcols = @upd_listcols + N'trg.' + CAST([Name] AS NVARCHAR(MAX)) + N' = src.' + CAST([Name] AS NVARCHAR(MAX)) + N', '
    	FROM #cols;
    	
    	SET @upd_listcols = SUBSTRING(@upd_listcols, 1, LEN(@upd_listcols) - 1);
    	
    	DELETE FROM #cols;
    	
    	SET @sql = N'SET QUOTED_IDENTIFIER ON; select N''[''+col.[name]+N'']'' ' +
    	N'from [' + @src + N'].sys.columns as col ' +
    	N'inner join [' + @src + N'].sys.tables as tbl on col.[object_id]=tbl.[object_id] ' +
    	N'where tbl.[name]=''' + @name + ''' and (col.[name] <> ''CheckSumVal'')';--+''' and [is_identity]=0';
    	
    	INSERT INTO #cols ([Name])
    	EXEC sys.sp_executesql @sql;
    	
    	SET @listcols = N'';
    	
    	SELECT
    		@listcols = @listcols + CAST([Name] AS NVARCHAR(MAX)) + N', '
    	FROM #cols;
    	
    	SET @listcols= SUBSTRING(@listcols,1, LEN(@listcols)-1);
    	
    	DELETE FROM #cols;
    	
    	SET @sql= N'SET QUOTED_IDENTIFIER ON; select N''src.[''+col.[name]+N'']'' '+
    	N'from ['+@src+ N'].sys.columns as col '+
    	N'inner join ['+@src+ N'].sys.tables as tbl on col.[object_id]=tbl.[object_id] '+
    	N'where tbl.[name]='''+@name+''' and (col.[name] <> ''CheckSumVal'')';--+''' and [is_identity]=0';
    	
    	INSERT INTO #cols ([Name])
    	EXEC sys.sp_executesql @sql;
    	
    	SET @ins_listcols= N'';
    	
    	SELECT
    		@ins_listcols=@ins_listcols+ CAST([Name] AS NVARCHAR(MAX))+ N', '
    	FROM #cols;
    	
    	SET @ins_listcols= SUBSTRING(@ins_listcols,1, LEN(@ins_listcols)-1);
    	
    	SET @ParmDefinition= N'@count int';
    	
    	SET @sql= N'SET QUOTED_IDENTIFIER ON; DECLARE @is_identity BIT = 0;
    	   
    	        declare @tbl_id int;
    	   
    	        SELECT TOP (1)
    	        @tbl_id = [object_id]
    	        FROM ['+@trg+ N'].sys.objects
    	        WHERE [name] = '''+@name+ N''';
    	       
    	        SET @is_identity =
    	        CASE
    	            WHEN (EXISTS (SELECT TOP (1)
    	                        1
    	                    FROM ['+@trg+ N'].sys.columns
    	                    WHERE [object_id] = @tbl_id
    	                    AND [is_identity] = 1)
    	                ) THEN 1
    	            ELSE 0
    	        END;
    	       
    	        IF (@is_identity = 1) SET IDENTITY_INSERT ['+@trg+ N'].[dbo].['+@name+ N'] ON;
    	   
    	        --BEGIN TRAN
    	   
    	        ;MERGE TOP (@count)
    	              ['+@trg+ N'].[dbo].['+@name+ N'] AS trg
    	              USING ['+@src+ N'].[dbo].['+@name+ N'] AS src
    	                    ON src.[REPL_GUID] = trg.[REPL_GUID]
    	        WHEN MATCHED AND (src.[CheckSumVal]<>trg.[CheckSumVal])
    	            THEN UPDATE
    	                 SET '+@upd_listcols+ N'
    	        WHEN NOT MATCHED BY TARGET
    	            THEN INSERT ('+@listcols+ N')
    	            VALUES ('+@ins_listcols+ N')
    	        WHEN NOT MATCHED BY SOURCE
    	            THEN DELETE;
    	       
    	        --ROLLBACK TRAN
    	   
    	        IF (@is_identity = 1) SET IDENTITY_INSERT ['+@trg+ N'].[dbo].['+@name+ N'] OFF;';--PRINT @sql;--begin tran
    	EXEC sys.sp_executesql @sql,@ParmDefinition,@count=@count;--rollback tran
    	
    	FETCH NEXT FROM sql_cursor
    	INTO @name;END
    
    CLOSE sql_cursor;
    DEALLOCATE sql_cursor;
    
    DROP TABLE #cols;
    
    DROP TABLE #fk_list;
    
    DROP TABLE #tbl;

    首先,在JobEmplRead数据库中复制表的所有外键。然后使用MERGE语句分批复制数据。在我们的示例中,每个重复有10万行。该脚本包含一次迭代,并执行以下T-SQL代码。

    SET QUOTED_IDENTIFIER ON; 
    ALTER TABLE [JobHistory] NOCHECK CONSTRAINT [FK_JobHistory_Employee_EmployeeID];
    
    SET QUOTED_IDENTIFIER ON; DECLARE @is_identity BIT = 0;
    	   
    	        declare @tbl_id int;
    	   
    	        SELECT TOP (1)
    	        @tbl_id = [object_id]
    	        FROM [JobEmplRead].sys.objects
    	        WHERE [name] = 'Employee';
    	       
    	        SET @is_identity =
    	        CASE
    	            WHEN (EXISTS (SELECT TOP (1)
    	                        1
    	                    FROM [JobEmplRead].sys.columns
    	                    WHERE [object_id] = @tbl_id
    	                    AND [is_identity] = 1)
    	                ) THEN 1
    	            ELSE 0
    	        END;
    	       
    	        IF (@is_identity = 1) SET IDENTITY_INSERT [JobEmplRead].[dbo].[Employee] ON;
    	   
    	        --BEGIN TRAN
    	   
    	        ;MERGE TOP (@count)
    	              [JobEmplRead].[dbo].[Employee] AS trg
    	              USING [JobEmpl].[dbo].[Employee] AS src
    	                    ON src.[REPL_GUID] = trg.[REPL_GUID]
    	        WHEN MATCHED AND (src.[CheckSumVal]<>trg.[CheckSumVal])
    	            THEN UPDATE
    	                 SET trg.[FirstName] = src.[FirstName], trg.[LastName] = src.[LastName], trg.[Address] = src.[Address]
    	        WHEN NOT MATCHED BY TARGET
    	            THEN INSERT ([EmployeeID], [FirstName], [LastName], [Address], [REPL_GUID])
    	            VALUES (src.[EmployeeID], src.[FirstName], src.[LastName], src.[Address], src.[REPL_GUID])
    	        WHEN NOT MATCHED BY SOURCE
    	            THEN DELETE;
    	       
    	        --ROLLBACK TRAN
    	   
    	        IF (@is_identity = 1) SET IDENTITY_INSERT [JobEmplRead].[dbo].[Employee] OFF;

    该脚本应按预先指定的时间间隔自动运行。例如,它可以每分钟运行一次,甚至可以更交替地运行,具体而言分析需求。

    经过多次迭代后,独特行的数量必须减少。

    独特行数的变化

    图9独特行数的变化

    请记住,要在剩余的表中启用外键,应运行以下脚本。

    USE [JobEmplRead]
    GO
    
    DECLARE @sql NVARCHAR(MAX);
    
    CREATE TABLE #fk_list (
    	[TblName] NVARCHAR(255)
       ,[Name] NVARCHAR(255)
    );
    
    SELECT
    	[Name] INTO #tbl
    FROM sys.tables
    WHERE [Name] IN (
    N'Employee',
    N'JobHistory'
    );
    
    INSERT INTO #fk_list ([TblName], [Name])
    	SELECT
    		t.[Name]
    	   ,fk.[Name]
    	FROM sys.foreign_keys AS fk
    	INNER JOIN sys.tables AS tbl
    		ON fk.[parent_object_id] = tbl.[object_id]
    	INNER JOIN #tbl AS t
    		ON t.[Name] = tbl.[Name];
    
    --select * from #fk_list;
    
    IF (EXISTS (SELECT TOP (1)
    			1
    		FROM #fk_list)
    	)
    BEGIN
    	SELECT
    		N'ALTER TABLE [' + [TblName] + N'] CHECK CONSTRAINT [' + [Name] + N']; ' AS [Script] INTO #script_fk_on
    	FROM #fk_list;
    
    	--select *
    	--from #script_fk_on;
    
    	DECLARE sql_cursor0 CURSOR LOCAL FOR SELECT
    		[Script]
    	FROM #script_fk_on;
    
    	OPEN sql_cursor0;
    
    	FETCH NEXT FROM sql_cursor0
    	INTO @sql;
    
    	WHILE (@@fetch_status = 0)
    	BEGIN
    	--print @sql;
    
    	EXEC sys.sp_executesql @sql;
    
    	FETCH NEXT FROM sql_cursor0
    	INTO @sql;
    	END
    
    	CLOSE sql_cursor0;
    	DEALLOCATE sql_cursor0;
    
    	DROP TABLE #script_fk_on;
    END
    
    DROP TABLE #fk_list;
    
    DROP TABLE #tbl;

    在我们的情况下,将生成并执行以下脚本。

    ALTER TABLE [JobHistory] CHECK CONSTRAINT [FK_JobHistory_Employee_EmployeeID];

    请记住,在复制运行之前,除非复制所有数据,否则无法在复制的表上启用外键。

    摘要

    我们已经审查了实现从源到目的地的一个方向上复制表的过程的一种方法。

    但是,当然,必须根据复制表的细节来修改这些脚本。例如,如果表具有计算变量,则可能需要修改。

    SQL Complete是帮助我构建这些脚本的主要工具。该工具还允许代码格式化以及重命名对象及其所有引用。

    参考资料

posted @ 2020-07-13 21:10  syncnavigator  阅读(421)  评论(0编辑  收藏  举报