Google BigQuery:UNNEST,其中每个不同的键成为一列

Google BigQuery: UNNEST where each different key becomes a column

我有这个table,其中有几列包含字典:payloadKV、metaKV 等

我需要取消字典的嵌套并旋转结果以将每个键放在一列中,并将值放在该行、列的对应单元格中。 上面截图的期望输出是:

+---------------------+-------+---------------+----------------+---------------------+-----+
|   ingestTimestamp   |  ...  | metadata.Area |  metadata.Cell | metadata.Department | ... |
+---------------------+-------+---------------+----------------+---------------------+-----+
| 2022-03-23 02:34:41 |   ... | MC            |           0010 |                0752 | ... |
| ...                 |   ... | ...           |            ... |                 ... | ... |
+---------------------+-------+---------------+----------------+---------------------+-----+

这些词典中的每一个都有任意数量的key/values,可以是数百个,我无法事先知道键名,所以我需要一些通用表达式来提取它们。

我看到了如何通过硬编码来提取所需密钥的示例,但我似乎找不到通用的方法来执行此操作。

在数据库中,行是观察结果,列描述这些观察结果。 您想要 non-consistently 描述观察结果 - 这是数据库的元级别,仅在这方面保持一致。

您可以cross join with an unnested array and then pivot,但“您”仍然需要提前知道列名。

“您”可以是您本人,也可以是通过预先收集信息来准备 SQL 语句的一段代码 - 例如,这可以是 python 中的自动解决方案。 基本上

  1. 使用 python+bigquery 收集数据透视列信息:展平数组并获得不同的 metadata.key 值
  2. 在 python 中,使用步骤 1
  3. 中的 metadata.key 信息准备一个带有自定义数据透视表的 sql 语句
  4. 运行 那句话

能拿到这样的剧本,有点难度。这是可能的,尽管您可能需要进行大量编码和 try-error。如果 table 非常广泛,您可能希望使用 python(如 martin weitzmann 所建议的那样)来检索列信息并创建脚本来获取数据。

您也可以只使用 BigQuery,但您可能会发现很难在大型 table 上实施,但这是我的方法,如果适合您的场景,您可以尝试这种方法:

  1. 用一些记录创建我们的测试table
create or replace table`projectid.dataset.table`
(
    id INT64,
    ingestTimeStamp date,
    payloadKV STRUCT<id INT64,json STRING>,
    metaKV STRUCT<id INT64,description STRING>
)

insert into `projectid.dataset.table`(id,ingestTimeStamp,payloadKV,metaKV)values(1,"2022-03-03",(100,'{"kardexid":11,"desc":"d1"}'),(100,"a desc1"));
insert into `projectid.dataset.table`(id,ingestTimeStamp,payloadKV,metaKV)values(2,"2022-03-04",(101,'{"kardexid":22,"desc":"d2"}'),(110,"a desc2"));
insert into `projectid.dataset.table`(id,ingestTimeStamp,payloadKV,metaKV)values(3,"2022-03-05",(102,'{"kardexid":34,"desc":"d3"}'),(120,"a desc3"));
insert into `projectid.dataset.table`(id,ingestTimeStamp,payloadKV,metaKV)values(4,"2022-03-06",(103,'{"kardexid":53,"desc":"d4"}'),(130,"a desc4"));
  1. 让我们声明我们的工作变量
declare working_table string;
declare loop_col String;
declare query String;
declare single_col_names String;
declare nested_col_array ARRAY<STRING>;
declare nested_col_string String DEFAULT "";
  1. 设置我们的工作变量
# Set columns to work
set working_table = "table";

set single_col_names = (SELECT STRING_AGG(column_name) FROM `projectid.dataset.INFORMATION_SCHEMA.COLUMNS`
where table_name = working_table and data_type not like 'STRUCT%');

set nested_col_array = (SELECT ARRAY_AGG(column_name) FROM `projectid.dataset.INFORMATION_SCHEMA.COLUMNS`
where table_name = working_table and data_type like 'STRUCT%');
  1. 获取我们的嵌套列
# Retrieve nested columns
FOR record IN
  (SELECT * FROM unnest(nested_col_array) as col_names)
DO   
    SET loop_col = (SELECT CONCAT(column_name,
                                    ".",
                                    REPLACE(ARRAY_TO_STRING(REGEXP_EXTRACT_ALL(data_type,r'[STRUCT<,INT64 STRING ]+(.+?) '),",")
                                            ,",",
                                            CONCAT(",",column_name,".")))
    FROM `projectid.dataset.INFORMATION_SCHEMA.COLUMNS`
    where table_name = working_table and data_type like 'STRUCT%' and column_name=record.col_names);

    SET nested_col_string = (SELECT CONCAT(nested_col_string,",",loop_col));
END FOR;
  1. 然后我们通过创建自定义查询和 运行 来完成。
# build & run query
set query = (SELECT FORMAT("select %s%s from `projectid.dataset.table` order by 1",single_col_names,nested_col_string));
EXECUTE IMMEDIATE(query);

输出:

id ingestTimeStamp id_1 json id_2 description
1 2022-03-03 100 {"kardexid":11,"desc":"d1"} 100 a desc1
2 2022-03-04 101 {"kardexid":22,"desc":"d2"} 110 a desc2
3 2022-03-05 102 {"kardexid":34,"desc":"d3"} 120 a desc3
4 2022-03-06 103 {"kardexid":53,"desc":"d4"} 130 a desc4

如您所见,像 BigQuery 上的这个过程这样的过程非常具有挑战性,因为您必须解析结构类型以获取内部列的名称、编写脚本并且绝对不是最佳选择。为了这个问题,这是可以做到的,但我不推荐这样做。

在处理 BigQuery 时,您通常希望进行资源投入较少的查询,只选择真正需要的查询。您可以使用 client libraries 在 BigQuery 端执行更少的操作,并使用代码对从原始查询中获得的数据执行转换。

为了创建此代码,我参考了以下文档,检查它:

使用 BigQuery PIVOTEXECUTE IMMEDIATE 可以相对容易地完成,如下例

create temp table temp as (
  select t.* except(payloadKV, metaKV), replace(key, '.', '_') key, value
  from your_table t, unnest(payloadKV)
  union all
  select t.* except(payloadKV, metaKV), replace(key, '.', '_') key, value
  from your_table t, unnest(metaKV)
);

execute immediate (select '''
  select * from temp pivot (any_value(value) for key in (''' || 
  (select string_agg("'" || key || "'", ',' order by key) from (select distinct key from temp))
  || '''))
''')           

如果应用于样本数据(类似于)您的问题

select '2022-03-23 02:34:41' ingestTimestamp, [
    struct('payload.Area' as key, 'MC1' as value), ('payload.Cell', '00101'), ('payload.Department', '07521')] payloadKV, [
    struct('metadata.Area' as key, 'MC' as value),('metadata.Cell', '0010'), ('metadata.Department', '0752')] metaKV
union all
select '2022-03-24 02:34:41' ingestTimestamp, [
    struct('payload.Area' as key, 'MC2' as value), ('payload.Cell', '00102'), ('payload.Department', '07522')] payloadKV, [
    struct('metadata.Area' as key, 'MC3' as value),('metadata.Cell', '00103'), ('metadata.Department', '07523')] metaKV

输出是