Neptune의 OpenCypher에서 실행되도록 Cypher 쿼리를 재작성 - Amazon Neptune

기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.

Neptune의 OpenCypher에서 실행되도록 Cypher 쿼리를 재작성

openCypher는 속성 그래프용 선언적 쿼리 언어로, Neo4j에서 처음 개발한 후 2015년에 오픈 소스로 제공되었으며, Apache 2 오픈 소스 라이선스에 따라 openCypher 프로젝트에 기여했습니다. AWS에서는 오픈소스가 모두에게 유익하다고 믿으며 고객에게 오픈소스의 가치를, 오픈소스 커뮤니티에 AWS의 운영상의 우수성을 제공하기 위해 최선을 다하고 있습니다.

이 구문은 Cypher 쿼리 언어 참조 버전 9에 문서화되어 있습니다.

openCypher에는 Cypher 쿼리 언어의 일부 구문 및 기능이 포함되어 있으므로 일부 마이그레이션 시나리오에서는 OpenCypher 호환 형식으로 쿼리를 다시 작성하거나 원하는 기능을 구현하기 위한 대체 방법을 검토해야 합니다.

이 섹션에는 일반적인 차이점을 처리하기 위한 권장 사항이 포함되어 있지만 모든 내용을 모두 포함하는 것은 아닙니다. 이러한 재작성을 사용하는 모든 애플리케이션을 철저하게 테스트하여 결과가 예상과 일치하는지 확인해야 합니다.

None, All, 및 Any 술어 함수 재작성

이러한 함수는 openCypher 사양에 포함되지 않습니다. openCypher에서는 목록 포괄을 사용하여 유사한 결과를 얻을 수 있습니다.

예를 들어, Start 노드에서 노드로 이동하는 모든 경로를 찾지만 D의 클래스 속성이 다음과 같은 End 노드를 통과하는 여정은 허용되지 않습니다.

# 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) 사이에 1~3개의 정류장이 있는 경로의 모든 공항을 찾아 각 경로의 총 거리를 반환합니다.

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

다음과 같이 Neptune용 openCypher에서 동일한 쿼리를 작성할 수 있습니다.

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 DB 클러스터에서 데이터 내보내기 참조).

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 절과 결합하는 것입니다.

  • 또 다른 방법은 그렘린에서 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() 함수를 ‘List Compremension’과 함께 사용하여 유사한 결과를 얻는 방법을 보여주는 재작성 예제를 사용할 수 있습니다.

# 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