isnumeric() 与 PostgreSQL
Posted
技术标签:
【中文标题】isnumeric() 与 PostgreSQL【英文标题】:isnumeric() with PostgreSQL 【发布时间】:2013-04-24 15:25:45 【问题描述】:我需要确定给定的字符串是否可以在 SQL 语句中解释为数字(整数或浮点数)。如下:
SELECT AVG(CASE WHEN x ~ '^[0-9]*.?[0-9]*$' THEN x::float ELSE NULL END) FROM test
我发现 Postgres 的 pattern matching 可以用于此。所以我修改了this place 中给出的语句来合并浮点数。这是我的代码:
WITH test(x) AS (
VALUES (''), ('.'), ('.0'), ('0.'), ('0'), ('1'), ('123'),
('123.456'), ('abc'), ('1..2'), ('1.2.3.4'))
SELECT x
, x ~ '^[0-9]*.?[0-9]*$' AS isnumeric
FROM test;
输出:
x | isnumeric
---------+-----------
| t
. | t
.0 | t
0. | t
0 | t
1 | t
123 | t
123.456 | t
abc | f
1..2 | f
1.2.3.4 | f
(11 rows)
如您所见,前两项(空字符串''
和唯一的句点'.'
)被错误分类为数字类型(它们不是)。我现在无法接近这一点。任何帮助表示赞赏!
更新基于this answer(及其cmets),我将模式调整为:
WITH test(x) AS (
VALUES (''), ('.'), ('.0'), ('0.'), ('0'), ('1'), ('123'),
('123.456'), ('abc'), ('1..2'), ('1.2.3.4'), ('1x234'), ('1.234e-5'))
SELECT x
, x ~ '^([0-9]+[.]?[0-9]*|[.][0-9]+)$' AS isnumeric
FROM test;
这给出了:
x | isnumeric
----------+-----------
| f
. | f
.0 | t
0. | t
0 | t
1 | t
123 | t
123.456 | t
abc | f
1..2 | f
1.2.3.4 | f
1x234 | f
1.234e-5 | f
(13 rows)
正如我现在所看到的,科学记数法和负数仍然存在一些问题。
【问题讨论】:
您不必担心负数吗?科学记数法呢? @muistooshort 再次感谢,我对这种输入特别感兴趣。这种模式匹配方法并不像我预期的那么简单。 负数的正则表达式很简单:'^-?([0-9]+[.]?[0-9]*|[.][0-9]+)$'
正确吗?
【参考方案1】:
您可能已经注意到,基于正则表达式的方法几乎不可能正确执行。例如,您的测试表明 1.234e-5
不是有效数字,而实际上是。此外,您错过了负数。如果某个东西看起来像一个数字,但是当您尝试存储它时会导致溢出怎么办?
相反,我建议创建函数,尝试实际转换为NUMERIC
(或FLOAT
,如果您的任务需要它)并返回TRUE
或FALSE
,具体取决于此转换是否成功。
这段代码将完全模拟函数ISNUMERIC()
:
CREATE OR REPLACE FUNCTION isnumeric(text) RETURNS BOOLEAN AS $$
DECLARE x NUMERIC;
BEGIN
x = $1::NUMERIC;
RETURN TRUE;
EXCEPTION WHEN others THEN
RETURN FALSE;
END;
$$
STRICT
LANGUAGE plpgsql IMMUTABLE;
对您的数据调用此函数会得到以下结果:
WITH test(x) AS ( VALUES (''), ('.'), ('.0'), ('0.'), ('0'), ('1'), ('123'),
('123.456'), ('abc'), ('1..2'), ('1.2.3.4'), ('1x234'), ('1.234e-5'))
SELECT x, isnumeric(x) FROM test;
x | isnumeric
----------+-----------
| f
. | f
.0 | t
0. | t
0 | t
1 | t
123 | t
123.456 | t
abc | f
1..2 | f
1.2.3.4 | f
1x234 | f
1.234e-5 | t
(13 rows)
它不仅更正确、更易于阅读,而且如果数据实际上是一个数字,它的运行速度也会更快。
【讨论】:
1.234d+5 也是一个“有效”数字。几年前,我在做一些数据仓库工作时遇到了这种格式。它在一个旧的 Fortran 程序的输出中;它表示一个双精度浮点值。无论他们是否正确导入了任何办公软件。 好吧,我的意思是,如果您要说存储在 Postgres 数据库中的给定字符串是否是有效数字,唯一合理的方法是询问 Postgres 服务器本身对它的看法。如果它说1.234d+5
不是一个有效数字,那么你不能真正使用 Postgres 方法将它转换为有效数字。
有更简单的解决方案来处理NULL
s。保持函数体不变,只需将这一行添加到函数声明中:RETURNS NULL ON NULL INPUT
再次阅读文档后发现,只需添加关键字STRICT
就相当于RETURNS NULL ON NULL INPUT
并且可以满足您的要求。
我已经编辑了答案,包括上面讨论的 STRICT
关键字。这是一个数据库功能;它应该用NULL
做正确的事情。我对其进行了测试,如果没有STRICT
,它会为NULL
返回true
,这绝不是任何人想要的。【参考方案2】:
你的问题是小数点两边的两个 0 或多个 [0-9] 元素。您需要在号码识别行中使用逻辑 OR |
:
~'^([0-9]+\.?[0-9]*|\.[0-9]+)$'
这将排除小数点作为有效数字。
【讨论】:
您在.
s 上缺少一些转义符,它们将匹配'1x1'
和'x1'
。
是的,我习惯了 Oracle 和 Java,请确保您的转义正确,|应该是正确的 POSIX OR 运算符和 .应该是 PERIOD,而不是 POSIX 'all characters' 运算符。【参考方案3】:
我想人们可能会有这样的看法(这不是对异常处理的滥用),但通常我认为应该为此使用异常处理机制。测试字符串是否包含数字是正常处理的一部分,并不是“例外”。
但是你不处理指数是对的。这是正则表达式的第二次尝试(下)。我必须寻求使用正则表达式的解决方案的原因是,当遇到错误时给出退出指令时,此处作为“正确”解决方案提供的解决方案将失败:
SET exit_on_error = true;
我们经常在运行 SQL 脚本组时使用它,并且当我们想要在出现任何问题/错误时立即停止时。当给出这个 session 指令时,调用 isnumeric 的“正确”版本将导致脚本立即退出,即使没有遇到“真正的”异常。
create or replace function isnumeric(text) returns boolean
immutable
language plpgsql
as $$
begin
if $1 is null or rtrim($1)='' then
return false;
else
return (select $1 ~ '^ *[-+]?[0-9]*([.][0-9]+)?[0-9]*(([eE][-+]?)[0-9]+)? *$');
end if;
end;
$$;
【讨论】:
你还是漏了点。您的正则表达式将通过1e
,这不是一个数字。并且,它会传递 1e100000,它可能看起来像一个数字,但它不能存储为一个,1e100000::NUMERIC
将无法投射。
上面的正则表达式将为“1e”返回false,这是正确的。但是你对非常大或非常小的幂表示是正确的。但是,numeric定义为:小数点前最多131072位;小数点后最多 16383 位。因此,在我的上下文中,并且我认为对于许多其他业务上下文,如果我们尝试测试比这更大的数字,则会出现严重问题,我希望程序崩溃或抛出异常.如果我从事天文学或物理学工作,这可能不是真的。出于我的目的,我不能每次调用它时都抛出一个。
严格来说,像 1e100000 这样的数字是数字。您只是不能将其从字符串转换为数字。
如果不能转换成数字,就不是数字。【参考方案4】:
从 PostgreSQL 9.5 (2016) 开始,您可以只询问 json 字段的类型:
jsonb_typeof(field)
来自PostgreSQL documentation:
json_typeof(json) jsonb_typeof(jsonb)
以文本字符串的形式返回最外层 JSON 值的类型。可能的类型有 object、array、string、number、boolean 和 null。
示例
当聚合数字并想忽略字符串时:
SELECT m.title, SUM(m.body::numeric)
FROM messages as m
WHERE jsonb_typeof(m.body) = 'number'
GROUP BY m.title;
如果没有 WHERE,::numeric
部分会崩溃。
【讨论】:
如果您知道要测试的字符串是 JSON,但它似乎不适用于任意字符串,这可以正常工作。例如:比较select jsonb_typeof('"foo"')
和select jsonb_typeof('foo');
不幸的是,这并没有真正起作用。它在大多数输入上都会崩溃,甚至在像1.
这样的数字上也会崩溃【参考方案5】:
公认的解决方案的明显问题是它滥用了异常处理。如果遇到另一个问题,您将永远不会知道它,因为您已经抛弃了异常。很糟糕的形式。正则表达式将是执行此操作的更好方法。下面的正则表达式似乎表现良好。
create function isnumeric(text) returns boolean
immutable
language plpgsql
as $$
begin
if $1 is not null then
return (select $1 ~ '^(([-+]?[0-9]+(\.[0-9]+)?)|([-+]?\.[0-9]+))$');
else
return false;
end if;
end;
$$
;
【讨论】:
这不是滥用异常处理。它只包含单个运算符:x = $1::NUMERIC;
,旨在将参数转换为数值。如果此演员表出现任何问题,则意味着 $1
不是数字,这就是我们想要得到答案的全部。没有办法在这里得到任何其他例外。您的尝试仍然无效 - 它不会解析 1e6
,这是一个有效数字。
您的解决方案返回不适用于科学记数法,例如1e-5
或 1.e5
或 'Nan'
被接受的解决方案正确检测为有效数值。以上是关于isnumeric() 与 PostgreSQL的主要内容,如果未能解决你的问题,请参考以下文章
[Python3 填坑] 002 isdecimal() 与 isdigit() 的区别 + isnumeric() 的补充