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 中的自动解决方案。
基本上
- 使用 python+bigquery 收集数据透视列信息:展平数组并获得不同的 metadata.key 值
- 在 python 中,使用步骤 1
中的 metadata.key 信息准备一个带有自定义数据透视表的 sql 语句
- 运行 那句话
能拿到这样的剧本,有点难度。这是可能的,尽管您可能需要进行大量编码和 try-error。如果 table 非常广泛,您可能希望使用 python(如 martin weitzmann
所建议的那样)来检索列信息并创建脚本来获取数据。
您也可以只使用 BigQuery,但您可能会发现很难在大型 table 上实施,但这是我的方法,如果适合您的场景,您可以尝试这种方法:
- 用一些记录创建我们的测试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"));
- 让我们声明我们的工作变量
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 "";
- 设置我们的工作变量
# 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%');
- 获取我们的嵌套列
# 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;
- 然后我们通过创建自定义查询和 运行 来完成。
# 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 PIVOT
和 EXECUTE 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
输出是
我有这个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 中的自动解决方案。 基本上
- 使用 python+bigquery 收集数据透视列信息:展平数组并获得不同的 metadata.key 值
- 在 python 中,使用步骤 1 中的 metadata.key 信息准备一个带有自定义数据透视表的 sql 语句
- 运行 那句话
能拿到这样的剧本,有点难度。这是可能的,尽管您可能需要进行大量编码和 try-error。如果 table 非常广泛,您可能希望使用 python(如 martin weitzmann
所建议的那样)来检索列信息并创建脚本来获取数据。
您也可以只使用 BigQuery,但您可能会发现很难在大型 table 上实施,但这是我的方法,如果适合您的场景,您可以尝试这种方法:
- 用一些记录创建我们的测试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"));
- 让我们声明我们的工作变量
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 "";
- 设置我们的工作变量
# 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%');
- 获取我们的嵌套列
# 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;
- 然后我们通过创建自定义查询和 运行 来完成。
# 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 PIVOT
和 EXECUTE 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
输出是