#sql #sql-server #tsql #triggers
Вопрос:
Я пытаюсь выполнить следующие 3 простые задачи в качестве транзакции (т. Е. Мне нужно заблокировать old_table и new_table до завершения процесса).
- Создайте новую таблицу (new_table)
- Добавьте триггер в old_table, который ставит обновления в очередь в new_table.
- Выберите все данные из 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. Вот тут-то у меня и возникают проблемы.