【图形数据库】图形查询语言比较:Gremlin与Cypher与nQL

视频号

微信公众号

知识星球

Chinese, Simplified


要比较哪些图查询语言

 

Gremlin

Gremlin是Apache TinkerPop开发的一种图遍历语言,已被许多图数据库解决方案所采用。它可以是声明性的,也可以是命令性的。

Gremlin是基于Groovy的,但有许多语言变体,允许开发人员用许多现代编程语言(如Java、JavaScript、Python、Scala、Clojure和Groovy)本地编写Gremlin查询。

支持的图形数据库:Janus graph、InfiniteGraph、Cosmos DB、DataStax Enterprise(5.0+)和Amazon Neptune

Cypher

Cypher是一种声明性图查询语言,它允许在属性图中进行富有表现力和高效的数据查询。

该语言是利用SQL的强大功能设计的。Cypher语言的关键字不区分大小写,但属性、标签、关系类型和变量区分大小写。

支持的图形数据库:Neo4j、AgentsGraph和RedisGraph

nQL

NebulaGraph引入了自己的查询语言nQL,这是一种与SQL类似的声明性文本查询语言,但专为图形设计。

nQL语言的关键字是区分大小写的,它支持语句组合,因此不需要嵌入语句。

支持图形数据库:Nebula graph

术语比较

在比较这三种图查询语言之前,让我们先来看看它们的术语和概念。下表解释了这些语言如何定义节点和边:

语法比较--CRUD

在理解了Gremlin、Cypher和nQL中的常见术语后,让我们来看看这些图查询语言的一般语法。

本节将分别介绍Gremlin、Cypher和nQL的基本CRUD语法。

图表

请参阅以下关于如何创建图形空间的示例。我们省略了Cypher,因为在向图数据库添加任何数据之前,您不需要创建图空间。

 

# Create a graph that Gremlin can traverse
g = TinkerGraph.open().traversal()# Create a graph space in nGQL
CREATE SPACE gods

角顶

我们都知道图是由节点和边组成的。Cypher中的节点在Gremlin和nQL中称为顶点。边是两个节点之间的连接。

请参阅以下分别在这些查询语言中插入新顶点的示例。

# Insert vertex in Gremlin
g.addV(vertexLabel).property()# Insert vertex in Cypher
CREATE (:nodeLabel {property})# Insert vertex in nGQL
INSERT VERTEX tagName (propNameList) VALUES vid:(tagKey propValue)

顶点类型

节点/顶点可以有类型。它们在Gremlin和Cypher中称为标签,在nQL中称为标记。

一个顶点类型可以具有多个特性。例如,顶点类型Person有两个属性,即name和age。

Create Vertex Type

请参阅以下关于顶点类型创建的示例。我们省略了Cypher,因为在插入数据之前不需要标签。

# Create vertex type in Gremlin
g.addV(vertexLabel).property()# Create vertex type in nGQL
CREATE tagName(PropNameList)

请注意,Gremlin和nQL都支持IF NOT EXISTS。该关键字会自动检测是否存在相应的顶点类型。如果它不存在,则会创建一个新的。否则,不会创建任何顶点类型。

Show Vertex Types

创建顶点类型后,可以使用以下查询显示它们。它们将列出所有标签/标记,而不是某些标签/标记。

# Show vertex types in Gremlin
g.V().label().dedup();# Show vertex types in Cypher method 1
MATCH (n)
RETURN DISTINCT labels(n)
# Show vertex types in Cypher method 2
CALL db.labels();# Show vertex types in nGQL
SHOW TAGS

顶点上的CRUD

 

本节介绍使用三种查询语言对顶点进行的基本CRUD操作。

 

插入顶点

