【问题标题】:Google Big Query SQL - Get most recent unique value by dateGoogle Bigquery SQL - 按日期获取最新的唯一值
【发布时间】:2019-03-13 04:56:37
【问题描述】:

#EDIT - 在 cmets 之后,我重新表述我的问题

我有一个 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;

带有 RANK() OVER 的 SELECT 正确返回具有排名列的用户,该列允许我知道哪个条目是每个 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. 错误...

哪种方案更能成功?我应该改变什么?

而且,如果我存储数据的方式不好,我应该如何继续以保持准确的历史记录?

【问题讨论】:

  • 请编辑您的问题以显示您遇到问题的代码的Minimal, Complete, and Verifiable example,然后我们可以尝试帮助解决具体问题。你也可以阅读How to Ask
  • 谢谢@MikhailBerlyant,我用自己的进步重新表述了整个问题,希望现在可以了。

标签: sql google-bigquery google-data-studio


【解决方案1】:

以下是 BigQuery 标准 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    

【讨论】:

  • 您好,感谢您的回答!实际上,这给了我:date | total2018-06-17 | 12018-05-13 | 12018-05-04 | 1 我希望每天都有总活跃用户,而不仅仅是新的活跃用户。
  • 我认为您需要更好地解释您的数据@RobinToppan。您是说这些是状态何时从活动变为非活动以及反之亦然的指标吗?
  • 是的,每次用户状态发生变化时,我都会为用户保存一个新条目,以便获得更好的年表。我想获取在某个日期实际活跃的所有用户。
  • @RobinToppan 您的示例输入表和示例输出结果不匹配。您将需要提供可以通过给定示例输入获得的输出。这样我们就更容易回答了。
  • 完美运行,非常感谢!我真的被这个案子困住了。祝您有美好的一天,再次感谢!
【解决方案2】:

我认为这 - 或类似的东西 - 会做你想要的:

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() 生成日期。如果您有所有日期的数据,则该步骤可能是不必要的

【讨论】:

  • 感谢您的回答!我应该添加什么 ON 子句?实际上左连接需要一个。
  • @RobinToppan 。 . .哎呀。它确实需要一个on 子句。
  • 谢谢!我试过了,我的合并总是 0,当我只尝试子请求时,似乎 running_inactives 总是大于 runnin_actives。这很正常,因为每个活跃用户之前都是非活跃用户,所以充其量我们对每一列都有相同的值。
猜你喜欢
  • 2014-10-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-12-22
  • 2021-11-19
  • 1970-01-01
相关资源
最近更新 更多