web-dev-qa-db-ja.com

既存のPKに自動インクリメントを追加する

別のDBにすでに存在するDBにテーブルを作成しました。最初は古いDBデータが入力されていました。テーブルのPKはそれらのレコードに既に存在する値を受け取る必要があったため、自動インクリメントできませんでした。

ここで、新しいテーブルにPKを自動インクリメントとして設定する必要があります。しかし、PKが既に存在し、データを持っている場合、どうすればよいですか?

14
Hikari

私の質問を理解する方法は、これまで手動で値が入力されていた列を持つ既存のテーブルがあり、(1)この列をIDENTITY列にして、(2) IDENTITYは、既存の行の最新の値から始まります。

まず、いくつかのテストデータを操作します。

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

目標は、テーブルの主キー列、id、挿入される次のレコードの21から始まるIDENTITY列を作成することです。この例では、列xyzはテーブルの他のすべての列を表します。

何かする前に、この投稿の下部にある警告をお読みください。

まず、何かがうまくいかない場合に備えて:

BEGIN TRANSACTION;

次に、一時的な作業列id_tempを追加し、その列を既存のid列の値に設定します。

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

次に、既存のid列を削除する必要があります(IDENTITYを既存の列に「追加」するだけではなく、IDENTITYとして列を作成する必要があります)。列が依存しているため、主キーも移動する必要があります。

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

...列をもう一度追加します。今回は、IDENTITYとして主キーとともに追加します。

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

ここが面白いところです。テーブルでIDENTITY_INSERTを有効にできます。つまり、新しい行を挿入するときに(既存の行を更新するのではなく)、IDENTITY列の値を手動で定義できます。

SET IDENTITY_INSERT dbo.ident_test ON;

そのセットを使用すると、テーブル内のすべての行をDELETE削除しますが、削除する行は、まったく同じテーブルにOUTPUTですが、id列の特定の値が(バックアップ列から)あります。

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

完了したら、IDENTITY_INSERTを再びオフにします。

SET IDENTITY_INSERT dbo.ident_test OFF;

追加した一時的な列を削除します。

ALTER TABLE dbo.ident_test DROP COLUMN id_temp;

最後に、IDENTITY列を再シードします。これにより、次のレコードのidは、id列の既存の最大数の後に再開します。

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

サンプルテーブルを確認すると、idの最大値は20です。

SELECT * FROM dbo.ident_test;

別の行を追加して、その新しいIDENTITYを確認します。

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

この例では、新しい行にはid=21が含まれます。最後に、問題がなければ、トランザクションをコミットします。

COMMIT TRANSACTION;

重要

これは簡単な操作ではなく、注意が必要なリスクがかなりあります。

  • 専用のテスト環境でこれを行ってください。バックアップを持っています。 :)

  • BEGIN/COMMIT TRANSACTIONを使用したいのは、変更の最中に他のプロセスがテーブルを乱すのを防ぎ、何か問題が発生した場合にすべてをロールバックする可能性があるためです。ただし、トランザクションをコミットする前にテーブルにアクセスしようとする他のプロセスは、最終的に待機状態になります。大きなテーブルがある場合や本番環境を使用している場合は、これはかなり悪いことになる場合があります。

  • OUTPUT .. INTOは、ターゲットテーブルに外部キー制約がある場合、または頭上で覚えていない他の多くの機能がある場合は機能しません。代わりに、一時テーブルにデータをオフロードし、元のテーブルに挿入することもできます。パーティション切り替えを使用できる場合があります(パーティションを使用しない場合でも)。

  • これらのステートメントは、バッチとして、またはストアード・プロシージャーとしてではなく、1つずつ実行してください。

  • 削除して再作成するid列に依存する可能性のある他のことを考えてみてください。インデックスはすべて削除して再作成する必要があります(主キーで行ったように)。事前に再作成する必要があるすべてのインデックスと制約をスクリプト化することを忘れないでください。

  • テーブルのINSERTおよびDELETEトリガーを無効にします。

テーブルの再作成がオプションの場合:

テーブルの再作成がオプションである場合、すべてがはるかに簡単です。

  • id列をIDENTITYとして空のテーブルを作成し、
  • テーブルにIDENTITY_INSERT ONを設定し、
  • テーブルに入力し、
  • IDENTITY_INSERT OFFを設定し、
  • アイデンティティを再シードします。
