在单个SQL表中表示记录之间的多对多关系的最佳方法是什么?

时间:2009-01-23 19:19:59

标签: sql database many-to-many entity-relationship relational

我有一个像这样的SQL表:

更新:我正在更改示例表,因为原始数据(州,城市,学校)的现有分层性质使得项目之间需要简单关系的事实蒙上阴影。

entities
id      name               
1       Apple     
2       Orange            
3       Banana             
4       Carrot                
5       Mushroom        

我想定义这些实体之间的双向关系,以便查看一个实体的用户可以看到所有相关实体的列表。

关系由最终用户定义。

在数据库中表示这些关系并随后查询和更新它们的最佳方法是什么?

我看到的一种方式......

我的直觉说的是这样的关系表:

entity_entity
entity_id_a       entity_id_b
1                 2
5                 1
4                 1
5                 4
1                 3

既然如此,如果提供的entity_id为4,那么如何获得所有相关记录,即1和5?

同样,entity_id = 1的查询应返回2,3,4和5。

感谢您的时间,让我知道我是否可以澄清这个问题。

8 个答案:

答案 0 :(得分:11)

定义约束:entity_id_a < entity_id_b

创建索引:

CREATE UNIQUE INDEX ix_a_b ON entity_entity(entity_id_a, entity_id_b);
CREATE INDEX ix_b ON entity_entity(entity_id_b);

第二个索引不需要包含entity_id_a,因为您只会在一个a中选择所有bRANGE SCAN上的ix_b将比SKIP SCAN上的ix_a_b更快。

使用您的实体填充表格,如下所示:

INSERT
INTO entity_entity (entity_id_a, entity_id_b)
VALUES (LEAST(@id1, @id2), GREATEST(@id1, @id2))

然后选择:

SELECT entity_id_b
FROM entity_entity
WHERE entity_id_a = @id
UNION ALL
SELECT entity_id_a
FROM entity_entity
WHERE entity_id_b = @id

UNION ALL这里允许您使用上面的索引并避免对唯一性进行额外排序。

以上所有都适用于对称和反自我关系。这意味着:

  • 如果 a b 相关,那么 b a

  • 相关
  • a 永远不会与 a

  • 相关联

答案 1 :(得分:1)

我认为你建议的结构很好。

要获取相关记录,请执行

之类的操作
SELECT related.* FROM entities AS search 
LEFT JOIN entity_entity map ON map.entity_id_a = search.id
LEFT JOIN entities AS related ON map.entity_id_b = related.id
WHERE search.name = 'Search term'

希望有所帮助。

答案 2 :(得分:1)

链接表方法看起来很好,除了你可能想要一个'关系类型',这样你就知道为什么它们是相关的。

例如,罗利和北卡罗来纳州之间的关系与罗利和达勒姆之间的关系不同。此外,如果您正在推动条件下拉菜单,您可能想知道关系中的“父母”是谁。 (即你选择一个州,你可以看到该州的城市)。

根据您的要求的复杂程度,您现在的简单设置可能还不够。如果您只需要以某种方式显示两条记录是相关的,那么链接表就足够了。

答案 3 :(得分:1)

我已经在您的设计中发布了一种方法,但如果您的设计具有一定的灵活性,并且更贴合您的需求,我也希望提供这种独立的设计见解。

如果项目在(非重叠)等价类中,您可能希望将等价类作为表设计的基础,其中类中的所有内容都被视为等效。课程本身可以是匿名的:

CREATE TABLE equivalence_class (
    class_id int -- surrogate, IDENTITY, autonumber, etc.
    ,entity_id int
)
对于您的空间的非重叠分区,

entity_id应该是唯一的。

这避免了确保正确的左手或右手或迫使右上角关系矩阵的问题。

然后你的查询有点不同:

SELECT c2.entity_id
FROM equivalence_class c1
INNER JOIN equivalence_class c2
    ON c1.entity_id = @entity_id
    AND c1.class_id = c2.class_id
    AND c2.entity_id <> @entity_id

或等同于:

SELECT c2.entity_id
FROM equivalence_class c1
INNER JOIN equivalence_class c2
    ON c1.entity_id = @entity_id
    AND c1.class_id = c2.class_id
    AND c2.entity_id <> c1.entity_id

答案 4 :(得分:0)

select * from entities
where entity_id in 
(
    select entity_id_b 
    from entity_entity 
    where entity_id_a = @lookup_value
)

答案 5 :(得分:0)

我可以想到几个方法。

一次使用CASE:

SELECT DISTINCT
    CASE
        WHEN entity_id_a <> @entity_id THEN entity_id_a
        WHEN entity_id_b <> @entity_id THEN entity_id_b
    END AS equivalent_entity
FROM entity_entity
WHERE entity_id_a = @entity_id OR entity_id_b = @entity_id

或者两个过滤的查询UNIONed因此:

SELECT entity_id_b AS equivalent_entity
FROM entity_entity
WHERE entity_id_a = @entity_id
UNION
SELECT entity_id_a AS equivalent_entity
FROM entity_entity
WHERE entity_id_b = @entity_id

答案 6 :(得分:0)

根据您更新的架构,此查询应该有效:

select if(entity_id_a=:entity_id,entity_id_b,entity_id_a) as related_entity_id where :entity_id in (entity_id_a, entity_id_b)

其中:entity_id绑定到您要查询的实体

答案 7 :(得分:-1)

我的建议是你的初始桌面设计不好。不要在同一个表中存储不同类型的东西。 (数据库设计的第一条规则,在那里不要在同一领域存储多条信息)。这样查询起来要困难得多,并且会导致严重的性能问题。此外,将数据输入到实际关系表中会出现问题 - 如何知道在进行新条目时需要实现哪些实体?设计适当的关系表会好得多。实体表几乎总是一个坏主意。我认为这个例子中没有任何理由在一个表中提供这种类型的信息。坦率地说,我有一张大学桌子和一张相关的地址表。它很容易查询和执行得更好。