在 BigQuery UDF 中运行 SQL,可能是递归的
Posted
技术标签:
【中文标题】在 BigQuery UDF 中运行 SQL,可能是递归的【英文标题】:Run SQL inside BigQuery UDF, possibly recursively 【发布时间】:2017-01-11 10:48:22 【问题描述】:我想知道 BigQuery 中的递归 UDF 函数是否是我正在做的事情的正确解决方案。但首先,是否可以从 UDF 内部运行查询?
我在这里看到一个类似的问题:BigQuery : is it possible to execute another query inside an UDF?,但解决方案似乎是一种直接执行 SQL 的解决方法。就我而言,我可能不得不重复/递归地调用 UDF,而不事先知道步骤数(比如 3-7 步)。
这是一个在表中的用户名条目上构建关系图的简单用例,具有 X 度的分离,其中 X 将由最终用户作为参数提供。我的猜测是递归式 UDF 会很好用,但有可能吗?
****编辑:有关用例的更多详细信息:** 考虑一个包含事务数据的表,其中包含每行中的对应项以及一些其他信息:
Buyer, Seller
Bob->Alice
Bob->Carol
Bob->John
John-Peter
John-Sam
Bob->Mary
假设我想以 1 度的分离度可视化 Bob 与其对应方的关系(即,还显示每个对应方的关系从 Bob 移开 1 步)。我想在这里使用这样的力图:D3 Force-Collapsible Graph
此图需要具有以下结构的 .JSON 文件:
"name": "Bob", "size":5000,
"children":
[
"name":"Alice","size":3000,
"name":"Carol","size":3000,
"name":"John","size":3000,
"children":[
"name":"Peter","size":3000,
"name":"Sam","size":3000
],
"name":"Mary","size":3000
]
因此,如果分离度为 1,Bob 有 4 个孩子,其中,John 有 2 个孩子。这可以通过 X 度的分离更深入,理想情况下使用用户提供的 X,但实际上也可以硬编码为 3 级或 5 级。
【问题讨论】:
您使用的是旧版 SQL 还是标准 SQL(它们处理 UDF 的方式不同),例如cloud.google.com/bigquery/docs/reference/standard-sql/… BigQuery UDF 的当前实现不支持递归调用。我建议您更愿意展示您的用例以及您如何尝试解决它以及您遇到什么样的问题,以便有人会帮助您。 用例是生成一个类似于link 的可折叠树,其中远离中心的分支数将由用户提供。关系数据在 BigQuery 中采用简单的 UserA->UserB 格式,前端通过 Node.JS 提供服务,如 BigQuery 文档中所示。下面基于 GENERATE_ARRAY 的答案可以工作。将调查... ** 至于遗留/标准 SQL,我会使用任何适合该任务的方法! 理想情况下,您应该提供输入数据和所需输出的示例,否则它将基于假设来回抽象 - 这通常根本没有生产力 抱歉耽搁了,我更新了主要问题以包含有关用例的更多详细信息;希望现在更清楚...... 【参考方案1】:试试下面 如果您需要将其扩展到更多程度的分离,它足够通用并且具有非常简单的模式可以遵循 为了举例,我介绍了 size 属性的逻辑——它(在下面的例子中)是项目的字面大小,就其中的项目数量而言(包括它本身)——所以它本质上是孩子的数量 + 1 所以,享受吧:
#standardSQL
CREATE TEMP FUNCTION size(item STRING) AS (
(SELECT CAST(IFNULL(1 + (LENGTH(item) - LENGTH(REPLACE(item, 'name', '')))/4, 1) AS STRING))
);
CREATE TEMP FUNCTION dress(parent STRING, children STRING) AS (
(SELECT CONCAT('"name":"', parent, '","size":', size(children), IFNULL(CONCAT(',"children":[', children, ']'), ''), ''))
);
WITH items AS (
SELECT 'Bob' AS parent, 'Alice' AS child UNION ALL
SELECT 'Bob' AS parent, 'Carol' AS child UNION ALL
SELECT 'Bob' AS parent, 'John' AS child UNION ALL
SELECT 'John' AS parent, 'Peter' AS child UNION ALL
SELECT 'John' AS parent, 'Sam' AS child UNION ALL
SELECT 'Peter' AS parent, 'Sam' AS child UNION ALL
SELECT 'Sam' AS parent, 'Mike' AS child UNION ALL
SELECT 'Sam' AS parent, 'Nick' AS child UNION ALL
SELECT 'Bob' AS parent, 'Mary' AS child
), degree2 AS (
SELECT d1.parent AS parent, d1.child AS child_1, d2.child AS child_2
FROM items AS d1 LEFT JOIN items AS d2 ON d1.child = d2.parent
), degree3 AS (
SELECT d1.*, d2.child AS child_3
FROM degree2 AS d1 LEFT JOIN items AS d2 ON d1.child_2 = d2.parent
), degree4 AS (
SELECT d1.*, d2.child AS child_4
FROM degree3 AS d1 LEFT JOIN items AS d2 ON d1.child_3 = d2.parent
)
SELECT STRING_AGG(dress(parent, child_1), ',') AS parent FROM (
SELECT parent, STRING_AGG(dress(child_1, child_2), ',') AS child_1 FROM (
SELECT parent, child_1, STRING_AGG(dress(child_2, child_3), ',') AS child_2 FROM (
SELECT parent, child_1, child_2, STRING_AGG(dress(child_3, child_4), ',') AS child_3 FROM (
SELECT parent, child_1, child_2, child_3, STRING_AGG(dress(child_4, NULL), ',') AS child_4 FROM degree4
GROUP BY 1,2,3,4 ORDER BY 1,2,3,4 )
GROUP BY 1,2,3 ORDER BY 1,2,3 )
GROUP BY 1,2 ORDER BY 1,2 ) GROUP BY 1 ORDER BY 1 )
它返回的正是你所需要的 - 请参阅下面的“美化”版本
"name": "Bob","size": 12,"children": [
"name": "Alice","size": 1,
"name": "Carol","size": 1,
"name": "John","size": 8,"children": [
"name": "Peter","size": 4,"children": [
"name": "Sam","size": 3,"children": [
"name": "Mike","size": 1,
"name": "Nick","size": 1 ]
],
"name": "Sam","size": 3,"children": [
"name": "Mike","size": 1,
"name": "Nick","size": 1 ]
],
"name": "Mary","size": 1
],
"name": "John","size": 8,"children": [
"name": "Peter","size": 4,"children": [
"name": "Sam","size": 3,"children": [
"name": "Mike","size": 1,
"name": "Nick","size": 1 ]
],
"name": "Sam","size": 3,"children": [
"name": "Mike","size": 1,
"name": "Nick","size": 1 ]
],
"name": "Peter","size": 4,"children": [
"name": "Sam","size": 3,"children": [
"name": "Mike","size": 1,
"name": "Nick","size": 1 ]
],
"name": "Sam","size": 3,"children": [
"name": "Mike","size": 1,
"name": "Nick","size": 1 ]
很可能,上面可以进一步概括 - 但我认为它已经足够你尝试了:o)
【讨论】:
【参考方案2】:您可以让javascript UDF 进行递归调用,但它不能执行另一个 SQL 语句。如果事先知道递归/迭代的次数,或许可以定义一个SQL函数来代替,如:
#standardSQL
CREATE TEMP FUNCTION SumToN(x INT64) AS (
(SELECT SUM(v) FROM UNNEST(GENERATE_ARRAY(1, x)) AS v)
);
使用GENERATE_ARRAY
,您可以创建所需长度的for
循环。这是另一个不涉及 UDF,但使用 GENERATE_ARRAY
连接可变数量的字符串的示例:
#standardSQL
WITH T AS (
SELECT 2 AS x, 'foo' AS y UNION ALL
SELECT 4 AS x, 'bar' AS y)
SELECT
y,
(SELECT STRING_AGG(CONCAT(y, CAST(v AS STRING)))
FROM UNNEST(GENERATE_ARRAY(1, x)) AS v) AS rep_y
FROM T;
+-----+---------------------+
| y | rep_y |
+-----+---------------------+
| foo | foo1,foo2 |
| bar | bar1,bar2,bar3,bar4 |
+-----+---------------------+
【讨论】:
这看起来可以工作 - 这是我可以在 WebUI 中测试的东西,还是只能在命令行中工作? WebUI 为上述代码生成错误。最终,我需要一个可以作为参数传递给 node.js 中的 QueryOptions JavaScript 对象的语句——它可以在那里工作吗? 确保取消选中“显示选项”下的“使用旧版 SQL”以运行它(您还需要添加一个使用它的查询),或者您可以将#standardSQL
放在顶部的查询编辑器尝试一下。
啊,但是当然,它是这样工作的)但是我如何才能真正循环遍历生成的数字并为每个数字执行一个 SELECT 呢?
例如,您可以对GENERATE_ARRAY
的元素使用子选择,这样您就可以完成类似的操作。如果您草拟了您希望完成的伪代码,我或其他人可能会就该问题给出更具体的答案。
抱歉耽搁了,我更新了主要问题以包含有关用例的更多详细信息;希望现在更清楚......以上是关于在 BigQuery UDF 中运行 SQL,可能是递归的的主要内容,如果未能解决你的问题,请参考以下文章
如何使用调用 UDF 的 Python 脚本来使用 BigQuery API
Google 标准 SQL UDF - 写入 BigQuery
描述如何在 SQL 中内联定义 Javascript UDF 函数的 BigQuery 文档在哪里(不是在 UDF 编辑器或单独的文件中)?