Insert Vertices# Insert vertex of certain type in Gremlin
g.addV(String vertexLabel).property()# Insert vertex of certain type in Cypher
CREATE (node:label) # Insert vertex of certain type in nGQL
INSERT VERTEX <tag_name> (prop_name_list) VALUES <vid>:(prop_value_list)Get Vertices# Fetch vertices in Gremlin
g.V(<vid>)# Fetch vertices in Cypher
MATCH (n)
WHERE condition
RETURN properties(n)# Fetch vertices in nGQL
FETCH PROP ON <tag_name> <vid>Delete Vertices# Delete vertex in Gremlin
g.V(<vid>).drop()# Delete a vertex in Cypher
MATCH (node:label)
DETACH DELETE node# Delete vertex in nGQL
DELETE VERTEX <vid>Update a Vertex's Property

本节介绍如何更新顶点的特性。

 

# Update vertex in Gremlin
g.V(<vid>).property()# Update vertex in Cypher
SET n.prop = V# Update vertex in nGQL
UPDATE VERTEX <vid> SET <update_columns>

Cypher和nQL都使用关键字SET来设置顶点类型,只是在nQL中添加了UPDATE关键字来标识操作。Gremlin的操作与上述获取顶点的操作类似,只是添加了更改属性的操作。

本节介绍边缘上的基本CRUD操作。

边缘类型

 

与顶点一样,边也可以具有类型。

 

# Create an edge type in Gremlin
g.edgeLabel()# Create an edge type in nGQL
CREATE EDGE edgeTypeName(propNameList)

边缘CRUD

Insert Edges of Certain Types

插入边类似于插入顶点。Cypher使用-[]->和nQL分别使用->来表示边。Gremlin使用关键字to()来指示边缘方向。

默认情况下,边以三种语言定向。下面左边的图表是有向边,而右边的图表是无向边。

 

# Insert edge of certain type in Gremlin
g.addE(String edgeLabel).from(v1).to(v2).property()# Insert edge of certain type in Cypher
CREATE (<node1-name>:<label1-name>)-
[(<relationship-name>:<relationship-label-name>)]
->(<node2-name>:<label2-name>)# Insert edge of certain type in nGQL
INSERT EDGE <edge_name> ( <prop_name_list> ) VALUES <src_vid> ->
<dst_vid>: ( <prop_value_list> )Delete Edges# Delete edge in Gremlin
g.E(<eid>).drop()# Delete edge in Cypher
MATCH (<node1-name>:<label1-name>)-[r:relationship-label-name]->()
DELETE r# Delete edge in nGQL
DELETE EDGE <edge_type> <src_vid> -> <dst_vid>Fetch Edges# Fetch edges in Gremlin
g.E(<eid>)# Fetch edges in Cypher
MATCH (n)-[r:label]->()
WHERE condition
RETURN properties(r)# Fetch edges in nGQL
FETCH PROP ON <edge_name> <src_vid> -> <dst_vid>

其他操作

除了顶点和边上常见的CRUD之外,我们还将向您展示三种图查询语言中的一些组合查询。

遍历边

# Traverse edges with specified vertices in Gremlin
g.V(<vid>).outE(<edge>)# Traverse edges with specified vertices in Cypher
Match (n)->[r:label]->[]
WHERE id(n) = vid
RETURN r# Traverse edges with specified vertices in nGQL
GO FROM <vid> OVER <edge>

反向遍历边

在反向遍历中,Gremlin使用中表示反转,Cypher使用<-。nQL使用关键字REVERSELY。

# Traverse edges reversely with specified vertices Gremlin
g.V(<vid>).in(<edge>)# Traverse edges reversely with specified vertices Cypher
MATCH (n)<-[r:label]-()# Traverse edges reversely with specified vertices nGQL
GO FROM <vid> OVER <edge> REVERSELY

双向遍历边

如果边缘方向不相关(任何一个方向都可以接受),则Gremlin使用bothE(),Cypher使用-[]-,nQL使用关键字BIDIRECT。

# Traverse edges reversely with specified vertices Gremlin
g.V(<vid>).bothE(<edge>)# Traverse edges reversely with specified vertices Cypher
MATCH (n)-[r:label]-()# Traverse edges reversely with specified vertices nGQL
GO FROM <vid> OVER <edge> BIDIRECT

沿指定边缘查询N个跃点

 

