维基上面是这么介绍图数据库的:
A graph database is a database that uses graph structures for semantic queries with nodes, edges and properties to represent and store data.
这里有个关键词”semantic queries”,与之相对应的可能是形式语言(Formal Language)中只关心句法。最让人心碎的是:
As of 2016, no single graph query language has been universally adopted in the same way as SQL was for relational databases, and there are a wide variety of systems, most often tightly tied to one product. Some standardization efforts have occurred, leading to multi-vendor query languages like Gremlin, SPARQL, and Cypher. In addition to having query language interfaces, some graph databases are accessed through APIs.
我在这上面吃了大亏。问题的根本在于图数据库本身市场就不大而且还没有统一的查询标准,极度分裂。各自优化的目标可能不一样,一般的实现大多是聚集于自家的API,对于相对通用的Gremlin和SPARQL的支持可能只是有而已,功能支持少缺胳膊少腿,各种让人难受。
我既然能有机会在这里扯淡至少我个人是相信这一点肯定会有所改进的——文本信息抽取工具逐渐成熟和以维基百科为基础的知识库(Freebase/DBPedia等)的相继出现,会对存储及查询提出更多的要求,也会有更多的人投入到改善查询和存储效率上的。
很多人会问一个问题是图查询能不能用SQL做,首先可以肯定这个是能做的,如果效率也非常高的话也就没有必要再多做探讨。用SQL查询是需要对表设计有一些要求的,同样的Gremlin和SPARQL两种查询标准都是对存储模式是有一定假设(或者要求)的。这篇文章只讨论查询模式上的差异,并且对每一种的查询给出相应的例子,学习学习基本的语法、做做对比即可。效率问题以及问题产生的根本需要还是再开一篇来讲好了,这篇文章还是将内容限定在对于同样的问题,数据应该如何存储与查询上。
问题:非洲国家的首都有哪些?
首先设计两张表,洲和国家两张表。
TABLE: continent | TABLE: country |
---|---|
id | id |
country_id | capital |
name | name |
查询语句:
SELECT
country.capital
FROM
continent
JOIN
country
ON continent.country_id = country.id
WHERE
continent.name =
'Afica'
RDF本身没有schema,但是有一些推荐的RDF字典,掌握字典本身也比较麻烦。还好有schema.org这一类的标准化工作让事情变得稍微简单一点点,一般的通用字段已经给出了定义域和值域。
PREFIX ex:
<http://example.com/exampleOntology#>
SELECT ?capital
?country
WHERE
{
?x ex:cityname ?capital ;
ex:isCapitalOf ?y .
?y ex:countryname ?country ;
ex:isInContinent ex:Africa
.
}
PREFIX
相当于引入了一个字典,并且给出了字典前缀,接下来用的时候就可以把<http://example.com/exampleOntology#cityname>
简写成ex:cityname
了;?x
问号开头的都是变量,出现在select
部分的变量会用于输出;where
条件中的每一行都是一个三元组(SPO, Subject/Predicate/Object),以.
结束。例子中出现了复用Subject的写法。SPARQL中最常用的当然是这类SELECT
语句,还有一个比较实用的是DESCRIBE
。比如:
PREFIX ex:
<http://example.com/exampleOntology#>
DESCRIBE ?x
WHERE {
?x ex:isInContinent ex:Africa
.
}
就是获取非洲国家的所有直接关联信息(所有出边,不包括入边)。
Gremlin是由Groovy实现的图查询语言,查询过程就是图遍历的过程。
g.
// 图的起点,总是这样
V().has('continent',
'name',
'Afica')
// continent类的结点,并且name是Africa
.out('capital')
// 不再需要id外键,直接加入了一条capital边
.values('name')
// 取country类结点的name字段
这个问题没法回避,太多人只想要一个简单答案,但是事实却没有那么美好。
答:这里涉及到一个问题,属性值并不总是单一值(List),而SQL表是有这个假设的(比如MySQL,PostgreSQL是有Array类型的)。针对每一个多值属性都需要进行额外拆表,这对表的管理带来了巨大挑战。查询时频繁地进行多表联接对数据库性能也是个挑战。另外,也可以直接在关系型数据库中存储三元组,但是查询效率并不高。
RDF本身是schema-free的,这的确给管理带来很大困难。schema.org以及国内的cnschema可以作为一个起点,尽量使用这些已经有良好定义的词典。我更倾向于在做数据的时候一类一类的数据对应分别的表,仅仅在最后面向使用时转换成RDF格式。
Gremlin查询的图本质仍然是一张一张的表,处理数据、管理数据相对简单一些。我曾经选用过这种方式,但是有一个比较大的问题是各家对Gremlin的实现不一,自动生成代码比较困难,实现的效率也不一样,让人比较头疼。SPARQL是W3C标准,查询语句比较简单,自动生成语义查询也相对容易。另外RDF数据本身在数据交换上比较有优势,比如DBPedia、Freebase之类的数据都有RDF版。
也许很好,考虑到只能在Neo4j上使用,并且社区版的Neo4j只能跑在单机上,以及有无数号称速度超过Neo4j的图数据库已经出现了,个人不太想学。用Gremlin和SPARQL可以很容易地从某个数据库转到另外一个,但是Cypher就不要想了。另外,Neo4j的数据组织是属性图的。
你要是觉得写查询不累、构造查询不麻烦,其实都行的。抽象到像SPARQL/Gremlin这种级别的查询上还是需要一些工作的,本身未必很难,但是得做。
当然了,我个人其实是有明确倾向的,图查询还是用SPARQL吧。下一篇在讲讲常见的图数据和ODBA吧,AZA-AZA。