数据库设计 - 一对多关系中的“特殊”条目

时间:2015-01-16 22:41:33

标签: mysql database-design

假设我有两对一对多的关系表。我们将第一个称为Bros,第二个称为Homies。兄弟可以有多个兄弟,但其中只有一个可以成为他的“主要人物”。

(看,例子很难。不要对我大喊大叫。)

我怎么代表那个?我可以将一个'main_man'条目放入bros表中,但这会重复我在homies表中的条目。

我也可以在同性恋表中加入一个条目,但这并不会限制其他同性恋者成为主要人物。

有没有正确的方法呢?是否更容易以错误的方式执行并使用应用程序来处理它?<​​/ p>

2 个答案:

答案 0 :(得分:3)

有几种方法可以对此进行建模。

首先,main_man也必须是homie吗?如果是这样,我会在homies表上添加一个标志。 MySQL数据类型有点不完美,但我使用的是布尔值,我们总是映射到TINYINT(1) DEFAULT NULL COMMENT 'boolean'数据类型。

下一步是将此值限制为1NULL,不允许任何其他值。不幸的是,MySQL不强制执行CHECK约束,因此如果我们希望数据库强制执行此规则,我们需要实现BEFORE INSERT/BEFORE UPDATE触发器来强制执行它。

最后,我们添加UNIQUE约束

... ON homies (bro_id, main_man)

有了这个,MySQL将只允许每个1的main_man值为bro_id的单行。

这与NULL的规范模式略有不同,意思是“未知”,我认为这是由Microsoft文档所支持的。在我们的实现中,我们使用NULL值来表示“不,不是main_man”。允许NULL值的主要优点是SQL(通常)和MySQL特别不认为NULL值是另一个NULL值的“重复”。 UNIQUE约束允许多行具有NULL值。 (我认为有一些SQL_MODE设置会改变这种行为,但我们不会去那里。)

仅获取homies ...

main_man
WHERE main_man = 1

或者更简洁,因为我们没有使用零来表示TRUE,并且如果我们确定不存在其他非零值......

WHERE main_man

另一个逻辑非常简单,检查main_man IS NULLMAIN_MAN <=> NULLORDER BY main_man, ...,如果您愿意,请返回main_man中的SELECT列在客户端上解决它。

您可以考虑使用MySQL ENUM数据类型,只要我们允许NULL值,并且我们验证MySQL将允许并强制ENUM列上的UNIQUE约束。 (我以前从未尝试过)。

这只是几种方法中的一种,但它是我过去成功使用过的方法。

-

<强>演示

CREATE TABLE bro 
( id INT UNSIGNED NOT NULL PRIMARY KEY
) ENGINE=INNODB;

CREATE TABLE homie
( id         INT UNSIGNED NOT NULL PRIMARY KEY 
, bro_id     INT UNSIGNED NOT NULL COMMENT 'FK ref bros.id'
, main_man   TINYINT(1) DEFAULT NULL COMMENT 'boolean, 1=is the main man'
, homie_name VARCHAR(10)
) ENGINE=INNODB;

ALTER TABLE homie
  ADD UNIQUE INDEX homie_UX1 (bro_id, main_man);

ALTER TABLE homie
  ADD CONSTRAINT FK_homie_bro FOREIGN KEY (bro_id) REFERENCES bro (id);

TODO:添加BEFORE INSERT / BEFORE UPDATE触发器以限制main_man列的值。

通过添加一些行来对此进行测试,并检查给定main_man我们不能有多个bro_id

INSERT INTO bro (id) VALUES 
(2),(3);

INSERT INTO homie (id, bro_id, main_man, homie_name) VALUES
  ( 11, 2, NULL, 'mr.slate' )
, ( 12, 2, 1, 'barney')
;

-- attempt to insert another main_man        
INSERT INTO homie (id, bro_id, main_man, homie_name) VALUES
  ( 13, 2, 1, 'wilma' )
;

-- Error Code: 1062
-- Duplicate entry '2-1' for key 'homie_UX1'

UPDATE homie SET main_man = 1 WHERE id = 11 ; 

-- Error Code: 1062
-- Duplicate entry '2-1' for key 'homie_UX1'

注意:我忽略了,作为一个小小的奖励,homie_UX1索引(为强制执行UNIQUE约束而创建)也用于支持外键,因为bro_id是前导列。这就是我们在添加外键约束之前添加索引的原因。

答案 1 :(得分:2)

以下是建立一对多关系的非常标准的方法,其中一个子行被认为是“特殊的”:

enter image description here

此模型具有以下重要属性:

  • 除了从孩子到父母的正常FK之外,我们还使用从父母到孩子的“反向”FK。
  • 我们使用识别关系,使孩子成为弱实体(即孩子的密钥包含从父母迁移的密钥)。兄弟由他所属的通过他的“数字”(BRO_NO)在该特定的同性恋中识别。不同同一性中的不同兄弟可能具有相同的BRO_NO。

总之,这两个属性确保:

  • 每个父母最多只有一个孩子。 1
  • 父母不能在其自己的子集之外选择特殊行 - 请注意HOMIE表中的FK1不仅仅是MAIN_MAN_ID,还包括HOMIE_ID。

但是,在并发环境中,您必须小心如何生成BRO_NO。一些可能性:

  • 使其自动递增并在值中使用“孔”。
  • 锁定父级,然后使用MAX + 1。
  • 只需使用MAX + 1而不进行锁定,但要准备好处理密钥冲突,并在并发事务尝试插入相同值时重试INSERT。

如果有其他表引用BRO,您可以考虑添加代理键(例如BRO_ID)。有关代理键的利弊,请参阅here

顺便说一句,上面的模型有一个变化:失去反向FK,只考虑哪个兄弟将最小 BRO_NO视为特殊。如果您事先知道特殊兄弟,或者如果您不介意更新密钥(并且可能级联更改)以将兄弟移动到顶部,那么这很好。


1 如果DBMS支持延迟约束,则父级中的FK可以为非NULL,并确保完全一个子级是特殊的(而不仅仅是零或一)。当存在圆形FK时插入新数据时,其中一个FK会破坏鸡和蛋的问题。不幸的是,MySQL不支持延迟约束。