Подсчет отдельных клиентов, активных в течение года, для каждой недели в году. - PullRequest
1 голос
/ 16 июня 2020

Я работаю с существующей базой данных электронной коммерции. На самом деле, этот процесс обычно выполняется в Excel, но мы хотим попробовать его напрямую с запросом в PostgreSQL (версия 10.6).

Мы определяем как активного клиента человека, который хотя бы раз совершил покупку в течение 1 год. Это означает, что если я проанализирую 22-ю неделю в 2020 году, активным клиентом будет тот, который купил хотя бы один раз с 22-й недели 2019 года.

Я хочу получить результат за каждую неделю года (2020). В основном мне нужно ...

select
    email,
    orderdate,
    id
from
    orders_table
where
    paid = true;

|---------------------|-------------------|-----------------|
|      email          |     orderdate     |        id       |
|---------------------|-------------------|-----------------|
|  email1@email.com   |2020-06-02 05:04:32|     Order-2736  |
|---------------------|-------------------|-----------------|

Я не могу создавать новые таблицы. И я бы хотел, чтобы результат был таким:

Year| Week | Active customers
2020| 25   | 6978
2020| 24   | 3948

Ответы [ 2 ]

0 голосов
/ 18 июня 2020

если я проанализирую 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. См .:

Использование сравнения значений строк для упрощения определения диапазона . См .:

0 голосов
/ 17 июня 2020

в зависимости от того, есть ли столбцы year и week, вы можете использовать OVER (PARTITION BY ...) с extract:

SELECT
    extract(year from orderdate),
    extract(week from orderdate),
    sum(1) as customer_count_in_week,
    OVER (PARTITION BY extract(YEAR FROM TIMESTAMP orderdate), 
                       extract(WEEK FROM TIMESTAMP orderdate))
FROM ordertable
WHERE paid=true;

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

ссылки:

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