使用同一个表中的select来更新前的PLpgsql

Alp*_*aus 0 postgresql plpgsql

我正在尝试创建一个PL/PGSQL触发器函数来检查新行的日期范围,以确保表中没有其他日期范围重叠的行(对于相同的product_id).我已经成功创建了该函数并将其设置为BEFORE INSERT触发器,但我正在试图弄清楚如何将其设置为BEFORE UPDATE触发器,因为触发器内的SELECT语句肯定会抛出异常,因为它适合重叠自身更新版本日期的标准.

这是我的功能:

CREATE OR REPLACE FUNCTION check_specials_dates() 
RETURNS trigger AS 
$$
DECLARE
BEGIN
  IF EXISTS (SELECT * FROM rar.product_specials 
           WHERE product_id = NEW.product_id 
             AND (
             (NEW.end_time between start_time and end_time) OR
             (NEW.start_time between start_time and end_time) OR
             (start_time between NEW.start_time and NEW.end_time))
  THEN
    RAISE EXCEPTION 
     'Cannot insert overlapping specials date for Product ID#%', NEW.product_id;   
 END IF; 
  RETURN NEW;
END
$$ LANGUAGE plpgsql;
Run Code Online (Sandbox Code Playgroud)

我的想法是IF EXISTS SELECT语句将返回一个匹配项,因为它将在它尝试更新的行上匹配.

它是否正确?如果是这样,我该如何解决它?

ara*_*nid 5

您使用的是哪个版本的PostgreSQL?从9.0开始,您可以使用排除约束和cube/btree_gist扩展来实现所有这些.

要使用触发器实现这一点,我通常会使用一个插入/更新后触发器,它使用相同的产品ID查看其他特殊内容,但插入/更新的行具有不同的主键.那是:

IF EXISTS (SELECT 1 FROM rar.product_specials
           WHERE product_specials.product_id = NEW.product_id
                 AND product_specials.product_special_id <> NEW.product_special_id
                 AND overlaps(NEW.start_time, NEW.end_time,
                              product_specials.start_time, product_specials.end_time))
Run Code Online (Sandbox Code Playgroud)

如果你还没有生成主键product_specials,那么这将是合理的添加一个.

排除约束解决方案

(因为我一直需要提醒自己该怎么做,所以我想把它写在某处)

(请注意,如果您的开始/结束时间是离散的(例如日期或您可以将端点修复到足够大的粒度),那么您可以在由触发器填充的辅助表上使用唯一性约束:PostgreSQL,触发器和并发强制执行临时密钥)

PostgreSQL可以使用其广泛的运算符/索引方法基础结构来强制执行广义排除约束 - 如果任何其他行满足一组操作,则拒绝接受行.传统的唯一性约束本质上就是这种情况的特殊情况 - 如果行中的某些值/值集合等于某个其他行的值/ 值,则会导致行被拒绝.

在您的情况下,如果与表中的其他行相比,product_id相等且范围(start_time,end_time)重叠,则需要拒绝行.

索引方法"gist"可用于构建索引以满足这种请求(具体地,重叠范围).扩展名"cube"提供了一个通用数据类型,它是gist-indexable,而"btree_gist"为整数提供了一个gist索引方法,允许这两种类型组合在一个索引中.

所以在PostgreSQL 9.1中:

CREATE EXTENSION cube;
CREATE EXTENSION btree_gist;
Run Code Online (Sandbox Code Playgroud)

(在9.0中,从contrib运行脚本)

这是我测试过的例子:

create table product_specials(product_special_id serial primary key,
    product_id int not null,
    start_time timestamp not null, end_time timestamp not null);
insert into product_specials(product_id, start_time, end_time)
 values(1, '2011-10-31 15:00:00', '2011-11-01 09:00:00'),
 (2, '2011-10-31 12:00:00', '2011-11-01 12:00:00'),
 (1, '2011-11-01 15:00:00', '2011-11-02 09:00:00');
Run Code Online (Sandbox Code Playgroud)

现在,这些范围不重叠,所以我们可以添加约束:

alter table product_specials add constraint overlapping_times exclude using gist (
  product_id with = ,
  cube(extract(epoch from start_time), extract(epoch from end_time)) with &&
);
Run Code Online (Sandbox Code Playgroud)

cube(n1, n2)创建一个从n1延伸到n2的一维"立方体".extract(epoch from t)将时间戳t转换为数字.如果你有两个立方体,"&&"运算符如果重叠则返回true.因此,这会为每行索引product_id和start_time/end_time"cube",并且每次插入/更新行时,都会通过查找与新行的值匹配的现有行来测试约束:使用"="测试product_id "运算符,以及带有"&&"运算符的start_time/end_time"cube".

如果您现在尝试插入冲突行,则会收到错误消息:

insert into product_specials(product_id, start_time, end_time)
  values(2, '2011-10-31 00:00:00', '2011-10-31 13:00:00');
ERROR:  conflicting key value violates exclusion constraint "overlapping_times"
DETAIL:  Key (product_id, cube(date_part('epoch'::text, start_time), date_part('epoch'::text, end_time)))=(2, (1320019200),(1320066000)) conflicts with existing key (product_id, cube(date_part('epoch'::text, start_time), date_part('epoch'::text, end_time)))=(2, (1320062400),(1320148800)).
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,错误消息详细信息的易读性还有待改进!(来自文章http://thoughts.j-davis.com/2010/09/25/exclusion-constraints-are-generalized-sql-unique/的"期间"类型,提到@a_horse_with_no_name可能产生更好的那些)但是,功能完好无损.

使用约束排除解决了一些与我没有解决的锁定有关的问题.严格地说,在触发器中的"IF EXISTS ..."查询之前,您应该执行SELECT 1 FROM rar.product_specials WHERE product_specials.product_id = NEW.product_id FOR SHARE以确保您正在测试的其他行都不会在被检查的约束和其事务提交之间发生变化.但是,当同时插入两个新特价时仍然存在竞争条件,其中没有什么可以锁定的 - 这是使用辅助表来排除离散值的动机,但是作为排除空间存在缩放问题变得更细粒度.

使用PostgreSQL 9.2,将有一个"范围"数据类型,这将消除使用立方体扩展或类似的需要.范围类型还允许正确指定边界是否在每端打开或关闭,而使用立方体边界始终在两端关闭(因此您需要做一些摆弄以避免日期范围重叠的错误).像往常一样,Depesz在此功能上有一篇很好的帖子:http://www.depesz.com/index.php/2011/11/07/waiting-for-9-2-range-data-types/

例如:

create table product_specials(product_special_id serial primary key,
    product_id int not null,
    applicable_dates tsrange not null);
insert into product_specials(product_id, applicable_dates)
 values(1, tsrange('2011-10-31 15:00:00', '2011-11-01 09:00:00')),
 (2, tsrange('2011-10-31 12:00:00', '2011-11-01 12:00:00')),
 (1, tsrange('2011-11-01 15:00:00', '2011-11-02 09:00:00'));
alter table product_specials add exclude using gist (
  product_id with =,
  applicable_dates with &&
);
Run Code Online (Sandbox Code Playgroud)

现在,如果您尝试插入冲突的行,您也会收到更易读的错误消息:

insert into product_specials(product_id, applicable_dates)    
  values(2, tsrange('2011-10-31 00:00:00', '2011-10-31 13:00:00'));
ERROR:  conflicting key value violates exclusion constraint "product_specials_product_id_applicable_dates_excl"
DETAIL:  Key (product_id, applicable_dates)=(2, ["2011-10-31 00:00:00","2011-10-31 13:00:00")) conflicts with existing key (product_id, applicable_dates)=(2, ["2011-10-31 12:00:00","2011-11-01 12:00:00")).
Run Code Online (Sandbox Code Playgroud)

请注意,您不必更改表的架构以使用此新类型,因为您可以索引函数调用的结果.因此,使用范围类型来强制执行约束的细节不必放入应用程序或触发器中.那是:

alter table product_specials add exclude using gist (
  product_id with =,
  tsrange(start_time, end_time) with &&
);
Run Code Online (Sandbox Code Playgroud)