【问题标题】:How to drop IDENTITY property of column in SQL Server 2005如何在 SQL Server 2005 中删除列的 IDENTITY 属性
【发布时间】:2010-10-17 08:53:24
【问题描述】:

我希望能够将具有标识列的表中的数据插入 SQL Server 2005 中的临时表中。

TSQL 看起来像:

-- Create empty temp table
SELECT *
INTO #Tmp_MyTable
FROM MyTable
WHERE 1=0
...
WHILE ...
BEGIN
    ...
    INSERT INTO #Tmp_MyTable
    SELECT TOP (@n) *
    FROM MyTable
    ...

END

上面的代码使用标识列创建了#Tmp_Table,随后插入失败并出现错误“表'#Tmp_MyTable'中标识列的显式值只能在使用列列表并且IDENTITY_INSERT为ON时指定。”

在 TSQL 中有没有办法删除临时表中列的标识属性而不显式列出所有列?我特别想使用“SELECT *”,以便在将新列添加到 MyTable 时代码将继续工作。

我相信删除和重新创建列会改变它的位置,从而无法使用 SELECT *。

更新:

我已尝试按照一个回复中的建议使用 IDENTITY_INSERT。它不起作用 - 请参阅下面的重现。我做错了什么?

-- Create test table
CREATE TABLE [dbo].[TestTable](
    [ID] [numeric](18, 0) IDENTITY(1,1) NOT NULL,
    [Name] [varchar](50) NULL,
 CONSTRAINT [PK_TestTable] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)
) 
GO
-- Insert some data
INSERT INTO TestTable
(Name)
SELECT 'One'
UNION ALL
SELECT 'Two'
UNION ALL
SELECT 'Three'
GO
-- Create empty temp table
SELECT *
INTO #Tmp
FROM TestTable
WHERE 1=0

SET IDENTITY_INSERT #Tmp ON -- I also tried OFF / ON
INSERT INTO #Tmp
SELECT TOP 1 * FROM TestTable

SET IDENTITY_INSERT #Tmp OFF 
GO
-- Drop test table
DROP TABLE [dbo].[TestTable]
GO

请注意,错误消息“只有在使用列列表时并且 IDENTITY_INSERT 为 ON,才能为表 '#TmpMyTable' 中的标识列指定显式值。” em> - 我特别不想使用上面解释的列列表。

更新 2 试过the suggestion from Mike 但这给出了同样的错误:

-- Create empty temp table
SELECT *
INTO #Tmp
FROM (SELECT
      m1.*
      FROM TestTable                 m1
          LEFT OUTER JOIN TestTable  m2 ON m1.ID=m2.ID
      WHERE 1=0
 ) dt

INSERT INTO #Tmp
SELECT TOP 1 * FROM TestTable

至于我为什么要这样做:MyTable 是一个临时表,可以包含大量要合并到另一个表中的行。我想处理暂存表中的行,插入/更新我的主表,然后在每个事务处理 N 行的循环中从暂存表中删除它们。我意识到还有其他方法可以实现这一目标。

更新 3

我无法让Mike's solution 工作,但它建议了以下可行的解决方案:使用非身份列作为前缀并删除身份列:

SELECT CAST(1 AS NUMERIC(18,0)) AS ID2, *
INTO #Tmp
FROM TestTable
WHERE 1=0
ALTER TABLE #Tmp DROP COLUMN ID

INSERT INTO #Tmp
SELECT TOP 1 * FROM TestTable

Mike 建议仅将键存储在临时表中也是一个不错的建议,尽管在这种特定情况下,我更喜欢将所有列都放在临时表中是有原因的。

【问题讨论】:

  • 它对我有用,看我回答...
  • 更新 3 中的解决方案对我来说非常有用。我试图将 UPDATE 的 OUTPUT 放入临时表中而不指定列(大约 100 个!)

标签: sql sql-server-2005 tsql


【解决方案1】:

在 SQL Server 上删除标识列(尤其是对于大型数据库)的最有效方法是直接修改 DDL 元数据,在 2005 年以前的 SQL Server 上,可以这样做:

sp_configure 'allow update', 1
go
reconfigure with override
go

