如何:使用 Neo4j Desktop 导入 CSV 数据

简介

Neo4j Desktop 提供了一个用户友好的界面,用于创建和启动 Neo4j 实例、添加或删除插件、更改配置和其他功能。它还包含一些快捷方式和简易访问功能,用于将文件(例如 CSV 文件)导入 Neo4j。

在本指南中,您将使用一个包含三个 CSV 文件的压缩文件夹,并将数据导入到 Neo4j Desktop 中的图中。CSV 文件包含产品、订单和订单明细的数据。您将在本指南的后面部分查看文件中的数据。

创建和启动 Neo4j 实例

如果您已经知道如何在 Neo4j Desktop 中创建项目、Neo4j 实例(DBMS)并启动 DBMS,则可以跳到下一步:将 CSV 文件添加到导入文件夹

如果您第一次打开 Neo4j Desktop,您将看到一个 **Neo4j Primer 项目**,其中已启动了 *电影数据库*。如果您想开始学习如何使用 Neo4j 和 Cypher®,可以使用此数据库。但是,您可以创建自己的项目,该项目可以包含一个或多个 DBMS。

让我们看一下如何在 Neo4j Desktop 中创建新项目,以及如何创建和启动 Neo4j 实例。

  1. 您一次只能运行一个 DBMS。要启动一个新的 DBMS,必须首先单击顶部栏上的“停止”按钮停止活动的 DBMS。结果,您将看到 **没有活动的 DBMS**。

  2. 要添加新项目,请转到左侧侧边栏上的 **项目** 抽屉,方法是单击图标 neo4j 桌面项目图标,然后单击 **项目** 前面的“新建”按钮。这将创建一个名为“项目”的项目。

  3. 要更改项目名称,请将鼠标悬停在项目名称上并选择“编辑”按钮。

    输入项目名称并选择“确认”按钮以保存它。

  4. 接下来,您将在项目中创建一个本地 DBMS。单击要添加 DBMS 的项目名称旁边的“添加”按钮,然后选择“本地 DBMS”。这将打开一个对话框,您可以在其中指定 DBMS 的详细信息。

  5. 现在您可以命名您的 DBMS。您可以使用默认名称 *Graph DBMS*,但建议您将其重命名以帮助您识别它。有关更多详细信息,请参阅 升级和迁移指南 → 数据库命名规则

    此处将 *MyDBMS* 指定为名称

  6. 必须为 DBMS 指定密码。

    从 **Neo4j 5.3** 开始,初始密码必须至少为 **八个字符** 长。

  7. Neo4j Desktop 自动创建具有默认版本的 DBMS,但您可以为其选择其他版本。但是,您可以选择其他版本。请记住,如果版本旁边显示向下箭头,则表示 Neo4j Desktop 需要下载该特定版本 DBMS 的资源。为此,您 必须连接到互联网。

  8. 指定 DBMS 的详细信息后,单击“创建”按钮。以下是 DBMS 成功创建后您应该看到的内容

  9. 由于您一次只能拥有一个活动 DBMS,因此请确保在启动新创建的 DBMS 之前停止任何正在运行的实例,方法是将鼠标悬停在其名称右侧并单击“启动”按钮。

    DBMS 将需要几秒钟才能启动。如果成功,您应该会看到类似以下内容

DBMS 启动后,您可以通过系统上运行的 Neo4j 浏览器和 Neo4j Bloom 等客户端访问它。在 Neo4j Desktop 中,DBMS 是一个企业服务器,但只能在本地访问。

将 CSV 文件添加到导入文件夹

首先,下载此 zip 文件。将其解压缩以生成三个用于产品、订单和订单详细信息的 CSV 文件,然后将它们添加到 Neo4j Desktop 中的 **import** 文件夹。

您可以通过将鼠标悬停在已启动 DBMS 右侧的三个点上并选择“打开文件夹”,然后选择“导入”来打开查找器窗口

另一种选择是将三个 CSV 文件复制或移动到系统上的 **导入目录**。有关 Neo4j 文件位置的更多信息,请参阅 操作手册 → 默认文件位置

现在您的文件已位于 **import** 文件夹中,您可以将数据导入到 DBMS 管理的数据库中。您将使用 CSV 文件中的当前表和列格式,并将其转换为节点和关系。这可以通过几种不同的方式完成,但您将在本指南中使用 Cypher 的 LOAD CSV 命令。

