【问题标题】:Why am I getting duplicate rows with these postgresql temporal db schema & queries?为什么我会使用这些 postgresql 时态数据库模式和查询得到重复的行?
【发布时间】:2015-10-17 07:44:29
【问题描述】:

我正在关注一些关于在 postgresql 中设置时态数据库的信息。首先是问题,然后是技术。

问题:当我在public.countries 表中执行一次干净插入时,为什么temporal.countries 表中的行数加倍?我只看到一个插入(在countries_ins 规则中)。这是功能还是错误?

好的,现在是架构:

DROP SCHEMA IF EXISTS temporal CASCADE;
DROP SCHEMA IF EXISTS history CASCADE;

----------------------------
-- Temporal countries schema
-- vjt@openssl.it
--
create schema temporal; -- schema containing all temporal tables
create schema history;  -- schema containing all history tables

-- Current countries data - nothing special
--
create table temporal.countries (
  id   serial primary key,
  name varchar UNIQUE
);

-- Countries historical data.
--
-- Inheritance is used to avoid duplicating the schema from the main table.
-- Please note that columns on the main table cannot be dropped, and other caveats
-- http://www.postgresql.org/docs/9.0/static/ddl-inherit.html#DDL-INHERIT-CAVEATS
--
create table history.countries (

  hid         serial primary key,
  valid_from  timestamp not null,
  valid_to    timestamp not null default '9999-12-31',
  recorded_at timestamp not null default now(),

  constraint from_before_to check (valid_from < valid_to),

  constraint overlapping_times exclude using gist (
    box(
      point( extract( epoch from valid_from), id ),
      point( extract( epoch from valid_to - interval '1 millisecond'), id )
    ) with &&
  )
) inherits ( temporal.countries );

create index timestamps on history.countries using btree ( valid_from, valid_to ) with ( fillfactor = 100 );
create index country_id on history.countries using btree ( id ) with ( fillfactor = 90 );

-- The countries view, what the Rails' application ORM will actually CRUD on, and
-- the core of the temporal updates.
--
-- SELECT - return only current data
--
create view public.countries as select * from only temporal.countries;

-- INSERT - insert data both in the current data table and in the history table
--
create rule countries_ins as on insert to public.countries do instead (
  insert into temporal.countries ( name )
    values ( new.name )
    returning temporal.countries.*;

  insert into history.countries ( id, name, valid_from )
    values ( currval('temporal.countries_id_seq'), new.name, now() )
);

-- UPDATE - set the last history entry validity to now, save the current data in
-- a new history entry and update the current table with the new data.
--
create rule countries_upd as on update to countries do instead (
  update history.countries
    set   valid_to = now()
    where id       = old.id and valid_to = '9999-12-31';

  insert into history.countries ( id, name, valid_from ) 
  values ( old.id, new.name, now() );

  update only temporal.countries
    set name = new.name
    where id = old.id
);

-- DELETE - save the current data in the history and eventually delete the data
-- from the current table.
--
create rule countries_del as on delete to countries do instead (
  update history.countries
    set   valid_to = now()
    where id       = old.id and valid_to = '9999-12-31';

  delete from only temporal.countries
  where temporal.countries.id = old.id
);

-- EOF

当我将它加载到一个空白数据库并执行一次插入时,会发生以下情况(查看第 39-40 行以了解(对我而言)令人惊讶的结果)。

 1 test=# \i /home/username/temporal.sql
 2 psql:/home/sirrobert/temporal.sql:1: NOTICE:  drop cascades to 3 other objects
 3 DETAIL:  drop cascades to table temporal.countries
 4 drop cascades to view countries
 5 drop cascades to table history.countries
 6 DROP SCHEMA
 7 DROP SCHEMA
 8 CREATE SCHEMA
 9 CREATE SCHEMA
10 CREATE TABLE
11 CREATE TABLE
12 CREATE INDEX
13 CREATE INDEX
14 CREATE VIEW
15 CREATE RULE
16 CREATE RULE
17 CREATE RULE
18 test=# SELECT * FROM public.countries;
19  id | name
20 ----+------
21 (0 rows)
22 
23 test=# SELECT * FROM temporal.countries;
24  id | name
25 ----+------
26 (0 rows)
27 
28 test=# INSERT INTO public.countries (name) VALUES ('USA');
29 INSERT 0 1
30 test=# SELECT * FROM public.countries;
31  id | name
32 ----+------
33   1 | USA
34 (1 row)
35 
36 test=# SELECT * FROM temporal.countries;
37  id | name
38 ----+------
39   1 | USA
40   1 | USA
41 (2 rows)

【问题讨论】:

  • 这里只是猜测...取出inherits ( temporal.countries ); 并尝试再次运行您的脚本,看看会发生什么!

标签: sql postgresql duplicates temporal-database


【解决方案1】:

您将数据插入到temporal.countrieshistory.countries 两个表中,后者继承自前者。那是错误的做法。您应该插入到history.countries,并带有附加属性。然后,当您查询 temporal.countries 时,您会看到一条记录,但没有有效的 from/to 信息。

更新记录后,您将获得重复记录。您目前的方法无法解决这个问题。但你并不真的需要继承开始。您可以有两个单独的表,然后创建一个视图public.countries,它从temporal.countries 返回当前有效的行:

create table temporal.countries (
  id   serial primary key,
  name varchar UNIQUE
);

create table history.countries (
  hid         serial primary key,
  country     integer not null references temporal.countries,
  name        varchar,
  valid_from  timestamp not null,
  valid_to    timestamp not null default '9999-12-31',
  recorded_at timestamp not null default now(),

  constraint from_before_to check (valid_from < valid_to),

  constraint overlapping_times exclude using gist (
    box(
      point( extract( epoch from valid_from), id ),
      point( extract( epoch from valid_to - interval '1 millisecond'), id )
    ) with &&
  )
)  inherits ( temporal.countries );

现在创建视图以仅返回当前有效的国家/地区:

create view public.countries as
  select c.*
  from temporal.countries c
  join history.countries h on h.country = c.id
  where localtimestamp between h.valid_from and h.valid_to;

还有你的三个规则:

-- INSERT - insert data in temporal.countries and metadata in history.countries
create rule countries_ins as on insert to public.countries do instead (
  insert into temporal.countries ( name )
    values ( new.name )
    returning temporal.countries.*;

  insert into history.countries ( country, name, valid_from )
    values ( currval('temporal.countries_id_seq'), new.name, now() )
);

-- UPDATE - set the last history entry validity to now, save the current data in
-- a new history entry and update the current table with the new data.
create rule countries_upd as on update to countries do instead (
  update history.countries
    set   valid_to = now()
    where id       = old.id and valid_to = '9999-12-31'; -- view shows only valid data

  insert into history.countries ( country, name, valid_from ) 
  values ( old.id, new.name, now() );

  update only temporal.countries
    set name = new.name
    where id = old.id
);

-- DELETE - save the current date in the history and eventually delete the data
-- from the current table.
create rule countries_del as on delete to countries do instead (
  update history.countries
    set   valid_to = LOCALTIMESTAMP
    where id       = old.id and valid_to = '9999-12-31';

  -- don't delete country data, view won't show it anyway
  delete from only temporal.countries
  where temporal.countries.id = old.id
); 

【讨论】:

  • 谢谢;这就是我需要的!
猜你喜欢
  • 2022-12-06
  • 1970-01-01
  • 2013-05-10
  • 2013-03-08
  • 2020-01-30
  • 1970-01-01
  • 2016-01-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多