Моето решение има някои разумни изисквания, но мога да работя и без него (цената е известна производителност).
Създадох няколко помощни таблици, които се попълват автоматично с тригер. Изискването е UPDATE
или DELETE
не е разрешено при посещение таблица.
mst_user таблицата съхранява различен user_id -s и е първо_посещение .user_monthly_visit таблицата съхранява последната и първата data_на_посещение pro user_id и месец .
ТАБЛИЦИ
CREATE TABLE mst_user (
id BIGINT,
first_visit TIMESTAMP,
CONSTRAINT pk_mst_user PRIMARY KEY (id)
);
CREATE TABLE visit (
user_id BIGINT,
visit_date TIMESTAMP,
CONSTRAINT visit_user_fkey FOREIGN KEY (user_id) REFERENCES mst_user (id)
);
CREATE TABLE user_monthly_visit (
user_id BIGINT,
month DATE,
first_visit_this_month TIMESTAMP,
last_visit_this_month TIMESTAMP,
CONSTRAINT pk_user_monthly_visit PRIMARY KEY (user_id, month),
CONSTRAINT user_monthly_visit_user_fkey FOREIGN KEY (user_id) REFERENCES mst_user (id)
);
CREATE INDEX ix_user_monthly_visit_month ON user_monthly_visit(month);
ТРИГЕР
CREATE OR REPLACE FUNCTION trf_visit() RETURNS trigger
VOLATILE
AS $xx$
DECLARE
l_user_id BIGINT;
l_row RECORD;
l_user_monthly_visit user_monthly_visit;
BEGIN
IF (tg_op = 'INSERT')
THEN
l_row := NEW;
INSERT INTO mst_user(id, first_visit) VALUES (l_row.user_id, l_row.visit_date)
ON CONFLICT(id) DO UPDATE SET first_visit = LEAST(mst_user.first_visit, l_row.visit_date);
INSERT INTO user_monthly_visit(user_id,month,first_visit_this_month,last_visit_this_month) VALUES (l_row.user_id,date_trunc('month',l_row.visit_date),l_row.visit_date,l_row.visit_date)
ON CONFLICT(user_id,month) DO UPDATE SET first_visit_this_month = LEAST(user_monthly_visit.first_visit_this_month,l_row.visit_date),
last_visit_this_month = GREATEST(user_monthly_visit.last_visit_this_month,l_row.visit_date);
ELSE
RAISE EXCEPTION 'UPDATE and DELETE arent allowed!';
END IF;
RETURN l_row;
END;
$xx$ LANGUAGE plpgsql;
CREATE TRIGGER trig_visit
BEFORE INSERT OR DELETE OR UPDATE ON visit
FOR EACH ROW
EXECUTE PROCEDURE trf_visit();
ТЕСТНИ ДАННИ
INSERT INTO visit (user_id, visit_date)
VALUES (1, '20200101 122915');
INSERT INTO visit (user_id, visit_date)
VALUES (1, '20200102 123011');
INSERT INTO visit (user_id, visit_date)
VALUES (1, '20200401 123101');
INSERT INTO visit (user_id, visit_date)
VALUES (2, '20200501 123114');
ЗАЯВКА
SELECT mnt AS month, user_id,
CASE WHEN first_visit IS NULL OR first_visit> yyyymm + INTERVAL '1 month' THEN NULL
WHEN first_visit_this_month = first_visit THEN 'FIRST'
WHEN first_visit_this_month IS NULL AND last_three_month + INTERVAL '3 month' >= yyyymm THEN 'RETENTION'
WHEN first_visit_this_month IS NOT NULL THEN 'REACTIVATE'
ELSE NULL
END user_type
FROM
(SELECT date_part('month', gs.yyyymm)::INTEGER AS mnt, gs.yyyymm, u.id user_id, umv.first_visit_this_month, umv.last_visit_this_month, u.first_visit,
GREATEST(
LAG(last_visit_this_month) OVER w,
LAG(last_visit_this_month,2) OVER w,
LAG(last_visit_this_month,3) OVER w
) last_three_month
FROM
generate_series('2020-01-01'::TIMESTAMP, '2020-12-01'::TIMESTAMP, INTERVAL '1 month') gs(yyyymm)
CROSS JOIN mst_user u
LEFT JOIN user_monthly_visit umv on (umv.user_id=u.id AND umv.month = gs.yyyymm)
WINDOW w AS (PARTITION BY u.id ORDER BY gs.yyyymm)
) monthly_visit
ORDER BY user_id,mnt;
РЕЗУЛТАТ
месец | user_id | user_type |
---|---|---|
1 | 1 | ПЪРВИ |
2 | 1 | ЗАДЪРЖАНЕ |
3 | 1 | ЗАДЪРЖАНЕ |
4 | 1 | РЕАКТИВИРАНЕ |
5 | 1 | ЗАДЪРЖАНЕ |
6 | 1 | ЗАДЪРЖАНЕ |
7 | 1 | ЗАДЪРЖАНЕ |
8 | 1 | (null) |
9 | 1 | (null) |
10 | 1 | (null) |
11 | 1 | (null) |
12 | 1 | (null) |
1 | 2 | (null) |
2 | 2 | (null) |
3 | 2 | (null) |
4 | 2 | (null) |
5 | 2 | ПЪРВИ |
6 | 2 | ЗАДЪРЖАНЕ |
7 | 2 | ЗАДЪРЖАНЕ |
8 | 2 | ЗАДЪРЖАНЕ |
9 | 2 | (null) |
10 | 2 | (null) |
11 | 2 | (null) |
12 | 2 | (null) |