Comment copier une table avec SELECT INTO mais en ignorant la propriété IDENTITY?

43

J'ai une table avec la colonne identité dire:

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

Il est bien connu que ce

select * into copy_from_with_id_1 from with_id;

aboutit également à copy_from_with_id_1 avec identity on id.

La question de débordement de pile suivante mentionne explicitement la liste de toutes les colonnes.

Essayons

select id, val into copy_from_with_id_2 from with_id;

Oups, même dans ce cas, l'id est une colonne d'identité.

Ce que je veux c'est une table comme

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

Réponses:

55

De la documentation en ligne

Le format de new_table est déterminé en évaluant les expressions de la liste de sélection. Les colonnes dans new_table sont créées dans l'ordre spécifié par la liste de sélection. Chaque colonne de new_table a le même nom, le type de données, la nullabilité et la même valeur que l'expression correspondante dans la liste de sélection. La propriété IDENTITY d'une colonne est transférée, sauf dans les conditions définies dans "Utilisation des colonnes d'identité" dans la section Remarques.

En bas de la page:

Lorsqu'une colonne d'identité existante est sélectionnée dans une nouvelle table, celle-ci hérite de la propriété IDENTITY, sauf si l'une des conditions suivantes est remplie:

  • L'instruction SELECT contient une jointure, une clause GROUP BY ou une fonction d'agrégat.
  • Plusieurs instructions SELECT sont jointes à l'aide de UNION.
  • La colonne d'identité est répertoriée plus d'une fois dans la liste de sélection.
  • La colonne identité fait partie d'une expression.
  • La colonne d'identité provient d'une source de données distante.

Si l'une de ces conditions est vraie, la colonne est créée NOT NULL au lieu d'hériter de la propriété IDENTITY. Si une colonne d'identité est requise dans la nouvelle table mais que cette colonne n'est pas disponible, ou si vous souhaitez une valeur de départ ou d'incrément différente de la colonne d'identité source, définissez la colonne dans la liste de sélection à l'aide de la fonction IDENTITY. Voir "Création d'une colonne d'identité à l'aide de la fonction IDENTITY" dans la section Exemples ci-dessous.

Donc ... vous pourriez théoriquement vous en tirer avec:

select id, val 
into copy_from_with_id_2 
from with_id

union all

select 0, 'test_row' 
where 1 = 0;

Il serait important de commenter ce code pour l'expliquer, de peur qu'il ne soit supprimé la prochaine fois que quelqu'un le consultera.

Eric Humphrey - Aide au frais
la source
30

Inspiré par la réponse Erics, j'ai trouvé la solution suivante qui ne dépend que des noms de table et n'utilise pas de nom de colonne spécifique:

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;

modifier

Il est même possible d'améliorer cela pour

select * into without_id from with_id
union all
select * from with_id where 1 = 0
;
Bernd_k
la source
13

Vous pouvez utiliser une jointure pour créer et remplir la nouvelle table en une fois:

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

En raison de cette 1 = 0condition, le côté droit n'aura pas de correspondance et empêchera ainsi la duplication des lignes du côté gauche. Comme il s'agit d'une jointure externe, les lignes du côté gauche ne seront pas non plus éliminées. Enfin, puisqu'il s'agit d'une jointure, la propriété IDENTITY est éliminée.

Par conséquent, sélectionner uniquement les colonnes de gauche produira une copie exacte de dbo.TableWithIdentity uniquement en termes de données, c'est-à-dire avec la propriété IDENTITY supprimée.

Cela étant dit, Max Vernon a soulevé un point valable dans un commentaire qu'il convient de garder à l'esprit. Si vous regardez le plan d'exécution de la requête ci-dessus:

Plan d'exécution

vous remarquerez que la table source est mentionnée dans le plan d'exécution une seule fois. L'autre instance a été éliminée par l'optimiseur.

Par conséquent, si l'optimiseur peut établir correctement que le plan ne requiert pas le côté droit de la jointure, il est raisonnable de penser que, dans une future version de SQL Server, il sera peut-être en mesure de déterminer que la propriété IDENTITY n'est pas nécessairement utilisée. supprimé non plus, car il n'y a plus d'autre colonne IDENTITY dans le jeu de lignes source conformément au plan de requête. Cela signifie que la requête ci-dessus risque de ne plus fonctionner comme prévu à un moment donné.

