

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

# 重写 Cypher 查询以在 Neptune 上的 openCypher 中运行
<a name="migration-opencypher-rewrites"></a>

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

OpenCypher 语法记录在《[密码查询语言参考版本 9》中](https://s3.amazonaws.com/artifacts.opencypher.org/openCypher9.pdf)。

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

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

## 重写 `None`、`All` 和 `Any` 谓词函数
<a name="migration-opencypher-rewrites-none-all-any"></a>

这些函数不是 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()` 函数
<a name="migration-opencypher-rewrites-reduce"></a>

`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 子句
<a name="migration-opencypher-rewrites-foreach"></a>

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 过程
<a name="migration-opencypher-rewrites-apoc"></a>

以下示例使用 openCypher 来取代一些最常用的 [APOC 过程](https://neo4j.com/blog/intro-user-defined-procedures-apoc/)。这些示例仅供参考，旨在提供一些有关如何处理常见场景的建议。实际上，每个应用程序都是不同的，您必须制定自己的策略来提供所需的所有功能。

### 重写 `apoc.export` 过程
<a name="migration-opencypher-rewrites-apoc-export"></a>

Neptune 使用 [neptune-export](https://github.com/aws/neptune-export) 实用程序为完整图形和基于查询的导出提供了一系列选项，且采用 CSV 和 JSON 等各种输出格式（请参阅[从 Neptune 数据库集群中导出数据](neptune-data-export.md)）。

### 重写 `apoc.schema` 过程
<a name="migration-opencypher-rewrites-apoc-schema"></a>

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` 过程的替代方案
<a name="migration-opencypher-rewrites-apoc-do"></a>

这些过程用于提供条件查询执行，这使用其它 openCypher 子句即可轻松实现。在 Neptune 中，至少有两种方法可以实现类似的行为：
+ 一种方法是将 openCypher 的列表推导功能与子句 `UNWIND` 结合起来。
+ 另一种方法是使用 Gremlin 中的 choose() 和 coalesce() 步骤。

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

#### apoc.do.when 的替代方案
<a name="migration-opencypher-rewrites-apoc-do-when"></a>

```
# 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 的替代方案
<a name="migration-opencypher-rewrites-apoc-do-case"></a>

```
# 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())
```

## 基于列表的属性的替代方案
<a name="migration-opencypher-rewrites-lists"></a>

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
```

## 重写 CALL 子查询
<a name="migration-opencypher-rewrites-call-subqueries"></a>

 Neptune `CALL` 子查询不支持将变量导入子查询范围（在本示例中为 `friend`）的语法 `CALL (friend) { ... }`。请使用子查询中的 `WITH` 子句来达成这一目的，例如 `CALL { WITH friend ... }`。

 可选的 `CALL` 子查询目前不受支持。

## Neptune openCypher 和 Cypher 之间的其它区别
<a name="opencypher-compliance-other-differences"></a>
+ Neptune 仅支持 Bolt 协议的 TCP 连接。 WebSockets不支持螺栓连接。
+ Neptune openCypher 会移除 Unicode 在 `trim()`、`ltrim()` 和 `rtrim()` 函数中定义的空格。
+ 在 Neptune openCypher 中，对于较大的双精度值，`tostring(`double`)` 不会自动切换到 E 表示法。