【问题标题】:Odd behavior with overloaded function in Postgres 13.3Postgres 13.3 中重载函数的奇怪行为
【发布时间】:2021-06-28 03:41:18
【问题描述】:

我在 PG 13.3 中添加了一对重载函数来处理安全视觉,可选择舍入。我通过例程运行了一些简单的示例案例,在一种情况下,输出意外变化。我希望有人可以阐明可能导致这种不一致的原因。首先,这是div_safe (anycompatible, anycompatible) : realdiv_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 

没有它,最后一行返回1.54,正如预期的那样。

任何人都可以了解发生了什么吗? anycompatibleUNION ALL 的组合是否与此有关?我错过了一些非常简单的东西?

而且,如果有人知道的话,anynum 将来是否有可能被添加为伪类型?

输出不一致的跟进

我的原始问题已经得到了有用的答案(谢谢!),并且正在跟进后续问题。即,为什么我的函数在返回数据之前先对数据进行舍入,然后在最终结果中更改值。它认为我在这里缺少一些基本的东西,而且并不明显。我想我需要确认正在调用正确版本的函数,并且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控制台输出,函数返回3.33

NOTICE:  Calling div_safe_p(10, 3, 2) : 3.33

是的,这个3.33 显示为3.3299999237060547。我不清楚为什么会从函数返回的方式修改值。我也无法通过手动转换值来重现转换。 select 3.33::realselect 3.33::double precision 都返回 3.33

另一个变体,除了没有::real 铸件外,与原版相同:

select  5/nullif(8,0) union all
select div_safe_p(10,3, 2)

+----------+
| ?column? |
+----------+
| 0        |
| 3.33     |
+----------+

确实看起来遇到的第一个值是指导列输入,正如已经回答的那样。但是,我很难理解为什么这会改变函数本身的行为。或者,至少改变了输出的解释方式。

如果这听起来不错……也许是这样。当我遇到无法解释的特殊情况时,我希望弄清楚发生了什么,以便我可以预测未来更复杂的示例并解决问题。

感谢您的任何启发!

【问题讨论】:

    标签: postgresql stored-procedures overloading


    【解决方案1】:

    这与type resolution rules for UNION 的预期一致:

    选择第一个非未知输入类型作为候选类型,然后从左到右考虑其他非未知输入类型。

    现在第一个非 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)
    

    【讨论】:

    • 感谢您对文档的澄清和说明。在这种情况下,我根本无法理解。如果我理解您的解释,类型解析器会找出最佳类型以适应列中的各种值。在这种情况下,double 是我预期的real。我无法理解的是,为什么这会在将值添加到列之前将结果显式四舍五入为 2 位小数的函数的输出发生变化。显然,这里还有一些东西让 m 无法理解。
    • UNION ALL 列表中的第一个非 NULL 值恰好是 real。然后将所有以下元素强制转换为该数据类型。
    • 感谢您帮助我理解。我对为什么函数返回3.33(已确认)感到困惑,然后该值被转换为看起来像double 的值。当我将3.33 显式转换为double 时,我得到3.33,而不是上面显示的值。我已经用 PL/PgSQL 编写的新版本的方法更新了我原来的问题,并提供了一些简单的案例来说明我的困惑。
    • 在最后添加的情况下,结果被强制转换为numeric
    猜你喜欢
    • 2013-03-04
    • 1970-01-01
    • 2015-10-24
    • 1970-01-01
    • 2010-10-07
    • 2016-01-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多