1. 程式人生 > >sqlserver表結構(含約束)複製儲存過程

sqlserver表結構(含約束)複製儲存過程

        上文中介紹了SQL Server中各種約束以及使用sql查詢各種約束的方法,本文基於上文實現了表結構

(含約束)複製的儲存過程。該儲存過程在SQL Server 2008 及 SQL Server 2014上測試可行。sql如下

 /************************************************************
 * 表結構複製(含約束)儲存過程
 * Time: 2017/7/23 19:54:38
 ************************************************************/
if object_id(N'sp_copy_table' ,N'P') is not null
    drop procedure sp_copy_table;
go

create procedure sp_copy_table
	@srcTableName varchar(200),
	@dstTableName varchar(220)
as
	set nocount on
	
	begin try
		-- 如果源表不存在,則丟擲異常
		declare @tabID varchar(30) = object_id(@srcTableName ,N'U'); 
		if @tabID is null
		    raiserror('src table not exists' ,16 ,1); 
		
		-- 如果目標表已經存在,則丟擲異常
		if object_id(@dstTableName ,N'U') is not null
		    raiserror('destination table already exists' ,16 ,1);
		
		-- 建立表(不復制資料)
		declare @createSql varchar(max) = '';
		set @createSql = 'SELECT * INTO ' + @dstTableName + ' FROM ' + @srcTableName 
		    + ' WHERE 1 > 1';
		exec (@createSql); 
		
		-- ============== 新增約束 ================
		--
		-- ============= 1. unique constraint / primary constraint =============
		declare @tb1 table
		        (IdxName varchar(255) ,colName varchar(255) ,consType tinyint)
		
		declare @tb2 table
		        (IdxName varchar(255) ,colName varchar(255) ,consType tinyint)
		
		-- 查詢原表的主鍵約束、唯一約束(統一約束可能作用與多列上,結果集中作為多列)
		insert into @tb1
		select idx.name as idxName
		      ,col.name as colName
		      ,(case when idx.is_primary_key = 1 then 1 
                             when idx.is_unique_constraint = 1 then 2 else 0 end) consType
		from   sys.indexes idx
		       join sys.index_columns idxCol
		       on (
		           idx.object_id = idxCol.object_id
		           and idx.index_id = idxCol.index_id
		           and (idx.is_unique_constraint = 1 or idx.is_primary_key = 1)
		       )
		       join sys.columns col
		       on (idx.object_id = col.object_id and idxCol.column_id = col.column_id)
		       where idx.[object_id] = @tabID
		
		-- 按照約束名,將同一約束的多行結果集合併為一行,寫入臨時表
		insert into @tb2
		select idxName
		      ,colsName = stuff(
		           (
		               select ',' + colName
		               from   @tb1
		                      where IdxName = a.idxName
		               and consType = a.consType for xml path('')
		           )
		          ,1
		          ,1
		          ,''
		       )
		      ,a.consType
		from   @tb1 a;
		
		-- @tb1 臨時表資料已經沒用,刪除
		delete 
		from   @tb1; 
		
		-- 迴圈遍歷約束,寫入目標表
		declare @checkName     varchar(255)
		       ,@colName       varchar(255)
		       ,@consType      varchar(255)
		       ,@tmp           varchar(max);
		
		while exists(
		          select 1
		          from   @tb2
		      )
		begin
		    select @checkName = IdxName
		          ,@colName = colName
		          ,@consType = consType
		    from   @tb2;
		    
		    -- 主鍵約束		
		    if @consType = 1
		    begin
		        set @tmp = 'ALTER TABLE ' + @dstTableName + ' ADD CONSTRAINT ' 
                            + @checkName + '_01' 
		            + ' PRIMARY KEY (' + @colName + ')';
		        exec (@tmp);
		    end-- 	唯一約束
		    else 
		    if @consType = 2
		    begin
		        set @tmp = 'ALTER TABLE ' + @dstTableName + ' ADD CONSTRAINT ' 
                            + @checkName + '_01' 
		            + ' UNIQUE (' + @colName + ')'; 
		        exec (@tmp);
		    end
		    
		    -- 使用完後,刪除
		    delete 
		    from   @tb2
		    where  IdxName = @checkName
		           and colName = @colName;
		end
		
		-- ================= 2. 外來鍵約束 ===================
		declare @tb3 table (fkName varchar(255) ,colName varchar(255) ,referTabName varchar(255),
                      referColName varchar(255))
		
		-- 查詢源表外來鍵約束,寫入臨時表
		insert into @tb3
		select fk.name as fkName
		      ,SubCol.name as colName
		      ,oMain.name as referTabName
		      ,MainCol.name as referColName
		from   sys.foreign_keys fk
		       join sys.all_objects oSub
		       on (fk.parent_object_id = oSub.object_id)
		       join sys.all_objects oMain
		       on (fk.referenced_object_id = oMain.object_id)
		       join sys.foreign_key_columns fkCols
		       on (fk.object_id = fkCols.constraint_object_id)
		       join sys.columns SubCol
		       on (oSub.object_id = SubCol.object_id and fkCols.parent_column_id = SubCol.column_id)
		       join sys.columns MainCol
		       on (oMain.object_id = MainCol.object_id
                           and fkCols.referenced_column_id = MainCol.column_id)
		       where oSub.[object_id] = @tabID;
		
		-- 遍歷每一個外來鍵約束,寫入目標表
		declare @referTabName     varchar(255)
		       ,@referColName     varchar(255);
		while exists(
		          select 1
		          from   @tb3
		      )
		begin
		    select @checkName = fkName
		          ,@colName = colName
		          ,@referTabName = referTabName
		          ,@referColName = referColName
		    from   @tb3;
		    
		    set @tmp = 'ALTER TABLE ' + @dstTableName + ' ADD CONSTRAINT ' 
                        + @checkName + '_01' 
		        + ' FOREIGN KEY (' + @colName + ') REFERENCES ' 
                        + @referTabName + '(' + @referColName + ')';
		    exec (@tmp);
		    
		    delete 
		    from   @tb3
		    where  fkName = @checkName;
		end
		
		-- =============== 3.CHECK約束 ===================
		declare @tb4 table (checkName varchar(255) ,colName varchar(255) ,
                    definition varchar(max));
		
		insert into @tb4
		select chk.name as checkName
		      ,col.name as colName
		      ,chk.definition
		from   sys.check_constraints chk
		       join sys.columns col
		       on (chk.parent_object_id = col.object_id and chk.parent_column_id = col.column_id)
		       where chk.parent_object_id = @tabID
		
		-- 遍歷每一個CHECK約束,為目標表新增約束
		declare @definition varchar(max);
		while exists(
		          select 1
		          from   @tb4
		      )
		begin
		    select @checkName = checkName
		          ,@colName = colName
		          ,@definition = [definition]
		    from   @tb4;
		    
		    set @tmp = 'ALTER TABLE ' + @dstTableName + ' ADD CONSTRAINT ' 
                        + @checkName + '_01' 
		        + ' CHECK ' + @definition;
		    exec (@tmp);
		    
		    delete 
		    from   @tb4
		    where  checkName = @checkName;
		end
		
		--  ================ 4. default約束 =====================
		insert into @tb4
		select df.name as checkName
		      ,c.name as colName
		      ,df.definition
		from   sys.default_constraints df
		       join sys.[columns] as c
		       on df.parent_column_id = c.column_id
		       and df.parent_object_id = c.[object_id]
		       where df.parent_object_id = @tabID;
		
		-- 遍歷每一個default 約束
		while exists(
		          select 1
		          from   @tb4
		      )
		begin
		    select @checkName = checkName
		          ,@colName = colName
		          ,@definition = [definition]
		    from   @tb4;
		    
		    set @tmp = 'ALTER TABLE ' + @dstTableName + ' ADD CONSTRAINT ' 
                        + @checkName + '_01' 
		        + ' DEFAULT ' + @definition + ' FOR ' + @colName;
		    print 'default: ' + @tmp;
		    exec (@tmp);
		    
		    delete 
		    from   @tb4
		    where  checkName = @checkName;
		end
	end try
	begin catch
		-- 輸出錯誤資訊
		select error_number() as ErrorNumber
		      ,error_severity() as ErrorSeverity
		      ,error_state() as ErrorState
		      ,error_procedure() as ErrorProcedure
		      ,error_line() as ErrorLine
		      ,error_message() as ErrorMessage
	end catch

        儲存過程定義好之後,就可以呼叫該儲存過程進行表結構複製了。這裡使用ReportServer資料庫的 Schedule表做測試  

use ReportServer
exec dbo.sp_copy_table
     @srcTableName = '[dbo].Schedule'
    ,@dstTableName = '[dbo].Schedule_bak'

exec sp_help 'dbo.Schedule_bak'
      可以發現,已經成功複製了ReportServer資料庫的 Schedule表


      注意事項:

      1. 由於該儲存過程中使用了所在資料庫的一些系統表,不同的資料庫所含的系統表的資料不一致,

該儲存過程不支援跨資料庫複製表結構。使用時務必在需要進行表複製的資料庫新建該儲存過程,

再呼叫儲存過程進行表結構複製

      2. 上面ReportServer資料庫的Schedule表進行表結構複製時,會有如下警告:

警告! 最大鍵長度為 900 個位元組。索引 'IX_Schedule_01' 的最大長度為 1040 個位元組。
 對於某些大值組合,插入/更新操作將失敗。

      這個是由於Schedule表的唯一約束含有兩個長度為520的varchar列,導致最大長度超出鍵的最大長度900導致的

    3. 使用 select into 複製表結構時,如果原資料表內有各類約束(預設約束,唯一值約束等等)則會因為約束名重複

而導致約束丟失。因此,這裡使用相關表查詢源表的約束,然後在約束名後新增字尾之後再設定到新建的目標表