【问题标题】:How to find combination of intersection from many tables?如何从许多表中找到交集的组合?
【发布时间】:2021-02-01 04:56:53
【问题描述】:

我列出了可能将用户带到网站的不同渠道(自然、搜索引擎优化、在线营销等)。我想找到一种有效的方法来计算来自这些渠道组合的每日活跃用户。每个频道都有自己的表格并跟踪其各自的用户。

表格如下所示,

channel A
date         user_id
2020-08-01   A
2020-08-01   B
2020-08-01   C

channel B
date         user_id
2020-08-01   C
2020-08-01   D
2020-08-01   G

channel C
date         user_id
2020-08-01   A
2020-08-01   C
2020-08-01   F

我想知道以下组合

  1. 只访问A频道
  2. 仅访问通道 A 和 B
  3. 只访问B&C频道
  4. 只访问B频道

但是,当频道很多时(我有大约 8 个频道),组合就会很多。我做的大致就是这么简单(这个包括A频道)

SELECT 
    a.date, 
    COUNT(DISTINCT IF(b.user_id IS NULL AND c.user_id IS NULL, a.user_id, NULL)) AS dau_a,
    COUNT(DISTINCT IF(b.user_id IS NOT NULL AND c.user_id IS NULL, a.user_id, NULL)) AS dau_a_b,
    ...
FROM a LEFT JOIN b ON a.user_id = b.user_id AND a.date = b.date 
LEFT JOIN c ON a.user_id = c.user_id AND a.date = c.date
GROUP BY 1

但当总通道数为 8 时(2 种组合 28 种变体,3 种组合 56 种,4 种组合 70 种,等等),则非常繁琐。

有什么聪明的想法可以解决这个问题吗?我正在考虑使用FULL OUTER JOIN,但似乎无法摆脱它。非常感谢您的回答。

【问题讨论】:

  • 期望的结果真的很有帮助。在这种情况下,“每日活跃用户”是什么意思?
  • 这将是每个日期访问该网站的唯一用户数,在这种情况下,我想知道来自这些渠道组合的数字。

标签: sql count google-bigquery full-outer-join


【解决方案1】:

但是,当频道很多时(我大约有 8 个频道),组合很多

当总通道数为 8 时(2 种组合有 28 种变化,3 种组合 56 种,4 种组合 70 种,等等),这非常乏味。

有什么聪明的想法可以解决这个问题吗?

以下是 BigQuery 标准 SQL 并解决了 OP 关注的上述方面

#standardSQL
CREATE TEMP FUNCTION generate_combinations(a ARRAY<INT64>) 
RETURNS ARRAY<STRING>
LANGUAGE js AS '''
  var combine = function(a) {
    var fn = function(n, src, got, all) {
      if (n == 0) {
        if (got.length > 0) {
          all[all.length] = got;
        } return;
      }
      for (var j = 0; j < src.length; j++) {
        fn(n - 1, src.slice(j + 1), got.concat([src[j]]), all);
      } return;
    }
    var all = []; for (var i = 1; i < a.length; i++) {
      fn(i, a, [], all);
    }
    all.push(a);
    return all;
  } 
  return combine(a)
''';
with users as (
    select distinct date, user_id, 'A' channel from channel_A union all
    select distinct date, user_id, 'B' from channel_B union all
    select distinct date, user_id, 'C' from channel_C 
), visits as (
  select date, user_id, 
    string_agg(channel, ' & ' order by channel) combination
  from users
  group by date, user_id
), channels AS (
  select channel, cast(row_number() over(order by channel) as string) channel_num
  from (select distinct channel from users)
), combinations as (
  select string_agg(channel, ' & ' order by channel_num) combination
  from unnest(generate_combinations(generate_array(1,(select count(1) from channels)))) AS items, 
    unnest(split(items)) AS channel_num
  join channels using(channel_num)
  group by items
)
select date, 
  combination as channels_visited_only, 
  count(distinct user_id) dau
from visits
join combinations using (combination)
group by date, combination
order by combination

如果应用于您问题中的样本数据 - 输出是

一些解释,以帮助使用上述

  • CTE users 只是简单地合并所有表并添加通道列以便能够区分相应行来自哪个表

  • CTE visits 提取每个用户日期组合的所有访问频道列表

  • CTE channels 只是简单地准备频道列表并分配编号以供以后使用

  • CTE combinations 使用 JS UDF 生成频道编号的所有组合,然后将它们连接回频道以生成频道组合

  • 最后的 SELECT 语句只是查找那些访问过的频道列表与上一步生成的频道组合匹配的用户

进一步精简上述代码的一些建议

  • 假设您的频道表名称遵循channel_* 模式

您可以在users CTE 中使用通配符表功能来代替

select distinct date, user_id, 'A' channel from channel_A union all
select distinct date, user_id, 'B' from channel_B union all
select distinct date, user_id, 'C' from channel_C 

你可以使用类似下面的东西 - 所以只有一行而不是你拥有的cannles那么多行

select distinct date, user_id, _TABLE_SUFFIX as channel from channel_*      

【讨论】:

  • 哇,我不知道通配符,非常感谢!但是,我在我的桌子上尝试了上面的解决方案,结果与@gordon-linoff 答案相同,所以我想知道除了combinations 之外的所有 CTE 已经足够了吗?
  • 哎呀,抱歉,在处理您需要提前了解所有可能的频道排列的问题时,我想到了其他/额外内容。但这绝对不是您最初问题的一部分。所以底线 - 是的 - 对于您的案例组合,这里不需要 cte
【解决方案2】:

我会使用union all 和两个级别的聚合来解决这个问题:

select date, channels, count(*) as num_users
from (select date, user_id, string_agg(channel order by channel) as channels
      from ((select distinct date, user_id, 'a' as channel from a) union all
            (select distinct date, user_id, 'b' as channel from b) union all
            (select distinct date, user_id, 'c' as channel from c) 
           ) abc
      group by date, user_id
     ) c
group by date, channels;
  

【讨论】:

  • 谢谢!这个可行,但我仍在审查@mikhail-berlyant 答案
【解决方案3】:

我在想full join 和聚合:

select date, a.channel_a, b.channel_b, c.channel_c, count(*) cnt
from      (select 'a' channel_a, a.* from channel_a) a
full join (select 'b' channel_b, b.* from channel_b b) b using (date, user_id)
full join (select 'c' channel_c, c.* from channel_c c) c using (date, user_id)
group by date, a.channel_a, b.channel_b, c.channel_c

【讨论】:

  • 抱歉,我不太明白select 'a' channel_a, a.* from channel_a 的子查询,在这种情况下a.* 是否调用'a' channel_a?似乎无法在我的控制台上运行
【解决方案4】:

我认为您可以使用集合运算符来回答您的问题:https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#set_operators

例如

  1. 是(A 除外 B)C 除外
  2. 是A相交B

等等

【讨论】:

  • 刚刚发现了这些新的运算符,例如 EXCEPT DISTINCT 和 INTERSECT DISTINCT,谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多