简体   繁体   English

使用 postgres 触发器有效地更新标签计数

[英]Using postgres triggers to update tag counts efficiently

I have a schema where users have tags associated with them:我有一个架构,其中用户具有与其关联的标签:

user_id: int4    tags: text[]
-------------    ------------
1                [ 'apple', 'carrot', 'jelly' ]
2                [ 'jelly', 'zebra' ]

I am looking to create query that returns the list of all tags along with their associated counts.我正在寻找创建返回所有标签列表及其相关计数的查询。 Example:例子:

tag: text      count: int4
---------      -----------
'apple'        1
'carrot'       1
'jelly'        2
'zebra'        1

Triggers seem to be the ideal way to do this, since the application is read-heavy and write-light.触发器似乎是实现此目的的理想方式,因为该应用程序是重读和轻写。

However, I am having difficulty implementing this.但是,我很难实现这一点。 The trigger itself seems simple enough I think:我认为触发器本身似乎很简单:

CREATE TRIGGER tags_count_update
AFTER UPDATE OF tags ON person
FOR EACH ROW
EXECUTE PROCEDURE trigger_update_tags_count();

The trigger_update_tags_count is the part I'm having trouble with, because it seems to be a highly complex operation. trigger_update_tags_count是我遇到问题的部分,因为它似乎是一个非常复杂的操作。 For example, if the tag doesn't already exist in the tags table then it should be inserted with a count of 1.例如,如果tags表中尚不存在该tags ,则应将其插入,计数为 1。

Also, I believe you need to do some sort of diff operation, because if someone's tags are updated from [ 'apple', 'carrot', 'jelly' ] to [ 'apple', 'dogs' ] then apple 's count doesn't change, carrot and jelly is decremented by 1, and dogs is created with its count set to 1. This is compounded with the fact that tags are arrays.另外,我相信您需要进行某种差异操作,因为如果某人的标签从[ 'apple', 'carrot', 'jelly' ][ 'apple', 'dogs' ]那么apple的计数不会不变, carrot and jelly减1,创建dogs将其计数设置为1。这与标签是数组的事实相结合。 I currently have something like this:我目前有这样的事情:

CREATE OR REPLACE FUNCTION public.trigger_update_tags_count()
  RETURNS trigger
  LANGUAGE plpgsql
AS $function$
BEGIN
   INSERT INTO tags (tag, count) VALUES (new.tag, 1)
   ON CONFLICT (tag) DO UPDATE
   SET count = CASE WHEN (tag.new AND NOT tag.old) THEN count + 1 
                    WHEN (tag.old AND NOT tag.new) THEN count - 1
                    ELSE count
               END
   RETURN NEW;
END;
$function$;

Which is slightly pseudocode because I'm not sure how to integrate the fact that I'm dealing with text arrays, nor how to handle the diffing case.这有点伪代码,因为我不确定如何整合我正在处理文本数组的事实,也不知道如何处理不同的情况。

You have nothing called tag in the data;你在数据中没有任何叫做tag东西; it is tags .它是tags So you need to UNNEST() and to handle both old and new tags.所以你需要UNNEST()并处理旧标签和新标签。 I'm thinking something like:我在想这样的事情:

INSERT INTO tags (tag, count) 
   SELECT COALESCE(ntag, otag),
          (ntag IS NOT NULL)::int - (otag IS NOT NULL)::int
   FROM UNNEST(new.tags) ntag FULL JOIN
        UNNEST(old.tags) otag
        ON ntag = otag
ON CONFLICT (tag) DO UPDATE
   SET count = count + (excluded.ntag IS NOT NULL)::int - (excluded.otag IS NOT NULL)::int

I am unsure that you do need a trigger a complex logic for that.我不确定您是否需要为此触发复杂的逻辑。 You can get the output that you want with a simple query that leverages Postgres' array function unnest() :您可以通过利用Postgres 数组函数unnest()的简单查询获得所需的输出:

select x.tag, count(*)
from mytable t
cross join lateral unnest(t.tags) x(tag)
group by x.tag

Note that if you find yourself repeatedly running such query, it is an indication that your design does not really fit your use case;请注意,如果您发现自己重复运行此类查询,则表明您的设计并不真正适合您的用例; you might be better off storing each user_id/tag tuple in a separate table row, separated from the tables where users are stored.您最好将每个user_id/tag元组存储在单独的表行中,与存储用户的表分开。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM