Thêm tự động vào PK hiện có


13

Tôi đã tạo một bảng trong một DB đã tồn tại trong một DB khác. Ban đầu nó được điền với dữ liệu DB cũ. PK của bảng phải nhận các giá trị đã tồn tại trên các bản ghi đó, vì vậy nó không thể tự động.

Bây giờ tôi cần bảng mới để có PK tự động. Nhưng làm thế nào tôi có thể làm điều đó sau khi PK đã tồn tại và có dữ liệu?


3
Khi bạn nói "tự động", chính xác những gì bạn đang đề cập đến? Trong SQL Server không có thuộc tính như vậy cho một cột. Ý bạn là IDENTITYsao
Max Vernon

Vâng, đó là cách nó được gọi trong MSSQL. Trong cơ sở dữ liệu nói chung, đó là một PK tự động.
Hikari

Câu trả lời:


13

Cách tôi hiểu câu hỏi của bạn là bạn có một bảng hiện có với một cột cho đến nay đã được điền với các giá trị thủ công và bây giờ bạn muốn (1) biến cột này thành một IDENTITYcột và (2) đảm bảo rằng IDENTITYbắt đầu từ giá trị gần đây nhất trong các hàng hiện có.

Trước hết, một số dữ liệu thử nghiệm để chơi với:

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

Mục tiêu là tạo cột khóa chính của bảng id, một IDENTITYcột sẽ bắt đầu từ 21 cho bản ghi tiếp theo được chèn. Trong ví dụ này, cột xyzđại diện cho tất cả các cột khác của bảng.

Trước khi bạn làm bất cứ điều gì, xin vui lòng đọc các cảnh báo ở dưới cùng của bài viết này.

Trước hết, trong trường hợp có lỗi xảy ra:

BEGIN TRANSACTION;

Bây giờ, hãy thêm một cột công việc tạm thời id_tempvà đặt cột đó thành các idgiá trị của cột hiện có :

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

Tiếp theo, chúng ta cần bỏ idcột hiện có (bạn không thể "thêm" một IDENTITYcột vào cột hiện có, bạn phải tạo cột dưới dạng một IDENTITY). Khóa chính cũng phải đi, vì cột phụ thuộc vào nó.

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

... và thêm cột một lần nữa, lần này là một IDENTITY, cùng với khóa chính:

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

Đây là nơi nó trở nên thú vị. Bạn có thể bật IDENTITY_INSERTtrên bảng, điều đó có nghĩa là bạn có thể xác định thủ công các giá trị của IDENTITYcột khi bạn chèn các hàng mới (mặc dù không cập nhật các hàng hiện có).

SET IDENTITY_INSERT dbo.ident_test ON;

Với tập hợp đó, DELETEtất cả các hàng trong bảng, nhưng các hàng bạn đang xóa đều nằm OUTPUTtrong cùng một bảng - nhưng có các giá trị cụ thể cho idcột (từ cột dự phòng).

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

Một lần, xong, IDENTITY_INSERTtắt lại.

SET IDENTITY_INSERT dbo.ident_test OFF;

Bỏ cột tạm thời mà chúng tôi đã thêm:

ALTER TABLE dbo.ident_test DROP COLUMN id_temp;

Và cuối cùng, đã định lại IDENTITYcột, vì vậy bản ghi tiếp theo idsẽ tiếp tục sau số hiện có cao nhất trong idcột:

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

Kiểm tra bảng ví dụ, idsố cao nhất là 20.

SELECT * FROM dbo.ident_test;

Thêm một hàng khác và kiểm tra mới của nó IDENTITY:

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

Trong ví dụ, hàng mới sẽ có id=21. Cuối cùng, nếu bạn hài lòng, hãy thực hiện giao dịch:

COMMIT TRANSACTION;

Quan trọng

Đây không phải là một hoạt động tầm thường, và nó mang theo khá nhiều rủi ro mà bạn nên biết.

  • Làm điều này trong một môi trường thử nghiệm chuyên dụng. Có bản sao lưu. :)

  • Tôi thích sử dụng BEGIN/COMMIT TRANSACTIONvì nó ngăn các quá trình khác làm hỏng bảng trong khi bạn đang thay đổi nó và nó cho bạn khả năng khôi phục lại mọi thứ nếu có sự cố. Tuy nhiên, bất kỳ quy trình nào khác cố gắng truy cập vào bảng của bạn trước khi bạn cam kết giao dịch của mình sẽ phải chờ. Điều này có thể khá tệ nếu bạn có một bàn lớn và / hoặc bạn đang ở trong môi trường sản xuất.

  • OUTPUT .. INTOsẽ không hoạt động nếu bảng mục tiêu của bạn có các ràng buộc khóa ngoại hoặc bất kỳ tính năng nào khác mà tôi không thể nhớ ra khỏi đỉnh đầu. Thay vào đó, bạn có thể tải dữ liệu vào một bảng tạm thời, sau đó chèn lại vào bảng gốc. Bạn có thể sử dụng chuyển đổi phân vùng (ngay cả khi bạn không sử dụng phân vùng).

  • Chạy các câu lệnh này từng cái một, không phải theo lô hoặc trong một thủ tục được lưu trữ.

  • Hãy thử nghĩ về những thứ khác có thể phụ thuộc vào idcột mà bạn đang thả và tạo lại. Bất kỳ chỉ mục nào cũng sẽ phải được loại bỏ và tạo lại (giống như chúng ta đã làm với khóa chính). Hãy nhớ kịch bản mọi chỉ mục và ràng buộc mà bạn sẽ cần phải tạo lại trước đó.

  • Vô hiệu hóa bất kỳ INSERTDELETEkích hoạt trên bảng.

Nếu tạo lại bảng là một tùy chọn:

Nếu việc tạo lại bảng là một tùy chọn cho bạn, mọi thứ sẽ đơn giản hơn rất nhiều:

  • Tạo bảng trống, với idcột là một IDENTITY,
  • Đặt IDENTITY_INSERT ONcho bảng,
  • Đặt bàn
  • Đặt IDENTITY_INSERT OFF
  • Thay đổi danh tính.

Câu trả lời tuyệt vời, cảm ơn rất nhiều! Thật vậy, trong trường hợp của tôi, tôi chỉ có thể thiết lập IDENTITY_INSERT ON, điền và vô hiệu hóa nó. Đó là những gì tôi muốn làm, nhưng không biết MSSQL đã hỗ trợ nó.
Hikari

5

Sử dụng CẬP NHẬT, XÓA hoặc XÁC NHẬN để di chuyển dữ liệu có thể mất khá nhiều thời gian và sử dụng tài nguyên (IO) trên cả dữ liệu và tệp nhật ký / đĩa. Có thể tránh điền vào nhật ký giao dịch với rất nhiều bản ghi trong khi làm việc trên một bảng lớn: Sử dụng chuyển đổi phân vùng, chỉ có siêu dữ liệu được thay đổi.

Không có chuyển động dữ liệu liên quan và do đó điều này được thực hiện rất nhanh (gần như tức thời).

Bảng mẫu

Câu hỏi không hiển thị bảng gốc DDL. DDL sau đây sẽ được sử dụng làm ví dụ trong câu trả lời này:

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

Một nửa tá id ngẫu nhiên giả từ 0 đến 15 được thêm vào với truy vấn này:

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

Dữ liệu ví dụ trong 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

Bảng mới với IDENTITY(0, 1)

Vấn đề duy nhất idTlà thiếu IDENTITY(0, 1)tài sản trên id. Một bảng mới có cấu trúc tương tự và IDENTITY(0, 1)được tạo:

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

Ngoài ra IDENTITY(0, 1),idT_Switch là giống hệt với idT.

Khóa ngoại

Bật khóa ngoại idTPhải để cho phép kỹ thuật này được sử dụng.

Chuyển đổi phân vùng

Các bảng idTidT_Switchcó một cấu trúc tương thích. Thay vì sử dụng DELETE, UPDATEINSERTbáo cáo để di chuyển hàng từ idTđể idT_Switchhoặc trên idTchính nó, ALTER TABLE ... SWITCHcó thể được sử dụng:

ALTER TABLE dbo.idT
SWITCH TO dbo.idT_Switch;

'Phân vùng' duy nhất của PK_idT(toàn bộ bảng) được chuyển đến PK_idT_Switch(và ngược lại). idTbây giờ chứa 0 hàng và idT_Switchchứa 6 hàng.

Bạn có thể tìm thấy danh sách đầy đủ các yêu cầu tương thích nguồn và đích ở đây:

Truyền dữ liệu hiệu quả bằng cách sử dụng chuyển đổi phân vùng

Lưu ý việc sử dụng này của SWITCH này không yêu cầu Enterprise Edition, vì không có phân vùng rõ ràng. Một bảng không liên kết được coi là một bảng có một phân vùng duy nhất từ ​​SQL Server 2005 trở đi.

Thay thế idT

idT bây giờ trống rỗng và vô dụng và có thể được bỏ:

DROP TABLE idT;

idT_Switchcó thể đổi tên và sẽ thay thế idTbảng cũ :

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

Khóa ngoại

Khóa ngoại có thể được thêm một lần nữa vào idTbảng mới . Bất cứ điều gì khác được xóa trước đó idTđể làm cho các bảng tương thích để chuyển đổi cũng sẽ cần phải được làm lại.

Hạt cải

SELECT IDENT_CURRENT( 'dbo.idT');

Lệnh này trả về 0. Bảng idT chứa 6 hàng với MAX (id) = 15. DBCC CHECKIDENT (tên_bảng) có thể được sử dụng:

DBCC CHECKIDENT ('dbo.idT');

Vì 15 lớn hơn 0, nó sẽ tự động quay lại mà không cần tìm MAX (id):

Nếu giá trị nhận dạng hiện tại cho một bảng nhỏ hơn giá trị nhận dạng tối đa được lưu trữ trong cột định danh, thì nó được đặt lại bằng cách sử dụng giá trị tối đa trong cột định danh. Xem phần 'Ngoại lệ' theo sau.

IDENT_CURRENT hiện trả về 15 .

Kiểm tra và thêm dữ liệu

Một INSERTtuyên bố đơn giản :

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

Thêm hàng này:

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

Các idcột hiện đang sử dụng bản sắc và giá trị mới được chèn thực sự là 16 (15 + 1).

Thêm thông tin

Có một câu hỏi và câu trả lời liên quan với nhiều nền tảng về SWITCHkỹ thuật ở đây:

Tại sao xóa thuộc tính Danh tính trên một cột không được hỗ trợ


4

Nếu bạn muốn bắt đầu với một giá trị nhận dạng mới, bạn cần phải xác định lại danh tính của mình. Có một cái nhìn vào các tài liệu choCHECKIDENT

DBCC CHECKIDENT (yourtable, reseed, starting point)

0

ENABLE và DISABLE IDENTITY_INSERT

Nếu bảng của bạn là TABLE_A thì

  1. TẠO TABLE TABLE_B tương tự TABLE_A với cột định danh
  2. THIẾT LẬP IDENTITY_INSERT TABLE_B ON
  3. XÁC NHẬN vào TABLE_B từ TABLE_A
  4. THIẾT LẬP IDENTITY_INSERT TABLE_B TẮT
  5. DROP TABLE TABLE_A và đổi tên bảng B Exec sp_rename 'TABLE_B', 'TABLE_A'
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.