2014-09-27 355 views
3

我有一个类型为TEXT的PostgreSQL列中的数据,我需要对其进行一些字符替换。具体而言,我想用大括号替换方括号。问题是我只想替换深度不超过两层的括号,如果你包含主要的封闭括号。这些字符串可能会很长,所以我认为正则表达式可能是要走的路(regexp_replace函数),但我对正则表达式并不擅长。这里有一个这样的价值的一个例子:PostgreSQL:正则表达式用大括号替换一级方括号

[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} 

提前感谢!

+0

哇,谢谢你的详细解决方案。我需要一点时间来评估这些。不过,我会提到,表现是一个很重要的考虑因素。我已经创建了自己的plpgsql解决方案,但对于包含超过1000万个字符的列,使用position()和substr()方法太慢,正如我的情况。对不起,我没有在我的第一篇文章中提到@wildplasser。 – 2014-09-27 22:12:16

+0

我会进一步提到,我已经开始使用plpythonu解决方案,但目前尚未安装语言扩展,并且尝试安装它时出现错误。我可能不得不咬紧牙关,弄清楚。 – 2014-09-27 22:37:27

回答

3

这将是一个痛苦与正则表达式为in PostgreSQL flavor possibly no recursion is available

对于最多2级的嵌套深度检查时,如果下面的双替换工作(不能测试它)

regexp_replace(
    regexp_replace('str', E'\\[(([^][]|\\[([^][]|\\[[^][]*\\])*\\])*)\\]', E'{\\1}', 'g') 
, E'\\[(([^][]|\\[([^][]|\\[[^][]*\\])*\\])*)\\]', E'{\\1}', 'g') 

的想法是匹配并在两次通过更换最外[]。 参见在regex101例如:

pass 1{0,0,0,[12,2],0,0,[12,[1,2,3]],12,0,[12,2,[2]],12,0,12,0,0}
pass 2{0,0,0,{12,2},0,0,{12,[1,2,3]},12,0,{12,2,[2]},12,0,12,0,0}

\[[^][]*\](未逸出),随后的任何量[...]

  • \[开口括号
  • [^][]*的实例相匹配字符,没有方括号
  • \]接着右方括号

注意的是,如果在字符串总是与[启动时,与]结束,并且表示0电平的一个实例(不通过][被分隔)第一/内regexp_replace可以还通过在$结束在^开始和]更换[来完成:E'^\\[(.*)\\]$'E'{\\1}'


要添加在这里筑巢,最大的例子4级深度:

\[([^][]| # outer 
\[([^][]| # lvl 1 
\[([^][]| # lvl 2 
\[([^][]| # lvl 3 
\[[^][]*\] # lvl 4 
)*\] 
)*\] 
)*\] 
)*\] 

结束语什么是外[]内部分成capture group为4个级别的模式将成为:

\[(([^][]|\[([^][]|\[([^][]|\[([^][]|\[[^][]*\])*\])*\])*\])*)\] 

为使用regex_replace需要额外逃脱[]

\\[(([^][]|\\[([^][]|\\[([^][]|\\[([^][]|\\[[^][]*\\])*\\])*\\])*\\])*)\\] 

这可以像t在两次传球,他的第一图案和用E'{\\1}'

+0

乔尼5,这个作品!提供的其他解决方案也看起来很有前途,但是这成功地使用了正则表达式,因为我多少有点要求。正如我在后面的评论中指出的那样,我已经尝试了@ wildplasser的方法,对于真正的长文本值它太慢了。在几十万字符的字符串中,位置和子字符串方法在处理几个小时后永远不会结束。这个正则表达式的方法需要270毫秒,由pgAdmin报告!我也非常感谢正则表达式的详细解释以及如何在必要时做更深入的研究。做得好! – 2014-09-28 00:25:14

+0

@PaulAngelno好吧,如果速度是一个问题,我建议使用PL/Python,它比较快,或者使用像我发布的例子那样的C扩展。我不认为正则表达式是这个工作的正确工具,但这就是你要求的。 – 2014-09-28 01:00:15

+0

@CraigRinger,同意了,我有兴趣在plpythonu中使用它,看看它是如何执行的,特别是因为数据已经是Python列表格式。不幸的是,Python扩展没有在我的环境中工作,在这个问题上搜索表明我将不得不从源重建我的PostgreSQL等等。现在我将走上一次阻力的道路。 – 2014-09-28 02:14:34

3

这是丑陋的,但它的工作原理(并避免正则表达式复杂;-)我希望我有涵盖所有角落的情况下更换...

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; 
3

只是踢,这是一个完全在SQL中的解决方案。它使用CTE来表示符号的清晰度,但是您可以在FROM中使用子查询,而不使用递归CTE。

编辑:在Pl/Python中增加了简化的,更快的SQL版本,版本和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; 
$$; 

但是,它比其他解决方案慢。

  • 约翰尼5的正则表达式解决方案需要450毫秒的10,000次迭代。
  • wildplasser的replbracket需要950ms进行10,000次迭代。
  • 此CTE解决方案需要2050ms进行10,000次迭代。

摆脱热膨胀系数和使用unnest ... with ordinality其加速到约1400ms:

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) 
$$; 

如果你想快,使用恰当的过程语言 - 或C.在PL/Python2:

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)) 
$$; 

需要160ms。

OK,白费力气,让这样做的C. Full source code as an extension is here但这里的.c文件:

#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)); 
} 

运行时间:8ms的10,000迭代。足够好的是,它比原来的速度快了250倍,这就是强制子查询的开销。