Gremlin和nQL分别使用时间和STEP来表示N个跳。Cypher使用关系*N。

 

# Query N hops along specified edge in Gremlin
g.V(<vid>).repeat(out(<edge>)).times(N)# Query N hops along specified edge in Cypher
MATCH (n)-[r:label*N]->()
WHERE condition
RETURN r# Query N hops along specified edge in nGQL
GO N STEPS FROM <vid> OVER <edge>

查找两个顶点之间的路径

 

# Find paths between two vertices in Gremlin
g.V(<vid>).repeat(out()).until(<vid>).path()# Find paths between two vertices in Cypher
MATCH p =(a)-[.*]->(b)
WHERE condition
RETURN p# Find paths between two vertices in nGQL
FIND ALL PATH FROM <vid> TO <vid> OVER *

查询示例

本节介绍一些演示查询。

演示模型:众神之图

本节中的示例广泛使用了Janus graph分发的玩具图,称为“众神之图”,如下图所示

这个例子描述了罗马万神殿的存在和地点之间的关系。


Inserting data

# Inserting vertices
## nGQL
nebula> INSERT VERTEX character(name, age, type) VALUES hash("saturn")
:("saturn", 10000, "titan"), hash("jupiter"):("jupiter", 5000, "god");
## Gremlin
gremlin> saturn = g.addV("character").property(T.id, 1).property('name', 'saturn').property('age', 10000).property('type', 'titan').next();
==>v[1]
gremlin> jupiter = g.addV("character").property(T.id, 2).property('name', 'jupiter').property('age', 5000).property('type', 'god').next();
==>v[2]
gremlin> prometheus = g.addV("character").property(T.id, 31).property('name', 'prometheus').property('age', 1000).property('type', 'god').next();
==>v[31]
gremlin> jesus = g.addV("character").property(T.id, 32).property('name', 'jesus').property('age', 5000).property('type', 'god').next();
==>v[32]
## Cypher
cypher> CREATE (src:character {name:"saturn", age: 10000, type:"titan"})
cypher> CREATE (dst:character {name:"jupiter", age: 5000, type:"god"})# Inserting edges
## nGQL
nebula> INSERT EDGE father() VALUES hash("jupiter")->hash("saturn"):();
## Gremlin
gremlin> g.addE("father").from(jupiter).to(saturn).property(T.id, 13);
==>e[13][2-father->1]
## Cypher
cypher> CREATE (src)-[rel:father]->(dst)

Deleting

# nGQL
nebula> DELETE VERTEX hash("prometheus");
# Gremlin
gremlin> g.V(prometheus).drop();
# Cypher
cypher> MATCH (n:character {name:"prometheus"}) DETACH DELETE n

Updating

# nGQL
nebula> UPDATE VERTEX hash("jesus") SET character.type = 'titan';
# Gremlin
gremlin> g.V(jesus).property('age', 6000);
==>v[32]
# Cypher
cypher> MATCH (n:character {name:"jesus"}) SET n.type = 'titan';

Fetching/Reading

# nGQL
nebula> FETCH PROP ON character hash("saturn");
===================================================
| character.name | character.age | character.type |
===================================================
| saturn | 10000 | titan |
---------------------------------------------------
# Gremlin
gremlin> g.V(saturn).valueMap();
==>[name:[saturn],type:[titan],age:[10000]]
# Cypher
cypher> MATCH (n:character {name:"saturn"}) RETURN properties(n)
╒════════════════════════════════════════════╕
│"properties(n)" │
╞════════════════════════════════════════════╡
│{"name":"saturn","type":"titan","age":10000}│
└────────────────────────────────────────────┘

Finding the name of Hercules’s Father

# nGQL
nebula> LOOKUP ON character WHERE character.name == 'hercules' | \
-> GO FROM $-.VertexID OVER father YIELD $$.character.name;
=====================
| $$.character.name |
=====================
| jupiter |
---------------------
# Gremlin
gremlin> g.V().hasLabel('character').has('name','hercules').
out('father').values('name');
==>jupiter
# Cypher
cypher> MATCH (src:character{name:"hercules"})-[:father]->
(dst:character) RETURN dst.name
╒══════════╕
│"dst.name"│
╞══════════╡
│"jupiter" │
└──────────┘

