SELECT INTO行为和IDENTITY属性

时间:2011-07-06 19:55:57

标签: sql tsql sql-server-2008

我一直在研究一个项目,并在使用SELECT INTO时遇到了一些有趣的行为。如果我有一个列定义为int identity(1,1) not null的表并使用SELECT INTO复制它,则新表将保留IDENTITY属性,除非涉及到连接。如果存在连接,则新表上的相同列仅定义为int not null

这是一个可以运行以重现行为的脚本:

CREATE TABLE People (Id INT IDENTITY(1,1) not null, Name VARCHAR(10))
CREATE TABLE ReverseNames (Name varchar(10), ReverseName varchar(10))

INSERT INTO People (Name)
VALUES ('John'), ('Jamie'), ('Joe'), ('Jenna')

INSERT INTO ReverseNames (Name, ReverseName)
VALUES ('John','nhoJ'), ('Jamie','eimaJ'), ('Joe','eoJ'), ('Jenna','anneJ')

--------

SELECT Id, Name
INTO People_ExactCopy
FROM People

SELECT Id, ReverseName as Name
INTO People_WithJoin
FROM People
    JOIN ReverseNames
        ON People.Name = ReverseNames.Name

SELECT Id, (SELECT ReverseName FROM ReverseNames WHERE Name = People.Name) as Name
INTO People_WithSubSelect
FROM People

--------

SELECT OBJECT_NAME(c.object_id) as [Table],
    c.is_identity as [Id Column Retained Identity]
FROM sys.columns c
where 
OBJECT_NAME(c.object_id) IN ('People_ExactCopy','People_WithJoin','People_WithSubSelect')
    AND c.name = 'Id'

--------

DROP TABLE People
DROP TABLE People_ExactCopy
DROP TABLE People_WithJoin
DROP TABLE People_WithSubSelect
DROP TABLE ReverseNames

我注意到WithJoin和WithSubSelect查询的执行计划都包含一个连接运算符。如果我们处理更多的行,我不确定是否会在性能上有明显改善。

任何人都可以对此有所了解并告诉我是否有办法利用SELECT INTO加入并仍保留IDENTITY属性?

3 个答案:

答案 0 :(得分:8)

来自Microsoft

  

现有标识列时   选入新表,新   列继承IDENTITY属性,   除非出现下列情况之一   是的:

The SELECT statement contains a join, GROUP BY clause, or aggregate function.

Multiple SELECT statements are joined by using UNION.

The identity column is listed more than one time in the select list.

The identity column is part of an expression.

The identity column is from a remote data source.
     

如果这些条件中的任何一个是   如果为true,则创建列为NOT NULL   而不是继承IDENTITY   属性。如果是标识列   在新表中需要但是这样   列不可用,或者您想要一个   种子或增量值   不同于源身份   列,定义中的列   使用IDENTITY选择列表   功能

您可以按照他们的建议使用IDENTITY功能并省略IDENTITY列,但之后您会丢失这些值,因为IDENTITY功能会生成新值而我不会我认为这些很容易确定,即使使用ORDER BY

答案 1 :(得分:2)

我不相信您可以做很多事情,除了手动构建CREATE TABLE语句,SET IDENTITY_INSERT ON,插入现有值,然后SET IDENTITY_INSERT OFF。是的,你失去了SELECT INTO的好处,但除非你的桌子很大并且你这么做了,[耸肩]。这当然不好玩,它不像SELECT INTO那样漂亮或简单,但你可以通过编程方式进行,假设有两个表,一个具有简单标识(1,1),以及简单 INNER JOIN:

    SET NOCOUNT ON;

DECLARE
    @NewTable SYSNAME = N'dbo.People_ExactCopy',
    @JoinCondition NVARCHAR(255) = N' ON p.Name = r.Name';

DECLARE
    @cols TABLE(t SYSNAME, c SYSNAME, p CHAR(1));

INSERT @cols SELECT N'dbo.People', N'Id', 'p'
    UNION ALL SELECT N'dbo.ReverseNames', N'Name', 'r';

DECLARE @sql NVARCHAR(MAX) = N'CREATE TABLE ' + @NewTable + '
(
';

SELECT @sql += c.name + ' ' + t.name 
    + CASE WHEN t.name LIKE '%char' THEN 
        '(' + CASE WHEN c.max_length = -1 
            THEN 'MAX' ELSE RTRIM(c.max_length/
            (CASE WHEN t.name LIKE 'n%' THEN 2 ELSE 1 END)) END 
        + ')' ELSE '' END
    + CASE c.is_identity
    WHEN 1 THEN ' IDENTITY(1,1)' 
    ELSE ' ' END + ',
    '
    FROM sys.columns AS c 
    INNER JOIN @cols AS cols
    ON c.object_id = OBJECT_ID(cols.t)
    INNER JOIN sys.types AS t
    ON c.system_type_id = t.system_type_id
    AND c.name = cols.c;

SET @sql = LEFT(@sql, LEN(@sql)-1) + '
);

SET IDENTITY_INSERT ' + @NewTable + ' ON;

INSERT ' + @NewTable + '(';

SELECT @sql += c + ',' FROM @cols;

SET @sql = LEFT(@sql, LEN(@sql)-1) + ')
    SELECT ';

SELECT @sql += p + '.' + c + ',' FROM @cols;

SET @sql = LEFT(@sql, LEN(@sql)-1) + '
    FROM ';

SELECT @sql += t + ' AS ' + p + ' 
    INNER JOIN ' FROM (SELECT DISTINCT
        t,p FROM @cols) AS x;

SET @sql = LEFT(@sql, LEN(@sql)-10) 
    + @JoinCondition + ';

SET IDENTITY_INSERT ' + @NewTable + ' OFF;';

PRINT @sql;

使用上面给出的表,这将生成以下内容,您可以将其传递给EXEC sp_executeSQL而不是PRINT:

CREATE TABLE dbo.People_ExactCopy
(
    Id int IDENTITY(1,1),
    Name varchar(10) 
);

SET IDENTITY_INSERT dbo.People_ExactCopy ON;

INSERT dbo.People_ExactCopy(Id,Name)
    SELECT p.Id,r.Name
    FROM dbo.People AS p 
    INNER JOIN dbo.ReverseNames AS r 
     ON p.Name = r.Name;

SET IDENTITY_INSERT dbo.People_ExactCopy OFF;

我没有处理其他复杂性,例如DECIMAL列或其他具有max_length参数的列,也没有处理可空性,但如果你需要更大的灵活性,这些事情并不难添加。

在下一版本的SQL Server(代号为“Denali”)中,您应该能够使用新的元数据发现功能更轻松地构建CREATE TABLE语句 - 这些功能在指定方面为您做了大量工作精度/比例/长度,处理MAX等。您仍然需要手动创建索引和约束;但是你也没有使用SELECT INTO。

我们真正需要的是DDL,它可以让你说出“创建一个IDENTICAL to b;”或者“基于b创建表;”......这里已被要求,但已被拒绝(这是关于将表复制到另一个模式,但相同的概念可以应用于同一模式中的新表,不同的表名)。 http://connect.microsoft.com/SQLServer/feedback/details/632689

答案 2 :(得分:0)

我意识到这是一个非常晚的回应,但无论是谁还在寻找这个解决方案,就像我一直在找到这个解决方案:

您无法使用JOIN运算符继承IDENTITY列属性。 你可以做的是使用这样的WHERE子句:

选择a。* INTO NewTable 从   MyTable a 哪里   EXISTS(从SecondTable中选择1 b WHERE b.ID = a.ID)

这很有效。