建模设计

本页介绍了 Neo4j 中常用的图数据建模模式和设计示例。其目的是让您概览构建图数据模型时可用的选项,以及如何将已知策略应用于您的项目。

中间节点

中间节点是指包含图数据中必需、但在初始模型中又难以完美融合的信息的节点。

有时您需要在关系中传达大量信息。在数学图论中,这可以通过超边(hyperedge)来解决,即连接两个以上节点的边。Neo4j 不直接支持超边,但可以通过使用中间节点来解决。

例如,考虑一个人在某公司工作,您需要传达关于他们职位的相关信息。

在数学图论中,您可以使用相同的 WORKED_AT 关系同时连接 Person(人)节点与 Role(职位)和 Company(公司)节点。然而,Neo4j 并不支持这种方式。

相反,您可以将 Role 节点转换为 WORKED_AT 关系的一个属性,或者在 PersonCompanyRole 节点之间使用一个中间节点

在这个新图中,不再说 Patrick 在 Acme 公司工作,而是 Patrick 有一个就业事件(employment event),这成为了一个新的节点。就业事件包含就业的开始和结束日期,并在逻辑上与上述三个节点相关联。

尽管就业事件是一个抽象概念,但它是链接相关额外信息的一种好方法。

共享上下文

在上一个示例的扩展版本中,添加了一个名为 David 的新 Person 节点。

这个扩展示例突显了使用公共事件(中间节点)在多个节点之间展示共享上下文的能力。具体来说,在这个示例中,Person 节点通过 RoleCompany 节点共享上下文。Employment 节点提供了一种追踪详细信息的方式,例如个人的职业生涯,或者不同个人在同一 Company 的重叠情况,亦或是担任相同 Role 的人员。

中间节点的使用还可以回答“谁在同一时间在同一家公司工作?”这样的问题,因为添加的就业事件包含有关每个人在特定公司工作时间的信息。MATCH 子句将显示 Patrick 和 David 都曾在 Acme 工作,由于他们的就业事件在该时间段内重叠,因此他们从 2004 年到 2005 年是同事。

MATCH (p1:Person)-[w1:WORKED_AT]->(c:Company {name: "Acme"}),
      (p2:Person)-[w2:WORKED_AT]->(c)
WHERE p1 <> p2
  AND w1.startDate <= w2.endDate
  AND w2.startDate <= w1.endDate
RETURN p1.name AS Person1, p2.name AS Person2
ORDER BY Person1, Person2

共享数据

中间节点还可以通过提供共享数据的方式来增加模型的价值,从而减少重复信息。在这个示例中,Sarah 给 Lucy 发送了一封电子邮件,并抄送给了 David 和 Claire。每封邮件的内容都是每个关系上的一个属性。

如果您转而对模型进行“扇出”(fan out)处理,通过将 content 属性从所有关系中剥离出来,并将其转变为中间节点 Email,就可以减少冗余。

一旦属性值 content 被移动到单个 Email 节点,它就可以通过关系被之前持有该值的 User 节点引用。现在不存在重复了。

组织数据

中间节点也有助于组织结构。在上一个示例中,Sarah 将同一封电子邮件发送给了几个人。如果 Sarah 给更多人发送更多邮件而不使用中间节点,图结构会迅速增长成这样:

当每个 EMAILED 关系都包含一个带有邮件内容的属性时,除了重复之外,还会出现另外两个问题:

  • Sarah 的节点变得非常稠密:她发送的每一封邮件(包括抄送)都会让她的节点增加一个新的关系。

  • 检索邮件内容成本高昂:按照这种方式建模数据,通过搜索多个 'EMAILED' 关系中的内容来确定 Sarah 收件人网络中谁收到了特定邮件,成本非常高。

当您进行“扇出”并添加中间节点来代表每一封邮件时,无论收件人数量如何,Sarah 的节点每封邮件只有一条关系。

使用这种模型,您可以定位包含 content 属性(即邮件内容)的特定 Email 节点,然后查看哪些用户通过 TO 关系与该节点连接,从而找到收件人。

虽然两种模型都使用了“收集并检查”的方法,但在重构之后,问题的范围显著缩小。在第一次迭代中,如果您想查看谁收到了某封特定邮件,需要找到所有通过 EMAILED 关系与 Sarah 相连的用户。而在第二次迭代中,您只需要定位正确的 Email 节点,然后从中遍历到所有已连接的收件人即可。

总而言之,在重构过程中,您可能会发现中间节点有许多用途,因为您很少在数据建模之初就意识到对它们的需求。

链表

链表在计算机科学中非常常用,特别是在对象顺序很重要的场景下。单向链表是指每个节点仅链接到下一个节点。

双向链表中,每个节点既链接到下一个节点,也链接到上一个节点。

不建议使用双向链表,因为其中一个关系是多余的(如果一个是“下一个”,那么另一个就是“上一个”),而且 Cypher® 也允许双向匹配。此外,虽然使用动词作为关系类型是惯例,但对于链表,使用“next”(下一个)和“previous”(上一个)等术语来连接顺序项是可以接受的。

交错链表

当您想基于上下文而非时间顺序对一组项目进行排序时,会使用交错链表。此示例结合了一个普通链表和一个《神秘博士》(Dr. Who)剧集的交错链表。

电视节目的播出顺序通常与制作顺序不同。此示例包含了第 12 季的五集《神秘博士》,展示了:

  • 使用 NEXT 关系和单向链表表现的剧集播出顺序。

  • 使用 NEXT_IN_PRODUCTION 关系表现的剧集制作顺序,这创建了一个交错链表。这不是一个线性列表,其顺序为 1, 3, 2, 5, 4。

请注意,此示例不是双向链表,因为这些关系并不是互斥的。

链表的头和尾

使用链表时,通常有一个作为入口点的“父”节点。父节点几乎总是指向序列中的第一项,并使用命名恰当的关系。有时,另一个关系会指向列表中的最后一项。

在此示例中,您可以看到 FIRSTLAST 关系,分别指向它们在序列中的位置。

某些实现还有一个“进度”指针,用于追踪当前感兴趣的节点。这可以通过关系来实现,如下所示:

此处的进度指针是 LATEST_AIRED 关系,它显示了最近播出的剧集(即《太空方舟》)。当下一集(《桑塔兰实验》)播出时,通过删除当前关系并创建新的 LATEST_AIRED 指针来更新关系,使其始终指向当前项。