【发布时间】:2014-06-22 04:12:38
【问题描述】:
给定一个包含 VARCHAR 中的 ngram 的列和 utf8mb4_unicode_ci 排序规则:
+---------------------------+
| ngram |
+---------------------------+
| stack overflow |
| stack |
| overflow |
| stack overflow protection |
| overflow protection |
| protection |
+---------------------------+
还有一个查询:
SELECT * FROM ngrams WHERE ngram IN ('stack', 'stack overflow', 'protection', 'overflow')
鉴于此查询返回的行,我怎样才能只保留具有最长 ngram 的行从返回的行?
在本例中,我得到 3 行:stack、stack overflow 和 protection。
然后,我需要像这样过滤行:
- 我过滤掉了
stack,因为stack overflow存在于返回的行中 - 我保留
stack overflow,因为没有其他返回的行是包含stack overflow的ngram(表中有stack overflow protection,但它不在返回的行中) - 我也保留
protection - 我过滤掉了
overflow,因为stack overflow存在于返回的行中
由于排序规则,必须在 MySQL 中完成(在 MySQL 之外的比较不会给出与 MySQL 中相同的结果)。 (除非我不知道某些 MySQL 函数允许公开 collated 版本的字符串。)
我可以想到以下解决方案:(sql fiddle)
SELECT ngram
FROM ngrams n1
WHERE n1.ngram IN ('stack', 'stack overflow', 'protection')
AND NOT EXISTS (
SELECT 1
FROM ngrams n2
WHERE n2.ngram IN ('stack', 'stack overflow', 'protection')
AND LENGTH(n2.ngram) > LENGTH(n1.ngram)
AND CONCAT(' ', n2.ngram, ' ') LIKE CONCAT('% ', n1.ngram, ' %')
)
但效率低下,因为子查询将为每个匹配的 ngram 执行。
所以我正在寻找
- 任何一种使查询高效的方法
- 或在 MySQL 之外可靠地执行此操作的方法(考虑排序规则)
【问题讨论】:
-
您希望查询返回什么?目前尚不清楚,人们正在提供多种不同的解决方案。
-
尽管答案很花哨,
NOT EXISTS可能是outperforms them all,因为无论如何都无法在单个 SELECT 中执行操作。我相信使用 CTE 可能会更快,因为您可以使用递归,但 MySQL 似乎不支持这样的东西。 -
但是,您可以只检查
n2.ngram <> n1.ngram而不是检查LENGTH(n2.ngram) > LENGTH(n1.ngram),我不确定您为什么要检查CONCAT(' ', n2.ngram, ' ')?你需要LIKE中的空格吗? -
@plalx 一些答案的查询似乎比问题中的查询更有效。对于 CONCAT,它是为了避免匹配部分单词,例如
%foo%将匹配foobar,而% foo %不会。 -
您需要多久执行一次此操作?这是一个有向图问题的示例,您可以通过预处理 ngram 表本身来解决该问题。还有,ngram 表有多大,
in列表有多长?