Чи дозволяє 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 , де команда 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 компілює всю партію в поточному просторі перед виконанням, щоб згадані об'єкти повинні існувати (перекомпіляції на рівні висловлювань можуть також відбуватися пізніше). Основним винятком є розділене дозвіл імені, але це стосується таблиць, а не стовпців:

Відкладене дозвіл імен можна використовувати лише тоді, коли ви посилаєтесь на неіснуючі об’єкти таблиці. Усі інші об'єкти повинні існувати в момент створення збереженої процедури. Наприклад, коли ви посилаєтесь на існуючу таблицю у збереженій процедурі, ви не можете перелічити неіснуючі стовпці для цієї таблиці.

Загальні обхідні шляхи включають динамічний код (як в Джо відповіді ) або відокремлюючи 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

На твердження "взагалі ні" на відповідь Пола Уайта, я сподіваюсь, що надає пряму відповідь на питання, але також слугує для відображення системних обмежень такого процесу і відхиляє вас від методів, які не піддаються простому управлінню та не піддаються впливу ризики.

Це може бути згадано багато разів , щоб не зробити DDL зміни в той же час ви робите DML. Хороше програмування розділяє ці функції, щоб підтримувати підтримку та уникати змін в рядах спагетті.

І як Лаконічно зазначив Пол, SQL Server працює партіями .

Тепер, для тих, хто сумнівається в цьому, це, мабуть, не у вашому випадку, але в деяких версіях, таких як 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, то, як заявив Павло, існує багато інших засобів для перевірки коду, перш ніж перейти до наступного кроку вашого коду.

  • Оператор EXISTS може допомогти вам створити код, який працює на всіх версіях SQL Server
  • Це булева функція, яка дозволяє здійснювати складні перевірки в одному операторі

Ні, ви не можете вставити в новий стовпець, якщо ви робите це в тій же партії, де ви створювали стовпець. Більш загально, ви не можете статично посилатися на нові стовпці в тій же партії після того, як ви її створили. Трюк IF EXISTS в цьому випадку не працює. Або викликайте DML динамічно, або зробіть це в іншій партії.
Андрій М

@AndriyM вибачте, неправильно зробив заяву про dbfiddle. Але ви спробували це на своєму інстанції? Він працює на SP1 2017 року. Я завантажу gif, але ви тестували це на своїх системах?
clifton_h

i.imgur.com/fhAC7lB.png Насправді ви можете сказати, що він не буде компілюватися на основі хвилястого рядка, зазначеного bв операторі вставки. Я використовую SQL Server 2014
Андрій М

@AndriyM цікаво. Я раніше бачив, як це впливає на роботу, і, здається, працює на деяких версіях SQL Server, як-от на SQL Server 2017, про який я згадував.
clifton_h

@AndriyM дивись нову редакцію до публікації
clifton_h
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.