这个问题需要相当程度的复杂性来解决,而且它的运行时间会随着字符串长度和记录数量的增加而急剧增加。但是,鉴于您的表 T1 相当小,您可能只需使用以下 PL/pgSQL 函数即可。
算法
- 在 T1(col) 中找到最短的值。这是所有记录中最长的匹配。这是候选字符串。
- 查看候选人是否出现在 T1 的所有其他行中。如果是,则将当前候选者返回结果集。
- 将候选者移动到最短值的一个位置,然后返回步骤 2,直到候选者到达最短字符串的末尾。
- 如果找到匹配的候选者,则从函数返回。否则,将候选者缩短 1 并从最短字符串的开头重新开始,然后转到步骤 2。如果无法从最短字符串中提取更多候选者,则返回
NULL。
代码
下面代码中重要的是检查匹配的短路:只要单个记录不匹配col 到候选字符串,就不需要进一步检查。因此,对于长字符串,比较实际上是从最短的字符串与另一个字符串进行比较,仅当候选字符串变得如此短以至于它们确实更普遍时才增加检查的行数。
字符串比较区分大小写;如果要使其不区分大小写,请将LIKE 更改为ILIKE。作为一项奖励功能,您将获得所有行中都存在的多个匹配字符串(显然都是相同的长度)。不利的一面是,一旦达到单个字符匹配(可能还有一些 2 字符和更长的字符串),它将报告多个相同的字符串。您可以使用SELECT DISTINCT * 删除这些重复项。
CREATE FUNCTION find_longest_string_in_T1() RETURNS SETOF text AS $$
DECLARE
shortest varchar; -- The shortest string in T1(col) so the longest possible match
candidate varchar; -- Candidate string to test
sz_sh integer; -- Length of "shortest"
l integer := 1; -- Starting position of "candidate" in "shortest"
sz integer; -- Length of "candidate"
fail boolean; -- Has "candidate" been found in T1(col)?
found_one boolean := false; -- Flag if we found at least one match
BEGIN
-- Find the shortest string and its size, don't worry about multiples, need just 1
SELECT col, char_length(col) INTO shortest, sz_sh
FROM T1
ORDER BY char_length(col) ASC NULLS LAST
LIMIT 1;
-- Get all the candidates from the shortest string and test them from longest to single char
candidate := shortest;
sz := sz_sh;
LOOP
-- Check rows in T1 if they contain the candidate string.
-- Short-circuit as soon as a record does not match the candidate
<<check_T1>>
BEGIN
FOR fail IN SELECT col NOT LIKE '%' || candidate || '%' FROM T1 LOOP
EXIT check_T1 WHEN fail;
END LOOP;
-- Block was not exited, so the candidate is present in all rows: we have a match
RETURN NEXT candidate;
found_one := true;
END;
-- Produce the next candidate
IF l+sz > sz_sh THEN -- "candidate" reaches to the end of "shortest"
-- Exit if we already have at least one matching candidate
EXIT WHEN found_one;
-- .. otherwise shorthen the candidate
sz := sz - 1;
l := 1;
ELSE
-- Exit with empty result when all candidates have been examined
EXIT WHEN l = sz_sh;
-- .. otherwise move one position over to get the next candidate
l := l + 1;
END IF;
candidate := substring(shortest from l for sz);
END LOOP;
RETURN;
END;
$$ LANGUAGE plpgsql IMMUTABLE;
调用SELECT * FROM find_longest_string_in_T1() 应该可以解决问题。
简单测试
创建一些测试数据:
INSERT INTO T1
SELECT 'hello' || md5(random()::text) || md5(random()::text) || 'match' || md5(random()::text) FROM generate_series(1, 25);
INSERT INTO T1
SELECT md5(random()::text) || 'match' || 'hello' || md5(random()::text) || md5(random()::text) FROM generate_series(1, 25);
INSERT INTO T1
SELECT 'match' || md5(random()::text) || 'hello' || md5(random()::text) || md5(random()::text) FROM generate_series(1, 25);
INSERT INTO T1
SELECT md5(random()::text) || 'hello' || md5(random()::text) || 'match' || md5(random()::text) FROM generate_series(1, 25);
这会生成 100 行,长度为 106 个字符,并生成匹配“hello”和“match”(不太可能出现任何其他匹配)。这会在不到半秒的时间内生成正确的两个字符串(没有多余的 Ubuntu 服务器、PG 9.3、CPU i5、4GB RAM)。