如何编写将检查插入值的postgresql触发器

时间:2018-01-15 16:42:02

标签: postgresql

我有一个我无法弄清楚的问题。我准备了一张表格,可以存储一些交货价格数据。

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有问题。我将不胜感激,建议或帮助

2 个答案:

答案 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.