3

假设我有一个名为CategoryENUM列和名为SubcategoryENUM列。我有时只想SELECTCategory,这就是它们被拆分的原因。如何最好地保持表格中两列之间的完整性?

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"仅与"a""b"有效),这惹恼我,这个约束没有烤成表的设计。如果MySQL具有某种“对”类型(其中该类型的列可以在该值的前导子序列上索引),那么这不是一个问题。

如果我想维护类别和子类别之间的完整性,我一直坚持在触发器中写入长条件。或者我最好离开它?你会怎么做?

我想最关系导向的方法应该是存储一个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 

我可以做任何事情更简单?这种查找将潜在地花费我在调用代码时的复杂性和性能,因为INSERT s变成Bonza,不是吗?

回答

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; 
+0

啊,那是一个很好的主意。不需要'EventCategories.EventCategoryId'然后...?我仍然想坚持使用'ENUM',因为我不喜欢有限集合的自由文本,并且这些值实际上不是单个字符。 –

0

我就喜欢上这个问题,但是,这个信息我会定义一组有效的对只是一个ENUM列:

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

我认为这只会是一组有限的有用的价值观,当他们变得更大时,我会选择你的第二种选择而不是触发式的。 第一个原因是绝对的意见,我不喜欢触发太多,他们不喜欢我 第二个原因是一个很好的索引和适当大小的参考从一个表到另一个有一个真正的高性能

+0

海事组织通过将类别和子类别合并为单个值而走错了方向。对于单个字符,好吧,不是那么糟糕,但一般... –

1

我的想法是:“最佳”几乎总是意见为主,但还是有一些共同的东西,可以说

使用关系结构

一旦你有一个问题是,并非所有对是有效的 - 你有一个问题 - 那你必须存储这个信息。因此,您需要存储哪些对无效或存储哪些对有效。就关系数据库管理系统而言,您的附加表的样本完全有效。事实上,如果我们面临这样的问题,它几乎是在数据库设计层面上解决它的唯一方法。随着它:

  • 您正在存储有效对。正如我所说的:您必须将这些信息存储在某处,并且我们正在创建新表
  • 您通过FOREIGN KEY保持参照完整性。所以你的数据总是正确的,并指向有效的对

什么不好的事情可能发生,这会如何影响性能?

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

    SELECT 
        Bonza.id, 
        EventCategories.Subcategory, 
        EventCategories.Category 
    FROM 
        Bonza 
        LEFT JOIN EventCategories 
        ON Bonza.EventCategoryId=EventCategory.id 
    
  • 这个JOIN的性能将是很好的:你会做的是FK - 因此,根据定义,你只会得到。这是关于索引质量(即它的基数) - 但通常它会很快。
  • 一个有多复杂JOIN?这很简单 - 但它可能会增加复杂查询的开销。但是,在我看来:没关系。有没有什么复杂的在里面。
  • 您可以通过简单更改EventCategories数据来更改配对。那就是:你可以很容易的删除禁止对的限制,这会影响什么也没有。我认为这是这种结构的一大优点。但是,增加新的限制并不是那么简单 - 因为,是的,它需要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选项。它很简单,可读,可扩展。它符合关系数据库原则。

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

  • 允许对将永远不会改变(或将被改变非常罕见)
  • SELECT语句将被多少事,更多的时候多,那么INSERT/UPDATE语句。并且SELECT声明性能将在您的应用程序的性能方面处于最高优先级。
+0

非常好,详细的答案。非常感谢你。乔尔提出的中间解决方案似乎一次性解决所有这些问题。 –

+0

嗯,我确实注意到了这一点 - 但实际上,这会导致存储数据的重复(在我看来,应该总是避免这种重复。在你的情况下,它只是'CHAR' - 但在一般情况下,复合键 - VARCHAR'-s会导致很大的开销)。如果这对你没有问题 - 那就没关系。但在一般情况下,我强烈建议使用代理键并通过它强制执行完整性 –

+0

实际上,它不会是'CHAR'。这将是一串,例如20个字符。将会有20个类别和6个或7个子类别。插入率会相对较低。我没有被数据重复论证所支持:一个表保存数据,另一个保存排序模式,就像具有基本'ENUM'的表保存可能的值,然后实际的行“复制”那些值他们的数据。我没有看到任何问题。 –

相关问题