Dgraph-Take a tour

Take a Tour

Dgraph的GraphQL+-是一种基于facebook的GraphQL的图查询语言。GraphQL并不是专门用于图数据库的,但是它像图一样的查询语法,schema验证以及子图形式的response使它成为一个非常好的语言选择。我们修改了这个语言,并且移除了某些feature,使它可以更好地支持图操作,更好地适用于图数据库,并将新的语言命名为“GraphQL+-”

GraphQL+-还在继续改进,我们在添加更多的feature,将来也会简化某些已有的feature

1. GraphQL+-基础

在图中,object(或者说entity)被称作node,关系被称作edge或者predicate(谓语)

图不止可以用于社交网络,还可以用于:

  • 连接数据,例如需要连接的SQL表
  • 高级搜索
  • 推荐引擎
  • 模式检测
  • 网络,例如电脑、路
  • 流程,例如商业流程及生物流程
  • 事件以及它们之间的因果或其他联系
  • 公司或者市场的结构

1.1 Hello World

{
  find_michael(func: eq(name@., "Michael")) {
    uid
    name@.
    age
  }
}

每个查询都有一个名称(查询语句中的find_michael),查询结果会带上同一个名称作为标签

查询规范func: ...用于匹配nodes。函数eq用于匹配name是Michael的node。查询结果是匹配到的node以及这些node出度的边

Dgraph通过唯一的内部id——UID识别每个node,UID并不是一条边,但是它可以通过包含uid的查询返回。如果你已经知道了一个node的UID,可以使用func: uid(<uid-number>)获取到它

1.2 查询结果

{
  michaels_friends(func: eq(name, "Michael")) {
    name
    age
    friend {
      name@.
    }
  }
}

在Dgraph中,查询返回的结果是一个图,而不是表或者数据的列表

一个查询是在一个图上执行的,查询的结果是这个图的一个子图,或者基于这个图的一些操作与计算

1.3 数据类型与schema

{
  "data": {
    "michaels_friends": [
      {
        "name": "Michael",
        "age": 39,
        "friend": [
          {
            "name@.": "Amit"
          },
          {
            "name@.": "Sarah"
          },
          {
            "name@.": "Sang Hyun"
          },
          {
            "name@.": "Catalina"
          },
          {
            "name@.": "Artyom"
          }
        ]
      }
    ]
  }
}

上面的查询结果有些很magic的地方,因为有些edge返回的是value(例如name、age),而有些返回的是其他node(例如friend)

事实是,有一个schema告诉我们如何解释一个edge

schema(pred: [name, age, friend, owns_pet]) {
  type
  index
}
{
  "data": {
    "schema": [
      {
        "predicate": "age",
        "type": "int",
        "index": true
      },
      {
        "predicate": "friend",
        "type": "uid"
      },
      {
        "predicate": "name",
        "type": "string",
        "index": true
      },
      {
        "predicate": "owns_pet",
        "type": "uid"
      }
    ]
  }
}

一个图中有两种node,我们可以把它们分别称作node与value。在这个例子中,node表示people,每个people有一个string类型的名为name的edge,以及一个int类型的名为age的edge。一个value不能有任何出度

Dgraph支持下列类型的value:

Dgraph Type Description
int signed 64 bit integer
float double precision floating point number
string string
bool boolean
id ID’s stored as strings
dateTime RFC3339 time format with optional timezone eg: 2006-01-02T15:04:05.999999999+10:00 or 2006-01-02T15:04:05.999999999
geo geometries stored using go-geom

此外,对于node来说,还有一个uid类型。这个schema表示friend边是从一个node指向另一个node,而不是指向一个value

1.4语言支持

{
  language_support(func: allofterms(name@hi, "अमित")) {
    name@bn:hi:en
    age
    friend {
      name@ko:ru
      age
    }
  }
}

Dgraph支持的查询语言编码方式为UTF-8

字符串类型的值可以带语言标签。例如Amit的英文名字为“Amit”@en,印地语名字为"अमित"@hi,孟加拉名字为"অমিত"@bn

可以指定按哪种语言进行查询,以及按哪种语言返回查询结果

@lang1:…:langN语法指定了按哪种顺序返回值,规则如下:

  • 至多一个结果被返回
  • 如果有这些语言中有结果,那么按最左边的有结果的语言返回
  • 如果有这些语言中没有结果,那么就不返回结果,除非语言序列是以.结尾的,在这种情况下,会返回一个不带语言标签的值。如果没有不带语言标签的值,那就回返回一个带某种语言标签的值

