Come copio una tabella con SELECT INTO ma ignoro la proprietà IDENTITY?


43

Ho una tabella con colonna identità dire:

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

È noto, questo

select * into copy_from_with_id_1 from with_id;

risulta in copy_from_with_id_1 anche con identità su ID.

La seguente domanda di overflow dello stack menziona esplicitamente tutte le colonne.

Proviamo

select id, val into copy_from_with_id_2 from with_id;

Oops, anche in questo caso id è una colonna di identità.

Quello che voglio è un tavolo simile

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

Risposte:


55

Da libri online

Il formato di new_table viene determinato valutando le espressioni nell'elenco di selezione. Le colonne in new_table vengono create nell'ordine specificato dall'elenco di selezione. Ogni colonna in new_table ha lo stesso nome, tipo di dati, nullability e valore dell'espressione corrispondente nell'elenco di selezione. La proprietà IDENTITY di una colonna viene trasferita ad eccezione delle condizioni definite in "Utilizzo delle colonne di identità" nella sezione Note.

In fondo alla pagina:

Quando una colonna di identità esistente viene selezionata in una nuova tabella, la nuova colonna eredita la proprietà IDENTITY, a meno che una delle seguenti condizioni sia vera:

  • L'istruzione SELECT contiene un join, una clausola GROUP BY o una funzione aggregata.
  • Più istruzioni SELECT vengono unite utilizzando UNION.
  • La colonna identità è elencata più di una volta nell'elenco di selezione.
  • La colonna identità fa parte di un'espressione.
  • La colonna identità proviene da un'origine dati remota.

Se una di queste condizioni è vera, la colonna viene creata NOT NULL invece di ereditare la proprietà IDENTITY. Se una nuova colonna di identità è richiesta nella nuova tabella ma tale colonna non è disponibile o si desidera un seme o un valore di incremento diverso dalla colonna di identità di origine, definire la colonna nell'elenco di selezione utilizzando la funzione IDENTITÀ. Vedere "Creazione di una colonna identità mediante la funzione IDENTITÀ" nella sezione Esempi di seguito.

Quindi ... potresti teoricamente cavartela con:

select id, val 
into copy_from_with_id_2 
from with_id

union all

select 0, 'test_row' 
where 1 = 0;

Sarebbe importante commentare questo codice per spiegarlo, per evitare che venga rimosso la prossima volta che qualcuno lo guarda.


30

Ispirato dalla risposta di Erics, ho trovato la seguente soluzione che dipende solo dai nomi delle tabelle e non utilizza alcun nome di colonna specifico:

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;

modificare

È anche possibile migliorare questo

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

13

È possibile utilizzare un join per creare e popolare la nuova tabella in una sola volta:

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

A causa della 1 = 0condizione, il lato destro non avrà corrispondenze e quindi impedirà la duplicazione delle file del lato sinistro, e poiché si tratta di un join esterno, le file del lato sinistro non verranno eliminate. Infine, poiché si tratta di un join, la proprietà IDENTITY viene eliminata.

Selezionando solo le colonne sul lato sinistro, quindi, verrà prodotta una copia esatta di dbo.TableWithIdentity solo dal punto di vista dei dati, ovvero con la proprietà IDENTITY rimossa.

Detto questo, Max Vernon ha sollevato un punto valido in un commento che vale la pena ricordare. Se guardi il piano di esecuzione della query sopra:

Progetto esecutivo

noterai che la tabella di origine è menzionata nel piano di esecuzione solo una volta. L'altra istanza è stata eliminata dall'ottimizzatore.

Quindi, se l'ottimizzatore può stabilire correttamente che il lato destro del join non è necessario nel piano, dovrebbe essere ragionevole aspettarsi che in una versione futura di SQL Server potrebbe essere in grado di capire che la proprietà IDENTITY non deve essere rimosso, poiché non esiste più un'altra colonna IDENTITY nella riga di origine impostata in base al piano di query. Ciò significa che la query sopra potrebbe smettere di funzionare come previsto ad un certo punto.

