如何才能最好地保持表中两列之间的完整性?

时间:2014-03-28 11:35:15

标签: mysql database-design referential-integrity data-integrity

假设我有一个名为ENUM的{​​{1}}列和一个名为Category的{​​{1}}列。我有时会单独ENUM Subcategory,这就是为什么它们会分开。

SELECT

但并非所有子类别对所有类别都有效(例如,Category仅对CREATE TABLE `Bonza` ( `EventId` INT UNSIGNED NOT NULL AUTO_INCREMENT, `Category` ENUM("a", "b", "c") NOT NULL, `Subcategory` ENUM("x", "y", "z") NOT NULL, PRIMARY KEY(`EventId`) ) ENGINE=InnoDB; "z"有效),并且让我觉得这个约束没有被烘焙进入表的设计。如果MySQL有某种"对" type(该类型的列可以在值的前导子序列上转换)然后这不会是这样的问题。

如果我想在类别和子类别之间保持完整性,我会坚持在触发器中编写长条件。或者我离开它会更好吗?你会做什么?

我认为最接近面向关系的方法是存储"a",并将其映射到包含所有有效事件类型对的表,并在每次我想查找其含义时加入该表。一个事件类别。

"b"

我能做更简单的事吗?这次查询可能会让我在调用代码时花费复杂性和性能,EventCategoryId转换为CREATE TABLE `Bonza` ( `EventId` INT UNSIGNED NOT NULL AUTO_INCREMENT, `EventCategoryId` INT UNSIGNED NOT NULL, PRIMARY KEY(`EventId`), FOREIGN KEY `EventCategoryId` REFEFRENCES(`EventCategories`.`EventCategoryId`) ON DELETE RESTRICT ON UPDATE CASCADE ) ENGINE=InnoDB; CREATE TABLE `EventCategories` ( `EventCategoryId` INT UNSIGNED NOT NULL, `Category` ENUM("a", "b", "c") NOT NULL, `Subcategory` ENUM("x", "y", "z") NOT NULL, PRIMARY KEY(`EventCategoryId`) ) ENGINE=InnoDB; -- Now populate this table with valid category/subcategory pairs at installation ,不是吗?

3 个答案:

答案 0 :(得分:1)

假设您的类别和子类别经常不会改变,并且假设您愿意接受大更新,那么您可以执行以下操作:

使用EventCategories表来控制类别和子类别之间的层次约束。该表的主键应该是包含CategorySubcategory复合键。在Bonza表中引用此表。 Bonza中的外键恰好包含您要过滤的两个列,因此您无需加入即可获取您之后的内容。也无法分配无效的组合。

CREATE TABLE `Bonza` (
   `EventId`         UNSIGNED INT NOT NULL AUTO_INCREMENT,
   `Category`        CHAR(1) NOT NULL,
   `Subcategory`     CHAR(1) NOT NULL,

   PRIMARY KEY(`EventId`),
   FOREIGN KEY `Category`, `Subcategory` 
   REFEFRENCES(`EventCategories`.`Category`, `EventCategories`.`Subcategory`)
     ON DELETE RESTRICT ON UPDATE CASCADE
) ENGINE=InnoDB;

CREATE TABLE `EventCategories` (
   `EventCategoryId` UNSIGNED INT NOT NULL,
   `Category`    CHAR(1) NOT NULL,
   `Subcategory` CHAR(1) NOT NULL,

   PRIMARY KEY(`Category`, `Subcategory`)
) ENGINE=InnoDB;

答案 1 :(得分:1)

我的想法是:“最好”几乎总是以意见为基础,但仍有一些常见的事情可以说

使用关系结构

如果您遇到的问题并非所有配对都有效 - 您就会遇到问题 - 您必须存储此信息。因此,您需要存储哪些对无效或存储哪些对是有效的。根据关系DBMS,包含附加表的示例完全有效。事实上,如果我们将面临这样的问题,它几乎是在数据库设计层面上解决它的唯一方法。有了它:

  • 您正在存储有效的对。就像我说的那样:你必须在某处存储这些信息,我们在这里 - 创建新表
  • 您通过FOREIGN KEY维护参照完整性。因此,您的数据将始终正确并指向有效的对

