时间:2022-10-10 18:08:41 | 浏览:8195
图数据库已经越来越被人们熟知,同时也在许多企业中得到了应用,但是由于市面上没有统一的图查询语言标准,所以有部分开发者对于不同图数据库的用法存在着疑问。因此本文作者对市面上主流的几款图数据库进行了一番分析,并以查询操作为例进行深入介绍。
文章的开头我们先来看下什么是图数据库,根据维基百科的定义:图数据库是使用图结构进行语义查询的数据库,它使用节点、边和属性来表示和存储数据。
虽然和关系型数据库存储的结构不同(关系型数据库为表结构,图数据库为图结构),但不计各自的性能问题,关系型数据库可以通过递归查询或者组合其他 SQL 语句(Join)完成图查询语言查询节点关系操作。得益于 1987 年 SQL 成为国际标准化组织(ISO)标准,关系型数据库行业得到了很好的发展。同 60、70 年代的关系型数据库类似,图数据库这个领域的查询语言目前也没有统一标准,虽然 19 年 9 月经过国际 SQL 标准委员会投票表决,决定将图查询语言(Graph Query Language)纳为一种新的数据库查询语言,但 GQL 的制定仍需要一段时间。
鉴于市面上没有统一的图查询语言标准,在本文中我们选取市面上主流的几款图查询语言来分析一波用法,由于篇幅原因本文旨在简单介绍图查询语言和常规用法,更详细的内容将在进阶篇中讲述。
Gremlin 是 Apache ThinkerPop 框架下的图遍历语言。Gremlin 可以是声明性的也可以是命令性的。虽然 Gremlin 是基于 Groovy 的,但具有许多语言变体,允许开发人员以 Java、JavaScript、Python、Scala、Clojure 和 Groovy 等许多现代编程语言原生编写 Gremlin 查询。
支持图数据库:Janus Graph、InfiniteGraph、Cosmos DB、DataStax Enterprise(5.0+) 、Amazon Neptune
Cypher 是一个描述性的图形查询语言,允许不必编写图形结构的遍历代码对图形存储有表现力和效率的查询,和 SQL 很相似,Cypher 语言的关键字不区分大小写,但是属性值,标签,关系类型和变量是区分大小写的。
支持图数据库: Neo4j、RedisGraph、AgensGraph
nGQL 是一种类 SQL 的声明型的文本查询语言,nGQL 同样是关键词大小写不敏感的查询语言,目前支持模式匹配、聚合运算、图计算,可无嵌入组合语句。
支持图数据库:Nebula Graph
在比较这 3 个图查询语言之前,我们先来看看他们各自的术语,如果你翻阅他们的文档会经常见到下面这些“关键字”,在这里我们不讲用法,只看这些图数据库常用概念在这 3 个图数据库文档中的叫法。
术语GremlinCyphernGQL点VertexNodeVertex边EdgeRelationshipEdge点类型LabelLabelTag边类型labelRelationshipTypeedge type点 IDvidid(n)vid边 IDeidid®无插入addcreateinsert删除dropdeletedelete / drop更新属性setPropertysetupdate
我们可以看到大体上对点和边的叫法类似,只不过 Cypher 中直接使用了 Relationship 关系一词代表边。其他的术语基本都非常直观。
上面说了一通术语之类的“干货”之后,是时候展示真正的技术了——来个具体一点的例子,在具体的例子中我们将会分析 Gremlin、Cypher、nGQL 的用法不同。
实操示例使用了 Janus Graph 的示例图 The Graphs of Gods。该图结构如下图所示,描述了罗马万神话中诸神关系。
复制代码
# 插入点## nGQLnebula> INSERT VERTEX character(name, age, type) VALUES hash("saturn"):("saturn", 10000, "titan"), hash("jupiter"):("jupiter", 5000, "god");## Gremlingremlin> 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]## Cyphercypher> CREATE (src:character {name:"saturn", age: 10000, type:"titan"})cypher> CREATE (dst:character {name:"jupiter", age: 5000, type:"god"})# 插入边## nGQLnebula> INSERT EDGE father() VALUES hash("jupiter")->hash("saturn"):();## Gremlingremlin> g.addE("father").from(jupiter).to(saturn).property(T.id, 13);==>e[13][2-father->1]## Cyphercypher> CREATE (src)-[rel:father]->(dst)
在数据插入这块,我们可以看到 nGQL 使用 INSERT VERTEX 插入点,而 Gremlin 直接使用类函数的 g.addV() 来插入点,Cypher 使用 CREATE 这个 SQL 常见关键词来创建插入的点。在点对应的属性值方面,nGQL 通过 VALUES 关键词来赋值,Gremlin 则通过操作 .property() 进行对应属性的赋值,Cypher 更直观直接在对应的属性值后面跟上想对应的值。
在边插入方面,可以看到和点的使用语法类似,只不过在 Cypher 和 nGQL 中分别使用 -[]-> 和 **-> 来表示关系,而 Gremlin 则用 to() ** 关键词来标识指向关系,在使用这 3 种图查询语言的图数据库中的边均为有向边,下图左边为有向边,右边为无向边。
复制代码
# nGQLnebula> DELETE VERTEX hash("prometheus");# Gremlingremlin> g.V(prometheus).drop();# Cyphercypher> MATCH (n:character {name:"prometheus"}) DETACH DELETE n
这里,我们可以看到大家的删除关键词都是类似的:Delete 和 Drop,不过这里需要注意的是上面术语篇中提过 nGQL 中删除操作对应单词有 Delete 和 Drop ,在 nGQL 中 Delete 一般用于点边,Drop 用于 Schema 删除,这点和 SQL 的设计思路是一样的。
复制代码
# nGQLnebula> UPDATE VERTEX hash("jesus") SET character.type = "titan";# Gremlingremlin> g.V(jesus).property("age", 6000);==>v[32]# Cyphercypher> MATCH (n:character {name:"jesus"}) SET n.type = "titan";
可以看到 Cypher 和 nGQL 都使用 SET 关键词来设置点对应的类型值,只不过 nGQL 中多了 UPDATE 关键词来标识操作,Gremlin 的操作和查看点操作类似,只不过增加了变更 property 值操作,这里我们注意到的是,Cypher 中常见的一个关键词便是 MATCH,顾名思义,它是一个查询关键词,它会去选择匹配对应条件下的点边,再进行下一步操作。
复制代码
# nGQLnebula> FETCH PROP ON character hash("saturn");===================================================| character.name | character.age | character.type |===================================================| saturn | 10000 | titan |---------------------------------------------------# Gremlingremlin> g.V(saturn).valueMap();==>[name:[saturn],type:[titan],age:[10000]]# Cyphercypher> MATCH (n:character {name:"saturn"}) RETURN properties(n) ╒════════════════════════════════════════════╕ │"properties(n)" │ ╞════════════════════════════════════════════╡ │{"name":"saturn","type":"titan","age":10000}│ └────────────────────────────────────────────┘
在查看数据这块,Gremlin 通过调取 valueMap() 获得对应的属性值,而 Cypher 正如上面更新数据所说,依旧是 MATCH 关键词来进行对应的匹配查询再通过 RETURN 返回对应的数值,而 nGQL 则对 saturn 进行 hash 运算得到对应 VID 之后去获取对应 VID 的属性值。
复制代码
# nGQLnebula> LOOKUP ON character WHERE character.name == "hercules" | -> GO FROM $-.VertexID OVER father YIELD $$.character.name;=====================| $$.character.name |=====================| jupiter |---------------------# Gremlingremlin> g.V().hasLabel("character").has("name","hercules").out("father").values("name");==>jupiter# Cyphercypher> MATCH (src:character{name:"hercules"})-[:father]->(dst:character) RETURN dst.name ╒══════════╕ │"dst.name"│ ╞══════════╡ │"jupiter" │ └──────────┘
查询父亲,其实是一个查询关系 / 边的操作,这里不做赘述,上面插入边的时候简单介绍了 Gremlin、Cypher、nGQL 这三种图数据库是各自用来标识边的关键词和操作符是什么。
复制代码
# nGQLnebula> LOOKUP ON character WHERE character.name == "hercules" | -> GO 2 STEPS FROM $-.VertexID OVER father YIELD $$.character.name;=====================| $$.character.name |=====================| saturn |---------------------# Gremlingremlin> g.V().hasLabel("character").has("name","hercules").out("father").out("father").values("name");==>saturn# Cyphercypher> MATCH (src:character{name:"hercules"})-[:father*2]->(dst:character) RETURN dst.name ╒══════════╕ │"dst.name"│ ╞══════════╡ │"saturn" │ └──────────┘
查询祖父,其实是一个查询对应点的两跳关系,即:父亲的父亲,我们可以看到 Gremlin 使用了两次 out() 来表示为祖父,而 nGQL 这里使用了 |(Pipe 管道) 的概念,用于子查询。在两跳关系处理上,上面说到 Gremlin 是用了 2 次 out(),而 Cypher、nGQL 则引入了 step 数的概念,分别对应到查询语句的 GO 2 STEP 和 [:father *2],相对来说 Cypher、nGQL 这样书写更优雅。
复制代码
# nGQLnebula> 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 |---------------------------------------------------------# Gremlingremlin> g.V().hasLabel("character").has("age",gt(100)).values("name");==>saturn==>jupiter==>neptune==>pluto# Cyphercypher> MATCH (src:character) WHERE src.age > 100 RETURN src.name ╒═══════════╕ │"src.name" │ ╞═══════════╡ │ "saturn" │ ├───────────┤ │ "jupiter" │ ├───────────┤ │ "neptune" │ │───────────│ │ "pluto" │ └───────────┘
这个是一个典型的查询语句,找寻符合特定条件的点并返回结果,在 Cypher 和 nGQL 中用 WHRER 进行条件判断,而 Gremlin 延续了它的“编程风”用 gt(100) 表示年大于龄 100 的这个筛选条件,延伸下 Gremlin 中 eq() 则表示等于这个查询条件。
复制代码
# nGQLnebula> 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 |---------------# Gremlingremlin> g.V(pluto).out("lives").in("lives").where(is(neq(pluto))).values("name");==>cerberus# Cyphercypher> MATCH (src:character{name:"pluto"})-[:lives]->()<-[:lives]-(dst:character) RETURN dst.name ╒══════════╕ │"dst.name"│ ╞══════════╡ │"cerberus"│ └──────────┘
这是一个沿指定点 Pluto 反向查询指定边(居住)的操作,在反向查询中,Gremlin 使用了 in 来表示反向关系,而 Cypher 则更直观的将指向箭头反向变成 <- 来表示反向关系,nGQL 则用关键词 REVERSELY 来标识反向关系。
复制代码
# which brother lives in which place?## nGQLnebula> 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 |-------------------------## Gremlingremlin> g.V(pluto).out("brother").as("god").out("lives").as("place").select("god","place").by("name");==>[god:jupiter, place:sky]==>[god:neptune, place:sea]## Cyphercypher> MATCH (src:Character{name:"pluto"})-[:brother]->(bro:Character)-[:lives]->(dst)RETURN bro.name, dst.name ╒═════════════════════════╕ │"bro.name" │"dst.name"│ ╞═════════════════════════╡ │ "jupiter" │ "sky" │ ├─────────────────────────┤ │ "neptune" │ "sea" │ └─────────────────────────┘
这是一个通过查询指定点 Pluto 查询指定边 brother 后再查询指定边 live 的查询,相对来说不是很复杂,这里就不做解释说明了。
最后,本文只是对 Gremlin、Cypher、nGQL 等 3 个图查询语言进行了简单的介绍,更复杂的语法将在本系列的后续文章中继续,欢迎在论坛留言交流。
俗话说,“百中难挑一,万里难成对”说的就是文玩核桃。因此,想要玩一对儿好核桃,配对儿也就成了不可忽视的问题。想要配出品相完美的核桃需要有两个步骤,即分别挑选和组合成对儿。分别挑选是指要挑选出适合盘玩的核桃,这既是配对的前提,也是盘出好核桃的
盘玩核桃,好多玩友只想着上手就想核桃变漂亮,殊不知文玩核桃变漂亮要经历以下4个阶段,今天小编给大家分享一下,希望能帮到您。1、 轻上色阶段包浆级别:★★有核包浆初养成,还得继续多盘刷!!每个缝隙、每个角落,都要周到的关照!2、 变红润阶段包
乾隆爷当年这样称赞核桃“掌上悬日月。周身气血涌,何年是白头”,虽然这首诗屌丝了一些,但是还是能完整的表达乾隆皇帝对核桃的珍爱之情。远的不说,咱说近的,文玩爱好者大多都是从盘玩核桃开始自己的文玩生涯,怀揣着梦想有一天手里盘着一对儿包浆厚重、玉
何为文玩,一种解释为文人的玩物,那文玩葫芦呢就是可以当做文玩的葫芦。那什么样的葫芦才可以当做文玩葫芦呢?哪样的葫芦可以卖个好价钱呢?1. 尺寸小 一般的文玩葫芦高度在10厘米以内,且越小越稀有珍贵,3厘米以下的本地葫芦可以称之为“草里金”这
国庆长假结束的第一天汽车界就被一则爆料震惊,竟然是奇瑞缺钱,要“被”收购,最后一些自媒体更为干脆,涉及整个奇瑞,殊不知他们眼中所及的“奇瑞”其实真实意义上而言只能代表奇瑞汽车,也就是奇瑞汽车股份有限公司,而非整个“奇瑞”板块,很多人能够知晓
巴里黄檀(Dalbergia bariensisPierre ex Prain)隶属于豆科(Fabaceae),黄檀属主产于越南、泰国、柬埔寨、缅甸和老挝,在红木木材市场上,巴里黄檀的俗称是花酸枝,简称花枝。在红木市场上,大部分厂家将它标注
古典红木家具在我国历史中充当着尤为重要的角色,是承载我国古典艺术文化的瑰宝。古时,红木家具可谓是王公贵族或文人雅士的专属,时光荏苒,红木家具传承至今,成为我国历史文化重要载体之一。大红酸枝明式圆台大红酸枝明式官帽椅国泰民安,由于人们的生活水
暑期是近视高发期!眼科医生带你了解孩子近视的10个真相 | 暑期安全公开课⑥暑假以来,在眼科医院就诊患者中,有不少是家长带着孩子前来矫正视力的。假期,孩子们多了放松娱乐的时光,但稍不注意近视问题也会悄悄来到孩子身旁。该如何预防孩子视力下滑?
【近视能够治愈吗?这5大细节你需要了解 】近视的低龄化和持续的高患病率已成为社会普遍关注的问题,首份《义务教育质量监测报告》指出,目前学生视力不良问题突出,四年级、八年级学生视力不良检出率分别达36.5%和65.3%。戳图,常州市第一人民医
人民网北京6月5日电 每年的6月6日是全国爱眼日,眼睛是人类感官中最重要的器官之一,不当的用眼习惯会导致眼部疾病,危害身体健康。新冠肺炎疫情以来,儿童青少年使用电子产品增多、户外活动减少,增加了近视发生的风险。人民网“求真”栏目在第二十五届