Использование postgres триггеров для эффективного обновления счетчиков - PullRequest
0 голосов
/ 14 февраля 2020

У меня есть схема, в которой у пользователей есть связанные с ними теги:

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

Я ищу создать запрос, который возвращает список всех тегов вместе с их счетчиками. Пример:

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

Триггеры, кажется, идеальный способ сделать это, так как приложение является тяжелым для чтения и легким для записи.

Однако у меня возникают трудности при реализации этого. Сам триггер кажется достаточно простым, я думаю:

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

trigger_update_tags_count - это та часть, с которой у меня проблемы, потому что она кажется очень сложной операцией. Например, если тег еще не существует в таблице tags, он должен быть вставлен со счетом 1.

Кроме того, я считаю, что вам нужно выполнить какую-то операцию сравнения, потому что если чьи-то теги обновляются с [ 'apple', 'carrot', 'jelly' ] до [ 'apple', 'dogs' ], тогда счет apple не изменяется, carrot и jelly уменьшается на 1, а dogs создается с его счетчиком, установленным на 1. Это усугубляется тем, что теги являются массивами. В настоящее время у меня есть что-то вроде этого:

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$;

Это немного псевдокод, потому что я не уверен, как интегрировать тот факт, что я имею дело с текстовыми массивами, или как обрабатывать случай сравнения. Спасибо за чтение.

Ответы [ 2 ]

1 голос
/ 15 февраля 2020

У вас ничего не называется tag в данных; это tags. Так что вам нужно UNNEST() и обрабатывать как старые, так и новые теги. Я думаю что-то вроде:

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
0 голосов
/ 15 февраля 2020

Я не уверен, что вам нужен триггер сложный лог c для этого. Вы можете получить нужный вывод с помощью простого запроса, который использует Postgres 'функцию массива unnest():

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

Обратите внимание, что если вы неоднократно запускаете такой запрос, это признак того, что ваш дизайн не совсем соответствует вашему варианту использования; Вам может быть лучше хранить каждый user_id/tag кортеж в отдельной строке таблицы, отдельно от таблиц, где хранятся пользователи.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...