如何在PostgreSQL中获取字符串中正则表达式匹配的位置?

时间:2014-01-14 01:06:31

标签: sql regex database postgresql

我有一个带书名的表格,我想选择标题与正则表达式匹配的书籍,并按标题中正则表达式匹配的位置排序结果。

单词搜索很容易。 E.g。

TABLE book
id   title
1    The Sun
2    The Dead Sun
3    Sun Kissed

在将查询发送到DB之前,我将把.*放在客户端搜索词中的单词之间,所以我在这里用准备好的regexp编写SQL。

SELECT book.id, book.title FROM book
    WHERE book.title ~* '.*sun.*'
    ORDER BY COALESCE(NULLIF(position('sun' in book.title), 0), 999999) ASC;

RESULT
id   title
3    Sun Kissed
1    The Sun
2    The Dead Sun

但是如果搜索词有多个单词我想匹配那些包含搜索词中所有单词的标题与它们之间的任何内容,并按照之前的位置排序,那么我需要一个返回regexp位置的函数,I在PostgreSQL官方文档中找不到合适的文件。

TABLE books
id   title
4    Deep Space Endeavor
5    Star Trek: Deep Space Nine: The Never Ending Sacrifice
6    Deep Black: Space Espionage and National Security

SELECT book.id, book.title FROM book
    WHERE book.title ~* '.*deep.*space.*'
    ORDER BY ???REGEXP_POSITION_FUNCTION???('.*deep.*space.*' in book.title);

DESIRED RESULT
id   title
4    Deep Space Endeavor
6    Deep Black: Space Espionage and National Security
5    Star Trek: Deep Space Nine: The Never Ending Sacrifice

我没有找到类似于??? REGEXP_POSITION_FUNCTION的任何功能???,你有什么想法吗?

2 个答案:

答案 0 :(得分:3)

执行此操作的一种方式(很多):从匹配开始删除字符串的其余部分并测量截断字符串的长度:

SELECT id, title
FROM   book
WHERE  title ILIKE '%deep%space%'
ORDER  BY length(regexp_replace(title, 'deep.*space.*', '','i'));

在WHERE子句中使用ILIKE,因为这通常更快(并且在此处也是如此) 另请注意regexp_replace()函数('i')的第四个参数,以使其不区分大小写。

替代

根据评论中的要求 同时演示如何首先对匹配进行排序(和NULLS LAST)。

SELECT id, title
      ,substring(title FROM '(?i)(^.*)deep.*space.*') AS sub1
      ,length(substring(title FROM '(?i)(^.*)deep.*space.*')) AS pos1

      ,substring(title FROM '(?i)^.*(?=deep.*space.*)') AS sub2
      ,length(substring(title FROM '(?i)^.*(?=deep.*space.*)')) AS pos2

      ,substring(title FROM '(?i)^.*(deep.*space.*)') AS sub3
      ,position((substring(title FROM '(?i)^.*(deep.*space.*)')) IN title) AS p3

      ,regexp_replace(title, 'deep.*space.*', '','i') AS reg4
      ,length(regexp_replace(title, 'deep.*space.*', '','i')) AS pos4
FROM   book
ORDER  BY title ILIKE '%deep%space%' DESC NULLS LAST
         ,length(regexp_replace(title, 'deep.*space.*', '','i'));

您可以在手册herehere中找到上述所有文档。

-> SQLfiddle展示所有。

答案 1 :(得分:0)

另一种方法是首先获得模式的文字匹配,然后找到文字匹配的位置:

strpos(input, (regexp_match(input, pattern, 'i'))[1]);

或者在这种情况下:

SELECT   id, title
FROM     book
ORDER BY strpos(input, (regexp_match(input, pattern, 'i'))[1]);
顺便说一下,你应该使用一个贪婪的量词并尽可能地缩小你的角色类而不是。*来提高性能(例如,'deep [\ w] *?space')