1.5 查询描述图

Dgraph的查询结果是一个图,事实上,查询结果的结构跟查询的结构是匹配的

The braces edge_name { ... } in the query signify nested blocks where the edges inside the block are matched against nodes found by following the edge that begins the block. We continue nesting the query as we follow edges from node to node.

虽然没有严格的要求,但是查询语言的锁进是一个很好的习惯

{
  michael_friends_friends(func: allofterms(name, "Michael")) {
    name
    age
    friend {
      name
      friend {
        name
      }
    }
  }
}

Dgraph是把查询构想成对图的遍历,顺着边找到需要的数据。查询结果中的uid可以使用图的方式解释结果,而不是树。例如,Michael的朋友的朋友形成了一个环。

1.6 函数与过滤

Node基于应用在它的出度上的函数进行过滤

到现在为止我们用到的查询仅仅针对顶级node应用了一个filter,但是事实上filter可以应用在查询中的所有node

函数只能用于已经被索引的谓语,详见lesson about schema

有许多类型的函数可以用于过滤,包括但不限于:

  • allOfTerms(edge_name, "term1 ... termN"): 匹配有一个string类型的出边edge_name,并且这个string包含所有列出的term
  • anyOfTerms(edge_name, "term1 ... termN"): 类似于allOfTerms, 但是只需匹配到至少一个term即可
  • 相等与不等关系可用于如下类型: int, float, string and date
    • eq(edge_name, value): 等于
    • ge(edge_name, value): ga大于等于
    • le(edge_name, value): 小于等于
    • gt(edge_name, value): 大于
    • lt(edge_name, value): 小于
  • 还有正则表达式、全文检索以及地理查询,它们都是很大的主题,有专门的章节进行讲解

1.7 AND、OR、NOT

AND, OR 以及 NOT可以把一个filter中的多个function结合起来

{
  michael_friends_and(func: allofterms(name, "Michael")) {
    name
    age
    friend @filter(ge(age, 27) AND le(age, 48)) {
      name@.
      age
    }
  }
}

1.8 排序

查询结果可以使用orderascorderdesc进行排序

可视化的结果与排序前是一样的,但是JSON中的结果是按顺序的

{
  michael_friends_sorted(func: allofterms(name, "Michael")) {
    name
    age
    friend (orderasc: age) {
      name@.
      age
    }
  }
}

1.9 分页

查询返回数千条结果很常见,但是你可能只想要前面的若干条结果,分页可以用于展示,或者限制一个过大的返回结果

{
  michael_friends_first(func: allofterms(name, "Michael")) {
    name
    age
    friend (orderasc: name@., offset: 1, first: 2) {
      name@.
    }
  }
}

在GraphQL+-中,分页是通过firstoffsetafter结合排序实现的

  • first: N 返回前N条记录
  • offset: N 跳过前N条记录
  • after: uid 返回uid后面的记录

默认情况下,查询结果使用uid排序,你可以改成自己想要的排序方式

after用于只知道已经读到的最后一条记录的uid,而不知道之前已经读了多少条的情况

1.10 Count

可以使用count来统计出度

{
  michael_number_friends(func: allofterms(name, "Michael")) {
    name
    age
    count(friend)
  }
}

1.11 Dgraph查询的工作方式

Dgraph中的图可能会非常大,因此从所有的节点开始查询效率不高。Dgraph需要一个查询的起始node,这就是root node

在root上,我们使用func:以及一个function来找到初始的node集合。到目前为止,我们只用了eqallofterms来进行字符串搜索,但是事实上我们还可以在其他类型的值上检索,例如date、number,甚至还可以对count进行过滤

为了能够按这种方式检索,Dgraph需要先对值建索引,因为如果没有索引的话,Dgraph必须遍历整个数据里来找到匹配的值

从root filter匹配到的node集合开始,Dgraph会顺着边执行剩下的查询

root上的func:只接受单个function,不接受AND, OR 以及 NOT拼成的filter。因此需要使用(func: ...) @filter(... AND ...)语法来对root执行多个属性的filter

{
  lots_of_friends(func: ge(count(friend), 2)) @filter(ge(age, 20) AND lt(age, 30)) {
    name@.
    age
    friend {
        name@.
    }
  }
}

1.12 Has

has(edge-name)函数会返回有相应名字的出边的node

{
  have_friends(func: has(friend)) {
    name@.
    age
    number_of_friends : count(friend)
  }
}

