插入整数列时不要静默舍入浮点输入

Don't round float input silently when inserting into integer column

我有一个 table 喜欢:

CREATE TABLE foo(bar int)

我有一个脚本可以向其中插入值 table:

INSERT INTO foo(bar)
VALUES (1), (2), (3.2)

浮点值自动四舍五入以适应数据类型:

> SELECT * FROM foo;
 bar
-----
   1
   2
   3
(3 rows)

Postgres 是否有任何内置的东西可以防止这种情况发生,而是引发错误? (甚至警告?)

数字常量 3.2 最初解析为数据类型 numeric(而非 float)。手册中的详细信息 here.

integer 列的赋值工作静默进行,因为在标准 Postgres 中为 numeric --> integer 注册了一个“赋值”转换。

要获得所需的行为,您必须在 Postgres 源代码中实现转换的函数中加入警告(并重新编译)。付出的代价非常高。 (版本更新呢?)

或者您删除或更改已注册的演员表。您可以用基于您自己的功能的版本替换演员 - 并在那里提出 WARNING 。但这很昂贵,并且可能会 大量 垃圾邮件警告。

您不想完全删除该演员表。许多计算都使用它。

解决方法?

可以使用这个简单的解决方法:仅对事务禁用转换:

BEGIN;

UPDATE pg_cast
SET    castcontext = 'e'               -- make the cast "explicit"
WHERE  castsource = 'numeric'::regtype
AND    casttarget = 'integer'::regtype;

INSERT INTO foo(bar)
VALUES (1), (2), (3.2);

UPDATE pg_cast
SET    castcontext = 'a'               -- revert back to "assignment"!
WHERE  castsource = 'numeric'::regtype
AND    casttarget = 'integer'::regtype;

COMMIT;

现在,在实际 numeric 输入的情况下会引发异常。 但是您需要超级用户权限才能执行此操作。您可以将其封装在 SECURITY DEFINER 函数中。相关:

  • Is there a way to disable updates/deletes but still allow triggers to perform them?

并且您不想长时间锁定系统目录 pg_cast,而并发操作可能会发生。所以我宁愿不在并发的情况下这样做。

解决方案?

您可以将输入值移动到 CTE 并在 WHERE 子句中进行测试以跳过插入(静默地)如果它们不是所有有效的整数值:

WITH input(i) AS (
   VALUES (1), (2), (3.2)  -- one numeric makes all fall back to numeric
   )
INSERT INTO foo (bar)
SELECT i
FROM   input
WHERE  (SELECT pg_typeof(i) FROM input LIMIT 1) = 'integer'::regtype;

db<>fiddle here

然后您可以检查命令标记是否插入了任何内容。

或者将其全部包装在一个 plpgsql 函数中,检查是否实际插入了任何内容,如果没有,则 RAISE 根据您的需要进行检查。相关范例:

  • Count the rows affected by plpgsql function
  • How to programmatically check if row is deletable?