LOAD CSV

LOAD CSV 是 Cypher 中的一个内置命令,允许您读取 CSV 文件并将常规 Cypher 查询附加到创建或更新数据作为图。您也可以在不创建图的情况下使用 LOAD CSV 来输出样本、计数或分布。这有助于在写入和存储数据之前检测不正确的标题列计数、分隔符、引号、转义符或标题名称的拼写。

要输入并在已启动的 DBMS 上运行 Cypher 查询,您可以

  1. 使用 Neo4j 浏览器

    1. 单击已启动 DBMS 的“打开”按钮。

    2. 在顶部的编辑窗格中键入或复制 Cypher 查询 (Cypher 编辑器)。

    3. 使用右侧的“播放”按钮执行 Cypher 查询。

  2. 使用 Cypher Shell

    1. 单击“打开”按钮右侧的下拉菜单,然后选择“终端”。

    2. 输入 bin/cypher-shell

    3. 输入 **neo4j** 作为用户。

    4. 输入您为 DBMS 指定的密码。

    5. 使用 :exit 退出。

所有 Cypher 查询必须在 Cypher Shell 中以分号 ; 结尾。

之前您下载了.zip文件并将它的CSV文件复制到了DBMS的import文件夹。建议您在将任何内容插入图数据库之前,检查要添加到import文件夹中的文件中的数据。为此,您可以使用LOAD CSV语句。

如果您之前打开过这些文件,您可能已经注意到其中两个文件有标题,而一个文件没有(products.csv)。要检查每个文件,请检查CSV文件中存在多少行,以确保它们在潜在的导出过程中没有损坏或被截断。对于有标题的文件,您可以在LOAD CSV之后添加WITH HEADERS子句,以便它排除标题行并在计数中仅计算数据行。

以下是将要使用的Cypher查询

//count data rows in products.csv (no headers)
LOAD CSV FROM 'file:///products.csv' AS row
RETURN count(row);
//count data rows in orders.csv (headers)
LOAD CSV WITH HEADERS FROM 'file:///orders.csv' AS row
RETURN count(row);
//count data rows in order-details.csv (headers)
LOAD CSV WITH HEADERS FROM 'file:///order-details.csv' AS row
RETURN count(row);

运行这些语句应返回以下计数

  • products.csv有77行

  • orders.csv有830行

  • order-details.csv有2155行

使用LOAD CSV查看数据

接下来,您可以查看CSV文件中的数据以及LOAD CSV如何读取它。您需要从上面的Cypher查询中更改的唯一一行是RETURN子句。由于这些文件有多行,因此使用LIMIT仅获取样本。

//view data rows in products.csv
LOAD CSV FROM 'file:///products.csv' AS row
RETURN row
LIMIT 3;

您的结果应如下所示

["1", "Chai", "18"]

["2", "Chang", "19"]

["3", "Aniseed Syrup", "10"]

//count data rows in orders.csv (headers)
LOAD CSV WITH HEADERS FROM 'file:///orders.csv' AS row
RETURN row
LIMIT 5;

您的结果应如下所示

{ "orderID": "10248", "orderDate": "1996-07-04 00:00:00.000", "shipCountry": "France" }

{ "orderID": "10249", "orderDate": "1996-07-05 00:00:00.000", "shipCountry": "Germany" }

{ "orderID": "10250", "orderDate": "1996-07-08 00:00:00.000", "shipCountry": "Brazil" }

{ "orderID": "10251", "orderDate": "1996-07-08 00:00:00.000", "shipCountry": "France" }

{ "orderID": "10252", "orderDate": "1996-07-09 00:00:00.000", "shipCountry": "Belgium" }

//count data rows in order-details.csv (headers)
LOAD CSV WITH HEADERS FROM 'file:///order-details.csv' AS row
RETURN row
LIMIT 8;

您的结果应如下所示

{ "quantity": "12", "productID": "11", "orderID": "10248" }

{ "quantity": "10", "productID": "42", "orderID": "10248" }

{ "quantity": "5", "productID": "72", "orderID": "10248" }

{ "quantity": "9", "productID": "14", "orderID": "10249" }

{ "quantity": "40", "productID": "51", "orderID": "10249" }

