【发布时间】:2015-02-15 09:57:39
【问题描述】:
我有一个
posts(id, likes)表(likes = number of likes for that post)
还有一个
post_likes(post_id, user_id) 表(the users who liked a post)。
我有一个触发器(post_likes_after_insert)
所以当我添加一个新的类似它时,它会增加posts.likes 和另一个触发器(post_likes_before_delete),它会减少posts.likes。一切正常。
然后我有另一个触发器(before delete) 用于删除post_likes,但这将触发上面的触发器减少posts.likes 列。
所以它给了我这个错误:
表 posts 正在发生变化,触发器/函数可能看不到它 post_likes_before_delete,第 4 行;执行触发器时出错 post_likes_before_delete
这是一个带有代码的 pastebin,底部有完整的错误。 http://pastebin.com/EQHTLZFp
谢谢。
-----------------------------------------------------------------------
-- USERS
-----------------------------------------------------------------------
CREATE TABLE users
(
id NUMBER(10) PRIMARY KEY NOT NULL,
email CHAR(100) UNIQUE NOT NULL,
password CHAR(60) NOT NULL,
first_name CHAR(30) NOT NULL,
last_name CHAR(30) NOT NULL,
birth_date DATE NULL,
gender CHAR(10) NULL,
joined TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
CONSTRAINT gender_check CHECK (UPPER(gender) IN ('M', 'F'))
);
-- Auto increment for users.id
DROP SEQUENCE users_sequence;
CREATE SEQUENCE users_sequence START WITH 1 INCREMENT BY 1;
CREATE TRIGGER users_before_insert
BEFORE INSERT
ON users REFERENCING NEW AS NEW
FOR EACH ROW
BEGIN
SELECT users_sequence.nextval INTO :NEW.ID FROM dual;
END;
/
--
-- POSTS
--
CREATE TABLE posts
(
id NUMBER(10) PRIMARY KEY NOT NULL,
user_id NUMBER(10) REFERENCES users(id) NOT NULL,
created TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
content char(1000) NOT NULL,
likes NUMBER(10) DEFAULT 0 NOT NULL,
privacy CHAR(10) DEFAULT 'public' NOT NULL,
CONSTRAINT fk_posts FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
-- Auto increment for posts.id
DROP SEQUENCE posts_sequence;
CREATE SEQUENCE posts_sequence START WITH 1 INCREMENT BY 1;
CREATE TRIGGER posts_before_insert
BEFORE INSERT
ON posts REFERENCING NEW AS NEW
FOR EACH ROW
BEGIN
SELECT posts_sequence.nextval INTO :NEW.ID FROM dual;
END;
/
CREATE TRIGGER posts_before_delete
BEFORE DELETE
ON posts
FOR EACH ROW
BEGIN
DELETE FROM post_likes WHERE post_id = :old.id;
END;
/
--
-- POST_LIKES
--
CREATE TABLE post_likes
(
user_id NUMBER(10) NOT NULL REFERENCES users(id),
post_id NUMBER(10) NOT NULL REFERENCES posts(id),
PRIMARY KEY (user_id, post_id),
--CONSTRAINT fk_post_likes1 FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE,
CONSTRAINT fk_post_likes2 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
-- Increments posts.likes
CREATE TRIGGER post_likes_after_insert
AFTER INSERT
ON post_likes
FOR EACH ROW
DECLARE
n NUMBER(10);
BEGIN
SELECT likes INTO n FROM posts WHERE id = :new.post_id;
UPDATE posts SET likes = n+1 WHERE id = :new.post_id;
END;
/
-- Decrements posts.likes
CREATE TRIGGER post_likes_before_delete
BEFORE DELETE
ON post_likes
FOR EACH ROW
DECLARE
n NUMBER(10);
BEGIN
SELECT likes INTO n FROM posts WHERE id = :old.post_id;
UPDATE posts SET likes = n-1 WHERE id = :old.post_id;
END;
/
-- Full error, EUSEBIU is the username that I'm logged in
-- ERROR at line 1:
-- ORA-04091: table EUSEBIU.POSTS is mutating, trigger/function may not see it
-- ORA-06512: at "EUSEBIU.POST_LIKES_BEFORE_DELETE", line 4
-- ORA-04088: error during execution of trigger 'EUSEBIU.POST_LIKES_BEFORE_DELETE'
-- ORA-06512: at "EUSEBIU.POSTS_BEFORE_DELETE", line 2
-- ORA-04088: error during execution of trigger 'EUSEBIU.POSTS_BEFORE_DELETE'
【问题讨论】:
-
首先,您确定需要以这种方式对数据进行非规范化吗?假设您出于性能原因确实需要物化该数据,而不是说,有一个物化视图会定期进行增量刷新以物化点赞数。如果您确实需要具体化数据,您是否确定确实需要使用触发器来维护数据,而不是将该逻辑放在应用层(即实现适当业务逻辑的存储过程)?
-
这只是 SQL 实现(我没有在应用程序中使用实际的数据库),我认为有这个更新喜欢的触发器会很好,但我会坚持COUNT * FROM 获取点赞数。
-
我不确定“我没有在应用程序中使用实际数据库”是什么意思——如果您使用触发器,那么您已经离开 SQL 并转移到 PL/SQL。如果您使用的是 PL/SQL,我不清楚为什么一个 PL/SQL 解决方案(触发器)可以接受,而另一个(存储过程)则不能。从这个角度来看,物化视图会更好,因为它实际上是一个纯 SQL 解决方案。如果出于某种原因必须使用触发器,那是可能的。就简单性和可维护性而言,这不是我最喜欢的 10 种方法。
-
在这里发布代码。链接可能会随着时间的推移而中断,然后您的问题对其他人没有帮助。
-
也许我误解了一些东西,但 IMO 你不应该使用触发器来强制引用完整性。如果我了解您的设计,那么 POST_LIKES.POST_ID 上应该有一个引用 POSTS.ID 的外键约束。通过设置此外键,使其删除操作为 CASCADE,当您从 POSTS 中删除时,FK 约束应负责删除所有 POST_LIKES。祝你好运。