将值传递给PL / pgSQL函数的语法

时间:2016-06-04 23:44:10

标签: postgresql triggers plpgsql precision type-conversion

我刚刚在Postgres中编写触发器和函数。

我编写了一个函数,只要将价格输入数据库,价格就会变为.99或.00。

CREATE OR REPLACE FUNCTION public.cents(price numeric)
 RETURNS numeric
 LANGUAGE plpgsql
 LEAKPROOF
AS $function$
DECLARE
   dollars text;
   cents text;
   new_price numeric;
BEGIN
   dollars := split_part(price::text, '.', 1);
   cents := split_part(price::text, '.', 2);
   IF cents != '00' THEN cents := '99';
   ENDIF;
   new_price := dollars || '.' || cents;
   RETURN new_price;
END;
$function$

我已阅读doc on triggers,这些例子似乎更复杂。

我不确定我是否理解如何创建一个触发器,只要价格列中的记录更新,它就会运行此功能。

这看起来是否正确?触发器中没有提到价格列。

CREATE OR REPLACE FUNCTION public.cents()
 RETURNS trigger
 LANGUAGE plpgsql
 LEAKPROOF
AS $tr_cents$
DECLARE
   dollars text;
   cents text;
   new_price numeric;
BEGIN
   dollars := split_part(OLD::text, '.', 1);
   cents := split_part(OLD::text, '.', 2);
   IF cents != '00' THEN cents := '99';
   ENDIF;
   new_price := dollars || cents;
   RETURN new_price;
END;
$tr_cents$;

CREATE TRIGGER tr_cents BEFORE INSERT OR UPDATE ON microwaves
    FOR EACH ROW EXECUTE PROCEDURE cents();

文档中的示例也使用了RETURN NEW,但我不确定这对我的代码有何影响,或者是否有必要?

2 个答案:

答案 0 :(得分:2)

假设缺少信息:

  • price定义为 numeric NOT NULL
  • 有一个CHECK约束强制 正面 价格。
  • Postgres 9.5。 (解决方案适用于Postgres 9.0 +。)

我这样阅读 目标

保留没有(重要)小数位数(.00)的数字,并将所有其他数字更改为.99

见下文"没有(重要)小数位"或.00 ...

如果所有触发器都有,那么最有效的方法是将条件放在触发器本身的WHEN子句中。 The manual:

  

BEFORE触发器中,WHEN条件在之前评估   函数是或将被执行,因此使用WHEN并不重要   不同于在触发功能开始时测试相同的条件。

(还有更多,请阅读手册。)
这样,如果不需要,甚至不会调用触发器函数。逻辑可以从根本上简化

CREATE OR REPLACE FUNCTION tr_cents()
  RETURNS trigger AS
