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

问题描述

一段时间以来,我一直想添加一个安全除法函数来帮助那些没有花太多时间编写 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;

样本选择和输出

-- (real,int))
select '5.1/nullif(null,0)',5.1/nullif(null,0)    as result union all 
select 'div_safe(5.1,div_safe(5.1,0)         as result union all

-- (0,0)
select '0/nullif(0,0)         as result union all 
select 'div_safe(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'',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,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,2) | 3.33              |
+-----------------------------------+-------------------+

解决方法

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

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