重寫 Cypher 查詢以在 Neptune 上的 OpenCpher 中執行 - Amazon Neptune

本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。

重寫 Cypher 查詢以在 Neptune 上的 OpenCpher 中執行

OpenCypher 語言是屬性圖的宣告式查詢語言,最初由 Neo4j 開發,然後在 2015 年成為開放原始碼,並在 Apache 2 開放原始碼授權下投入 OpenCypher 專案。在 AWS,我們相信開放原始碼對每個人都有好處,因此我們致力於將開放原始碼的價值帶給客戶,並將 AWS 的操作優勢帶給開放原始碼社群。

OpenCypher 語法記載於 Cypher 查詢語言參考第 9 版

由於 OpenCypher 包含 Cypher 查詢語言的語法和功能子集,因此某些遷移案例需要以 OpenCypherr 相容的格式重寫查詢,或檢查替代方法以實現所需的功能。

本節包含處理常見差異的建議,但它們絕不是全面的。您應該使用這些重寫徹底測試任何應用程式,以確保結果就是您預期的。

重寫 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 子句的組合來實現類似的結果。

例如,在安克雷奇 (ANC) 與奧斯汀 (AUS) 之間具有一到三個停靠站的路徑上,下列 Cypher 查詢會找出所有位於這些路徑上的機場,並傳回每條路徑的總距離:

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