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

Syntax for passing values to a PL/pgSQL function

我不熟悉在 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,但我不确定它如何与我的代码一起工作,或者是否有必要?

您将 return 值描述为数字,但 return 实际上是一个字符串。还有几个类型转换不是一个好点。有更简单的方法。 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;

要对任何 insert/update 执行此类更新,需要:

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)

假设缺少信息:

  • price 定义为 numeric NOT NULL.
  • 有一个 CHECK 约束执行 正值 价格。
  • Postgres 9.5。 (解决方案应该适用于 Postgres 9.0+。)

我这样读你的objective:

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

见下文关于 "without (significant) fractional digits" 或 .00 ...

如果这就是触发器所做的全部,最有效的方法是将条件放在 WHEN 子句中 触发器 本身。 The manual:

In a BEFORE trigger, the WHEN condition is evaluated just before the function is or would be executed, so using WHEN is not materially different from testing the same condition at the beginning of the trigger function.

(还有很多,看说明书)
这样,如果不需要,甚至不会调用 触发函数 。逻辑可以彻底简化:

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 
<b>WHEN ((NEW.price % 1) <> ALL ('{.00,.99}'::numeric[]))</b>
EXECUTE PROCEDURE tr_cents();

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

whenever a record in the price column is updated.

需要RETURN NEW;在触发函数结束时否则对行的操作将被取消。 The manual:

A trigger function must return either NULL or a record/row value having exactly the structure of the table the trigger was fired for.

您根本不需要 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 是任意精度类型。它保留与输入完全相同的文字数字 - 除非您为类型指定 precisionscale 。喜欢:numeric(10,2)The manual:

Specifying:

NUMERIC

without any precision or scale creates a column in which numeric values of any precision and scale can be stored, up to the implementation limit on precision. A column of this kind (numeric without precision and scale) will not coerce input values to any particular scale, whereas numeric columns with a declared scale will coerce input values to that scale.

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

Numeric values are physically stored without any extra leading or trailing zeroes.

注意单词“extra”。意思是,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

这样,无关紧要 尾随零变为 重要。我严重怀疑它应该是这样的。函数 public.cents(price numeric) 中的测试 IF cents != '00' ... 上膛的步兵枪 。当您从 numeric(9,2) 列传递值时,它可能会按预期工作,但一旦您使用其他来源的值,"suddenly" 就会中断。