重写 Cypher 查询以在 Neptune 上的 openCypher 中运行 - Amazon Neptune
Amazon Web Services 文档中描述的 Amazon Web Services 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅 中国的 Amazon Web Services 服务入门 (PDF)

本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。

重写 Cypher 查询以在 Neptune 上的 openCypher 中运行

openCypher 语言是一种用于属性图的声明式查询语言,最初由 Neo4j 开发,然后于 2015 年开源,并在 Apache 2 开源许可证下为 openCypher 项目做出了贡献。在 Amazon,我们相信开源对每个人都有好处,我们致力于将开源的价值带给我们的客户,为开源社区带来 Amazon 的运营卓越性。

openCypher 语法在 Cypher 查询语言参考版本 9 中介绍。

由于 openCypher 包含 Cypher 查询语言的语法和特征的子集,因此某些迁移方案需要以兼容 openCypher 的形式重写查询,或者研究其它方法来实现所需的特征。

本节包含处理常见差异的建议,但绝非详尽无遗。您应该使用这些重写对任何应用程序进行全面测试,以确保结果符合您的预期。

重写 NoneAllAny 谓词函数

这些函数不是 openCypher 规范的一部分。使用列表推导可以在 openCypher 中获得类似的结果。

例如,找到从节点 Start 到节点 End 的所有路径,但不允许任何旅程通过一个类属性为 D 的节点:

# Neo4J Cypher code match p=(a:Start)-[:HOP*1..]->(z:End) where none(node IN nodes(p) where node.class ='D') return p # Neptune openCypher code match p=(a:Start)-[:HOP*1..]->(z:End) where size([node IN nodes(p) where node.class = 'D']) = 0 return p

列表推导可以实现以下结果:

all => size(list_comprehension(list)) = size(list) any => size(list_comprehension(list)) >= 1 none => size(list_comprehension(list)) = 0

在 openCypher 中重写 Cypher reduce() 函数

reduce() 函数不是 openCypher 规范的一部分。它通常用于从列表中的元素创建数据的聚合。在许多情况下,您可以结合使用列表推导和 UNWIND 子句来获得相似的结果。

例如,以下 Cypher 查询查找安克雷奇 (ANC) 和奥斯汀 (AUS) 之间有一到三个停靠点的路径上的所有机场,并返回每条路径的总距离:

MATCH p=(a:airport {code: 'ANC'})-[r:route*1..3]->(z:airport {code: 'AUS'}) RETURN p, reduce(totalDist=0, r in relationships(p) | totalDist + r.dist) AS totalDist ORDER BY totalDist LIMIT 5

您可以在 openCypher 中为 Neptune 编写相同的查询,如下所示:

MATCH p=(a:airport {code: 'ANC'})-[r:route*1..3]->(z:airport {code: 'AUS'}) UNWIND [i in relationships(p) | i.dist] AS di RETURN p, sum(di) AS totalDist ORDER BY totalDist LIMIT 5

在 openCypher 中重写 Cypher FOREACH 子句

FOREACH 子句不是 openCypher 规范的一部分。它通常用于在查询过程中更新数据,通常来自路径中的聚合或元素。

作为路径示例,找到安克雷奇 (ANC) 和奥斯汀 (AUS) 之间不超过两站的路径上的所有机场,并在每个机场上设置一个已访问的属性:

# Neo4J Example MATCH p=(:airport {code: 'ANC'})-[*1..2]->({code: 'AUS'}) FOREACH (n IN nodes(p) | SET n.visited = true) # Neptune openCypher MATCH p=(:airport {code: 'ANC'})-[*1..2]->({code: 'AUS'}) WITH nodes(p) as airports UNWIND airports as a SET a.visited=true

另一个示例是:

# Neo4J Example MATCH p=(start)-[*]->(finish) WHERE start.name = 'A' AND finish.name = 'D' FOREACH (n IN nodes(p) | SET n.marked = true) # Neptune openCypher MATCH p=(start)-[*]->(finish) WHERE start.name = 'A' AND finish.name = 'D' UNWIND nodes(p) AS n SET n.marked = true

在 Neptune 中重写 Neo4j APOC 过程

以下示例使用 openCypher 来取代一些最常用的 APOC 过程。这些示例仅供参考,旨在提供一些有关如何处理常见场景的建议。实际上,每个应用程序都是不同的,您必须制定自己的策略来提供所需的所有功能。

重写 apoc.export 过程

Neptune 使用 neptune-export 实用程序为完整图形和基于查询的导出提供了一系列选项,且采用 CSV 和 JSON 等各种输出格式(请参阅从 Neptune 数据库集群中导出数据)。

重写 apoc.schema 过程

Neptune 没有显式定义的架构、索引或约束,因此不再需要许多 apoc.schema 过程。示例为:

  • apoc.schema.assert

  • apoc.schema.node.constraintExists

  • apoc.schema.node.indexExists,

  • apoc.schema.relationship.constraintExists

  • apoc.schema.relationship.indexExists

  • apoc.schema.nodes

  • apoc.schema.relationships

Neptune openCypher 确实支持检索与这些过程检索的值相似的值,如下所示,但在较大的图形上可能会遇到性能问题,因为这样做需要扫描图形的很大一部分才能返回答案。

