Postgres 13.3 中函数重载的奇怪行为
Odd behavior with overloaded function in Postgres 13.3
我在 PG 13.3 中添加了一对重载函数来处理安全视觉,可以选择舍入。我通过例程 运行 一些简单的示例案例,在一个案例中,输出意外地变化。我希望有人可以阐明可能导致这种不一致的原因。首先,这里是 div_safe (anycompatible, anycompatible) : real
和 div_safe (anycompatible, anycompatible, integer) : real
函数的代码。 (我尝试在第三个参数中将 integer
替换为 anycompatible
,但没有任何区别。)
------------------------------
-- No rounding
------------------------------
CREATE OR REPLACE FUNCTION tools.div_safe(
numerator anycompatible,
denominator anycompatible)
RETURNS real
AS $BODY$
SELECT numerator/NULLIF(denominator,0)::real
$BODY$
LANGUAGE SQL;
COMMENT ON FUNCTION tools.div_safe (anycompatible, anycompatible) IS
'Pass in any two values that are, or can be coerced into, numbers, and get a safe division real result.';
------------------------------
-- Rounding
------------------------------
CREATE OR REPLACE FUNCTION tools.div_safe(
numerator anycompatible,
denominator anycompatible,
rounding_in integer)
RETURNS real
AS $BODY$
SELECT ROUND(numerator/NULLIF(denominator,0)::numeric, rounding_in)::real
$BODY$
LANGUAGE sql;
COMMENT ON FUNCTION tools.div_safe (anycompatible, anycompatible, integer) IS
'Pass in any two values that are, or can be coerced into, numbers, the number of rounding digits, and get back a rounded, safe division real result.';
我在编写代码时将这些检查放在一起:
-- (real, int))
select '5.1/nullif(null,0)', 5.1/nullif(null,0) as result union all
select 'div_safe(5.1,0)', div_safe(5.1, 0) as result union all
-- (0, 0)
select '0/nullif(0,0)', 5.1/nullif(null,0) as result union all
select 'div_safe(0, 0)', div_safe(0, 0) as result union all
-- (int, int)
select '5/nullif(8,0)::real', 5/nullif(8,0)::real as result union all
select 'div_safe(5,8)', div_safe(5, 8) as result union all
-- (string, int)
select 'div_safe(''5'',8)', div_safe('5', 8) as result union all
select 'div_safe(''8'',5)', div_safe('8', 5) as result union all
-- Rounding: Have to convert real result to numeric to pass it into ROUND (numeric, integer)
select 'round(div_safe(10,3)::numeric, 2)',
round(div_safe(10,3)::numeric, 2) as result union all
-- Pass a third parameter to specify rounding:
select 'div_safe(20,13,2)', div_safe(20, 13, 2) as result
+-----------------------------------+--------------------+
| ?column? | result |
+-----------------------------------+--------------------+
| 5.1/nullif(null,0) | NULL |
| div_safe(5.1,0) | NULL |
| 0/nullif(0,0) | NULL |
| div_safe(0, 0) | NULL |
| 5/nullif(8,0)::real | 0.625 |
| div_safe(5,8) | 0.625 |
| div_safe('5',8) | 0.625 |
| div_safe('8',5) | 1.600000023841858 |
| round(div_safe(10,3)::numeric, 2) | 3.33 |
| div_safe(20,13,2) | 1.5399999618530273 |
+-----------------------------------+--------------------+
最后一行我觉得不对,应该四舍五入为1.54
。我发现我 在其他测试之一存在的情况下出现这种行为。 具体来说:
select '5/nullif(8,0)::real', 5/nullif(8,0)::real as result union all
没有它,最后一行 returns 1.54
,正如预期的那样。
任何人都可以阐明发生了什么事吗?与anycompatible
和UNION ALL
的组合有关吗?我缺少一些非常简单的东西?
而且,如果有人知道,anynum
是否有可能在未来添加为伪类型?
关于不一致输出的跟进
我的原始问题已经得到了有用的答案(谢谢!),并且正在跟进后续问题。即,为什么我的函数在 returning 之前对数据进行舍入,然后在最终结果中更改值。它认为我在这里缺少一些基本的东西,而且并不明显。我认为我需要确认正在调用函数的正确版本,并且 RAISE NOTIFICATION
以获取值,如方法内部所示。这个新版本是div_safe_p (anycompatible, anycompatible, integer) : real
,写在PL/PgSQL:
------------------------------
-- Rounding
------------------------------
drop function if exists tools.div_safe_p(anycompatible,anycompatible,integer);
CREATE OR REPLACE FUNCTION tools.div_safe_p(
numerator anycompatible,
denominator anycompatible,
rounding_in integer)
RETURNS real
AS $BODY$
DECLARE
result_r real := 0;
BEGIN
SELECT ROUND(numerator/NULLIF(denominator,0)::numeric, rounding_in)::real INTO result_r;
RAISE NOTICE 'Calling div_safe_p(%, %, %) : %', numerator, denominator, rounding_in, result_r;
RETURN result_r;
END
$BODY$
LANGUAGE plpgsql;
COMMENT ON FUNCTION tools.div_safe_p (anycompatible, anycompatible, integer) IS
'Pass in any two values that are, or can be coerced into, numbers, the number of roudning digits, and get back a rounded, safe division real result.';
这是一个示例调用和输出:
select 5/nullif(8,0)::real union all
select div_safe_p(10,3, 2)::real
+--------------------+
| ?column? |
+--------------------+
| 0.625 |
| 3.3299999237060547 |
+--------------------+
div_safe_p
的结果似乎被转换为 double
,而不是真实的。检查 RAISE NOTICE
控制台输出,函数 returned 3.33
:
NOTICE: Calling div_safe_p(10, 3, 2) : 3.33
是的,这个3.33
显示为3.3299999237060547
。我不清楚为什么值会根据函数return编辑的方式进行修改。我也无法通过手动转换值来重现转换。 select 3.33::real
和 select 3.33::double precision
return 3.33
.
另一个变体,与原始变体相同,只是没有 ::real
铸件:
select 5/nullif(8,0) union all
select div_safe_p(10,3, 2)
+----------+
| ?column? |
+----------+
| 0 |
| 3.33 |
+----------+
确实看起来遇到的第一个值正在引导列键入,正如已经回答的那样。但是,我很困惑为什么这会改变函数本身的行为。或者,至少改变了输出的解释方式。
如果这听起来不错……也许是。当我 运行 遇到我无法解释的特殊情况时,我希望弄清楚发生了什么,以便我可以在未来预测和解决更复杂的示例。
感谢任何照明!
这符合预期,因为 type resolution rules for UNION
:
Select the first non-unknown input type as the candidate type, then consider each other non-unknown input type, left to right.
现在第一个非 NULL 数据类型是 double precision
(参见 type resolution rules for operators),因此所有结果都转换为 double precision
,导致不精确性可见。如果没有该测试,结果的类型为 real
,因此 PostgreSQL 显示的数字很少,足以隐藏不精确性。
使用 pg_typeof
函数来显示数据类型是很有用的,这将使事情变得清晰:
SELECT pg_typeof(v)
FROM (SELECT NULL
UNION ALL
SELECT 2::real / 3::real
UNION ALL
SELECT pi()) AS t(v);
pg_typeof
══════════════════
double precision
double precision
double precision
(3 rows)
我在 PG 13.3 中添加了一对重载函数来处理安全视觉,可以选择舍入。我通过例程 运行 一些简单的示例案例,在一个案例中,输出意外地变化。我希望有人可以阐明可能导致这种不一致的原因。首先,这里是 div_safe (anycompatible, anycompatible) : real
和 div_safe (anycompatible, anycompatible, integer) : real
函数的代码。 (我尝试在第三个参数中将 integer
替换为 anycompatible
,但没有任何区别。)
------------------------------
-- No rounding
------------------------------
CREATE OR REPLACE FUNCTION tools.div_safe(
numerator anycompatible,
denominator anycompatible)
RETURNS real
AS $BODY$
SELECT numerator/NULLIF(denominator,0)::real
$BODY$
LANGUAGE SQL;
COMMENT ON FUNCTION tools.div_safe (anycompatible, anycompatible) IS
'Pass in any two values that are, or can be coerced into, numbers, and get a safe division real result.';
------------------------------
-- Rounding
------------------------------
CREATE OR REPLACE FUNCTION tools.div_safe(
numerator anycompatible,
denominator anycompatible,
rounding_in integer)
RETURNS real
AS $BODY$
SELECT ROUND(numerator/NULLIF(denominator,0)::numeric, rounding_in)::real
$BODY$
LANGUAGE sql;
COMMENT ON FUNCTION tools.div_safe (anycompatible, anycompatible, integer) IS
'Pass in any two values that are, or can be coerced into, numbers, the number of rounding digits, and get back a rounded, safe division real result.';
我在编写代码时将这些检查放在一起:
-- (real, int))
select '5.1/nullif(null,0)', 5.1/nullif(null,0) as result union all
select 'div_safe(5.1,0)', div_safe(5.1, 0) as result union all
-- (0, 0)
select '0/nullif(0,0)', 5.1/nullif(null,0) as result union all
select 'div_safe(0, 0)', div_safe(0, 0) as result union all
-- (int, int)
select '5/nullif(8,0)::real', 5/nullif(8,0)::real as result union all
select 'div_safe(5,8)', div_safe(5, 8) as result union all
-- (string, int)
select 'div_safe(''5'',8)', div_safe('5', 8) as result union all
select 'div_safe(''8'',5)', div_safe('8', 5) as result union all
-- Rounding: Have to convert real result to numeric to pass it into ROUND (numeric, integer)
select 'round(div_safe(10,3)::numeric, 2)',
round(div_safe(10,3)::numeric, 2) as result union all
-- Pass a third parameter to specify rounding:
select 'div_safe(20,13,2)', div_safe(20, 13, 2) as result
+-----------------------------------+--------------------+
| ?column? | result |
+-----------------------------------+--------------------+
| 5.1/nullif(null,0) | NULL |
| div_safe(5.1,0) | NULL |
| 0/nullif(0,0) | NULL |
| div_safe(0, 0) | NULL |
| 5/nullif(8,0)::real | 0.625 |
| div_safe(5,8) | 0.625 |
| div_safe('5',8) | 0.625 |
| div_safe('8',5) | 1.600000023841858 |
| round(div_safe(10,3)::numeric, 2) | 3.33 |
| div_safe(20,13,2) | 1.5399999618530273 |
+-----------------------------------+--------------------+
最后一行我觉得不对,应该四舍五入为1.54
。我发现我 在其他测试之一存在的情况下出现这种行为。 具体来说:
select '5/nullif(8,0)::real', 5/nullif(8,0)::real as result union all
没有它,最后一行 returns 1.54
,正如预期的那样。
任何人都可以阐明发生了什么事吗?与anycompatible
和UNION ALL
的组合有关吗?我缺少一些非常简单的东西?
而且,如果有人知道,anynum
是否有可能在未来添加为伪类型?
关于不一致输出的跟进
我的原始问题已经得到了有用的答案(谢谢!),并且正在跟进后续问题。即,为什么我的函数在 returning 之前对数据进行舍入,然后在最终结果中更改值。它认为我在这里缺少一些基本的东西,而且并不明显。我认为我需要确认正在调用函数的正确版本,并且 RAISE NOTIFICATION
以获取值,如方法内部所示。这个新版本是div_safe_p (anycompatible, anycompatible, integer) : real
,写在PL/PgSQL:
------------------------------
-- Rounding
------------------------------
drop function if exists tools.div_safe_p(anycompatible,anycompatible,integer);
CREATE OR REPLACE FUNCTION tools.div_safe_p(
numerator anycompatible,
denominator anycompatible,
rounding_in integer)
RETURNS real
AS $BODY$
DECLARE
result_r real := 0;
BEGIN
SELECT ROUND(numerator/NULLIF(denominator,0)::numeric, rounding_in)::real INTO result_r;
RAISE NOTICE 'Calling div_safe_p(%, %, %) : %', numerator, denominator, rounding_in, result_r;
RETURN result_r;
END
$BODY$
LANGUAGE plpgsql;
COMMENT ON FUNCTION tools.div_safe_p (anycompatible, anycompatible, integer) IS
'Pass in any two values that are, or can be coerced into, numbers, the number of roudning digits, and get back a rounded, safe division real result.';
这是一个示例调用和输出:
select 5/nullif(8,0)::real union all
select div_safe_p(10,3, 2)::real
+--------------------+
| ?column? |
+--------------------+
| 0.625 |
| 3.3299999237060547 |
+--------------------+
div_safe_p
的结果似乎被转换为 double
,而不是真实的。检查 RAISE NOTICE
控制台输出,函数 returned 3.33
:
NOTICE: Calling div_safe_p(10, 3, 2) : 3.33
是的,这个3.33
显示为3.3299999237060547
。我不清楚为什么值会根据函数return编辑的方式进行修改。我也无法通过手动转换值来重现转换。 select 3.33::real
和 select 3.33::double precision
return 3.33
.
另一个变体,与原始变体相同,只是没有 ::real
铸件:
select 5/nullif(8,0) union all
select div_safe_p(10,3, 2)
+----------+
| ?column? |
+----------+
| 0 |
| 3.33 |
+----------+
确实看起来遇到的第一个值正在引导列键入,正如已经回答的那样。但是,我很困惑为什么这会改变函数本身的行为。或者,至少改变了输出的解释方式。
如果这听起来不错……也许是。当我 运行 遇到我无法解释的特殊情况时,我希望弄清楚发生了什么,以便我可以在未来预测和解决更复杂的示例。
感谢任何照明!
这符合预期,因为 type resolution rules for UNION
:
Select the first non-unknown input type as the candidate type, then consider each other non-unknown input type, left to right.
现在第一个非 NULL 数据类型是 double precision
(参见 type resolution rules for operators),因此所有结果都转换为 double precision
,导致不精确性可见。如果没有该测试,结果的类型为 real
,因此 PostgreSQL 显示的数字很少,足以隐藏不精确性。
使用 pg_typeof
函数来显示数据类型是很有用的,这将使事情变得清晰:
SELECT pg_typeof(v)
FROM (SELECT NULL
UNION ALL
SELECT 2::real / 3::real
UNION ALL
SELECT pi()) AS t(v);
pg_typeof
══════════════════
double precision
double precision
double precision
(3 rows)