Une valeur explicite pour la colonne d'identité dans la table ne peut être spécifiée que lorsqu'une liste de colonnes est utilisée et que IDENTITY_INSERT est ON SQL Server

188

J'essaye de faire cette requête

INSERT INTO dbo.tbl_A_archive
  SELECT *
  FROM SERVER0031.DB.dbo.tbl_A

mais même après avoir couru

set identity_insert dbo.tbl_A_archive on

Je reçois ce message d'erreur

Une valeur explicite pour la colonne d'identité dans la table 'dbo.tbl_A_archive' ne peut être spécifiée que lorsqu'une liste de colonnes est utilisée et que IDENTITY_INSERT est activé.

tbl_Aest une table énorme en lignes et en largeur, c'est à dire qu'elle a BEAUCOUP de colonnes. Je ne veux pas avoir à taper toutes les colonnes manuellement. Comment puis-je faire fonctionner cela?

jhowe
la source
J'ai déjà installé un serveur lié au fait!
jhowe
4
J'ai aussi ce problème, sauf que "insérer dans X select * Y" n'était pas un problème jusqu'à ce que je change le schéma de la table
FistOfFury

Réponses:

81

Résumé

SQL Server ne vous permet pas d'insérer une valeur explicite dans une colonne d'identité, sauf si vous utilisez une liste de colonnes. Ainsi, vous avez les options suivantes:

  1. Créez une liste de colonnes (manuellement ou à l'aide d'outils, voir ci-dessous)

OU

  1. faites la colonne d'identité dans tbl_A_archiveune colonne régulière, sans identité : Si votre table est une table d'archive et que vous spécifiez toujours une valeur explicite pour la colonne d'identité, pourquoi avez-vous même besoin d'une colonne d'identité? Utilisez simplement un int régulier à la place.

Détails sur la solution 1

Au lieu de

SET IDENTITY_INSERT archive_table ON;

INSERT INTO archive_table
  SELECT *
  FROM source_table;

SET IDENTITY_INSERT archive_table OFF;

tu dois écrire

SET IDENTITY_INSERT archive_table ON;

INSERT INTO archive_table (field1, field2, ...)
  SELECT field1, field2, ...
  FROM source_table;

SET IDENTITY_INSERT archive_table OFF;

avec field1, field2, ...contenant les noms de toutes les colonnes de vos tables. Si vous souhaitez générer automatiquement cette liste de colonnes, jetez un œil à la réponse de Dave ou à la réponse d' Andomar .


Détails sur la solution 2

Malheureusement, il n'est pas possible de simplement "changer le type" d'une colonne identity int en une colonne non-identity int. Fondamentalement, vous avez les options suivantes:

  • Si la table d'archive ne contient pas encore de données, supprimez la colonne et ajoutez-en une nouvelle sans identité.

OU

  • Utilisez SQL Server Management Studio pour définir la propriété Identity Specification/ (Is Identity)de la colonne d'identité dans votre table d'archivage sur No. Dans les coulisses, cela créera un script pour recréer la table et copier les données existantes, donc, pour ce faire, vous devrez également désactiver Tools/ Options/ Designers/ Table and Database Designers/ Prevent saving changes that require table re-creation.

OU

Heinzi
la source
1
Pour votre solution du bas: si la colonne a "IDENTITY (1, 1)" ou quelque chose comme ça, il devra également la supprimer temporairement.
Aleksandr Khomenko
1
@AleksandrKhomenko: Oui, c'est ce que je voulais dire par "en faire une colonne int régulière ( sans identité )" .
Heinzi
1
Désolé pour la confusion. Je voulais dire en fait qu'il devra également supprimer la propriété d' auto-incrémentation . Dans le cas de SQL-Server, il appelle "IDENTITY (1, 1)" - votre réponse est tout à fait juste. Mais MySQL et Oracle ont une autre commande pour cela (et cela devient non évident, veuillez consulter w3schools.com/sql/sql_autoincrement.asp )
Aleksandr Khomenko
2
Cela manque de détails. Je comprends que c'est peut-être par cœur pour certains mais c'est absurde pour d'autres
DJ.
1
@DJ .: J'ai ajouté quelques détails.
Heinzi
332
SET IDENTITY_INSERT tableA ON

Vous devez créer une liste de colonnes pour votre instruction INSERT:

INSERT Into tableA ([id], [c2], [c3], [c4], [c5] ) 
SELECT [id], [c2], [c3], [c4], [c5] FROM tableB

pas comme "INSERT Into tableA SELECT ........"

SET IDENTITY_INSERT tableA OFF
Ehsan
la source
17
+1 ... Vous n'avez besoin que de colonnes explicites dans la clause SELECT de la requête. Vous pouvez conserver un astérisque dans la clause INSERT de la requête
MacGyver
8
... sauf si la cible a une colonne d'identité et que la table source n'a pas de colonne d'identité
MacGyver
1
cette réponse est un peu plus claire que Heinzi car elle mentionne SET IDENTITY_INSERT
Marty
4
+1 Fonctionne comme un charme! Cependant, "Vous pouvez conserver un astérisque dans la clause INSERT de la requête" ne fonctionnera pas ici.
Shai Alon
1
J'ai eu cette erreur lorsque les colonnes de la clause select ne correspondaient pas à la table de destination. S'assurant de cela, l'insert a fonctionné pour moi
Jose
39

Si vous utilisez SQL Server Management Studio, vous n'avez pas besoin de taper vous-même la liste des colonnes - cliquez simplement avec le bouton droit sur la table dans l' Explorateur d'objets et choisissez Script Table as -> SELECT to -> New Query Editor Window .

Si ce n'est pas le cas, une requête similaire à celle-ci devrait vous aider comme point de départ:

SELECT SUBSTRING(
    (SELECT ', ' + QUOTENAME(COLUMN_NAME)
        FROM INFORMATION_SCHEMA.COLUMNS
        WHERE TABLE_NAME = 'tbl_A'
        ORDER BY ORDINAL_POSITION
        FOR XML path('')),
    3,
    200000);
Dave Cluderay
la source
2
Cela peut être sélectionné dans une variable et utilisé dans une requête SQL dynamique. Tu as sauvé ma journée. Merci beaucoup!
Pawel Cioch
1
Merci! Je l'ai fait avec gist.github.com/timabell/0ddd6a69565593f907c7
Tim Abell
sensationnel. J'étais désespérément à la recherche de cela pour construire ma requête de manière dynamique. Surpris de voir ça ici. Merci mon pote.
Yasin Bilir
24

D'accord avec la réponse de Heinzi. Pour la première deuxième option, voici une requête qui génère une liste de colonnes séparées par des virgules dans une table:

select name + ', ' as [text()] 
from sys.columns 
where object_id = object_id('YourTable') 
for xml path('')

Pour les grandes tables, cela peut économiser beaucoup de travail de frappe :)

Andomar
la source
@Merci, vraiment utile. :)
Chirag Thakar
Parfois, vous avez juste besoin d'une solution pragmatique. Je vous remercie!
Tobias Feil
15

Si la table "archive" est censée être une copie exacte de votre table principale, je vous suggérerais simplement de supprimer le fait que l'id est une colonne d'identité. De cette façon, il vous permettra de les insérer.

Vous pouvez également autoriser et interdire les insertions d'identité pour la table avec l'instruction suivante

SET IDENTITY_INSERT tbl_A_archive ON
--Your inserts here
SET IDENTITY_INSERT tbl_A_archive OFF

Enfin, si vous avez besoin que la colonne d'identité fonctionne telle quelle, vous pouvez toujours simplement exécuter le processus stocké.

sp_columns tbl_A_archive 

Cela vous renverra toutes les colonnes de la table que vous pourrez ensuite couper et coller dans votre requête. (C'est presque TOUJOURS mieux que d'utiliser un *)

Jour de Robin
la source
11

Pour l'instruction SQL, vous devez également spécifier la liste des colonnes. Pour par exemple.

INSERT INTO tbl (idcol1,col2) VALUES ( value1,value2)

au lieu de

INSERT INTO tbl VALUES ( value1,value2)
Mohsin Mahmood
la source
2
Ce n'est pas le cas de la déclaration du PO. VALUES n'est pas nécessaire si INSERT INTO est suivi d'une instruction SELECT. Veuillez réviser ou supprimer votre réponse.
Gary le
4

Les deux fonctionneront mais si vous obtenez toujours une erreur en utilisant le # 1, passez au # 2

1)

SET IDENTITY_INSERT customers ON
GO
insert into dbo.tbl_A_archive(id, ...)
SELECT Id, ...
FROM SERVER0031.DB.dbo.tbl_A

2)

