我刚刚在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
,但我不确定这对我的代码有何影响,或者是否有必要?
答案 0 :(得分:2)
假设缺少信息:
price
定义为 numeric NOT NULL
。CHECK
约束强制 正面 价格。我这样阅读 目标 :
保留没有(重要)小数位数(.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)