我正在试图弄清楚什么是最佳数据库和表结构来存储类型(var)char的节点之间的关系。我上次使用MySQL作为一些简单的PHP网页的后端,从来没有超越过它。我希望一些经验丰富的用户可以给我他们的意见。
假设我有一堆名字:
我现在想存储他们的关系。我的想法是有两个表可能是这样的:
names (id, name) relationships (id_1, id_2)
0 Thomas 0 1
1 Jane 0 3
2 Felix 1 2
3 Marc 3 4
4 Anne ...
...
数据范围如下:
我的问题是:
欢迎任何指示。谢谢。
答案 0 :(得分:0)
首先,你需要考虑你的人际关系是否有一个方向"与他们相关联。例如,关系"是"的孩子。与其他相同的关系有相反的方向"是"的父母。另一方面,这种关系是"的兄弟姐妹。是无向的(或双向的,取决于一个人的观点)。
您描述的结构非常适合直接关系。
另一方面,双向关系通常最好由故意执行第二个要点中描述的复制来表示;虽然这会消耗更多的存储空间,但它大大简化了查询,例如"找到X"的所有兄弟姐妹 - 否则可能需要结合两个单独的查询:
SELECT id_2 FROM my_table WHERE id_1=X
UNION
SELECT id_1 FROM my_table WHERE id_2=X
由于结果列上没有索引,如果想要对结果执行更多操作(例如按id
排序或加入names
,则这些类型的查询可能会非常慢table-albeit在这种特殊情况下,可以在union之前执行连接,但这只会增加一个数据操作代码中的冗余和复杂性。
可以使用triggers来确保无论何时将关系写入(插入,更新或删除)到表示双向关系的表,都会在反向关系上自动执行相同的操作。
其次,您描述的表示被称为"邻接列表",这非常简单易懂。但它在处理数据层次结构的深度搜索方面并不出色,特别是在MySQL上(与其他一些RDBMS不同,它不支持递归函数)。从而找到了X"的所有后代。或者" Y"的所有祖先其实很难。其他数据模型,例如" nested sets"或" transitive closure"这些任务要好得多。
随着序言说到你的问题:
我记得正确使用PRIMARY_KEY非常重要。我依稀记得有可能将密钥分配给两列(即我的情况下为id_1,id_2);这有助于我想象的查询吗?
relationship
表有四种可能的主键:
(id_1)
(id_2)
(id_1, id_2)
(id_2, id_1)
根据定义,主键必须在表中唯一。实际上,它是识别记录的主要方法。但是如果需要,还可以定义进一步的UNIQUE
密钥,它们具有与主密钥相同的约束效果(差异相对较小且超出了本答案的范围):因此可以实际强制执行上述任意组合约束
上述约束将分别为:将每个名称限制在关系的一侧不超过一次;将每个名字限制在关系的另一边不超过一次;并且最后两个将名称的每个组合限制在相同关系内不超过一次(差异仅仅是存储索引的顺序)。如果表表示无向关系,那么显然第二和第四个约束在语义上分别等同于第一个和第三个约束。
一些例子:
如果您的表格代表" id_1
是id_2
"的遗传父亲然后id_1
可能有很多孩子。因此(id_1)
不能成为主键,因为它不能唯一地标识拥有多个孩子的父亲的记录。另一方面,id_2
只能有一个遗传父亲(胚胎进展除外),因此(id_2)
将唯一地识别记录,可以成为主键(也就是说,这种多对一关系也可以通过father_id
表中的names
列建模。其他两个(复合)钥匙将允许孩子有很多父亲,因此必须是不正确的。
如果您的表格代表" id_1
是id_2
"的父级。那么父母可以有很多孩子和孩子可以有多个父母(这被称为多对多关系)。因此前两个约束是不正确的,必须在后两个之间进行选择(如前所述,差异仅仅是索引存储的顺序 - 因此MySQL必须先定位第一列才能查找第二列)。顺便提一下,在这种情况下,可以考虑在relationship
表中添加一个附加列,指示关系表示哪个父项;如果一个孩子每个类型只能有一个父母,那么可以将主键定义为(child_id, parent_type)
。
如果您的表格代表" id_1
和id_2
已结婚"那么(id_1)
和(id_2)
都是"候选键",因为没有人可以与多个其他人结婚(至少在英国,一夫多妻制除外)。因此,可以将(id_1)
定义为主键,并将定义为UNIQUE
上的第二个(id_2)
键。如前所述,人们可能希望将记录放在表中,并且这些约束不会阻止它。
在MySQL中是否有办法阻止在插入过程中创建重复关系(例如0:4& 4:0)?
是的,人们可以通过触发器来做到这一点:但请注意上面关于双向关系所说的内容(通常需要这样的"重复")。强制执行此类约束的触发器示例可能是:
CREATE TRIGGER rel_ins BEFORE INSERT ON relationships FOR EACH ROW
IF EXISTS (
SELECT * FROM relationships WHERE id_1=NEW.id_2 AND id_2=NEW.id_1
) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Reverse relationship already exists';
END IF;;
在更新"之前,可能还需要类似的触发器。
可能需要这种约束的情况是表格表示"是#34;的父母,因为父母不能是他们孩子的孩子(但是,在这种情况下)值得注意的是,在这样的关系表中,实际上可能希望进一步防止所有循环 - 例如防止孩子成为其祖父母的父母。再次,"邻接列表"并不是执行这种约束的最佳模型 - 另一方面,嵌套集"完全可以完全阻止所有的圆形结构。
MySQL默认为InnoDB。这是您为我的方案推荐的数据库吗?
InnoDB的最大优势在于它完全符合ACID,因此提供了事务支持。如果您可以一次从多个位置写入数据库,这将特别有用。如果您只是将一堆静态数据一次性加载到数据库中以进行后续查询,那么它可能比MyISAM慢一点。