別のDBにすでに存在するDBにテーブルを作成しました。最初は古いDBデータが入力されていました。テーブルのPKはそれらのレコードに既に存在する値を受け取る必要があったため、自動インクリメントできませんでした。
ここで、新しいテーブルにPKを自動インクリメントとして設定する必要があります。しかし、PKが既に存在し、データを持っている場合、どうすればよいですか?
私の質問を理解する方法は、これまで手動で値が入力されていた列を持つ既存のテーブルがあり、(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
を設定し、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
_テーブルは互換性のある構造を持っています。 DELETE
、UPDATE
および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
テクニックの背景について、関連する質問と回答があります。
新しいID値で開始する場合は、IDを再シードする必要があります。 CHECKIDENT
のドキュメントをご覧ください
DBCC CHECKIDENT (yourtable, reseed, starting point)
ENABLEおよびDISABLE IDENTITY_INSERT
テーブルがTABLE_Aの場合