45

我需要确定给定的字符串是否可以在 SQL 语句中解释为数字(整数或浮点数)。如下所示:

SELECT AVG(CASE WHEN x ~ '^[0-9]*.?[0-9]*$' THEN x::float ELSE NULL END) FROM test

我发现可以使用 Postgres 的模式匹配。所以我修改了这个地方给出的声明来合并浮点数。这是我的代码:

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)

如您所见,前两项(空字符串''和唯一句'.'点)被错误分类为数字类型(它们不是)。我现在无法接近这一点。任何帮助表示赞赏!


更新基于此答案(及其评论),我将模式调整为:

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)

正如我现在所看到的,科学记数法和负数仍然存在一些问题。

4

5 回答 5

100

您可能会注意到,基于正则表达式的方法几乎不可能正确执行。例如,您的测试说这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)

它不仅更正确、更容易阅读,而且如果数据实际上是一个数字,它的工作速度也会更快。

于 2013-04-25T03:58:01.130 回答
10

您的问题是小数点两侧的两个 0 或更多 [0-9] 元素。您需要|在号码标识行中使用逻辑 OR:

~'^([0-9]+\.?[0-9]*|\.[0-9]+)$'

这将仅排除小数点作为有效数字。

于 2013-04-24T15:30:16.660 回答
-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;
$$;
于 2018-10-30T15:14:19.577 回答
-3

从 PostgreSQL 9.5 (2016) 开始,您只需询问 json 字段的类型:

jsonb_typeof(field)

PostgreSQL 文档

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部分会崩溃。

于 2020-02-26T14:43:31.687 回答
-4

公认解决方案的明显问题是它滥用了异常处理。如果遇到另一个问题,您将永远不会知道它,因为您已经抛弃了异常。很糟糕的形式。正则表达式将是执行此操作的更好方法。下面的正则表达式似乎表现良好。

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;
$$
;
于 2018-10-17T16:12:31.270 回答