导出为 Cypher 脚本

“导出为 Cypher”过程将数据导出为 Cypher 语句,这些语句随后可用于将数据导入到另一个 Neo4j 实例中。

在导出节点时,如果某个节点标签不包含唯一约束,导出器会为这些节点添加 UNIQUE IMPORT LABEL 标签和 UNIQUE IMPORT ID 属性,以确保导出脚本在新的数据库中执行时节点的唯一性。导出脚本的最后一步会移除 UNIQUE IMPORT LABEL 标签和 UNIQUE IMPORT ID,因此当脚本执行完毕后,这些标签和属性将不会在新的数据库中存在。

如果节点标签确实具有唯一约束,则将使用定义了唯一约束的属性来确保唯一性。

可用过程

下表描述了可用的过程:

限定名称 类型

apoc.export.cypher.all
apoc.export.cypher.all(file STRING, config MAP<STRING, ANY>) - 将整个数据库(包括索引)以 Cypher 语句形式导出到提供的文件中(默认:Cypher Shell)。

过程

apoc.export.cypher.data
apoc.export.cypher.data(nodes LIST<NODE>, rels LIST<RELATIONSHIP>, file STRING, config MAP<STRING, ANY>) - 将给定的 NODERELATIONSHIP 值(包括索引)以 Cypher 语句形式导出到提供的文件中(默认:Cypher Shell)。

过程

apoc.export.cypher.graph
apoc.export.cypher.graph(graph MAP<STRING, ANY>, file STRING, config MAP<STRING, ANY>) - 将给定的图形(包括索引)以 Cypher 语句形式导出到提供的文件中(默认:Cypher Shell)。

过程

apoc.export.cypher.query
apoc.export.cypher.query(statement STRING, file STRING, config MAP<STRING, ANY>) - 将给定 Cypher 查询的 NODERELATIONSHIP 值(包括索引)以 Cypher 语句形式导出到提供的文件中(默认:Cypher Shell)。

过程

apoc.export.cypher.schema
apoc.export.cypher.schema(file STRING, config MAP<STRING, ANY>) - 将所有模式索引和约束导出为 Cypher 语句。

过程

导出的标签按字母顺序排列。labels() 函数的输出不是排序的,请将其与 apoc.coll.sort() 结合使用。

配置参数

该过程支持以下配置参数

表 1. 配置参数
名称 (name) type 默认 description(描述)

format

STRING

cypher-shell

导出格式。支持以下值:

  • cypher-shell - 用于通过 Cypher Shell 导入

  • neo4j-shell - 用于通过 Neo4j Shell 导入

  • plain - 导出纯 Cypher,不含 begincommitawait 命令。用于通过 Neo4j Browser 导入

cypherFormat

STRING

create

Cypher 更新操作类型。支持以下值:

  • create - 仅使用 CREATE 子句

  • updateAll - 使用 MERGE 代替 CREATE

  • addStructure - 对节点使用 MATCH,对关系使用 MERGE

  • updateStructure - 对节点和关系均使用 MERGEMATCH

useOptimizations

MAP

{type: "UNWIND_BATCH", unwindBatchSize: 20}

用于生成 Cypher 语句的优化配置。type 支持以下值:

  • NONE - 使用 CREATE 语句导出文件

  • UNWIND_BATCH - 通过 UNWIND 方法批量导出实体,详情请参考 Michael Hunger 关于快速批量写入的文章。

  • UNWIND_BATCH_PARAMS - 与 UNWIND_BATCH 类似,但在适当的情况下也会使用参数

awaitForIndexes

INTEGER(整数)

300

当使用 format: "cypher-shell" 时,用于 db.awaitIndexes 的超时时间

saveIndexNames

布尔值 (BOOLEAN)

false

导出时保存名称索引

saveConstraintNames

布尔值 (BOOLEAN)

false

导出时保存名称约束

multipleRelationshipsWithType

布尔值 (BOOLEAN)

false

如果两个 NODE 值之间存在多种相同类型的关系,请添加 UNIQUE IMPORT ID REL 属性,以便在使用 MERGE 时对它们进行区分。

导出到文件

默认情况下,导出到文件系统是被禁用的。我们可以通过在 apoc.conf 中设置以下属性来启用它:

apoc.conf
apoc.export.file.enabled=true

有关访问 apoc.conf 的更多信息,请参阅配置选项章节。

如果我们尝试在未先设置此属性的情况下使用任何导出过程,我们将收到以下错误消息:

Failed to invoke procedure: Caused by: java.lang.RuntimeException: Export to files not enabled, please set apoc.export.file.enabled=true in your apoc.conf. Otherwise, if you are running in a cloud environment without filesystem access, use the {stream:true} config and null as a 'file' parameter to stream the export back to your client.

导出文件被写入 import 目录,该目录由 server.directories.import 属性定义。这意味着我们提供的任何文件路径都是相对于此目录的。如果我们尝试写入绝对路径(例如 /tmp/filename),我们将收到类似于以下内容的错误消息:

Failed to invoke procedure: Caused by: java.io.FileNotFoundException: /path/to/neo4j/import/tmp/fileName (No such file or directory)

我们可以通过在 apoc.conf 中设置以下属性来允许写入文件系统上的任何位置:

apoc.conf
apoc.import.file.use_neo4j_config=false

Neo4j 现在将能够写入文件系统上的任何位置,因此在设置此属性之前,请确保这是您的意图。

导出数据流

如果我们不想导出到文件,可以通过将文件名设为 null 来流式传输结果。

默认情况下,所有的 Cypher 语句将会在 cypherStatements 列的单行中返回。

以下语句将整个数据库导出为一行
CALL apoc.export.cypher.all(null);

