在 SQL Server 中,只有在使用列列表并且启用了 IDENTITY_INSERT 时,才能为表中的标识列指定显式值。

262

我正在尝试执行这个查询

INSERT INTO dbo.tbl_A_archive
  SELECT *
  FROM SERVER0031.DB.dbo.tbl_A

但即使运行后

set identity_insert dbo.tbl_A_archive on

我收到了这个错误信息:

只有在使用列列表且 IDENTITY_INSERT 为 ON 时,才可以为表 'dbo.tbl_A_archive' 中的标识列指定显式值。

tbl_A 是一个非常大的表,它有很多行和列。 我不想手动输入所有列。 有什么方法可以让这个工作?


我已经设置了一个链接服务器,顺便说一下! - jhowe
4
我也遇到了这个问题,只不过在我改变表结构之前,“insert into X select * from Y”没有问题。 - FistOfFury
18个回答

382
SET IDENTITY_INSERT tableA ON

你需要为你的INSERT语句创建一个列列表:

INSERT Into tableA ([id], [c2], [c3], [c4], [c5] ) 
SELECT [id], [c2], [c3], [c4], [c5] FROM tableB

不要像 "INSERT Into tableA SELECT ........" 那样。

SET IDENTITY_INSERT tableA OFF

19
只需要在查询的SELECT子句中明确列名。你可以在INSERT子句中保留星号。 - Entree
10
除非目标表具有标识列,而源表没有标识列。 - Entree
1
这个答案比Heinzi的更清晰,因为它提到了SET IDENTITY_INSERT。 - Marty
4
+1 能够完美运作!然而,“您不能在查询的INSERT子句中保留星号” 在这里行不通。 - Shai Alon
1
当选择子句中的列与目标表不匹配时,我遇到了这个错误。确保匹配后,插入操作成功执行。 - Jose
哦,谢谢。答案是需要明确列出列名!!!我没有意识到SELECT *不起作用。你应该把它放在粗体中:-D - ripvlan

185

总结

在 SQL Server 中,除非使用列列表,否则无法在标识列中插入显式值。因此,您有以下几个选项:

  1. 创建一个列列表(手动创建或使用工具,见下文)

或者

  1. tbl_A_archive 中的标识列更改为常规的、非标识列:如果您的表是一个归档表,并且您总是为标识列指定显式值,那么您甚至不需要标识列,只需使用普通的 int 列即可。

解决方案 1 的详细信息

不要使用

SET IDENTITY_INSERT archive_table ON;

INSERT INTO archive_table
  SELECT *
  FROM source_table;

SET IDENTITY_INSERT archive_table OFF;

你需要写代码

SET IDENTITY_INSERT archive_table ON;

INSERT INTO archive_table (field1, field2, ...)
  SELECT field1, field2, ...
  FROM source_table;

SET IDENTITY_INSERT archive_table OFF;

使用field1、field2、...包含您表中所有列的名称。如果想自动生成该列列表,请查看Dave's answerAndomar's answer


关于解决方案2的详细信息

不幸的是,将标识int列的类型更改为非标识int列是不可能的。基本上,您有以下几个选项:

  • 如果存档表尚未包含数据,请删除该列并添加一个没有标识的新列。

或者

  • 使用SQL Server Management Studio将存档表中的标识列的Identity Specification/(Is Identity)属性设置为No。在幕后,这将创建一个脚本来重新创建表格和复制现有数据,因此,为了实现这一点,您还需要取消设置 Tools/Options/Designers/Table and Database Designers/Prevent saving changes that require table re-creation

或者