$tr_cents$
BEGIN
   -- only called WHEN (NEW.price % 1 IN (.00, .99)
   NEW.price := trunc(NEW.price) + .99;
   RETURN NEW;
END
$tr_cents$  LANGUAGE plpgsql LEAKPROOF;
CREATE TRIGGER microwaves_cents
BEFORE INSERT OR UPDATE ON microwaves
FOR EACH ROW 
WHEN ((NEW.price % 1) <> ALL ('{.00,.99}'::numeric[]))
EXECUTE PROCEDURE tr_cents();

请注意,触发器会以非法价格值启动INSERT UPDATE。不只是

  

每当价格列中的记录更新时。

您在触发功能结束时 需要 RETURN NEW;,否则行上的操作将被取消。 The manual:

  

触发器函数必须返回NULL或具有与触发器触发的表格结构完全相同的记录/行值。

你根本不需要public.cents()功能。

测试用例

CREATE TABLE microwaves (m_id serial PRIMARY KEY, price numeric);

INSERT INTO microwaves (m_id, price) VALUES
     (1, 0.00)
   , (2, 0.01)
   , (3, 0.02)
   , (4, 0.99)
   , (5, 1.00)
   , (6, 1.01)
   , (7, 1.02)
   , (8, 1.99)
   , (9, 12.34);

UPDATE microwaves SET price = 2.0  WHERE m_id = 7;
UPDATE microwaves SET price = 2.5  WHERE m_id = 8;
UPDATE microwaves SET price = 5.99 WHERE m_id = 9;

TABLE microwaves;

结果:

 m_id | price
------+-------
    1 |  0.00
    2 |  0.99
    3 |  0.99
    4 |  0.99
    5 |  1.00
    6 |  1.99
    7 |   2.0
    8 |  2.99
    9 |  5.99

数据类型numeric比例

..以及为什么您的功能public.cents(price numeric) 陷阱

比例是小数位数。

numeric是一种任意精度类型。除非您为类型指定 precision scale ,否则它将保留与输入完全相同的文字数字。喜欢:numeric(10,2)The manual:

  

指定:

NUMERIC
     

没有任何精度或比例创建一个数字列   任何精度和比例的值都可以存储,直到   实施精度限制。这种专栏(数字   没有精度和比例)不会强制输入任何值   特定比例,而具有声明比例的数字列将   将输入值强制转换为该比例。

从不存储前导零,但小数部分中的尾随零以这种方式保留,即使无关紧要The manual在这方面很容易被误读,进一步下来:

  

数字值在物理上存储时没有任何额外的前导或尾随零。

注意单词&#34; extra &#34;。意思是,Postgres不会添加尾随零,但它会保留你添加的零 - 即使这些对数值完全无关紧要。

numeric转换为text时,您需要注意这一点。在小数部分中检查"00"将适用于numeric,其配置比例为numeric (9,2)。但是对于您在函数中使用的普通numeric 不可靠 。考虑:

SELECT (numeric(9,2) '1')::numeric AS num_cast_from_num_with_scale
      , numeric '1.00'             AS num_with_scale
      , numeric '1'                AS num_without_scale;

 num_cast_from_num_with_scale | num_with_scale | num_without_scale
------------------------------+----------------+-------------------
                         1.00 |           1.00 |                 1

这样,无关紧要的尾随零变为重要。而且我非常怀疑它应该是怎样的。您的函数IF cents != '00' ...中的测试public.cents(price numeric)加载的步枪。当您从numeric(9,2)列传递值时,它可能会按预期工作,但是&#34;突然&#34;一旦你使用其他来源的价值就会中断。

答案 1 :(得分:1)

您将返回值描述为数字,但按事实返回字符串。还有几种类型的转换不是一个好点。有更简单的方法。 F.前。

CREATE OR REPLACE FUNCTION cents(price numeric) RETURNS numeric AS
$BODY$
begin
    IF price IS NOT NULL then
        IF price % 1 != 0 then
            price := floor(price) + 0.99;
        end IF;
    END IF;
RETURN price;
end;
$BODY$ LANGUAGE plpgsql;

要对任何插入/更新需求执行此类更新:

CREATE TABLE test (
    price numeric
);

CREATE FUNCTION price_update() RETURNS trigger AS $price_update$
    BEGIN

        IF NEW.price IS NOT NULL THEN
            NEW.price = public.cents(NEW.price);
        END IF;

        RETURN NEW;
    END;
$price_update$ LANGUAGE plpgsql;

CREATE TRIGGER on_price_update BEFORE INSERT OR UPDATE ON test
    FOR EACH ROW EXECUTE PROCEDURE price_update();

让我们检查:

=# insert into test (price) values (2), (1.1), (5);
INSERT 0 3

=# select * from test;
 price 
-------
     2
  1.99
     5
(3 rows)

=# update test set price = 5.01 where price = 5;
UPDATE 1

=# select * from test;
 price 
-------
     2
  1.99
  5.99
(3 rows)

=# update test set price = 3 where price = 1.99;
UPDATE 1

=# select * from test;
 price 
-------
     2
  5.99
     3
(3 rows)