在 Postgres 13 中使用 anycompatible 伪类型的安全除法函数,有问题吗?

Safe division function using the anycompatible pseudo-type in Postgres 13, gotchas?

一段时间以来,我一直想添加一个安全除法函数,以帮助那些没有花太多时间编写 SQL 查询的团队成员。我刚刚注意到 PG 13 似乎添加了一个 anycompatible 伪类型。我已经试过了,这似乎按希望和预期的方式工作。我以前没有使用过伪类型,我想知道我是否正在涉足一些我可能没有预料到的风险和限制。有人有什么注意事项吗?

下面列出了函数代码,并附有一个快速 select 来说明其在几种情况下的行为。

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 numbers that are, or can be converted to, numbers, and get a safe division real result.';

ALTER FUNCTION tools.div_safe (anycompatible, anycompatible)
    OWNER TO user_bender;

示例 select 并输出:

-- (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

+-----------------------------------+-------------------+
| ?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              |
+-----------------------------------+-------------------+

我认为您的使用没有问题。在这种情况下,您也可以使用 anyelement,它适用于旧版本。

如果你总是想要 real 结果,你的功能就很好。如果您希望结果与参数具有相同的类型,请对结果也使用伪类型。