SQL Server: триггер удаления не работает - PullRequest
0 голосов
/ 07 мая 2018

Ниже таблица Country:

+------------------+-----------+
|   COLUMN_NAME    | DATA_TYPE |
+------------------+-----------+
| ID               | int       |
| Name             | varchar   |
| CapitalCity      | varchar   |
| Population       | int       |
| OfficialReligion | varchar   |
| OfficialLanguage | varchar   |
| DateEntered      | datetime  |
+------------------+-----------+

У меня есть еще одна очень похожая таблица с именем CountryRecord, в которой есть те же столбцы, а затем еще две новые:

+-------------------------+-----------+
|   COLUMN_NAME           | DATA_TYPE |
+-------------------------+-----------+
| ID                      | int       |
| OriginalID              | int       |
| Name                    | varchar   |
| CapitalCity             | varchar   |
| Population              | int       |
| OfficialReligion        | varchar   |
| OfficialLanguage        | varchar   |
| DateEntered             | datetime  |
| QueryType               | varchar   |
+-------------------------+-----------+

При вставке, обновлении или удалении данных в Country вызывается триггер, который также вставляет строки в CountryRecord с целью записи того, какой запрос был сделан для какой строки данных. Триггер выглядит следующим образом:

ALTER TRIGGER [dbo].[CountryRecord_Trigger]
ON [dbo].[Country]
AFTER UPDATE, INSERT, DELETE
AS
    DECLARE @CountryID INT, @queryType VARCHAR(20);

    --Update trigger
    IF EXISTS (SELECT * FROM inserted) AND EXISTS (SELECT * FROM deleted)
    BEGIN
        SET @queryType = 'UPDATE';

        SELECT @CountryID = ID FROM inserted i;

        INSERT INTO CountryRecord (OriginalID, Name, CapitalCity, Population, OfficialReligion, OfficialLanguage, DateEntered, QueryType) 
            SELECT  
                ID, Name, CapitalCity, Population, OfficialReligion, 
                OfficialLanguage, DateEntered, @queryType 
            FROM
                Country 
            WHERE
                @CountryID = ID;
    END

    --Insert trigger
    IF EXISTS (SELECT * FROM inserted) AND NOT EXISTS (SELECT * FROM deleted)       
    BEGIN
        SET @queryType = 'INSERT';

        SELECT @CountryID = ID FROM inserted i;

        INSERT INTO CountryRecord (OriginalID, Name, CapitalCity, Population, OfficialReligion, OfficialLanguage, DateEntered, QueryType) 
            SELECT 
                ID, Name, CapitalCity, Population, OfficialReligion,
                OfficialLanguage, DateEntered, @queryType 
            FROM
                Country 
            WHERE
                @CountryID = ID;
     END

     --Delete trigger
     IF NOT EXISTS (SELECT * FROM inserted) AND EXISTS (SELECT * FROM deleted)
     BEGIN
         SET @queryType = 'DELETE';

         SELECT @CountryID = ID FROM deleted d;

         INSERT INTO CountryRecord (OriginalID, Name, CapitalCity, Population, OfficialReligion, OfficialLanguage, DateEntered, QueryType) 
             SELECT 
                 ID, Name, CapitalCity, Population, OfficialReligion,
                 OfficialLanguage, DateEntered, @queryType 
             FROM
                 Country 
             WHERE
                 @CountryID = ID;
     END

Однако, хотя триггеры вставки и обновления работают, триггер удаления не работает. Когда я запускаю запрос на удаление, я выбираю только одну строку, поэтому я вполне уверен, что запрос на удаление не выбирает более одной строки (вместо этого, похоже, что он ничего не выбирает, так как делает проверку текущей идентичности CountryRecord после выполнения запроса на удаление видно, что он не увеличился на единицу).

Обновление: Изменение триггера удаления на использование ELSE вместо выполнения набора условий не решает проблему. Вместо этого любые запросы на обновление будут вызывать триггер обновления и удаления, в то время как запросы на удаление по-прежнему не действуют.

Обновление 2: Просто чтобы убедиться, что я воздействовал только на одну строку, я добавил в запросы на выборку для каждого триггера, а также вывод @CountryID - запросы на выборку показали только 1 строку, в то время как @CountryID не отображается как ноль / пусто. Я также пытался изменить столбцы, думая, что ограничение NOT NULL оказало какое-либо влияние, но это не так.

Обновление 3: У меня теперь работает Delete, хотя он работает только для удаления одной строки. Многострочные удаления не работают должным образом - если, например, я удаляю две записи, в таблицу аудита попадает только одна строка. Я также обновил «Вставить и обновить», чтобы использовать аналогичную структуру кода, чтобы они также имели одну и ту же проблему с несколькими строками. Фрагмент кода для удаляемой части:

SET @queryType = 'DELETE';
SELECT CountryID = ID FROM deleted;
INSERT INTO CountryRecord (OriginalID, Name, CapitalCity, Population, OfficialReligion, OfficialLanguage, DateEntered, QueryType) 
    SELECT ID, Name, CapitalCity, Population, OfficialReligion, OfficialLanguage, DateEntered, @queryType FROM deleted WHERE @CountryID = ID;
DECLARE @auditID int;
SET @auditID = SCOPE_IDENTITY();
UPDATE CountryRecord SET QueryType = @queryType WHERE ID = @auditID;

1 Ответ

0 голосов
/ 07 мая 2018

Причина, по которой ваше удаление не работает, заключается в том, что запись уже была удалена из таблицы country. Так что вы не можете выбрать его оттуда.

Вам нужно получить его из таблицы deleted.

Два других ваших запроса также ссылаются на таблицы Country в триггере, что является плохой идеей. Вы не должны использовать исходную таблицу в своем запросе - вы должны использовать inserted и deleted.

Подумайте, как можно написать один запрос из вставленных и удаленных таблиц, который выполняет все, что вы хотите, в одном запросе, независимо от того, сколько записей вставлено и удалено. Если вы не оставите этот вопрос (это часто случается), я добавлю пример к этому ответу.

Вот запрос, который сохраняет ваши вставки и обновления за один раз:

INSERT INTO CountryRecord (
 Name, 
 CapitalCity, 
 Population, 
 OfficialReligion, 
 OfficialLanguage, 
 DateEntered, 
 QueryType)
SELECT 
 I.Name, 
 I.CapitalCity, 
 I.Population, 
 I.OfficialReligion, 
 I.OfficialLanguage,
 GETDATE() As DateEntered, 
 CASE 
   WHEN EXISTS(SELECT * FROM DELETED D WHERE D.ID = I.ID) 
   THEN 'U' ELSE 'I' 
 END As QueryType
 FROM INSERTED I

Я пропустил OriginalID, потому что люди, которым вы не должны обновлять первичный ключ и, кроме того, невозможно выяснить, что было обновлено в этом случае. Вы можете сделать это только перед триггером

Вот тот, который захватывает ваши удаления. Я сделал это отдельно, чтобы их было легче понять, и, вероятно, они будут работать лучше для больших наборов записей

INSERT INTO CountryRecord (
 Name, 
 CapitalCity, 
 Population, 
 OfficialReligion, 
 OfficialLanguage, 
 DateEntered, 
 QueryType)
SELECT 
 D.Name, 
 D.CapitalCity, 
 D.Population, 
 D.OfficialReligion, 
 D.OfficialLanguage,
 GETDATE() As DateEntered, 
 'D' As QueryType
 FROM DELETED D
 WHERE NOT EXISTS (SELECT * FROM INSERTED I WHERE I.ID = D.ID)

Они будут работать независимо от того, сколько строк было изменено

Если вы хотите проверить, какие столбцы были изменены в обновлении, вы можете снова сравнить их по отдельности в операторе выбора или использовать функцию UPDATED()

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

Что является релевантным это решение на основе множеств, которое я разместил здесь

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...