当我在表中执行INSERT时出现奇怪的触发器问题

时间:2009-06-13 15:19:58

标签: sql sql-server tsql sql-server-2008 triggers

我的桌子上有一个触发器。

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(日期时间)字段。

好的,所以我得到的是批量插入东西..

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

现在所有行都已正确插入。 LastModifiedOn字段默认为null。因此,所有条目都为null - 除了第一行。

这是否意味着不会为插入表中的每一行调用触发器,而是在插入查询完成后,即。是否插入了所有行?这意味着,INSERTED表(在触发器中)没有一行,而是'n'行?!

如果是这样..呃.. :(这是否意味着我需要在这个触发器中使用光标?(如果我需要为每一行做一些独特的逻辑,我现在这样做。)

更新

我将添加完整的触发器代码,以查看是否可以在没有光标的情况下执行此操作。

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 个答案:

答案 0 :(得分:8)

为什么不更改触发器来处理多行? 不需要游标或循环:这是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:再次使用RANKING

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

答案 1 :(得分:2)

对于INSERT INTO查询,触发器只运行一次。 INSERTED表将包含多行。

答案 2 :(得分:1)

好的伙计们,我想我自己搞清楚了。受到之前的答案和评论的启发,我做了以下工作。 (你们可以快速浏览一下,看看我是否过度引导了这个孩子?)

0.1。创建了一个索引视图,表示需要清理的“主题”字段。这个领域必须是独一无二的......但在我们将它变得独一无二之前,我们需要将它分组。

-- 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。创建现在为什么的触发器代码 a)抓住所有“改变的”项目(没有新的/很难的)
b)命令所有插入的行,用干净的主题划分编号的行 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减去1。这意味着同一主题的第二个实例,唯一主题将是=> cleansubject +'1'。

我认为我需要有一个索引视图的原因是因为如果我有两个非常相似的主题,当你已经剥离(即清理)所有坏字符(我已经确定是坏的) )..两个干净的科目可能是相同的。因此,我需要在cleaningSubject上进行所有连接,而不是主题。现在,对于我拥有的大量行,当我没有视图时,这是性能的废话。 :)

那么..这是过度设计的吗?

编辑1:

重构了触发器代码,因此它具有更高的性能。