使用 CSV 文件

本页面列出了一些在将 CSV 文件导入 Neo4j 之前应考虑的优化要点。

CSV 文件的结构

CSV 文件中有几个元素可以帮助您理解准备导入并最终进行建模的数据。

数据格式

Neo4j 将 CSV 文件中的所有数据读取为 string(字符串)。对于其他数据类型,您需要使用 toInteger()toFloat()toBoolean() 或类似的函数将数据转换为适当的类型。同时请记住,标签、属性名称、关系类型和变量是区分大小写的。

有关如何操作数据格式的更多信息,请参阅转换数据值

字段分隔符

字段分隔符(也称为分隔符)是用于分隔 CSV 文件中每个字段的字符。在本示例中,使用的是逗号 (,),但其他字符(如制表符 \t 或管道符 |)同样适用,并且可以混合使用。

personId,name,birthYear
23945,"Gerard Pires"|1942
553509,"Helen Reddy"|1941
113934,"Susan Flannery"|1939

您可以将示例复制并粘贴到任何文本编辑器(如记事本或 TextEdit)或电子表格应用程序(如 Excel 或 Google Sheets)中,然后将其另存为 CSV 文件。

标题行(Header)

标题行通常是 CSV 文件的第一行。虽然不是强制要求的,但添加标题行是一种良好的做法。在前面的示例中,标题行为:

personId,name,birthYear

如果您的 CSV 文件没有标题行,则需要了解各列的顺序,并使用索引来引用它们。这可能会使处理数据变得比预想中更复杂。

引号