1.13 Alias

可以给查询到的边设置别名

{
  michael_number_friends(func: allofterms(name, "Michael")) {
    persons_name : name
    age
    number_of_friends : count(friend)
  }
}

上面的查询把name设置为persons_name

注意,别名可以跟原名一摸一样

1.14 Cascade

cascade用于移除不含有所有匹配用到的边的节点

The @cascade directive removes any nodes that don’t have all matching edges in the query

{
  michael_friends_with_pets(func: allofterms(name, "Michael")) @cascade {
    name
    age
    friend {
      name@.
      owns_pet{
        name
      }
    }
  }
}

上面的查询,如果不加@cascade,会返回Michael的所有朋友的名字,以及有宠物的朋友的宠物的名字。而加上@cascade之后,只会返回有宠物的朋友以及他们宠物的名字

cascade的另外一个用途是移除一个block中的filter返回空的节点

这个查询

{
  michael_friends(func: allofterms(name, "Michael"))  @cascade {
    name
    age
    friend {
      name@.
      age
      friend @filter(ge(age, 27)) {
        name@.
        age
      }
    }
  }
}
{
  "data": {
    "michael_friends": [
      {
        "name": "Michael",
        "age": 39,
        "friend": [
          {
            "name@.": "Amit",
            "friend": [
              {
                "name@.": "Michael",
                "age": 39
              },
              {
                "name@.": "Artyom",
                "age": 35
              }
            ]
          },
          {
            "name@.": "Sang Hyun",
            "friend": [
              {
                "name@.": "Amit",
                "age": 35
              }
            ]
          }
        ]
      }
    ]
  }
}

1.15 Normalize

@normalize命令用于:

  • 只返回拥有别名的边
  • 将结果打平,移除嵌套

别名可以跟原名一样

将上面cascade的查询加上@normalize:

{
  michael_friends(func: allofterms(name, "Michael")) @cascade  @normalize {
    name
    age
    friend {
      name : name@.
      age
      friend @filter(ge(age, 27)) {
        name : name@.
        age : age
      }
    }
  }
}
{
  "data": {
    "michael_friends": [
      {
        "age": 39,
        "name": [
          "Amit",
          "Michael"
        ]
      },
      {
        "age": 35,
        "name": [
          "Amit",
          "Artyom"
        ]
      },
      {
        "age": 35,
        "name": [
          "Sang Hyun",
          "Amit"
        ]
      }
    ]
  }
}

1.16 注释

查询可以包含注释

一行中#之后的任何内容都是注释,在查询处理时会被忽略掉

{
  # A comment in a query looks like this
  michael_number_friends(func: allofterms(name, "Michael")) @normalize {
    name : name   # or like this
    age
    number_of_friends : count(friend)
  }
}

2. Schema

2.1 Adding schema - mutating schema

当我们需要往一个新的schema中添加数据的时候,有两种选择:

  • 添加数据,让Dgraph设计schema
  • 指定一个schema,然后添加数据

Dgraph可以很好地设计schema,但是因为function跟filter只能作用于有索引的predicate,因此我们需要指定schema

修改schema被称作mutate schema

//ALTER
industry: string @index(term) .
boss_of: uid .

2.2 Adding Data - mutating data

既然schema已经更新了,现在我们可以通过triple的方式添加数据

//mutate
{
  set {
    _:company1 <name> "CompanyABC" .
    _:company2 <name> "The other company" .

    _:company1 <industry> "Manufacturing" .
    _:company1 <industry> "Fabricated Metal" .
    _:company1 <industry> "Machinery" .

    _:company2 <industry> "Manufacturing" .
    _:company2 <industry> "High Tech" .

    _:jack <works_for> _:company1 .
    _:ivy <works_for> _:company1 .
    _:zoe <works_for> _:company1 .

    _:jose <works_for> _:company2 .
    _:alexei <works_for> _:company2 .

    _:ivy <boss_of> _:jack .

    _:alexei <boss_of> _:jose .
  }
}

Dgraph会给node创建内部id,但是我们需要用某种方式在我们输入的数据中多次引用同一个node。这就是_:company1干的事

技术上来说,空节点是存在的。它们告诉Dgraph创建一个节点,给它一个内部id并确保它可以始终如一地被使用

在上传之后,标签_:company1 并不会存在于Dgraph中,我们也不能查询它。Dgraph已经把它替换为内部id,你可以查询这个uid

