SQL Server อนุญาต (ทำให้มองเห็นได้) DDL ภายในธุรกรรมไปยังธุรกรรมก่อนที่จะส่งมอบหรือไม่?


9

ใน PostgreSQL ฉันสามารถสร้างตารางที่มีข้อมูลการทดสอบบางส่วนและจากนั้นในการทำธุรกรรมโยกย้ายไปยังคอลัมน์ใหม่ของรูปแบบที่แตกต่างกันส่งผลให้ในหนึ่งตารางเขียนเมื่อCOMMIT,

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

ติดตามโดย,

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

อย่างไรก็ตามสิ่งเดียวกันใน SQL Server ของ Microsoft ดูเหมือนจะสร้างข้อผิดพลาด เปรียบเทียบซอ db db ที่ใช้งานได้โดยที่ADDคำสั่ง (คอลัมน์) อยู่นอกธุรกรรม

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

ต่อซอ db ตัวนี้ที่ใช้งานไม่ได้

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

แต่แทนที่จะเกิดข้อผิดพลาด

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

อย่างไรก็ตามมีการทำธุรกรรมนี้ปรากฏสำหรับ DDL ทำตัวเหมือน PostgreSQL?

คำตอบ:


17

โดยทั่วไปแล้วไม่มี SQL Server คอมไพล์ชุดทั้งหมดที่ขอบเขตปัจจุบันก่อนการดำเนินการเพื่อให้เอนทิตีอ้างอิงต้องมีอยู่ (recompilations ระดับคำสั่งอาจเกิดขึ้นในภายหลัง) ข้อยกเว้นหลักคือการแก้ไขชื่อที่เลื่อนออกไปแต่ใช้กับตารางไม่ใช่คอลัมน์:

การจำแนกชื่อแบบผ่อนผันสามารถใช้ได้เมื่อคุณอ้างอิงวัตถุตารางที่ไม่มีอยู่เท่านั้น วัตถุอื่น ๆ ทั้งหมดจะต้องมีอยู่ในเวลาที่กระบวนการจัดเก็บถูกสร้างขึ้น ตัวอย่างเช่นเมื่อคุณอ้างอิงตารางที่มีอยู่ในกระบวนงานที่เก็บไว้คุณจะไม่สามารถแสดงรายการคอลัมน์ที่ไม่มีอยู่สำหรับตารางนั้น

วิธีแก้ปัญหาทั่วไปเกี่ยวข้องกับรหัสแบบไดนามิก (เช่นเดียวกับคำตอบของโจ) หรือแยก DML และ DDL ออกเป็นแบทช์แยกต่างหาก

สำหรับกรณีเฉพาะนี้คุณสามารถเขียน:

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ในแบทช์และขอบเขตเดียวกันได้ แต่จะทำให้งานเสร็จ

เกี่ยวกับ SQL Server มีโรงเรียนแห่งความคิดที่กล่าวว่าการผสม DDL และ DML ในการทำธุรกรรมไม่ใช่ความคิดที่ดี มีข้อบกพร่องในอดีตที่การทำเช่นนี้ส่งผลให้มีการบันทึกที่ไม่ถูกต้องและฐานข้อมูลที่ไม่สามารถกู้คืนได้ อย่างไรก็ตามคนทำมันโดยเฉพาะอย่างยิ่งกับตารางชั่วคราว มันอาจส่งผลให้บางรหัสยากต่อการติดตาม


12

นี่คือสิ่งที่คุณกำลังมองหา?

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

สำหรับคำสั่ง "โดยทั่วไป" ในคำตอบของ Paul White ฉันหวังว่าต่อไปนี้จะเสนอคำตอบโดยตรงกับคำถาม แต่ยังทำหน้าที่แสดงข้อ จำกัด ที่เป็นระบบของกระบวนการดังกล่าวและพาคุณออกไปจากวิธีการที่ไม่ให้ยืมง่าย ความเสี่ยง