可能会发生什么不好的事情,这会对性能产生怎样的影响?

  • 要重建完整行,您需要使用简单的JOIN

    SELECT 
      Bonza.id, 
      EventCategories.Subcategory,
      EventCategories.Category
    FROM
      Bonza
        LEFT JOIN EventCategories
        ON Bonza.EventCategoryId=EventCategory.id
    
  • 这个JOIN的效果会很好:你会做FK - 因此,根据定义,你只会获得INDEX SCAN。这是关于索引质量(即它的基数) - 但总的来说它会很快。
  • JOIN有多复杂?它操作简单 - 但它可能会给复杂查询增加一些开销。但是,在我看来:没关系。 中没有任何复杂的
  • 您可以通过简单更改EventCategories数据来更改配对。也就是说:您可以轻松删除对禁止对的限制,这将影响 nothing 。我认为这是这种结构的一大好处。但是,添加新限制并非如此简单 - 因为,是的,它需要DELETE操作。您已为您的FK选择ON DELETE RESTRICT操作 - 这意味着您必须在添加新限制之前处理所有冲突记录。当然,这取决于你的应用程序的逻辑 - 但是以另一种方式考虑它:如果你要添加新的限制,那么就不应该删除所有冲突的记录(因为逻辑说:是的,它们应该)?如果是,请将您的FK更改为ON DELETE CASCADE

所以:拥有额外的表格是简单灵活并且实际上简单方式来解决您的问题。

存储在一个表中

您已经提到,您可以使用触发器来解决您的问题。这实际上是适用的,所以我会表明 - 这有它的弱点(好吧,还有一些好处)。让我们说,我们将创建触发器:

DELIMITER //
CREATE TRIGGER catCheck BEFORE INSERT ON Bonza
    FOR EACH ROW
    BEGIN
        IF NEW.Subcategory = "z" && NEW.Category = "c" THEN
            SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Invalid category pair';
        END IF;
    END;//
DELIMITER ;

显然,我们仍然 来存储有关如何验证我们的对的信息,但在这种情况下,我们存储无效的组合。一旦我们得到无效数据,我们将捕获这个内部触发器并中止我们的插入,返回正确的用户定义的errno(45000)和一些解释文本。现在,复杂性和性能如何?

  • 这种方式允许您按原样存储一个表中的数据。这是一个好处:你将摆脱JOIN - 完整性由另一个工具维护。您可能会忘记存储对并处理它们,将此逻辑隐藏在触发器中
  • 因此,您将在SELECT语句中获胜:您的数据始终包含有效对。并且不需要JOIN
  • 但是,是的,你会在INSERT / UPDATE语句中松散:它们将调用触发器并在其中 - 一些检查条件。它可能很复杂(许多IF部分),MySQL将逐一检查它们。制定一个单一的条件并没有多大帮助 - 因为在最坏的情况下,MySQL会检查它直到它结束。
  • 此方法的可伸缩性很差。每次你需要添加/删除对限制 - 你将不得不重新定义触发器。更糟糕的是,与JOIN案例不同,您将无法执行任何级联操作。相反,你必须进行手动处理。

选择什么?

对于一般情况,如果您不确定 - 您的申请条件是什么,我建议您使用JOIN选项。它简单,易读,可扩展。它符合关系DB原则。

对于某些特殊情况,您可能想要选择第二个选项。那些条件是:

  • 永远不会更改允许的对(或将非常罕见地更改)
  • SELECT语句将更频繁地完成,然后INSERT / UPDATE语句。此外,SELECT语句性能在应用程序性能方面也是最高优先级。

答案 2 :(得分:0)

我喜欢这个问题但是,有了这些信息,我会为一个枚举列定义一组有效的对:

CategorySubcategory ENUM("ax", "ay", "az", "bx", "by", "bz", "cx", "cy")

我认为这只会对一组有限的值有用,当他们个人变大时我会选择你的第二个选项而不是触发的选项。 第一个原因绝对是一种观点,我不太喜欢触发器,而且他们不喜欢我 第二个原因是从一个表到另一个表的良好索引和适当大小的引用具有非常高的性能