если я проанализирую 22-ю неделю в 2020 году, активным клиентом будет тот, который купил хотя бы один раз с 22-й недели 2019 года.
Проблемы на вашей стороне
Этот метод имеет некоторые двусмысленности / проблемы в крайнем случае:
Вы включаете или исключаете «22 неделя 2020 года»? (Я исключаю это ниже, чтобы оставаться ближе к «году».)
В году может быть 52 или 53 полных недели. В зависимости от текущей даты расчет основан на 52 или 53 неделях, что приводит к возможной погрешности почти в 2%!
Если вы начнете временной диапазон "в тот же день прошлого года" ", то погрешность составляет всего 1/365 или ~ 0,3% из-за високосных лет.
Фиксированный« период в 365 дней »(или 366) полностью устранит смещение.
Проблемы на стороне SQL
К сожалению, оконные функции в настоящее время не позволяют использовать ключевое слово DISTINCT
(по уважительным причинам). Итак, что-то вроде:
SELECT count(DISTINCT email) OVER (ORDER BY year, week
GROUPS BETWEEN 52 PRECEDING AND 1 PRECEDING)
FROM ...
.. триггеры:
ERROR: DISTINCT is not implemented for window functions
Ключевое слово GROUPS
было добавлено только в Postgres 10, а в противном случае было бы именно то, что нам нужно.
Более того, ваше определение нечетного кадра даже не будет работать точно, поскольку количество недель, которые следует учитывать, не всегда равно 52, как обсуждалось выше.
Итак, мы должны свернуть свои собственные.
Решение
Следующее просто генерирует все недели интереса и вычисляет отдельное количество клиентов для каждой. Просто, за исключением того, что математика дат никогда не бывает полностью простой. Но, в зависимости от деталей вашей настройки, могут быть более быстрые решения. (У меня было несколько других идей.)
Диапазон времени, за который нужно сообщить, может измениться. Вот вспомогательная функция для генерации недель данного года:
CREATE OR REPLACE FUNCTION f_weeks_of_year(_year int)
RETURNS TABLE(year int, week int, week_start timestamp)
LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE
ROWS 52 COST 10 AS
$func$
SELECT _year, d.week::int, d.week_start
FROM generate_series(date_trunc('week', make_date(_year, 01, 04)::timestamp) -- first day of first week
, LEAST(date_trunc('week', localtimestamp), make_date(_year, 12, 28)::timestamp) -- latest possible start of week
, interval '1 week') WITH ORDINALITY d(week_start, week)
$func$;
Call:
SELECT * FROM f_weeks_of_year(2020);
Она возвращает 1 строку в неделю, но останавливается на текущая неделя текущего года. (Пустой набор для будущих лет.)
Расчет основан на этих фактах :
- Первая неделя года по ISO всегда содержит 04 января.
- Последняя неделя ISO не может начаться после 28 декабря.
Фактические номера недель вычисляются на лету с использованием WITH ORDINALITY
. См .:
Кроме того, я придерживаюсь timestamp
и избегаю timestamptz
для этого цель. См .:
Функция также возвращает временную метку начала недели (week_start
), который нам не нужен для решения рассматриваемой задачи. Но я оставил это, чтобы сделать функцию более полезной в целом.
Делает основной запрос проще:
WITH weekly_customer AS (
SELECT DISTINCT
EXTRACT(YEAR FROM orderdate)::int AS year
, EXTRACT(WEEK FROM orderdate)::int AS week
, email
FROM orders_table
WHERE paid
AND orderdate >= date_trunc('week', timestamp '2019-01-04') -- max range for 2020!
ORDER BY 1, 2, 3 -- optional, might improve performance
)
SELECT d.year, d.week
, (SELECT count(DISTINCT email)
FROM weekly_customer w
WHERE (w.year, w.week) >= (d.year - 1, d.week) -- row values, see below
AND (w.year, w.week) < (d.year , d.week) -- exclude current week
) AS active_customers
FROM f_weeks_of_year(2020) d; -- (year int, week int, week_start timestamp)
db <> fiddle здесь
CTE weekly_customer
сводится к уникальным клиентам за календарную неделю один раз, поскольку повторяющиеся записи - это просто шум для наших расчетов. Он используется много раз в основном запросе. Условие отключения снова основано на 4 января. Отрегулируйте фактический отчетный период.
Фактический подсчет выполняется с помощью слабо коррелированного подзапроса. Вместо этого может быть LEFT JOIN LATERAL ... ON true
. См .:
Использование сравнения значений строк для упрощения определения диапазона . См .: