#EDIT - После комментариев я перефразирую свой вопрос

У меня есть таблица BigQuery, которую я хочу использовать для получения некоторых KPI моего приложения. В этой таблице я сохраняю каждое создание или обновление как новую строку, чтобы лучше сохранять историю. Так что у меня несколько раз одни и те же данные с разным состоянием.

Пример таблицы :

uuid  |status     |date         
––––––|–––––––––––|––––––––––      
3     |'inactive' |2018-05-12
1     |'active'   |2018-05-10
1     |'inactive' |2018-05-08
2     |'active'   |2018-05-08
3     |'active'   |2018-05-04
2     |'inactive' |2018-04-22
3     |'inactive' |2018-04-18

Мы видим, что у нас есть несколько значений для каждых данных.

Что бы я хотел получить :

Я хотел бы иметь номер текущей «активной» записи (поэтому не должно быть «неактивной» записи с таким же uuid после). И чтобы все усложнить, мне нужна эта сумма за день . Итак, для каждого дня количество «активных» записей, включая записи за предыдущие дни.

Итак, в этом примере у меня должен быть такой результат:

date        | actives
____________|_________
2018-05-02  |   0
2018-05-03  |   0
2018-05-04  |   1
2018-05-05  |   1
2018-05-06  |   1
2018-05-07  |   1
2018-05-08  |   2
2018-05-09  |   2
2018-05-10  |   3
2018-05-11  |   3
2018-05-12  |   2

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

Что я пробовал :

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

Первое решение :

WITH
  dates AS(
      SELECT GENERATE_DATE_ARRAY(
          DATE_SUB(CURRENT_DATE(), INTERVAL 6 MONTH), CURRENT_DATE(), INTERVAL 1 DAY)               
      arr_dates )
SELECT
  i_date date,
  (
  SELECT COUNT(uuid)
  FROM (
    SELECT
      uuid, status, date,
      RANK() OVER(PARTITION BY uuid ORDER BY date DESC) rank
    FROM users
    WHERE
      PARSE_DATE("%Y-%m-%d", FORMAT_DATETIME("%Y-%m-%d",date)) <= i_date
  )
  WHERE
    status = 'active'
    and rank = 1
    ## rank is the condition which causes the error
  ) users
FROM
  dates, UNNEST(arr_dates) i_date
ORDER BY i_date;

SELECT с RANK () OVER правильно возвращает пользователей со столбцом ранга, который позволяет мне знать, какая запись является последней для каждого uuid. Но когда я пробую это, у меня появляется: Correlated subqueries that reference other tables are not supported unless they can be de-correlated, such as by transforming them into an efficient JOIN. из-за условия rank = 1 .

Второе решение :

WITH
  dates AS(
      SELECT GENERATE_DATE_ARRAY(
          DATE_SUB(CURRENT_DATE(), INTERVAL 6 MONTH), CURRENT_DATE(), INTERVAL 1 DAY)               
      arr_dates )
SELECT
  i_date date,
  (
  SELECT
    COUNT(t1.uuid)
  FROM
    users t1
  WHERE
    t1.date = (
      SELECT MAX(t2.date)
      FROM users t2
      WHERE
        t2.uuid = t1.uuid
        ## Here that's the i_date condition which causes problem 
        AND PARSE_DATE("%Y-%m-%d", FORMAT_DATETIME("%Y-%m-%d", t2.date)) <= i_date 
    )
    AND status='active' ) users
FROM
  dates,
  UNNEST(arr_dates) i_date
ORDER BY i_date;

Здесь второй выбор тоже работает и правильно возвращает количество активных пользователей за текущий день. Но проблема в том, когда я пытаюсь использовать i_date для получения данных за несколько дней. И вот у меня ошибка LEFT OUTER JOIN cannot be used without a condition that is an equality of fields from both sides of the join. ...

Какое решение более успешное? Что мне следует изменить?

И, если мой способ хранения данных не подходит, как мне действовать, чтобы вести точную историю?

1
RobinToppan 8 Окт 2018 в 16:41

2 ответа

Лучший ответ

Ниже для BigQuery Standard SQL

#standardSQL
SELECT date, COUNT(DISTINCT uuid) total_active 
FROM `project.dataset.table`
WHERE status = 'active'
GROUP BY date 
-- ORDER BY date   

Обновите, чтобы ответить на ваш "перефразированный" вопрос: o)
Ниже в примере используются фиктивные данные из вашего вопроса.

#standardSQL
WITH `project.dataset.users` AS (
  SELECT 3 uuid, 'inactive' status, DATE '2018-05-12' date UNION ALL
  SELECT 1, 'active', '2018-05-10' UNION ALL
  SELECT 1, 'inactive', '2018-05-08' UNION ALL
  SELECT 2, 'active', '2018-05-08' UNION ALL
  SELECT 3, 'active', '2018-05-04' UNION ALL
  SELECT 2, 'inactive', '2018-04-22' UNION ALL
  SELECT 3, 'inactive', '2018-04-18' 
), dates AS (
  SELECT day FROM UNNEST((
    SELECT GENERATE_DATE_ARRAY(MIN(date), MAX(date))
    FROM `project.dataset.users`
  )) day
), active_users AS (
  SELECT uuid, status, date first, DATE_SUB(next_status.date, INTERVAL 1 DAY) last FROM (
    SELECT uuid, date, status, LEAD(STRUCT(status, date)) OVER(PARTITION BY uuid ORDER BY date ) next_status
    FROM `project.dataset.users` u
  )
  WHERE status = 'active'
)
SELECT day, COUNT(DISTINCT uuid) actives
FROM dates d JOIN active_users u
ON day BETWEEN first AND IFNULL(last, day)
GROUP BY day 
-- ORDER BY day

С результатом

Row day         actives  
1   2018-05-04  1    
2   2018-05-05  1    
3   2018-05-06  1    
4   2018-05-07  1    
5   2018-05-08  2    
6   2018-05-09  2    
7   2018-05-10  3    
8   2018-05-11  3    
9   2018-05-12  2    
2
Mikhail Berlyant 10 Окт 2018 в 01:08

Я думаю, что это - или что-то подобное - сделает то, что вы хотите:

SELECT day,
       coalesce(running_actives, 0) - coalesce(running_inactives, 0)
FROM UNNEST(GENERATE_DATE_ARRAY(DATE('2015-05-11'), DATE('2018-06-29'), INTERVAL 1 DAY)
           ) AS day left join
     (select date, sum(countif(status = 'active')) over (order by date) as running_actives,
             sum(countif(status = 'active')) over (order by date) as running_inactives
      from t
      group by date
     ) a
     on a.date = day
order by day;

Точное решение зависит от того, включает ли «неактивный» день (как указано выше) или вступает в силу на следующий день. И то, и другое обрабатывается одинаково, используя совокупные суммы активных и неактивных активов, а затем вычитая разницу.

Чтобы получить данные за все дни, он генерирует дни с использованием массивов и unnest(). Если у вас есть данные за все дни, этот шаг может быть ненужным.

0
Gordon Linoff 9 Окт 2018 в 15:25