在触发触发器后给出SELECT结果的PostgreSQL INSERT或UPDATE值

时间:2014-12-23 22:47:00

标签: sql postgresql triggers plpgsql postgresql-9.3

这是我的结构(带有值):

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”答案,我抬头看了几个小时,除了琐碎的例子之外什么都找不到。

1 个答案:

答案 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();