【问题标题】:What is the best way to represent a many-to-many relationship between records in a single SQL table?表示单个 SQL 表中记录之间的多对多关系的最佳方式是什么?
【发布时间】:2009-01-23 19:19:59
【问题描述】:

我有一个这样的 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。

感谢您抽出宝贵时间,如果我能澄清这个问题,请告诉我。

【问题讨论】:

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


    【解决方案1】:

    定义一个约束: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,因为您只会使用它来选择一个b 中的所有aRANGE SCAN on ix_b 会比 SKIP SCAN on 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 此处允许您使用上述索引并避免为唯一性进行额外排序。

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

    • 如果 ab 相关,则 ba

    • 相关
    • a 从未与 a

    • 相关

    【讨论】:

    • 这种方法在实践中效果很好。非常感谢。
    • 当您不知道您的实体的 id 是在 a 列还是 b 列中时,您是否有建议的方法来导航加入过程?我们是否应该像您拥有的 SELECT 那样创建一个视图,然后使用它加入(对性能有什么影响?)我使用的是 PostgreSQL,是否适用相同的索引建议?
    • @Azendale:只存储两对。请参阅我的这篇旧文章:explainextended.com/2009/03/07/selecting-friends,它适用于 MySQL,但原则适用于大多数系统。
    【解决方案2】:

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

    要获取相关记录,请执行以下操作

    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'
    

    希望对您有所帮助。

    【讨论】:

    • 如果我的搜索词匹配一个实体,其 id 只出现在地图中的 entity_id_b 中?
    • 换句话说,您的查询只有在每个关系存储两次时才有效,相反。例如。 (1,4) 和 (4,1)。
    【解决方案3】:

    链接表方法看起来不错,只是您可能需要一个“关系类型”,以便知道它们为何相关。

    例如,Raleigh 和 North Carolina 之间的关系与 Raleigh 和 Durham 之间的关系不同。此外,您可能想知道谁是关系中的“父母”,以防您使用条件下拉菜单。 (即您选择一个州,您可以看到该州的城市)。

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

    【讨论】:

    • 我明白你在说什么。在这种情况下,我们特别不代表层次结构。此系统中只会有一个状态,并且这些关系不会用于向下钻取式导航。
    【解决方案4】:

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

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

    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
    

    【讨论】:

    • 不错!您还可以测试 c2.entity_id c1.entity_id,而不是 c2.entity_id @entity_id。这样您就不必传递 @entity_id 参数两次。
    • 我认为这将是一个存储过程,但是是的,这对于参数化的临时查询爱好者来说是等效的。
    【解决方案5】:

    我能想到几个办法。

    带一个 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
    

    或者两个过滤后的查询联合起来:

    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】:
      select * from entities
      where entity_id in 
      (
          select entity_id_b 
          from entity_entity 
          where entity_id_a = @lookup_value
      )
      

      【讨论】:

        【解决方案7】:

        根据您更新的架构,此查询应该可以工作:

        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 绑定到您要查询的实体

        【讨论】:

          【解决方案8】:

          我的建议是您的初始表格设计很糟糕。不要在同一张表中存储不同类型的东西。 (数据库设计的第一条规则,就是不要在同一个字段中存储多条信息)。这更难查询,并且会导致严重的性能问题。另外,将数据输入到 realtionship 表中会是一个问题——当你做一个新的条目时,你怎么知道哪些实体需要被 realted?设计适当的关系表会好得多。实体表几乎总是一个坏主意。从示例中我看不出有任何理由将此类信息放在一个表中。坦率地说,我有一个大学表和一个相关的地址表。这将很容易查询并执行得更好。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-11-02
            • 2018-09-22
            • 1970-01-01
            • 2020-08-17
            • 2012-01-05
            • 1970-01-01
            相关资源
            最近更新 更多