Странная проблема триггера, когда я делаю INSERT в таблицу - PullRequest
1 голос
/ 13 июня 2009

У меня есть триггер, прикрепленный к столу.

ALTER TRIGGER [dbo].[UpdateUniqueSubjectAfterInsertUpdate]
   ON  [dbo].[Contents]
   AFTER INSERT,UPDATE
AS
BEGIN

-- Grab the Id of the row just inserted/updated
DECLARE @Id INT

SELECT @Id = Id
FROM INSERTED

END

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

Хорошо, так что у меня есть пакетная вставка ...

INSERT INTO [dbo].[Contents]
SELECT Id, a, b, c, d, YouDontKnowMe
FROM [dbo].[CrapTable]

Теперь все строки вставлены правильно. Поле LastModifiedOn по умолчанию равно нулю. Таким образом, все записи для этого являются нулевыми - ЗА ИСКЛЮЧЕНИЕМ первой строки.

Означает ли это, что триггер НЕ вызывается для каждой строки, вставленной в таблицу, но после ПОСЛЕ запроса на вставку, т.е. ВСЕ строки вставлены? Что означает, что таблица INSERTED (в триггере) имеет не одно, а n строк??

Если это так .. er .. :( Значит ли это, что в этом триггере мне понадобится курсор?

UPDATE

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

BEGIN
    SET NOCOUNT ON

    DECLARE @ContentId INTEGER,
        @ContentTypeId TINYINT,
        @UniqueSubject NVARCHAR(200),
        @NumberFound INTEGER

    -- Grab the Id. Also, convert the subject to a (first pass, untested)
    -- unique subject.
    -- NOTE: ToUriCleanText just replaces bad uri chars with a ''. 
    --   eg. an '#' -> ''
    SELECT @ContentId = ContentId, @ContentTypeId = ContentTypeId, 
        @UniqueSubject = [dbo].[ToUriCleanText]([Subject])
    FROM INSERTED

    -- Find out how many items we have, for these two keys.
    SELECT @NumberFound = COUNT(ContentId)
    FROM [dbo].[Contents]
    WHERE ContentId = @ContentId
        AND UniqueSubject = @UniqueSubject

    -- If we have at least one identical subject, then we need to make it 
    -- unique by appending the current found number.
    -- Eg. The first instance has no number. 
    --     Second instance has subject + '1',
    --     Third instance has subject + '2', etc...
    IF @NumberFound > 0
        SET @UniqueSubject = @UniqueSubject + CAST(@NumberFound AS NVARCHAR(10))

    -- Now save this change.
    UPDATE [dbo].[Contents]
    SET UniqueSubject = @UniqueSubject
    WHERE ContentId = @ContentId
END

Ответы [ 3 ]

8 голосов
/ 13 июня 2009

Почему бы не изменить триггер для работы с несколькими строками? Курсор или циклы не нужны: весь смысл SQL ...

UPDATE
    dbo.SomeTable
SET
    LastModifiedOn = GETDATE()
WHERE
    EXIST (SELECT * FROM INSERTED I WHERE I.[ID] = dbo.SomeTable.[ID]

Редактировать: Нечто подобное ...

INSERT @ATableVariable
    (ContentId, ContentTypeId, UniqueSubject)
SELECT 
    ContentId, ContentTypeId, [dbo].[ToUriCleanText]([Subject])
FROM
    INSERTED

UPDATE
    [dbo].[Contents]
SET
    UniqueSubject + CAST(NumberFound AS NVARCHAR(10))
FROM
    --Your original COUNT feels wrong and/or trivial
    --Do you expect 0, 1 or many rows.
    --Edit2: I assume 0 or 1 because of original WHERE so COUNT(*) will suffice
    -- .. although, this implies an EXISTS could be used but let's keep it closer to OP post
    (
    SELECT ContentId, UniqueSubject, COUNT(*) AS NumberFound
    FROM @ATableVariable
    GROUP BY ContentId, UniqueSubject
    HAVING COUNT(*) > 0
    ) foo
    JOIN
    [dbo].[Contents] C ON C.ContentId = foo.ContentId AND C.UniqueSubject = foo.UniqueSubject

Редактировать 2: и снова с помощью РЕЙТИНГА

UPDATE
    C
SET
    UniqueSubject + CAST(foo.Ranking - 1 AS NVARCHAR(10))
FROM
    (
    SELECT
        ContentId, --not needed? UniqueSubject,
        ROW_NUMBER() OVER (PARTITION BY ContentId ORDER BY UniqueSubject) AS Ranking
    FROM
        @ATableVariable
    ) foo
JOIN
    dbo.Contents C ON C.ContentId = foo.ContentId 
    /* not needed? AND C.UniqueSubject = foo.UniqueSubject */
WHERE
foo.Ranking > 1
2 голосов
/ 13 июня 2009

Триггер будет запущен только один раз для запроса INSERT INTO. Таблица INSERTED будет содержать несколько строк.

1 голос
/ 15 июня 2009

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

0,1. Создано представление Index'd, представляющее поле «Тема», которое необходимо очистить. Это поле должно быть уникальным ... но прежде чем мы сможем сделать его уникальным, нам нужно сгруппировать его по нему.

-- Create the view.
CREATE VIEW ContentsCleanSubjectView with SCHEMABINDING AS
SELECT ContentId, ContentTypeId, 
    [dbo].[ToUriCleanText]([Subject]) AS CleanedSubject
FROM [dbo].[Contents]
GO

-- Index the view with three index's. Custered PK and a non-clustered, 
-- which is where most of the joins will be done against.
-- Last one is because the execution plan reakons i was missing statistics
-- against one of the fields, so i added that index and the stats got gen'd.
CREATE UNIQUE CLUSTERED INDEX PK_ContentsCleanSubjectView ON 
    ContentsCleanSubjectView(ContentId)
CREATE NONCLUSTERED INDEX IX_BlahBlahSnipSnip_A ON 
    ContentsCleanSubjectView(ContentTypeId, CleanedSubject)
CREATE INDEX IX_BlahBlahSnipSnip_B ON
    ContentsCleanSubjectView(CleanedSubject)

0,2. Создайте код триггера, который теперь
а) захватывает все «измененные» предметы (ничего нового / сложного в этом нет)
б) упорядочивает все вставленные строки, пронумерованные с разделением строк по чистой теме
c) обновить единственную строку, о которой мы говорим, в главном предложении обновления.