SET IDENTITY_INSERT customers ON
GO
insert into dbo.tbl_A_archive(id, ...)
VALUES(@Id,....)
Chirag Thakar
la source
4
Ce n'est vraiment pas la voie à suivre si l'OP insère plusieurs enregistrements. Vous ignorez "tbl_A est une table énorme en lignes et en largeur, c'est-à-dire qu'elle a BEAUCOUP de colonnes. Je ne veux pas avoir à taper toutes les colonnes manuellement."
Gary le
4

Afin de remplir tous les noms de colonnes dans une liste délimitée par des virgules pour une instruction Select pour les solutions mentionnées pour cette question, j'utilise les options suivantes car elles sont un peu moins verbeuses que la plupart des réponses ici. Cependant, la plupart des réponses ici sont toujours parfaitement acceptables.

1)

SELECT column_name + ',' 
FROM   information_schema.columns 
WHERE  table_name = 'YourTable'

2) Il s'agit probablement de l'approche la plus simple pour créer des colonnes, si vous disposez de SQL Server SSMS.

1) Allez dans la table dans l'Explorateur d'objets et cliquez sur le + à gauche du nom de la table ou double-cliquez sur le nom de la table pour ouvrir la sous-liste.

2) Faites glisser le sous-dossier de colonnes vers la zone de requête principale et il recopiera automatiquement la liste des colonnes entière pour vous.

Ryan
la source
3

Vous devez spécifier le nom des colonnes que vous souhaitez insérer s'il existe une colonne d'identité. Donc, la commande sera comme ceci ci-dessous:

SET IDENTITY_INSERT DuplicateTable ON

INSERT Into DuplicateTable ([IdentityColumn], [Column2], [Column3], [Column4] ) 
SELECT [IdentityColumn], [Column2], [Column3], [Column4] FROM MainTable

SET IDENTITY_INSERT DuplicateTable OFF

Si votre table comporte de nombreuses colonnes, obtenez le nom de ces colonnes à l'aide de cette commande.

SELECT column_name + ','
FROM   information_schema.columns 
WHERE  table_name = 'TableName'
for xml path('')

(après avoir supprimé la dernière virgule (',')) Copiez simplement le nom des colonnes.

Rousonur Jaman
la source
3

Cela devrait fonctionner. Je viens de rencontrer votre problème:

SET IDENTITY_INSERT dbo.tbl_A_archive ON;
INSERT INTO     dbo.tbl_A_archive (IdColumn,OtherColumn1,OtherColumn2,...)
SELECT  *
FROM        SERVER0031.DB.dbo.tbl_A;
SET IDENTITY_INSERT dbo.tbl_A_archive OFF;

Malheureusement, il semble que vous ayez besoin d'une liste des colonnes comprenant la colonne d'identité pour insérer des enregistrements qui spécifient l'identité. Cependant , vous n'avez pas besoin de lister les colonnes dans le SELECT. Comme @ Dave Cluderay l'a suggéré, cela se traduira par une liste formatée que vous pourrez copier et coller (si moins de 200 000 caractères).

J'ai ajouté l'utilisation depuis que je passe d'une instance à l'autre.

USE PES
SELECT SUBSTRING(
    (SELECT ', ' + QUOTENAME(COLUMN_NAME)
        FROM INFORMATION_SCHEMA.COLUMNS
        WHERE TABLE_NAME = 'Provider'
        ORDER BY ORDINAL_POSITION
        FOR XML path('')),
    3,
    200000);
Gary
la source
3
SET IDENTITY_INSERT tableA ON

INSERT Into tableA ([id], [c2], [c3], [c4], [c5] ) 
SELECT [id], [c2], [c3], [c4], [c5] FROM tableB

Pas comme ça

INSERT INTO tableA
SELECT * FROM tableB

SET IDENTITY_INSERT tableA OFF
lapin
la source
2

Cet extrait de code montre comment insérer dans la table lorsque la colonne de clé primaire d'identité est activée.

SET IDENTITY_INSERT [dbo].[Roles] ON
GO
insert into Roles (Id,Name) values(1,'Admin')
GO
insert into Roles (Id,Name) values(2,'User')
GO
SET IDENTITY_INSERT [dbo].[Roles] OFF
GO
AminGolmahalle
la source
1

Pour si vous souhaitez insérer vos valeurs d'une table à une autre via une procédure stockée. J'ai utilisé ceci et cela , ce dernier qui ressemble presque à la réponse d'Andomar.

CREATE procedure [dbo].[RealTableMergeFromTemp]
    with execute as owner