Finding the name of Hercules’s Grandfather

# nGQL
nebula> LOOKUP ON character WHERE character.name == 'hercules' | \
-> GO 2 STEPS FROM $-.VertexID OVER father YIELD $$.character.name;
=====================
| $$.character.name |
=====================
| saturn |
---------------------
# Gremlin
gremlin> g.V().hasLabel('character').has('name','hercules').out('father').
out('father').values('name');
==>saturn
# Cypher
cypher> MATCH (src:character{name:"hercules"})-[:father*2]->
(dst:character) RETURN dst.name
╒══════════╕
│"dst.name"│
╞══════════╡
│"saturn" │
└──────────┘

Find the characters with age > 100

# nGQL
nebula> LOOKUP ON character WHERE character.age > 100 YIELD
character.name, character.age;
=========================================================
| VertexID | character.name | character.age |
=========================================================
| 6761447489613431910 | pluto | 4000 |
---------------------------------------------------------
| -5860788569139907963 | neptune | 4500 |
---------------------------------------------------------
| 4863977009196259577 | jupiter | 5000 |
---------------------------------------------------------
| -4316810810681305233 | saturn | 10000 |
---------------------------------------------------------
# Gremlin
gremlin> g.V().hasLabel('character').has('age',gt(100)).values('name');
==>saturn
==>jupiter
==>neptune
==>pluto
# Cypher
cypher> MATCH (src:character) WHERE src.age > 100 RETURN src.name
╒═══════════╕
│"src.name" │
╞═══════════╡
│ "saturn" │
├───────────┤
│ "jupiter" │
├───────────┤
│ "neptune" │
│───────────│
│ "pluto" │
└───────────┘

Find who are Pluto’s cohabitants, excluding Pluto himself

# nGQL
nebula> GO FROM hash("pluto") OVER lives YIELD lives._dst AS place |
 GO FROM $-.place OVER lives REVERSELY WHERE \
$$.character.name != "pluto" YIELD $$.character.name AS cohabitants;
===============
| cohabitants |
===============
| cerberus |
---------------
# Gremlin
gremlin> g.V(pluto).out('lives').in('lives').where(is(neq(pluto))).
values('name');
==>cerberus
# Cypher
cypher> MATCH (src:character{name:"pluto"})-[:lives]->()<-[:lives]-
(dst:character) RETURN dst.name
╒══════════╕
│"dst.name"│
╞══════════╡
│"cerberus"│
└──────────┘

Pluto’s Brothers

# which brother lives in which place?
## nGQL
nebula> GO FROM hash("pluto") OVER brother YIELD brother._dst AS god | \
GO FROM $-.god OVER lives YIELD $^.character.name AS Brother,
$$.location.name AS Habitations;
=========================
| Brother | Habitations |
=========================
| jupiter | sky |
-------------------------
| neptune | sea |
-------------------------
## Gremlin
gremlin> g.V(pluto).out('brother').as('god').out('lives').as('place').
select('god','place').by('name');
==>[god:jupiter, place:sky]
==>[god:neptune, place:sea]
## Cypher
cypher> MATCH (src:Character{name:"pluto"})-[:brother]->
(bro:Character)-[:lives]->(dst)
RETURN bro.name, dst.name
╒═════════════════════════╕
│"bro.name" │"dst.name"│
╞═════════════════════════╡
│ "jupiter" │ "sky" │
├─────────────────────────┤
│ "neptune" │ "sea" │
└─────────────────────────┘

除了三种图查询语言中的基本操作外,我们还将对这些语言中的高级操作进行另一项比较。敬请期待!

本文地址
https://architect.pub/graph-query-language-comparison-gremlin-vs-cypher-vs-ngql
SEO Title
Graph Query Language Comparison: Gremlin vs Cypher vs nGQL