update syscolumns set colstat = 0 --turn off bit 1 which indicates identity column
where id = object_id('table_name') and name = 'column_name'
go

exec sp_configure 'allow update', 0
go
reconfigure with override
go

SQL Server 2005+ 不支持reconfigure with override,但您可以在单用户模式下启动 SQL Server 实例时执行 Ad Hoc Queries(使用 -m 启动 db instance 标志,即“C:\Program Files\Microsoft SQL Server\MSSQL10.MSSQLSERVER\MSSQL\Binn\sqlservr.exe -m”,确保以管理员身份运行)与专用管理控制台(来自 SQL Management Studio 连接ADMIN: 前缀,即 ADMIN:MyDatabase)。列元数据存储在 sys.sysschobjs 内部表中(未显示没有 DAC):

use myDatabase

update sys.syscolpars set status = 1, idtval = null -- status=1 - primary key, idtval=null - remove identity data
where id = object_id('table_name') AND name = 'column_name' 

更多关于这种方法on this blog

【讨论】:

【解决方案2】:

我编写了这个程序,作为自动和快速删除列标识的许多答案的汇编:

CREATE PROCEDURE dbo.sp_drop_table_identity @tableName VARCHAR(256) AS
BEGIN
    DECLARE @sql VARCHAR (4096);
    DECLARE @sqlTableConstraints VARCHAR (4096);
    DECLARE @tmpTableName VARCHAR(256) = @tableName + '_noident_temp';

    BEGIN TRANSACTION

    -- 1) Create temporary table with edentical structure except identity
    -- Idea borrowed from https://stackoverflow.com/questions/21547/in-sql-server-how-do-i-generate-a-create-table-statement-for-a-given-table
    -- modified to ommit Identity and honor all constraints, not primary key only!
    SELECT
        @sql = 'CREATE TABLE [' + so.name + '_noident_temp] (' + o.list + ')'
        + ' ' + j.list
    FROM sysobjects so
    CROSS APPLY (
        SELECT
            ' [' + column_name + '] '
            + data_type
            + CASE data_type
                WHEN 'sql_variant' THEN ''
                WHEN 'text' THEN ''
                WHEN 'ntext' THEN ''
                WHEN 'xml' THEN ''
                WHEN 'decimal' THEN '(' + CAST(numeric_precision as VARCHAR) + ', ' + CAST(numeric_scale as VARCHAR) + ')'
                ELSE COALESCE('(' + CASE WHEN character_maximum_length = -1 THEN 'MAX' ELSE CAST(character_maximum_length as VARCHAR) END + ')', '')
            END
            + ' '
            /* + case when exists ( -- Identity skip
            select id from syscolumns
            where object_name(id)=so.name
            and name=column_name
            and columnproperty(id,name,'IsIdentity') = 1
            ) then
            'IDENTITY(' +
            cast(ident_seed(so.name) as varchar) + ',' +
            cast(ident_incr(so.name) as varchar) + ')'
            else ''
            end + ' ' */
            + CASE WHEN IS_NULLABLE = 'No' THEN 'NOT ' ELSE '' END
            + 'NULL'
            + CASE WHEN information_schema.columns.column_default IS NOT NULL THEN ' DEFAULT ' + information_schema.columns.column_default ELSE '' END
            + ','
        FROM
            INFORMATION_SCHEMA.COLUMNS
        WHERE table_name = so.name
        ORDER BY ordinal_position
        FOR XML PATH('')
    ) o (list)
    CROSS APPLY(
        SELECT
            CHAR(10) + 'ALTER TABLE ' + @tableName + '_noident_temp ADD ' + LEFT(alt, LEN(alt)-1)
        FROM(
            SELECT
                CHAR(10)
                + ' CONSTRAINT ' + tc.constraint_name  + '_ni ' + tc.constraint_type + ' (' + LEFT(c.list, LEN(c.list)-1) + ')'
                + COALESCE(CHAR(10) + r.list, ', ')
            FROM
                information_schema.table_constraints tc
                CROSS APPLY(
                    SELECT
                        '[' + kcu.column_name + '], '
                    FROM
                        information_schema.key_column_usage kcu
                    WHERE
                        kcu.constraint_name = tc.constraint_name
                    ORDER BY
                        kcu.ordinal_position
                    FOR XML PATH('')
                ) c (list)
                OUTER APPLY(
                    -- https://stackoverflow.com/questions/3907879/sql-server-howto-get-foreign-key-reference-from-information-schema
                    SELECT
                        '  REFERENCES [' + kcu1.constraint_schema + '].' + '[' + kcu2.table_name + ']' + '([' + kcu2.column_name + ']) '
                        + CHAR(10)
                        + '    ON DELETE ' + rc.delete_rule
                        + CHAR(10)
                        + '    ON UPDATE ' + rc.update_rule + ', '
                    FROM information_schema.referential_constraints as rc
                        JOIN information_schema.key_column_usage as kcu1 ON (kcu1.constraint_catalog = rc.constraint_catalog AND kcu1.constraint_schema = rc.constraint_schema AND kcu1.constraint_name = rc.constraint_name)
                        JOIN information_schema.key_column_usage as kcu2 ON (kcu2.constraint_catalog = rc.unique_constraint_catalog AND kcu2.constraint_schema = rc.unique_constraint_schema AND kcu2.constraint_name = rc.unique_constraint_name AND kcu2.ordinal_position = KCU1.ordinal_position)
                    WHERE
                        kcu1.constraint_catalog = tc.constraint_catalog AND kcu1.constraint_schema = tc.constraint_schema AND kcu1.constraint_name = tc.constraint_name
                ) r (list)
            WHERE tc.table_name = @tableName
            FOR XML PATH('')
        ) a (alt)
    ) j (list)
    WHERE
        xtype = 'U'
    AND name NOT IN ('dtproperties')
    AND so.name = @tableName

    SELECT @sql as '1) @sql';
    EXECUTE(@sql);

    -- 2) Obtain current back references on our table from others to reenable it later
    -- https://stackoverflow.com/questions/3907879/sql-server-howto-get-foreign-key-reference-from-information-schema
    SELECT
        @sqlTableConstraints = (
            SELECT
                'ALTER TABLE [' + kcu1.constraint_schema + '].' + '[' + kcu1.table_name + ']'
                + ' ADD CONSTRAINT ' + kcu1.constraint_name + '_ni FOREIGN KEY ([' + kcu1.column_name + '])'
                + CHAR(10)
                + '  REFERENCES ['  + kcu2.table_schema + '].[' + kcu2.table_name + ']([' + kcu2.column_name + '])'
                + CHAR(10)
                + '    ON DELETE ' + rc.delete_rule
                + CHAR(10)
                + '    ON UPDATE ' + rc.update_rule + ' '
            FROM information_schema.referential_constraints as rc
                JOIN information_schema.key_column_usage as kcu1 ON (kcu1.constraint_catalog = rc.constraint_catalog AND kcu1.constraint_schema = rc.constraint_schema AND kcu1.constraint_name = rc.constraint_name)
                JOIN information_schema.key_column_usage as kcu2 ON (kcu2.constraint_catalog = rc.unique_constraint_catalog AND kcu2.constraint_schema = rc.unique_constraint_schema AND kcu2.constraint_name = rc.unique_constraint_name AND kcu2.ordinal_position = KCU1.ordinal_position)
            WHERE
                kcu2.table_name = 'department'
            FOR XML PATH('')
        );
    SELECT @sqlTableConstraints as '8) @sqlTableConstraints';
    -- Execute at end

    -- 3) Drop outer references for switch (structure must be identical: http://msdn.microsoft.com/en-gb/library/ms191160.aspx) and rename table
    SELECT
        @sql = (
            SELECT
                ' ALTER TABLE [' + kcu1.constraint_schema + '].' + '[' + kcu1.table_name + '] DROP CONSTRAINT ' + kcu1.constraint_name
            FROM information_schema.referential_constraints as rc
                JOIN information_schema.key_column_usage as kcu1 ON (kcu1.constraint_catalog = rc.constraint_catalog AND kcu1.constraint_schema = rc.constraint_schema AND kcu1.constraint_name = rc.constraint_name)
                JOIN information_schema.key_column_usage as kcu2 ON (kcu2.constraint_catalog = rc.unique_constraint_catalog AND kcu2.constraint_schema = rc.unique_constraint_schema AND kcu2.constraint_name = rc.unique_constraint_name AND kcu2.ordinal_position = KCU1.ordinal_position)
            WHERE
                kcu2.table_name = @tableName
            FOR XML PATH('')
        );
    SELECT @sql as '3) @sql'
    EXECUTE (@sql);

    -- 4) Switch partition
    -- http://www.calsql.com/2012/05/removing-identity-property-taking-more.html
    SET @sql = 'ALTER TABLE ' + @tableName + ' switch partition 1 to ' + @tmpTableName;
    SELECT @sql as '4) @sql';
    EXECUTE(@sql);

    -- 5) Rename real old table to bak
    SET @sql = 'EXEC sp_rename ' + @tableName + ', ' + @tableName + '_bak';
    SELECT @sql as '5) @sql';
    EXECUTE(@sql);

    -- 6) Rename temp table to real
    SET @sql = 'EXEC sp_rename ' + @tmpTableName + ', ' + @tableName;
    SELECT @sql as '6) @sql';
    EXECUTE(@sql);

    -- 7) Drop bak table
    SET @sql = 'DROP TABLE ' + @tableName + '_bak';
    SELECT @sql as '7) @sql';
    EXECUTE(@sql);

    -- 8) Create again doped early constraints
    SELECT @sqlTableConstraints as '8) @sqlTableConstraints';
    EXECUTE(@sqlTableConstraints);


    -- It still may fail if there references from objects with WITH CHECKOPTION
    -- it may be recreated - https://stackoverflow.com/questions/1540988/sql-2005-force-table-rename-that-has-dependencies
    COMMIT