引号 (") 用于定义应作为单个值存储的文本。例如:

personId,name,birthYear
23945,"Pires, Gerard",1942
553509,"Reddy, Helen",1941
113934,"Flannery, Susan",1939

此 CSV 文件使用逗号作为字段分隔符。在第二行 names 中,诸如 Pires, Gerard 之类的条目也包含逗号。如果条目 Pires, Gerard 没有用引号括起来,它将被视为两个不同的条目(即 Pires 一个,Gerard 一个)。

规范化数据 (Normalized data)

如果源数据是规范化的(例如从关系数据模型导出时),通常会有多个 CSV 文件。每个 CSV 文件代表关系数据模型中的一个表,并且文件之间通过唯一 ID 相关联。

在此规范化数据示例中,有三个文件分别对应人员、电影和角色:

person.csv
personId,name,birthYear
23945,Gerard Pires,1942
553509,Helen Reddy,1941
113934,Susan Flannery,1939
movies.csv
movieId,title,avgVote,releaseYear,genres
189,Sin City,8.000000,2005,Crime|Thriller
2300,The Fifth Element,7.700000,1997,Action|Adventure|Sci-Fi
11969,Tombstone,7.800000,1993,Action|Romance|Western
roles.csv
personId,movieId,character
2295,189,Marv
56731,189,Nancy
16851,189,Dwight

请注意,person.csv 文件为每个人都有一个唯一 ID,而 movies.csv 文件为每部电影都有一个唯一 ID。roles.csv 文件将人员与电影关联起来,并提供角色信息。它对应图中的关系,因为它包含了绑定节点所需的链接。

反规范化数据 (De-normalized data)

反规范化数据通常表示来自多个表的数据。如果源数据是反规范化的,通常会有一个包含所有数据的单一 CSV 文件,在实体之间存在关系的地方,数据往往是重复的。例如:

movies-n.csv
movieId,title,avgVote,releaseYear,genres,personType,name,birthYear,character
2300,The Fifth Element,7.700000,1997,Action|Adventure|Sci-Fi,ACTOR,Bruce Willis,1955,Korben Dallas
2300,The Fifth Element,7.700000,1997,Action|Adventure|Sci-Fi,ACTOR,Gary Oldman,1958,Jean-Baptiste Emanuel Zorg
2300,The Fifth Element,7.700000,1997,Action|Adventure|Sci-Fi,ACTOR,Ian Holm,1931,Father Vito Cornelius
11969,Tombstone,7.800000,1993,Action|Romance|Western,ACTOR,Kurt Russell,1951,Wyatt Earp
11969,Tombstone,7.800000,1993,Action|Romance|Western,ACTOR,Val Kilmer,1959,Doc Holliday
11969,Tombstone,7.800000,1993,Action|Romance|Western,ACTOR,Sam Elliott,1944,Virgil Earp

在这里,每当出现有关特定演员角色的新信息时,电影和人员数据(包括 ID)都会在不同的行中重复。这种重复会损害图数据结构。在这种情况下,建议在导入之前准备您的文件

文件位置

当使用 LOAD CSV 命令加载 CSV 数据时,可以通过 URL 访问 CSV 文件,可以是互联网上的 URL:

LOAD CSV WITH HEADERS
FROM 'https://data.neo4j.com/importing-cypher/people.csv' AS row
RETURN row

或者,如果您使用本地部署,则可以从本地文件夹访问。在这种情况下,您需要在文件名之前添加 file:/// 前缀:

LOAD CSV WITH HEADERS
FROM 'file:///people.csv' AS row
RETURN row

出于安全原因,默认情况下,本地文件只能从 Neo4j 导入目录中读取,该目录的位置因操作系统而异。有关更多信息,请参阅操作指南 → 文件位置

如果您想从其他位置打开 CSV 文件,则需要更改 server.directories.import 设置。

安全

强烈建议仅通过 HTTPS 等安全协议加载资源,而不是 HTTP 等不安全协议。这可以通过将 加载权限 (load privileges) 限制为仅使用安全协议的受信任源来实现。

如果无法避免使用不安全协议,您需要将 JVM 参数 -Dsun.net.http.allowRestrictedHeaders=true 添加到配置设置 server.jvm.additional 中,以避免被 Neo4j 的内置安全检查自动阻止。

文件准备

在导入 CSV 数据之前,您应该考虑数据的来源。它可能来自:

  • 关系数据库

  • Web API

  • 公共数据目录

  • BI 工具

  • 电子表格(如 Excel 或 Google Sheets)

大多数数据系统都有将数据导出为 CSV 文件的选项,因为这是常见的数据交换格式。然而,现实世界的数据往往很杂乱,这意味着在导入到另一个系统之前,需要对某些值进行清理或转换。

以下是您可能会遇到的一些常见问题:

  1. 源文件包含的数据多于您实际需要的

    例如,如果您只对某一位导演及其参与的电影感兴趣,而 电影数据集 包含了大量与您无关的数据。为了提高导入过程的效率,您需要在导入 CSV 文件之前删除不必要的数据。

  2. 标题与数据不一致

    标题可能与数据不一致,或者标题缺失,或在过多的列中丢失。

    为避免此问题:

    • 检查标题是否与文件中的数据匹配。

    • 在导入之前调整格式、列等,以确保流程顺畅。

  3. 额外的或缺失的引号

    在非引号文本中间出现的孤立双引号 (") 或单引号 ('),或者在引号文本中未转义的引号,在读取文件以进行加载时可能会导致问题。最好转义删除这些多余的引号。在 Cypher® 风格指南 中查找正确的转义说明。

  4. 特殊字符或换行符

    处理文件中的任何特殊字符时,请确保它们被引号引起来,或者直接删除它们。对于带引号或不带引号字段中的换行符,要么为它们添加引号,要么直接删除。

  5. 不一致的换行符

    确保整个文件中的换行符保持一致。对于 Linux 用户,建议使用 Unix 风格以保持兼容性。

  6. 二进制零、BOM 字节顺序标记(2 个 UTF-8 字节)或其他非文本字符

    应用程序工具中有时会隐藏异常字符或特定于工具的格式。您可以使用基础编辑器来检测并从文件中删除此类字符,或使用反引号进行转义

数据类型

由于 Neo4j 将所有导入的值读取为 string(字符串),因此您需要转换任何非 string 类型的值。您可以使用 Cypher 中的函数来完成此操作:

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

  • toFloat(): 将值转换为 float(浮点数,例如货币金额)。

  • datetime(): 将值转换为 DateTime(日期时间)。

根据 CSV 文件中数据的类型,您需要相应地转换值。有关 Cypher 中可用值和类型的更多信息,请参阅 Cypher → 值和类型

清理数据

CSV 文件中的某些问题需要加载前解决,但其他一些问题可以通过在 LOAD CSV 命令中添加额外子句来加载过程中解决。

空值 (Null values)

Neo4j 不存储空值,但您可以通过在 LOAD CSV 命令中添加子句或函数来跳过或将它们替换为默认值。假设您有以下 CSV 文件:

companies.csv
Id,Name,Location,Email,BusinessType
1,Neo4j,San Mateo,contact@neo4j.com,P
2,AAA,,info@aaa.com,
3,BBB,Chicago,,G

第三行和第四行在某些标题下没有条目,这意味着它们包含需要跳过的空值。您可以使用 WHERE 子句来指定跳过它们:

LOAD CSV WITH HEADERS FROM 'file:///companies.csv' AS row
WITH row WHERE row.Id IS NOT NULL
MERGE (c:Company {companyId: row.Id});

或者,您可以为它们设置一个默认值(例如 "Unknown"),并使用 coalesce 函数:

LOAD CSV WITH HEADERS FROM 'file:///companies.csv' AS row
MERGE (c:Company {companyId: row.Id, hqLocation: coalesce(row.Location, "Unknown")})

您还可以使用 SET 子句将空 strings(字符串)更改为不会被存储的空值:

LOAD CSV WITH HEADERS FROM 'file:///companies.csv' AS row
MERGE (c:Company {companyId: row.Id})
SET c.emailAddress = CASE trim(row.Email) WHEN "" THEN null ELSE row.Email END

条件转换

可以使用 CASE 实现条件转换。前面的示例检查了空值或空 strings,但您也可以在此清理阶段根据 CSV 文件中的值设置属性。

例如,您可以根据 CSV 文件中的缩写值设置 businessType 属性:

LOAD CSV WITH HEADERS FROM 'file:///companies.csv' AS row
WITH row WHERE row.Id IS NOT NULL
WITH row,
(CASE row.BusinessType
 WHEN 'P' THEN 'Public'
 WHEN 'R' THEN 'Private'
 WHEN 'G' THEN 'Government'
 ELSE 'Other' END) AS type
MERGE (c:Company {companyId: row.Id, hqLocation: coalesce(row.Location, "Unknown")})
SET c.emailAddress = CASE trim(row.Email) WHEN "" THEN null ELSE row.Email END
SET c.businessType = type
RETURN *

作为条目的列表

如果 CSV 文件中的某个字段是一个列表,且您想将其拆分为单独的行,则可以使用 Cypher 的 split() 函数来分隔单元格中的数组。例如:

employees.csv
Id,Name,Skills,Email
1,Joe Smith,Cypher:Java:JavaScript,joe@neo4j.com
2,Mary Jones,Java,mary@neo4j.com
3,Trevor Scott,Java:JavaScript,trevor@neo4j.com

Joe 和 Trevor 在此文件中都列出了多种技能。您可以使用 split() 函数配合 UNWIND 子句来拆分它们,如下所示:

LOAD CSV WITH HEADERS FROM 'file:///employees.csv' AS row
MERGE (e:Employee {employeeId: row.Id, email: row.Email})
WITH e, row
UNWIND split(row.Skills, ':') AS skill
MERGE (s:Skill {name: skill})
MERGE (e)-[r:HAS_EXPERIENCE]->(s)

清理工具

您可以使用以下第三方工具来确保您的 CSV 文件处于良好状态,从而高效地导入数据:

  • CSVKit:一套 Python 工具,提供统计 (csvstat)、搜索 (csvgrep) 等功能。

  • CSVLint:用于验证 CSV 文件的在线服务。您可以上传文件或提供 URL 来加载它。

  • Papa Parse:一个全面的 JavaScript CSV 解析库,允许流式传输 CSV 数据,并针对问题提供良好的、易于阅读的错误报告。

文件大小

您可以对中小型数据集(最多 1000 万条记录)使用大多数 Neo4j 导入方法。如果您要导入更大的数据集,建议使用 neo4j-admin database import。请参阅 Neo4j-admin import 教程以了解更多信息。

优化

在处理大量数据或复杂加载时,性能可能会成为问题。然而,一些策略可以提高处理大量信息的速度。

例如,如果您想使用 前面的 companies.csv 文件 和以下文件创建图:

people.csv
employeeId,Name,companyId
1,Bob Smith,1
2,Joe Jones,3
3,Susan Scott,2
4,Karen White,1

在这种情况下,您应该将节点和关系的创建过程分离开来。例如,不要使用以下方式:

LOAD CSV WITH HEADERS FROM 'file:///people.csv' AS row
MERGE (e:Employee {employeeId: row.employeeId})
MERGE (c:Company {companyId: row.companyId})
MERGE (e)-[r:WORKS_FOR]->(c)

您可以这样写:

加载 Employee 节点
LOAD CSV WITH HEADERS FROM 'file:///people.csv' AS row
MERGE (e:Employee {employeeId: row.employeeId, name: row.Name})
RETURN count(e);
加载 Company 节点
LOAD CSV WITH HEADERS FROM 'file:///people.csv' AS row
MERGE (c:Company {companyId: row.companyId})
RETURN count(c);
创建关系
LOAD CSV WITH HEADERS FROM 'file:///people.csv' AS row
MATCH (e:Employee {employeeId: row.employeeId, name: row.Name})
MATCH (c:Company {companyId: row.companyId})
MERGE (e)-[:WORKS_FOR]->(c)
RETURN *

通过这种方式,加载过程一次只处理导入的一部分,可以快速高效地处理大量数据,减少繁重的处理负担。

最终得到的结果是这个图: