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

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

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

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

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