SQL Поиск записей за последние 30 дней, сгруппированных по - PullRequest
0 голосов
/ 26 января 2020

Я пытаюсь получить количество клиентов ежедневно для каждого статуса в динамическом окне c - последние 30 дней. В результате запроса каждый день должно отображаться количество клиентов на каждый статус клиента (A, B, C) за последние 30 дней (т. Е. Сегодня () - 29 дней). Каждый клиент может иметь один статус за раз, но при переходе от одного статуса к другому. Цель этого запроса - показать «движение» клиента на протяжении всей его жизни. Я сгенерировал серию дат, начиная с первой даты, когда клиент был создан, до сегодняшнего дня.

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

Я попытался изобразить ниже данные и SQL, которые я использую для достижения оптимального результата.

Начальная точка (пример таблицы customer_statuses):

   customer_id | status | created_at 
---------------------------------------------------
    abcdefg1234   B      2019-08-22
    abcdefg1234   C      2019-01-17 
    ...   
    abcdefg1234   A      2018-01-18 
    bcdefgh2232   A      2017-09-02
    ghijklm4950   B      2018-06-06

статусы - A, B, C Нет последовательного заказа статусов, клиент может иметь любой статус в начале деловых отношений и переключаться между статусами в течение всей жизни.

таблица клиентов:

    id        |      f_name      |      country      |    created_at |
---------------------------------------------------------------------
abcdefg1234      Michael                 FR              2018-01-18
bcdefgh2232      Sandy                   DE              2017-09-02
....
ghijklm4950      Daniel                  NL              2018-06-06

SQL - текущая версия:

WITH customer_list AS (
    SELECT
    DISTINCT a.id,
    a.created_at
    FROM
    customers a
),
dates AS (
     SELECT 
    generate_series(
        MIN(DATE_TRUNC('day', created_at)::DATE),
        MAX(DATE_TRUNC('day', now())::DATE),
        '1d'
        )::date AS day
     FROM customers a
), 
customer_statuses AS (
       SELECT
      customer_id,
      status,
      created_at,
      ROW_NUMBER() OVER 
      (
      PARTITION BY customer_id
      ORDER BY created_at DESC
      ) col
    FROM
        customer_status
)
SELECT
   day,
    (
    SELECT
    COUNT(DISTINCT id) AS accounts
    FROM customers 
    WHERE created_at::date BETWEEN day - 29 AND day
   ),
   status
FROM dates d
    LEFT JOIN customer_list cus
    ON d.day = cus.created_at
    LEFT JOIN customer_statuses cs 
    ON cus.id = cs.customer_id
WHERE
    cs.col = 1
GROUP BY 1,3
ORDER BY 1 DESC,3 ASC

В настоящее время как выглядят результаты запроса:

  day    | count | status
-------------------------
2020-01-24   1230     C
2020-01-24   1230     B
2020-01-24   1230     A
2020-01-23   1200     C
2020-01-23   1200     B
2020-02-23   1200     A
2020-02-22   1150     C
2020-02-22   1150     B
...
2017-01-01    50      C
2017-01-01    50      B
2017-01-01    50      A

Две вещи, которые я заметил из приведенных выше результатов - в большинстве случаев результаты показывают одинаковое количество для всех статусов в данный день. Второе наблюдение, есть дни, когда появляются только два статуса - что не должно иметь место. Если в данный день создаются новые учетные записи с определенным статусом, счет предыдущего дня следует перенести - верно? или это проблема с запросом, который я создал, или с логи c, которые я имею в виду ?? Возможно, я ожидаю результата, который не будет логически получен?

Требуемый результат:

    day    | count | status
-------------------------
2020-01-24   1230     C
2020-01-24   1000     B
2020-01-24   2500     A
2020-01-23   1200     C
2020-01-23   1050     B
2020-02-23   2450     A
2020-02-22   1160     C
2020-02-22   1020     B
2020-02-22   2400     A
...
2017-01-01    10      C
2017-01-01    4       B
2017-01-01   50       A

Спасибо!

Ответы [ 2 ]

1 голос
/ 26 января 2020

Ваш запрос кажется слишком сложным. Вот другой подход:

  • Используйте lead(), чтобы узнать, когда заканчивается статус для каждой записи статуса клиента.
  • Используйте generate_series() для генерации дней.

Остальное только фильтрация и агрегация:

select gs.dte, cs.status, count(*)
from (select cs.*,
             lead(cs.created_at, 1, now()::date) over (partition by cs.customer_id order by cs.created_at) as next_ca
      from customer_statuses cs
     ) cs cross join lateral
     generate_series(cs.created_at, cs.next_ca - interval '1 day', interval '1 day') gs(dte)
where gs.dte < now()::date - interval '30 day'
0 голосов
/ 12 февраля 2020

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

Например, вывод с запросом @ Gordon:

        dte       |  status 
---------------------------
    2020-02-12         B
    ...                ...
    01.02.2020         A
    01.02.2020         B
    31.01.2020         A
    30.01.2020         A

Я адаптировал запрос, см. Ниже, в то время как результаты правильно отображают изменения между статусами (нет повторяющихся записей в день изменения), однако, записи продолжаются до now()::date - interval '1day' и не включают now()::date (как сегодня). Я не уверен, почему и не могу найти правильный лог c, чтобы убедиться, что все так, как я хочу. Даты правильно отображают статус каждого клиента, а возвращаемый статус включает сегодня.

Скорректированный запрос:

select gs.dte, cs.status, count(*)
from (select cs.*,
             lead(cs.created_at, 1, now()::date) over (partition by cs.customer_id order by cs.created_at) - INTERVAL '1day' as next_ca
      from customer_statuses cs
     ) cs cross join lateral
     generate_series(cs.created_at, cs.next_ca, interval '1 day') gs(dte)
where gs.dte < now()::date - interval '30 day'

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

a - добавил уменьшение на 1 день от ведущей функции (строка 3)

lead(cs.created_at, 1, now()::date) over (partition by cs.customer_id order by cs.created_at) - INTERVAL '1 day' as next_ca

b - убрано уменьшение на 1 день от переменной next_ca (строка 6)

generate_series(cs.created_at, cs.next_ca - interval '1 day', interval '1 day')

Пример вывода с настроенным запросом:

       dte       |  status 
---------------------------
    2020-02-11         B
    ...                ...
    01.02.2020         B
    31.01.2020         A
    30.01.2020         A

Спасибо за вашу помощь!

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