web-dev-qa-db-fra.com

Ajouter l'auto-incrémentation à PK existant

J'ai créé une table dans une base de données qui existe déjà dans une autre base de données. Il a été initialement rempli avec les anciennes données DB. Le PK de la table devait recevoir les valeurs qui existent déjà dans ces enregistrements, il ne pouvait donc pas s'agir d'une auto-incrémentation.

Maintenant, j'ai besoin de la nouvelle table pour avoir son PK comme incrémentation automatique. Mais comment puis-je faire cela après que le PK existe déjà et ait des données?

14
Hikari

La façon dont je comprends votre question est que vous avez une table existante avec une colonne qui jusqu'à présent a été remplie avec des valeurs manuelles, et maintenant vous voulez (1) faire de cette colonne une colonne IDENTITY et (2 ) assurez-vous que IDENTITY commence à partir de la valeur la plus récente des lignes existantes.

Tout d'abord, quelques données de test avec lesquelles jouer:

CREATE TABLE dbo.ident_test (
    id    int NOT NULL,
    xyz   varchar(10) NOT NULL,
    CONSTRAINT PK_ident_test PRIMARY KEY CLUSTERED (id)
);

INSERT INTO dbo.ident_test (id, xyz)
VALUES (1, 'test'),
       (2, 'test'),
       (5, 'test'),
       (6, 'test'),
       (10, 'test'),
       (18, 'test'),
       (19, 'test'),
       (20, 'test');

Le but est de créer la colonne de clé primaire de la table, id, une colonne IDENTITY qui commencera à 21 pour le prochain enregistrement qui sera inséré. Pour cet exemple, la colonne xyz représente toutes les autres colonnes de la table.

Avant de faire quoi que ce soit, veuillez lire les avertissements au bas de cet article.

Tout d'abord, en cas de problème:

BEGIN TRANSACTION;

Ajoutons maintenant une colonne de travail temporaire, id_temp et définissez cette colonne sur les valeurs existantes de la colonne id:

ALTER TABLE dbo.ident_test ADD id_temp int NULL;
UPDATE dbo.ident_test SET id_temp=id;

Ensuite, nous devons supprimer la colonne id existante (vous ne pouvez pas simplement "ajouter" un IDENTITY à une colonne existante, vous devez créer la colonne en tant que IDENTITY ). La clé primaire doit également disparaître, car la colonne en dépend.

ALTER TABLE dbo.ident_test DROP CONSTRAINT PK_ident_test;
ALTER TABLE dbo.ident_test DROP COLUMN id;

... et ajoutez à nouveau la colonne, cette fois sous la forme d'un IDENTITY, avec la clé primaire:

ALTER TABLE dbo.ident_test ADD id int IDENTITY(1, 1) NOT NULL;
ALTER TABLE dbo.ident_test ADD CONSTRAINT PK_ident_test PRIMARY KEY CLUSTERED (id);

Voici où cela devient intéressant. Vous pouvez activer IDENTITY_INSERT sur la table, ce qui signifie que vous pouvez définir manuellement les valeurs d'une colonne IDENTITY lorsque vous insérez de nouvelles lignes (mais pas en mettant à jour les lignes existantes).

SET IDENTITY_INSERT dbo.ident_test ON;

Avec cet ensemble, DELETE toutes les lignes du tableau, mais les lignes que vous supprimez sont OUTPUT directement dans le même tableau - mais avec des valeurs spécifiques pour le id colonne (à partir de la colonne de sauvegarde).

DELETE FROM dbo.ident_test
OUTPUT deleted.id_temp AS id, deleted.xyz
INTO dbo.ident_test (id, xyz);

Une fois terminé, tournez IDENTITY_INSERT recule à nouveau.

SET IDENTITY_INSERT dbo.ident_test OFF;

Déposez la colonne temporaire que nous avons ajoutée:

ALTER TABLE dbo.ident_test DROP COLUMN id_temp;

Et enfin, réamorcez la colonne IDENTITY, afin que le prochain enregistrement id reprenne après le nombre existant le plus élevé dans la colonne id:

DECLARE @maxid int;
SELECT @maxid=MAX(id) FROM dbo.ident_test;
DBCC CHECKIDENT ("dbo.ident_test", RESEED, @maxid)

En vérifiant l'exemple de table, le nombre id le plus élevé est 20.

SELECT * FROM dbo.ident_test;

