插入整数列时不要静默舍入浮点输入
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?
我有一个 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?