1
对于您的底层解决方案: 如果该列具有“IDENTITY(1,1)”或类似内容,则需要暂时将其删除。 - Alexander Khomenko
1
@AleksandrKhomenko:是的,我所说的“将其变成一个常规(非标识)int列”就是这个意思。 - Heinzi
1
抱歉造成困惑。我的意思是他需要删除自动递增属性。在SQL-Server的情况下,它称之为“IDENTITY(1,1)”-你的答案是完全正确的。但是MySQL和Oracle有另外的命令来实现它(并且这变得不太明显,请参考http://www.w3schools.com/sql/sql_autoincrement.asp)。 - Alexander Khomenko
2
这个缺乏细节。我知道对一些人来说可能是机械式的,但对其他人来说却是无意义的。 - DJ.
1
@DJ:我已经添加了一些细节。 - Heinzi
显示剩余3条评论

47

如果你正在使用SQL Server Management Studio,你不必自己输入列列表 - 只需右键单击对象资源管理器中的表格,并选择生成脚本 -> 选择 -> 新建查询编辑器窗口

如果没有使用SQL Server Management Studio,则类似于此查询的查询可作为起点:

SELECT SUBSTRING(
    (SELECT ', ' + QUOTENAME(COLUMN_NAME)
        FROM INFORMATION_SCHEMA.COLUMNS
        WHERE TABLE_NAME = 'tbl_A'
        ORDER BY ORDINAL_POSITION
        FOR XML path('')),
    3,
    200000);

2
这可以被选入一个变量并在动态 SQL 查询中使用。你救了我的一天,非常感谢! - Pawel Cioch
1
谢谢!我用它创建了这个 https://gist.github.com/timabell/0ddd6a69565593f907c7 - Tim Abell
哇,我一直在拼命寻找这个来动态构建我的查询。惊讶地看到它在这里。谢谢伙计。 - Yasin Bilir

34

同意Heinzi的答案。对于第一和第二个选项,这里有一个查询语句可以生成表中逗号分隔的列列表:

select name + ', ' as [text()] 
from sys.columns 
where object_id = object_id('YourTable') 
for xml path('')

对于大型表格,这可以节省很多打字工作 :)


谢谢,非常有帮助。 :) - Chiragkumar Thakar
有时候你只需要一个实用的解决方案。谢谢! - Tobias Feil

17
如果“archive”表意味着是主表的完整副本,那么建议删除id是自增列的属性。这样就可以插入它们了。 或者,您可以使用以下语句允许并禁止向表中插入标识列:
SET IDENTITY_INSERT tbl_A_archive ON
--Your inserts here
SET IDENTITY_INSERT tbl_A_archive OFF

最后,如果您需要身份列按原样工作,则始终可以运行存储过程。

sp_columns tbl_A_archive 

这将返回表中的所有列,然后您可以将它们剪切并粘贴到您的查询中。(这几乎总是比使用 * 更好)


13

对于 SQL 语句,您还必须指定列列表。例如:

INSERT INTO tbl (idcol1,col2) VALUES ( value1,value2)

取代

INSERT INTO tbl VALUES ( value1,value2)

3
这并不适用于OP的陈述。如果INSERT INTO后面跟着SELECT语句,则不需要VALUES。请修改或删除您的答案。 - Gary

8
两种方法都可以,但如果使用#1仍然出错,则选择#2。
SET IDENTITY_INSERT customers ON
GO
insert into dbo.tbl_A_archive(id, ...)
SELECT Id, ...
FROM SERVER0031.DB.dbo.tbl_A

2)

SET IDENTITY_INSERT customers ON
GO
insert into dbo.tbl_A_archive(id, ...)
VALUES(@Id,....)

4
如果OP正在插入多条记录,这并不是正确的做法。你忽略了"tbl_A是一个非常大的表,有很多列。我不想手动输入所有的列名。" - Gary

4
如果存在标识列,你必须要指定想要插入的列名。因此,命令应该像下面这样:
SET IDENTITY_INSERT DuplicateTable ON

INSERT Into DuplicateTable ([IdentityColumn], [Column2], [Column3], [Column4] ) 
SELECT [IdentityColumn], [Column2], [Column3], [Column4] FROM MainTable

SET IDENTITY_INSERT DuplicateTable OFF

如果你的表有很多列,可以使用以下命令获取这些列的名称。
SELECT column_name + ','
FROM   information_schema.columns 
WHERE  table_name = 'TableName'
for xml path('')

(去掉最后一个逗号','后)直接复制粘贴列名。

4
为了将所有列名填充到逗号分隔的列表中,以便为此问题提到的解决方案生成Select语句,我使用以下选项,因为它们比大多数答案更简洁。尽管这里的大多数答案仍然是完全可接受的。
SELECT column_name + ',' 
FROM   information_schema.columns 
WHERE  table_name = 'YourTable'

2) 如果您使用SQL Server SSMS,这可能是创建列最简单的方法。

1) 在对象资源管理器中找到表格,并单击表格名称左侧的“+”或双击表格名称以打开子列表。

2) 将列子文件夹拖到主查询区域,它将自动将整个列列表粘贴到您的代码中。


4
SET IDENTITY_INSERT tableA ON

INSERT Into tableA ([id], [c2], [c3], [c4], [c5] ) 
SELECT [id], [c2], [c3], [c4], [c5] FROM tableB

不是这样的

INSERT INTO tableA
SELECT * FROM tableB

SET IDENTITY_INSERT tableA OFF

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接