正则表达式或 LIKE 模式的转义函数
Posted
技术标签:
【中文标题】正则表达式或 LIKE 模式的转义函数【英文标题】:Escape function for regular expression or LIKE patterns 【发布时间】:2011-07-05 20:49:51 【问题描述】:为了放弃阅读整个问题,我的基本问题是:PostgreSQL 中有一个函数可以转义字符串中的正则表达式字符吗?
我查阅了文档,但找不到这样的功能。
这是完整的问题:
在 PostgreSQL 数据库中,我有一列具有唯一名称。我还有一个过程,它会定期将名称插入此字段,并且为了防止重复,如果它需要输入一个已经存在的名称,它会在末尾附加一个空格和括号。
即姓名、姓名(1)、姓名(2)、姓名(3)等
就目前而言,我使用以下代码来查找要添加到系列中的下一个数字(用 plpgsql 编写):
var_name_id := 1;
SELECT CAST(substring(a.name from E'\\((\\d+)\\)$') AS int)
INTO var_last_name_id
FROM my_table.names a
WHERE a.name LIKE var_name || ' (%)'
ORDER BY CAST(substring(a.name from E'\\((\\d+)\\)$') AS int) DESC
LIMIT 1;
IF var_last_name_id IS NOT NULL THEN
var_name_id = var_last_name_id + 1;
END IF;
var_new_name := var_name || ' (' || var_name_id || ')';
(var_name
包含我要插入的名称。)
这暂时可行,但问题在于WHERE
语句:
WHERE a.name LIKE var_name || ' (%)'
此检查无法验证所讨论的 %
是一个数字,并且它不考虑多个括号,例如“名称 ((1))”,如果任何一种情况都存在,则进行强制转换会抛出异常。
WHERE
声明确实需要更像:
WHERE a.r1_name ~* var_name || E' \\(\\d+\\)'
但是var_name
可能包含正则表达式字符,这导致了上面的问题:PostgreSQL 中是否有一个函数可以转义字符串中的正则表达式字符,所以我可以这样做:
WHERE a.r1_name ~* regex_escape(var_name) || E' \\(\\d+\\)'
非常感谢任何建议,包括可能对我的重名解决方案进行修改。
【问题讨论】:
我意识到这个问题很老了,但是在搜索 Postgres 函数以转义特殊的正则表达式字符时,它不断出现。所以我添加了一个提供 that 的答案。 【参考方案1】:解决顶部的问题:
假设standard_conforming_strings = on
,就像它自 Postgres 9.1 以来的默认值一样。
正则表达式转义函数
让我们从regular expression 模式中具有特殊含义的字符的完整列表开始:
!$()*+.:<=>?[\]^|-
包裹在bracket expression 中的大多数都失去了特殊意义——除了少数例外:
-
必须是第一个或最后一个,否则它表示 范围 个字符。
]
和 \
必须用 \
转义(在替换中也是如此)。
在下面添加capturing parentheses for the back reference 后,我们得到这个正则表达式模式:
([!$()*+.:<=>?[\\\]^|-])
使用它,此函数使用反斜杠 (\
) 转义所有特殊字符 - 从而消除特殊含义:
CREATE OR REPLACE FUNCTION f_regexp_escape(text)
RETURNS text
LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS
$func$
SELECT regexp_replace($1, '([!$()*+.:<=>?[\\\]^|-])', '\\\1', 'g')
$func$;
在 Postgres 10 或更高版本中添加 PARALLEL SAFE
(因为它是)以允许使用它的查询的并行性。
演示
SELECT f_regexp_escape('test(1) > Foo*');
返回:
test\(1\) \> Foo\*
同时:
SELECT 'test(1) > Foo*' ~ 'test(1) > Foo*';
返回FALSE
,这可能会让天真的用户大吃一惊,
SELECT 'test(1) > Foo*' ~ f_regexp_escape('test(1) > Foo*');
返回TRUE
,就像现在一样。
LIKE
转义函数
为了完整起见,LIKE
模式的挂件,其中只有三个字符是特殊的:
\%_
The manual:
默认转义字符是反斜杠,但可以使用
ESCAPE
子句选择不同的转义字符。
此函数采用默认值:
CREATE OR REPLACE FUNCTION f_like_escape(text)
RETURNS text
LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS
$func$
SELECT replace(replace(replace($1
, '\', '\\') -- must come 1st
, '%', '\%')
, '_', '\_');
$func$;
我们也可以在这里使用更优雅的regexp_replace()
,但对于少数字符,replace()
函数的级联会更快。
同样,PARALLEL SAFE
在 Postgres 10 或更高版本中。
演示
SELECT f_like_escape('20% \ 50% low_prices');
返回:
20\% \\ 50\% low\_prices
【讨论】:
感谢您的出色回答,也很好的解释。这对我帮助很大! 在 postgres 11 中尝试这个, f_regex_escape 的演示返回输入字符串,没有转义字符。SELECT regexp_replace('test(1) > Foo*', '([!$()*+.:<=>?[\\]^|-])', '\\1', 'g')
返回test(1) > Foo*
@zurbergram:在您的示例中,第三个 \
在两个地方丢失了。 (可能是由于评论显示,\
需要转义。)它的工作原理与宣传的一样:dbfiddle here
呃。很棒的答案,但很遗憾没有原生解决方案【参考方案2】:
您可以随意更改架构吗?我认为如果您可以使用复合主键,问题就会消失:
name text not null,
number integer not null,
primary key (name, number)
然后显示层的职责就是将 Fred #0 显示为“Fred”,将 Fred #1 显示为“Fred (1)”,等等。
如果您愿意,您可以为此职责创建一个视图。这是数据:
=> select * from foo;
name | number
--------+--------
Fred | 0
Fred | 1
Barney | 0
Betty | 0
Betty | 1
Betty | 2
(6 rows)
观点:
create or replace view foo_view as
select *,
case
when number = 0 then
name
else
name || ' (' || number || ')'
end as name_and_number
from foo;
结果:
=> select * from foo_view;
name | number | name_and_number
--------+--------+-----------------
Fred | 0 | Fred
Fred | 1 | Fred (1)
Barney | 0 | Barney
Betty | 0 | Betty
Betty | 1 | Betty (1)
Betty | 2 | Betty (2)
(6 rows)
【讨论】:
我考虑过这个解决方案,但由于插入数据确实是唯一存在问题的时间,因此大部分工作将是对所有显示查询进行更改。看起来修改插入查询会容易得多 @Benny,如果你愿意,你可以使用视图。请参阅修改后的答案。【参考方案3】:尝试这样的事情怎么样,用var_name
代替我硬编码的'John Bernard'
:
create table my_table(name text primary key);
insert into my_table(name) values ('John Bernard'),
('John Bernard (1)'),
('John Bernard (2)'),
('John Bernard (3)');
select max(regexp_replace(substring(name, 13), ' |\(|\)', '', 'g')::integer+1)
from my_table
where substring(name, 1, 12)='John Bernard'
and substring(name, 13)~'^ \([1-9][0-9]*\)$';
max
-----
4
(1 row)
一个警告:我假设在此过程运行时单用户访问数据库(您的方法也是如此)。如果不是这样,那么max(n)+1
方法将不是一个好方法。
【讨论】:
数据库将被多个用户访问,但我的查询中的附加过滤器(在我的问题中省略)将限制它考虑一次只允许一个用户使用的行。感谢您的帮助,我会接受您拆分名称并使用 substring 和 char_length(字面上用 12 表示)分别比较这两个部分的解决方案。以上是关于正则表达式或 LIKE 模式的转义函数的主要内容,如果未能解决你的问题,请参考以下文章
notepad++ 正则表达式引擎 (scintilla) 是不是支持子字符串转义(smth. like "\Q.*[escaped string]()+\E")?