简体   繁体   English

如何在多个子实体和列表属性之间建立关系?

[英]How to build a relationship between multiple sub-entities and a list attribute?

The model of the (PHP & MySQL based) application I'm currently working on contains a inheritance similar to the one described here .我目前正在处理的(基于 PHP 和 MySQL 的)应用程序的模型包含类似于此处描述的继承。 Simplified for the purpose of this question the classes structure can look like this:为了这个问题的目的而简化的类结构可以是这样的:

在此处输入图片说明

To map this to the database I'm using the Class Table Inheritance design pattern.为了将其映射到数据库,我使用了 类表继承设计模式。 Here is the physical data model:这是物理数据模型:

在此处输入图片说明

The most specific attributes are actually specific for every single subclass.最具体的属性实际上是针对每个子类的。 But there are some attributes, that are needed in several classes (but also not in all of them -- otherwise they could be managed in the Foo class/table).但是有一些属性在几个类中是需要的(但也不是在所有类中都需要——否则它们可以在Foo类/表中进行管理)。 When it's a simple attribute, it causes some code duplication, but isn't a big problem.当它是一个简单的属性时,它会导致一些代码重复,但不是一个大问题。 But there are also some cases with complex attributes.但也有一些具有复杂属性的情况。

Eg: FooTypeBaz and FooTypeBuz should contain a list of Whatever elements.如: FooTypeBazFooTypeBuz应该包含列表Whatever元素。

Normally I would implement this 1:n relationship with a table whatever containing FOREIGN KEY .通常我会实现这个1:n与表的关系whatever含有FOREIGN KEY But in this case I would need multiple FOREIGN KEY columns whatever (for foo_type_baz , foo_type_buz , and maybe some tables more).但是,在这种情况下,我需要多FOREIGN KEYwhatever (对于foo_type_bazfoo_type_buz ,也许有些表的更多)。 It's dirty.这个不干净。

Another solution: Something like a "facade" table for the table whatever :另一个解决方案:类似于桌子的“立面”桌子, whatever

在此处输入图片说明

Looks better (for me), but I'm still not happy with this model.看起来更好(对我来说),但我仍然对这个模型不满意。

How to build a relationship between multiple sub-entities and a collection/list attribute?如何在多个子实体和集合/列表属性之间建立关系? Is there an elegant solution for this problem?这个问题有优雅的解决方案吗? Maybe a best practice / design pattern?也许是最佳实践/设计模式?

Recording the relationship is easy enough - you could make a table foo_whatever (foo_id PK, whatever_set_id FK) and insert rows only for appropriate foo ids.记录关系很容易——你可以制作一个表foo_whatever (foo_id PK, whatever_set_id FK)并只为适当的 foo id 插入行。 However, that schema doesn't enforce any constraint on the subtypes you can associate with whatever sets, but neither does your existing schema enforce that subtypes are mutually exclusive.但是,该架构不会对您可以与任何集合关联的子类型施加任何约束,但您现有的架构也不会强制子类型互斥。 It's possible to enforce both with the same technique.可以使用相同的技术强制执行两者。

Consider including a type indicator on all the foo_* tables, eg using an enum('bar', 'baz', 'buz') .考虑在所有foo_*表上包含一个类型指示符,例如使用enum('bar', 'baz', 'buz') This provides subtype information in foo (which can be more convenient than joining 3 tables to find a match) and allows foreign key constraints and check constraints to enforce exclusive subtypes and restrict the types that can be recorded in foo_whatever .这在foo提供了子类型信息(这比连接 3 个表来查找匹配更方便)并允许外键约束和检查约束来强制执行独占子类型并限制可以在foo_whatever记录的foo_whatever Yes, it involves a bit of redundant information, but it's small and there's no risk of update anomalies.是的,它涉及一些冗余信息,但它很小,并且不存在更新异常的风险。

Using composite foreign key constraints that involve a type indicator, together with check constraints that limit the value of the type indicator for each subtype table, should do the trick.使用涉及类型指示符的复合外键约束,以及限制每个子类型表的类型指示符值的检查约束,应该可以解决问题。 Here's the schema I suggest:这是我建议的架构:

CREATE TABLE `foo` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `type` enum('bar','baz','buz') NOT NULL,
  PRIMARY KEY (`id`),
  KEY `foo_id` (`id`,`type`)
);

CREATE TABLE `foo_type_bar` (
  `foo_id` int(11) NOT NULL,
  `foo_type` enum('bar','baz','buz') NOT NULL CHECK (foo_type = 'bar'),
  PRIMARY KEY (`foo_id`),
  KEY `foo_bar_fk` (`foo_id`,`foo_type`),
  CONSTRAINT `foo_bar_fk` FOREIGN KEY (`foo_id`, `foo_type`)
  REFERENCES `foo` (`id`, `type`) ON DELETE CASCADE ON UPDATE CASCADE
);

