Реализация медленно изменяющегося типа 2 в PostgreSQL - PullRequest
2 голосов
/ 25 октября 2009

Я планирую внедрить Тип 2 SCD в PostgreSQL. Недостатком SCD является то, что вы не можете иметь внешние ключи, ссылающиеся на эти таблицы, если они используются, как это часто видели. Другими словами, я часто вижу, что ссылочная целостность рассматривается в коде приложения. Это кажется мне плохой практикой, поскольку можно сделать непосредственно в базе данных. Добавление нескольких триггеров может даже скрыть эту деталь реализации от кодировщиков приложений.

Я придумал следующие схемы. Это нормально?

Один-ко-многим

--
-- One-to-Many
--
BEGIN;

   CREATE TABLE document(
      id serial not null,
      revision integer not null default 1,
      title varchar(30),
      primary key (id, revision)
   );

   CREATE TABLE page(
      id serial not null,
      title varchar(30),
      document_id integer not null,
      document_revision integer not null,
      foreign key (document_id, document_revision) references document(id, revision)
   );


   -- Insert the first revision
   INSERT INTO document (title) VALUES ('my first document');
   INSERT INTO page (title, document_id, document_revision) VALUES ('my first page', 1, 1);

   -- DEBUG: display
   SELECT * FROM document d inner join page p ON ( d.id = p.document_id and d.revision = p.document_revision );

   -- "update" the document, by inserting a new revision
   INSERT INTO document (id, revision, title) VALUES (1, 2, 'my first document, edited');

   -- update the references
   UPDATE page SET document_revision = 2 WHERE document_id = 1;

   -- DEBUG: display
   SELECT * FROM document d inner join page p ON ( d.id = p.document_id and d.revision = p.document_revision );

ROLLBACK;

Многие-к-одному

--
-- Many-to-One
--
BEGIN;

   CREATE TABLE page(
      id serial not null primary key,
      title varchar(30)
   );

   CREATE TABLE document(
      id serial not null,
      revision integer not null default 1,
      title varchar(30),
      page_id integer references page(id),
      primary key (id, revision)
   );

   -- Insert initial revision
   INSERT INTO page (title) VALUES ('my first page');
   INSERT INTO document (title, page_id) VALUES ('my first document', 1);
   INSERT INTO document (title, page_id) VALUES ('my second document', 1);

   -- DEBUG: display
   SELECT * FROM page p inner join document d on (p.id = d.page_id);

   -- destroy the link "from" the old revision
   UPDATE document SET page_id = NULL WHERE id=1;

   -- Add a new revision, referencing the page
   INSERT INTO document ( id, revision, title, page_id ) VALUES ( 1, 2, 'My First Document, edited', 1 );

   -- DEBUG: display
   SELECT * FROM page p inner join document d on (p.id = d.page_id);
   SELECT * FROM document;

ROLLBACK;

Многие-ко-многим

--
-- Many-to-Many
--
BEGIN;
   CREATE TABLE page(
      id serial not null primary key,
      title varchar(30)
   );

   CREATE TABLE document(
      id serial not null,
      revision integer not null default 1,
      title varchar(30),
      primary key (id, revision)
   );

   CREATE TABLE page_contains_document(
      page_id integer not null references page(id),
      document_id integer not null,
      document_revision integer not null,
      foreign key (document_id, document_revision) references document( id, revision )
   );

   -- Insert initial revision
   INSERT INTO page (title) VALUES ('My First page');
   INSERT INTO document (title) VALUES ('My Fist Document');
   INSERT INTO page_contains_document (page_id, document_id, document_revision) VALUES (1, 1, 1);

   -- DEBUG: display
   SELECT p.title, d.title, d.revision FROM page p INNER JOIN page_contains_document pcd ON (p.id = pcd.page_id) INNER JOIN document d ON (d.id = pcd.document_id and d.revision = pcd.document_revision);

   -- Add a new document revision
   INSERT INTO document (id, revision, title) VALUES (1, 2, 'My Fist Document, edited');

   -- update the reference
   UPDATE page_contains_document SET document_revision=2 WHERE document_id=1;

   -- DEBUG: display
   SELECT p.title, d.title, d.revision FROM page p INNER JOIN page_contains_document pcd ON (p.id = pcd.page_id) INNER JOIN document d ON (d.id = pcd.document_id and d.revision = pcd.document_revision);

ROLLBACK;

Ответы [ 2 ]

3 голосов
/ 09 сентября 2014

Я знаю, что уже немного поздно для ответа на этот вопрос, но я думаю, что это может быть полезно для людей, которые ищут то же, что и вы.

Мы написали модуль, реализующий SCD-тип 2, который используется с Django. Он был протестирован с использованием PostgreSQL, поэтому он должен соответствовать вашим требованиям. Он также охватывает отношения OneToMany и ManyToMany.

Для получения более подробной информации поищите CleanerVersion на GitHub или перейдите на https://github.com/swisscom/cleanerversion напрямую.

3 голосов
/ 03 ноября 2009

OK. Я думаю, что нам нужно выяснить некоторые важные недоразумения о том, почему мы делаем SCD типа 2.

Он должен содержать все данные в одной таблице в скобках по датам (не номерам ревизий!).

Итак, вы могли бы иметь:

   id   ,  name    ,  valid_from, valid_to
  1111  , MyBook   , '2009-03-01', '9999-12-31'

After an update:
  1111  , Mybook   , '2009-03-01', '2009-06-20'
  1111  , Mybook   , '2009-06-21', '9999-12-31'

Аналогичная структура с действительными датами и действительными датами должна существовать в базе данных "страниц".

Суть в том, что теперь вы можете получить либо последнюю версию с:

select * from books where valid_to = '9999-12-31'

Или получите версию, которая действовала на первое апреля

select * from books where valid_to >= '2009-04-01' and valid_from <= '2009-04-01'

Также в вашей структуре страницы вам нужно хранить только обновленные страницы. Вам не нужна новая копия всех страниц для каждой ревизии.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...