【问题标题】:CASE with regexp: "set-valued function called in context that cannot accept a set"带有正则表达式的 CASE:“在不能接受集合的上下文中调用的集合值函数”
【发布时间】:2012-09-29 12:07:48
【问题描述】:

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

有人可以给我建议吗?

【问题讨论】:

  • 第一:更新,您使用的是过时版本。升级到 9.0.10 以进行错误修复、安全性和稳定性改进。无需转储和重新加载,只需安装新的二进制文件即可。

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


【解决方案1】:

给定数据:

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 子句之外调用时,它们具有......有趣的......行为。

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

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

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

【讨论】:

  • 将参数增量更改为generate_series() 会看到行数的变化令人恐惧。
  • @cpburnz ... 以及它完全荒谬的工作方式?是的。这就是我使用LATERAL 的原因,从不在SELECT 列表中设置返回函数。
【解决方案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))

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

【讨论】:

  • 无论出于何种原因,substring 在这里的性能明显较差(尽管它确实解决了我的特定问题,这与上述相同)。您的答案缺少该问题包括数组索引器:regexp_matches(src, regex)[1]
  • @RhysvanderWaerden:这个原因会很有趣,我无法重现它。 substring() 在我的测试中更快(如预期的那样。)为什么答案会丢失有关数组索引的任何内容? SRF 在使用时无效,无论有无数组索引。我进行了一些更新并添加了一个更快的替代方案。
猜你喜欢
  • 2017-04-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-17
相关资源
最近更新 更多