如果我们正在导出大型数据库,可以通过提供 streamStatements:true 配置并设置 batchSize 配置,将这些语句分批显示在多行中。

以下示例根据批处理大小将整个数据库导出到多行中
CALL apoc.export.cypher.all(null, {
    streamStatements: true,
    batchSize: 100
});

示例

本节包含演示如何使用“导出为 Cypher”过程的示例。这些示例基于电影数据集,可以通过运行以下 Cypher 查询来导入:

CREATE (TheMatrix:Movie {title:'The Matrix', released:1999, tagline:'Welcome to the Real World'})
CREATE (Keanu:Person {name:'Keanu Reeves', born:1964})
CREATE (Carrie:Person {name:'Carrie-Anne Moss', born:1967})
CREATE (Laurence:Person {name:'Laurence Fishburne', born:1961})
CREATE (Hugo:Person {name:'Hugo Weaving', born:1960})
CREATE (LillyW:Person {name:'Lilly Wachowski', born:1967})
CREATE (LanaW:Person {name:'Lana Wachowski', born:1965})
CREATE (JoelS:Person {name:'Joel Silver', born:1952})
CREATE
(Keanu)-[:ACTED_IN {roles:['Neo']}]->(TheMatrix),
(Carrie)-[:ACTED_IN {roles:['Trinity']}]->(TheMatrix),
(Laurence)-[:ACTED_IN {roles:['Morpheus']}]->(TheMatrix),
(Hugo)-[:ACTED_IN {roles:['Agent Smith']}]->(TheMatrix),
(LillyW)-[:DIRECTED]->(TheMatrix),
(LanaW)-[:DIRECTED]->(TheMatrix),
(JoelS)-[:PRODUCED]->(TheMatrix);

下方的 Neo4j Browser 可视化显示了导入的图

导出为 Cypher Shell 格式

默认情况下,由“导出为 Cypher”过程生成的 Cypher 语句采用 Cypher Shell 格式。

以下查询使用默认的 UNWIND_BATCH 优化,以默认的 cypher-shell 格式将整个数据库导出到 all.cypher
// default config populated for illustration
CALL apoc.export.cypher.all("all.cypher", {
    format: "cypher-shell",
    useOptimizations: {type: "UNWIND_BATCH", unwindBatchSize: 20}
})
YIELD file, batches, source, format, nodes, relationships, properties, time, rows, batchSize
RETURN file, batches, source, format, nodes, relationships, properties, time, rows, batchSize;
表 2. 结果
file batches source format 节点 relationships 属性 time rows batchSize

"all.cypher"

1

"database: nodes(8), rels(7)"

"cypher"

8

7

21

10

15

20000

all.cypher 的内容(为增强可读性已添加额外行)如下所示:

all.cypher
:begin
CREATE CONSTRAINT uniqueConstraint FOR (node:`UNIQUE IMPORT LABEL`) REQUIRE (node.`UNIQUE IMPORT ID`) IS UNIQUE;
:commit

:begin
UNWIND [{_id:0, properties:{tagline:"Welcome to the Real World", title:"The Matrix", released:1999}}] AS row
CREATE (n:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row._id}) SET n += row.properties SET n:Movie;

UNWIND [{_id:1, properties:{born:1964, name:"Keanu Reeves"}}, {_id:2, properties:{born:1967, name:"Carrie-Anne Moss"}}, {_id:3, properties:{born:1961, name:"Laurence Fishburne"}}, {_id:4, properties:{born:1960, name:"Hugo Weaving"}}, {_id:5, properties:{born:1967, name:"Lilly Wachowski"}}, {_id:6, properties:{born:1965, name:"Lana Wachowski"}}, {_id:7, properties:{born:1952, name:"Joel Silver"}}] AS row
CREATE (n:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row._id}) SET n += row.properties SET n:Person;
:commit

:begin
UNWIND [{start: {_id:1}, end: {_id:0}, properties:{roles:["Neo"]}}, {start: {_id:2}, end: {_id:0}, properties:{roles:["Trinity"]}}, {start: {_id:3}, end: {_id:0}, properties:{roles:["Morpheus"]}}, {start: {_id:4}, end: {_id:0}, properties:{roles:["Agent Smith"]}}] AS row
MATCH (start:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row.start._id})
MATCH (end:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row.end._id})
CREATE (start)-[r:ACTED_IN]->(end) SET r += row.properties;

UNWIND [{start: {_id:7}, end: {_id:0}, properties:{}}] AS row
MATCH (start:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row.start._id})
MATCH (end:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row.end._id})
CREATE (start)-[r:PRODUCED]->(end) SET r += row.properties;

UNWIND [{start: {_id:5}, end: {_id:0}, properties:{}}, {start: {_id:6}, end: {_id:0}, properties:{}}] AS row
MATCH (start:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row.start._id})
MATCH (end:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row.end._id})
CREATE (start)-[r:DIRECTED]->(end) SET r += row.properties;
:commit

:begin
MATCH (n:`UNIQUE IMPORT LABEL`)  WITH n LIMIT 20000 REMOVE n:`UNIQUE IMPORT LABEL` REMOVE n.`UNIQUE IMPORT ID`;
:commit

:begin
DROP CONSTRAINT uniqueConstraint;
:commit

此 Cypher 脚本执行 5 个事务,每个事务均由 :begin:commit 命令包裹。这些事务执行以下操作:

  1. UNIQUE IMPORT LABEL 标签和 UNIQUE IMPORT ID 属性上创建唯一约束

  2. 导入 PersonMovie 节点

  3. 在这些节点之间创建 ACTED_INPRODUCEDDIRECTED 关系

  4. 从节点中移除 UNIQUE IMPORT LABEL 标签和 UNIQUE IMPORT ID 属性

  5. 删除 UNIQUE IMPORT LABEL 标签和 UNIQUE IMPORT ID 属性上的唯一约束

此脚本可以使用 Cypher Shell 命令行工具执行。

例如,我们可以通过运行以下命令将 all.cypher 的内容导入到 Neo4j Aura 数据库中:

cat all.cypher | ./bin/cypher-shell -a <bolt-url> -u neo4j -p <password> --format verbose

别忘了将 <bolt-url> 和 <password> 替换为相应的凭据。

如果我们针对空数据库运行此命令,将看到以下输出:

0 rows available after 70 ms, consumed after another 0 ms
Added 1 constraints
0 rows available after 16 ms, consumed after another 0 ms
Added 2 nodes, Set 8 properties, Added 4 labels
0 rows available after 40 ms, consumed after another 0 ms
Added 14 nodes, Set 42 properties, Added 28 labels
0 rows available after 51 ms, consumed after another 0 ms
Created 8 relationships, Set 8 properties
0 rows available after 38 ms, consumed after another 0 ms
Created 2 relationships
0 rows available after 38 ms, consumed after another 0 ms
Created 4 relationships
0 rows available after 20 ms, consumed after another 0 ms
Set 16 properties, Removed 16 labels
0 rows available after 3 ms, consumed after another 0 ms
Removed 1 constraints
故障排除

如果您在试验导入过程中遇到失败,可以添加 --debug 命令行参数,以查看最后执行并导致失败的语句。

同时检查 Neo4j 实例的内存配置,您可能需要通过 neo4j.conf 中的 dbms.memory.heap.max_size=2G 设置将堆(HEAP)大小增加到 2–4GB

我们也可以通过在命令前添加 JAVA_OPTS=-Xmx4G bin/cypher-shell … 来为 cypher-shell 本身提供更多内存。

如果我们没有文件系统访问权限,或者因其他原因不想写入文件,我们可以流式返回导出语句。

以下查询在 cypherStatements 列中流式返回整个数据库
CALL apoc.export.cypher.all(null, {
    batchSize: 5,
    streamStatements: true,
    format: "cypher-shell",
    useOptimizations: {type: "UNWIND_BATCH", unwindBatchSize: 5}
})
YIELD nodes, relationships, properties, cypherStatements
RETURN nodes, relationships, properties, cypherStatements;
表 3. 结果
节点 relationships 属性 cypherStatements

16

0

34

":begin CREATE CONSTRAINT uniqueConstraint FOR (node:`UNIQUE IMPORT LABEL`) REQUIRE (node.UNIQUE IMPORT ID) IS UNIQUE; :commit :begin UNWIND [{_id:0, properties:{tagline:\"Welcome to the Real World\", title:\"The Matrix\", released:1999}}, {_id:1, properties:{tagline:\"Welcome to the Real World\", title:\"The Matrix\", released:1999}}] AS row CREATE (n:`UNIQUE IMPORT LABEL`{UNIQUE IMPORT ID: row._id}) SET n += row.properties SET n:Movie; UNWIND [{_id:35, properties:{born:1967, name:\"Carrie-Anne Moss\"}}, {_id:36, properties:{born:1961, name:\"Laurence Fishburne\"}}, {_id:37, properties:{born:1965, name:\"Lana Wachowski\"}}] AS row CREATE (n:`UNIQUE IMPORT LABEL`{UNIQUE IMPORT ID: row._id}) SET n += row.properties SET n:Person; :commit :begin UNWIND [{_id:38, properties:{born:1964, name:\"Keanu Reeves\"}}, {_id:39, properties:{born:1952, name:\"Joel Silver\"}}, {_id:40, properties:{born:1960, name:\"Hugo Weaving\"}}, {_id:41, properties:{born:1967, name:\"Lilly Wachowski\"}}, {_id:42, properties:{born:1967, name:\"Carrie-Anne Moss\"}}] AS row CREATE (n:`UNIQUE IMPORT LABEL`{UNIQUE IMPORT ID: row._id}) SET n += row.properties SET n:Person; :commit :begin UNWIND [{_id:43, properties:{born:1965, name:\"Lana Wachowski\"}}, {_id:50, properties:{born:1960, name:\"Hugo Weaving\"}}, {_id:51, properties:{born:1964, name:\"Keanu Reeves\"}}, {_id:57, properties:{born:1967, name:\"Lilly Wachowski\"}}, {_id:58, properties:{born:1961, name:\"Laurence Fishburne\"}}] AS row CREATE (n:`UNIQUE IMPORT LABEL`{UNIQUE IMPORT ID: row._id}) SET n += row.properties SET n:Person; :commit :begin UNWIND [{_id:59, properties:{born:1952, name:\"Joel Silver\"}}] AS row CREATE (n:`UNIQUE IMPORT LABEL`{UNIQUE IMPORT ID: row._id}) SET n += row.properties SET n:Person; :commit "

16

14

42