CREATE TABLE `foo_type_baz` (
  `foo_id` int(11) NOT NULL,
  `foo_type` enum('bar','baz','buz') NOT NULL CHECK (foo_type = 'baz'),
  PRIMARY KEY (`foo_id`),
  KEY `foo_baz_fk` (`foo_id`,`foo_type`),
  CONSTRAINT `foo_baz_fk` FOREIGN KEY (`foo_id`, `foo_type`)
  REFERENCES `foo` (`id`, `type`) ON DELETE CASCADE ON UPDATE CASCADE
);

CREATE TABLE `foo_type_buz` (
  `foo_id` int(11) NOT NULL,
  `foo_type` enum('bar','baz','buz') NOT NULL CHECK (foo_type = 'buz'),
  PRIMARY KEY (`foo_id`),
  KEY `foo_buz_fk` (`foo_id`,`foo_type`),
  CONSTRAINT `foo_buz_fk` FOREIGN KEY (`foo_id`, `foo_type`)
  REFERENCES `foo` (`id`, `type`) ON DELETE CASCADE ON UPDATE CASCADE
);

CREATE TABLE `foo_whatever` (
  `foo_id` int(11) NOT NULL,
  `foo_type` enum('bar','baz','buz') NOT NULL CHECK (foo_type IN ('baz', 'buz')),
  `whatever_set_id` int(11) NOT NULL,
  PRIMARY KEY (`foo_id`),
  KEY `whatever_foo_fk` (`foo_id`,`foo_type`),
  KEY `whatever_set_fk` (`whatever_set_id`),
  CONSTRAINT `whatever_foo_fk` FOREIGN KEY (`foo_id`, `foo_type`)
  REFERENCES `foo` (`id`, `type`) ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT `whatever_set_fk` FOREIGN KEY (`whatever_set_id`)
  REFERENCES `whatever_set` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
);

However, since MySQL ignores check constraints, you would need to use triggers to achieve the same:但是,由于 MySQL 忽略检查约束,因此您需要使用触发器来实现相同的目的:

DELIMITER ;;

CREATE TRIGGER foo_bar_insert_type_check
    BEFORE INSERT ON foo_type_bar
    FOR EACH ROW
BEGIN 
    IF NEW.foo_type != 'bar' THEN 
        SIGNAL SQLSTATE '12345'
            SET MESSAGE_TEXT = 'Invalid foo_type in foo_type_bar';
    END IF; 
END;;

CREATE TRIGGER foo_bar_update_type_check
    BEFORE UPDATE ON foo_type_bar
    FOR EACH ROW
BEGIN 
    IF NEW.foo_type != 'bar' THEN 
        SIGNAL SQLSTATE '12345'
            SET MESSAGE_TEXT = 'Invalid foo_type in foo_type_bar';
    END IF; 
END;;

CREATE TRIGGER foo_baz_insert_type_check
    BEFORE INSERT ON foo_type_baz
    FOR EACH ROW
BEGIN 
    IF NEW.foo_type != 'baz' THEN 
        SIGNAL SQLSTATE '12345'
            SET MESSAGE_TEXT = 'Invalid foo_type in foo_type_baz';
    END IF; 
END;;

CREATE TRIGGER foo_baz_update_type_check
    BEFORE UPDATE ON foo_type_baz
    FOR EACH ROW
BEGIN 
    IF NEW.foo_type != 'baz' THEN 
        SIGNAL SQLSTATE '12345'
            SET MESSAGE_TEXT = 'Invalid foo_type in foo_type_baz';
    END IF; 
END;;

CREATE TRIGGER foo_buz_insert_type_check
    BEFORE INSERT ON foo_type_buz
    FOR EACH ROW
BEGIN 
    IF NEW.foo_type != 'buz' THEN 
        SIGNAL SQLSTATE '12345'
            SET MESSAGE_TEXT = 'Invalid foo_type in foo_type_buz';
    END IF; 
END;;

CREATE TRIGGER foo_buz_update_type_check
    BEFORE UPDATE ON foo_type_buz
    FOR EACH ROW
BEGIN 
    IF NEW.foo_type != 'buz' THEN 
        SIGNAL SQLSTATE '12345'
            SET MESSAGE_TEXT = 'Invalid foo_type in foo_type_buz';
    END IF; 
END;;

CREATE TRIGGER foo_whatever_insert_type_check
    BEFORE INSERT ON foo_whatever
    FOR EACH ROW
BEGIN 
    IF NEW.foo_type NOT IN ('baz', 'buz') THEN 
        SIGNAL SQLSTATE '12345'
            SET MESSAGE_TEXT = 'Invalid foo_type in foo_whatever';
    END IF; 
END;;

CREATE TRIGGER foo_whatever_update_type_check
    BEFORE UPDATE ON foo_whatever
    FOR EACH ROW
BEGIN 
    IF NEW.foo_type NOT IN ('baz', 'buz') THEN 
        SIGNAL SQLSTATE '12345'
            SET MESSAGE_TEXT = 'Invalid foo_type in foo_whatever';
    END IF; 
END;;

DELIMITER ;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM