CASE with regexp:“在上下文中调用的集值函数,不能接受集合”

时间:2012-10-09 07:53:20

标签: regex postgresql postgresql-9.0 set-returning-functions

我正在尝试在PostgreSQL 9.0.1中进行稍微复杂的字符串转换。 my_col中的值是长字符串,格式为:

'12345_sometext_X12B_1'
'12345_sometext_optionaltext_Y09B_1'
'12345_sometext_optionaltext_X12A_1'

我需要将'X12'部分转换为已知的数值,有几个不同的已知值(最多5个)。

我希望能够在一个查询中确定这一点,而无需子查询。但是,以下内容对我不起作用。最后一列是抛出异常的那一列。由于某些原因,似乎我无法使用这些函数的输出来执行CASE语句。我已经将进行中的列仅用于演示目的。

select
          regexp_matches(my_col, E'^.*_([^_]*)[A-Z]{1}_\\d*$'), -- returns {'X12'}
         (regexp_matches(my_col, E'^.*_([^_]*)[A-Z]{1}_\\d*$'))[1], -- returns 'X12'
    case (regexp_matches(my_col, E'^.*_([^_]*)[A-Z]{1}_\\d*$'))[1]
        when 'X12' then '1200'
        when 'Y09' then '950'
        else '?' end -- should return '1200' but throws error
from my_table;

相反,我得到了错误:

ERROR: set-valued function called in context that cannot accept a set
SQL state: 0A000

有人可以告诉我吗?

2 个答案:

答案 0 :(得分:8)

鉴于数据:

create table my_table(my_col text);
insert into my_table(my_col) values
('12345_sometext_X12B_1'),
('12345_sometext_optionaltext_Y09B_1'),
('12345_sometext_optionaltext_X12A_1'),
('nomatch');

上述查询确实会产生您报告的错误。非常奇怪,因为:

SELECT pg_typeof((regexp_matches(my_col, E'^.*_([^_]*)[A-Z]{1}_\\d*$'))[1]);

返回'text'。它应该真的说setof text,这就是陷阱:regex_matches是一个集合返回函数。那些在PostgreSQL中从FROM子句外部调用时有......有趣......行为。

来自pattern matching

  

regexp_matches函数返回所有的文本数组   捕获由匹配POSIX正则表达式产生的子字符串   图案。它具有语法regexp_matches(string,pattern [,flags])。   该函数不能返回任何行,一行或多行

尝试重新构造查询以使用子查询来调用SRF。如果匹配器返回多行,则会失败:

SELECT 
  CASE (SELECT x[1] FROM regexp_matches(my_col, E'^.*_([^_]*)[A-Z]{1}_\\d*$') x)
    WHEN 'X12' THEN '1200'
    WHEN 'Y09' THEN '950'
    ELSE '?'
  END
FROM my_table;

想看看SELECT中有多奇怪的SRF在Pg中?比较这些查询的结果:

SELECT generate_series(1,10), generate_series(1,15);

SELECT generate_series(1,10), generate_series(1,20);

第1行产生30行。第二个产生20.有趣解释原因。如果偶尔有用的结果,Pg中SELECT列表中的多个SRF会产生疯狂。

PostgreSQL 9.3支持SQL标准LATERAL子句,感谢Tom Lane,它为当前行为提供了一个明智且定义明确的替代方案。

答案 1 :(得分:2)

regexp_matches()返回SETOF text[] 文本数组 ),这对于同一字符串中的一个模式的多个匹配很有用。但它只是这项任务的错误工具

使用substring()代替正则表达式。它返回text。重用demo table in @Craig's answer

SELECT CASE substring(my_col, '^.*_([^_]*)[A-Z]_\d*$')
         WHEN 'X12' THEN '1200'
         WHEN 'Y09' THEN '950'
         ELSE            '?'
       END As result
FROM   my_table;

返回:

 result
--------
 1200
 950
 1200
 ?

还略微简化了正则表达式。 {1}只是噪音。

如果你需要优化性能,请尝试不使用正则表达式,这些表达式功能强大但价格相对较高。类似的东西:

reverse(right(split_part(reverse(my_col), '_', 2), -1))

看起来更复杂,但在我的测试中仍然更快。