这是我的结构(带有值):
user_eval_history table
user_eval_id | user_id | is_good_eval
--------------+---------+--------------
1 | 1 | t
2 | 1 | t
3 | 1 | f
4 | 2 | t
user_metrics table
user_metrics_id | user_id | nb_good_eval | nb_bad_eval
-----------------+---------+--------------+-------------
1 | 1 | 2 | 1
2 | 2 | 1 | 0
对于访问时间(性能)原因,我希望避免一次又一次地从历史记录中重新计算用户评估。 我想在每次向用户提供新的评估时存储/更新评估的总和(对于给定的用户)(意味着每次在user_eval_history表中存在INSERT时我想更新相应user_id的user_metrics表)。
我觉得我可以通过触发器和存储过程实现这一点,但我无法找到正确的语法。
我想我需要做以下事情:
1。为用户指标创建触发器:
CREATE TRIGGER update_user_metrics_trigger AFTER INSERT
ON user_eval_history
FOR EACH ROW
EXECUTE PROCEDURE update_user_metrics('user_id');
2。创建存储过程update_user_metrics
2.1从user_eval_history表中为user_id计算指标
SELECT
user_id,
SUM( CASE WHEN is_good_eval='t' THEN 1 ELSE 0) as nb_good_eval,
SUM( CASE WHEN is_good_eval='f' THEN 1 ELSE 0) as nb_bad_eval
FROM user_eval_history
WHERE user_id = 'user_id' -- don't know the syntax here
2.2.1如果尚未存在,则创建user_metrics条目
INSERT INTO user_metrics
(user_id, nb_good_eval, nb_bad_eval) VALUES
(user_id, nb_good_eval, nb_bad_eval) -- Syntax?????
2.2.2更新user_metrics条目(如果已存在)
UPDATE user_metrics SET
(user_id, nb_good_eval, nb_bad_eval) = (user_id, nb_good_eval, nb_bad_eval)
我认为我已接近所需,但不知道如何实现这一目标。特别是我不知道语法。
有什么想法吗?
注意:请不要“RTFM”答案,我抬头看了几个小时,除了琐碎的例子之外什么都找不到。
答案 0 :(得分:1)
首先,我将重新考虑这样的假设:维持一个始终当前的物化视图实际上是一个显着的性能增益。您创建了大量开销,并使user_eval_history
的写入成本更高。如果写入罕见,则该方法才有意义,而读取则更为常见。否则,请考虑使用VIEW,这对于读取而言更昂贵,但始终是最新的。使用user_eval_history
上的适当索引,这可能比您想象的要便宜。
接下来,考虑user_metrics
的实际MATERIALIZED VIEW
(Postgres 9.3+),而不是手动更新它,尤其是当user_eval_history
的写操作非常罕见时< / em>的。棘手的部分是当刷新MV时。
您的方法是有意义的,如果您介于两者之间,user_eval_history
具有非平凡的大小,您需要user_metrics
来准确反映当前状态并接近真实状态-time。
还在船上?好。首先,您需要完全定义 允许/可能的内容以及不允许/可能的内容。可以删除user_eval_history
中的行吗?可以删除user_eval_history
中用户的最后一行吗?也许是的,即使你会回答“不”#34;可以更新user_eval_history
中的行吗? user_id
可以更改吗? is_good_eval
可以更改吗?如果是,您需要为每种情况做好准备。
假设琐碎的案例:INSERT
。没有UPDATE
,没有DELETE
。您仍然可以与@ sn00k4h讨论可能的竞争条件。您找到了an answer to that,但这确实适用于 INSERT或SELECT ,而您有一个经典的 UPSERT
问题: INSERT或更新:
FOR UPDATE
不 这里的银弹。 UPDATE user_metrics ...
无论如何都会锁定它更新的行。有问题的情况是两个INSERT尝试同时为新user_id
创建一行。但是,在Postgres中,您无法锁定唯一索引中不存在的键值。 FOR UPDATE
无法提供帮助。您需要为这些链接的答案中讨论的可能的唯一违规和重试做准备:
进一步假设表定义:
CREATE TABLE user_eval_history (
user_eval_id serial PRIMARY KEY
, user_id int NOT NULL
, is_good_eval boolean NOT NULL
);
CREATE TABLE user_metrics (
user_metrics_id -- seems useless
, user_id int PRIMARY KEY
, nb_good_eval int NOT NULL DEFAULT 0
, nb_bad_eval int NOT NULL DEFAULT 0
);
首先,您需要一个触发器功能才能创建触发器。
CREATE OR REPLACE FUNCTION trg_user_eval_history_upaft()
RETURNS trigger AS
$func$
BEGIN
LOOP
IF NEW.is_good_eval THEN
UPDATE user_metrics
SET nb_good_eval = nb_good_eval + 1
WHERE user_id = NEW.user_id;
ELSE
UPDATE user_metrics
SET nb_bad_eval = nb_bad_eval + 1
WHERE user_id = NEW.user_id;
END IF;
EXIT WHEN FOUND;
BEGIN -- enter block with exception handling
IF NEW.is_good_eval THEN
INSERT INTO user_metrics (user_id, nb_good_eval)
VALUES (NEW.user_id, 1);
ELSE
INSERT INTO user_metrics (user_id, nb_bad_eval)
VALUES (NEW.user_id, 1);
END IF;
RETURN NULL; -- returns from function, NULL for AFTER trigger
EXCEPTION WHEN UNIQUE_VIOLATION THEN -- user_metrics.user_id is UNIQUE
RAISE NOTICE 'It actually happened!'; -- hardly ever happens
END;
END LOOP;
RETURN NULL; -- NULL for AFTER trigger
END
$func$ LANGUAGE plpgsql;
特别是,您不能将user_id
作为参数传递给触发器功能。特殊变量 NEW
会自动保存触发行的值。 Details in the manual here.
触发:
CREATE TRIGGER upaft_update_user_metrics
AFTER INSERT ON user_eval_history
FOR EACH ROW EXECUTE PROCEDURE trg_user_eval_history_upaft();