sql - 如何设计具有修订历史的数据库?

标签 sql database database-design version-control content-management-system

我所在的团队正在为我们的公共(public)网站构建新的内容管理系统。我正在尝试找到构建修订控制 机制的最简单和最好的方法。对象模型非常基础。我们有一个抽象的 BaseArticle 类,其中包含版本独立/元数据的属性,例如 HeadingCreatedBy。许多类继承自此,例如 DocumentArticle,它具有属性 URL,它将成为文件的路径。 WebArticle 也继承自 BaseArticle 并包括 FurtherInfo 属性和一组 Tabs 对象,其中包括 Body 将保存要显示的 HTML(Tab 对象不派生自任何东西)。 NewsArticleJobArticle 继承自 WebArticle。我们还有其他派生类,但这些提供了足够的示例。

我们为版本控制提出了两种持久化方法。我将它们称为 Approach1Approach2。我使用 SQL Server 为每一个做了一个基本图表:Database Diagram of Approach 1 Database Diagram of Approach 2

  • 对于 Approach1,计划是通过数据库 Update 保留新版本的 Articles。将为更新设置触发器并将旧数据插入到 xxx_Versions 表中。我认为需要在每个表上配置触发器。这种方法确实有一个优点,即每篇文章的唯一 head 版本保存在主表中,旧版本被分割开来。这使得将文章的头版本从开发/暂存数据库复制到 Live 变得容易。
  • 使用方法2,计划将新版本的文章 插入到数据库中。文章的头部版本将通过 View 来识别。这似乎具有更少的表和更少的代码(例如,不是触发器)的优势。

请注意,对于这两种方法,计划都是为映射到相关对象的表调用 Upsert 存储过程(我们必须记住要处理添加新文章的情况)。此 upsert 存储过程将为它派生的类调用它,例如upsert_NewsArticle 会调用 upsert_WebArticle

我们使用的是 SQL Server 2005,尽管我认为这个问题与数据库风格无关。我对互联网进行了广泛的拖网搜索,并找到了对这两种方法的引用。但我还没有发现任何可以比较两者并显示其中一个更好的东西。我认为,对于世界上所有的数据库书籍,这种方法选择以前一定出现过。

我的问题是:哪些方法最好,为什么?

最佳答案

我的实现可能有点复杂。

首先,您只有一个表来处理所有事情,以便仅在一个点上保持模型设计和数据完整性。

这是基本思想,如果需要,您可以使用 created_byupdated_by 列扩展设计。

在MySQL上实现

下面的实现是针对 MySQL 的,但这个想法也可以在其他类型的 SQL 数据库上实现。

表格

DROP TABLE IF EXISTS `myTable`;

CREATE TABLE `myTable` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Primary Key',
  `version` int(11) NOT NULL DEFAULT 0 COMMENT 'Version',
  `title` varchar(32) NOT NULL COMMENT 'Title',
  `description` varchar(1024) DEFAULT NULL COMMENT 'Description',
  `deleted_at` datetime DEFAULT NULL COMMENT 'Record deleted at',
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Record created at'
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

ALTER TABLE `myTable`
  ADD PRIMARY KEY (`id`, `version`) USING BTREE,
  ADD KEY `i_title` (`title`);
  • 记录 IDidversion 定义。
  • 使用 deleted_at,此模型支持软删除 功能。

观看次数

获取当前版本

获取当前记录版本:

CREATE OR REPLACE VIEW vMyTableCurrentVersion AS
SELECT
    `id`
  , MAX(`version`) AS `version`
  , MIN(`created_at`) AS `created_at`
FROM `myTable`
GROUP BY `id`;

获取所有记录(包括删除的记录)

获取所有记录,包括软删除记录:

CREATE OR REPLACE VIEW vMyTableAll AS
SELECT
    T.id
  , T.version

  , T.title
  , T.description

  , T.deleted_at
  , _T.created_at
  , T.created_at AS `updated_at`
FROM
  `myTable` AS T
  INNER JOIN vMyTableCurrentVersion AS _T ON
    T.id = _T.id
    AND T.version = _T.version;

获取记录

获取记录,从结果中删除软删除记录。

CREATE OR REPLACE VIEW vMyTable AS
SELECT *
FROM `vMyTableAll`
WHERE `deleted_at` IS NULL;

触发器和验证

对于这个例子,我将实现一个唯一的title验证:

DROP PROCEDURE IF EXISTS myTable_uk_title;
DROP TRIGGER IF EXISTS myTable_insert_uk_title;
DROP TRIGGER IF EXISTS myTable_update_uk_title;

DELIMITER //

CREATE PROCEDURE myTable_uk_title(id INT, title VARCHAR(32)) BEGIN
  IF (
    SELECT COUNT(*)
    FROM vMyTable AS T
    WHERE
      T.id <> id
      AND T.title = title
  ) > 0 THEN
    SIGNAL SQLSTATE '45000'
    SET MESSAGE_TEXT = 'Duplicated "title"', MYSQL_ERRNO = 1000;
  END IF;
END //

CREATE TRIGGER myTable_insert_uk_title BEFORE INSERT ON myTable
FOR EACH ROW
BEGIN
  CALL myTable_uk_title(NEW.id, NEW.title);
END //

CREATE TRIGGER myTable_update_uk_title BEFORE UPDATE ON myTable
FOR EACH ROW
BEGIN
  CALL myTable_uk_title(NEW.id, NEW.title);
END //

DELIMITER ;

使用示例

选择

SELECT * FROM `vMyTable`;

SELECT * FROM vMyTable

选择已删除的记录

SELECT * FROM `vMyTableAll`;

SELECT * FROM vMyTableAll

插入/添加/新建

INSERT INTO myTable (`title`) VALUES ('Test 1');

更新/编辑

更新操作应该使用以下代码来完成,而不是 UPDATE ...:

INSERT INTO myTable (`id`, `version`, `title`, `description`)
SELECT
    `id`
  , `version` + 1 as `version` -- New version
  , `title`
  , 'New description' AS `description`
  FROM `vMyTable`
  WHERE id = 1;

软删除

软删除 操作是历史记录中的另一点:

INSERT INTO myTable (`id`, `version`, `title`, `description`, `deleted_at`)
SELECT
    `id`
  , `version` + 1 as `version` -- New version
  , `title`
  , `description`
  , NOW() AS `deleted_at`
  FROM `vMyTable`
  WHERE id = 1;

恢复软删除记录

INSERT INTO myTable (`id`, `version`, `title`, `description`, `deleted_at`)
SELECT
    `id`
  , `version` + 1 as `version` -- New version
  , `title`
  , `description`
  , null AS `deleted_at`
  FROM `vMyTableAll` -- Get with deleted
  WHERE id = 1;

删除记录&历史

删除相关的历史记录:

DELETE FROM `myTable` WHERE id = 1;

记录历史

SELECT *
FROM `myTable`
WHERE id = 1
ORDER BY `version` DESC;

Record history

缺点

  • Unique key constrain 是不可能的,但您可以创建一个触发器来处理它。
  • 更新许多记录(UPDATE ...)如果您想保存历史是不可能的。
  • 同时删除多条记录(DELETE ...)如果您想保存历史记录是不可能的。

引用文献

关于sql - 如何设计具有修订历史的数据库?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7465225/

相关文章:

sql - Oracle:类似于 sysdate 但只返回时间和日期

php - pdo->query() 返回每个结果中的两个?

sql - 这种订单情况应该如何设置数据库表

javascript - MongoDB 索引 : String vs Int

c - 在sqlite3中,我们可以将sqlexecute(.....callback function...)转换为sqlite_prepare,step,finalize

MySql数据库模式引用问题

php - SQL 更新、设置、案例 : What am I doing wrong?

mysql - #1054 - 'general' 中的未知列 'where clause'

mysql - 客户/公司地址/电话号码

ruby-on-rails - Rails 4如何使用带有其他text_field的复选框集合来建模表单