我有一个我无法弄清楚的问题。我准备了一张表格,可以存储一些交货价格数据。
CREATE TABLE delivers_prices(
id SERIAL PRIMARY KEY
, price NUMERIC(40,2) NOT NULL
, weight_from NUMERIC(20) NOT NULL
, weight_to NUMERIC(20) NOT NULL
, zone INTEGER NULL REFERENCES zones (id) ON UPDATE CASCADE ON DELETE CASCADE
, deliver INTEGER NOT NULL REFERENCES delivers (id) ON UPDATE CASCADE ON DELETE CASCADE
, CHECK(weight_from < weight_to)
);
问题是我需要约束两个字段 weight_from 和 weight_to
算法需要检查当前插入的行是否不在已插入行的weight_from和weight_to之间的范围内。更准确地说,当交货价格(行)是指定范围内交货的两倍时,情况不可能
我已经实现了触发器,但它们没有像我预期的那样工作
CREATE FUNCTION delivers_prices_unique( ) RETURNS TRIGGER AS
$func$
DECLARE weightFrom NUMERIC(20);
DECLARE weightTo NUMERIC(20);
DECLARE deliverId INTEGER;
BEGIN
CASE
WHEN TG_OP = 'DELETE' THEN
weightFrom := OLD.weight_from;
weightTo := OLD.weight_to;
deliverId := OLD.deliver;
ELSE
weightFrom := NEW.weight_from;
weightTo := NEW.weight_to;
deliverId := NEW.deliver;
END CASE;
IF
EXISTS (
SELECT * FROM delivers_prices oth
WHERE oth.deliver = deliverId
AND oth.weight_from BETWEEN weightFrom AND weightTo
OR oth.weight_to BETWEEN weightFrom AND weightTo
)
THEN
RAISE EXCEPTION 'delivery price for given weight exist';
RETURN NULL;
ELSE
RETURN NEW;
END IF;
END;
$func$ LANGUAGE 'plpgsql';
使用功能
CREATE TRIGGER check_delivers_prices
BEFORE UPDATE OR INSERT OR DELETE
ON delivers_prices
FOR EACH ROW
EXECUTE PROCEDURE delivers_prices_unique();
和一些例子:
INSERT INTO delivers_prices(price, weight_from, weight_to, deliver)
VALUES (22, 20, 100, 1); // we inserting the range 20 - 100
所以从现在开始交付20到100公斤的产品将花费22欧元
INSERT INTO delivers_prices(price, weight_from, weight_to, deliver)
VALUES (22, 21, 22, 1); // should not be inserted beacuse we already have row with range 20 - 100 and this row will be between that range.
为什么触发不抛出异常beacuse
SELECT * FROM delivers_prices oth
WHERE oth.deliver = 1
AND oth.weight_from BETWEEN 21 AND 22
OR oth.weight_to BETWEEN 21 AND 22
我在If子句中的触发器中知道SELECT有问题。我将不胜感激,建议或帮助
答案 0 :(得分:1)
无需触发器,您可以使用exclusion constraint
执行此操作alter table delivers_prices
add constraint unique_weight_range
exclude using gist (numrange(weight_from, weight_to, '[]') with &&);
使用的范围将包括边界,因此如果范围为20-100,则无法插入范围,例如从15-20。但是这可以在约束的定义中改变。
您可以对每个delivery_id应用检查。
alter table delivers_prices
add constraint unique_weight_range
exclude using gist (delivery with =, numrange(weight_from, weight_to, '[]') with &&);
这意味着仅对具有相同deliver
值的行检查重叠范围。
但是,您需要install扩展名btree_gist才能在GiST索引中使用整数值。
答案 1 :(得分:0)
Firstly it's not entirely clear to me whether you want to check whether the record you're inserting is entirely contained within some existing row - for example, existing row from/to: (20, 100), new row (21, 22), new row is wholly contained within existing row - or whether you're just looking for some sort of overlap (e.g. existing row (20, 100), new row (19, 22), overlap found).
Either way your EXISTS query is broken because you are missing brackets around the parts of the OR statement. As it is, a match on delivery id is optional. Due to using OR, I assume you're happy to raise an exception on a range overlap. So:
SELECT TRUE
FROM delivers_prices
WHERE deliver = deliverId
AND (weightFrom BETWEEN weight_from AND weight_to
OR weightTo BETWEEN weight_from AND weight_to
)
Currently you're checking whether the existing record starts or ends inside the new range. I assume what you really want is to check whether the new range starts or ends inside the existing range.
It's also worth pointing out a couple other issues with the trigger: it's pointless returning NULL after raising an exception, as that code will never execute; and use snake_case for variable names.