Ajoutez une autre ligne et vérifiez son nouveau IDENTITY:

INSERT INTO dbo.ident_test (xyz) VALUES ('New row');
SELECT * FROM dbo.ident_test;

Dans l'exemple, la nouvelle ligne aura id=21. Enfin, si vous êtes satisfait, validez la transaction:

COMMIT TRANSACTION;

Important

Ce n'est pas une opération banale, et elle comporte plusieurs risques dont vous devez être conscient.

  • Faites-le dans un environnement de test dédié. Avoir des sauvegardes. :)

  • J'aime utiliser BEGIN/COMMIT TRANSACTION car cela empêche les autres processus de jouer avec la table pendant que vous êtes en train de la changer, et vous donne la possibilité de tout annuler en cas de problème. Cependant, tout autre processus qui tente d'accéder à votre table avant d'avoir validé votre transaction finira par attendre. Cela peut être assez mauvais si vous avez une grande table et/ou si vous êtes dans un environnement de production.

  • OUTPUT .. INTO ne fonctionnera pas si votre table cible a des contraintes de clé étrangère ou l'une des nombreuses autres fonctionnalités dont je ne me souviens pas. Au lieu de cela, vous pouvez décharger les données dans une table temporaire, puis les réinsérer dans la table d'origine. Vous pourrez peut-être utiliser la commutation de partition (même si vous n'utilisez pas de partitions).

  • Exécutez ces instructions une par une, pas en tant que lot ou dans une procédure stockée.

  • Essayez de penser à d'autres choses qui peuvent dépendre de la colonne id que vous supprimez et recréez. Tous les index devront être supprimés et recréés (comme nous l'avons fait avec la clé primaire). N'oubliez pas de scripter chaque index et contrainte que vous devrez recréer au préalable.

  • Désactivez tous les déclencheurs INSERT et DELETE sur la table.

Si recréer la table est une option:

Si la recréation de la table est une option pour vous, tout est beaucoup plus simple:

  • Créez la table vide, avec la colonne id comme IDENTITY,
  • Ensemble IDENTITY_INSERT ON pour la table,
  • Remplissez la table,
  • Ensemble IDENTITY_INSERT OFF, et
  • Ressuscitez l'identité.
14
Daniel Hutmacher

L'utilisation de UPDATE, DELETE ou INSERT pour déplacer des données peut prendre beaucoup de temps et utiliser des ressources (IO) sur les données et les fichiers journaux/disques. Il est possible d'éviter de remplir le journal des transactions avec potentiellement beaucoup d'enregistrements tout en travaillant sur une grande table: à l'aide de la commutation de partition, seules les métadonnées sont modifiées.

Aucun mouvement de données n'est impliqué et ceci est donc effectué très rapidement (presque instantanément).

Exemple de table

La question n'affiche pas la table DDL d'origine. Le DDL suivant sera utilisé comme exemple dans cette réponse:

CREATE TABLE dbo.idT(
    id int not null
    , uid uniqueidentifier not null
    , name varchar(50)
);
ALTER TABLE dbo.idT ADD CONSTRAINT PK_idT PRIMARY KEY CLUSTERED(id);

Une demi-douzaine d'identifiants aléatoires factices de 0 à 15 sont ajoutés à cette requête:

WITH ids(n) AS(
    SELECT x1.n+x2.n*4
    FROM (values(0), (3)) as x1(n)
    CROSS JOIN (values(0), (2), (3)) as x2(n)
)
INSERT INTO idt(id, uid, name)
SELECT n, NEWID(), NEWID() 
FROM ids

Exemples de données dans IdT

id  uid                                     name
0   65533096-5007-43EA-88AD-D6776B3B94FA    6A69D4F2-D682-4168-A92F-4CD2E2DBC21D
3   CE87F1ED-BE1A-4F2D-8D62-E1ECA822D35B    AF0524D9-0DBB-41E1-883B-003CB4E4F012
8   34A1DBFD-4F92-4F34-9F04-4CDC824AB15A    02B4BDA4-D515-4262-9031-0BE496AC24CE
11  51606C95-9DE8-4C30-B23B-F915EEA41156    93258103-9C22-4F9C-85CF-712ED0FB3CE6
12  CEC80431-0513-4751-A250-0EB3390DACAB    2DA6B8AF-3EBC-42B3-A76C-028716E24661
15  5037EA83-286F-4EBC-AD7C-E237B570C1FF    095E51E9-8C38-4104-858F-D14AA810A550

Nouvelle table avec IDENTITY(0, 1)

Le seul problème avec idT est le manque de la propriété IDENTITY(0, 1) sur id. Une nouvelle table avec une structure similaire et IDENTITY(0, 1) est créée:

CREATE TABLE dbo.idT_Switch(
    id int identity(0, 1) not null
    , uid uniqueidentifier not null
    , name varchar(50)
);
ALTER TABLE dbo.idT_Switch ADD CONSTRAINT PK_idT_Switch PRIMARY KEY CLUSTERED(id);

Mis à part IDENTITY(0, 1), idT_Switch Est identique à idT.

Clés étrangères

Les clés étrangères sur idT doivent être supprimées pour permettre l'utilisation de cette technique.

Commutateur de partition

Les tables idT et idT_Switch Ont une structure compatible. Au lieu d'utiliser les instructions DELETE, UPDATE et INSERT pour déplacer des lignes de idT vers idT_Switch Ou sur idT lui-même , ALTER TABLE ... SWITCH Peut être utilisé:

ALTER TABLE dbo.idT
SWITCH TO dbo.idT_Switch;

La "partition" unique de PK_idT (La table entière) est déplacée vers PK_idT_Switch (Et vice versa). idT contient maintenant 0 lignes et idT_Switch contient 6 lignes.

Vous pouvez trouver la liste complète des exigences de compatibilité source et destination ici:

Transfert efficace des données à l'aide de la commutation de partition

Notez que cette utilisation de SWITCH ne nécessite pas Enterprise Edition, car il n'y a pas de partitionnement explicite. Une table non partitionnée est considérée comme une table avec une seule partition à partir de SQL Server 2005.

Remplacer idT

idT est maintenant vide et inutile et peut être supprimé:

DROP TABLE idT;

idT_Switch Peut être renommé et remplacera l'ancienne table idT:

EXECUTE sys.sp_rename
    @objname = N'dbo.idT_Switch',
    @newname = N'idT', -- note lack of schema prefix
    @objtype = 'OBJECT';

Clés étrangères

Les clés étrangères peuvent être ajoutées à nouveau à la nouvelle table idT. Tout autre élément précédemment supprimé de idT pour rendre les tables compatibles pour la commutation devra également être refait.

Réensemencer

SELECT IDENT_CURRENT( 'dbo.idT');

Cette commande renvoie 0. La table idT contient 6 lignes avec MAX (id) = 15. DBCC CHECKIDENT (nom_table) peut être utilisé:

DBCC CHECKIDENT ('dbo.idT');

Parce que 15 est plus grand que 0, il se réamorcera automatiquement sans chercher MAX (id):

Si la valeur d'identité actuelle d'une table est inférieure à la valeur d'identité maximale stockée dans la colonne d'identité, elle est réinitialisée à l'aide de la valeur maximale de la colonne d'identité. Voir la section "Exceptions" qui suit.

IDENT_CURRENT renvoie désormais 15 .

Testez et ajoutez des données

Une simple instruction INSERT:

INSERT INTO idT(uid, name) SELECT NEWID(), NEWID();

Ajoute cette ligne:

id  uid                                     name
16  B395D692-5D7B-4DFA-9971-A1497B8357A1    FF210D9E-4027-479C-B5D8-057E77FAF378

La colonne id utilise maintenant l'identité et la nouvelle valeur insérée est en effet 16 (15 + 1).

Plus d'information

Il existe une question et une réponse connexes avec plus d'informations sur la technique SWITCH ici:

Pourquoi la suppression de la propriété d'identité sur une colonne n'est pas prise en charge

5
Julien Vavasseur

Si vous souhaitez commencer avec une nouvelle valeur d'identité, vous devez réamorcer votre identité. Jetez un œil à la documentation de CHECKIDENT

DBCC CHECKIDENT (yourtable, reseed, starting point)

ACTIVER et DÉSACTIVER IDENTITY_INSERT

Si votre table est TABLE_A alors

  1. CREATE TABLE TABLE_B similaire à TABLE_A avec la colonne d'identité
  2. SET IDENTITY_INSERT TABLE_B ON
  3. INSÉRER dans TABLE_B à partir de TABLE_A
  4. SET IDENTITY_INSERT TABLE_B OFF
  5. DROP TABLE TABLE_A et renommez la table B Exec sp_rename 'TABLE_B', 'TABLE_A'
0
user4321