【问题标题】:In SQL, how to check if a string is the substring of any other string in the same table?在 SQL 中,如何检查一个字符串是否是同一个表中任何其他字符串的子字符串?
【发布时间】:2023-03-24 12:46:01
【问题描述】:

我有一个充满字符串的表 (TEXT),我喜欢获取同一个表中任何其他字符串的子字符串的所有字符串。例如,如果我的表中有这三个字符串:

WORD        WORD_ID
cup         0
cake        1
cupcake     2

根据我的查询,我想得到这样的结果:

WORD        WORD_ID        SUBSTRING        SUBSTRING_ID
cupcake     2              cup              0
cupcake     2              cake             1 

我知道我可以使用两个循环(使用 Python 或 JS)来执行此操作,方法是遍历表中的每个单词并将其与同一个表中的每个单词进行匹配,但我不确定如何使用SQL(就此而言是 PostgreSQL)。

【问题讨论】:

  • 看起来像一个使用word LIKE '%'+substring+'%'的条件连接。
  • 感谢你们的 cmets,为我指明了正确的方向。
  • 这个查询对于大表来说可能非常昂贵。请澄清:您的标题是您需要的(check if a string is the substring of any other string in the same table),还是您需要的示例(列出所有匹配的组合)?语义差异很细微,但结果和性能上的差异可能巨大。无论哪种方式,都有更好的解决方案。
  • @shawnt00:连接字符串的运算符在 SQL 中是 ||,而不是 +
  • @a_horse 这只是我多年来使用 SQL Server 的一个习惯。我认为这个想法仍然存在。实际上我注意到戈登甚至在他自己的回答中混合了两者:)

标签: sql postgresql loops pattern-matching string-function


【解决方案1】:

使用自联接:

select w1.word, w1.word_id, w2.word, w2.word_id
from words w1
join words w2
on w1.word <> w2.word
and w1.word like format('%%%s%%', w2.word);

  word   | word_id | word | word_id 
---------+---------+------+---------
 cupcake |       2 | cup  |       0
 cupcake |       2 | cake |       1
(2 rows)

【讨论】:

  • 此查询的 O(N²) 特性可能会影响表中任何重要行数的性能...
  • 吹毛求疵:如果 WORD 具有 CupCake 之类的值,则此查询不会返回
【解决方案2】:

我会这样处理:

select w1.word_id, w1.word, w2.word_id as substring_id w2.word as substring
from words w1 join
     words w2
     on w1.word like '%' || w2.word || '%' and w1.word <> w2.word;

注意:这可能比在应用程序中执行循环要快一些。但是,此查询将在 Postgres 中作为嵌套循环实现,因此不会非常快。

【讨论】:

  • 实际上有一些方法可以快速制作
【解决方案3】:

问题

该任务有可能使您的数据库服务器停止处理非平凡大小的表,因为只要您不能为其使用索引,这是一个 O(N²) 问题。

在顺序扫描中,您必须检查两行的每个可能组合,即 n * (n-1) / 2 组合 - Postgres 将运行 n * n-1 测试,因为排除反向重复组合并不容易。如果您对第一场比赛感到满意,它会变得更便宜 - 多少取决于数据分布。对于许多匹配,Postgres 会提前找到一行匹配,并且可以跳过测试其余部分。对于少数匹配项,无论如何都必须执行大部分检查。

无论哪种方式,性能都会随着表中的行数而迅速下降。使用EXPLAIN ANALYZE 和表中的 10、100、1000 等行测试每个查询,以亲自查看。

解决方案

word 上创建一个三元组索引 - 最好是GIN

CREATE INDEX tbl_word_trgm_gin_idx ON tbl USING gin (word gin_trgm_ops);

详情:

到目前为止,两个答案中的查询都不会使用索引,即使您拥有它。使用可以实际使用此索引的查询:

列出所有个匹配项(根据问题正文):
使用LATERAL CROSS JOIN

SELECT t2.word_id, t2.word, t1.word_id, t1.word
FROM   tbl t1
     , LATERAL (
   SELECT word_id, word
   FROM   tbl
   WHERE  word_id <> t1.word_id
   AND    word like format('%%%s%%', t1.word)
   ) t2;

仅获取具有 any 匹配 的行(根据您的标题): 使用EXISTS 半连接:

SELECT t1.word_id, t1.word
FROM   tbl t1
WHERE EXISTS (
   SELECT 1
   FROM   tbl
   WHERE  word_id <> t1.word_id
   AND    word like format('%%%s%%', t1.word)
   );

【讨论】:

  • 。 .很酷,我没有意识到三元组索引可以与like 一起使用。对于包含一两个字符的单词,我不确定三元组是否有帮助。
  • @GordonLinoff:它仍然会 - 如果索引一开始就有意义的话。 (像'a' 这样的单字母字符串通常包含在很大比例的行中,无论如何,索引对此毫无用处,如您所知)。但一般来说,三元组是通过在内部添加和添加空格字符的技巧构建的,因此它们也仅适用于 1 或 2 个字母。
  • 。 .问题是“ca”不会匹配“Cupcake”中的任何内容。三个字符,就有一个匹配。但如果单词是“ca”,则三元组将是“__c”、“_ca”和“ca”,它们都不匹配“Cupcake”中的任何内容。顺便说一句,我非常喜欢这个答案,评论只是一个澄清。如果 pg_trgm 支持 n-gram 就好了,其中“n”可以由用户输入。
  • @GordonLinoff:为什么“ca”不匹配“Cupcake”?它匹配,并且可以为此使用三元组索引。 (但如果估计字符串具有足够的选择性,Postgres 只会选择位图索引扫描而不是顺序扫描。您可以使用set enable_seqscan = false 强制它进行调试。)
  • 。 . “Cupcake”没有等于“ca”的三元组。它有“pca”和“cak”,但没有“_ca”。索引是否处理部分匹配?
猜你喜欢
  • 2013-05-12
  • 1970-01-01
  • 1970-01-01
  • 2019-05-11
  • 2021-11-28
  • 1970-01-01
  • 1970-01-01
  • 2022-12-14
相关资源
最近更新 更多