3

我正在尝试在 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

有人可以给我建议吗?

4

2 回答 2

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

返回“文本”。但它确实应该说setof text,这就是陷阱:regex_matches是一个集合返回函数。当在 PostgreSQL 中的 FROM 子句之外调用时,它们具有......有趣的......行为。

模式匹配

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

第一个产生 30 行。第二个产生 20 个。玩得开心解释为什么。Pg 中的 SELECT 列表中的多个 SRF 会产生疯狂的结果,如果偶尔有用的话。

PostgreSQL 9.3 支持 SQL 标准LATERAL子句,这要归功于 Tom Lane,它为当前行为提供了一个健全且定义明确的替代方案。

于 2012-10-09T09:07:27.110 回答
2

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

改用substring()正则表达式。它返回text。在@Craig 的回答中重用演示表

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

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

于 2012-10-10T01:47:07.507 回答