Разрешает ли 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;

Однако то же самое в Microsoft SQL Server, похоже, вызывает ошибку. Сравните эту рабочую базу данных , где команда 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;

на эту скрипку БД, которая не работает,

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

Пол Уайт 9
источник
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.
  • Это булева функция, которая позволяет сложные проверки в одном выражении
clifton_h
источник
Нет, вы не можете вставить в новый столбец, если вы делаете это в том же пакете, где вы создаете столбец. В более общем смысле, вы не можете статически ссылаться на новые столбцы в том же пакете после того, как вы только что создали его / их. Прием IF EXISTS в этом случае не работает. Либо вызовите DML динамически, либо сделайте это в другом пакете.
Андрей М
@AndriyM извините, неправильно сделал заявление о dbfiddle. Но вы пробовали это на своем экземпляре? Работает на 2017 SP1. Я загрузлю GIF, но вы проверяли это на своих системах?
clifton_h
i.imgur.com/fhAC7lB.png Вы действительно можете сказать, что он не будет компилироваться на основе волнистой линии bв операторе вставки. Я использую SQL Server 2014
Андрей М,
@AndriyM интересно. Я видел, как эта аффект работала раньше, и, похоже, она работает на некоторых версиях SQL Server, таких как SQL Server 2017, о котором я упоминал.
clifton_h
@AndriyM см. Новое редактирование к сообщению
clifton_h