web-dev-qa-db-fra.com

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

J'ai une table avec une colonne d'identité qui dit:

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;

se traduit par copy_from_with_id_1 avec identité sur id aussi.

Ce qui suit question de débordement de pile mentionne la liste explicite de toutes les colonnes.

Essayons

select id, val into copy_from_with_id_2 from with_id;

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

Ce que je veux c'est une table comme

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

De Livres en ligne

Le format de new_table est déterminé en évaluant les expressions dans la liste de sélection. Les colonnes de 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, type de données, nullité et 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, la nouvelle colonne 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égation.
  • Plusieurs instructions SELECT sont jointes à l'aide de UNION.
  • La colonne d'identité est répertoriée plusieurs fois dans la liste de sélection.
  • La colonne d'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 remplie, 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 qu'une telle 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 sortir 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 regarde.

56

Inspiré par la réponse d'Erics, j'ai trouvé la solution suivante qui ne dépend que des noms de table et n'utilise aucun 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

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

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

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

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

Par conséquent, la sélection des seules colonnes de gauche produira une copie exacte de dbo.TableWithIdentity uniquement sur les 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 qui mérite d'être gardé à l'esprit. Si vous regardez le plan d'exécution de la requête ci-dessus:

Execution plan

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

Ainsi, si l'optimiseur peut établir correctement que le côté droit de la jointure n'est pas nécessaire dans le plan, il devrait être raisonnable de s'attendre à ce que dans une future version de SQL Server, il puisse être en mesure de comprendre que la propriété IDENTITY n'a pas besoin d'être supprimé non plus, car il n'y a plus d'autre colonne IDENTITY dans l'ensemble de lignes source selon le plan de requête. Cela signifie que la requête ci-dessus peut cesser de fonctionner comme prévu à un moment donné.

Mais, comme correctement noté par ypercubeᵀᴹ , jusqu'ici le manuel a explicitement déclaré que s'il y a 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, la nouvelle colonne hérite de la propriété IDENTITY, sauf si [...] [t] l'instruction SELECT contient une jointure.

Donc, tant que le manuel le mentionne, nous pouvons probablement être assurés que le comportement restera le même.

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

13
Andriy M

Essayez ce code ..

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

L'appel ISNULL garantit que la nouvelle colonne est créée avec NOT NULL nullabilité.

6
Saurav Ghosh

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;

À ce stade, vous exécutez le select * into code, référençant le localserver nom du serveur lié en quatre parties:

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

Après cela, nettoyez le serveur lié localserver avec ceci:

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

Ou, vous pouvez utiliser la syntaxe OPENQUERY

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

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

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

donnera également le comportement souhaité (de la colonne id copiée pour ne pas conserver la propriété IDENTITY. Cependant, cela aura pour effet secondaire de ne copier aucune ligne du tout! (comme avec d'autres vous devrez alors faire:

insert into without_id select * from with_id;

(merci AakashM!)

1
anon-99

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

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

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

Modifiez ceci pour appliquer une expression à ID et vous n'aurez plus de colonne IDENTITY on 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 peuvent inclure: convert (), la concaténation de chaînes ou Isnull ()

1
FistOfFury

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 à l'aide d'IDENTITY ou non. Ce n'est peut-être même pas une colonne entière avec laquelle vous travaillez. Dans ce cas, les éléments suivants fonctionneront:

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 non annulable. TOP (0) créera un tableau vide que vous pourrez ensuite utiliser pour insérer des lignes sélectionnées dans. Vous pouvez également compresser la table avant d'insérer des données si nécessaire.

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

supprimera l'identité.

L'inconvénient est que id devient nullable mais vous pouvez ajouter cette contrainte.

0
john hunter