从 PostgreSQL 中的字段中提取数字

Posted

技术标签:

【中文标题】从 PostgreSQL 中的字段中提取数字【英文标题】:Extract numbers from a field in PostgreSQL 【发布时间】:2017-03-26 15:47:10 【问题描述】:

我在 Postgres 8.4 中有一个带有 po_number 类型的列 varchar 的表。它存储带有一些特殊字符的字母数字值。我想忽略字符 [/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

但是显式转换时出现错误:

【问题讨论】:

取消删除你的新问题,你会得到答案。只需仔细解释所有边缘情况,仅此而已。 没关系,蒂姆。从现在开始,我将发布一个适当的问题,无需任何编辑:) 没问题。顺便谢谢:) 【参考方案1】:

简单地说:

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

\D 是“不是数字”的类简写。 您需要第四个参数'g'(用于“全局”)来替换所有次出现。Details in the manual.

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

Regex remove all occurrences of multiple characters in a string PostgreSQL SELECT only alpha characters on a row Is there a regexp_replace equivalent for postgresql 7.4?

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

考虑过时版本的缺陷:

Order varchar string as numeric WARNING: nonstandard use of escape in a string literal

【讨论】:

您可能对我为这个问题提出的解决方案感兴趣 - 在这个特别简单的情况下使用 TRANSLATE 函数而不是更昂贵的 REGEXP_REPLACE?我会对您对我所写内容的任何评论感兴趣并感激不尽!让我来到这里的是this question - 很狡猾! :-) 我有一个适用于 PostgreSQL 的有效解决方案,而我正在与之聊天的一个人提出了一个 SQL Server 解决方案 - 尝试在没有正则表达式的情况下做到这一点很棘手!【参考方案2】:

我想你想要这样的东西:

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

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

注意:这里假设“数字”只是一串数字。

【讨论】:

你能解释一下正则表达式,.-+吗?这是什么意思? @Abelisto 我认为它们应该在括号内,尽管我现在不在 Postgre 前面进行测试。 在使用上述方法后我只得到空值......相反,我需要每条记录中的数字和没有数字的记录的空值【参考方案3】:

我用来确定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

【讨论】:

我在尝试此操作后遇到 SQL 错误 [42883] @user1538020 该错误是由您使用没有length 功能的Postgres 8.x 引起的。我更新为使用char_length,它现在应该可以工作了。 我做了一个选择版本();我现在使用的是 PostgreSQL 9.5.2,由 Visual C++ build 1800 编译,64 位。仍然出现错误。我发布了图片错误。 @user1538020:旁白:您得到的错误是由于输入错误:regex_replace regexp_replace。在 pg 8.4 中有 一个 length() 函数。 @Tim Biegeleisen : 当 char_length(regexp_replace(po_number, '\d', '', 'g')) 【参考方案4】:

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

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

这与 Erwin Brandstetter 的答案相同,但表达方式不同:

[^...] - 匹配除排除字符列表之外的任何字符,放置排除字符而不是 ...

\. - 点字符(也可以改为, char)

\d - 数字字符

【讨论】:

【参考方案5】:

自第 12 版以来 - 在撰写本文时是 2 年 + 4 个月前(但我可以在接受的答案中看到的最后一次编辑之后),您可以 一次性使用GENERATED FIELD 很容易做到这一点,而不是每次您希望SELECT 一个新的po_number 时都必须计算它。

此外,您可以使用TRANSLATE 函数来提取您的数字,这比@ErwinBrandstetter 的REGEXP_REPLACE solution proposed 更便宜!

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

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

您可以酌情在TRANSLATE 函数中添加'ABCDEFG... 字符串 - 我在末尾有小数点 (.) 和一个空格 ( ) - 您可能希望那里有更多字符取决于您的输入!

并检查:

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;

两者都非常快速且相同!

【讨论】:

是的,众所周知,正则表达式函数的成本要高得多(在最近的版本中变得更快,但仍然如此)。在过去的十年中,我一直在指出这一点:***.com/a/28172693/939860、***.com/a/34106732/939860、...但是,在 ASCII 时代列出所有可能的非数字更实用,而不是 UTF...跨度>

以上是关于从 PostgreSQL 中的字段中提取数字的主要内容,如果未能解决你的问题,请参考以下文章

从 PostgreSQL 的 json 字段中查询字符串和数字字段的有效方法

如何使用postgresql从字段中获取最后10位数字

如何使用 PostgreSql 中的子字符串函数从字符串中提取单词

从包含十进制 SQL Server 18 的字符串字段中提取数字

如何在 POSTGRESQL 中从 DATE 中提取年份

Excel从如何右开始提取字符