修改Dgraph中存储的数据被称作mutate data

2.3 外部识别符

Dgraph不支持直接设置node的uid。如果一个应用需要uid之外的唯一识别符,需要应用自己维护一个

在RDF中,Linked Open Data使用URL识别node

2.4 语言支持

在插入时,语言标签被用于string值

_:myID <an_edge> "something"@en .
_:myID <an_edge> "某物"@zh-Hans .

而在查询时,语言标签被用于边上

2.5 反向边

边是有向的,查询不能反向穿过一条边

有两种方式可以从两个方向查询一条边:

  • 将反向边加入schema,并且加入所有的反向数据
  • 在schema中使用@reverse关键字告诉Dgraph总是存储反向边
//ALTER
boss_of: uid @reverse .

执行schema mutation,Dgraph将会计算所有的反向边

an_edge 的反向边是 ~an_edge

对于数据模型来说,有些反向边总是有用的,例如boss_offriend

2.6 练习:整合已有数据

我们已经添加了一个新的schema,并且载入了一些公司数据,但是该怎么把之前的朋友数据跟公司数据整合到一起呢?

用前面的mutation创建的空节点是不行的,因为这些空节点并没有被持久化到存储中。当我们想访问之前创建的node时,我们需要用uid

因此我们不能用:

_:sarah <works_for> _:company1 .

而应该用:

uid-for-sarah <works_for> uid-for-company1 .

因为uid在Dgraph中是唯一的

这里的处理通常是通过编程实现的——查询数据获得uid,构建mutation,然后执行更新

//MUTATE
{
  set {
    ...michael... <boss_of> ...someone... .
    
    ...sarah... <works_for> ...company... .
  }
}

2.7 删除数据

在一个delete mutation中有三种删除操作:

  • <uid> <edge> <uid>/"value" . 删除一个triple
  • <uid> <edge> * . 删除一个给定边的所有triple
  • <uid> * * . 删除一个给定节点的所有triple
//mutate
{
  delete {
    # Delete a single triple
    <0x1c3eb> <name> "Steven" .
    <0x1c3eb> <age> "38" .

    # Delete all triples for a given edge
    <0x1c3eb> <friend> * .

    # Delete all triples for a given node
    <0x1c3eb> * * .
  }
}

2.8 Predicate

对于图中的任何一个节点,_predicate_会查询所有出边的name

注意,在设计一个图的时候schema或者意图是不同的,因为任何一个节点都可能没有其他节点的谓语

2.9 Expand Predicate

expand(...predicates...) 用于查询所有给定的谓语,而不是在查询中把它们列出来

调用expand(_all_)会查询在这个level匹配到的每个节点的所有出边。Expand可以嵌套,扩展到下个level的所有谓语

{
  expand(func: allofterms(name, "Michael")) {
    expand(_all_) {
      expand(_all_) {
        expand(_all_)
      }
    }
  }
}

接下来我们会看到如何使用带变量的expand查询某些特定的边

3. 更大的数据集

3.1 更大的数据集

首先,创建schema:

//ALTER
director.film: uid @reverse @count .
genre: uid @reverse .
initial_release_date: dateTime @index(year) .
name: string @index(term) .
starring: uid @count .

下载一百万电影的数据集,并导入:

$ cd ~/dgraph
$ wget "https://github.com/dgraph-io/tutorial/blob/master/resources/1million.rdf.gz?raw=true" -O 1million.rdf.gz -q
$ dgraph live -r 1million.rdf.gz --zero localhost:5080

3.2 Movie的schema

执行:

{
  q(func:allofterms(name@en, "Kathryn Bigelow")) {
    _predicate_
    }
}

返回:

{
  "data": {
    "q": [
      {
        "_predicate_": [
          "name",
          "director.film"
        ]
      }
    ]
  }
}

可以发现这个导演有name以及director.film,通过上面的schema可以知道那么是一个value,而director.film是一条指向film的边

扩展查询,再深入一层:

{
  q(func:allofterms(name@en, "Kathryn Bigelow")) {
    _predicate_
    director.film (first: 1) { _predicate_ }
    }
}
{
  "data": {
    "q": [
      {
        "_predicate_": [
          "name",
          "director.film"
        ],
        "director.film": [
          {
            "_predicate_": [
              "name",
              "starring",
              "initial_release_date",
              "genre"
            ]
          }
        ]
      }
    ]
  }
}

可以发现movile有name、initial_release_date两个value以及genre(流派)、starring(主演)两条边

要记住,可以使用如下语句查询所有schema:

schema {}
performance.actor
performance.character
performance.film

这三种边是干嘛的?这些边的起始节点没有value

4. 查询语句块、查询变量与聚合

4.1 多名查询块

执行接下来的查询,你需要先载入前面的一百万电影数据集

对于有多个标签q1...qn的多名查询块,返回的JSON结果会包含q1...qn的多个结果。如果多个结果有连接的部分,图形化界面会把多个结果连起来,但是JSON里是独立的

{
  caro(func: allofterms(name@en, "Marc Caro")) {
    name@en
    director.film {
      name@en
    }
  }
  jeunet(func: allofterms(name@en, "Jean-Pierre Jeunet")) {
    name@en
    director.film {
      name@en
    }
  }
}

如果一个语句块加了var标记,那么这个查询不会返回结果

查询变量是GraphQL+-最强大的特征之一。查询变量允许一个查询的结果被其他的查询使用,从而将多个查询连接起来。接下来的几页会详细介绍查询变量

4.2 查询变量

4.2.1 定义

查询结果可以存储在变量中,并在查询的任意地方使用

使用如下方式定义变量:

var_name as some_block { ... }

var_name是任意不重复的变量名,some_block是一个完整的查询或者一个匹配边的内部查询块

一旦被定义,变量可以在下列位置使用:

  • 在定义语句块的子块中
  • 另一个查询中
{
  A as not_a_real_query(...) {
    B as some_edge @filter(...) { # can't use C or B in this filter
      C as ... { 
        # A, B and C here
      }

      # A, B and C here
    }

    # A, B and C can be used in any blocks here
  }

  # A, B and C can be used in any other query block
}

变量在定义时并不会影响查询的语义

查询变量是通过uid(<variable-name>)被使用的。变量会计算出定义它的查询匹配到的所有uid。事实上,变量就是uid列表,而不是查询匹配到的子图。而且,变量会计算出整个查询匹配到的uid,而不是查询的某个分支匹配到的uid

如果定义了一个变量,但是没有使用它,会报错

4.2.2子块中的查询变量

子块中的查询变量允许某个level的查询将它的查询结果带到它的子块中进行过滤

例如,对于Jane Campion的所有电影中的演员集合,找到哪些演员同时出演了某部不是Jane Campion导演的电影是一个挑战

{
  coactors(func:allofterms(name@en, "Jane Campion")) @cascade {
    JC_films as director.film {      # JC_films = all Jane Campion's films
      starting_movie: name@en
      starring {
        JC_actors as performance.actor {      # JC_actors = all actors in all JC films
          actor : name@en
          actor.film {
            performance.film @filter(not uid(JC_films)) {
              film_together : name@en
              starring {
                # find a coactor who has been in some JC film
                performance.actor @filter(uid(JC_actors)) {
                  coactor_name: name@en
                }
              }
            }
          }
        }
      }
    }
  }
}

这个查询使用了所有Jane Campion的电影的集合JC_film,并在子块中使用Jane Campion的所有电影中的所有演员集合JC_actor

查询语句中的@cascade命令保证查询结果匹配查询的所有部分——如果不这样的话,那些没有co-actor的演员也会被返回

这个查询会返回各个演员自己(会把自己也当成coactor),以及其他的coastor

4.2.3 另一个查询块中的查询变量

让我们回一下上面那个查询,变量JC_actor计算Jane Campion的所有电影中的所有演员。无论我们在哪里用它,它都是一个全集

正确地使用变量的关键是:理解它们是全局的,会算出匹配这个边的所有的node;而不是本地的,会对每个Jane Campion电影计算出不同的集合

我们也可以在另外一个查询块中使用查询变量,可以当作root filter匹配到的node集合,也可以当作内部filter匹配到的node集合

Peter Jackson经常出现在他自己的电影中,通常只是背景或者一瞥,但是他的确在。下面这个查询显示所有的Peter Jackson导演,并且参演的电影

{
  PJ as var(func:allofterms(name@en, "Peter Jackson")) @normalize @cascade {
    F as director.film
  }

  peterJ(func: uid(PJ)) @normalize @cascade {
    name : name@en
    actor.film {
      performance.film @filter(uid(F)) {
        film_name: name@en
      }
      performance.character {
        character: name@en
      }
    }
  }
}

另一个查询块中的查询变量也可以用来重新组织结果:

{
  var(func: allofterms(name@en, "Taraji Henson")) {
    actor.film {
      F as performance.film {
        G as genre
      }
    }
  }

  Taraji_films_by_genre(func: uid(G)) {
    genre_name : name@en
    films : ~genre @filter(uid(F)) {
      film_name : name@en
    }
  }
}

我们已经学习了变量的两种常用的用途:过滤以及重新组织结果。它还有一种用途,那就是把两个查询连接起来,然后基于连接后的新结果提供新的视角

对于两个执导的电影中有同一个演员的导演(不一定是同一个电影)。许多导演是没有公共演员的,因此你需要从一些你确定的导演开始

{
        var(func: allofterms(name@en, "Peter Jackson")) {
      director.film{
          name@.            #可去掉
        starring{
          PJA as performance.actor{
            name@.          #可去掉
            }
        }
        }  
    }
  
      var(func: allofterms(name@en, "Martin Scorsese")) {
      director.film{
        name@.                  #可去掉
        starring{
          MSA as performance.actor{
            name@.              #可去掉
            }
        }
        }  
    }
  
    both(func: uid(PJA)) @filter(uid(MSA)){
        name@.
    }
  
  
}

如果同时需要列出演员出演过的两个导演的电影:

{
        var(func: allofterms(name@en, "Peter Jackson")) @cascade {
        PJF as director.film{
        #director.film{
          name@.
        starring{
          PJA as performance.actor{
            }
        }
        }  
    }
  
      var(func: allofterms(name@en, "Martin Scorsese")) @cascade {
        #director.film{
        MSF as director.film{
          name@.
        starring{
          MSA as performance.actor{
            }
        }
        }  
    }
  
    both(func: uid(PJA)) @filter(uid(MSA)){
     # _predicate_
      name@.
      #actor.film{_pridicate_
      actor.film {
        performance.film @filter(uid(PJF) OR uid(MSF)) {
        name@.
        }
        }
    }
}

首先,需要对两个子查询加@cascade,保证PJF、MSF获得的电影列表是需要的电影,之后再用@filter(uid(PJF) OR uid(MSF))得到每个演员演出的电影

uid(PJF) OR uid(MSF)可以简写成uid(PJF,MSF)

4.3 值变量

4.3.1 min与max

值变量的定义方式如下:

val_name as aggregation_function(...)

我们前面学习了存储uid的查询变量,下面我们将学习值变量,它们存储查询匹配到的值

值变量

值变量跟查询变量的工作方式是不同的。值变量是上下文独立的,事实上,值变量是uid到value的映射,我们可以在读取以及过滤值变量的时候使用这个映射。可以使用val(<variable-name>)获取一个值变量的value

在定义的时候,由于是在相应的uid的上下文中,值变量就像uid的相关值。在封闭的block中,值变量是uid到value的映射,而且必须是可以聚合的

值变量的聚合与index是独立的——无须建索引,值变量本来就对查询是可见的

  • minmax可以被应用于int, float, string, bool, 以及 date类型
{
  q(func: allofterms(name@en, "Ang Lee")) {
    director.film {
      uid
      name@en

      #对每个AngLee的电影的starring边计数
      num_actors as count(starring)

      # In this block, num_actors is the value calculated for this film.
      # The film with uid and name
    }

    # 在这里,num_actors是uid到value的映射,它不能直接被使用,但是可以对map的所有值进行聚合
    most_actors : max(val(num_actors))
  }

  # 如果要在另外一个查询中使用num_actors,需要保证它在一个film uid与值的映射有意义的上下文
}

4.3.2 sum与avg

sumavg只能被用于数据类型为intfloat的值变量

找出斯皮尔博客导演的电影数量,以及他执导的电影中演员的平均数,并判断哪个更大

{
  ss(func: eq(name@en,"Steven Spielberg")){
    films_count as count(director.film)
    director.film{
      actors_number as count(starring)
    }
    films_count : val(films_count)
    avg_actors_number : avg(val(actors_number))
  }
}

//或者:
{
  ID as var(func: eq(name@en,"Steven Spielberg")){
    films_count as count(director.film)    #值变量定义
    director.film{
      actors_number as count(starring)
    }
    avg_actors_number as avg(val(actors_number))
  }
  
  #ID是查询变量,是所有名为Steven Spielberg的uid集合
  get_result(func: uid(ID)){
    name : name@.
    films_count : val(films_count)
    avg_actors_number : val(avg_actors_number)
  }
}

