Füge Autoincrement zu bestehender PK hinzu

13

Ich habe eine Tabelle in einem DB erstellt, der bereits in einem anderen DB vorhanden ist. Es wurde ursprünglich mit den alten DB-Daten gefüllt. Der PK der Tabelle musste die Werte empfangen, die bereits in diesen Datensätzen vorhanden waren, damit es nicht zu einer automatischen Inkrementierung kommen konnte.

Jetzt brauche ich die neue Tabelle, um ihre PK als Autoincrement zu haben. Aber wie kann ich das tun, wenn die PK bereits vorhanden ist und Daten hat?

Hikari
quelle
3
Wenn Sie "Autoincrement" sagen, worauf beziehen Sie sich genau ? In SQL Server gibt es keine solche Eigenschaft für eine Spalte. Meinst du IDENTITY?
Max Vernon
Ja, so heißt es in MSSQL. In der Datenbank im Allgemeinen ist es eine Autoincrement-PK.
Hikari

Antworten:

13

Ich verstehe Ihre Frage so, dass Sie eine vorhandene Tabelle mit einer Spalte haben, die bisher mit manuellen Werten gefüllt war, und jetzt möchten Sie (1) diese Spalte zu einer IDENTITYSpalte machen und (2) sicherstellen, dass sie IDENTITYbeginnt vom neuesten Wert in den vorhandenen Zeilen.

Zunächst einige Testdaten zum Spielen:

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');

Das Ziel ist es, die Primärschlüsselspalte der Tabelle zu erstellen id, eine IDENTITYSpalte, die bei 21 für den nächsten Datensatz beginnt, der eingefügt wird. In diesem Beispiel xyzrepräsentiert die Spalte alle anderen Spalten der Tabelle.

Bevor Sie etwas tun, lesen Sie bitte die Warnungen am Ende dieses Beitrags.

Zunächst einmal, falls etwas schief geht:

BEGIN TRANSACTION;

Nun fügen wir eine temporäre Arbeitsspalte hinzu id_tempund setzen diese Spalte auf die idWerte der vorhandenen Spalte:

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

Als nächstes müssen wir die vorhandene idSpalte löschen (Sie können IDENTITYeiner vorhandenen Spalte nicht einfach "hinzufügen" , Sie müssen die Spalte als erstellen IDENTITY). Der Primärschlüssel muss auch gehen, weil die Spalte davon abhängt.

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

... und fügen Sie die Spalte erneut hinzu, diesmal als IDENTITYzusammen mit dem Primärschlüssel:

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);

Hier wird es interessant. Sie können IDENTITY_INSERTfür die Tabelle aktivieren. Dies bedeutet, dass Sie die Werte einer IDENTITYSpalte manuell definieren können, wenn Sie neue Zeilen einfügen (jedoch keine vorhandenen Zeilen aktualisieren).

SET IDENTITY_INSERT dbo.ident_test ON;

Mit diesem Satz befinden sich DELETEalle Zeilen in der Tabelle, aber die Zeilen, die Sie löschen, OUTPUTdirekt in derselben Tabelle - jedoch mit bestimmten Werten für die idSpalte (aus der Sicherungsspalte).

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

Sobald Sie fertig sind, schalten Sie ihn IDENTITY_INSERTwieder aus.

SET IDENTITY_INSERT dbo.ident_test OFF;

Löschen Sie die temporäre Spalte, die wir hinzugefügt haben:

ALTER TABLE dbo.ident_test DROP COLUMN id_temp;

Und schließlich die IDENTITYSpalte neu auslegen , damit die nächsten Datensätze idnach der höchsten vorhandenen Nummer in der idSpalte fortgesetzt werden :

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

In der Beispieltabelle ist die höchste idZahl 20.

SELECT * FROM dbo.ident_test;

Fügen Sie eine weitere Zeile hinzu und überprüfen Sie die neue IDENTITY:

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

Im Beispiel wird die neue Zeile haben id=21. Wenn Sie zufrieden sind, führen Sie die Transaktion aus:

COMMIT TRANSACTION;

Wichtig

Dies ist keine triviale Operation und birgt eine Reihe von Risiken, die Sie berücksichtigen sollten.

  • Führen Sie dies in einer dedizierten Testumgebung durch. Backups haben. :)

  • Ich benutze BEGIN/COMMIT TRANSACTIONes gerne, weil es verhindert, dass andere Prozesse mit der Tabelle in Konflikt geraten, während Sie gerade dabei sind, sie zu ändern, und es gibt Ihnen die Möglichkeit, alles zurückzusetzen, wenn etwas schief geht. Jeder andere Prozess, der versucht, auf Ihre Tabelle zuzugreifen, bevor Sie Ihre Transaktion festgeschrieben haben, wird jedoch warten. Dies kann ziemlich schlimm sein, wenn Sie einen großen Tisch haben und / oder sich in einer Produktionsumgebung befinden.

  • OUTPUT .. INTOfunktioniert nicht, wenn Ihre Zieltabelle Fremdschlüsseleinschränkungen oder eine Reihe anderer Funktionen enthält, an die ich mich nicht mehr ganz genau erinnern kann. Sie können die Daten stattdessen in eine temporäre Tabelle entladen und dann wieder in die Originaltabelle einfügen. Möglicherweise können Sie die Partitionsumschaltung verwenden (auch wenn Sie keine Partitionen verwenden).

  • Führen Sie diese Anweisungen einzeln aus, nicht als Stapel oder in einer gespeicherten Prozedur.

  • Denken Sie an andere Dinge, die von der idSpalte abhängen , die Sie löschen und neu erstellen. Alle Indizes müssen gelöscht und neu erstellt werden (wie wir es mit dem Primärschlüssel getan haben). Denken Sie daran, jeden Index und jede Einschränkung zu skripten, die Sie zuvor neu erstellen müssen.

  • Deaktivieren Sie alle INSERTund DELETElöst auf dem Tisch.

Wenn das Neuerstellen der Tabelle eine Option ist:

Wenn es für Sie eine Option ist, die Tabelle neu zu erstellen, ist alles viel einfacher:

  • Erstellen Sie die leere Tabelle mit der idSpalte als IDENTITY,
  • Gedeck IDENTITY_INSERT ONfür den Tisch,
  • Füllen Sie die Tabelle,
  • Set IDENTITY_INSERT OFFund
  • Erneuere die Identität.
Daniel Hutmacher
quelle
Tolle Antwort, vielen Dank! Tatsächlich kann ich es in meinem Fall einfach einstellen IDENTITY_INSERT ON, auffüllen und deaktivieren. Das wollte ich tun, wusste aber nicht, dass MSSQL es unterstützt.
Hikari
5

Das Verschieben von Daten mit UPDATE, DELETE oder INSERT kann viel Zeit in Anspruch nehmen und Ressourcen (IO) auf Daten- und Protokolldateien / -datenträgern belegen. Es ist möglich, zu vermeiden, dass das Transaktionsprotokoll bei der Arbeit an einer großen Tabelle mit möglicherweise vielen Datensätzen gefüllt wird: Beim Partitionswechsel werden nur die Metadaten geändert.

Es ist keine Datenverschiebung erforderlich, und dies erfolgt daher sehr schnell (fast augenblicklich).

Probentabelle

Die Frage zeigt nicht die ursprüngliche Tabelle DDL. Die folgende DDL wird in dieser Antwort als Beispiel verwendet:

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);

Ein halbes Dutzend zufällige Dummy-IDs von 0 bis 15 werden mit dieser Abfrage hinzugefügt:

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

Beispieldaten in 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

Neuer Tisch mit IDENTITY(0, 1)

Das einzige Problem idTist das Fehlen der IDENTITY(0, 1)Eigenschaft auf ID. Eine neue Tabelle mit einer ähnlichen Struktur IDENTITY(0, 1)wird erstellt:

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);

