数据建模

将数据从 DataFrames 转换为图

在将任何复杂的数据集准备加载到 Neo4j 时,您有两个选择

  • 规范化加载

  • Cypher® 解构

本节描述了这两种方法,并从性能和复杂性的角度提供了有关其优势和潜在缺点的信息。

尽可能使用规范化加载方法来获得最佳性能和可维护性。

规范化加载

假设您想将名为 purchases 的单个 DataFrame 加载到 Neo4j 中,其内容如下

product_id,product,customer_id,customer,quantity
1,Socks,10,David,2
2,Pants,11,Andrea,1

这些数据表示一个简单的 (:Customer)-[:BOUGHT]→(:Product) 图模型。

规范化加载方法要求您创建多个不同的 DataFrames:一个用于您所需图中的每个节点标签和关系类型。例如,在这种情况下,您可能会创建三个 DataFrames

  • val products = spark.sql("SELECT product_id, product FROM purchases")

  • val customers = spark.sql("SELECT customer_id, customer FROM purchases")

  • val bought = spark.sql("SELECT product_id, customer_id, quantity FROM purchases")

一旦这些简单的 DataFrames 代表了'标签表的规范视图'(即,每个节点标签或关系类型一个 DataFrame/表)——那么连接器提供的用于写入节点和关系的现有实用程序就可以使用,无需额外的 Cypher。此外,如果这些框架是通过标识符来进行唯一标识的,那么数据就已经准备好了,可以最大限度地提高并行性。(请参阅下面各节中的并行性说明。)

优点

  • 规范化加载方法将大部分数据转换工作转移到 Spark 本身(在数据拆分、唯一化、分区方面)。如果可能,任何数据转换/清理工作都应该在 Spark 中完成。

  • 这种方法使代码易于理解;最终,将每个 DataFrame 写入 Neo4j 非常简单,只需要一个标签和一个键。

  • 这允许并行性(在下面各节中讨论)。

缺点

  • 您需要在数据加载到 Neo4j 之前完成更多 SQL 工作。

  • 这种方法要求在开始之前确定图模式,而不是将数据加载到 Neo4j 中,然后使用 Cypher 在之后对其进行操作。

Cypher 解构

Cypher 解构是使用单个 Cypher 语句将复杂记录处理成完成的图形模式的过程。让我们再看看数据示例

product_id,product,customer_id,customer,quantity
1,Socks,10,David,2
2,Pants,11,Andrea,1

要将其存储在 Neo4j 中,您可以使用以下 Cypher 查询

MERGE (p:Product { id: event.product_id })
  ON CREATE SET p.name = event.product
WITH p
MERGE (c:Customer { id: event.customer_id })
  ON CREATE SET c.name = event.customer
MERGE (c)-[:BOUGHT { quantity: event.quantity }]->(p);

在这种情况下,整个工作可以通过单个 Cypher 语句完成。随着 DataFrames 的变得复杂,Cypher 语句也会变得相当复杂。

优点

  • 极其灵活:您可以执行 Cypher 提供的任何操作。

  • 如果您是 Neo4j 专家,那么您很容易上手。

缺点

  • Cypher 解构方法倾向于将转换工作转移到 Neo4j,这不是一个好主意,因为 Neo4j 不像 Spark 那样拥有支持转换工作的基础设施。

  • 这种方法倾向于创建大量的锁定行为,这违反了并行性,可能还会影响性能。

  • 它鼓励您在 Cypher 查询中嵌入模式信息,而不是使用 Spark 实用程序。

将数据从图转换回 DataFrames

一般情况下,始终要有明确的 RETURN 语句并解构您的结果。

一种常见的模式是编写一个复杂的 Cypher 语句(可能是一个遍历许多关系的语句),以将数据集返回到 Spark。由于 Spark 不了解图形基元,因此没有太多有用的方法可以表示 Spark 中的原始节点、关系或路径。因此,强烈建议不要从 Cypher 返回这些类型到 Spark,而是专注于您可以作为 Spark 中的简单类型表示的具体属性值和函数结果。

例如,以下 Cypher 查询会导致一个难以操作的 DataFrame

MATCH path=(p:Person { name: "Andrea" })-[r:KNOWS*]->(o:Person)
RETURN path;

一个更好的 Cypher 查询,它会导致一个更干净的 DataFrame,如下所示

MATCH path=(p:Person { name: "Andrea" })-[r:KNOWS*]->(o:Person)
RETURN length(path) as pathLength, p.name as p1Name, o.name as p2Name