尽管查询可以在一个block中完成,但是上面第二种写法展示了当值变量map的uid可以使用的情况下如何在别的bolck中使用值变量

4.3.3 filtering与ordering

如果上下文提供的uid是对的,值变量也可以用于过滤及排序

{
  ID as var(func: allofterms(name@en, "Steven")) {
    director.film {
      num_actors as count(starring)
    }
    average as avg(val(num_actors))
  }

  avs(func: uid(ID), orderdesc: val(average)) @filter(ge(val(average), 40)) @normalize {
    name : name@en
    average_actors : val(average)
    num_films : count(director.film)
  }
}

在这里,ID是所有名为Steven的导演的uid集合,average是这些uid到每个导演的平均值的集合。过滤、排序以及var(average)的结果在这个上下文中被计算,以获取各自的值

可以使用值变量来代替uid变量,uid(<value-variable>)会返回这个映射中的uid。例如,avs查询块可以写成:

avs(func: uid(average), orderdesc: val(average)) @filter(ge(val(average), 40)) @normalize {

4.3.4 数学函数

除了聚合函数 min, max, avg, 与 sum 之外,Dgraph还支持许多可以被应用于值变量的函数。这些需要使用math(...)包围,并保存在变量中

完整函数列表如下:

Operator Accepted Type Notes
+ - * / % intfloat
min max 除了 geobool之外的其他类型
< > <= >= == != 除了 geobool之外的其他类型 返回值为boolean
floor ceil ln exp sqrt intfloat
since date number of seconds (float) from the time specified
pow(a, b) intfloat a^b
logbase(a,b) intfloat log(a) to the base b
cond(a, b, c) a 必须是boolean selects b if a is true else c

注意,这些函数是针对值的,不是针对聚合值映射的。这些函数的结果是值变量,并且可以被聚合

{
    var(func:allofterms(name@en, "Jean-Pierre Jeunet")) {
        name@en
        films as director.film {
            stars as count(starring)
            directors as count(~director.film)
            ratio as math(stars / directors)
        }
    }

    best_ratio(func: uid(films), orderdesc: val(ratio)){
        name@en
        stars_per_dirctor : val(ratio)
    num_stars : val(stars)
    }
}

写一个查询,找到每个导演最新发布的电影,并根据发布日期排序:


{
  var(func: has(director.film)){ #这里可以加上@cascade,过滤掉没有发布日期的电影
    director.film{
        initial_release_date_ as initial_release_date
    }
    max_initial_release_date as max(val(initial_release_date_))
    }
  
  sorted_films(func: uid(max_initial_release_date), orderdesc: val(max_initial_release_date), first: 10)  {
    name@.
    val(max_initial_release_date)
    director.film @filter(eq(initial_release_date,val(max_initial_release_date))){
      #除了使用eq之外,还可以再次按initial_release_date排序,取出日期最大的
      #但是我感觉还是eq好,能保证两个查询块的数据一致
      #这里可能会返回多个,可以使用`first:1`限定
      name@.
    }
  }
}

4.4 GroupBy

groupby查询会将查询结果按分组元素聚合。例如这样的一个查询块:

director.film @groupby(genre) {
  a as count(uid)
}

它会顺着director.film边找到节点,根据genre将它们分到不同的组中,然后计算各个分组有多少节点

groupby块内,只允许执行聚合,而且count只能用于uid

查询返回的结果是分组后的边,以及聚合的值变量。上面的例子中,值变量就是genre到value的映射

通常,我们需要将groupby与另外一个查询结合使用,来获得值,而不是聚合

{
  var(func:allofterms(name@en, "Steven Spielberg")) {
    director.film @groupby(genre) {
      a as count(uid)
    }
  }

  byGenre(func: uid(a), orderdesc: val(a)) {
    name@en
    num_movies : val(a)
  }
}

4.5 expand与_predicate_

我们已经学过expand与_predicate_了,它们都可以与变量一起使用

Cherie Nowlan导演了电影,包括澳大利亚浪漫喜剧Thank God He Met Lizzie,但是没有参演任何电影。因此,一个expand他的所有谓语的查询只有name与director.film

{
  var(func:allofterms(name@en, "Cherie Nowlan")) {
    pred as _predicate_    #返回name与director.film
  }

  q(func:allofterms(name@en, "Cherie")) {  #这里与上面不是一个人
    expand(val(pred)) { expand(_all_) }  
    #由于只有director.film是边,所以expand只返回了director.film
  }
}

5.Search——更多查找node的方式

5.1 Index

当Dgraph通过filter搜索string、date或者其他值时,需要先建立索引来使查询更高效。我们已经在schema mutation中学习了指定index的方式

int, float, geodate 有默认的index,但是string可以选择index的方式。对于同一个string谓语,可以选择多个index

string可以使用下列索引类型:

  • term (默认index类型) 用于 alloftermsanyofterms
  • exact 用于不等,例如全字符串匹配
  • hash 类似exact,但是是hash字符串,用于长字符串
  • fulltext 用于全文搜索,例如alloftermsanyoftext
  • trigram 用于正则表达式(trigram 卦,三字铭)

多index是用@index参数指定的

修改movie数据集的schema,添加index

//ALERT
name: string @index(term, exact, fulltext, trigram) .

5.2 Term搜索

有了term索引,可以使用 alloftermsanyofterms 找到符合的string,本文全面的查询已经用到了

5.3 正则表达式

正则表达式需要trigram索引。trigram是一个有序的三个字符的序列

卦象有:tri, rig, igr, gra and ram

The trigrams of trigram are: tri, rig, igr, gra and ram

有效的正则表达式可以转换成一个对于index的trigram查询。Dgraph搜索trigram索引,获得可能匹配的项,然后再在这些项上执行全部的正则表达式

每个正则表达式必须至少满足一种trigram

仔细考虑正则表达式查询是一个好主意,一个查询匹配到的trigram越多,查询效率越低。因此,一个正则表达式加的trigram越多,Dgraph越能充分利用索引,提高查询性能

因此,写更高效的正则表达式通常意味着使用更长的子字符串,并尽可能地避免可选项

如果希望查询大小写敏感,需要在最后的 / 后面加上选项i

Natalie Portman是数据集中唯一的演了alien电影,并且在名字中含有alien的演员

{
  aliens(func: regexp(name@en, /.*alien.*/i)) @cascade {
    name@en
    ~genre {
      name@en
      starring {
        performance.actor @filter(regexp(name@en,
          /(.*ali.*e.*n.*)|(.*a.*lie.*n.*)|(.*a.*l.*ien.*)/i)) {
          name@en
        }
      }
    }
  }
}

5.4 Exact索引与inequality

exact索引可以用于string的不等关系

hash索引可以让strin上的eq过滤更快

{
  alpha(func: has(starring), orderasc: name@en)
    @filter(ge(name@en, "You") AND le(name@en, "Zoo"))
  {
    name@en
  }
}

5.5 全文搜索

5.5.1 全文搜索

全文搜索是Google用于网页的。这种查询很难用term匹配,因为这种查询试图遵从语言、语法以及时态。例如,“run”的全文搜索匹配项包含“run”、“running”、“ran”

全文搜索不会准确地匹配项,而是利用:

  • 词干:找到不同时态、单复数等词的公共部分,
  • 停止词:移除and、or、it等过于常见的词
{
  movie(func:alloftext(name@en, "the man runs"))
    @filter(has(genre))
  {
    name@en
  }
}

5.5.2 语言支持

全文搜索只能用于有词干处理器以及停止词列表的语言

到目前为止,Dgraph支持如下语言:

Language Country Code
Danish da
Dutch nl
English en
Finnish fi
French fr
German de
Hungarian hu
Italian it
Norwegian no
Portuguese pt
Romanian ro
Russian ru
Spanish es
Swedish sv
Turkish tr
Chinese zh
Japanese ja
Korean ko

5.6 Geo queries : Near

Comming soon

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容

  • 查询语言 Dgraph的GraphQL+-是一种基于facebook的GraphQL的图查询语言。GraphQL并...
    羽_da59阅读 6,619评论 0 3
  • ORA-00001: 违反唯一约束条件 (.) 错误说明:当在唯一索引所对应的列上键入重复值时,会触发此异常。 O...
    我想起个好名字阅读 5,141评论 0 9
  • 周易第三十一卦:咸---婚姻的故事(上) 兑上艮下。“咸”:亨,利贞。取女吉。 初六:咸其拇。 六二:咸其腓,凶;...
    田园读书人阅读 17,994评论 5 14
  • 2018.7.16 星期一 晴 今天美术班大画班第一天开始了。一三五大画,二四六古筝,真够我家姑娘忙活的。...
    石雨琪妈妈阅读 194评论 0 0
  • 便是有一丝狂意,一丝洒脱,八分烟火气。
    若心成文阅读 191评论 0 1