【问题标题】:Identity insert on linked server fails链接服务器上的身份插入失败
【发布时间】:2012-09-12 15:35:05
【问题描述】:

我想使用存储过程将表从我的测试数据库复制到具有相同 ID/身份的链接服务器,但我无法让它工作.. 我已将 IDENTITY_INSERT 设置为 ON 但它仍然抱怨 ID 列。

这是我的程序:

CREATE PROCEDURE [dbo].[TEST2PROD_CopyUIDataSServer]
AS Begin
declare @sql nvarchar(max)
-- First truncate target table
set @sql = 'EXEC [LINKEDSERVER].tempdb.sys.sp_sqlexec' + char(39)+ 'TRUNCATE Table [ProductManager].dbo.[UIData]' + char(39)+  ';'
---- SET IDENTITY_INSERT ON
set @sql = @sql + 'EXEC [LINKEDSERVER].tempdb.sys.sp_sqlexec' + char(39)+ 'SET IDENTITY_INSERT [ProductManager].[dbo].[UIData] ON' + char(39)+  ';'
---- INSERT UIDATA records from DB1 into linked server DB2
set @sql = @sql + 'WITH TestData as (SELECT * from ProductManager.dbo.UIData UID)' + NCHAR(13)+  'INSERT INTO   [LINKEDSERVER].[ProductManager].[dbo].[UIData]' + NCHAR(13) + 'select * from TestData;'
print @sql
exec (@sql) 
end

但是当我执行 SP 时,它给了我以下错误:

链接服务器的 OLE DB 提供程序“SQLNCLI10”......由于列“Id”,无法插入表“[LINKEDSERVER].[ProductManager].[dbo].[UIData]”。用户无权写入该列。

链接服务器属性 RPC 和 RPC out 设置为 true。我希望有人能在这里帮助我吗?

更新:我决定把事情分开,首先我将数据从本地服务器复制到 TEMP_TABLE 中的链接服务器,我不必处理 IDENTITY 问题.

然后我在链接/远程服务器上编写了一个存储过程,因为我没有使用SELECT *,而是指定了列列表。很有可能这也可以在 SP 中的本地服务器上运行,但我还没有时间或兴趣去检查它。..

USE [ProductManager]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[TEST2PROD_CopyBaseTables]
AS BEGIN
DECLARE @DestTable VARCHAR(50)
DECLARE @DestPath VARCHAR(50)
DECLARE @SrceTable VARCHAR(255)
declare @sql nvarchar(max)
DECLARE @columnList varchar(max)
DECLARE @err int

Begin TRY

declare @comma_delimited_list varchar(4000) 
--- FIRST TRY WITH ONE TABLE, EXTENDABLE...
set @comma_delimited_list = 'UIData' 

declare @cursor cursor 
set @cursor = cursor static for  
  select * from dbo.Split(@comma_delimited_list,',') a 

declare @naam varchar(50)
open @cursor 
while 1=1 begin 
  fetch next from @cursor into @DestTable
  if @@fetch_status <> 0 break 

    --Create tablenames
    SET @SrceTable = '[ProductManager].[dbo].TEMP_' + @DestTable
    SET @DestPath = '[ProductManager].[dbo].'+ @DestTable 
    print @srceTable;
    print @DestTable;

    --Truncate target table
    set @sql ='TRUNCATE TABLE '+ @DestPath + ';'

    --Insert statement needs column names
    set @columnList =''
    SELECT  @columnList = coalesce(@columnList + '[' + name + '],','') FROM sys.columns Where OBJECT_NAME(OBJECT_ID) = @DestTable
    if RIGHT(RTRIM(@columnList),1) = ',' 
    begin
        SET @columnList = LEFT(@columnList, LEN(@columnList) - 1) 
    end

    --Transfer data from source table 2 destination
     set @sql = @sql + ' SET IDENTITY_INSERT ' + @DestPath + ' ON;' + ' INSERT INTO ' + @DestPath + '(' + @columnList + ') SELECT ' + @columnList + ' FROM ' + @SrceTable

    print @sql;

    exec (@sql)
end 
-- not strictly necessary w/ cursor variables since the will go out of scope like a normal var 
close @cursor 
deallocate @cursor 

End Try
Begin Catch
 declare @ErrorMsg nvarchar(MAX);
 select @ErrorMsg =  ERROR_MESSAGE();

SELECT @err = @@error IF @err <> 0 Return @err
end Catch
END

【问题讨论】:

    标签: sql sql-server sql-server-2008


    【解决方案1】:

    IDENTITY_INSERT 不适用于链接服务器 AFAIK,除非您在批处理中执行包含 SET IDENTITY_INSERT 的动态 SQL,或者在远程服务器上有一些代码(例如存储过程)为您执行此操作。

    IDENTITY_INSERT 是每个会话的(参见MSDN),当您使用远程服务器时,这可能与您通过[LINKEDSERVER].tempdb.sys.sp_sqlexec 执行的语句处于不同的会话中,这会导致它在您看到它时失败正在发生。

    【讨论】:

    • 感谢您的回答 Lucero 但如果我创建一个 SP 以在远程服务器上打开身份并调用它:set ATsql = ATsql + 'EXEC [37.230.100.75].ProductManager.dbo.TEST2PROD_IdentityOn; '我犯了同样的错误?问候,迈克
    • @MikeDole,整个代码(设置身份插入,插入数据,设置身份插入关闭)需要在那个 SP 中。例如。您可以将要插入的单个项目提供给 SP,或者如果您想要进行设置操作,您需要传入一个 TVP 或让链接服务器使用本地服务器作为链接服务器。
    • 仅供参考。通过将其包装在动态 sql 中,它不会神奇地工作。您仍然会收到此错误:对象名称“LINKED_SERVER.DestinationDatabase.dbo.DestinationTable”包含的前缀数量超过了最大数量。最大值为 2。
    • 我可以让它工作的唯一方法是在目标链接服务器上的存储过程中设置 IDENTITY_INSERT。
    • 事实上,链接服务器不支持这一点(这一发现导致我重新设计了一些数据表并使用 uniqueidentifier 而不是 IDENTITY 列)。
    【解决方案2】:

    您可以使用“SWITCH TO”技巧将身份值插入链接服务器上具有身份列的表中。

    如果您没有使用“SWITCH TO”技巧在列上添加和删除标识,它会非常快,即使在大表上也是如此!

    从概念上讲,您只需创建一个新的 SCHEMA,就像您想要 INSERT 的表一样没有定义的身份。然后将表切换到该 SCHEMA 并执行您的INSERT。然后切换回已定义身份的 SCHEMA。
    下面的示例已在 AZURE 中的链接服务器上进行了测试。 使用“SWITCH TO”的所有注意事项都适用(索引必须相同,删除并重新创建外键等)

    要进行测试,您可以在链接的 Azure SQL Server 数据库上运行下面的完整脚本。您需要使用[LINKED_SERVER_NAME][DATABASE_NAME] 进行查找/替换,替换为您的值。在非 Azure DB 上,您可能需要将“ON PRIMARY”添加到表创建中。

    --Let's setup the example by creating a table with an IDENTITY column on the Linked Server
    EXEC('
    CREATE TABLE [DATABASE_NAME].[dbo].[Example_Table](
        [ID] [int] IDENTITY(1,1) NOT NULL,
        [Name] [nchar](10) NULL
    )
     '
    ) AT [LINKED_SERVER_NAME]
    
    --INSERT some data into the table
    INSERT INTO [LINKED_SERVER_NAME].[DATABASE_NAME].[dbo].[Example_Table] ([Name]) VALUES ('Travis')
    INSERT INTO [LINKED_SERVER_NAME].[DATABASE_NAME].[dbo].[Example_Table] ([Name]) VALUES ('Mike')
    
    -- Looks good
    SELECT * FROM [LINKED_SERVER_NAME].[DATABASE_NAME].[dbo].[Example_Table]
    
    GO
    
    
    -- Create a TABLE with an identical schema, without the identity defined
    EXEC('
    CREATE TABLE [DATABASE_NAME].[dbo].[Example_Table_temp](
        [ID] [int] NOT NULL,
        [Name] [nchar](10) NULL
    )
     '
    ) AT [LINKED_SERVER_NAME]
    
    --Now Use the "SWITCH TO" to move the data to the new table
    EXEC('
     ALTER TABLE [DATABASE_NAME].[dbo].[Example_Table] SWITCH TO [DATABASE_NAME].[dbo].[Example_Table_temp]
      '
    ) AT [LINKED_SERVER_NAME]
    
    
    --Drop the old table (It should now be empty, but you may want to verify that if you are unsure here)
    EXEC('
     DROP TABLE [DATABASE_NAME].[dbo].[Example_Table]
     '
    ) AT [LINKED_SERVER_NAME] 
    
    --Rename the new table back to the old table name
    -- NOTE the lack of database and owner identifiers in the new name
    -- NOTE the use of double single qoutes (ESCAPED single quotes)
    EXEC('USE [DATABASE_NAME];
      EXEC sp_rename ''[DATABASE_NAME].[dbo].Example_Table_temp'',''Example_Table''
       '
    ) AT [LINKED_SERVER_NAME] 
      
    
      -- Now do your IDENTITY INSERTs !!!!
    
     INSERT INTO [LINKED_SERVER_NAME].[DATABASE_NAME].[dbo].[Example_Table] (ID,[Name]) VALUES (888,'Travis')
     INSERT INTO [LINKED_SERVER_NAME].[DATABASE_NAME].[dbo].[Example_Table] (ID,[Name]) VALUES (999,'Mike')
    
     --Verify they got put in
    SELECT * FROM [LINKED_SERVER_NAME].[DATABASE_NAME].[dbo].[Example_Table] 
    
      
      --Now let's switch it back to our SCHEMA with an IDENTITY
    
      EXEC('
    CREATE TABLE [DATABASE_NAME].[dbo].[Example_Table_temp](
        [ID] [int] IDENTITY(1,1) NOT NULL,
        [Name] [nchar](10) NULL
    )
    
     ALTER TABLE [DATABASE_NAME].[dbo].[Example_Table] SWITCH TO [DATABASE_NAME].[dbo].[Example_Table_temp]
    
     DROP TABLE [DATABASE_NAME].[dbo].[Example_Table]
    
      EXEC sp_rename ''[DATABASE_NAME].[dbo].Example_Table_temp'',''Example_Table''
         '
    ) AT [LINKED_SERVER_NAME] 
    
    --Data is still there
     SELECT * FROM [LINKED_SERVER_NAME].[DATABASE_NAME].[dbo].[Example_Table] 
     GO
     -- And note you can no longer INSERT the IDENTITY
     INSERT INTO [LINKED_SERVER_NAME].[DATABASE_NAME].[dbo].[Example_Table] (ID,[Name]) VALUES (45,'Travis')
    GO
    

    【讨论】:

    • 非常有用。请注意,我必须在调用SWITCH TO 后调用DBCC CHECKIDENT ('[dbo].Example_Table_temp');,才能将标识重置为正确的新值。
    【解决方案3】:

    您需要执行动态查询示例:

    exec [LINKSERVERNAME].[DATABASENAME].[SCHEMANAME].sp_executesql N'Your Query'
    

    如果有任何列集标识,您需要设置SET IDENTITY_INSERT TargetTable ON 并需要指定列名。示例:

    SET IDENTITY_INSERT TargetTable ON;
    INSERT INTO TargetTable(Col1, Col2, Col3)
    SELECT Col1, Col2, Col3 FROM SourceTable;
    SET IDENTITY_INSERT TargetTable OFF;
    

    【讨论】:

    • 这不需要从远程数据库链接回源数据库,以便跨数据库INSERT ... SELECT 可以工作吗?
    • 我喜欢这个主意。它与任何动态 SQL 类似,但“Select Col1, Col2...”更像是“VALUES (1,2,3),(4,5,6) 等”。
    猜你喜欢
    • 2017-05-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-18
    • 2018-07-09
    • 2020-06-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多