如何使用SELECT INTO复制表但忽略IDENTITY属性?


41

我有一个带有标识列的表说:

create table with_id (
 id int identity(1,1),
 val varchar(30)
);

众所周知,

select * into copy_from_with_id_1 from with_id;

也会导致copy_from_with_id_1的身份也为id。

以下堆栈溢出问题提到显式列出所有列。

我们试试吧

select id, val into copy_from_with_id_2 from with_id;

糟糕,即使在这种情况下,id也是一个标识列。

我想要的是一张桌子

create table without_id (
 id int,
 val varchar(30)
);

Answers:


52

在线书籍

通过评估选择列表中的表达式来确定new_table的格式。new_table中的列是按选择列表指定的顺序创建的。new_table中的每一列与选择列表中的相应表达式具有相同的名称,数据类型,可空性和值。除非在“备注”部分的“使用身份列”中定义的条件下,否则将转移列的IDENTITY属性

在页面下方:

当将现有的标识列选择到新表中时,除非满足以下条件之一,否则新列将继承IDENTITY属性:

  • SELECT语句包含一个join,GROUP BY子句或聚合函数。
  • 使用UNION可以连接多个SELECT语句。
  • 在选择列表中,身份列被列出了不止一次。
  • 标识列是表达式的一部分。
  • 标识列来自远程数据源。

如果这些条件中的任何一个为true,则将创建列NOT NULL而不是继承IDENTITY属性。如果新表中需要标识列,但该列不可用,或者您想要的种子或增量值与源标识列不同,请使用IDENTITY函数在选择列表中定义该列。请参阅下面的示例部分中的“使用IDENTITY函数创建身份列”。

所以...从理论上讲,您可以摆脱:

select id, val 
into copy_from_with_id_2 
from with_id

union all

select 0, 'test_row' 
where 1 = 0;

注释该代码以对其进行解释很重要,以免下次有人查看时将其删除。


29

受Erics答案的启发,我发现以下解决方案仅取决于表名,而不使用任何特定的列名:

select * into without_id from with_id where 1 = 0
union all
select * from with_id where 1 = 0
;
insert into without_id select * from with_id;

编辑

甚至有可能将其改进为

select * into without_id from with_id
union all
select * from with_id where 1 = 0
;

13

您可以使用联接一次创建并填充新表:

SELECT
  t.*
INTO
  dbo.NewTable
FROM
  dbo.TableWithIdentity AS t
  LEFT JOIN dbo.TableWithIdentity ON 1 = 0
;

由于这种1 = 0情况,右侧将没有匹配项,因此可以防止左侧行的重复,并且由于这是一个外部联接,因此左侧行也不会被消除。最后,因为这是一个联接,所以消除了IDENTITY属性。

因此,仅选择左侧的列将仅在数据方面生成dbo.TableWithIdentity的精确副本,即,去除IDENTITY属性。

话虽如此,马克斯·弗农Max Vernon)在评论中提出了一个正确的观点,值得牢记。如果您查看上述查询的执行计划:

执行计划

您会注意到在执行计划中仅提及源表一次。优化器已消除了另一个实例。

因此,如果优化器可以正确地确定计划中不需要连接的右侧,则可以合理地预期在SQL Server的未来版本中,它可能能够确定IDENTITY属性不需要删除了两者之一,因为根据查询计划,源行集中不再有另一个IDENTITY列。这意味着以上查询可能会在某些时候按预期停止工作。

但是,正如ypercubeᵀᴹ正确指出的那样,到目前为止,该手册已明确指出如果存在联接,则不会保留IDENTITY属性:

当将现有的标识列选择到新表中时,新列将继承IDENTITY属性,除非SELECT语句包含联接。

因此,只要手册不断提及它,我们就可以放心,行为将保持不变。

荣誉对Shaneisypercubeᵀᴹ为造就了相关主题的聊天。


请问JOIN (SELECT 1) AS dummy ON 1 = 1可以吗?
ypercubeᵀᴹ


5

试试这个代码。

SELECT isnull(Tablename_old.IDENTITYCOL + 0, -1) AS 'New Identity Column'
INTO   dbo.TableName_new
FROM   dbo.TableName_old 

ISNULL调用可确保创建的新列具有可NOT NULL空性。


1
是它ISNULL()还是+0那个?还是两者都需要?
ypercubeᵀᴹ

3

只是为了显示一种不同的方式:

您可以使用链接服务器

SELECT * 
INTO without_id 
FROM [linked_server].[source_db].dbo.[with_id];

您可以使用以下方法临时创建到本地服务器的链接服务器:

DECLARE @LocalServer SYSNAME 
SET @LocalServer = @@SERVERNAME;
EXEC master.dbo.sp_addlinkedserver @server = N'localserver'
    , @srvproduct = ''
    , @provider = 'SQLNCLI'
    , @datasrc = @LocalServer;
EXEC master.dbo.sp_addlinkedsrvlogin @rmtsrvname=N'localserver'
    , @useself = N'True'
    , @locallogin = NULL
    , @rmtuser = NULL
    , @rmtpassword = NULL;

此时,您将运行select * into代码,引用localserver链接服务器的四部分名称:

SELECT * 
INTO without_id 
FROM [localserver].[source_db].dbo.[with_id];

完成之后,使用以下命令清理localserver链接的服务器:

EXEC sp_dropserver @server = 'localserver'
    , @droplogins = 'droplogins';

或者,您可以使用OPENQUERY语法

SELECT * 
INTO without_id 
FROM OPENQUERY([linked_server], 'SELECT * FROM [source_db].dbo.[with_id]');

1

如果select语句包含联接,则不会传输identity属性,因此

select a.* into without_id from with_id a inner join with_id b on 1 = 0;

也会给出(复制的id列不保留该IDENTITY属性的)期望的行为。但是,这样做的副作用是根本不复制任何行!(与其他方法一样),因此您需要执行以下操作:

insert into without_id select * from with_id;

(感谢AakashM!)


1

最简单的方法是使列成为表达式的一部分。

示例:
如果表dbo.Employee在ID列上具有标识,则在下面的示例中,临时表#t在ID列上也将具有IDENTITY。

--temp table has IDENTITY
select ID, Name 
into #t
from dbo.Employee

更改此项以将表达式应用于ID,您#t的ID列将不再具有IDENTITY。在这种情况下,我们对ID列进行简单的添加。

--no IDENTITY
select ID = ID + 0, Name 
into #t
from dbo.Employee

其他数据类型的表达式的其他示例可能包括:convert(),字符串串联或Isnull()


1
docs.microsoft.com/zh-cn/sql/t-sql/queries/…:“将现有标识列选择到新表中时,除非满足以下条件之一,否则新列将继承IDENTITY属性。 …Identity列是表达式的一部分…将该列创建为NOT NULL而不是继承IDENTITY属性。”
Manngo,

1

有时,您想从不知道(或不在乎)该表是否使用IDENTITY创建的表中插入。它甚至可能不是您正在使用的整数列。在这种情况下,将执行以下操作:

SELECT TOP(0) ISNULL([col],NULL) AS [col], ... INTO [table2] FROM [table1]
ALTER TABLE [table2] REBUILD WITH (DATA_COMPRESSION=page)
INSERT INTO [table2] ...

ISNULL将从列中删除IDENTITY属性,但以与原始列相同的名称和类型插入该属性,并使该属性不可为空。TOP(0)将创建一个空表,然后您可以使用该表将选定的行插入其中。如果需要,还可以在插入数据之前压缩表。


0
select convert(int, id) as id, val 
into copy_from_with_id_without_id 
from with_id;

将删除身份。

缺点是可以id变为空,但是您可以添加该约束。


1
您可以使用ISNULL来解决此问题。
埃里克·达林

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.