A 要求:
让我们假设下面的简化案例/场景
1 我们有一个“大”表:
全部表格
Row a b c
1 1 11 12
2 1 13 14
3 1 15 16
4 1 17 18
5 2 21 22
6 2 23 24
7 2 25 26
8 2 27 28
9 3 31 32
10 3 33 34
11 3 35 36
12 3 37 38
2 我们需要将数据拆分为由字段“a”分区的“较小”表
表A1
Row b c
1 11 12
2 13 14
3 15 16
4 17 18
表A2
Row b c
1 21 22
2 23 24
3 25 26
4 27 28
TableA3
Row b c
1 31 32
2 33 34
3 35 36
4 37 38
3 解决问题
最直接的方法是发出三个单独的语句,分别将输出写入 TableA1、TableA2、TableA3
SELECT b, c FROM TableAll WHERE a = 1;
SELECT b, c FROM TableAll WHERE a = 2;
SELECT b, c FROM TableAll WHERE a = 3;
优点:速度与激情!
缺点:我们需要对整个表(全部成本)进行尽可能多的表扫描,与我们拥有的“a”的不同值一样多(在这种特殊情况下只有三个,但在现实生活中,可以说最多 N=1K 不同的值) .
所以最终成本是 $5 * N * SizeInTB(TableAll)
我们的目标
We want to minimize cost as much as possible
ideally down to fixed price of $5 * SizeInTB(TableAll)
B 可能的解决方案(想法和简单的实现):
逻辑步骤 1 - 将数据转换为如下所示(将列转换为 JSON)
Row a json
1 1 {"b":"11", "c":"12"}
2 1 {"b":"13", "c":"14"}
3 1 {"b":"15", "c":"16"}
4 1 {"b":"17", "c":"18"}
5 2 {"b":"21", "c":"22"}
6 2 {"b":"23", "c":"24"}
7 2 {"b":"25", "c":"26"}
8 2 {"b":"27", "c":"28"}
9 3 {"b":"31", "c":"32"}
10 3 {"b":"33", "c":"34"}
11 3 {"b":"35", "c":"36"}
12 3 {"b":"37", "c":"38"}
逻辑步骤 2 - 数据透视表,使字段“a”的值成为字段名称(以 a 为前缀以确保我们符合列名约定)
Row a1 a2 a3
1 {"b":"11", "c":"12"} null null
2 {"b":"13", "c":"14"} null null
3 {"b":"15", "c":"16"} null null
4 {"b":"17", "c":"18"} null null
5 null {"b":"21", "c":"22"} null
6 null {"b":"23", "c":"24"} null
7 null {"b":"25", "c":"26"} null
8 null {"b":"27", "c":"28"} null
9 null null {"b":"31", "c":"32"}
10 null null {"b":"33", "c":"34"}
11 null null {"b":"35", "c":"36"}
12 null null {"b":"37", "c":"38"}
注意:上述数据的大小与原始表的大小相同(不含a列)
它仍然比原始数据大,因为现在数据是详细的 json 格式,而不是原生数据类型 + 列名。
这可以通过消除空格、不需要的引号、规范化/最小化原始列名以使其名称中只有一个字符等来优化。
我认为随着 N 的增加,这种差异变得可以忽略不计! (虽然还没有机会评估这个)
第 3 步 - 将结果数据透视表保存到 TableAllPivot 中
实现示例:
SELECT
IF(a=1, json, NULL) as a1,
IF(a=2, json, NULL) as a2,
IF(a=3, json, NULL) as a3
FROM (
SELECT a, CONCAT("{\"b\":\"",STRING(b), "\","," \"c\":\"", STRING(c), "\"}") AS json
FROM TableAll
)
第 3 步的费用:$5 * TableAllSizeInTB
基于步骤 2 中的 cmets 假设:Size(TableAllPivot) = 2 * Size(TableAll)
第 4 步 - 生成分片,每个分片仅查询一列
为了保留模式/数据类型——可以提前创建相应的分片表
数据提取 :
//对于表A1:
SELECT
JSON_EXTRACT_SCALAR(a1, '$.b') AS b,
JSON_EXTRACT_SCALAR(a1, '$.c') AS c
FROM TableAllPivot
WHERE NOT a1 IS NULL
//对于表A2:
SELECT
JSON_EXTRACT_SCALAR(a2, '$.b') AS b,
JSON_EXTRACT_SCALAR(a2, '$.c') AS c
FROM TableAllPivot
WHERE NOT a2 IS NULL
//对于A3表:
SELECT
JSON_EXTRACT_SCALAR(a3, '$.b') AS b,
JSON_EXTRACT_SCALAR(a3, '$.c') AS c
FROM TableAllPivot
WHERE NOT a3 IS NULL
第 4 步的成本:$5 * TableAllPivot
总成本:Step 3 Cost + Step 4 Cost =
$5 * SizeInTB(TableAll) + $5 * SizeInTB(TableAllPivot) ~ $5 * 3 * SizeInTB(TableAll)
总结:
建议方法固定价格 = $5 * 3 * SizeInTB(TableAll)
对比
初始线性价格 = $5 * N * SizeInTB(TableAll)
请注意:在我的简化示例中,$5 * 3 * SizeInTB(TableAll) 公式中的3 不是由分片数量定义的,而是主要反映将数据转换为 json 的价格的估计常数。分片的数量在这里无关紧要。相同的公式适用于 100 个分片和 1K 个分片,依此类推。此解决方案的唯一限制是 10K 分片,因为这是一个表中列数的硬性限制
C 一些帮助代码和参考:
1 生成透视查询(结果用于上述第 3 步)
当手动输入查询很无聊时,对于初始表中大于 10-20 的字段数可能很有用,因此您可以使用下面的脚本/查询
SELECT 'SELECT ' +
GROUP_CONCAT_UNQUOTED(
'IF(a=' + STRING(a) + ', json, NULL) as a' + STRING(a)
)
+ ' FROM (
SELECT a,
CONCAT("{\\\"b\\\":\\\"\",STRING(b),"\\\","," \\\"c\\\":\\\"\", STRING(c),"\\\"}") AS json
FROM TableAll
)'
FROM (
SELECT a FROM TableAll GROUP BY a
)
2 如果您想探索和深入了解此选项 - 另请参阅下面对相关和可能有用的代码的参考
Pivot Repeated fields in BigQuery
How to scale Pivoting in BigQuery?
How to extract all the keys in a JSON object with BigQuery