вот код ...

ALTER TRIGGER [dbo].[UpdateUniqueSubjectAfterInsertUpdate]
   ON  [dbo].[Contents]
   AFTER INSERT,UPDATE
AS
BEGIN
    SET NOCOUNT ON

    DECLARE @InsertRows TABLE (ContentId INTEGER PRIMARY KEY,
        ContentTypeId TINYINT,
        CleanedSubject NVARCHAR(300))

    DECLARE @UniqueSubjectRows TABLE (ContentId INTEGER PRIMARY KEY,
    UniqueSubject NVARCHAR(350))

DECLARE @UniqueSubjectRows TABLE (ContentId INTEGER PRIMARY KEY,
        UniqueSubject NVARCHAR(350))

    -- Grab all the records that have been updated/inserted.
    INSERT INTO @InsertRows(ContentId, ContentTypeId, CleanedSubject)
    SELECT ContentId, ContentTypeId, [dbo].[ToUriCleanText]([Subject])
    FROM INSERTED


    -- Determine the correct unique subject by using ROW_NUMBER partitioning.
    INSERT INTO @UniqueSubjectRows
    SELECT SubResult.ContentId, UniqueSubject = CASE SubResult.RowNumber 
        WHEN 1 THEN SubResult.CleanedSubject 
        ELSE SubResult.CleanedSubject + CAST(SubResult.RowNumber - 1 AS NVARCHAR(5)) END
    FROM (
        -- Order all the cleaned subjects, partitioned by the cleaned subject.
        SELECT a.ContentId, a.CleanedSubject, ROW_NUMBER() OVER (PARTITION BY a.CleanedSubject ORDER BY a.ContentId) AS RowNumber
        FROM ContentsCleanSubjectView a 
            INNER JOIN @InsertRows b ON a.ContentTypeId = b.ContentTypeId AND a.CleanedSubject = b.CleanedSubject
        GROUP BY a.contentId, a.cleanedSubject
    ) SubResult
    INNER JOIN [dbo].[Contents] c ON c.ContentId = SubResult.ContentId
    INNER JOIN @InsertRows d ON c.ContentId = d.ContentId

    -- Now update all the effected rows.
    UPDATE a
    SET a.UniqueSubject = b.UniqueSubject
    FROM [dbo].[Contents] a INNER JOIN @UniqueSubjectRows b ON a.ContentId = b.ContentId
END  

Теперь подзапрос правильно возвращает все очищенные объекты, правильно разделенные и пронумерованные. Я никогда не знал ничего о команде 'PARTITION', так что этот трюк был большим ответом здесь:)

Тогда я просто присоединился к подзапросу со строкой, которая обновляется в родительском запросе. Номер строки правильный, так что теперь я просто делаю дело. если это первый раз, когда очищенный объект существует (например, row_number = 1), не изменяйте его. в противном случае добавьте row_number минус один. Это означает, что 2-й экземпляр того же субъекта будет уникальным: => cleansubject + '1'.

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

Так ... это слишком спроектировано?

Редактировать 1:

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

...