Получить последнюю известную запись за месяц в BigQuery - PullRequest
0 голосов
/ 07 ноября 2019

Сбор остатка на счете, который показывает остаток на счете клиента в данный день:

+---------------+---------+------------+
|  customer_id  |  value  | timestamp  |
+---------------+---------+------------+
| 1             |  -500   | 2019-10-12 |
| 1             |  -300   | 2019-10-11 |
| 1             |  -200   | 2019-10-10 |
| 1             |  0      | 2019-10-09 |
| 2             |  200    | 2019-09-10 |
| 1             |  600    | 2019-09-02 |
+---------------+---------+------------+

Обратите внимание, что клиент № 2 не обновлял остаток на своем счете в октябре.

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

+---------------+---------+------------+
|  customer_id  |  value  | timestamp  |
+---------------+---------+------------+
| 1             |  -500   | 2019-10-12 |
| 2             |  200    | 2019-10-10 |
| 2             |  200    | 2019-09-10 |
| 1             |  600    | 2019-09-02 |
+---------------+---------+------------+

Поскольку остаток на счете клиента №2 не обновлялся в октябре, а в сентябре, мы создаем копию строки с сентября, изменяя дату на октябрь. Есть идеи, как этого добиться в BigQuery?

Ответы [ 2 ]

1 голос
/ 07 ноября 2019

Ниже для BigQuery Standard SQL

#standardSQL
WITH customers AS (
  SELECT DISTINCT customer_id FROM `project.dataset.table`
), months AS (
  SELECT month FROM (
    SELECT DATE_TRUNC(MIN(timestamp), MONTH) min_month, DATE_TRUNC(MAX(timestamp), MONTH) max_month
    FROM `project.dataset.table`
  ), UNNEST(GENERATE_DATE_ARRAY(min_month, max_month, INTERVAL 1 MONTH)) month
)
SELECT customer_id, 
  IFNULL(value, LEAD(value) OVER(win)) value,  
  IFNULL(timestamp, DATE_ADD(LEAD(timestamp) OVER(win), INTERVAL DATE_DIFF(month, LEAD(month) OVER(win), MONTH) MONTH)) timestamp  
FROM months, customers
LEFT JOIN (
  SELECT DATE_TRUNC(timestamp, MONTH) month, customer_id, 
    ARRAY_AGG(STRUCT(value, timestamp) ORDER BY timestamp DESC LIMIT 1)[OFFSET(0)].* 
  FROM `project.dataset.table` 
  GROUP BY month, customer_id
) USING(month, customer_id)
WINDOW win AS (PARTITION BY customer_id ORDER BY month DESC)

, если применить к образцу данных из вашего вопроса - как это показано в примере ниже

#standardSQL
WITH `project.dataset.table` AS (
  SELECT 1 customer_id, -500 value, DATE '2019-10-12' timestamp UNION ALL
  SELECT 1, -300, '2019-10-11' UNION ALL
  SELECT 1, -200, '2019-10-10' UNION ALL
  SELECT 2, 200, '2019-09-10' UNION ALL
  SELECT 2, 100, '2019-08-11' UNION ALL
  SELECT 2, 50, '2019-07-12' UNION ALL
  SELECT 1, 600, '2019-09-02' 
), customers AS (
  SELECT DISTINCT customer_id FROM `project.dataset.table`
), months AS (
  SELECT month FROM (
    SELECT DATE_TRUNC(MIN(timestamp), MONTH) min_month, DATE_TRUNC(MAX(timestamp), MONTH) max_month
    FROM `project.dataset.table`
  ), UNNEST(GENERATE_DATE_ARRAY(min_month, max_month, INTERVAL 1 MONTH)) month
)
SELECT customer_id, 
  IFNULL(value, LEAD(value) OVER(win)) value,  
  IFNULL(timestamp, DATE_ADD(LEAD(timestamp) OVER(win), INTERVAL DATE_DIFF(month, LEAD(month) OVER(win), MONTH) MONTH)) timestamp  
FROM months, customers
LEFT JOIN (
  SELECT DATE_TRUNC(timestamp, MONTH) month, customer_id, 
    ARRAY_AGG(STRUCT(value, timestamp) ORDER BY timestamp DESC LIMIT 1)[OFFSET(0)].* 
  FROM `project.dataset.table` 
  GROUP BY month, customer_id
) USING(month, customer_id)
WINDOW win AS (PARTITION BY customer_id ORDER BY month DESC)
-- ORDER BY month DESC, customer_id   

результат равен

Row customer_id value   timestamp    
1   1           -500    2019-10-12   
2   2           200     2019-10-10   
3   1           600     2019-09-02   
4   2           200     2019-09-10   
5   1           null    null     
6   2           100     2019-08-11   
7   1           null    null     
8   2           50      2019-07-12   
1 голос
/ 07 ноября 2019

Следующий запрос должен в основном отвечать на ваш вопрос, создавая запись «на конец месяца» для каждого клиента за каждый месяц и получая самый последний баланс:

with 

-- Generate a set of months
month_begins as (
  select dt from unnest(generate_date_array('2019-01-01','2019-12-01', interval 1 month)) dt
),

-- Get the month ends
month_ends as (
  select date_sub(date_add(dt, interval 1 month), interval 1 day) as month_end_date from month_begins
),

--  Cross Join and group so we get 1 customer record for every month to account for 
--  situations where customer doesn't change balance in a month
user_month_ends as (
  select
    customer_id,
    month_end_date
  from `project.dataset.table`
  cross join month_ends
  group by 1,2
),

--  Fan out so for each month end, you get all balances prior to month end for each customer
values_prior_to_month_end as (
  select
    customer_id,
    value,
    timestamp,
    month_end_date
  from `project.dataset.table`
  inner join user_month_ends using(customer_id)
  where timestamp <= month_end_date
),

-- Order by most recent balance before month end, even if it was more than 1+ months ago
ordered as (
  select
    *,
    row_number() over (partition by customer_id, month_end_date order by timestamp desc) as my_row
  from values_prior_to_month_end
),

-- Finally, select only the most recent record for each customer per month
final as (
  select
    * except(my_row)
  from ordered
  where my_row = 1
)
select * from final
order by customer_id, month_end_date desc

Несколько предостережений:

  1. Я не упорядочил результаты, чтобы они соответствовали желаемому набору результатов, и я также сохранил дату окончания месяца, чтобы проиллюстрировать концепцию. Вы можете легко изменить порядок и исключить ненужные поля.
  2. В month_begins CTE я установил диапазон месяцев в будущем, поэтому ваш набор результатов будет содержать самый последний баланс «будущих месяцев». Чтобы сделать это немного красивее, рассмотрите возможность изменения '2019-12-01' на 'current_date ()', и ваш запрос всегда будет возвращаться к концу текущего месяца.
  3. Ваше поле timestamp выглядит как даты, поэтому яиспользовала логику даты, но вы должны иметь возможность применять те же принципы для использования логики временных меток, если ваши базовые поля являются фактическими временными метками.
  4. В вашем наборе результатов я не уверен, почему ваша вторая строка (клиент 2)будет иметь метку времени '2019-10-10', которая кажется произвольной, поскольку у клиента 2 нет 2-й записи баланса.
  5. Я намеренно разделил логику на несколько CTE, чтобы я мог легче комментировать каждый шаг, вы могли быопределенно выполните несколько шагов в одном блоке кода для более сжатого запроса.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...