14

UPDATE、DELETE、またはINSERTを使用してデータを移動するには、かなりの時間がかかり、データとログファイル/ディスクの両方でリソース(IO)を使用する可能性があります。大きなテーブルで作業しているときに、トランザクションログが潜在的に大量のレコードでいっぱいになるのを回避できます。パーティションの切り替えを使用すると、メタデータのみが変更されます。

データの移動は含まれないため、これは非常に迅速に(ほぼ瞬時に)実行されます。

サンプル表

質問は元のテーブルDDLを示していません。この回答では、次のDDLが例として使用されます。

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

0から15までの6ダースのダミーランダムIDがこのクエリで追加されます。

_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
_

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
_

IDENTITY(0, 1)を含む新しいテーブル

idTの唯一の問題は、idにIDENTITY(0, 1)プロパティがないことです。同様の構造とIDENTITY(0, 1)を持つ新しいテーブルが作成されます。

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

IDENTITY(0, 1)を除いて、_idT_Switch_はidTと同じです。

外部キー

この手法を使用するには、idTの外部キーを削除する必要があります。

パーティションスイッチ

idTおよび_idT_Switch_テーブルは互換性のある構造を持っています。 DELETEUPDATEおよびINSERTステートメントを使用して行をidTから_idT_Switch_またはidT自体に移動する代わりに、 _ALTER TABLE ... SWITCH_を使用できます:

_ALTER TABLE dbo.idT
SWITCH TO dbo.idT_Switch;
_

_PK_idT_(テーブル全体)の単一の「パーティション」は、_PK_idT_Switch_(およびその逆)に移動されます。 idTには0行が含まれ、_idT_Switch_には6行が含まれます。

ソースと宛先の互換性要件の完全なリストは、次の場所にあります。

パーティション切り替えを使用してデータを効率的に転送する

このSWITCHの使用には、明示的なパーティション分割がないため、Enterprise Editionは必要ありません。パーティション分割されていないテーブルは、SQL Server 2005以降の単一のパーティションを持つテーブルと見なされます。

idTを置き換える

idTは空になり、役に立たなくなり、削除できるようになりました。

_DROP TABLE idT;
_

_idT_Switch_は名前を変更でき、古いidTテーブルを置き換えます。

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

外部キー

外部キーを新しいidTテーブルに再度追加できます。テーブルを切り替え可能にするためにidTから以前に削除された他のものもすべてやり直す必要があります。

再シード

_SELECT IDENT_CURRENT( 'dbo.idT');
_

このコマンドは0を返します。テーブルidTにはMAX(id)= 15の6行が含まれます DBCC CHECKIDENT(table_name) を使用できます:

_DBCC CHECKIDENT ('dbo.idT');
_

15は0よりも大きいので、MAX(id)を探すことなく自動的に再シードされます。

テーブルの現在のID値がID列に格納されている最大ID値よりも小さい場合、ID列の最大値を使用してリセットされます。次の「例外」セクションを参照してください。

IDENT_CURRENTは15を返すようになりました。

データをテストして追加する

単純なINSERTステートメント:

_INSERT INTO idT(uid, name) SELECT NEWID(), NEWID();
_

この行を追加します:

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

id列はIDを使用しており、新しく挿入された値は実際には16(15 + 1)です。

詳しくは

SWITCHテクニックの背景について、関連する質問と回答があります。

列のIdentityプロパティの削除がサポートされていない理由

5

新しいID値で開始する場合は、IDを再シードする必要があります。 CHECKIDENT のドキュメントをご覧ください

DBCC CHECKIDENT (yourtable, reseed, starting point)

ENABLEおよびDISABLE IDENTITY_INSERT

テーブルがTABLE_Aの場合

  1. ID_列を持つTABLE_Aと同様のCREATE TABLE TABLE_B
  2. SET IDENTITY_INSERT TABLE_B ON
  3. TABLE_AからTABLE_Bに挿入
  4. SET IDENTITY_INSERT TABLE_B OFF
  5. DROP TABLE TABLE_AとテーブルBの名前変更Exec sp_rename 'TABLE_B'、 'TABLE_A'
0
user4321