# openCypher replacement for apoc.schema.properties.distinct MATCH (n:airport) RETURN DISTINCT n.runways
# openCypher replacement for apoc.schema.properties.distinctCount MATCH (n:airport) RETURN DISTINCT n.runways, count(n.runways)

apoc.do 过程的替代方案

这些过程用于提供条件查询执行,这使用其它 openCypher 子句即可轻松实现。在 Neptune 中,至少有两种方法可以实现类似的行为:

  • 一种方法是将 openCypher 的列表推导功能与子句 UNWIND 结合起来。

  • 另一种方法是使用 Gremlin 中的 choose() 和 coalesce() 步骤。

这些方法的示例如下所示。

apoc.do.when 的替代方案

# Neo4J Example MATCH (n:airport {region: 'US-AK'}) CALL apoc.do.when( n.runways>=3, 'SET n.is_large_airport=true RETURN n', 'SET n.is_large_airport=false RETURN n', {n:n} ) YIELD value WITH collect(value.n) as airports RETURN size([a in airports where a.is_large_airport]) as large_airport_count, size([a in airports where NOT a.is_large_airport]) as small_airport_count # Neptune openCypher MATCH (n:airport {region: 'US-AK'}) WITH n.region as region, collect(n) as airports WITH [a IN airports where a.runways >= 3] as large_airports, [a IN airports where a.runways < 3] as small_airports, airports UNWIND large_airports as la SET la.is_large_airport=true WITH DISTINCT small_airports, airports UNWIND small_airports as la SET la.small_airports=true WITH DISTINCT airports RETURN size([a in airports where a.is_large_airport]) as large_airport_count, size([a in airports where NOT a.is_large_airport]) as small_airport_count #Neptune Gremlin using choose() g.V(). has('airport', 'region', 'US-AK'). choose( values('runways').is(lt(3)), property(single, 'is_large_airport', false), property(single, 'is_large_airport', true)). fold(). project('large_airport_count', 'small_airport_count'). by(unfold().has('is_large_airport', true).count()). by(unfold().has('is_large_airport', false).count()) #Neptune Gremlin using coalesce() g.V(). has('airport', 'region', 'US-AK'). coalesce( where(values('runways').is(lt(3))). property(single, 'is_large_airport', false), property(single, 'is_large_airport', true)). fold(). project('large_airport_count', 'small_airport_count'). by(unfold().has('is_large_airport', true).count()). by(unfold().has('is_large_airport', false).count())

apoc.do.case 的替代方案

# Neo4J Example MATCH (n:airport {region: 'US-AK'}) CALL apoc.case([ n.runways=1, 'RETURN "Has one runway" as b', n.runways=2, 'RETURN "Has two runways" as b' ], 'RETURN "Has more than 2 runways" as b' ) YIELD value RETURN {type: value.b,airport: n} # Neptune openCypher MATCH (n:airport {region: 'US-AK'}) WITH n.region as region, collect(n) as airports WITH [a IN airports where a.runways =1] as single_runway, [a IN airports where a.runways =2] as double_runway, [a IN airports where a.runways >2] as many_runway UNWIND single_runway as sr WITH {type: "Has one runway",airport: sr} as res, double_runway, many_runway WITH DISTINCT double_runway as double_runway, collect(res) as res, many_runway UNWIND double_runway as dr WITH {type: "Has two runways",airport: dr} as two_runways, res, many_runway WITH collect(two_runways)+res as res, many_runway UNWIND many_runway as mr WITH {type: "Has more than 2 runways",airport: mr} as res2, res, many_runway WITH collect(res2)+res as res UNWIND res as r RETURN r #Neptune Gremlin using choose() g.V(). has('airport', 'region', 'US-AK'). project('type', 'airport'). by( choose(values('runways')). option(1, constant("Has one runway")). option(2, constant("Has two runways")). option(none, constant("Has more than 2 runways"))). by(elementMap()) #Neptune Gremlin using coalesce() g.V(). has('airport', 'region', 'US-AK'). project('type', 'airport'). by( coalesce( has('runways', 1).constant("Has one runway"), has('runways', 2).constant("Has two runways"), constant("Has more than 2 runways"))). by(elementMap())

基于列表的属性的替代方案

Neptune 目前不支持存储基于列表的属性。但是,通过将列表值存储为逗号分隔的字符串,然后使用 join()split() 函数来构造和解构列表属性,也可以获得类似的结果。

例如,如果我们想将标签列表另存为属性,我们可以使用示例重写,它演示如何检索逗号分隔的属性,然后使用 split()join() 函数以及列表推导来获得同等的结果:

# Neo4j Example (In this example, tags is a durable list of string. MATCH (person:person {name: "TeeMan"}) WITH person, [tag in person.tags WHERE NOT (tag IN ['test1', 'test2', 'test3'])] AS newTags SET person.tags = newTags RETURN person # Neptune openCypher MATCH (person:person {name: "TeeMan"}) WITH person, [tag in split(person.tags, ',') WHERE NOT (tag IN ['test1', 'test2', 'test3'])] AS newTags SET person.tags = join(newTags,',') RETURN person