从 PostgreSQL 中的字段中提取数字
Extract numbers from a field in PostgreSQL
我有一个 table,在 Postgres 8.4 中有一个类型为 varchar
的列 po_number
。它存储带有一些特殊字符的字母数字值。我想忽略字符 [/alpha/?/$/encoding/.]
并检查该列是否包含数字。如果它是一个数字,则需要将其类型转换为数字或传递 null,因为我的输出字段 po_number_new
是一个数字字段。
示例如下:
我厌倦了这个说法:
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 条记录插入两个表 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;
两者都非常快而且完全相同!
我有一个 table,在 Postgres 8.4 中有一个类型为 varchar
的列 po_number
。它存储带有一些特殊字符的字母数字值。我想忽略字符 [/alpha/?/$/encoding/.]
并检查该列是否包含数字。如果它是一个数字,则需要将其类型转换为数字或传递 null,因为我的输出字段 po_number_new
是一个数字字段。
示例如下:
我厌倦了这个说法:
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 条记录插入两个表 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;
两者都非常快而且完全相同!