选择字符串中子串最长的行

Posted

技术标签:

【中文标题】选择字符串中子串最长的行【英文标题】:select rows with longest substring of the string 【发布时间】:2016-03-30 23:16:22 【问题描述】:

让我根据下面的例子来描述这个问题。 假设有一个字符串“abc12345”(可以是任何!!!),并且有一个表 mytable,其列 mycolumn 为 varchar(100)。

有些行以最后一个字符 5 结尾。 有些行以最后一个字符 45 结尾。 有一些行以最后一个字符 345 结尾 没有以最后一个字符 2345 结尾的行。

在这种情况下,应该选择这些行:

SELECT * FROM mytable WHERE mycolumn LIKE "%345"

这是因为“345”是“abc12345”的最长右子字符串,它作为 mycolumn 列中至少一个字符串的右子字符串至少出现一次。 任何想法如何在一个查询中编写它? 谢谢。

【问题讨论】:

【参考方案1】:

这是一种蛮力方法:

select t.*
from (select t.*,
             dense_rank() over (order by (case when mycolumn like '%abc12345' then 1
                                               when mycolumn like '%bc12345' then 2
                                               when mycolumn like '%c12345' then 3
                                               when mycolumn like '%12345' then 4
                                               when mycolumn like '%2345' then 5
                                               when mycolumn like '%345' then 6
                                               when mycolumn like '%45' then 7
                                               when mycolumn like '%5' then 8
                                         end)
                              ) as seqnum
      where mycolumn like '%5' -- ensure at least one match  
      from t
     ) t
where seqnum = 1;

这会激发这样的灵感:

select t.*
from (select t.*, max(i) over () as maxi
      from t join
           (select str, generate_series(1, length(str)) as i
            from (select 'abc12345' as str) s
           ) s
           on left(t.mycolumn, i) = left(str, i)
     ) t
where i = maxi;

【讨论】:

【参考方案2】:

有趣的谜题:)

这里最困难的问题是找到与您的后缀模式匹配的目标后缀的长度。

mysql 中,您可能需要使用生成系列或 UDF。其他人已经提出了这些建议。

在 PostgreSQL 和其他提供基于正则表达式的子字符串的系统中,您可以使用以下技巧:

select v,
    reverse(
      substring(
        reverse(v) || '#' || reverse('abcdefg')
        from '^(.*).*#\1.*'
    )) res
from table;

它的作用是:

构造一个结合你的字符串和后缀的字符串。注意,我们颠倒它们。 我们将# 放在重要的字符串之间,您需要一个字符串中不存在的字符。 我们使用substring从正则表达式中提取匹配,这样 从字符串^的开头开始 匹配任意数量的字符(.*) 可以有一些剩余字符.* 现在我们找到# 现在,我们希望与(.*) 匹配的相同字符串出现在# 之后。所以我们使用\1 并且可以有一些尾字符.* 我们反转提取的字符串

一旦你有最长的后缀,找到最大长度,然后找到所有具有该长度后缀的字符串是微不足道的。

这是一个使用 PostgreSQL 的SQLFiddle:

【讨论】:

【参考方案3】:

如果您无法重组表格,我会以这种方式解决问题:

在 C 中编写聚合 UDF LONGEST_SUFFIX_MATCH(col, str)(参见 MySQL 源代码中 sql/udf_example.c 中的示例,搜索 avgcost

SELECT @longest_match:=LONGEST_SUFFIX_MATCH(mycol, "abcd12345") FROM mytbl; SELECT * FROM mytbl WHERE mycol LIKE CONCAT('%', SUBSTR("abcd12345", -@longest_match))

如果您可以重组表格,我还没有完整的解决方案,但首先我会添加一个特殊列 mycol_rev 通过反转字符串获得(通过REVERSE() 函数)并在其上创建一个键,然后使用该键进行查找。有时间会发布完整的解决方案。

更新:

如果您可以添加带有键的反转列:

使用格式为 `SELECT myrevcol FROM mytbl WHERE myrevcol LIKE CONCAT(SUBSTR(REVERSE('$search_string'), $n),'%') LIMIT 1 对 $n 执行二进制搜索的查询从 1 到 $search_string 长度的范围,以找到查询返回行的 $n 的最大值 SELECT * FROM mytbl WHERE myrevcol LIKE CONCAT(SUBSTR(REVERSE('$search_string'), $found_n),'%')

只要返回的行数不多,此解决方案应该会非常快。我们将有总共 O(log(L)) 个查询,其中 L 是搜索字符串的长度,每个查询都是 B-tree 搜索,只读取一行,然后是另一个 B-tree 搜索,索引读取只包含所需的行。

【讨论】:

以上是关于选择字符串中子串最长的行的主要内容,如果未能解决你的问题,请参考以下文章

最长回文子串

寻找最长公共子串(高分)

C语言 计算字符串中子串出现的次数 求更改

C++ 字符串中子串个数

C语言--编程实现字符串中子串的查找

C语言试题159之计算字符串中子串出现的次数