当所有其他列相等时,对具有不同标签的值的约束

时间:2015-11-30 18:42:02

标签: sql postgresql constraints multiple-columns

我想创建一个约束来验证一个值(来自' nominal_value'列)是否归类为"最小值"在' stats_type'当其他列中的所有值相等时,列等于或小于分类为" average"的值。换句话说,给定相应的元组,除了列' oid',' stats_type'和' nominal_value',我想确保标记为"最小值"总是等于或小于标记为"平均"。

的值

很难解释,所以我做了下面的例子:

CREATE TABLE price (
    oid SERIAL  NOT NULL,
    product INTEGER  NOT NULL,
    territory INTEGER  NOT NULL,
    stats_type INTEGER  NOT NULL,
    year INTEGER  NOT NULL,
    nominal_value NUMERIC(6,2)  NOT NULL,
    data_source INTEGER  NOT NULL,
    CONSTRAINT pk_price PRIMARY KEY (oid),
    CONSTRAINT price_1 UNIQUE (product, territory, stats_type, year, data_source),
    CONSTRAINT price_2 CHECK (nominal_value > 0)
);

INSERT INTO price (oid, product, territory, stats_type, year, nominal_value, data_source) VALUES (1, 55, 5611, 1, 2014, 120, 3);
INSERT INTO price (oid, product, territory, stats_type, year, nominal_value, data_source) VALUES (2, 55, 5611, 2, 2014, 160, 3);
INSERT INTO price (oid, product, territory, stats_type, year, nominal_value, data_source) VALUES (3, 55, 5615, 1, 2014, 60, 3);
INSERT INTO price (oid, product, territory, stats_type, year, nominal_value, data_source) VALUES (4, 55, 5611, 3, 2014, 180, 3);
INSERT INTO price (oid, product, territory, stats_type, year, nominal_value, data_source) VALUES (5, 62, 5615, 1, 2013, 1500, 3);
INSERT INTO price (oid, product, territory, stats_type, year, nominal_value, data_source) VALUES (6, 62, 5615, 2, 2013, 1300, 3);

' stats_type'标签是:1 =最小,2 =平均,3 =最大。

看前两行;他们是平等的,除了' oid',' stats_type'和' nominal_value&#39 ;;所以我想验证:是120(在第一行标记为最小值)小于或等于160(标记为平均值的值)?在这种情况下答案是肯定的,所以它通过了验证。

现在,最后两行(5和6)也有关于列'产品','领域'年'年份'和' data_source'。但是,第5行中的标称值1500不应小于第6行中的1300(因为1500应该是最小值,1300是平均值)。

我该怎么做?是否可以使用“检查”来完成此类任务。约束

3 个答案:

答案 0 :(得分:2)

由于必须查询表,因此无法使用检查约束来完成此操作。您可以使用触发器:

create or replace function price_trigger()
returns trigger language plpgsql as $$
begin
    if exists (
        select 1
        from price p
        where (p.product, p.territory, p.year, p.data_source) = 
              (new.product, new.territory, new.year, new.data_source)
        and (
            p.stats_type < new.stats_type and p.nominal_value > new.nominal_value
            or
            p.stats_type > new.stats_type and p.nominal_value < new.nominal_value
            )
        )
    then
        raise exception 'Nominal value error';
    end if;
    return new;
end $$;

create trigger price_trigger
before insert or update on price
for each row
execute procedure price_trigger();

触发功能检查所有条件(min

答案 1 :(得分:1)

由于您的方案具有可以按任何顺序进行插入的复杂性,我建议您事先在辅助表中执行插入,例如price_check。

然后你可以设置一个触发器来执行SQL,例如:

SELECT pmin.* 
FROM price_check AS pmin
JOIN price_check AS pavg
    ON (pmin.product, pmin.territory, pmin.year, pmin.data_source) = (pavg.product, pavg.territory, pavg.year, pavg.data_source)
        AND pmin.stats_type = 1 
        AND pavg.stats_type = 2
        AND pmin.nominal_value <= pavg.nominal_value 

仅返回有效的最小值,并使用它来执行最终表格上的插入。请注意,我使用的是INNER JOIN,因此,如果没有平均对的最小值不会返回,则必须使用LEFT JOIN来执行此操作。

同样,您可以在price_check中设计查询,在price_log表中放置无效行,以便在后面手动检查它们。

或者你可以在你的桌子上放置一个INSTEAD OF INSERT触发器而不需要辅助表,但即便如此,创建一个LOG表来手动检查和修复无效行也是一种很好的做法。

答案 2 :(得分:0)

考虑对表上的所有插入/更新(或upsert)使用postgresql函数。然后,您可以使用案例逻辑来识别可以进入的内容,并为不正确的输入数据事件指定真实的API状态返回码。你可以用PL / pgSQL,python或任何你喜欢的postgres中的任何其他可扩展语言来编写它。

离。

CREATE OR REPLACE FUNCTION band.usp_band_add_update(
i_sess TEXT,
i_ban_name TEXT,
i_ban_email TEXT,
i_ban_description TEXT,
i_geo_id BIGINT
)
RETURNS TABLE(
band_id BIGINT,
status_id INT,
status_desc TEXT
)
LANGUAGE plpgsql
AS $$
DECLARE
_user BIGINT;
_ban_id BIGINT;
_status_id INT;
_status_desc TEXT;
_pass TEXT;
BEGIN

SELECT asr_user 
  INTO _user
  FROM sess.usp_sess_check(i_sess);

IF _user IS NOT NULL THEN
    SELECT ban_id 
      FROM band.band
     WHERE ban_user = _user
      INTO _ban_id;
    IF _ban_id IS NOT NULL THEN
        UPDATE band.band 
           SET ban_name = i_ban_name,
               ban_description = i_ban_description,
               ban_geo_id = i_geo_id,
               ban_modified = now()
         WHERE ban_user = _user;
        _status_id = 200;
        _status_desc = 'ban_id updated: ' || _ban_id::text;
    ELSE
        INSERT INTO band.band (ban_user, ban_name, ban_email, ban_description, ban_geo_id)
        VALUES (_user, i_ban_name, i_ban_email, i_ban_description, i_geo_id)
        RETURNING ban_id INTO _ban_id;
        _status_id = 200;
        _status_desc = 'ban_id inserted: ' ||  _ban_id::text;
    END IF;
ELSE
    _status_id = 402;
    _status_desc = 'User does not exist.';
END IF;
RETURN QUERY SELECT _ban_id, _status_id, _status_desc;
END;
$$;