【问题标题】:Trigger for updating total records on both insert and delete用于更新插入和删除总记录的触发器
【发布时间】:2013-05-17 16:28:01
【问题描述】:

我正在编写一个触发器来将一个表的记录计数存储为另一个表中的列,以加快大型数据库上的一些报告查询。

这是我到目前为止所得到的,它在删除时工作正常,但我还需要它在插入时工作。我需要使用单独的触发器吗?另外,是否有必要使用游标,还是有更有效的方法?

谢谢!

ALTER TRIGGER [dbo].[updateSourceTotals]
   ON  [dbo].imports
   AFTER INSERT, DELETE
AS 
BEGIN

    SET NOCOUNT ON;

    DECLARE @sourceId int;

    DECLARE deleteCursor CURSOR FOR SELECT DISTINCT sourceId FROM deleted

    OPEN deleteCursor

    FETCH NEXT FROM deleteCursor INTO @sourceId

    WHILE @@FETCH_STATUS = 0
    BEGIN

        UPDATE  sources
        SET     totalImports = (
                    SELECT  COUNT(*) 
                    FROM    imports 
                    WHERE   sourceId = @sourceId
                    )
        WHERE   id = @sourceId

    FETCH NEXT FROM deleteCursor INTO @sourceId
    END

    CLOSE deleteCursor
    DEALLOCATE deleteCursor 

END
GO

【问题讨论】:

  • 到底为什么需要通过触发器更新计数?您始终可以在运行时获得该计数,并且它不依赖于触发器(使用游标,不少于)。如果在选择时获取计数的成本很高,则考虑使用索引视图。应该删除此触发器(以及 totalImports 列)。
  • 一般来说,预先计算的汇总并不是解决报告绩效的可取方式,尤其是考虑到它们的非关系性质。但是,如果您的报告是这样编写的,那么我可以看到它们为什么很慢。但是,解决方案仍然没有使用会使您的INSERTs 和DELETEs 极其缓慢的触发器。更有可能的解决方案是优化报告及其表格和索引。
  • 这是一个示例,还使用了更复杂的聚合,并且它们需要实时数据,因此对我来说,在基础记录更改时计算一次而不是在每个请求上计算一次是有意义的.虽然我很喜欢其他方法,但它确实需要持久化而不是在运行时计算。

标签: sql-server tsql triggers


【解决方案1】:

如果您真的采用触发方法(我确实推荐它),那么这是您当前代码的一个更简单且可能更快的版本:

ALTER TRIGGER [dbo].[updateSourceTotals]
   ON  [dbo].imports
   AFTER INSERT, DELETE
AS 
BEGIN

    UPDATE  s
    SET     totalImports = (
                SELECT  COUNT(*) 
                FROM    imports i
                WHERE   i.sourceId = s.Id
                )
    FROM    sources s
    WHERE   s.id IN(SELECT sourceId FROM deleted)

END

如果你也想覆盖INSERTs,应该这样做:

ALTER TRIGGER [dbo].[updateSourceTotals]
   ON  [dbo].imports
   AFTER INSERT, DELETE
AS 
BEGIN

    UPDATE  s
    SET     totalImports = (
                SELECT  COUNT(*) 
                FROM    imports i
                WHERE   i.sourceId = s.id
                )
    FROM    sources s
    WHERE   s.id IN(
                    SELECT sourceId FROM deleted
                UNION
                    SELECT sourceId FROM inserted
                  )

END

作为额外的奖励,它应该也适用于UPDATEs。


澄清一下,在触发器中进行预聚合的问题是,即使您消除了光标,也不是在每个请求上重新计算查询,而是在每个请求上重新计算它们 修改

即使是抽象的,如果你做了很多这样的请求,这只是一个胜利,但不要对表格进行太多修改。然而,在一个活跃的 DBMS 服务器的真实环境中,即使是这个小优势,你也会失去大部分,因为如果你发出许多这样的请求,那么它们可能会非常有效地被缓存(反过来,因为读取更多的是缓存-比写有效)。

【讨论】:

  • 这不起作用 - 如果我运行一个简单的 UPDATE sources SET label = label 来更新所有行,它会计算 totalImports 但数字都是错误的。我认为对于更新,它必须按行(或至少按 sourceId)工作?
  • 这个确实根据 sourceid 计算它,它只是以面向集合的方式进行。至于 UPDATE 的问题,我不知道为什么它不能正常工作。您能否发布一些示例数据来证明或至少解释它的错误之处?
  • 我发现了问题。对于任何具有导入的源,它在更新时工作正常,但如果 COUNT() FROM 导入的结果为零,则它返回表中的记录总数,而不管 sourceId 是什么。 SELECT id, totalImports, (SELECT COUNT() FROM imports WHERE imports.sourceId = sources.id) AS actualImports FROM sources 6 136037 0 7 136037 0 8 136037 0 9 136037 0 10 136037 0 11 1873 1873 12 82 82 13 278 278 14 2762 2762 15 4509 4509
  • 抱歉格式化,还在摸索中,请多多包涵!
  • @Don'tPanicAndy 通过编辑您的原始帖子并将其添加到末尾来发布此类内容。
猜你喜欢
  • 2022-01-15
  • 1970-01-01
  • 2015-11-11
  • 2011-02-27
  • 1970-01-01
  • 2011-05-05
  • 1970-01-01
  • 2013-06-11
  • 2011-01-15
相关资源
最近更新 更多