2015-01-16 75 views
3

假设我有一对一关系中的两个表。我们会打电话给第一个Bros,第二个Homies。兄弟可以有多个兄弟,但其中只有一个可以成为他的“主要人物”。数据库设计 - 一对多关系中的“特殊”条目

(看;例如硬不要对我大喊大叫。)

我将如何表示?我可以将一个'main_man'条目放入bros表中,但是这会重复我在homies表中的条目。

我也可以在homies表中添加一个条目,但是这不会限制其他家族成为主要人物。

有没有适当的方法来做到这一点?只是用错误的方式来处理它,而用应用程序来处理它会更容易吗?

回答

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每个bro_id一个main_man值。

这与微软文档支持的NULL意义“未知”的规范模式略有偏差。在我们的实现中,我们使用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, ...,如果要在客户端上对其进行排序,则返回SELECT中的main_man列。

您可能会考虑使用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列限制值。

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

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是领先的列。这就是我们在添加外键约束之前添加索引的原因。

2

下面是建模一个一对多的关系,一个漂亮的标准方式,其中子行的一个被认为是“特别”:

enter image description here

这种模式有以下重要属性:

  • 除了从孩子到家长的正常FK,我们还使用父母之间的“反向”FK。
  • 我们使用识别关系,使孩子弱实体(即孩子的密钥包含从父母迁移的密钥)。兄弟是由他的同性恋中的他的“号码”(BRO_NO)属于。不同亲友中的不同兄弟可能具有相同的BRO_NO。

总之,这两个属性确保:

  • 最多一个孩子是每个父母的每个特殊。
  • 家长不能选择其自己孩子集外的特殊行 - 请注意HOMIE表中的FK1不仅包含MAIN_MAN_ID而且包含HOMIE_ID。

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

  • 使其自动增量并与值中的“孔”一起生活。
  • 锁定父然后用MAX + 1
  • 只需使用MAX + 1无锁,但要准备应付键冲突并重新插入,如果并发事务试图插入相同的值。

如果还有其他表引用BRO,则可以考虑添加代理键(例如BRO_ID)。代理键的优缺点见here

顺便说一下,上述模型有一个变化:失去反向FK,只考虑具有最小的最小的 BRO_NO。如果您事先知道特殊兄弟,或者您不介意更新密钥(可能会级联变更)以将兄弟移动到顶端,这很好。


如果DBMS支持延迟的限制,FK父可以由非NULL,并确保正好一个孩子是特殊的(不只是零或一)。在存在圆形FK的情况下插入新数据时,篡改其中一个FK会打破鸡与鸡蛋的问题。不幸的是,MySQL不支持延迟约束。