问的问题
测试表:
CREATE TABLE tbl (id int, str text);
INSERT INTO tbl VALUES
(1, 'a.b.c.d.e')
, (2, 'x1.yy2.zzz3') -- different number & length of elements for testing
, (3, '') -- empty string
, (4, NULL); -- NULL
LATERAL子查询中的递归CTE
SELECT *
FROM tbl, LATERAL (
WITH RECURSIVE cte AS (
SELECT str
UNION ALL
SELECT right(str, strpos(str, '.') * -1) -- trim leading name
FROM cte
WHERE str LIKE '%.%' -- stop after last dot removed
)
SELECT ARRAY(TABLE cte) AS result
) r;
在CROSS JOIN LATERAL
(, LATERAL
短)是安全的,因为子查询的总结果总是返回一行。你得到 ...
- ...
str = ''
基表中带有空字符串元素的数组
- ...
str IS NULL
基表中具有NULL元素的数组
在子查询中包装了廉价的数组构造函数,因此外部查询中没有聚合。
SQL功能的典范,但rCTE开销可能会阻止最佳性能。
琐碎的元素数量
对于元素数量很少的情况,没有子查询的简单方法可能会更快:
SELECT id, array_remove(ARRAY[substring(str, '(?:[^.]+\.){4}[^.]+$')
, substring(str, '(?:[^.]+\.){3}[^.]+$')
, substring(str, '(?:[^.]+\.){2}[^.]+$')
, substring(str, '[^.]+\.[^.]+$')
, substring(str, '[^.]+$')], NULL)
FROM tbl;
假设您最多评论了5个元素。您可以轻松扩展更多。
如果给定域的元素较少,则多余的substring()
表达式将返回NULL并被删除array_remove()
。
实际上,right(str, strpos(str, '.')
由于正则表达式函数更昂贵,因此嵌套数次的above()表达式可能会更快(尽管笨拙)。
@Dudu查询的分支
@Dudu的智能查询可以通过以下方式进行改进generate_subscripts()
:
SELECT id, array_agg(array_to_string(arr[i:], '.')) AS result
FROM (SELECT id, string_to_array(str,'.') AS arr FROM tbl) t
LEFT JOIN LATERAL generate_subscripts(arr, 1) i ON true
GROUP BY id;
还LEFT JOIN LATERAL ... ON true
用于保留具有NULL值的可能行。
PL / pgSQL函数
与rCTE类似的逻辑。比您所拥有的更简单,更快捷:
CREATE OR REPLACE FUNCTION string_part_seq(input text, OUT result text[]) AS
$func$
BEGIN
LOOP
result := result || input; -- text[] || text array concatenation
input := right(input, strpos(input, '.') * -1);
EXIT WHEN input = '';
END LOOP;
END
$func$ LANGUAGE plpgsql IMMUTABLE STRICT;
该OUT
参数将在函数末尾自动返回。
不需要初始化result
,因为NULL::text[] || text 'a' = '{a}'::text[]
。
这仅'a'
在正确键入时有效。NULL::text[] || 'a'
(字符串文字)会引发错误,因为Postgres选择了array || array
运算符。
strpos()
返回0
如果没有点被发现,所以right()
返回一个空字符串和循环结束。
这可能是这里所有解决方案中最快的。
它们都可以在Postgres 9.3+中使用
(短数组切片符号除外arr[3:]
。我在小提琴中添加了上限,以使其在pg 9.3:中可以使用arr[3:999]
。)
SQL提琴。
优化搜索的不同方法
我与@ jpmc26(和您自己)在一起:最好采用完全不同的方法。我喜欢jpmc26 reverse()
和和的组合text_pattern_ops
。
对于部分匹配或模糊匹配,三元组索引会更好。但是,由于您只对整个单词感兴趣,因此全文搜索是另一种选择。我希望索引的大小大大减小,从而获得更好的性能。
pg_trgm以及FTS支持不区分大小写的查询,顺便说一句。
主机名(例如q.x.t.com
或)t.com
(带内嵌点的单词)被标识为“主机”类型,并被视为一个单词。但是FTS中也有前缀匹配(有时似乎被忽略了)。手册:
此外,*
可以附加到词素以指定前缀匹配:
使用@ jpmc26的聪明点子reverse()
,我们可以完成此工作:
SELECT *
FROM tbl
WHERE to_tsvector('simple', reverse(str))
@@ to_tsquery ('simple', reverse('c.d.e') || ':*');
-- or with reversed prefix: reverse('*:c.d.e')
索引支持:
CREATE INDEX tbl_host_idx ON tbl USING GIN (to_tsvector('simple', reverse(str)));
注意'simple'
配置:我们不希望将词干或词库与默认'english'
配置一起使用。
另外(可能的查询种类更多),我们可以使用Postgres 9.6中文本搜索的新短语搜索功能。发行说明:
可以使用新的运算符<->
和在tsquery输入中指定词组搜索查询。前者表示词素之前和之后的词素必须按该顺序彼此相邻出现。后者意味着它们必须完全分开词素。<
N
>
N
查询:
SELECT *
FROM tbl
WHERE to_tsvector ('simple', replace(str, '.', ' '))
@@ phraseto_tsquery('simple', 'c d e');
'.'
用空格(' '
)代替点(),以防止解析器将't.com'分类为主机名,而是将每个单词用作单独的词素。
以及与之匹配的索引:
CREATE INDEX tbl_phrase_idx ON tbl USING GIN (to_tsvector('simple', replace(str, '.', ' ')));