PostgreSQL:regex用大括号替换第一级方括号
我有一个文本类型的PostgreSQL列中的数据,需要对其进行字符替换。具体来说,我想用大括号代替方括号。问题是,我只想替换不超过两层深的括号,如果包括主封闭括号的话。这些字符串可能相当长,因此我认为正则表达式可能是一种方法(PostgreSQL:regex用大括号替换第一级方括号,regex,postgresql,Regex,Postgresql,我有一个文本类型的PostgreSQL列中的数据,需要对其进行字符替换。具体来说,我想用大括号代替方括号。问题是,我只想替换不超过两层深的括号,如果包括主封闭括号的话。这些字符串可能相当长,因此我认为正则表达式可能是一种方法(regexp\u replace函数),但我不擅长正则表达式。以下是一个此类值的示例: [0,0,0,[12,2],0,0,[12,[1,2,3]],12,0,[12,2,[2]],12,0,12,0,0] 因此,我希望此字符串更改为: {0,0,0,{12,2},0,0
regexp\u replace
函数),但我不擅长正则表达式。以下是一个此类值的示例:
[0,0,0,[12,2],0,0,[12,[1,2,3]],12,0,[12,2,[2]],12,0,12,0,0]
因此,我希望此字符串更改为:
{0,0,0,{12,2},0,0,{12,[1,2,3]},12,0,{12,2,[2]},12,0,12,0,0}
提前谢谢 这将是Regex as的一大难题 对于最多两层嵌套深度的检查,如果以下双重替换有效(无法测试) 其想法是在两次过程中匹配并替换最外层的
[]
。
请参见regex101中的示例:
:{0,0,0[12,2],0,0[12,1,2,3]],12,0[12,2[2]],12,0,12,0,0,0}
:
{0,0,0,{12,2},0,0,{12,1,2,3]},12,0,{12,2,2]},12,0,12,0,0}
\[^][]*\]
(未scaped)匹配[…]
开口方括号\[
后跟任意数量的字符,这些字符没有方括号[^][]*
后跟一个结束方括号\]
[
开头,以]
结尾,并表示级别0的一个实例(不由][
分隔),则第一个/内部regexp\u replace
也可以通过在^
开始处替换[
和$
结束处替换$
:E'\\\\\\\\[/code>来完成$'
与E'{\\1}'
要在此处添加嵌套,请使用最大4个深度级别的示例:
\[([^][]| # outer
\[([^][]| # lvl 1
\[([^][]| # lvl 2
\[([^][]| # lvl 3
\[[^][]*\] # lvl 4
)*\]
)*\]
)*\]
)*\]
将外部[]
中的内容包装成一个包含4个级别的模式将成为:
\[(([^][]|\[([^][]|\[([^][]|\[([^][]|\[[^][]*\])*\])*\])*\])*)\]
与regex\u replace一起使用时
可能需要额外转义[]
\\[(([^][]|\\[([^][]|\\[([^][]|\\[([^][]|\\[[^][]*\\])*\\])*\\])*\\])*)\\]
这可以像两个过程中的第一个模式一样使用,并替换为
E'{\\1}
这很难看,但它可以工作(并避免了regexp的复杂性;-)我希望我已经涵盖了所有的角落案例
CREATE OR REPLACE FUNCTION replbracket( _source text ) returns text
AS $func$
DECLARE
pos_end INTEGER;
pos_begin INTEGER;
level INTEGER;
result text;
BEGIN
result = '' ;
level = 0;
LOOP
pos_begin = position ( '[' IN _source );
pos_end = position ( ']' IN _source );
-- raise notice 'Source=% Result=% Begin = % End=%'
-- ,_source, result, pos_begin, pos_end;
if (pos_begin < 1 AND pos_end < 1) THEN EXIT ;
elsif (pos_begin < 1 ) THEN pos_begin = pos_end + 1 ;
elsif (pos_end < 1 ) THEN pos_end = pos_begin + 1 ;
end if;
if (pos_begin < pos_end) THEN
result = result || LEFT(_source, pos_begin-1);
level = level + 1;
if (level <= 2) THEN result = result || '{'; else result = result || '['; end if;
_source = SUBSTR(_source, pos_begin+1);
ELSE
result = result || LEFT(_source, pos_end-1);
level = level - 1;
if (level < 2) THEN result = result || '}'; else result = result || ']'; end if;
_source = SUBSTR(_source, pos_end+1);
END IF;
END LOOP;
result = result || _source ;
return result;
END
$func$ LANGUAGE plpgsql;
CREATE或REPLACE函数replbracket(_源文本)返回文本
作为$func$
声明
pos_结束整数;
pos_开始整数;
水平整数;
结果文本;
开始
结果='';
级别=0;
环
pos_begin=位置(_源中的“[”);
pos_end=位置(']'在_源中);
--提出通知“源=%Result=%Begin=%End=%”
--,_来源,结果,位置开始,位置结束;
如果(位置开始<1,位置结束<1),则退出;
elsif(pos_begin<1)则pos_begin=pos_end+1;
elsif(pos_end<1)则pos_end=pos_begin+1;
如果结束;
如果(位置开始<位置结束),则
结果=结果| |左(_源,位置_开始-1);
级别=级别+1;
如果(level仅用于kicks,那么这里有一个完全使用SQL的解决方案。它使用CTE来清楚地表示,但是您可以在FROM中使用子查询,而不使用递归CTE
编辑:添加了简化、更快的SQL版本、Pl/Python版本和C版本。C版本稍微快一点,大约快250倍
create or replace function repl(text)
returns text
language sql
as $$
with
chars(pos, ch) as (
-- In PostgreSQL 9.4 this can be replaced with an UNNEST ... WITH ORDINALITY
-- it turns the string into a list of chars accompanied by their position within
-- the string.
select row_number() OVER (), ch
from regexp_split_to_table($1,'') ch
),
nesting(ch, pos, lvl) as (
-- This query then determines how many levels of nesting of [s and ]s are
-- in effect for each character.
select ch, pos,
sum(case ch when '[' then 1 when ']' then -1 else 0 end) OVER (ORDER BY pos)
from chars
),
transformed(ch, pos) as (
-- and this query transforms [s to {s or ]s to }s if the nesting
-- level is appropriate. Note that we use one less level of nesting
-- for closing brackets because the closing bracket it self has already
-- reduced the nesting level.
select
case
when ch = '[' and lvl <= 2 then '{'
when ch = ']' and lvl <= 1 then '}'
else ch
end,
pos
from nesting
)
-- Finally, reconstruct the new string from the (char, position) tuples
select
string_agg(ch, '' order by pos)
from transformed;
$$;
如果您想要快速,请使用适当的过程语言-或PL/Python2中的C:
create or replace function replpy(instr text) returns text language plpythonu as $$
def pyrepl(instr):
level=0
for ch in instr:
if ch == '[':
level += 1
if level <= 2:
yield '{'
else:
yield '['
elif ch == ']':
if level <= 2:
yield '}'
else:
yield ']'
level -= 1
else:
yield ch
return ''.join(pyrepl(instr))
$$;
create或replace函数replpy(instr text)将文本语言plpythonu返回为$$
def pyrepl(仪表):
级别=0
对于总仪表:
如果ch=='[':
级别+=1
如果级别为Wow,感谢您提供了详细的解决方案。我需要一点时间来评估这些解决方案。但是,我要提到的是,性能是一个很大的考虑因素。我已经创建了自己的plpgsql解决方案,但使用了position()和substr()对于包含超过1000万个字符的列,速度太慢,我就是这样。很抱歉,我在最初的帖子@wildplasser中没有提到这一点。我将进一步提到,我已经开始使用plpythonu解决方案,但该语言扩展目前尚未安装,并且我在尝试安装时出错。我可能不得不忍住想想看。Jonny 5,这很管用!提供的其他解决方案看起来也很有希望,但正如我或多或少要求的那样,它成功地使用了正则表达式。正如我在后面的评论中指出的,我已经尝试了@wildplasser的方法,但它在真正长的文本值上太慢了。在几十万个字符的字符串上position and substring方法在经过数小时的处理后永远不会完成。pgAdmin报告说,这种正则表达式方法需要270毫秒!我也非常感谢对正则表达式的详细解释,以及如何在必要时进行更深入的解释。做得好!@PaulAngelno Well,如果速度是一个问题,我建议使用PL/Python,这要快一点,或者使用C扩展,如我发布的示例。我不认为正则表达式是适合这项工作的工具,尽管这正是你所要求的。@CraigRinger同意了,我有兴趣在plpythonu中使用它,看看它是如何执行的,特别是因为数据已经是Python列表格式。不幸的是,Python扩展不是w在我的环境中工作并搜索该问题意味着我必须从源代码等方面重建我的PostgreSQL。现在,我将走最后一条阻力之路。Paul,很高兴这对你有效,因为我无法测试它:)以及@CraigRinger提供了关于该问题的不同观点,并进行了所有基准测试。
create or replace function repl(text) returns text language sql volatile as
$$
select
string_agg(ch, '' order by pos)
from (
select
case
when ch = '[' and sum(case ch when '[' then 1 when ']' then -1 else 0 end) OVER (ORDER BY pos) <= 2 then '{'
when ch = ']' and sum(case ch when '[' then 1 when ']' then -1 else 0 end) OVER (ORDER BY pos) <= 1 then '}'
else ch
end,
pos
from unnest(regexp_split_to_array($1,'')) with ordinality as chars(ch, pos)
) as transformed(ch, pos)
$$;
create or replace function replpy(instr text) returns text language plpythonu as $$
def pyrepl(instr):
level=0
for ch in instr:
if ch == '[':
level += 1
if level <= 2:
yield '{'
else:
yield '['
elif ch == ']':
if level <= 2:
yield '}'
else:
yield ']'
level -= 1
else:
yield ch
return ''.join(pyrepl(instr))
$$;
#include "postgres.h"
#include "fmgr.h"
#include "utils/builtins.h"
PG_MODULE_MAGIC;
PG_FUNCTION_INFO_V1(replc);
Datum replc(PG_FUNCTION_ARGS);
PGDLLEXPORT Datum
replc(PG_FUNCTION_ARGS)
{
/* Set `buf` to a palloc'd copy of the input string, deTOASTed if needed */
char * const buf = text_to_cstring(PG_GETARG_TEXT_PP(0));
char * ch = buf;
int depth = 0;
while (*ch != '\0')
{
switch (*ch)
{
case '[':
depth++;
if (depth <= 2)
*ch = '{';
break;
case ']':
if (depth <= 2)
*ch = '}';
depth--;
break;
}
ch++;
}
if (depth != 0)
ereport(WARNING,
(errmsg("Opening and closing []s did not match, got %d extra [s", depth)));
PG_RETURN_DATUM(CStringGetTextDatum(buf));
}