Como copio uma tabela com SELECT INTO, mas ignoro a propriedade IDENTITY?

41

Eu tenho uma tabela com coluna identidade dizer:

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

É sabido que este

select * into copy_from_with_id_1 from with_id;

resulta em copy_from_with_id_1 com identidade no ID também.

A seguinte pergunta de estouro de pilha menciona listar todas as colunas explicitamente.

Vamos tentar

select id, val into copy_from_with_id_2 from with_id;

Ops, mesmo neste caso, o id é uma coluna de identidade.

O que eu quero é uma mesa como

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

Respostas:

52

De Livros Online

O formato de new_table é determinado avaliando as expressões na lista de seleção. As colunas em new_table são criadas na ordem especificada pela lista de seleção. Cada coluna em new_table possui o mesmo nome, tipo de dados, capacidade de nulidade e valor que a expressão correspondente na lista de seleção. A propriedade IDENTITY de uma coluna é transferida, exceto nas condições definidas em "Trabalhando com colunas de identidade" na seção Comentários.

Abaixo da página:

Quando uma coluna de identidade existente é selecionada em uma nova tabela, a nova coluna herda a propriedade IDENTITY, a menos que uma das seguintes condições seja verdadeira:

  • A instrução SELECT contém uma junção, cláusula GROUP BY ou função agregada.
  • Várias instruções SELECT são unidas usando UNION.
  • A coluna de identidade é listada mais de uma vez na lista de seleção.
  • A coluna de identidade faz parte de uma expressão.
  • A coluna de identidade é de uma fonte de dados remota.

Se qualquer uma dessas condições for verdadeira, a coluna será criada NOT NULL em vez de herdar a propriedade IDENTITY. Se uma coluna de identidade for necessária na nova tabela, mas essa coluna não estiver disponível, ou você desejar um valor inicial ou de incremento diferente da coluna de identidade de origem, defina a coluna na lista de seleção usando a função IDENTITY. Consulte "Criando uma coluna de identidade usando a função IDENTITY" na seção Exemplos abaixo.

Então ... você poderia teoricamente se safar:

select id, val 
into copy_from_with_id_2 
from with_id

union all

select 0, 'test_row' 
where 1 = 0;

Seria importante comentar este código para explicá-lo, para que não seja removido na próxima vez que alguém o examinar.

Eric Humphrey - lotes de ajuda
fonte
29

Inspirado na resposta de Erics, encontrei a seguinte solução, que depende apenas dos nomes das tabelas e não usa nenhum nome específico de coluna:

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;

Editar

É até possível melhorar isso para

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

Você pode usar uma associação para criar e preencher a nova tabela de uma só vez:

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

Por causa da 1 = 0condição, o lado direito não terá correspondências e, assim, evitará a duplicação das linhas do lado esquerdo e, por ser uma junção externa, as linhas do lado esquerdo também não serão eliminadas. Por fim, como se trata de uma associação, a propriedade IDENTITY é eliminada.

Selecionar apenas as colunas do lado esquerdo, portanto, produzirá uma cópia exata do dbo.TableWithIdentity somente em dados, ou seja, com a propriedade IDENTITY removida.

Tudo isso dito, Max Vernon levantou um ponto válido em um comentário que vale a pena ter em mente. Se você observar o plano de execução da consulta acima:

Plano de execução

você notará que a tabela de origem é mencionada no plano de execução apenas uma vez. A outra instância foi eliminada pelo otimizador.

Portanto, se o otimizador puder estabelecer corretamente que o lado direito da junção não é necessário no plano, deve ser razoável esperar que em uma versão futura do SQL Server seja possível descobrir que a propriedade IDENTITY não precisa ser removido também, pois não há mais outra coluna IDENTITY na linha de origem definida de acordo com o plano de consulta. Isso significa que a consulta acima pode parar de funcionar conforme o esperado em algum momento.

Mas, como observado corretamente pelo ypercubeᵀᴹ , até agora o manual afirmou explicitamente que, se houver uma junção, a propriedade IDENTITY não será preservada:

Quando uma coluna de identidade existente é selecionada em uma nova tabela, a nova coluna herda a propriedade IDENTITY, a menos que [...] a instrução SELECT contenha uma junção.

Portanto, enquanto o manual continuar mencionando, provavelmente podemos ter certeza de que o comportamento permanecerá o mesmo.

Parabéns a Shaneis e ypercubeᵀᴹ por abrir um tópico relacionado no chat.

Andriy M
fonte
Funcionaria JOIN (SELECT 1) AS dummy ON 1 = 1também?
ypercubeᵀᴹ
5

Experimente este código ..

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

A ISNULLchamada garante que a nova coluna seja criada com capacidade de NOT NULLnulidade.

Saurav Ghosh
fonte
11
É o ISNULL()ou o +0que faz? Ou ambos são necessários?
ypercubeᵀᴹ
3

Apenas para mostrar uma maneira diferente:

Você pode usar um servidor vinculado .

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

Você pode criar temporariamente um servidor vinculado ao servidor local usando este:

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;

Nesse momento, você executaria o select * intocódigo, referenciando o localservernome de quatro partes do servidor vinculado:

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

Após a conclusão, limpe o localserverservidor vinculado com este:

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

Ou você pode usar OPENQUERYsintaxe

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

A propriedade identity não é transferida se a instrução select contiver uma associação e, portanto,

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

também fornecerá o comportamento desejado (da idcoluna copiada para não manter a IDENTITYpropriedade. No entanto, ele terá o efeito colateral de não copiar nenhuma linha! (como em alguns outros métodos), então você precisará fazer:

insert into without_id select * from with_id;

(obrigado AakashM!)

anon-99
fonte
1

A maneira mais fácil é tornar a coluna parte de uma expressão.

Exemplo:
se a tabela dbo.Employee tiver uma identidade na coluna ID, no exemplo abaixo da tabela temporária #t também terá uma IDENTITY na coluna ID.

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

Altere isso para aplicar uma expressão ao ID e você #t não terá mais uma coluna IDENTITY on ID. Nesse caso, aplicamos uma adição simples à coluna ID.

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

Outros exemplos de expressões para outros tipos de dados podem incluir: convert (), concatenação de cadeias ou Isnull ()

FistOfFury
fonte
11
Em docs.microsoft.com/en-us/sql/t-sql/queries/… : “Quando uma coluna de identidade existente é selecionada em uma nova tabela, a nova coluna herda a propriedade IDENTITY, a menos que uma das seguintes condições seja verdadeira … A coluna de identidade faz parte de uma expressão… a coluna é criada NOT NULL em vez de herdar a propriedade IDENTITY. ”
Manngo
1

Às vezes, você deseja inserir a partir de uma tabela em que não sabe (ou não se importa) se a coluna foi criada usando IDENTITY ou não. Pode até não ser uma coluna inteira com a qual você está trabalhando. Nesse caso, o seguinte funcionará:

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

ISNULL eliminará o atributo IDENTITY da coluna, mas o inserirá com o mesmo nome e tipo da coluna original e também o tornará nulo. TOP (0) criará uma tabela vazia que você poderá usar para inserir as linhas selecionadas. Você também pode compactar a tabela antes de inserir dados, se necessário.

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

irá remover a identidade.

A desvantagem é que idse torna anulável, mas você pode adicionar essa restrição.

caçador de john
fonte
11
Você pode usar ISNULL para contornar isso.
Erik Darling
-2

Você não select * intopreserva a identidade.

Mladen Prajdic
fonte
2
Não havia nenhum requisito na pergunta para usar *.
Martin Smith
2
E a identitypropriedade nem sempre é preservada, como outras respostas apontaram.
ypercubeᵀᴹ