我有一个要与几万个字符串的表匹配的几百万个字符串的表,像这样:
#standardSQL
SELECT record.* FROM `record`
JOIN `fragment` ON record.name
LIKE CONCAT('%', fragment.name, '%')
不幸的是,这花费了很长时间。
考虑到fragment
表仅2万条记录,我可以使用UDF将其加载到JavaScript数组中并以这种方式进行匹配吗?我现在想弄清楚该如何做,但是也许我已经可以在这里做一些魔术来使它更快。我尝试了CROSS JOIN
,但很快就超出了资源。我也尝试过使用EXISTS
,但在该子查询的record.name
中无法引用WHERE
,而不会出错。
这似乎反映了相同数量的 数据...
#standardSQL
WITH record AS (
SELECT LOWER(text) AS name
FROM `bigquery-public-data.hacker_news.comments`
), fragment AS (
SELECT LOWER(name) AS name, COUNT(*)
FROM `bigquery-public-data.usa_names.usa_1910_current`
GROUP BY name
)
SELECT record.* FROM `record`
JOIN `fragment` ON record.name
LIKE CONCAT('%', fragment.name, '%')
答案 0 :(得分:2)
以下是用于BigQuery标准SQL
ScaleQuantumToChar
以上操作在375秒内完成,而原始查询仍在2740秒处运行并保持运行,因此我什至不会等待它完成
答案 1 :(得分:1)
米哈伊尔(Mikhail)的答案似乎更快-但让我们有一个不需要SPLIT
或将文本分成单词的>
首先,使用所有要搜索的单词计算一个正则表达式:
#standardSQL
WITH record AS (
SELECT text AS name
FROM `bigquery-public-data.hacker_news.comments`
), fragment AS (
SELECT name AS name, COUNT(*)
FROM `bigquery-public-data.usa_names.usa_1910_current`
GROUP BY name
)
SELECT FORMAT('(%s)',STRING_AGG(name,'|'))
FROM fragment
现在,您可以获取结果字符串,并在忽略大小写的REGEX
中使用它:
#standardSQL
WITH record AS (
SELECT text AS name
FROM `bigquery-public-data.hacker_news.comments`
), largestring AS (
SELECT '(?i)(mary|margaret|helen|more_names|more_names|more_names|josniel|khaiden|sergi)'
)
SELECT record.* FROM `record`
WHERE REGEXP_CONTAINS(record.name, (SELECT * FROM largestring))
(〜510秒)
答案 2 :(得分:0)
在我的问题中未曾提及,我使用JavaScript UDF开发了一个版本,该版本解决该问题的方式比我接受的答案要慢。为了完整起见,我将其发布在这里,因为也许有人(将来像我一样)可能会觉得有用。
CREATE TEMPORARY FUNCTION CONTAINS_ANY(str STRING, fragments ARRAY<STRING>)
RETURNS STRING
LANGUAGE js AS """
for (var i in fragments) {
if (str.indexOf(fragments[i]) >= 0) {
return fragments[i];
}
}
return null;
""";
WITH record AS (
SELECT text AS name
FROM `bigquery-public-data.hacker_news.comments`
WHERE text IS NOT NULL
), fragment AS (
SELECT name AS name, COUNT(*)
FROM `bigquery-public-data.usa_names.usa_1910_current`
WHERE name IS NOT NULL
GROUP BY name
), fragment_array AS (
SELECT ARRAY_AGG(name) AS names, COUNT(*) AS count
FROM fragment
GROUP BY LENGTH(name)
), records_with_fragments AS (
SELECT record.name,
CONTAINS_ANY(record.name, fragment_array.names)
AS fragment_name
FROM record INNER JOIN fragment_array
ON CONTAINS_ANY(name, fragment_array.names) IS NOT NULL
)
SELECT * EXCEPT(rownum) FROM (
SELECT record.name,
records_with_fragments.fragment_name,
ROW_NUMBER() OVER (PARTITION BY record.name) AS rownum
FROM record
INNER JOIN records_with_fragments
ON records_with_fragments.name = record.name
AND records_with_fragments.fragment_name IS NOT NULL
) WHERE rownum = 1
想法是片段列表相对较小,可以在数组中进行处理,类似于Felipe使用正则表达式的答案。我要做的第一件事是创建一个fragment_array
表,该表按片段长度分组……一种防止超大型数组的便宜方法, 我发现这会导致UDF超时。 / p>
接下来,我创建一个名为records_with_fragments
的表,该表将这些数组连接到原始记录,并使用JavaScript UDF CONTAINS_ANY()
仅查找那些包含匹配片段的表。由于一条记录可能匹配多个片段,因此这将导致表中包含一些重复项。
最后的SELECT
然后拉入原始的record
表,连接到records_with_fragments
以确定匹配的片段,还使用ROW_NUMBER()
函数防止重复,例如仅显示由name
唯一标识的每条记录的第一行。
现在,我在最终查询中进行联接的原因是,在我的实际数据中,除了要匹配的字符串外,我还需要其他字段。较早之前,在我的实际数据中,我创建了一个DISTINCT
字符串表,随后需要将其重新连接。
Voila!不是最优雅的,但是可以完成工作。