AS
BEGIN
BEGIN TRANSACTION RealTableDataMerge
SET XACT_ABORT ON

    DECLARE @columnNameList nvarchar(MAX) =
     STUFF((select ',' + a.name
      from sys.all_columns a
      join sys.tables t on a.object_id = t.object_id 
       where t.object_id = object_id('[dbo].[RealTable]') 
    order by a.column_id
    for xml path ('')
    ),1,1,'')

    DECLARE @SQLCMD nvarchar(MAX) =N'INSERT INTO [dbo].[RealTable] (' + @columnNameList + N') SELECT * FROM [#Temp]'

    SET IDENTITY_INSERT [dbo].[RealTable] ON;
    exec(@sqlcmd)
    SET IDENTITY_INSERT [dbo].[RealTable] OFF

COMMIT TRANSACTION RealTableDataMerge
END

GO
Rubénisme
la source
0

Il existe une ou plusieurs colonnes qui ont une propriété d'incrémentation automatique ou la valeur de cet attribut sera calculée en tant que contraintes. Vous essayez de modifier cette colonne.

Il y a deux façons de le résoudre 1) Mentionnez explicitement les autres colonnes et définissez leurs valeurs uniquement et la clé primaire ou la valeur de la colonne à incrémentation automatique sera définie automatiquement.

2) Vous pouvez activer INDENTITY_INSERT puis exécuter votre requête d'insertion et enfin désactiver IDENTITY_INSERT.

Suggestion: Suivez la première étape car c'est une approche plus appropriée et plus efficace.

Pour plus d'informations, lisez cet article sur SQL-helper .

Shanu Dey
la source
0

Assurez-vous que les noms de colonne, les types de données et l'ordre dans la table à partir de laquelle vous sélectionnez les enregistrements sont exactement les mêmes que la table de destination. La seule différence doit être que la table de destination a une colonne d'identité comme première colonne, qui n'est pas là dans la table source.

J'étais confronté à un problème similaire lorsque j'exécutais "INSERT INTO table_Dest SELECT * FROM table_source_linked_server_excel". Les tableaux avaient 115 colonnes.

J'avais deux de ces tables où je chargeais des données d'Excel (en tant que serveur lié) dans des tables de la base de données. Dans les tables de base de données, j'avais ajouté une colonne d'identité appelée 'id' qui n'était pas là dans la source Excel. Pour une table, la requête s'exécutait avec succès et dans une autre, j'ai eu l'erreur "Une valeur explicite pour la colonne d'identité dans la table ne peut être spécifiée que lorsqu'une liste de colonnes est utilisée et IDENTITY_INSERT est SUR SQL Server". C'était déroutant car le scénario était exactement le même pour les deux requêtes. J'ai donc enquêté sur ceci et ce que j'ai trouvé était que dans la requête où j'obtenais une erreur avec INSERT INTO .. ​​SELECT *:

  1. Certains des noms de colonne de la table source ont été modifiés, bien que les valeurs soient correctes
  2. Il y avait des colonnes supplémentaires au-delà des colonnes de données réelles qui étaient sélectionnées par SELECT *. J'ai découvert cela en utilisant l'option "Script table as> Select to> new query window" sur la table Excel source (sous les serveurs liés). Il y avait une colonne masquée juste après la dernière colonne dans Excel, bien qu'elle ne contienne aucune donnée. J'ai supprimé cette colonne dans le tableau Excel source et l'ai enregistrée.

Après avoir effectué les deux modifications ci-dessus, la requête INSERT INTO ... SELECT * s'est exécutée avec succès. La colonne d'identité dans la table de destination a généré des valeurs d'identité pour chaque ligne insérée comme prévu.

Ainsi, même si la table de destination peut avoir une colonne d'identité qui ne figure pas dans la table source, INSERT INTO .. ​​SELECT * s'exécutera correctement si les noms, les types de données et l'ordre des colonnes dans la source et la destination sont exactement les mêmes.

J'espère que ça aide quelqu'un.

Uttam
la source
-1

Je pense que cette erreur se produit en raison de l'incompatibilité avec le nombre de colonnes dans la définition de table et le nombre de colonnes dans la requête d'insertion. La longueur de la colonne est également omise avec la valeur saisie. Consultez simplement la définition de la table pour résoudre ce problème

Codemaker
la source
Cela a fonctionné pour moi: j'ai oublié d'ajouter une colonne au tableau à partir de la conception, bonne chose à vérifier avant d'essayer d'autres réponses.
Exel Gamboa