SQL Server có cho phép (hiển thị) DDL bên trong một giao dịch đối với giao dịch trước khi cam kết không?


9

Trong PostgreSQL tôi có thể tạo một bảng với một số dữ liệu thử nghiệm và sau đó trong một giao dịch di chuyển nó sang một cột mới thuộc loại khác dẫn đến một bảng được viết lại COMMIT,

CREATE TABLE foo ( a int );
INSERT INTO foo VALUES (1),(2),(3);

Theo dõi bởi,

BEGIN;
  ALTER TABLE foo ADD COLUMN b varchar;
  UPDATE foo SET b = CAST(a AS varchar);
  ALTER TABLE foo DROP COLUMN a;
COMMIT;

Tuy nhiên, điều tương tự trong SQL Server của Microsoft dường như tạo ra lỗi. So sánh fiddle db làm việc này , trong đó lệnh ADD(cột) nằm ngoài giao dịch,

-- txn1
BEGIN TRANSACTION;
  ALTER TABLE foo ADD b varchar;
COMMIT;

-- txn2
BEGIN TRANSACTION;
  UPDATE foo SET b = CAST( a AS varchar );
  ALTER TABLE foo DROP COLUMN a;
COMMIT;

với fiddle db này không hoạt động,

-- txn1
BEGIN TRANSACTION;
  ALTER TABLE foo ADD b varchar;
  UPDATE foo SET b = CAST( a AS varchar );
  ALTER TABLE foo DROP COLUMN a;
COMMIT;

Nhưng thay vào đó là lỗi

Msg 207 Level 16 State 1 Line 2
Invalid column name 'b'.

Có cách nào để làm cho giao dịch này hiển thị, liên quan đến DDL, hoạt động như PostgreSQL không?

Câu trả lời:


17

Nói chung là không. SQL Server biên dịch toàn bộ ở phạm vi hiện tại trước khi thực hiện để các thực thể được tham chiếu phải tồn tại (biên dịch lại mức câu lệnh cũng có thể xảy ra sau này). Ngoại lệ chính là Độ phân giải tên hoãn lại nhưng áp dụng cho các bảng, không phải các cột:

Độ phân giải tên hoãn lại chỉ có thể được sử dụng khi bạn tham chiếu các đối tượng bảng không tồn tại. Tất cả các đối tượng khác phải tồn tại tại thời điểm thủ tục lưu trữ được tạo. Ví dụ: khi bạn tham chiếu một bảng hiện có trong một thủ tục được lưu trữ, bạn không thể liệt kê các cột không tồn tại cho bảng đó.

Cách giải quyết chung liên quan đến mã động (như trong câu trả lời của Joe ) hoặc tách DML và DDL thành các lô riêng biệt.

Đối với trường hợp cụ thể này, bạn cũng có thể viết:

BEGIN TRANSACTION;

    ALTER TABLE dbo.foo
        ALTER COLUMN a varchar(11) NOT NULL
        WITH (ONLINE = ON);

    EXECUTE sys.sp_rename
        @objname = N'dbo.foo.a',
        @newname = N'b',
        @objtype = 'COLUMN';

COMMIT TRANSACTION;

Bạn vẫn sẽ không thể truy cập cột được đổi tên btrong cùng một lô và phạm vi, nhưng nó sẽ hoàn thành công việc.

Đối với SQL Server, có một trường phái cho rằng việc trộn DDL và DML trong một giao dịch không phải là một ý tưởng tuyệt vời. Trước đây đã có những lỗi xảy ra khi việc này dẫn đến việc ghi nhật ký không chính xác và cơ sở dữ liệu không thể phục hồi. Tuy nhiên, mọi người làm điều đó, đặc biệt là với các bảng tạm thời. Nó có thể dẫn đến một số mã khá khó theo dõi.


12

Đây có phải là những gì bạn đang tìm kiếm?

BEGIN TRANSACTION;
  ALTER TABLE foo ADD b varchar;
  EXEC sp_executesql N'UPDATE foo SET b = CAST( a AS varchar )';
  ALTER TABLE foo DROP COLUMN a;
COMMIT;

2

Đối với tuyên bố "nói chung là không" về câu trả lời của Paul White, tôi hy vọng sẽ đưa ra câu trả lời trực tiếp cho câu hỏi nhưng cũng cho thấy những hạn chế mang tính hệ thống của quy trình đó và giúp bạn tránh xa các phương pháp không cho vay dễ dàng quản lý và phơi bày rủi ro.

thể đề cập nhiều lần để không làm thay đổi DDL giống như khi bạn tạo DML. Lập trình tốt tách các chức năng này để duy trì khả năng hỗ trợ và tránh thay đổi chuỗi spaghetti.

Và như Paul đã chỉ ra một cách ngắn gọn, SQL Server hoạt động theo từng đợt .

Bây giờ, đối với những người nghi ngờ điều này hoạt động, có thể nó không có trong trường hợp của bạn nhưng một số phiên bản như 2017 nó thực sự có thể hoạt động! Đây là bằng chứng: nhập mô tả hình ảnh ở đây

[MÃ TEST - CÓ THỂ không hoạt động trên nhiều phiên bản SQL Server]

USE master
GO
CREATE TABLE foo (a VARCHAR(11) )
GO
BEGIN TRANSACTION;
    INSERT INTO dbo.foo (a)
    VALUES ('entry')
/*****
[2] Check Values
*****/
    SELECT a FROM dbo.foo
/*****
[3] Add Column
*****/
    ALTER TABLE dbo.foo
        ADD b VARCHAR(11)
/*****
[3] Insert value into this new column in the same batch
-- Again, this is just an example. Please do not do this in production
*****/
    IF EXISTS (SELECT * FROM sys.columns WHERE object_ID('foo') = object_id
            AND name = 'b')
        INSERT INTO dbo.foo (b)
        VALUES ('d')
COMMIT TRANSACTION;
/*****
[4] SELECT outside transaction
-- this will fail
*****/
    --IF EXISTS (SELECT * FROM sys.columns WHERE object_ID('foo') = object_id
    --      AND name = 'b')
    --  SELECT b FROM dbo.foo
-- this will work...but a SELECT * ???
IF EXISTS (SELECT * FROM sys.columns WHERE object_ID('foo') = object_id
            AND name = 'b')
        SELECT * FROM dbo.foo

DROP TABLE dbo.foo

[PHẦN KẾT LUẬN]

Vì vậy, có, bạn có thể thực hiện DDL và DML trong cùng một đợt cho các phiên bản hoặc bản vá nhất định của SQL Server như @AndriyM - dbfiddle trên SQL 2017 chỉ ra, nhưng không phải tất cả DML đều được hỗ trợ và không có gì đảm bảo điều này sẽ luôn như vậy. Nếu nó hoạt động, đó có thể là một quang sai của phiên bản SQL Server của bạn và điều này có thể gây ra sự cố nghiêm trọng khi bạn vá hoặc di chuyển sang các phiên bản mới.

  • Thêm vào đó, nói chung thiết kế của bạn nên lường trước những thay đổi. Tôi hiểu các mối quan tâm của việc sửa đổi / thêm các cột có thể có trên một bảng, nhưng bạn có thể thiết kế chính xác xung quanh điều này theo từng đợt.

[TÍN DỤNG THÊM]

Đối với tuyên bố EXISTS, như Paul đã nêu, có rất nhiều phương tiện khác để xác thực mã trước khi chuyển sang bước tiếp theo trong mã của bạn.

  • Câu lệnh EXISTS có thể giúp bạn tạo mã hoạt động trên tất cả các phiên bản của SQL Server
  • Đây là một hàm Boolean cho phép kiểm tra phức tạp trong một câu lệnh

Không, bạn không thể chèn vào cột mới nếu bạn đang thực hiện việc này trong cùng một đợt mà bạn đang tạo cột. Tổng quát hơn, bạn không thể tham chiếu tĩnh (các) cột mới trong cùng một đợt sau khi bạn vừa tạo nó / chúng. Thủ thuật IF EXISTS không hoạt động trong trường hợp này. Hoặc gọi DML một cách linh hoạt hoặc thực hiện nó trong một đợt khác.
Andriy M

@AndriyM xin lỗi, đã đưa ra tuyên bố không chính xác về dbfiddle. Nhưng bạn đã thử điều này trên Instance của bạn? Nó hoạt động trên 2017 SP1. Tôi sẽ tải lên một gif, nhưng bạn đã kiểm tra điều này trên hệ thống của bạn chưa?
clifton_h

i.imgur.com/fhAC7lB.png Bạn thực sự có thể nói rằng nó sẽ không được biên dịch dựa trên đường lượn sóng bên dưới btrong câu lệnh chèn. Tôi đang sử dụng SQL Server 2014
Andriy M

@AndriyM thú vị. Tôi đã thấy điều này ảnh hưởng đến công việc trước đây và nó dường như hoạt động trên một số phiên bản của SQL Server, như SQL Server 2017 tôi đã đề cập.
clifton_h

@AndriyM xem chỉnh sửa mới để đăng bài
clifton_h
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.