【问题标题】:Weird trigger problem when I do an INSERT into a table当我在表中执行 INSERT 时出现奇怪的触发器问题
【发布时间】:2010-11-02 17:09:39
【问题描述】:

我有一个附加到表的触发器。

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”行?!

如果是这样 .. 呃.. :( 这是否意味着我需要在此触发器中使用游标?(如果我需要对每一行执行一些独特的逻辑,我目前正在这样做)。

?

更新

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

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

【问题讨论】:

  • 更新的答案。不确定您要使用 COUNT(ContentId) 做什么,因为 WHERE 子句将其限制为 0 或 1 行
  • 这里有没有回答你的问题?
  • 干杯 - 回答。我所有的触发器现在都可以处理多个记录集 .. 他们应该 :) 而不是光标...

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


【解决方案1】:

为什么不改变触发器来处理多行呢? 不需要游标或循环:这是 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 准确 - “INSERTED”表包含多行 - 只需更新所有这些行! :-)
  • 因为我每行都在做一些特殊的事情。我实际上正在为每个“主题”文本字段创建一个唯一的文本字符串(用作 URI 的一部分)。所以对于每个主题,我需要检查它是否已经存在。如果是这样,请通过在其末尾附加一个数字来使其唯一。数字是计数(例如原始主题存在的次数)。所以它不像 LastModifiedOn 那样简单。我只是想用它作为一个简单的例子。
  • 我已经更新了开头的帖子,以包含我的特殊日志内容。
  • 感谢 gdn 的回答。我还不确定逻辑是否正确。 INSERTED 表可以包含多行。现在,对于每一行,我们需要为该行设置唯一的主题。到目前为止,一切都很好。但是,该值必须是已清理的主题(例如 ToUriCleanText)+ 一个数字。当此行是第二个或更大的实例时,此数字应从 1 开始。我也在考虑使用 ROWNUMBER (OVER ContentId)。这样,我知道数字总是相同的,因为 ContentID 是 PK 标识。我不认为这个查询正在做所有这些??? (我很混乱)。真诚的道歉。
【解决方案2】:

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

【讨论】:

  • 呻吟不。 :( 就像...那不是我想听到的答案 :P 为及时回复干杯 :)
  • 如果可能的话,转移到没有触发器的解决方案;例如,从存储过程中插入。可能会为您省去很多麻烦:)
  • 您只需使用光标滚动已插入并执行您需要的操作。这不会破坏交易。
  • @Jonathan:提倡基于集合的思维,尤其是在处理 SQL 时!游标大体上是一个缓慢的拐杖。通常(并非总是)应避免使用它们。
【解决方案3】:

好吧,伙计们,我想我自己想通了。受先前答案和 cmets 的启发,我做了以下工作。 (你们可以快速查看一下我是否过度设计了这个婴儿吗?)

.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)

.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 减一。这意味着同一主题的第二个实例,唯一主题将是 => cleansubject + '1'。

我认为我需要索引视图的原因是,如果我有两个非常相似的主题,那么当您删除(即清理)所有坏字符(我确定它们是坏的)时) .. 两个干净的主题可能是相同的。因此,我需要在 cleanSubject 上进行所有连接,而不是在主题上。现在,对于我拥有的大量行,当我没有视图时,这对性能来说是废话。 :)

所以..这是过度设计的吗?

编辑 1:

重构触发器代码,使其性能更佳。

【讨论】:

    猜你喜欢
    • 2014-08-28
    • 2014-09-02
    • 2023-03-20
    • 1970-01-01
    • 1970-01-01
    • 2020-12-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多