END

使用非常简单:

EXEC sp_drop_table_identity @tableName = 'some_very_big_table'

优点和限制:

  1. 它使用switch partition(也适用于未分区表)语句进行无需完整数据复制的快速移动。它还应用了一些适用条件。
  2. 它会在没有身份的情况下即时制作表格副本。这样的解决方案I also post separately,它还可能需要调整像复合字段这样不那么琐碎的结构(它满足了我的需求)。
  3. 如果表包含在具有受CHECKOUPTION(sp,视图)绑定架构的对象中,它会阻止执行切换(参见代码中的最后一条注释)。可以另外编写脚本以临时删除此类绑定。我还没有这样做。

欢迎所有反馈。

【讨论】:

    【解决方案3】:

    如果您只是按照您的描述处理行,那么将前 N 个主键值选择到临时表中不是更好吗:

    CREATE TABLE #KeysToProcess
    (
         TempID    int  not null primary key identity(1,1)
        ,YourKey1  int  not null
        ,YourKey2  int  not null
    )
    
    INSERT INTO #KeysToProcess (YourKey1,YourKey2)
    SELECT TOP n YourKey1,YourKey2  FROM MyTable
    

    键不应该经常更改(我希望如此),但其他列可以这样做而不会造成伤害。

    获取插入的@@ROWCOUNT,您可以在 TempID 上执行一个简单的循环,从 1 到 @@ROWCOUNT

    和/或

    只需将#KeysToProcess 加入您的 MyKeys 表即可,无需复制所有数据。

    这在我的 SQL Server 2005 上运行良好,其中 MyTable.MyKey 是一个标识列。

    -- Create empty temp table
    SELECT *
    INTO #TmpMikeMike
    FROM (SELECT
          m1.*
          FROM MyTable                 m1
              LEFT OUTER JOIN MyTable  m2 ON m1.MyKey=m2.MyKey
          WHERE 1=0
     ) dt
    
    INSERT INTO #TmpMike
    SELECT TOP 1 * FROM MyTable
    
    SELECT * from #TmpMike
    



    编辑
    这工作,没有错误...

    -- Create empty temp table
    SELECT *
    INTO #Tmp_MyTable
    FROM (SELECT
              m1.*
              FROM MyTable                 m1
                  LEFT OUTER JOIN MyTable  m2 ON m1.KeyValue=m2.KeyValue
              WHERE 1=0
         ) dt
    ...
    WHILE ...
    BEGIN
        ...
        INSERT INTO #Tmp_MyTable
        SELECT TOP (@n) *
        FROM MyTable
        ...
    
    END
    

    但是,您真正的问题是什么? 为什么在将“*”插入此临时表时需要循环?您也许可以改变策略并提出一个整体上更好的算法。

    【讨论】:

    • 对我不起作用。我尝试按照您的建议修改我的复制品,见上文。
    • 嵌套查询的另一个选项是SELECT m1.* FROM MyTable m1 LEFT OUTER JOIN MyTable m2 ON 1 = 1 WHERE 1 = 0 然后你不需要知道关于表列结构的任何事情。
    【解决方案4】:

    你可以试试

    SET IDENTITY_INSERT #Tmp_MyTable ON 
    -- ... do stuff
    SET IDENTITY_INSERT #Tmp_MyTable OFF
    

    这将允许您选择进入#Tmp_MyTable,即使它有一个标识列。

    但这不会工作:

    -- Create empty temp table
    SELECT *
    INTO #Tmp_MyTable
    FROM MyTable
    WHERE 1=0
    ...
    WHILE ...
    BEGIN
        ...
        SET IDENTITY_INSERT #Tmp_MyTable ON 
    
        INSERT INTO #Tmp_MyTable
        SELECT TOP (@n) *
        FROM MyTable
    
        SET IDENTITY_INSERT #Tmp_MyTable OFF 
        ...    
    END
    

    (导致错误“只有在使用列列表并且 IDENTITY_INSERT 为 ON 时才能指定表 '#Tmp' 中标识列的显式值。”)

    似乎没有实际删除列的方法 - 但这会改变 OP 提到的列的顺序。丑陋的 hack:基于#Tmp_MyTable 创建一个新表...

    我建议您编写一个存储过程,该过程基于表名 (MyTable) 创建一个临时表,该表具有相同的列(按顺序),但缺少标识属性。

    您可以使用以下代码:

    select t.name as tablename, typ.name as typename, c.*
    from sys.columns c inner join
         sys.tables t on c.object_id = t.[object_id] inner join
         sys.types typ on c.system_type_id = typ.system_type_id
    order by t.name, c.column_id
    

    了解反射在 TSQL 中的工作原理。我相信您将不得不遍历相关表的列并对生成的表执行动态(手工制作、存储在字符串中然后评估)alter 语句。

    您介意为世界其他地方发布这样一个存储过程吗?这个问题似乎在其他论坛也出现了很多......

    【讨论】:

    • @tvanfosson - michaelpryor 是对的。您需要打开 identity_insert 以允许插入。然后,完成后将其关闭。
    • @NY -- 对,不知道我在想什么。先前的评论已删除。
    • 对于简单的解决方法,请参阅下面的解决方案...我使用带有连接的派生表,从“INTO”表中删除身份...
    【解决方案5】:

    EDIT 按照 Daren 的建议切换 IDENTITY_INSERT 肯定是更优雅的方法,在我的情况下,我需要消除标识列,以便我可以将选定的数据重新插入源表中

    我解决此问题的方法是像您一样创建临时表,显式删除标识列,然后动态构建 sql,以便我有一个排除标识列的列列表(如您的情况如此如果架构发生更改,proc 仍然可以工作)然后执行 sql 这是一个示例

    declare @ret int
    Select * into #sometemp from sometable
    Where
    id = @SomeVariable
    
    Alter Table #sometemp Drop column SomeIdentity 
    
    Select @SelectList = ''
    Select @SelectList = @SelectList 
    + Coalesce( '[' + Column_name + ']' + ', ' ,'')
    from information_schema.columns
    where table_name = 'sometable'
    and Column_Name <> 'SomeIdentity'
    
    Set @SelectList = 'Insert into sometable (' 
    + Left(@SelectList, Len(@SelectList) -1) + ')'
    Set @SelectList = @SelectList 
    + ' Select * from #sometemp '
    exec @ret  =  sp_executesql  @selectlist
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-17
      相关资源
      最近更新 更多