【问题标题】:MS SQL Server trigger to update item rating and number of votesMS SQL Server 触发器更新项目评级和投票数
【发布时间】:2009-04-15 23:48:39
【问题描述】:

为了更容易理解,我将提出与论坛完全相同的问题(实际的应用程序根本与论坛无关,但我认为这样的平行更容易对于我们大多数人来说,实际的应用程序是关于大多数程序员无法理解的非常具体的东西(这是一个专为核心图形设计师设计的应用程序)。

假设有一个线程表存储有关每个论坛线程的信息,还有一个线程评级表存储每个用户的线程评级 (1-5)。为了提高效率,我决定在线程表中缓存评分平均值和投票数,并且触发器听起来是更新它的好主意(我曾经在实际的应用程序代码中做这样的事情,但我认为触发器值得一试,尽管调试危险)。

如您所知,MS SQL Server 不支持按行执行触发器,它必须按语句执行。所以我尝试这样定义:

CREATE TRIGGER thread_rating ON threadrating
AFTER INSERT
AS
    UPDATE thread
    SET 
        thread.rating = (thread.rating * thread.voters + SUM(inserted.rating))/(thread.voters + COUNT(inserted.rating)),
        thread.voters = thread.voters + COUNT(inserted.rating)
    FROM thread
    INNER JOIN inserted ON(inserted.threadid = thread.threadid)
    GROUP BY inserted.threadid

但我收到“GROUP BY”子句的错误(这是我预期的)。问题是,我怎样才能做到这一点?

对不起,如果这个问题很愚蠢,但这是我第一次真正尝试使用触发器。

附加信息:线程表将包含 threadid (int, primary key)、rating (float)、voters(int) 和其他一些与当前问题无关的字段。 threadrating表只包含threadid(外键)、userid(users表主键的外键)和rating(1到5之间的小整数)。

错误消息是“关键字 'GROUP' 附近的语法不正确。”

【问题讨论】:

  • 桌子上的主键是什么?
  • 发布表格 DML 和实际错误消息也会有所帮助。

标签: sql-server triggers


【解决方案1】:

首先,我强烈建议您不要使用触发器。

如果您遇到语法错误,请检查您的括号以及您的 begin/ends 是否平衡。在您的情况下,您有一个 end (最后)但没有开始。您可以通过删除end 来解决这个问题。

修复该问题后,您可能会遇到更多错误,例如“列 x、y、z 不在聚合或分组依据中”。那是因为您有几列都不在其中。您需要将 thread.rating、thread.voters 等添加到您的组中,或者对它们执行某种聚合。

这都是假设有多条记录具有相同的threadID(即,它不是主键)。如果不是,那么group by 的目的是什么?


编辑:

我被语法错误难住了。我用几个相关的子查询解决了这个问题。我猜到了你的表结构,所以根据需要修改并试试这个:

--CREATE TABLE ThreadRating (threadid int not null, userid int not null, rating int not null)
--CREATE TABLE Thread (threadid int not null, rating int not null, voters int not null)

ALTER TRIGGER thread_rating ON threadrating
AFTER INSERT
AS 

UPDATE Thread
SET Thread.rating = 
    (SELECT (Thread.Rating * Thread.Voters + SUM(I.Rating)) / (Thread.Voters + COUNT(I.Rating))
     FROM ThreadRating I WHERE I.ThreadID = thread.ThreadID)
  ,Thread.Voters = 
    (SELECT Thread.Voters + COUNT(I.Rating) 
     FROM ThreadRating I WHERE I.ThreadID = Thread.ThreadID)                         
FROM Thread
JOIN Inserted ON Inserted.ThreadID = Thread.ThreadID

如果这是您想要的,那么我们可以检查性能/执行计划并根据需要进行修改。我们也许可以让它与小组一起工作。


触发器的替代方案

如果您要更新的数据仅影响少数选定位置的评分,我建议您直接在此处更新评分。将逻辑分解到触发器中很好,但会带来很多问题(性能、可见性等)。这可以通过一个函数来辅助。

考虑一下:每次有人触摸该表时,您的触发器都会执行。观看次数、上次更新日期等内容将执行此触发器。在这些情况下,您可以添加逻辑来使触发器短路,但它会很快变得复杂。

【讨论】:

  • threadrating 表中可能有多个具有相同threadid 的行,而不是thread 表中。我预料到你提到的同样的错误,但我得到了一个更丢脸的“关键字'GROUP'附近的语法不正确”。如果您不推荐触发器,您会针对这种情况推荐什么?
  • 去掉末尾的“End”关键字。 (或在“as”之后添加“begin”)
  • 我之前没有看过编辑。不,threadrating 表只有 3 个字段:threadid、userid、rating。你能详细说明一下性能部分吗?出于性能原因,我决定采用整个概念......
  • @Michael Haren:哎呀,你是对的!这并没有解决问题,但这绝对是需要纠正的事情。我也在原帖中更新了它。
  • 请为您的表格发布创建脚本
【解决方案2】:

天啊!我完全误解了你的问题,我以为你在问 MySQL。过失!我将保留下面的解决方案,并将其标记为社区 wiki。也许它对在 MySQL 上遇到类似问题的人有用。


MySQL 触发器 每行执行一次。此外,伪表“inserted”是 Microsoft SQL Server 约定。

MySQL 使用伪表 NEW 和 OLD 作为 trigger language 的扩展。

以下是您的问题的解决方案:

CREATE TRIGGER thread_rating 
  AFTER INSERT ON threadrating
  FOR EACH ROW
BEGIN
    UPDATE thread
    SET rating = (rating*voters + NEW.rating)/(voters+1),
        voters = voters + 1
    WHERE threadid = NEW.threadid;
END

同样,您需要 UPDATE 和 DELETE 的触发器:

CREATE TRIGGER thread_rating 
  AFTER UPDATE ON threadrating
  FOR EACH ROW
BEGIN
    UPDATE thread
    SET rating = (rating*voters - OLD.rating + NEW.rating)/voters,
    WHERE threadid = NEW.threadid;
END

CREATE TRIGGER thread_rating 
  AFTER DELETE ON threadrating
  FOR EACH ROW
BEGIN
    UPDATE thread
    SET rating = (rating*voters - OLD.rating)/(voters-1),
        voters = voters - 1
    WHERE threadid = OLD.threadid;
END

【讨论】:

  • 但我使用的是 MS SQL Server!使用 MySQL 一切都会变得更容易,但我对这个项目没有选择:(
  • Np,它肯定对其他人有用:)
【解决方案3】:

您可能会发现以下阅读内容很有帮助:

An introduction to Triggers
Wikipedia: DB Triggers

【讨论】:

  • 在过去的 3 个小时里,我一直在阅读有关触发器和 SQL Server 执行它们的方式的信息……没有什么对我的特殊情况有帮助:(
  • 哇,太糟糕了!我看看能不能找到更好的链接。
猜你喜欢
  • 2011-06-03
  • 1970-01-01
  • 1970-01-01
  • 2014-03-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-20
  • 2016-11-03
相关资源
最近更新 更多