{ "quantity": "10", "productID": "41", "orderID": "10250" }

{ "quantity": "35", "productID": "51", "orderID": "10250" }

{ "quantity": "15", "productID": "65", "orderID": "10250" }

请注意,orders.csvorder-details.csv返回的数据格式与products.csv不同。这是因为这些文件有标题,因此这些行的列名与值一起返回。由于products.csv没有列名,因此LOAD CSV只返回文件中的纯数据行。

使用LOAD CSV过滤加载的数据

检查完数据后,您可能只想查看或加载CSV文件中的数据子集。您可以按如下方式过滤要查看(或加载)的内容

//count data rows in orders.csv (headers)
LOAD CSV WITH HEADERS FROM 'file:///orders.csv' AS row
WITH row WHERE row.shipCountry = 'Germany'
RETURN row
LIMIT 5;

您的结果应如下所示

{ "orderID": "10249", "orderDate": "1996-07-05 00:00:00.000", "shipCountry": "Germany" }

{ "orderID": "10260", "orderDate": "1996-07-19 00:00:00.000", "shipCountry": "Germany" }

{ "orderID": "10267", "orderDate": "1996-07-29 00:00:00.000", "shipCountry": "Germany" }

{ "orderID": "10273", "orderDate": "1996-08-05 00:00:00.000", "shipCountry": "Germany" }

{ "orderID": "10277", "orderDate": "1996-08-09 00:00:00.000", "shipCountry": "Germany" }

数据类型

LOAD CSV命令将所有值读取为字符串。无论值在文件中如何显示,它都将作为字符串使用LOAD CSV加载。因此,在导入之前,请确保转换任何非字符串值。

Cypher中有多种转换函数。您将在本练习中使用以下函数

  • toInteger():将值转换为整数。

  • toFloat():将值转换为浮点数(在本例中,用于货币金额)。

  • datetime():将值转换为DateTime

我们查看每个CSV文件中的值以确定需要转换哪些值。

Products.csv

products.csv文件中的值对应于productIDproductNameunitCostproductID看起来像一个随着每行增加的整数值,因此您可以使用Cypher中的toInteger()函数将其转换为整数。productName可以保持为字符串,因为它由字符组成。最后一列是产品unitCost。尽管您检查的示例值都是整数,但货币金额通常有小数位值。出于这个原因,建议使用toFloat()函数将这些值转换为浮点数。

以下是您应该如何运行Cypher查询。请记住,您此时尚未将值加载到Neo4j中。您将只是查看具有转换值的CSV文件。

LOAD CSV FROM 'file:///products.csv' AS row
WITH toInteger(row[0]) AS productId, row[1] AS productName, toFloat(row[2]) AS unitCost
RETURN productId, productName, unitCost
LIMIT 3;

您的结果应如下所示

productId productName unitCost

1

"Chai"

18.0

2

"Chang"

19.0

3

"Aniseed Syrup"

10.0

请注意,我们使用集合位置(row[0]、row[1]、row[2])来引用行中的列,并通过使用别名来引用它们以提高可读性。在没有标题的文件中,这就是引用每个位置中的值的方式。

Orders.csv

orders.csv中的值(根据列名)对应于orderIDorderDateshipCountry。同样,您可以评估这些值并确定要应用的任何转换。

OrderID看起来像一个整数,因此您可以使用toInteger()函数进行转换。orderDate列当然采用日期格式,我们将需要使用datetime()函数对其进行格式化。最后,shipCountry值是字符,因此您可以将其保留为字符串。

就像您对上一个CSV文件所做的那样,让我们在不导入数据的情况下查看这些转换的结果。

LOAD CSV WITH HEADERS FROM 'file:///orders.csv' AS row
WITH toInteger(row.orderID) AS orderId, datetime(replace(row.orderDate,' ','T')) AS orderDate, row.shipCountry AS country
RETURN orderId, orderDate, country
LIMIT 5;

您的结果应如下所示

orderId orderDate country

10248

"1996-07-04T00:00:00Z"

"France"

10249

"1996-07-05T00:00:00Z"

"Germany"

10250

"1996-07-08T00:00:00Z"

"Brazil"

10251

"1996-07-08T00:00:00Z"

"France"

10252

"1996-07-09T00:00:00Z"

"Belgium"

这个CSV在orderDate列中有一个棘手的问题。Neo4j的datetime使用ISO 8601格式,该格式在日期和时间值之间使用分隔符T。CSV文件没有连接日期和时间值的'T',而是用空格分隔。您使用了replace()函数将空格更改为字符'T',并将字符串转换为预期格式。然后,您在它周围包装了datetime()函数以将更改后的字符串转换为DateTime值。

Order-details.csv

order-details.csv中的值(根据列名)对应于productIDorderIDquantity。让我们看看哪些需要转换。

productID也来自products.csv文件,您已将该值转换为整数。您将在这里执行相同的操作以确保格式匹配。orderID字段包含orders.csv文件中的值,因此您将匹配之前的转换并将此字段也转换为整数。此文件中的quantity字段是数值。您可以使用一直使用的toInteger()函数将其转换为整数。

这些转换的结果在下面的代码中。请记住,您仍然没有加载任何数据。

LOAD CSV WITH HEADERS FROM 'file:///order-details.csv' AS row
WITH toInteger(row.productID) AS productId, toInteger(row.orderID) AS orderId, toInteger(row.quantity) AS quantityOrdered
RETURN productId, orderId, quantityOrdered
LIMIT 8;

您的结果应如下所示

productId orderId quantityOrdered

11

10248

12

42

10248

10

72

10248

5

14

10249

9

51

10249

40

41

10250

10

51

10250

35

65

10250

15

加载数据

既然您已经确定CSV文件数据看起来不错,并且您已经验证了LOAD CSV如何读取数据并转换了任何非字符串值,那么您几乎准备好创建图数据库中的数据了。

为此,您将在上面使用的LOAD CSV命令旁边使用Cypher语句。LOAD CSV将读取文件,而Cypher语句将在您的数据库中创建数据。

图数据模型

不过,在编写Cypher语句之前,您需要执行的重要步骤是确定导入文件数据后图结构应是什么样子。毕竟,从现有的表和列数据中导入数据不会提供您希望从图中获得的值。为了充分利用图数据库,您需要一个图数据模型。

虽然有多种方法可以组织文件中的产品和订单,但这将在另一本指南中进行介绍。在本练习中,请使用模型的以下版本

您有两个节点——一个用于产品,一个用于订单。每个节点都具有来自CSV文件属性。对于Product,您有ID、名称和单位成本。对于Order,您有ID、日期/时间和订单送达的国家/地区。

order-details.csv文件定义了这两个节点之间的关系。它包含产品ID、它所属的订单ID以及订单中产品的数量。在数据模型中,这些成为ProductOrder节点之间的CONTAINS关系。属性quantityOrdered也包含在关系中,因为只有当产品与订单相关联时,产品数量值才存在。

现在您知道了将要拥有的节点和关系的类型以及涉及的属性,您可以构建Cypher语句以创建此模型的数据。

避免重复并提高性能

在您在图中创建数据之前,您需要考虑的最后一件事是确保值是唯一的并且性能是高效的。要处理此问题,您可以使用约束。就像其他数据库一样,约束确保不会违反数据完整性标准,同时还对具有约束的属性进行索引以提高查询性能。

在导入任何数据之前以及在已存在数据时,都有在数据库中应用索引的情况。在本练习中,您将在创建任何数据之前添加两个约束——一个用于productId,一个用于orderId。这将确保当您创建每个这些类型的节点或连接它们的关联时,您知道实体是唯一的且已编入索引。

以下是添加约束的Cypher代码

CREATE CONSTRAINT UniqueProduct FOR (p:Product) REQUIRE p.id IS UNIQUE;
CREATE CONSTRAINT UniqueOrder FOR (o:Order) REQUIRE o.id IS UNIQUE;

Cypher查询

现在您已准备好编写用于在图中创建数据的Cypher代码了。

您可以使用CREATE子句,在您确定CSV文件中不会有重复行,并使用MATCH查找现有数据以进行更新。但是,由于很难完全清理所有数据并从任何来源导入完全干净的数据,因此您将使用MERGE子句来检查数据是否已存在。如果节点或关系存在,则Cypher将匹配并返回它们(无需任何写入),但如果它们不存在,则Cypher将插入它们。

使用MERGE可能会导致一些性能开销,但通常它是维护高数据完整性的更好方法。

