【问题标题】:Filter strings with regex before casting to numeric在转换为数字之前使用正则表达式过滤字符串
【发布时间】:2024-01-19 12:58:01
【问题描述】:

我有这个代码(已经在那里,不是我的):

SELECT
    a.id_original_contrato AS contrato,
    ( CASE WHEN d.value~'^\\d+$' THEN d.value::integer ELSE 0 END ) AS monto,
    EXTRACT(YEAR FROM b.value)::integer AS anoinicio,
    EXTRACT(YEAR FROM c.value)::integer AS anofin

...等(一些 JOIN 和 WHERE)

让我解释一下:d.value 来自一个值为character varying (200) 的表。该代码稍后将在另一个表中插入d.value(现在称为“monto”)作为integer。有人编码该正则表达式以提取一些字符或在其他情况下(ELSE),将其定义为 0。这些值仅在 integer 时有效。如果我使用像 76.44 这样的 d.value,由于该正则表达式它不起作用,它总是将其定义为 0。

好吧,我必须更改该代码,因为:

  • 我需要将新表中的 d.value 存储为 numeric,而不是 integer(在我的新表中,数据类型现在是 numeric
  • 但首先,我需要更正那个正则表达式,因为它会弄乱我的数字,例如 76.4466,56(点或逗号)。

我不确定那个正则表达式在做什么。以及如何使用更好或新的正则表达式来满足我的需求?

【问题讨论】:

  • 伟大的头像! (来自游戏“李小龙”)
  • MOAAGH!谢谢!

标签: regex postgresql casting numeric


【解决方案1】:

您应该声明您的 Postgres 版本以及编写代码时使用的版本(如果您知道的话)。 \\d 中的双反斜杠表示带有 standard_conforming_strings = off 的旧版本。 The manual:

从 PostgreSQL 9.1 开始,默认值为 on(之前的版本默认为关闭)。

在带有standard_conforming_strings = on 的现代版本中,这个字符串作为正则表达式几乎没有意义:'^\\d+$'。要检测由一位或多位数字组成的字符串,请使用E'^\\d+$'(以E 为前缀)或'^\d+$'。详情:

整数文字还允许为负数/正数添加可选的前导符号。在 Postgres 中也允许(自动修剪)前导/悬空空白
所以,这是integer 的完整正则表达式:

CASE WHEN d.value ~ <b>'^\s*[-+]?\d+\s*$'</b> THEN d.value::int ELSE 0 END

正则表达式解释:

^ .. 字符串开头
\s .. class shorthand for [[:space:]](空格)
* .. quantifier for 0 次或更多次
@ 987654342@ .. 由+- 组成的字符类
? .. 0 或 1 次的量词
\d .. [[:digit:]](数字)的类简写
@ 987654348@ .. 量词 1 次或多次
\s* .. 同上
$ .. 字符串结束

现在我们知道了基础知识。在我链接到的手册中阅读更多内容。考虑numeric string literals 的语法规则。而且,关于合法数字常量的状态:

常量中不能嵌入任何空格或其他字符

这是因为数字常量没有被引用,因此空格是不可能的。不适用于 casting 字符串。 容忍空白: 指数字符的前导、尾随和紧随其后。

所以这些都是转换为numeric 的合法字符串:

'^\s*[-+]?\d*\.?\d+(?:[eE]\s*[-+]?\d+)?\s*$'

唯一的新元素是parentheses (()) to denote the contained regular expression as atom。由于我们对反向引用不感兴趣,因此使用“非捕获”:(?:...) 并附加一个问号(?:[eE]\s*[-+]?\d+)? 表示:可以添加或不添加“指数”部分,作为一个整体

假设点 (.) 作为小数点分隔符。您可以改用逗号 (,) 或 [,\.] 来允许。但只有点对演员表是合法的。

测试:

SELECT '|' || txt || '|' As text_with_delim
     , txt ~ '^\s*[-+]?\d*\.?\d+([eE]\s*[-+]?\d+)?\s*$' As test
     , txt::numeric AS number
FROM   unnest ('{1, 123, 000, "  -1     ", +2, 1.2, .34, 5e6, " .5e   -6  "}'::text[]) txt;

结果:

 text_with_delim | test |  number
-----------------+------+-----------
 |1|             | t    |         1
 |123|           | t    |       123
 |000|           | t    |         0
 |  -1     |     | t    |        -1
 |+2|            | t    |         2
 |1.2|           | t    |       1.2
 |.34|           | t    |      0.34
 |5e6|           | t    |   5000000
 | .5e   -6  |   | t    | 0.0000005

或者您可能使用to_number() 来转换任意给定格式的字符串。

【讨论】:

  • 我明白了,嗯。编码在 9.2 中工作,现在它将在 9.4 中运行。我仍在阅读并试图得到你的答案。谢谢(+1)
  • 很好,只能使用 '^\s*[-+]?\d*\.?\d+([eE]\s*[-+]?\d+)?\s *$' 正则表达式我可以让它工作!我的 Postgresql 在 postgresql.conf 中设置为 US,如下所示:lc_numeric = 'en_US.UTF-8' 所以现在我只能使用. 作为小数分隔符,但在生产环境中设置为lc_numeric = 'es_ES.UTF-8' 所以我想我不会有问题.我会继续阅读并完成你在这里写的所有内容,Erwin,谢谢。
  • @pmirnd: lc_numeric 会影响像to_char() 这样的函数的行为,但是从textnumeric类型转换 不会不 取决于区域设置。那将是疯狂的。因此,逗号在 cast 到数字中是不合法的。您必须将其替换为 replace()translate()。或使用to_number()
  • 最后,我所做的并且现在可以正常工作的是:select cast(replace(d.value, ',', '.') as numeric) AS monto。哒哒!
【解决方案2】:

选择一个变体:

with v(value) as (
    values
    ('12,3'),
    ('12.3'),
    ('123'),
    ('123.'),
    ('.123'),
    ('1.2.3')
    )

select 
    value, 
    value ~ '^(\d+[,\.]\d+|\d+)$' as variant_a,
    value ~ '^(\d*[,\.]\d*|\d+)$' as variant_b,
    value ~ '^\d+[,\.]\d+$' as variant_c
from v;

 value | variant_a | variant_b | variant_c 
-------+-----------+-----------+-----------
 12,3  | t         | t         | t
 12.3  | t         | t         | t
 123   | t         | t         | f
 123.  | f         | t         | f
 .123  | f         | t         | f
 1.2.3 | f         | f         | f
(6 rows)

要将带有点或逗号的字符串转换为数字,请使用replace()

select replace(value, ',', '.')::numeric;   

【讨论】:

  • 谢谢伙计,我为此加了一个 +1,因为我学到了一些我不知道的东西。
最近更新 更多