Как выполнить эти три действия в транзакции SQL Server — 1. создайте таблицу, 2.создайте триггер в таблице, 3. выберите из другой таблицы

#sql #sql-server #tsql #triggers

Вопрос:

Я пытаюсь выполнить следующие 3 простые задачи в качестве транзакции (т. Е. Мне нужно заблокировать old_table и new_table до завершения процесса).

  1. Создайте новую таблицу (new_table)
  2. Добавьте триггер в old_table, который ставит обновления в очередь в new_table.
  3. Выберите все данные из old_table и верните их.

Обратите внимание, что я хочу, чтобы они обрабатывались в одной транзакции. Я не могу разрешить вставки в old_table (и, следовательно, инициированные вставки в new_table) между созданием триггера и выбором в old_table.

Моя нынешняя ближайшая попытка заключается в следующем, но, честно говоря, я чувствую, что очень далек от достижения своей цели с помощью этого кода. Я добавил код только для справки о том, что я пытаюсь, но в основном меня интересуют неспецифические ответы, в которых описывается, как выполнить три вышеуказанные команды в транзакции.

 DROP PROCEDURE IF EXISTS dbo.BuildAll;

CREATE PROCEDURE dbo.BuildAll
AS
BEGIN
    BEGIN TRANSACTION
    DECLARE @TriggerCode VARCHAR(MAX)
   
    CREATE TABLE dbo.new_table
    (
        status nvarchar(5),
        type   char(1),
        col1   nvarchar(50),
        col2   smallint
    )

    SELECT @TriggerCode = 'CREATE TRIGGER myTrigger
                           ON dbo.old_table FOR INSERT  
                           AS
                              DECLARE @col1_new nvarchar(50)
                              DECLARE @col2_new  smallint 
    
                              SELECT @col1_new = col1 FROM inserted
                              SELECT @col2_new = col2 FROM inserted
        
                              IF @col1_new IS NOT NULL
                              BEGIN
                                  INSERT INTO new_table (status, type, col1, col2)
                                  SELECt "Q", "A", @col1, @col2 FROM inserted
                              END'
       
    EXEC(@TriggerCode)
  
    SELECT * FROM old_table

    COMMIT
END
 

Комментарии:

1. У вашего триггера есть СЕРЬЕЗНЫЙ недостаток в том, что вы, похоже, предполагаете, что он будет вызываться один раз за строку — это не так. Триггер срабатывает один раз для каждого оператора , поэтому, если ваш INSERT триггер, который вызывает срабатывание этого триггера, вставляет 25 строк, вы получите срабатывание триггера один раз , и Inserted псевдотаблица будет содержать 25 строк. Из каких из этих 25 строк будет выбран ваш код Inserted ? Это недетерминировано, вы получите одну произвольную строку и будете игнорировать все остальные строки . Вам нужно переписать свой триггер, чтобы учесть это!

2. Это, честно говоря, похоже на решение проблемы, которая, вероятно, решена каким-то гораздо лучшим способом, возможно, с использованием функций, о которых вы еще не знаете. Если вы пытаетесь создать некоторый стабильный по времени моментальный снимок данных, рассмотрите временные таблицы или изоляцию моментальных снимков; если вы хотите атомарно переключать таблицы, посмотрите ALTER TABLE SWITCH или выполните sp_rename транзакцию; если вам нужно реплицировать данные, рассмотрите репликацию транзакций. Я не могу придумать ни одного сценария, в котором описанный выше подход с триггером был бы необходим или желателен.

3. Это выглядит странно, возможно, это проблема XY . Объясните, что вы пытаетесь сделать в качестве обзора.

4. @Stu Это не проблема XY. Что касается постановки проблемы, я отредактировал ее в надежде внести ясность.

5. @ДалеК, я приношу свои извинения. Я подозреваю, что редактировал в то же время, когда вы внесли изменения. Теперь моя голова немного остыла, и я упростил вопрос. Я надеюсь, что это поможет.

Ответ №1:

Собираюсь предложить вам возможное решение, которое вы можете попробовать. Это не касается правильности вашего фактического триггера, на самом деле у вас есть два отдельных вопроса.

Вам не нужно инкапсулировать весь этот процесс в транзакцию.

  • Создайте свою новую таблицу.
  • Создайте свой триггер на старой таблице, но отключенный.
 set transaction isolation level serializable
begin tran
go
create trigger <Name> on <Table> etc
go
disable trigger <Name> on <Table>
go
commit
 
  • Теперь в транзакции вы можете заблокировать старую таблицу от других действий во время работы
 begin tran

update oldtable with(tablockx) set column=column where id=0 /* block other processes from updating table, id=0 row doesn't exist */

query your data and process as required

enable trigger <Name> on <Table>

commit
 

Комментарии:

1. Эта стратегия хорошо работала, когда я запускал ее из dbeaver, однако я все еще получаю следующую ошибку при попытке передать вышеприведенное в виде строки из C#: «СОЗДАТЬ ТРИГГЕР»должен быть первым оператором в пакете запросов». Тем не менее, ваше решение работает хорошо, и я думаю, что оно должно быть помечено правильно для тех, кто заглядывает в будущее.

Ответ №2:

Этот твой триггерный код довольно странный …. у вас есть триггер для всех трех операций — и все же кажется, что вы никогда не используете значения , которые вы извлекаете из deleted псевдотаблицы, и если значение из inserted таблицы таково NULL , вы ничего не делаете внутри своего триггера — так что вы действительно можете избавить себя от этого DELETE случая — это никогда ничего не сделает….

Кроме того, как уже упоминалось в моем комментарии, ваша Inserted псевдотаблица может легко содержать несколько строк, но вы выбираете из нее так, как будто вы когда — либо ожидали, что она будет содержать только одну строку.

Вам действительно следует переписать свой код триггера, чтобы обработать несколько строк Inserted и сделать все правильно настроенным-что — то вроде этого:

 CREATE TRIGGER myTrigger
ON dbo.old_table 
FOR INSERT, UPDATE
AS
    INSERT INTO new_table (status, type, col1, col2)
        SELECT 'Q', 'A', i.col1, i.col2
        FROM Inserted i
 

Нужно ли вам это UPDATE вообще по делу — я не могу сказать, вам нужно это решить. Но в принципе: просто выберите из Inserted таблицы, возьмите Col1 значения Col2 и и добавьте постоянные значения 'Q' и 'A' в свою вставку, чтобы правильно обрабатывать несколько строк. Этого должно хватить.

Комментарии:

1. удалил неиспользуемые значения в соответствии с вашим комментарием, но оставил, что я на самом деле буду использовать логику для всех трех сценариев (ВСТАВКА, ОБНОВЛЕНИЕ, УДАЛЕНИЕ), но для простоты я теперь включаю только добавление в приведенный выше код. С чем я борюсь, так это с тем, почему я не могу создать транзакцию, которая включает создание триггера с помощью инструкции table create и select. Вот тут-то у меня и возникают проблемы.