为什么同时使用约束和MERGE 使用约束与使用MERGE子句不同。违反约束条件的数据创建语句会提示错误,而使用MERGE子句的语句只会返回现有值(不会出现错误)。

如果您同时使用两者,则可以避免因约束冲突而终止加载语句,并且还可以确保不会在临时查询中意外创建重复项。

产品

要开始将产品加载到图中,请使用上面提到的LOAD CSV语句,然后运行Cypher查询,将CSV文件中的数据创建到您的模型中。请记住使用MERGE检查Product是否已存在。属性将设置为本指南前面处理的转换后的值。

LOAD CSV FROM 'file:///products.csv' AS row
WITH toInteger(row[0]) AS productId, row[1] AS productName, toFloat(row[2]) AS unitCost
MERGE (p:Product {productId: productId})
  SET p.productName = productName, p.unitCost = unitCost
RETURN count(p);

如果运行该语句,它将返回数据库中创建的产品节点数(count(p))。您可以将此数字与前面CSV文件中的行数进行交叉检查(products.csv中为77行)。您还可以运行验证查询以返回节点样本,并查看属性是否准确。

//validate products loaded correctly
MATCH (p:Product)
RETURN p LIMIT 20;

以下是Neo4j浏览器中的结果

订单

接下来,您将加载订单。同样,由于您希望验证不会创建重复的Order节点,因此可以使用MERGE子句。与产品一样,您从LOAD CSV命令开始,然后添加Cypher查询,并包含您的数据转换。

LOAD CSV WITH HEADERS FROM 'file:///orders.csv' AS row
WITH toInteger(row.orderID) AS orderId, datetime(replace(row.orderDate,' ','T')) AS orderDate, row.shipCountry AS country
MERGE (o:Order {orderId: orderId})
  SET o.orderDateTime = orderDate, o.shipCountry = country
RETURN count(o);

您还可以像以前一样运行验证查询,以验证图数据是否正确。

//validate orders loaded correctly
MATCH (o:Order)
RETURN o LIMIT 20;

以下是Neo4j浏览器中的结果

订单详细信息

最后但并非最不重要的是,您将在产品和订单之间创建关系。由于您预计图中已存在所有产品和所有订单(这些数据应已使用最后两个文件加载),因此您从MATCH开始查找现有的ProductOrder节点。然后,MERGE语句将添加新的关系或匹配现有关系。

如您在上面运行order-details文件的计数时发现的那样,CSV中有2,155行。虽然对于文件导入来说,这不是一个很大的数字,但您将让Cypher分批将数据提交到数据库,以减少事务状态的内存开销。为此,您可以在LOAD CSV子句之后使用子查询CALL {…​} IN TRANSACTIONS

输入行数由修饰符OF n ROWS(或ROW)设置。如果省略,则默认批处理大小为1000行。在本练习中,您将要求Cypher每500行提交一次。如果已经有大量内存分配给其他任务,或者内存有限,则可以减小此数字。

LOAD CSV WITH HEADERS FROM 'file:///order-details.csv' AS row
CALL {
 WITH row
 MATCH (p:Product {productId: toInteger(row.productID)})
 MATCH (o:Order {orderId: toInteger(row.orderID)})
 MERGE (o)-[rel:CONTAINS {quantityOrdered: toInteger(row.quantity)}]->(p)
} IN TRANSACTIONS OF 500 ROWS

在Neo4j浏览器中,不要忘记在前面的Cypher查询前加上:auto

就像您上面所做的那样,您可以使用下面的查询验证数据。

MATCH (o:Order)-[rel:CONTAINS]->(p:Product)
RETURN p, rel, o LIMIT 50;

以下是Neo4j浏览器中的结果

总结

您已成功使用Neo4j Desktop将三个CSV文件加载到Neo4j图数据库中。

LOAD CSV功能与Cypher相结合,对于将文件中的数据获取到图结构中非常有用。提高这方面技能的最佳方法是为各种数据集和模型加载各种文件。

增加挑战

如果您以后再次完成此练习,可以随意增加挑战,为这些文件设计自己的数据模型,或者尝试将其他一些CSV文件加载到图中。

如果您有任何疑问或需要有关使用LOAD CSV的帮助,请在社区网站上联系我们。