【问题标题】:Movies rating trigger电影分级触发
【发布时间】:2022-01-25 03:39:57
【问题描述】:

我想创建一个对电影进行平均评分的触发器。我试图创建一个触发器,但是当我添加新的评级数据时,我从触发器中得到一个错误

ORA-04091: 表 RATING 发生变异,触发器/函数可能看不到它

因为在触发器中使用了select,所以我已经搜索了该问题的解决方案。但我不知道我应该如何更改我的代码,这种情况有什么解决方案吗?

CREATE OR REPLACE TRIGGER AvgRate_trig
AFTER INSERT ON Rating
FOR EACH ROW
BEGIN
    UPDATE TB_MOVIES 
    SET AvgRating = (SELECT AVG(RATE) FROM RATING 
                     WHERE tb_movies.movieid = rating.movieid)
    WHERE movieid = new.movieid;
END

【问题讨论】:

  • 你的设计有缺陷。您不应该尝试存储“平均值”。数据设计的基本规则之一是“不要存储可以计算的内容”。虽然我们只能猜测您的实际设计,但您在名为 RATING 的表中似乎有单独的评级。因此,当您需要知道“平均值”时,请在运行时计算它。如果您尝试存储不断变化的平均值,“存储”值与实际值不同步只是时间问题。
  • 那么,对于平均数据,我应该做的最好的事情是什么?我是否必须在数据库外部手动编码,平均评分并将评分存储到数据库?抱歉英语不好。
  • 最好的办法是什么?您在触发器中看到该 SELECT 语句了吗?不是 UPDATE,而只是 SELECT。那 SELECT 在做什么?它正在计算“平均评级”。因此,不要尝试存储该选择的结果,只需当您需要知道平均值时发出相同的选择 - 在生成包含以下内容的报告时平均评分。

标签: sql oracle triggers


【解决方案1】:

为了解决有缺陷的设计,看到我原来的评论似乎不被 OP 理解,这里有一个演示,正是我在说什么。数据设计的一个基本规则是您不计算和存储可以在运行时计算的值。在这个特定的应用程序中,这意味着您不会尝试不断更新和存储多个电影评分的平均值。而是在需要时进行计算。

SQL> show user
USER is "SCOTT"
SQL> -- -------- create the tables
SQL> create table movies
  2     (    movie_id number not null enable,
  3          movie_title varchar2(30) not null enable,
  4           constraint movies_pk primary key (movie_id)
  5    )
  6  ;

Table created.

SQL> --
SQL> create table ratings
  2     (    rating_id number not null enable,
  3          movie_id number not null enable,
  4          rating_source varchar2(30) not null enable,
  5          movie_rating number not null enable,
  6           constraint ratings_pk primary key (rating_id, movie_id),
  7           constraint ratings_chk1 check (movie_rating in (1,2,3,4,5)) enable,
  8           constraint ratings_fk1 foreign key (movie_id)
  9            references scott.movies (movie_id) on delete cascade enable
 10     )
 11  ;

Table created.

SQL> -- -------- load tables
SQL> insert into movies values (1,'To Kill A Mockingbird');

1 row created.

SQL> insert into ratings values (1,1,'Rotten Tomatoes',2);

1 row created.

SQL> insert into ratings values (2,1,'ImDB',2);

1 row created.

SQL> insert into ratings values (3,1,'Amazon User',3);

1 row created.

SQL> --
SQL> insert into movies values (2,'Saving Private Ryan');

1 row created.

SQL> insert into ratings values (4,2,'Rotten Tomatoes',4);

1 row created.

SQL> insert into ratings values (5,2,'ImDB',5);

1 row created.

SQL> insert into ratings values (6,2,'Amazon User',5);

1 row created.

SQL>
SQL> -- -------- Do the query
SQL> select * from movies;

  MOVIE_ID MOVIE_TITLE
---------- ------------------------------
         1 To Kill A Mockingbird
         2 Saving Private Ryan

2 rows selected.

SQL> select * from ratings;

 RATING_ID   MOVIE_ID RATING_SOURCE                  MOVIE_RATING
---------- ---------- ------------------------------ ------------
         1          1 Rotten Tomatoes                           2
         2          1 ImDB                                      2
         3          1 Amazon User                               3
         4          2 Rotten Tomatoes                           4
         5          2 ImDB                                      5
         6          2 Amazon User                               5

6 rows selected.

SQL> select m.movie_title,
  2         avg(r.movie_rating)
  3  from movies m
  4    join ratings r on m.movie_id = r.movie_id
  5  group by m.movie_title
  6  order by m.movie_title
  7  ;

MOVIE_TITLE                    AVG(R.MOVIE_RATING)
------------------------------ -------------------
Saving Private Ryan                     4.66666667
To Kill A Mockingbird                   2.33333333

2 rows selected.

SQL> -- -------- clean up
SQL> drop table ratings purge;

Table dropped.

SQL> drop table movies purge;

Table dropped.

也就是说,原始问题(如何编写触发器)具有家庭作业问题的所有特征。现在,如果任务是非常具体地编写这样一个触发器,人们会希望其目的是教授一些编码技术。这不是我们第一次看到基于非常有缺陷的设计的特定家庭作业,并且问题所暗示的设计非常有缺陷。当我看到这样的时候,我严重质疑讲师的资格,做出一个与基本数据设计原则背道而驰的任务。他们应该已经涵盖(实际上,在专门针对此类的课程中,以及此类作业的先决条件)数据设计的基础知识,包括但不限于数据规范化、第一范式、第二范式和第三范式表格(查找它们)。

【讨论】:

    【解决方案2】:

    您可以更喜欢使用语句级触发器而不是行级触发器,首先删除行FOR EACH ROW以获得新的触发器主体,例如

    CREATE OR REPLACE TRIGGER AvgRate_trig
      AFTER INSERT ON rating
    BEGIN
      UPDATE tb_movies t
         SET AvgRating =
             (SELECT AVG(rate)
                FROM rating r
               WHERE t.movieid = r.movieid)
       WHERE movieid IN 
             (SELECT movieid
                FROM rating
               GROUP BY movieid)   
    END;
    /
    

    这样你就不会得到tabe mutating错误。

    【讨论】:

    • 谢谢,它的工作.. 但我需要解释为什么语句级别工作?为什么行级别会导致变异?
    • 嗨@FajarSatyaSanjaya,有一个很好的比较here
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-06-20
    • 1970-01-01
    • 1970-01-01
    • 2020-03-24
    • 1970-01-01
    • 1970-01-01
    • 2020-03-10
    相关资源
    最近更新 更多