Redshift SUPER 类型上的聚合

tim*_*hap 5 aggregate correlated-subquery amazon-redshift

语境

我正在尝试找到在 Redshift 中表示和聚合高基数列的最佳方式。源是基于事件的,看起来像这样:

用户 时间戳 事件类型
1 2021-01-01 12:00:00
1 2021-01-01 15:00:00 酒吧
2 2021-01-01 16:00:00
2 2021-01-01 19:00:00

在哪里:

  • 用户数量非常多
  • 单个用户可以拥有大量事件,但不太可能拥有许多不同的事件类型
  • 不同event_type值的数量非常大,并且不断增长

我想将这些数据聚合成一个更小的数据集,每个用户只有一条记录(文档)。这些文件随后将被导出。感兴趣的聚合是这样的:

  • 活动数量
  • 最近活动时间

但是也:

  • 每个 event_type 的事件数

我发现后一种情况很困难。

我考虑过的解决方案

解决此问题的简单“列数据库友好”方法就是为每个事件类型创建一个聚合列:

用户 nb_事件 ... NB_foo nb_bar
1 2 ... 1 1
2 2 ... 2 0

但我认为这不是一个合适的解决方案,因为 event_type 字段是动态的,可能有数百或数千个值(Redshift 的上限为 1600 列)。而且,这个 event_type 字段上可能有多种类型的聚合(不仅仅是count)。

第二种方法是将数据保持垂直形式,其中不是每个用户一行,而是每个(user, event_type)一行。然而,这实际上只是推迟了问题——在某些时候,数据仍然需要聚合成每个用户的单个记录以实现目标文档结构,并且列爆炸的问题仍然存在。

该数据的更自然的(我认为)表示是稀疏数组/文档/SUPER:

用户 nb_事件 ... 按事件类型计数(超级)
1 2 ... {“foo”:1,“酒吧”:1}
2 2 ... {“富”:2}

这也几乎完全符合AWS 文档描述的预期超级用例:

当您需要存储相对较小的键值对集时,可以通过以 JSON 格式存储数据来节省空间。由于 JSON 字符串可以存储在单列中,因此使用 JSON 可能比以表格格式存储数据更有效。例如,假设您有一个稀疏表,其中需要有许多列来完全表示所有可能的属性,但对于任何给定行或任何给定列,大多数列值为 NULL。通过使用 JSON 进行存储,您可以将行数据存储在单个 JSON 字符串中的键:值对中,并消除稀疏填充的表列。

这就是我一直在尝试实施的方法。但我还没有完全实现我所希望的,主要是由于填充和聚合 SUPER 列的困难。这些描述如下:

问题

问题一:

如何从另一个 SELECT 查询插入这种 SUPER 列?所有 Redshift 文档仅在初始数据加载的上下文中真正讨论 SUPER 列(例如,通过使用json_parse),但从未讨论从另一个 Redshift 查询生成此数据的情况。我理解这是因为首选方法是加载超级数据,但尽快将其转换为柱状数据。

问题2:

如何重新聚合这种 SUPER 列,同时保留 SUPER 结构?到目前为止,我已经讨论了一个仅按用户聚合的简化示例。实际上,还有其他维度的聚合,对此表的某些分析将需要重新聚合上表中显示的值。通过类比,所需的输出可能类似于(聚合所有用户):

nb_事件 ... 按事件类型计数(超级)
4 ... {“foo”:3,“酒吧”:1}

我可以通过如下查询接近实现这种重新聚合(其中listagg键值字符串对是我不知道如何做的 SUPER 类型构造的替代):

select
  sum(nb_events) nb_events,
  (
      select listagg(s)
      from (
          select
              k::text || ':' || sum(v)::text as s
          from my_aggregated_table inner_query,
              unpivot inner_query.count_by_event_type as v at k
          group by k
      ) a
  ) count_by_event_type
from my_aggregated_table outer_query
Run Code Online (Sandbox Code Playgroud)

但Redshift不支持这种关联查询:

[0A000] 错误:尚不支持这种类型的相关子查询模式

Q3:

是否有任何替代方法可供考虑?通常我会使用 Spark 来处理此类问题,我发现 Spark 对于此类问题更加灵活。但如果可能的话,坚持使用 Redshift 会很棒,因为那是源数据所在的位置。