":begin UNWIND [{start: {_id:35}, end: {_id:0}, properties:{roles:[\"Trinity\"]}}, {start: {_id:36}, end: {_id:0}, properties:{roles:[\"Morpheus\"]}}, {start: {_id:50}, end: {_id:1}, properties:{roles:[\"Agent Smith\"]}}, {start: {_id:40}, end: {_id:0}, properties:{roles:[\"Agent Smith\"]}}, {start: {_id:51}, end: {_id:1}, properties:{roles:[\"Neo\"]}}] AS row MATCH (start:`UNIQUE IMPORT LABEL`{UNIQUE IMPORT ID: row.start._id}) MATCH (end:`UNIQUE IMPORT LABEL`{UNIQUE IMPORT ID: row.end._id}) CREATE (start)-[r:ACTED_IN]→(end) SET r += row.properties; :commit :begin UNWIND [{start: {_id:42}, end: {_id:1}, properties:{roles:[\"Trinity\"]}}, {start: {_id:38}, end: {_id:0}, properties:{roles:[\"Neo\"]}}, {start: {_id:58}, end: {_id:1}, properties:{roles:[\"Morpheus\"]}}] AS row MATCH (start:`UNIQUE IMPORT LABEL`{UNIQUE IMPORT ID: row.start._id}) MATCH (end:`UNIQUE IMPORT LABEL`{UNIQUE IMPORT ID: row.end._id}) CREATE (start)-[r:ACTED_IN]→(end) SET r += row.properties; UNWIND [{start: {_id:59}, end: {_id:1}, properties:{}}, {start: {_id:39}, end: {_id:0}, properties:{}}] AS row MATCH (start:`UNIQUE IMPORT LABEL`{UNIQUE IMPORT ID: row.start._id}) MATCH (end:`UNIQUE IMPORT LABEL`{UNIQUE IMPORT ID: row.end._id}) CREATE (start)-[r:PRODUCED]→(end) SET r += row.properties; :commit :begin UNWIND [{start: {_id:37}, end: {_id:0}, properties:{}}, {start: {_id:57}, end: {_id:0}, properties:{}}, {start: {_id:43}, end: {_id:1}, properties:{}}, {start: {_id:41}, end: {_id:1}, properties:{}}] AS row MATCH (start:`UNIQUE IMPORT LABEL`{UNIQUE IMPORT ID: row.start._id}) MATCH (end:`UNIQUE IMPORT LABEL`{UNIQUE IMPORT ID: row.end._id}) CREATE (start)-[r:DIRECTED]→(end) SET r += row.properties; :commit "

16

14

42

":begin MATCH (n:`UNIQUE IMPORT LABEL`) WITH n LIMIT 5 REMOVE n:`UNIQUE IMPORT LABEL` REMOVE n.UNIQUE IMPORT ID; :commit :begin MATCH (n:`UNIQUE IMPORT LABEL`) WITH n LIMIT 5 REMOVE n:`UNIQUE IMPORT LABEL` REMOVE n.UNIQUE IMPORT ID; :commit :begin MATCH (n:`UNIQUE IMPORT LABEL`) WITH n LIMIT 5 REMOVE n:`UNIQUE IMPORT LABEL` REMOVE n.UNIQUE IMPORT ID; :commit :begin MATCH (n:`UNIQUE IMPORT LABEL`) WITH n LIMIT 5 REMOVE n:`UNIQUE IMPORT LABEL` REMOVE n.UNIQUE IMPORT ID; :commit :begin DROP CONSTRAINT uniqueConstraint; :commit "

我们可以将 cypherStatements 列的内容(不包括双引号)复制/粘贴到 Cypher Shell 会话中,或者放入本地文件中并流式传输到 Cypher Shell 会话中。

导出为 Neo4j Browser 友好格式

“导出为 Cypher”过程支持 format: "plain" 配置,这对于后续使用 Neo4j Browser 导入非常有用。

以下查询将整个数据库导出到 all-plain.cypher
CALL apoc.export.cypher.all("all-plain.cypher", {
    format: "plain",
    useOptimizations: {type: "UNWIND_BATCH", unwindBatchSize: 20}
})
YIELD file, batches, source, format, nodes, relationships, properties, time, rows, batchSize
RETURN file, batches, source, format, nodes, relationships, properties, time, rows, batchSize;
表 4. 结果
file batches source format 节点 relationships 属性 time rows batchSize

"all-plain.cypher"

1

"database: nodes(8), rels(7)"

"cypher"

8

7

21

9

15

20000

all-plain.cypher 的内容(为增强可读性已添加额外行)如下所示:

all-plain.cypher
CREATE CONSTRAINT uniqueConstraint FOR (node:`UNIQUE IMPORT LABEL`) REQUIRE (node.`UNIQUE IMPORT ID`) IS UNIQUE;

UNWIND [{_id:0, properties:{tagline:"Welcome to the Real World", title:"The Matrix", released:1999}}] AS row
CREATE (n:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row._id}) SET n += row.properties SET n:Movie;

UNWIND [{_id:1, properties:{born:1964, name:"Keanu Reeves"}}, {_id:2, properties:{born:1967, name:"Carrie-Anne Moss"}}, {_id:3, properties:{born:1961, name:"Laurence Fishburne"}}, {_id:4, properties:{born:1960, name:"Hugo Weaving"}}, {_id:5, properties:{born:1967, name:"Lilly Wachowski"}}, {_id:6, properties:{born:1965, name:"Lana Wachowski"}}, {_id:7, properties:{born:1952, name:"Joel Silver"}}] AS row
CREATE (n:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row._id}) SET n += row.properties SET n:Person;

UNWIND [{start: {_id:1}, end: {_id:0}, properties:{roles:["Neo"]}}, {start: {_id:2}, end: {_id:0}, properties:{roles:["Trinity"]}}, {start: {_id:3}, end: {_id:0}, properties:{roles:["Morpheus"]}}, {start: {_id:4}, end: {_id:0}, properties:{roles:["Agent Smith"]}}] AS row
MATCH (start:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row.start._id})
MATCH (end:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row.end._id})
CREATE (start)-[r:ACTED_IN]->(end) SET r += row.properties;

UNWIND [{start: {_id:7}, end: {_id:0}, properties:{}}] AS row
MATCH (start:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row.start._id})
MATCH (end:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row.end._id})
CREATE (start)-[r:PRODUCED]->(end) SET r += row.properties;