มันสามารถจะกล่าวถึงหลายครั้งที่จะไม่ทำให้ DDL เปลี่ยนแปลงในเวลาเดียวกันคุณจะทำให้ดราก้อน การเขียนโปรแกรมที่ดีจะแยกหน้าที่เหล่านี้เพื่อรักษาความสามารถในการรองรับและหลีกเลี่ยงการเปลี่ยนแปลงของสปาเก็ตตี้

และขณะที่พอลชัดถ้อยชัดคำชี้ SQL Server ทำงานในbatches

ตอนนี้สำหรับผู้ที่สงสัยว่างานนี้อาจไม่ได้อยู่ในอินสแตนซ์ของคุณ แต่บางรุ่นเช่น 2017 สามารถใช้งานได้จริง! นี่คือหลักฐาน: ป้อนคำอธิบายรูปภาพที่นี่

[รหัสการทดสอบ - อาจไม่ทำงานกับ 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

[สรุป]

ดังนั้นใช่คุณสามารถดำเนินการ DDL และ DML ในชุดเดียวกันสำหรับบางรุ่นหรือแพทช์ของ SQL Server เป็น@AndriyM - dbfiddle บน SQL 2017ชี้ให้เห็น แต่ไม่รองรับ DML ทั้งหมดและไม่มีการรับประกันนี้จะเป็นกรณีเสมอ หากใช้งานได้นั่นอาจเป็นความผิดปกติของ SQL Server รุ่นของคุณและอาจทำให้เกิดปัญหาอย่างมากเมื่อคุณแก้ไขหรือโอนย้ายเป็นเวอร์ชันใหม่

  • นอกจากนี้โดยทั่วไปการออกแบบของคุณควรคาดการณ์การเปลี่ยนแปลง ฉันเข้าใจถึงความกังวลของการแก้ไข / เพิ่มคอลัมน์ที่สามารถมีได้ในตาราง แต่คุณสามารถออกแบบได้อย่างเหมาะสมในแบทช์

[สินเชื่อพิเศษ]

สำหรับคำสั่ง EXISTS ดังที่ Paul ได้กล่าวไว้มีวิธีการมากมายในการตรวจสอบรหัสก่อนที่จะไปยังขั้นตอนถัดไปในรหัสของคุณ

  • คำสั่ง EXISTS สามารถช่วยคุณสร้างรหัสที่ใช้ได้กับ SQL Server ทุกรุ่น
  • มันเป็นฟังก์ชั่นบูลีนที่ช่วยให้การตรวจสอบที่ซับซ้อนในงบเดียว

ไม่คุณไม่สามารถแทรกลงในคอลัมน์ใหม่ได้หากคุณทำสิ่งนี้ในชุดเดียวกันกับที่คุณกำลังสร้างคอลัมน์ โดยทั่วไปคุณไม่สามารถอ้างอิงคอลัมน์ใหม่แบบคงที่ในชุดเดียวกันหลังจากที่คุณเพิ่งสร้างมัน / พวกเขา เคล็ดลับ IF EXISTS ใช้ไม่ได้ในกรณีนี้ เรียกใช้ DML แบบไดนามิกหรือทำในแบทช์อื่น
Andriy M

@AndriyM ขออภัยทำให้คำสั่งเกี่ยวกับ dbfiddle ไม่ถูกต้อง แต่คุณลองสิ่งนี้ในอินสแตนซ์ของคุณหรือไม่? ใช้งานได้กับ 2017 SP1 ฉันจะอัพโหลด gif แต่คุณทดสอบกับระบบของคุณหรือไม่?
clifton_h

i.imgur.com/fhAC7lB.pngคุณสามารถบอกได้ว่ามันจะไม่คอมไพล์ตามบรรทัดหยักใต้bในคำสั่งแทรก ฉันใช้ SQL Server 2014
Andriy M

@AndriyM ที่น่าสนใจ ฉันเคยเห็นสิ่งนี้ส่งผลกระทบต่องานก่อนหน้านี้และดูเหมือนว่าจะทำงานกับ SQL Server บางเวอร์ชันเช่น SQL Server 2017 ที่ฉันพูดถึง
clifton_h

@AndriyM ดูการแก้ไขใหม่เพื่อโพสต์
clifton_h
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.