Mais, comme l'a noté correctement ypercubeᵀᴹ , jusqu'à présent, le manuel indique explicitement que s'il existe une jointure, la propriété IDENTITY n'est pas conservée:

Lorsqu'une colonne d'identité existante est sélectionnée dans une nouvelle table, cette nouvelle colonne hérite de la propriété IDENTITY, sauf si [...] [t] e instruction SELECT contient une jointure.

Donc, tant que le manuel ne cesse de le mentionner, nous pouvons probablement être assurés que le comportement restera le même.

Félicitations à Shaneis et à ypercubeᵀᴹ pour avoir abordé un sujet connexe dans le chat.

Andriy M
la source
Serait JOIN (SELECT 1) AS dummy ON 1 = 1travailler, aussi?
Ypercubeᵀᴹ
6

Essayez ce code ..

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

L' ISNULLappel garantit que la nouvelle colonne est créée avec une valeur NOT NULLNULL.

Saurav Ghosh
la source
1
Est-ce le ISNULL()ou le +0qui le fait? Ou les deux sont nécessaires?
Ypercubeᵀᴹ
Juste en ajoutant 0 fonctionne. C'est la solution la plus simple, à condition de lister explicitement les colonnes et de ne pas utiliser select *.
Ian Horwill
3

Juste pour montrer une manière différente:

Vous pouvez utiliser un serveur lié .

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

Vous pouvez créer temporairement un serveur lié au serveur local en utilisant ceci:

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 ce stade, vous exécutez le select * intocode en faisant référence au nom localserveren quatre parties du serveur lié:

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

Une fois cette opération terminée, nettoyez le localserverserveur lié avec ceci:

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

Ou, vous pouvez utiliser la OPENQUERYsyntaxe

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

La propriété d'identité n'est pas transférée si l'instruction select contient une jointure, et ainsi de suite.

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

donnera également le comportement souhaité (de la idcolonne copiée pour ne pas conserver la IDENTITYpropriété. Cependant, cela aura l’effet secondaire de ne pas copier de ligne du tout!

insert into without_id select * from with_id;

(merci AakashM!)

anon-99
la source
1

Le moyen le plus simple consiste à intégrer la colonne à une expression.

Exemple:
si la table dbo.Employee a une colonne identité sur ID, dans l'exemple ci-dessous, table #t aura également une colonne IDENTITY sur ID.

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

Changez-le pour appliquer une expression à ID et vous n'aurez plus de colonne IDENTITY sur ID. Dans ce cas, nous appliquons un simple ajout à la colonne ID.

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

D'autres exemples d'expressions pour d'autres types de données pourraient inclure: convert (), concaténation de chaînes ou Isnull ()

FistOfFury
la source
1
À partir de docs.microsoft.com/en-us/sql/t-sql/queries/… : «Lorsqu'une colonne d'identité existante est sélectionnée dans une nouvelle table, cette nouvelle colonne hérite de la propriété IDENTITY, sauf si l'une des conditions suivantes est remplie. … La colonne d'identité fait partie d'une expression… la colonne est créée NOT NULL au lieu d'hériter de la propriété IDENTITY. ”
Manngo
1

Parfois, vous souhaitez insérer à partir d'une table où vous ne savez pas (ou ne vous souciez pas) si la colonne a été créée avec IDENTITY ou non. Il se peut même que vous ne travailliez pas avec une colonne entière. Dans ce cas, ce qui suit fonctionnera:

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

ISNULL supprimera l'attribut IDENTITY de la colonne mais l'insérera avec le même nom et le même type que la colonne d'origine et le rendra également non nullable. TOP (0) créera une table vide dans laquelle vous pourrez ensuite insérer des lignes sélectionnées. Vous pouvez également compresser la table avant d'insérer des données, si nécessaire.

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

va supprimer l'identité.

L'inconvénient est que cela iddevient nul, mais vous pouvez ajouter cette contrainte.

john hunter
la source
1
Vous pouvez utiliser ISNULL pour contourner cela.
Erik Darling
-2

Vous ne. select * intopréserve l'identité.

Mladen Prajdic
la source
2
Il n'y avait aucune exigence dans la question à utiliser *.
Martin Smith
2
Et la identitypropriété n'est pas toujours préservée, comme le soulignaient d'autres réponses.
Ypercubeᵀᴹ