UNWIND [{start: {_id:5}, end: {_id:0}, properties:{}}, {start: {_id:6}, end: {_id:0}, properties:{}}] AS row
MATCH (start:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row.start._id})
MATCH (end:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row.end._id})
CREATE (start)-[r:DIRECTED]->(end) SET r += row.properties;

MATCH (n:`UNIQUE IMPORT LABEL`)  WITH n LIMIT 20000 REMOVE n:`UNIQUE IMPORT LABEL` REMOVE n.`UNIQUE IMPORT ID`;

DROP CONSTRAINT uniqueConstraint;

然后,我们可以获取 all-plain.cypher 文件并将其拖动到 Neo4j Browser 窗口中。接着应该会看到以下提示:

export cypher plain drag
图 1. 将文件拖入 Neo4j Browser 时的提示

如果我们点击 Paste in editor(粘贴到编辑器),文件内容将会出现在查询编辑器中。

export cypher plain editor
图 2. 包含 all-plain.cypher 内容的 Neo4j Browser 查询编辑器

然后我们可以点击编辑器旁边的播放按钮,数据即会被导入。

使用不同的 Cypher 更新格式导出

“导出为 Cypher”过程使用 CREATEMATCHMERGE 子句生成 Cypher 语句。格式通过 cypherFormat 参数进行配置。支持以下值:

  • create - 仅使用 CREATE 子句(默认)

  • updateAll - 使用 MERGE 代替 CREATE

  • addStructure - 对节点使用 MATCH,对关系使用 MERGE

  • updateStructure - 对节点和关系均使用 MERGEMATCH

如果这是我们第一次导出数据库,应该使用默认的 create 格式,但对于后续的导出,其他格式可能更合适。

以下示例使用 create 格式将 ACTED_IN 关系及周边节点导出到 export-cypher-format-create.cypher
MATCH (person)-[r:ACTED_IN]->(movie)
WITH collect(DISTINCT person) + collect(DISTINCT  movie) AS importNodes, collect(r) AS importRels
CALL apoc.export.cypher.data(importNodes, importRels,
  "export-cypher-format-create.cypher",
  { format: "plain", cypherFormat: "create" })
YIELD file, batches, source, format, nodes, relationships, properties, time, rows, batchSize
RETURN file, batches, source, format, nodes, relationships, properties, time, rows, batchSize;
表 5. 结果
file batches source format 节点 relationships 属性 time rows batchSize

"export-cypher-format-create.cypher"

1

"data: nodes(5), rels(4)"

"cypher"

5

4

15

2

9

20000

export-cypher-format-create.cypher
CREATE CONSTRAINT uniqueConstraint FOR (node:`UNIQUE IMPORT LABEL`) REQUIRE (node.`UNIQUE IMPORT ID`) IS UNIQUE;
UNWIND [{_id:0, properties:{tagline:"Welcome to the Real World", title:"The Matrix", released:1999}}] AS row
CREATE (n:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row._id}) SET n += row.properties SET n:Movie;

UNWIND [{_id:7, properties:{born:1967, name:"Carrie-Anne Moss"}},
        {_id:80, properties:{born:1960, name:"Hugo Weaving"}},
        {_id:27, properties:{born:1964, name:"Keanu Reeves"}},
        {_id:44, properties:{born:1961, name:"Laurence Fishburne"}}] AS row
CREATE (n:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row._id}) SET n += row.properties SET n:Person;

UNWIND [{start: {_id:27}, end: {_id:0}, properties:{roles:["Neo"]}},
        {start: {_id:7}, end: {_id:0}, properties:{roles:["Trinity"]}},
        {start: {_id:44}, end: {_id:0}, properties:{roles:["Morpheus"]}},
        {start: {_id:80}, end: {_id:0}, properties:{roles:["Agent Smith"]}}] AS row
MATCH (start:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row.start._id})
MATCH (end:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row.end._id})
CREATE (start)-[r:ACTED_IN]->(end) SET r += row.properties;

MATCH (n:`UNIQUE IMPORT LABEL`)  WITH n LIMIT 20000 REMOVE n:`UNIQUE IMPORT LABEL` REMOVE n.`UNIQUE IMPORT ID`;
DROP CONSTRAINT uniqueConstraint;

所有图实体的创建均使用 Cypher CREATE 子句。如果这些实体在目标数据库中可能已经存在,我们可以选择使用其他格式。使用 cypherFormat: "updateAll" 意味着创建实体时将使用 MERGE 子句代替 CREATE

以下示例使用 updateAll 格式将 ACTED_IN 关系及周边节点导出到 export-cypher-format-updateAll.cypher
MATCH (person)-[r:ACTED_IN]->(movie)
WITH collect(DISTINCT person) + collect(DISTINCT  movie) AS importNodes, collect(r) AS importRels
CALL apoc.export.cypher.data(importNodes, importRels,
  "export-cypher-format-updateAll.cypher",
  { format: "plain", cypherFormat: "updateAll" })
YIELD file, batches, source, format, nodes, relationships, properties, time, rows, batchSize
RETURN file, batches, source, format, nodes, relationships, properties, time, rows, batchSize;
表 6. 结果
file batches source format 节点 relationships 属性 time rows batchSize

"export-cypher-format-updateAll.cypher"

1

"data: nodes(5), rels(4)"

"cypher"

5

4

15

8

9

20000

export-cypher-format-updateAll.cypher
CREATE CONSTRAINT uniqueConstraint FOR (node:`UNIQUE IMPORT LABEL`) REQUIRE (node.`UNIQUE IMPORT ID`) IS UNIQUE;
UNWIND [{_id:0, properties:{tagline:"Welcome to the Real World", title:"The Matrix", released:1999}}] AS row
MERGE (n:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row._id}) SET n += row.properties SET n:Movie;

UNWIND [{_id:80, properties:{born:1960, name:"Hugo Weaving"}},
        {_id:7, properties:{born:1967, name:"Carrie-Anne Moss"}},
        {_id:44, properties:{born:1961, name:"Laurence Fishburne"}},
        {_id:27, properties:{born:1964, name:"Keanu Reeves"}}] AS row
MERGE (n:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row._id}) SET n += row.properties SET n:Person;

UNWIND [{start: {_id:27}, end: {_id:0}, properties:{roles:["Neo"]}},
        {start: {_id:7}, end: {_id:0}, properties:{roles:["Trinity"]}},
        {start: {_id:44}, end: {_id:0}, properties:{roles:["Morpheus"]}},
        {start: {_id:80}, end: {_id:0}, properties:{roles:["Agent Smith"]}}] AS row
MATCH (start:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row.start._id})
MATCH (end:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row.end._id})
MERGE (start)-[r:ACTED_IN]->(end) SET r += row.properties;

MATCH (n:`UNIQUE IMPORT LABEL`)  WITH n LIMIT 20000 REMOVE n:`UNIQUE IMPORT LABEL` REMOVE n.`UNIQUE IMPORT ID`;
DROP CONSTRAINT uniqueConstraint;

如果我们已经在目标数据库中拥有了这些节点,可以使用 cypherFormat: "addStructure" 仅为关系创建 Cypher CREATE 语句。

以下示例使用 addStructure 格式将 ACTED_IN 关系及周边节点导出到 export-cypher-format-addStructure.cypher
MATCH (person)-[r:ACTED_IN]->(movie)
WITH collect(DISTINCT person) + collect(DISTINCT  movie) AS importNodes, collect(r) AS importRels
CALL apoc.export.cypher.data(importNodes, importRels,
  "export-cypher-format-addStructure.cypher",
  { format: "plain", cypherFormat: "addStructure" })
YIELD file, batches, source, format, nodes, relationships, properties, time, rows, batchSize
RETURN file, batches, source, format, nodes, relationships, properties, time, rows, batchSize;
表 7. 结果
file batches source format 节点 relationships 属性 time rows batchSize

"export-cypher-format-addStructure.cypher"

1

"data: nodes(5), rels(4)"

"cypher"

5

4

15

4

9

20000

export-cypher-format-addStructure.cypher
UNWIND [{_id:0, properties:{tagline:"Welcome to the Real World", title:"The Matrix", released:1999}}] AS row
MERGE (n:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row._id}) ON CREATE SET n += row.properties SET n:Movie;

UNWIND [{_id:7, properties:{born:1967, name:"Carrie-Anne Moss"}},
        {_id:27, properties:{born:1964, name:"Keanu Reeves"}},
        {_id:80, properties:{born:1960, name:"Hugo Weaving"}},
        {_id:44, properties:{born:1961, name:"Laurence Fishburne"}}] AS row
MERGE (n:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row._id}) ON CREATE SET n += row.properties SET n:Person;

UNWIND [{start: {_id:27}, end: {_id:0}, properties:{roles:["Neo"]}},
        {start: {_id:7}, end: {_id:0}, properties:{roles:["Trinity"]}},
        {start: {_id:44}, end: {_id:0}, properties:{roles:["Morpheus"]}},
        {start: {_id:80}, end: {_id:0}, properties:{roles:["Agent Smith"]}}] AS row
MATCH (start:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row.start._id})
MATCH (end:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row.end._id})
CREATE (start)-[r:ACTED_IN]->(end)  SET r += row.properties;

在此示例中,我们使用 MERGE 子句在节点不存在时创建节点,并仅在节点不存在时创建属性。在本例中,关系在目标数据库中不存在,因此需要被创建。

如果这些关系确实存在但属性需要更新,我们可以使用 cypherFormat: "updateStructure" 来创建我们的导入脚本。

以下示例使用 updateStructure 格式将 ACTED_IN 关系及周边节点导出到 export-cypher-format-updateStructure.cypher
MATCH (person)-[r:ACTED_IN]->(movie)
WITH collect(DISTINCT person) + collect(DISTINCT  movie) AS importNodes, collect(r) AS importRels
CALL apoc.export.cypher.data(importNodes, importRels,
  "export-cypher-format-updateStructure.cypher",
  { format: "plain", cypherFormat: "updateStructure" })
YIELD file, batches, source, format, nodes, relationships, properties, time, rows, batchSize
RETURN file, batches, source, format, nodes, relationships, properties, time, rows, batchSize;
表 8. 结果
file batches source format 节点 relationships 属性 time rows batchSize

"export-cypher-format-updateStructure.cypher"

1

"data: nodes(5), rels(4)"

"cypher"

0

4

4

2

4

20000

export-cypher-format-updateStructure.cypher
UNWIND [{start: {_id:27}, end: {_id:0}, properties:{roles:["Neo"]}},
        {start: {_id:7}, end: {_id:0}, properties:{roles:["Trinity"]}},
        {start: {_id:44}, end: {_id:0}, properties:{roles:["Morpheus"]}},
        {start: {_id:80}, end: {_id:0}, properties:{roles:["Agent Smith"]}}] AS row
MATCH (start:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row.start._id})
MATCH (end:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row.end._id})
MERGE (start)-[r:ACTED_IN]->(end) SET r += row.properties;

导出到多个文件或列

所有“导出为 Cypher”过程都支持写入到多个文件或多个列。我们可以通过传递 separateFiles: true 配置来启用此模式。

以下查询将所有的 ACTED_IN 关系及对应的节点导出到以 actedIn 为前缀的文件中
CALL apoc.export.cypher.query(
  "MATCH ()-[r:ACTED_IN]->()
   RETURN *",
  "actedIn.cypher",
  { format: "cypher-shell", separateFiles: true })
YIELD file, batches, source, format, nodes, relationships, time, rows, batchSize
RETURN file, batches, source, format, nodes, relationships, time, rows, batchSize;
表 9. 结果
file batches source format 节点 relationships time rows batchSize

"actedIn.cypher"

1

"statement: nodes(10), rels(8)"

"cypher"

10

8

3

18

20000

这将导致创建以下文件:

表 10. 结果
名称 字节大小 行数

actedIn.cleanup.cypher

234

6

actedIn.nodes.cypher

893

6

actedIn.relationships.cypher

757

6

actedIn.schema.cypher

109

3

这些文件中的每一个都包含图的特定部分。让我们看看它们的内容。

actedIn.cleanup.cypher
:begin
MATCH (n:`UNIQUE IMPORT LABEL`)  WITH n LIMIT 20000 REMOVE n:`UNIQUE IMPORT LABEL` REMOVE n.`UNIQUE IMPORT ID`;
:commit
:begin
DROP CONSTRAINT uniqueConstraint;
:commit
actedIn.nodes.cypher
:begin
UNWIND [{_id:28, properties:{tagline:"Welcome to the Real World", title:"The Matrix", released:1999}}, {_id:37, properties:{tagline:"Welcome to the Real World", title:"The Matrix", released:1999}}] AS row
CREATE (n:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row._id}) SET n += row.properties SET n:Movie;
UNWIND [{_id:31, properties:{born:1961, name:"Laurence Fishburne"}}, {_id:30, properties:{born:1967, name:"Carrie-Anne Moss"}}, {_id:42, properties:{born:1964, name:"Keanu Reeves"}}, {_id:0, properties:{born:1960, name:"Hugo Weaving"}}, {_id:29, properties:{born:1964, name:"Keanu Reeves"}}, {_id:38, properties:{born:1960, name:"Hugo Weaving"}}, {_id:43, properties:{born:1967, name:"Carrie-Anne Moss"}}, {_id:57, properties:{born:1961, name:"Laurence Fishburne"}}] AS row
CREATE (n:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row._id}) SET n += row.properties SET n:Person;
:commit
actedIn.relationships.cypher
:begin
UNWIND [{start: {_id:31}, end: {_id:28}, properties:{roles:["Morpheus"]}}, {start: {_id:42}, end: {_id:37}, properties:{roles:["Neo"]}}, {start: {_id:38}, end: {_id:37}, properties:{roles:["Agent Smith"]}}, {start: {_id:0}, end: {_id:28}, properties:{roles:["Agent Smith"]}}, {start: {_id:29}, end: {_id:28}, properties:{roles:["Neo"]}}, {start: {_id:43}, end: {_id:37}, properties:{roles:["Trinity"]}}, {start: {_id:30}, end: {_id:28}, properties:{roles:["Trinity"]}}, {start: {_id:57}, end: {_id:37}, properties:{roles:["Morpheus"]}}] AS row
MATCH (start:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row.start._id})
MATCH (end:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row.end._id})
CREATE (start)-[r:ACTED_IN]->(end) SET r += row.properties;
:commit
actedIn.schema.cypher
:begin
CREATE CONSTRAINT uniqueConstraint FOR (node:`UNIQUE IMPORT LABEL`) REQUIRE (node.`UNIQUE IMPORT ID`) IS UNIQUE;
:commit

我们可以将这些文件应用到我们的目标 Neo4j 实例中,可以通过将它们的内容流式传输到 Cypher Shell,或者使用 运行 Cypher 片段 中描述的过程。

当返回导出语句流时,我们也可以使用 separateFiles。结果将出现在名为 nodeStatementsrelationshipStatementscleanupStatementsschemaStatements 的列中,而不是 cypherStatements 列。

以下查询返回所有 ACTED_IN 关系及对应节点的流
CALL apoc.export.cypher.query(
  "MATCH ()-[r:ACTED_IN]->()
   RETURN *",
  null,
  { format: "cypher-shell", separateFiles: true })
YIELD nodes, relationships, properties, nodeStatements, relationshipStatements, cleanupStatements, schemaStatements
RETURN nodes, relationships, properties, nodeStatements, relationshipStatements, cleanupStatements, schemaStatements;
表 11. 结果
节点 relationships 属性 nodeStatements relationshipStatements cleanupStatements schemaStatements

10

8

30

":begin UNWIND [{_id:28, properties:{tagline:\"Welcome to the Real World\", title:\"The Matrix\", released:1999}}, {_id:37, properties:{tagline:\"Welcome to the Real World\", title:\"The Matrix\", released:1999}}] AS row CREATE (n:`UNIQUE IMPORT LABEL`{UNIQUE IMPORT ID: row._id}) SET n += row.properties SET n:Movie; UNWIND [{_id:0, properties:{born:1960, name:\"Hugo Weaving\"}}, {_id:42, properties:{born:1964, name:\"Keanu Reeves\"}}, {_id:31, properties:{born:1961, name:\"Laurence Fishburne\"}}, {_id:29, properties:{born:1964, name:\"Keanu Reeves\"}}, {_id:30, properties:{born:1967, name:\"Carrie-Anne Moss\"}}, {_id:43, properties:{born:1967, name:\"Carrie-Anne Moss\"}}, {_id:38, properties:{born:1960, name:\"Hugo Weaving\"}}, {_id:57, properties:{born:1961, name:\"Laurence Fishburne\"}}] AS row CREATE (n:`UNIQUE IMPORT LABEL`{UNIQUE IMPORT ID: row._id}) SET n += row.properties SET n:Person; :commit "

":begin UNWIND [{start: {_id:31}, end: {_id:28}, properties:{roles:[\"Morpheus\"]}}, {start: {_id:38}, end: {_id:37}, properties:{roles:\"Agent Smith\"}}, {start: {_id:0}, end: {_id:28}, properties:{roles:[\"Agent Smith\"]}}, {start: {_id:30}, end: {_id:28}, properties:{roles:[\"Trinity\"]}}, {start: {_id:29}, end: {_id:28}, properties:{roles:[\"Neo\"]}}, {start: {_id:43}, end: {_id:37}, properties:{roles:[\"Trinity\"]}}, {start: {_id:42}, end: {_id:37}, properties:{roles:[\"Neo\"]}}, {start: {_id:57}, end: {_id:37}, properties:{roles:[\"Morpheus\"]}}] AS row MATCH (start:`UNIQUE IMPORT LABEL`{UNIQUE IMPORT ID: row.start._id}) MATCH (end:`UNIQUE IMPORT LABEL`{UNIQUE IMPORT ID: row.end._id}) CREATE (start)-[r:ACTED_IN]→(end) SET r += row.properties; :commit "

":begin MATCH (n:`UNIQUE IMPORT LABEL`) WITH n LIMIT 20000 REMOVE n:`UNIQUE IMPORT LABEL` REMOVE n.UNIQUE IMPORT ID; :commit :begin DROP CONSTRAINT uniqueConstraint; :commit "

":begin CREATE CONSTRAINT uniqueConstraint FOR (node:`UNIQUE IMPORT LABEL`) REQUIRE (node.UNIQUE IMPORT ID) IS UNIQUE; :commit "

我们可以将每一列的内容(不包括双引号)复制/粘贴到 Cypher Shell 会话中,或者放入本地文件中并流式传输到 Cypher Shell 会话中。如果我们希望导出可以粘贴到 Neo4j Browser 查询编辑器中的 Cypher 语句,我们需要使用 format: "plain" 配置,如 导出为 Neo4j Browser 友好格式 中所述。

导出具有多种相同类型的关系

对于以下数据集:

create (pers:Person {name: 'MyName'})-[:WORKS_FOR {id: 1}]->(proj:Project {a: 1}),
    (pers)-[:WORKS_FOR {id: 2}]->(proj),
    (pers)-[:WORKS_FOR {id: 2}]->(proj),
    (pers)-[:WORKS_FOR {id: 3}]->(proj),
    (pers)-[:WORKS_FOR {id: 4}]->(proj),
    (pers)-[:WORKS_FOR {id: 5}]->(proj),
    (pers)-[:IS_TEAM_MEMBER_OF {name: 'aaa'}]->(:Team {name: 'one'}),
    (pers)-[:IS_TEAM_MEMBER_OF {name: 'eee'}]->(:Team {name: 'two'})

我们可以看到在 :Person:Project 节点之间,存在多个具有相同类型(WORKS_FOR)的关系。

在这种情况下,如果我们通过 MERGE 子句导出关系,必须使用配置 {multipleRelationshipsWithType: true},否则我们将无法区分它们,导出的脚本将只会创建一条 WORKS_FOR 关系。

例如,我们可以执行:

CALL apoc.export.cypher.all(null, {stream: true, multipleRelationshipsWithType: true}) YIELD cypherStatements
表 12. 结果
cypherStatements

":begin CREATE CONSTRAINT ON (node:`UNIQUE IMPORT LABEL`) ASSERT (node.`UNIQUE IMPORT ID`) IS UNIQUE; :commit CALL db.awaitIndexes(300); :begin UNWIND [{_id:1, properties:{a:1}}] AS row CREATE (n:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row._id}) SET n += row.properties SET n:Project; UNWIND [{_id:2, properties:{name:"one"}}, {_id:3, properties:{name:"two"}}] AS row CREATE (n:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row._id}) SET n += row.properties SET n:Team; UNWIND [{_id:0, properties:{name:"MyName"}}] AS row CREATE (n:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row._id}) SET n += row.properties SET n:Person; :commit :begin UNWIND [{start: {_id:0}, end: {_id:2}, properties:{name:"aaa"}}, {start: {_id:0}, end: {_id:3}, properties:{name:"eee"}}] AS row MATCH (start:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row.start._id}) MATCH (end:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row.end._id}) CREATE (start)-[r:IS_TEAM_MEMBER_OF]→(end) SET r += row.properties; UNWIND [{start: {_id:0}, id: 0, end: {_id:1}, properties:{id:1}}, {start: {_id:0}, id: 1, end: {_id:1}, properties:{id:2}}, {start: {_id:0}, id: 2, end: {_id:1}, properties:{id:2}}, {start: {_id:0}, id: 3, end: {_id:1}, properties:{id:3}}, {start: {_id:0}, id: 4, end: {_id:1}, properties:{id:4}}, {start: {_id:0}, id: 5, end: {_id:1}, properties:{id:5}}] AS row MATCH (start:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row.start._id}) MATCH (end:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row.end._id}) CREATE (start)-[r:WORKS_FOR{`UNIQUE IMPORT ID REL`:row.id}]→(end) SET r += row.properties; :commit :begin MATCH (n:`UNIQUE IMPORT LABEL`) WITH n LIMIT 20000 REMOVE n:`UNIQUE IMPORT LABEL` REMOVE n.`UNIQUE IMPORT ID`; :commit :begin DROP CONSTRAINT ON (node:`UNIQUE IMPORT LABEL`) ASSERT (node.`UNIQUE IMPORT ID`) IS UNIQUE; :commit "