PostgreSQL

时间:2015-07-17 20:19:05

标签: postgresql

我有一个字段,每个日期需要一个有效的行。从文档中我看到如何通过EXCLUDE使用gist来防止重叠。如何确保数据始终紧凑,即没有孔?

CREATE TABLE observations (
    vintage daterange,
    value numeric,
    EXCLUDE USING gist (vintage WITH &&)
);

如何确保每个日期都有值?在实际应用中有第一个观察,最新的观察结果为null,作为日期范围中的第二个字段。

1 个答案:

答案 0 :(得分:2)

假设您按时间顺序输入数据,最好的选择可能是INSERT触发器。这是因为您不仅需要插入新数据,还需要在当前条目之前关闭最近条目的日期范围。

日期范围

的解决方案
CREATE FUNCTION trf_bi_observations() RETURNS trigger AS $$
DECLARE
  recent_date date;
BEGIN
  -- Find the most recently entered date
  SELECT max(lower(vintage)) INTO recent_date
  FROM observations;

  -- Make sure the new date range makes sense: it has to be after the most recently
  -- entered date range and it has to have an open upper range.
  IF lower(NEW.vintage) <= recent_date OR NOT upper_inf(NEW.vintage) THEN
    RAISE NOTICE 'New vintage must be more recent than latest vintage and have an open upper bound';
    RETURN NULL; -- Fail the insert
  END IF;

  -- Update the most recent record to close the upper bound of vintage
  UPDATE observations
  SET vintage = daterange(recent_date, lower(NEW.vintage)) -- upper bound exclusive
  WHERE lower(vintage) = recent_date;

  -- All done, make the INSERT happen
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER tr_bi_observations
BEFORE INSERT ON observations
FOR EACH ROW EXECUTE PROCEDURE trf_bi_observations();

请注意,使用此INSERT触发器,您不再需要EXCLUDE约束,因为触发器功能的逻辑不允许重叠。但是,您必须考虑更新和删除。如果您想禁止此表上的删除,您可以简单地撤销该权限,或者使用BEFORE DELETE触发器除RETURN NULL之外什么也不做(这会使DELETE操作失败)。您必须允许更新(或上面的插入触发器将失败),但您可以将其限制为value列中的更改并关闭最近行的上限:

-- OLD upper bound must be open, else disallow change to vintage column
IF upper_inf(OLD.vintage) THEN
  -- Only allow changes in upper bound
  NEW.vintage = daterange(lower(OLD.vintage), upper(NEW.vintage));
ELSE
  NEW.vintage = OLD.vintage -- silently ignore changes
END IF;

没有日期范围的解决方案

根据您的要求 - 没有重叠,没有差距 - 您只需使用date处理&#34;年份&#34;就可以大大简化您的数据模型。列,而不是daterange。范围始终可以从当前行的date到以下角色的date构建。这隐含地满足了这两个要求,并且触发函数变得更加简单:

CREATE FUNCTION trf_bi_observations2() RETURNS trigger AS $$
BEGIN
  -- Make sure the new date makes sense: it has to be after the most recently
  -- entered date.
  PERFORM * FROM observations WHERE vintage >= NEW.vintage;
  IF FOUND THEN
    RAISE NOTICE 'New vintage must be more recent than latest vintage';
    RETURN NULL; -- Fail the insert
  END IF;

  -- All done, make the INSERT happen
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER tr_bi_observations2
BEFORE INSERT ON observations
FOR EACH ROW EXECUTE PROCEDURE trf_bi_observations2();

现在,您不必使用打开的上限更新最近的行,因此您可以禁止对列&#34; vintage&#34;进行任何更新。您还可以决定允许插入和更新任何日期,以便各个行的日期范围随此类事件而变化。由你决定。在任何一种情况下,使用简单的date值都比使用daterange更容易,更快。

您仍然可以使用&#34; vintage&#34;来检索数据。以daterange格式使用正确的SELECT语句:

SELECT daterange(vintage, lead(vintage) OVER (ORDER BY vintage ASC)) AS vintage, value
FROM observations;

lead()窗口功能会为您提供&#34;年份的date&#34;窗口框架中下一行的列,由于订购而成为下一个日期。