问题描述
CREATE TABLE foo(bar int)
INSERT INTO foo(bar)
VALUES (1),(2),(3.2)
float值被自动舍入以适合数据类型:
> 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
函数中。相关:
并且您不想长时间锁定系统目录pg_cast
,因为并发操作可能会发生。因此,我宁愿不使用并发性。
解决方案?
您可以将输入值移至CTE并在WHERE
子句中进行测试,以在所有插入值都不都是有效整数值的情况下(无提示)跳过插入内容:
WITH input(i) AS (
VALUES (1),(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 提琴here
然后您可以检查命令标签是否插入了任何内容。
或将其全部包装在plpgsql函数中,检查是否实际插入了任何内容,如果没有,则RAISE
。相关示例: