在postgres中,我可以编写一个自定义函数来处理不同的数字类型而无需多个函数定义吗?

时间:2013-04-22 07:49:39

标签: postgresql sql-function

作为一个例子,假设我想在postgres中编写一个安全地划分两个数字的自定义函数 - 即它应该检查其中一个参数是否为空以及除数是否为零。它应该优雅地处理这些错误条件,否则返回预期的商。

只要两个参数具有相同的数字类型(例如,两个整数,都是数字等),当前代码就可以正常工作。

CREATE OR REPLACE FUNCTION safe_divide(anyelement, anyelement) RETURNS numeric AS $$
  SELECT CASE 
     WHEN $1 IS NULL OR $2 IS NULL OR $2 = 0 THEN NULL 
     ELSE $1::numeric / $2::numeric
     END;
$$ LANGUAGE SQL;

我的问题:有没有办法编写这个函数,以便我可以提供不同的数字类型?

我想避免:

  • 在调用函数时需要显式转换参数(例如safe_divide(x::numeric, y::numeric)

  • 需要为每种可能的数据类型定义函数

谢谢!

3 个答案:

答案 0 :(得分:3)

如果使用数字参数和双精度定义函数,则可以将其用于每个数字参数。

  CREATE OR REPLACE FUNCTION safe_divide(numeric, numeric) RETURNS numeric AS $$
   SELECT CASE 
     WHEN $1 IS NULL OR $2 IS NULL OR $2 = 0 THEN NULL ELSE $1 / $2 END;
  $$ LANGUAGE SQL;

  CREATE OR REPLACE FUNCTION safe_divide(double precision, double precision)
  RETURNS numeric AS $$
   SELECT CASE 
     WHEN $1 IS NULL OR $2 IS NULL OR $2 = 0 THEN NULL 
                                             ELSE $1::numeric / $2::numeric END;
  $$ LANGUAGE SQL;

对其他类型的数据类型没有意义

 postgres=# select safe_divide(10::float,10::int);
       safe_divide       
 ------------------------
  1.00000000000000000000
 (1 row)

 postgres=# select safe_divide(10::numeric,10::int);
       safe_divide       
 ------------------------
  1.00000000000000000000
 (1 row)

 postgres=# select safe_divide(10::int,10::int);
       safe_divide       
 ------------------------
  1.00000000000000000000
 (1 row)

 postgres=# select safe_divide(10,10.0);
       safe_divide       
 ------------------------
  1.00000000000000000000
 (1 row)

这是PostgreSQL中的典型模式

答案 1 :(得分:0)

我担心这是不可能的:

  

声明为anyelement的每个位置(参数或返回值)   允许具有任何特定的实际数据类型,但在任​​何给定的   打电话他们必须都是相同的实际类型。   http://www.postgresql.org/docs/current/static/extend-type-system.html

我认为最好的方法是将参数投射到数字。

答案 2 :(得分:0)

我希望这也会对某人有所帮助。

感谢 Pavel Studule 将我引向这个答案。

它确实使代码干燥,但是要获得覆盖,您仍然需要定义它们。

在某些情况下,您可以定义包含原始工作的原始函数,然后创建调用原始函数并在函数内转换值的新函数定义。

CREATE OR REPLACE FUNCTION last_day_in_month(dt date) RETURNS integer AS $$
BEGIN
  RETURN DATE_PART('days',
    DATE_TRUNC('month', dt)
    + '1 MONTH'::INTERVAL
    - '1 DAY'::INTERVAL
  );
END; $$
LANGUAGE PLPGSQL;

CREATE OR REPLACE FUNCTION last_day_in_month(dt timestamp) RETURNS integer AS $$
BEGIN
  RETURN last_day_in_month(dt::DATE);
END; $$
LANGUAGE PLPGSQL;