Abgesehen davon IDENTITY(0, 1), idT_Switchist identisch idT.

Fremde Schlüssel

Fremdschlüssel an idTmüssen entfernt werden, damit diese Technik verwendet werden kann.

Partitionsschalter

Die Tabellen idTund idT_Switchhaben eine kompatible Struktur. Anstelle der Verwendung DELETE, UPDATEund INSERTAussagen Zeilen verschieben von idTzu idT_Switchoder auf idTsich selbst, ALTER TABLE ... SWITCHkönnen verwendet werden:

ALTER TABLE dbo.idT
SWITCH TO dbo.idT_Switch;

Die einzelne 'Partition' von PK_idT(der gesamten Tabelle) wird nach PK_idT_Switch(und umgekehrt) verschoben . idTenthält jetzt 0 Zeilen und idT_Switchenthält 6 Zeilen.

Die vollständige Liste der Quell- und Zielkompatibilitätsanforderungen finden Sie hier:

Effiziente Datenübertragung mit Partition Switching

Beachten Sie, dass für diese Verwendung von SWITCHkeine Enterprise Edition erforderlich ist, da keine explizite Partitionierung erfolgt. Eine nicht partitionierte Tabelle wird ab SQL Server 2005 als Tabelle mit einer einzelnen Partition betrachtet.

Ersetzen idT

idT ist jetzt leer und unbrauchbar und kann fallengelassen werden:

DROP TABLE idT;

idT_Switchkann umbenannt werden und ersetzt die alte idTTabelle:

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

Fremde Schlüssel

Der neuen idTTabelle können wieder Fremdschlüssel hinzugefügt werden . Alles, was zuvor entfernt wurde idT, um die Tabellen für den Wechsel kompatibel zu machen, muss ebenfalls wiederholt werden.

Reseed

SELECT IDENT_CURRENT( 'dbo.idT');

Dieser Befehl gibt 0 zurück. Die Tabelle idT enthält 6 Zeilen mit MAX (id) = 15. DBCC CHECKIDENT (table_name) kann verwendet werden:

DBCC CHECKIDENT ('dbo.idT');

Da 15 größer als 0 ist, wird die Saat automatisch wiederholt, ohne nach MAX (id) zu suchen:

Wenn der aktuelle Identitätswert für eine Tabelle kleiner als der in der Identitätsspalte gespeicherte maximale Identitätswert ist, wird er mit dem maximalen Wert in der Identitätsspalte zurückgesetzt. Siehe den folgenden Abschnitt "Ausnahmen".

IDENT_CURRENT gibt jetzt 15 zurück .

Testen und Daten hinzufügen

Eine einfache INSERTAussage:

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

Fügt diese Zeile hinzu:

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

Die idSpalte verwendet jetzt die Identität und der neu eingefügte Wert ist tatsächlich 16 (15 + 1).

Mehr Informationen

Hier gibt es eine verwandte Frage und Antwort mit mehr Hintergrundinformationen zur SWITCHTechnik:

Warum wird das Entfernen der Identity-Eigenschaft für eine Spalte nicht unterstützt?

Julien Vavasseur
quelle
4

Wenn Sie mit einem neuen Identitätswert beginnen möchten, müssen Sie Ihre Identität erneut überprüfen. Schauen Sie sich die Dokumentation für anCHECKIDENT

DBCC CHECKIDENT (yourtable, reseed, starting point)
Tom V - Team Monica
quelle
0

ENABLE und DISABLE IDENTITY_INSERT

Wenn Ihre Tabelle dann TABLE_A ist

  1. CREATE TABLE TABLE_B ähnlich wie TABLE_A mit der Identitätsspalte
  2. SET IDENTITY_INSERT TABLE_B ON
  3. INSERT in TABLE_B von TABLE_A
  4. SET IDENTITY_INSERT TABLE_B OFF
  5. DROP TABLE TABLE_A und benenne Tabelle B Exec sp_rename 'TABLE_B', 'TABLE_A'
user4321
quelle