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,如果您的任务需要它)并返回TRUEFALSE,具体取决于此转换是否成功。

这段代码将完全模拟函数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 方法将它转换为有效数字。 有更简单的解决方案来处理NULLs。保持函数体不变,只需将这一行添加到函数声明中: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、booleannull

示例

当聚合数字并想忽略字符串时:

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-51.e5'Nan' 被接受的解决方案正确检测为有效数值。

以上是关于isnumeric() 与 PostgreSQL的主要内容,如果未能解决你的问题,请参考以下文章

Linq 中的 IsNumeric 等效项

[Python3 填坑] 002 isdecimal() 与 isdigit() 的区别 + isnumeric() 的补充

IsNumeric() 不适用于 Request.QueryString

isnumeric()方法

Python isnumeric()方法

ISNUMERIC() 不适用于小数