从 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()
要便宜得多。正则表达式更加通用,我们希望在这种情况下消除所有 数字。相关:
但是为什么是 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 条记录插入到两个表 s
和 t
中,如下所示(来自 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% - 甚至更改了经历 INSERT
s 的表的顺序。
但是,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 中的子字符串函数从字符串中提取单词