Должен ли я хранить ежедневное количество пользователей в таблице, которая обновляется заданием cron?

#postgresql #cron

#postgresql #cron

Вопрос:

Мне нужно получать user количество для каждого account каждый день. Accounts может создавать и удалять пользователей, поэтому я не думаю, что простая агрегатная функция будет работать, поскольку, если вчера у an account было 10 users , а сегодня он удаляет один user (так что сегодня у них 9 users ), я все равно хотел бы знать, что users вчера было 10.

Мое текущее решение — иметь таблицу подсчета пользователей, которая обновляется заданием cron каждый день (см. Реализацию ниже). Было интересно, есть ли лучший способ?

 CREATE TABLE account (
  account_id bigint primary key
);

CREATE TABLE person (
  person_id    bigint primary key,
  account_id   bigint,
  created_at   timestamptz default now(),

  foreign key (account_id) references account
);

CREATE TABLE person_count (
  primary key (account_id, day),
  account_id   bigint,
  person_count integer,
  day          timestamptz,

  constraint day_formatted_dates_only check (day = date_trunc('day', day)),

  foreign key (account_id) references account
);

-- cron job that runs everyday
CREATE FUNCTION count_person_by_account()
RETURNS void AS $$

  INSERT INTO person_count
              (account_id, person_count, day)
       SELECT p.account_id, COUNT(*) person_count, date_trunc('day', now())
         FROM person p
     GROUP BY p.account_id

$$ LANGUAGE SQL
   VOLATILE;
  

Ответ №1:

Подумав об этом еще немного, я решил не идти в этом направлении и вместо этого создать представление для person.

 CREATE TABLE hidden_schema.person (
  person_id    bigint primary key,
  account_id   bigint,
  created_at   timestamptz default now(),
  is_deleted   boolean,

  foreign key (account_id) references account
);

CREATE VIEW visible_schema.person AS
  SELECT person_id, account_id, created_at
    FROM hidden_schema.person
   WHERE is_deleted = false;

-- Then I'll run the aggregate function on hidden_schema.person
  

Редактировать:
Подумав об этом еще больше, я понял, что более простым способом было бы не использовать представление, а просто добавить флаг is_delete (как указано выше) и использовать безопасность на уровне строк, чтобы отфильтровать его по моей роли клиента

 CREATE TABLE person (
  person_id    bigint primary key,
  account_id   bigint,
  created_at   timestamptz default now(),
  is_deleted   boolean,

  foreign key (account_id) references account
);

ALTER TABLE person ENABLE ROW LEVEL SECURITY;

CREATE POLICY select_for_public_facing_client_role
           ON person
          FOR SELECT
           TO public_facing_client_role
        USING (is_deleted = false);
  

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

 SELECT day, account_id, count
  FROM (
  SELECT generate_series AS day
    FROM generate_series('2020-10-14'::timestamptz, '2020-10-20'::timestamptz, '1d'::interval)
) d LEFT JOIN LATERAL (
  SELECT account_id, COUNT(*)
    FROM person
   WHERE created_at <= d.day
GROUP BY account_id
) c ON TRUE
ORDER BY d.day, c.account_id;
  

Вы не можете запустить это как public_facing_client_role, потому что они не могут видеть удаленных пользователей. Для авторизации можно использовать материализованное представление с предложением where.