Ma, come correttamente sottolineato da ypercubeᵀᴹ , finora il manuale ha esplicitamente dichiarato che se esiste un join, la proprietà IDENTITY non viene preservata:

Quando una colonna di identità esistente viene selezionata in una nuova tabella, la nuova colonna eredita la proprietà IDENTITY, a meno che [...] [t] l'istruzione SELECT contenga un join.

Quindi, fintanto che il manuale continuerà a menzionarlo, possiamo essere certi che il comportamento rimarrà lo stesso.

Complimenti a Shaneis e ypercubeᵀᴹ per aver sollevato un argomento correlato nella chat.


Funzionerebbe JOIN (SELECT 1) AS dummy ON 1 = 1anche tu?
ypercubeᵀᴹ


6

Prova questo codice ..

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

La ISNULLchiamata assicura che la nuova colonna venga creata con NOT NULLnullità.


1
È il ISNULL()o quello +0che lo fa? O entrambi sono necessari?
ypercubeᵀᴹ

Aggiungendo solo 0 opere. Questa è la soluzione più semplice se si elencano le colonne in modo esplicito e non si utilizza select *.
Ian Horwill,

3

Solo per mostrare un modo diverso:

È possibile utilizzare un server collegato .

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

È possibile creare temporaneamente un server collegato al server locale usando questo:

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;

A quel punto, avresti eseguito il select * intocodice, facendo riferimento al nome del localserverserver in quattro parti:

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

Al termine, ripulisci il localserverserver collegato con questo:

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

In alternativa, è possibile utilizzare la OPENQUERYsintassi

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

1

La proprietà identity non viene trasferita se l'istruzione select contiene un join e così

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

fornirà anche il comportamento desiderato (della idcolonna copiata per non mantenere la IDENTITYproprietà. Tuttavia, avrà l'effetto collaterale di non copiare alcuna riga! (come con altri metodi) quindi dovrai quindi fare:

insert into without_id select * from with_id;

(grazie AakashM!)


1

Il modo più semplice è rendere la colonna parte di un'espressione.

Esempio:
se la tabella dbo.Employee ha un'identità nella colonna ID, nell'esempio seguente la tabella temporanea #t avrà anche un'IDENTITÀ sulla colonna ID.

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

Modificalo per applicare un'espressione all'ID e #t non avrà più un IDENTITÀ nella colonna ID. In questo caso applichiamo una semplice aggiunta alla colonna ID.

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

Altri esempi di espressioni per altri tipi di dati potrebbero includere: convert (), concatenazione di stringhe o Isnull ()


1
Da docs.microsoft.com/en-us/sql/t-sql/queries/… : “Quando una colonna di identità esistente viene selezionata in una nuova tabella, la nuova colonna eredita la proprietà IDENTITY, a meno che una delle seguenti condizioni sia vera ... La colonna Identity fa parte di un'espressione ... la colonna viene creata NON NULL invece di ereditare la proprietà IDENTITY. "
Manngo

1

A volte, vuoi inserire da una tabella in cui non sai (o ti interessa) se la colonna è stata creata usando IDENTITY o meno. Potrebbe non essere nemmeno una colonna intera con cui stai lavorando. In questo caso, funzionerà quanto segue:

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

ISNULL eliminerà l'attributo IDENTITY dalla colonna ma lo inserirà con lo stesso nome e tipo della colonna originale e lo renderà non annullabile. TOP (0) creerà una tabella vuota che sarà quindi possibile utilizzare per inserire le righe selezionate. È inoltre possibile comprimere la tabella prima di inserire i dati, se necessario.


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

rimuoverà l'identità.

Il rovescio della medaglia è che iddiventa nullable ma è possibile aggiungere quel vincolo.


1
Puoi usare ISNULL per aggirare il problema .
Erik Darling,

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.