从 PostgreSQL 中的字段中提取数字

Extract numbers from a field in PostgreSQL

我有一个 table,在 Postgres 8.4 中有一个类型为 varchar 的列 po_number。它存储带有一些特殊字符的字母数字值。我想忽略字符 [/alpha/?/$/encoding/.] 并检查该列是否包含数字。如果它是一个数字,则需要将其类型转换为数字或传递 null,因为我的输出字段 po_number_new 是一个数字字段。

示例如下:

SQL Fiddle.

我厌倦了这个说法:

select 
(case when  regexp_replace(po_number,'[^\w],.-+\?/','') then po_number::numeric
else null
end) as po_number_new from test

但是我得到了显式转换的错误:

我想你想要这样的东西:

select (case when regexp_replace(po_number, '[^\w],.-+\?/', '') ~ '^[0-9]+$'
             then regexp_replace(po_number, '[^\w],.-+\?/', '')::numeric
        end) as po_number_new 
from test;

即替换后需要对字符串进行转换

注意:这假设 "number" 只是一串数字。

我用来确定 po_number 字段是否包含数字的逻辑是当尝试删除数字时它的长度应该减少。

如果是,则应从 po_number 列中删除所有非数字数字 ([^\d])。否则,应返回 NULL

select case when char_length(regexp_replace(po_number, '\d', '', 'g')) < char_length(po_number)
            then regexp_replace(po_number, '[^0-9]', '', 'g')
            else null
       end as po_number_new
from test

简单地说:

SELECT NULLIF(regexp_replace(po_number, '\D','','g'), '')::numeric AS result
FROM   tbl;

\D 是 class shorthand 表示“不是数字”。
您需要第 4 个参数 'g'(表示“全局”)来替换 all 次出现。
Details in the manual.

对于一组已知的、有限的要替换的字符,普通 string manipulation functions like replace() or translate() 便宜得多。正则表达式更加通用,在这种情况下我们想要消除所有 数字。相关:

  • Regex remove all occurrences of multiple characters in a string

但为什么是 Postgres 8.4? Consider upgrading to a modern version.

考虑过时版本的陷阱:

  • Order varchar string as numeric

如果你想提取浮点数尝试使用这个:

SELECT NULLIF(regexp_replace(po_number, '[^\.\d]','','g'), '')::numeric AS result FROM tbl;

与Erwin Brandstetter的回答相同,只是表述不同:

[^...] - 匹配除排除字符列表之外的任何字符,将排除的字符代替 ...

\. - 点字符(也可以将其更改为 , 字符)

\d - 数字字符

从第 12 版开始 - 在撰写本文时是 2 年 + 4 个月前(但 我可以在已接受的答案上看到的最后一次编辑之后),你 可以使用GENERATED FIELD在one-time的基础上很容易地做到这一点,而不是每次你想SELECT一个新的[=63]时都必须计算它=].

此外,您可以使用@ErwinB运行dstetter 的TRANSLATE function to extract your digits which is less expensive than the REGEXP_REPLACE

我会这样做(下面的所有代码都可以在 fiddle here 上找到):

CREATE TABLE s
(
  num TEXT,
  
  new_num INTEGER GENERATED ALWAYS AS
    (NULLIF(TRANSLATE(num, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ. ', ''), '')::INTEGER) STORED
);

您可以根据需要添加到 TRANSLATE 函数中的 'ABCDEFG... 字符串 - 我有小数点 (.) 和 space ( ) 最后 - 根据您的输入,您可能希望有更多的字符!

并检查:

INSERT INTO s VALUES ('2'), (''), (NULL), (' ');
INSERT INTO t VALUES ('2'), (''), (NULL), (' ');
SELECT * FROM s;
SELECT * FROM t;

结果(两者相同):

num    new_num
  2          2
          NULL
          NULL
          NULL

所以,我想检查我的解决方案的效率如何,所以我 运行 下面的测试将 10,000 条记录插入两个表 st 如下(来自 here):

EXPLAIN (ANALYZE, BUFFERS, VERBOSE)
INSERT INTO t 
with symbols(characters) as 
(
  VALUES ('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789')
)
select string_agg(substr(characters, (random() * length(characters) + 1) :: INTEGER, 1), '')
from symbols
join generate_series(1,10) as word(chr_idx) on 1 = 1 -- word length
join generate_series(1,10000) as words(idx) on 1 = 1 -- # of words
group by idx;

差异不是那么大,但正则表达式解决方案始终慢了大约 25% - 甚至改变了经历 INSERTs 的表的顺序。

然而,TRANSLATE 解决方案真正出色的地方在于执行如下“原始”SELECT

EXPLAIN (ANALYZE, BUFFERS, VERBOSE)
SELECT
  NULLIF(TRANSLATE(num, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ. ', ''), '')::INTEGER
FROM s;

REGEXP_REPLACE 解决方案相同。

差异非常明显,TRANSLATE 大约需要其他功能的时间的 25%。最后,为了公平起见,两张表我也都这样做了:

EXPLAIN (ANALYZE, BUFFERS, VERBOSE)
SELECT
  num, new_num
FROM t;

两者都非常快而且完全相同!