预览

快速开始

部署到 Dgraph 云

@auth 动词

在变更中使用 @auth 动词

API

API

请求和请求响应

GraphQL 变量

GraphQL 片段

一次请求多次操作

GraphQL 错误

DQL

开始

这是一份快速开始的手册,你可以在这里找到开始系列的教程。

Dgraph

Dgraph从头开始设计,以便在生产环境中运行,它是带有GraphQL后端的原生GraphQL数据库。它是开源的,可扩展的,分布式的,高可用性的和具有闪电般的速度。

Dgraph集群由不同的节点(Zero, AlphaRatel)组成,每个节点都有不同的用途:

  • Dgraph Zero控制Dgraph集群,将服务器分配给一个组,并在服务器组之间重新平衡数据。
  • Dgraph Alpha托管谓词(predicate)和索引(index)。谓词是与节点(node)关联的属性(facets)或两个节点之间的关系(relation)。索引是可以与谓词关联的标记器,可以使用适当的函数启用过滤。
  • Ratel服务于UI来运行查询、变更和更改Schema

你至少需要一个Dgraph Zero和一个Dgraph Alpha来启动Dgraph数据库。

这里有一个四个步骤的教程来帮助你启动和运行。

这是一个运行Dgraph的快速入门指南。如果你想了解互动的攻略,那就来看看吧。

提示:本指南针对的是强大的Dgraph查询语言,DQLFacebook创建的一种查询语言GraphQL的变体。您可以从dgraph.io/ GraphQL中找到开始使用GraphQL的说明。

步骤一:运行 Dgraph

安装和运行Dgraph有几种方法,都可以在下载页面中找到。

Dgraph启动并运行的最简单方法是使用dgraph/standalone这个docker映像。如果您还没有Docker,请按照这里的说明安装它:

docker run --rm -it -p "8080:8080" -p "9080:9080" -p "8000:8000" -v ~/dgraph:/dgraph "dgraph/standalone:v21.03.2"

注意:此独立映像仅用于快速启动目的。不建议在生产环境中使用。

这将启动一个容器,其中运行Dgraph AlphaDgraph ZeroRatel。您会发现Dgraph数据存储在主目录的一个名为Dgraph的文件夹中。

使用变更

提示:Dgraph运行起来后,您可以通过http://localhost:8000访问Ratel。它允许基于浏览器的查询、变化和可视化。您可以通过命令行中的curl或将突变数据粘贴到Ratel中来运行下面的突变和查询。

数据集

数据集是一个电影图,其中的图节点是类型导演、演员、类型或电影的实体。

Dgraph 中检索数据

更改存储在Dgraph中的数据叫做变更(mutation)。到目前为止,Dgraph支持通过两种方式变更数据:RDFJSON。下面的RDF变更存储关于星球大战系列的前三次发行和星际迷航系列电影之一的信息。提交RDF变更(通过curlRatel UImutate选项卡)将在Dgraph中存储数据:

curl "localhost:8080/mutate?commitNow=true" --silent --request POST \
 --header  "Content-Type: application/rdf" \
 --data $'
{
  set {
   _:luke <name> "Luke Skywalker" .
   _:luke <dgraph.type> "Person" .
   _:leia <name> "Princess Leia" .
   _:leia <dgraph.type> "Person" .
   _:han <name> "Han Solo" .
   _:han <dgraph.type> "Person" .
   _:lucas <name> "George Lucas" .
   _:lucas <dgraph.type> "Person" .
   _:irvin <name> "Irvin Kernshner" .
   _:irvin <dgraph.type> "Person" .
   _:richard <name> "Richard Marquand" .
   _:richard <dgraph.type> "Person" .

   _:sw1 <name> "Star Wars: Episode IV - A New Hope" .
   _:sw1 <release_date> "1977-05-25" .
   _:sw1 <revenue> "775000000" .
   _:sw1 <running_time> "121" .
   _:sw1 <starring> _:luke .
   _:sw1 <starring> _:leia .
   _:sw1 <starring> _:han .
   _:sw1 <director> _:lucas .
   _:sw1 <dgraph.type> "Film" .

   _:sw2 <name> "Star Wars: Episode V - The Empire Strikes Back" .
   _:sw2 <release_date> "1980-05-21" .
   _:sw2 <revenue> "534000000" .
   _:sw2 <running_time> "124" .
   _:sw2 <starring> _:luke .
   _:sw2 <starring> _:leia .
   _:sw2 <starring> _:han .
   _:sw2 <director> _:irvin .
   _:sw2 <dgraph.type> "Film" .

   _:sw3 <name> "Star Wars: Episode VI - Return of the Jedi" .
   _:sw3 <release_date> "1983-05-25" .
   _:sw3 <revenue> "572000000" .
   _:sw3 <running_time> "131" .
   _:sw3 <starring> _:luke .
   _:sw3 <starring> _:leia .
   _:sw3 <starring> _:han .
   _:sw3 <director> _:richard .
   _:sw3 <dgraph.type> "Film" .

   _:st1 <name> "Star Trek: The Motion Picture" .
   _:st1 <release_date> "1979-12-07" .
   _:st1 <revenue> "139000000" .
   _:st1 <running_time> "132" .
   _:st1 <dgraph.type> "Film" .
  }
}
' | python -m json.tool | less

提示:要通过curl使用文件提交RDF/JSON变更,可以使用curl选项--data-binary @/path/ To /mutation--data-binary选项跳过curl的默认url编码,包括删除所有换行符。因此,通过使用--data-binary选项,您可以在文本中使用#注释,因为使用--data选项,文本中第一个#之后的任何内容都将出现在同一行,因此将被视为一个长注释。

步骤三:修改Schema

修改Schema,在某些数据上添加索引,这样查询就可以使用术语匹配、过滤和排序。

curl "localhost:8080/alter" --silent --request POST \
  --data $'
name: string @index(term) .
release_date: datetime @index(year) .
revenue: float .
running_time: int .
starring: [uid] .
director: [uid] .

type Person {
  name
}

type Film {
  name
  release_date
  revenue
  running_time
  starring
  director
}
' | python -m json.tool | less

提示:要从Ratel UI提交Schema``,请转到Schema页面,单击批量编辑,然后粘贴Schema

步骤四:运行查询

查询所有电影

运行此查询以获取所有电影。该查询列出了所有具有主演优势的电影:

提示:您也可以在Ratel UIquery选项卡中运行DQL查询。

curl "localhost:8080/query" --silent --request POST \
  --header "Content-Type: application/dql" \
  --data $'
{
 me(func: has(starring)) {
   name
  }
}
' | python -m json.tool | less

注意GraphQL+-已重命名为Dgraph查询语言(DQL)。虽然application/dql是Content-Type头的首选值,但我们将继续支持Content-Type: application/graphql+-,以避免进行破坏性的更改。

获得1980年之后发行的所有电影

运行这个查询可以得到“1980年”之后发行的“星球大战”电影。在用户界面中尝试它,以图形的形式查看结果:

curl "localhost:8080/query" --silent --request POST \
  --header "Content-Type: application/dql" \
  --data $'
{
  me(func: allofterms(name, "Star Wars"), orderasc: release_date) @filter(ge(release_date, "1980")) {
    name
    release_date
    revenue
    running_time
    director {
     name
    }
    starring (orderasc: name) {
     name
    }
  }
}
' | python -m json.tool | less

返回结果:

{
  "data":{
    "me":[
      {
        "name":"Star Wars: Episode V - The Empire Strikes Back",
        "release_date":"1980-05-21T00:00:00Z",
        "revenue":534000000.0,
        "running_time":124,
        "director":[
          {
            "name":"Irvin Kernshner"
          }
        ],
        "starring":[
          {
            "name":"Han Solo"
          },
          {
            "name":"Luke Skywalker"
          },
          {
            "name":"Princess Leia"
          }
        ]
      },
      {
        "name":"Star Wars: Episode VI - Return of the Jedi",
        "release_date":"1983-05-25T00:00:00Z",
        "revenue":572000000.0,
        "running_time":131,
        "director":[
          {
            "name":"Richard Marquand"
          }
        ],
        "starring":[
          {
            "name":"Han Solo"
          },
          {
            "name":"Luke Skywalker"
          },
          {
            "name":"Princess Leia"
          }
        ]
      }
    ]
  }
}

就是这样!在这四个步骤中,我们设置了Dgraph、添加了一些数据、设置了一个Schema并查询了该数据。

下一步该做什么

  • 转到Clients查看如何从应用程序与Dgraph通信。
  • 请参阅本教程,了解如何在Dgraph中编写查询。
  • 查询语言参考中还可以找到更广泛的查询。
  • 如果希望在集群中运行Dgraph,请参阅部署。

DQL 基础

Dgraph Query Language, DQL查询语言(旧称GraphQL+-),是一门基于GraphQL的查询语言。GraphQL不是专门为图数据库而设计的,但是它的解析规则和图查询非常相似(查询语法的解析,schema的校验,子图塑型返回等),所以我们就直接在GraphQL的基础上增删一些特性,创造了DQL

DQL目前还在开发中,我们在未来可以会增加一些新特性,以及简化目前的版本。

一个小示例

这个小例子使用了一个包含2.1千万条元组的电影和戏子的数据库。负载这个数据库的Dgraph是跑在云https://play.dgraph.io/上面的。你也可以选择本地运行

定义数据库的数据结构

本例子使用下面的schema定义数据库的结构:

# Define Directives and index

director.film: [uid] @reverse .
actor.film: [uid] @count .
genre: [uid] @reverse .
initial_release_date: dateTime @index(year) .
name: string @index(exact, term) @lang .
starring: [uid] .
performance.film: [uid] .
performance.character_note: string .
performance.character: [uid] .
performance.actor: [uid] .
performance.special_performance_type: [uid] .
type: [uid] .

# Define Types

type Person {
    name
    director.film
    actor.film
}

type Movie {
    name
    initial_release_date
    genre
    starring
}

type Genre {
    name
}

type Performance {
    performance.film
    performance.character
    performance.actor
}

开始查询

对一些node进行的查询是建立在图数据库的搜索规则,匹配模式上的,返回一个图作为查询结果。 一个查询是由几个查询块组成的:先由一个根的查询块查询到一系列符合查询规则的nodes,然后再在这些nodes上面应用图匹配和图过滤。

更多的查询列子可以参照查询设计原则

错误码

当你使用DQL查询的时候,服务端可能会返回一个查询错误。在服务端返回的错误对象JSON中有一个code属性,code通常有两个取值

  1. ErrorInvalidRequest: 可能是错误的请求(400)或者是服务器内部的错误(500)
  2. Error: 服务器内部的错误(500)

例如,当你提交请求解析错误时,服务端返回:

{
  "errors": [
    {
      "message": "while lexing {\nq(func: has(\"test)){\nuid\n}\n} at line 2 column 12: Unexpected end of input.",
      "extensions": {
        "code": "ErrorInvalidRequest"
      }
    }
  ],
  "data": null
}
Error

这是一个很少见的错误,一般是服务器序列化GOstruct到JSON对象时发生错误导致

ErrorInvalidRequst

返回值

每一个查询都有一个名字,和你发送给后端的查询的位于根块的名字一致。 如果一条边是一个值类型,那么这条边将直接返回。 在本例子的数据库中,边(edges)连接电影movies到导演directors和戏子actorsmovies拥有namerelease date更新时间,还有它在出名的影视数据库的id 下面是一个名为bladerunner的请求:通过比对电影的名字Blade Runner获取电影信息

{
  bladerunner(func: eq(name@en, "Blade Runner")) {
    uid
    name@en
    initial_release_date
    netflix_id
  }
}

它将会返回

{
  "data": {
    "bladerunner": [
      {
        "uid": "0x394c",
        "name@en": "Blade Runner",
        "initial_release_date": "1982-06-25T00:00:00Z",
        "netflix_id": "70083726"
      }
    ]
  }
}

上面的请求首先通过索引找到name边等于Blade Runnernode,然后返回该node的出边属性。每一个node都有一个唯一的64位的id,上面的请求中,uid边返回了该nodeid,如果一个nodeid已知,可以通过uid函数直接查找该节点。

{
  bladerunner(func: uid(0x394c)) {
    uid
    name@en
    initial_release_date
    netflix_id
  }
}

上面的请求将返回

{
  "data": {
    "bladerunner": [
      {
        "uid": "0x394c",
        "name@en": "Blade Runner",
        "initial_release_date": "1982-06-25T00:00:00Z",
        "netflix_id": "70083726"
      }
    ]
  }
}

一个查询可能匹配到多个node,下面查询所有名字含有BladeRunner的节点。

{
  bladerunner(func: anyofterms(name@en, "Blade Runner")) {
    uid
    name@en
    initial_release_date
    netflix_id
  }
}
{
  "data": {
    "bladerunner": [
      {
        "uid": "0xd5f",
        "name@en": "The Runner"
      },
      {
        "uid": "0x394c",
        "name@en": "Blade Runner",
        "initial_release_date": "1982-06-25T00:00:00Z",
        "netflix_id": "70083726"
      },
      ....
    ]
  }
}

uid函数也能同时指定多个id

{
  movies(func: uid(0xb5849, 0x394c)) {
    uid
    name@en
    initial_release_date
    netflix_id
  }
}

返回

{
  "data": {
    "movies": [
      {
        "uid": "0x394c",
        "name@en": "Blade Runner",
        "initial_release_date": "1982-06-25T00:00:00Z",
        "netflix_id": "70083726"
      },
      {
        "uid": "0xb5849",
        "name@en": "James Cameron's Explorers: From the Titanic to the Moon",
        "initial_release_date": "2006-01-01T00:00:00Z",
        "netflix_id": "70058064"
      }
    ]
  }
}

展开图节点的边

一个查询能通过嵌套查询块{}从一个节点扩展到另一个节点 下面查询Blade Runner中的演员和人物:该查询首先查询出拥有名字为Blade Runner这条边的节点,然后从这个节点沿着向外的主演边starring找到演员饰演的角色的节点。从这些节点里面再沿着perfromance.actor边和performance.character边展开,找到演员的名字和在剧中饰演的角色的名字。

{
  brCharacters(func: eq(name@en, "Blade Runner")) {
    name@en
    initial_release_date
    starring {
      performance.actor {
        name@en  # actor name
      }
      performance.character {
        name@en  # character name
      }
    }
  }
}
{
  "data": {
    "brCharacters": [
      {
        "name@en": "Blade Runner",
        "initial_release_date": "1982-06-25T00:00:00Z",
        "starring": [
          {
            "performance.actor": [
              {
                "name@en": "John Edward Allen"
              }
            ],
            "performance.character": [
              {
                "name@en": "Kaiser"
              }
            ]
          },
          {
            "performance.actor": [
              {
                "name@en": "Joanna Cassidy"
              }
            ],
            "performance.character": [
              {
                "name@en": "Zhora"
              }
            ]
          }
          ...
        ]
      }
    ]
  }
}

注释

所有在 #后面的都是注释

使用过滤

查询根查找一组初始节点,查询通过返回值并沿着边继续查询————查询中到达的任何节点都是在根处搜索之后遍历找到的。找到的节点可以通过应用@filter过滤,可以在根节点之后或任何边进行过滤。

查询示例:《银翼杀手》导演雷德利·斯科特在2000年之前发行的电影:

{
  scott(func: eq(name@en, "Ridley Scott")) {
    name@en
    initial_release_date
    director.film @filter(le(initial_release_date, "2000")) {
      name@en
      initial_release_date
    }
  }
}

将返回:

{
  "data": {
    "scott": [
      {
        "name@en": "Ridley Scott",
        "director.film": [
          {
            "name@en": "Blade Runner",
            "initial_release_date": "1982-06-25T00:00:00Z"
          },
          {
            "name@en": "Alien",
            "initial_release_date": "1979-05-25T00:00:00Z"
          },
          {
            "name@en": "Black Rain",
            "initial_release_date": "1989-09-22T00:00:00Z"
          },
          {
            "name@en": "White Squall",
            "initial_release_date": "1996-02-02T00:00:00Z"
          },
          {
            "name@en": "Thelma & Louise",
            "initial_release_date": "1991-05-24T00:00:00Z"
          },
          {
            "name@en": "G.I. Jane",
            "initial_release_date": "1997-08-22T00:00:00Z"
          },
          {
            "name@en": "1492 Conquest of Paradise",
            "initial_release_date": "1992-10-08T00:00:00Z"
          },
          {
            "name@en": "The Duellists",
            "initial_release_date": "1977-12-01T00:00:00Z"
          },
          {
            "name@en": "Someone to Watch Over Me",
            "initial_release_date": "1987-10-09T00:00:00Z"
          },
          {
            "name@en": "Legend",
            "initial_release_date": "1985-08-28T00:00:00Z"
          },
          {
            "name@en": "Boy and Bicycle",
            "initial_release_date": "1997-09-07T00:00:00Z"
          }
        ]
      },
      {
        "name@en": "Ridley Scott"
      }
    ]
  }
}

查询示例:2000年之前上映的片名为“刀锋战士”或“奔跑者”的电影:

{
  bladerunner(func: anyofterms(name@en, "Blade Runner")) @filter(le(initial_release_date, "2000")) {
    uid
    name@en
    initial_release_date
    netflix_id
  }
}

将返回:

{
  "data": {
    "bladerunner": [
      {
        "uid": "0x394c",
        "name@en": "Blade Runner",
        "initial_release_date": "1982-06-25T00:00:00Z",
        "netflix_id": "70083726"
      },
      {
        "uid": "0x7160",
        "name@en": "Blade",
        "initial_release_date": "1973-12-01T00:00:00Z"
      },
      {
        "uid": "0x15adb",
        "name@en": "Lone Runner",
        "initial_release_date": "1986-01-01T00:00:00Z",
        "netflix_id": "70146965"
      },
      {
        "uid": "0x16f0c",
        "name@en": "Revenge of the Bushido Blade",
        "initial_release_date": "1980-01-01T00:00:00Z",
        "netflix_id": "70100967"
      }
      ...
    ]
  }
}

多语言支持

注意:必须在Schema中指定@lang指令来查询或修改带有语言标记的谓词。

Dgraph支持UTF-8

使用以下规则指定返回语言的优先顺序:

  • 最多只会返回一个结果(除了语言列表被设置为*的情况)。
  • 从左到右考虑首选项列表:如果没有找到给定语言的值,则考虑列表中的下一种语言。
  • 如果在任何指定的语言中没有值,则不返回值。
  • 最后一个.表示返回没有指定语言的值,或者如果没有没有语言的值,则返回some语言的值。
  • 将语言列表值设置为*将返回该谓词的所有值及其语言。也会返回没有语言标记的值。 例如:
  • name: 查找一个untagged的字符串;如果没有untagged的值存在,则不返回任何值。
  • name@.: 查找一个未标记的字符串,然后是任何语言。
  • name@en: 查找zh标记的字符串;如果不存在带en标记的字符串,则不返回任何内容。
  • name@en:.: 查找en,然后untagged,然后是任何语言。
  • name@en:pl: 查找en,然后是pl,否则什么都不返回。
  • name@*: 查找该谓词的所有值,并返回它们及其语言。例如,如果有两个语言为enhi的值,则该查询将返回两个名为name@enname@hi的键。

注意,在函数中,语言列表(包括@*符号)是不允许的。无标记谓词、单语言标记和.符号如上所述。 在全文搜索函数(alloftextanyoftext)中,当没有指定语言(untagged@.)时,将使用默认的(英文)全文标记器。这并不意味着在查询无标记值时将搜索带有en标记的值,而是将无标记值视为英语文本。如果您不希望出现这种情况,可以为所需的语言使用适当的标记,用于修改和查询值。

查询示例:宝莱坞导演和演员法尔汉·阿赫塔尔(Farhan Akhtar)的一些电影有俄语、印地语和英语的名字,其他的没有:

{
  q(func: allofterms(name@en, "Farhan Akhtar")) {
    name@.
    director.film {
      name@ru:hi:en
      name@en
      name@hi
      name@ru
    }
  }
}

将返回:

{
  "data": {
    "q": [
      {
        "name@.": "Farhan Akhtar",
        "director.film": [
          {
            "name@ru:hi:en": "दिल चाहता है",
            "name@en": "Dil Chahta Hai",
            "name@hi": "दिल चाहता है"
          },
          {
            "name@ru:hi:en": "Дон. Главарь мафии 2",
            "name@en": "Don 2",
            "name@hi": "डॉन २",
            "name@ru": "Дон. Главарь мафии 2"
          },
          {
            "name@ru:hi:en": "पोज़िटिव",
            "name@en": "Positive",
            "name@hi": "पोज़िटिव"
          },
          {
            "name@ru:hi:en": "लक्ष्य",
            "name@en": "Lakshya",
            "name@hi": "लक्ष्य"
          },
          {
            "name@ru:hi:en": "Дон. Главарь мафии",
            "name@en": "Don: The Chase Begins Again",
            "name@hi": "डॉन",
            "name@ru": "Дон. Главарь мафии"
          }
        ]
      }
    ]
  }
}

DQL 基础

Dgraph Query Language, DQL查询语言(旧称GraphQL+-),是一门基于GraphQL的查询语言。GraphQL不是专门为图数据库而设计的,但是它的解析规则和图查询非常相似(查询语法的解析,schema的校验,子图塑型返回等),所以我们就直接在GraphQL的基础上增删一些特性,创造了DQL

DQL目前还在开发中,我们在未来可以会增加一些新特性,以及简化目前的版本。

一个小示例

这个小例子使用了一个包含2.1千万条元组的电影和戏子的数据库。负载这个数据库的Dgraph是跑在云https://play.dgraph.io/上面的。你也可以选择本地运行

定义数据库的数据结构

本例子使用下面的schema定义数据库的结构:

# Define Directives and index

director.film: [uid] @reverse .
actor.film: [uid] @count .
genre: [uid] @reverse .
initial_release_date: dateTime @index(year) .
name: string @index(exact, term) @lang .
starring: [uid] .
performance.film: [uid] .
performance.character_note: string .
performance.character: [uid] .
performance.actor: [uid] .
performance.special_performance_type: [uid] .
type: [uid] .

# Define Types

type Person {
    name
    director.film
    actor.film
}

type Movie {
    name
    initial_release_date
    genre
    starring
}

type Genre {
    name
}

type Performance {
    performance.film
    performance.character
    performance.actor
}

开始查询

对一些node进行的查询是建立在图数据库的搜索规则,匹配模式上的,返回一个图作为查询结果。 一个查询是由几个查询块组成的:先由一个根的查询块查询到一系列符合查询规则的nodes,然后再在这些nodes上面应用图匹配和图过滤。

更多的查询列子可以参照查询设计原则

错误码

当你使用DQL查询的时候,服务端可能会返回一个查询错误。在服务端返回的错误对象JSON中有一个code属性,code通常有两个取值

  1. ErrorInvalidRequest: 可能是错误的请求(400)或者是服务器内部的错误(500)
  2. Error: 服务器内部的错误(500)

例如,当你提交请求解析错误时,服务端返回:

{
  "errors": [
    {
      "message": "while lexing {\nq(func: has(\"test)){\nuid\n}\n} at line 2 column 12: Unexpected end of input.",
      "extensions": {
        "code": "ErrorInvalidRequest"
      }
    }
  ],
  "data": null
}
Error

这是一个很少见的错误,一般是服务器序列化GOstruct到JSON对象时发生错误导致

ErrorInvalidRequst

返回值

每一个查询都有一个名字,和你发送给后端的查询的位于根块的名字一致。 如果一条边是一个值类型,那么这条边将直接返回。 在本例子的数据库中,边(edges)连接电影movies到导演directors和戏子actorsmovies拥有namerelease date更新时间,还有它在出名的影视数据库的id 下面是一个名为bladerunner的请求:通过比对电影的名字Blade Runner获取电影信息

{
  bladerunner(func: eq(name@en, "Blade Runner")) {
    uid
    name@en
    initial_release_date
    netflix_id
  }
}

它将会返回

{
  "data": {
    "bladerunner": [
      {
        "uid": "0x394c",
        "name@en": "Blade Runner",
        "initial_release_date": "1982-06-25T00:00:00Z",
        "netflix_id": "70083726"
      }
    ]
  }
}

上面的请求首先通过索引找到name边等于Blade Runnernode,然后返回该node的出边属性。每一个node都有一个唯一的64位的id,上面的请求中,uid边返回了该nodeid,如果一个nodeid已知,可以通过uid函数直接查找该节点。

{
  bladerunner(func: uid(0x394c)) {
    uid
    name@en
    initial_release_date
    netflix_id
  }
}

上面的请求将返回

{
  "data": {
    "bladerunner": [
      {
        "uid": "0x394c",
        "name@en": "Blade Runner",
        "initial_release_date": "1982-06-25T00:00:00Z",
        "netflix_id": "70083726"
      }
    ]
  }
}

一个查询可能匹配到多个node,下面查询所有名字含有BladeRunner的节点。

{
  bladerunner(func: anyofterms(name@en, "Blade Runner")) {
    uid
    name@en
    initial_release_date
    netflix_id
  }
}
{
  "data": {
    "bladerunner": [
      {
        "uid": "0xd5f",
        "name@en": "The Runner"
      },
      {
        "uid": "0x394c",
        "name@en": "Blade Runner",
        "initial_release_date": "1982-06-25T00:00:00Z",
        "netflix_id": "70083726"
      },
      ....
    ]
  }
}

uid函数也能同时指定多个id

{
  movies(func: uid(0xb5849, 0x394c)) {
    uid
    name@en
    initial_release_date
    netflix_id
  }
}

返回

{
  "data": {
    "movies": [
      {
        "uid": "0x394c",
        "name@en": "Blade Runner",
        "initial_release_date": "1982-06-25T00:00:00Z",
        "netflix_id": "70083726"
      },
      {
        "uid": "0xb5849",
        "name@en": "James Cameron's Explorers: From the Titanic to the Moon",
        "initial_release_date": "2006-01-01T00:00:00Z",
        "netflix_id": "70058064"
      }
    ]
  }
}

展开图节点的边

一个查询能通过嵌套查询块{}从一个节点扩展到另一个节点 下面查询Blade Runner中的演员和人物:该查询首先查询出拥有名字为Blade Runner这条边的节点,然后从这个节点沿着向外的主演边starring找到演员饰演的角色的节点。从这些节点里面再沿着perfromance.actor边和performance.character边展开,找到演员的名字和在剧中饰演的角色的名字。

{
  brCharacters(func: eq(name@en, "Blade Runner")) {
    name@en
    initial_release_date
    starring {
      performance.actor {
        name@en  # actor name
      }
      performance.character {
        name@en  # character name
      }
    }
  }
}
{
  "data": {
    "brCharacters": [
      {
        "name@en": "Blade Runner",
        "initial_release_date": "1982-06-25T00:00:00Z",
        "starring": [
          {
            "performance.actor": [
              {
                "name@en": "John Edward Allen"
              }
            ],
            "performance.character": [
              {
                "name@en": "Kaiser"
              }
            ]
          },
          {
            "performance.actor": [
              {
                "name@en": "Joanna Cassidy"
              }
            ],
            "performance.character": [
              {
                "name@en": "Zhora"
              }
            ]
          }
          ...
        ]
      }
    ]
  }
}

注释

所有在 #后面的都是注释

使用过滤

查询根查找一组初始节点,查询通过返回值并沿着边继续查询————查询中到达的任何节点都是在根处搜索之后遍历找到的。找到的节点可以通过应用@filter过滤,可以在根节点之后或任何边进行过滤。

查询示例:《银翼杀手》导演雷德利·斯科特在2000年之前发行的电影:

{
  scott(func: eq(name@en, "Ridley Scott")) {
    name@en
    initial_release_date
    director.film @filter(le(initial_release_date, "2000")) {
      name@en
      initial_release_date
    }
  }
}

将返回:

{
  "data": {
    "scott": [
      {
        "name@en": "Ridley Scott",
        "director.film": [
          {
            "name@en": "Blade Runner",
            "initial_release_date": "1982-06-25T00:00:00Z"
          },
          {
            "name@en": "Alien",
            "initial_release_date": "1979-05-25T00:00:00Z"
          },
          {
            "name@en": "Black Rain",
            "initial_release_date": "1989-09-22T00:00:00Z"
          },
          {
            "name@en": "White Squall",
            "initial_release_date": "1996-02-02T00:00:00Z"
          },
          {
            "name@en": "Thelma & Louise",
            "initial_release_date": "1991-05-24T00:00:00Z"
          },
          {
            "name@en": "G.I. Jane",
            "initial_release_date": "1997-08-22T00:00:00Z"
          },
          {
            "name@en": "1492 Conquest of Paradise",
            "initial_release_date": "1992-10-08T00:00:00Z"
          },
          {
            "name@en": "The Duellists",
            "initial_release_date": "1977-12-01T00:00:00Z"
          },
          {
            "name@en": "Someone to Watch Over Me",
            "initial_release_date": "1987-10-09T00:00:00Z"
          },
          {
            "name@en": "Legend",
            "initial_release_date": "1985-08-28T00:00:00Z"
          },
          {
            "name@en": "Boy and Bicycle",
            "initial_release_date": "1997-09-07T00:00:00Z"
          }
        ]
      },
      {
        "name@en": "Ridley Scott"
      }
    ]
  }
}

查询示例:2000年之前上映的片名为“刀锋战士”或“奔跑者”的电影:

{
  bladerunner(func: anyofterms(name@en, "Blade Runner")) @filter(le(initial_release_date, "2000")) {
    uid
    name@en
    initial_release_date
    netflix_id
  }
}

将返回:

{
  "data": {
    "bladerunner": [
      {
        "uid": "0x394c",
        "name@en": "Blade Runner",
        "initial_release_date": "1982-06-25T00:00:00Z",
        "netflix_id": "70083726"
      },
      {
        "uid": "0x7160",
        "name@en": "Blade",
        "initial_release_date": "1973-12-01T00:00:00Z"
      },
      {
        "uid": "0x15adb",
        "name@en": "Lone Runner",
        "initial_release_date": "1986-01-01T00:00:00Z",
        "netflix_id": "70146965"
      },
      {
        "uid": "0x16f0c",
        "name@en": "Revenge of the Bushido Blade",
        "initial_release_date": "1980-01-01T00:00:00Z",
        "netflix_id": "70100967"
      }
      ...
    ]
  }
}

多语言支持

注意:必须在Schema中指定@lang指令来查询或修改带有语言标记的谓词。

Dgraph支持UTF-8

使用以下规则指定返回语言的优先顺序:

  • 最多只会返回一个结果(除了语言列表被设置为*的情况)。
  • 从左到右考虑首选项列表:如果没有找到给定语言的值,则考虑列表中的下一种语言。
  • 如果在任何指定的语言中没有值,则不返回值。
  • 最后一个.表示返回没有指定语言的值,或者如果没有没有语言的值,则返回some语言的值。
  • 将语言列表值设置为*将返回该谓词的所有值及其语言。也会返回没有语言标记的值。 例如:
  • name: 查找一个untagged的字符串;如果没有untagged的值存在,则不返回任何值。
  • name@.: 查找一个未标记的字符串,然后是任何语言。
  • name@en: 查找zh标记的字符串;如果不存在带en标记的字符串,则不返回任何内容。
  • name@en:.: 查找en,然后untagged,然后是任何语言。
  • name@en:pl: 查找en,然后是pl,否则什么都不返回。
  • name@*: 查找该谓词的所有值,并返回它们及其语言。例如,如果有两个语言为enhi的值,则该查询将返回两个名为name@enname@hi的键。

注意,在函数中,语言列表(包括@*符号)是不允许的。无标记谓词、单语言标记和.符号如上所述。 在全文搜索函数(alloftextanyoftext)中,当没有指定语言(untagged@.)时,将使用默认的(英文)全文标记器。这并不意味着在查询无标记值时将搜索带有en标记的值,而是将无标记值视为英语文本。如果您不希望出现这种情况,可以为所需的语言使用适当的标记,用于修改和查询值。

查询示例:宝莱坞导演和演员法尔汉·阿赫塔尔(Farhan Akhtar)的一些电影有俄语、印地语和英语的名字,其他的没有:

{
  q(func: allofterms(name@en, "Farhan Akhtar")) {
    name@.
    director.film {
      name@ru:hi:en
      name@en
      name@hi
      name@ru
    }
  }
}

将返回:

{
  "data": {
    "q": [
      {
        "name@.": "Farhan Akhtar",
        "director.film": [
          {
            "name@ru:hi:en": "दिल चाहता है",
            "name@en": "Dil Chahta Hai",
            "name@hi": "दिल चाहता है"
          },
          {
            "name@ru:hi:en": "Дон. Главарь мафии 2",
            "name@en": "Don 2",
            "name@hi": "डॉन २",
            "name@ru": "Дон. Главарь мафии 2"
          },
          {
            "name@ru:hi:en": "पोज़िटिव",
            "name@en": "Positive",
            "name@hi": "पोज़िटिव"
          },
          {
            "name@ru:hi:en": "लक्ष्य",
            "name@en": "Lakshya",
            "name@hi": "लक्ष्य"
          },
          {
            "name@ru:hi:en": "Дон. Главарь мафии",
            "name@en": "Don: The Chase Begins Again",
            "name@hi": "डॉन",
            "name@ru": "Дон. Главарь мафии"
          }
        ]
      }
    ]
  }
}

函数

函数允许基于节点或变量的属性进行筛选。函数可以应用于根查询或过滤器中。

Dgraph v1.2.0添加了对非索引谓词上的过滤器的支持。

根查询(又名func:)中的比较函数(eq, ge, gt, le, lt)只能应用于索引谓词。从v1.2开始,现在可以在@filter指令上使用比较函数,甚至可以在没有索引的谓词上使用比较函数。对于大型数据集,在非索引谓词上进行筛选可能会很慢,因为它们需要在使用筛选器的级别上迭代所有可能的值。

根查询或筛选器中的所有其他函数只能应用于索引谓词。

对于字符串值谓词上的函数,如果没有给出语言首选项,则该函数将应用于所有没有语言标记的语言和字符串;如果给定了语言首选项,则该函数只应用于给定语言的字符串。

词匹配

allofterms

解析示例:allofterms(predicate, "空 格 分开 的词 列表")

Schema类型:string

索引要求:term

以任意顺序匹配具有所有指定项的字符串;不区分大小写。

用在根查询中

查询示例:所有名称中包含词条indianajones的节点,以英文返回英文名称和流派:

{
  me(func: allofterms(name@en, "jones indiana")) {
    name@en
    genre {
      name@en
    }
  }
}

这将返回:

{
  "data": {
    "me": [
      {
        "name@en": "The Adventures of Young Indiana Jones: Passion for Life",
        "genre": [
          {
            "name@en": "History"
          }
        ]
      },
      {
        "name@en": "The Young Indiana Jones Chronicles 2",
        "genre": [
          {
            "name@en": "Adventure Film"
          },
          {
            "name@en": "Action Film"
          }
        ]
      },
      {
        "name@en": "The Adventures of Young Indiana Jones: Travels with Father",
        "genre": [
          {
            "name@en": "Family"
          },
          {
            "name@en": "Adventure Film"
          }
        ]
      }
      ...
    ]
  }
}

用在过滤器中

查询示例:史蒂芬·斯皮尔伯格所有包含印第安纳和琼斯字样的电影。@filter(has(director.film))删除了名字为Steven Spielberg但不是导演的节点————数据还包含了一部名为Steven Spielberg的电影中的一个角色。

{
  me(func: eq(name@en, "Steven Spielberg")) @filter(has(director.film)) {
    name@en
    director.film @filter(allofterms(name@en, "jones indiana"))  {
      name@en
    }
  }
}

将返回:

{
  "data": {
    "me": [
      {
        "name@en": "Steven Spielberg",
        "director.film": [
          {
            "name@en": "Indiana Jones and the Temple of Doom"
          },
          {
            "name@en": "Indiana Jones and the Raiders of the Lost Ark"
          },
          {
            "name@en": "Indiana Jones and the Kingdom of the Crystal Skull"
          },
          {
            "name@en": "Indiana Jones and the Last Crusade"
          }
        ]
      }
    ]
  }
}

anyofterms

解析示例:anyofterms(predicate, "space-separated term list") Schema类型:string 索引要求:term

以任意顺序匹配具有任何指定术语的字符串;不区分大小写。

用在根查询中

查询示例:所有名称包含poisonpeacock的节点。许多返回的节点都是电影,但像Joan Peacock这样的人也满足搜索条件,因为没有级联指令,查询就不需要类型:

{
  me(func:anyofterms(name@en, "poison peacock")) {
    name@en
    genre {
      name@en
    }
  }
}

将返回:

{
  "data": {
    "me": [
      {
        "name@en": "The Peacock Spring",
        "genre": [
          {
            "name@en": "Drama"
          }
        ]
      },
      {
        "name@en": "David Peacock"
      },
      {
        "name@en": "Harry Peacock"
      },
      {
        "name@en": "Poison Apple",
        "genre": [
          {
            "name@en": "Family"
          },
          {
            "name@en": "Fantasy"
          },
          {
            "name@en": "Short Film"
          },
          {
            "name@en": "Backstage Musical"
          }
        ]
      }
      ...
    ]
  }
}

用在过滤器中

查询示例:所有史蒂文·斯皮尔伯格的战争或间谍电影。@filter(has(director.film))删除了名字为Steven Spielberg但不是导演的节点————数据还包含了一部名为Steven Spielberg的电影中的一个角色:

{
  me(func: eq(name@en, "Steven Spielberg")) @filter(has(director.film)) {
    name@en
    director.film @filter(anyofterms(name@en, "war spies"))  {
      name@en
    }
  }
}

将返回:

{
  "data": {
    "me": [
      {
        "name@en": "Steven Spielberg",
        "director.film": [
          {
            "name@en": "War Horse"
          },
          {
            "name@en": "War of the Worlds"
          },
          {
            "name@en": "Bridge of Spies"
          }
        ]
      }
    ]
  }
}

正则表达式

解析示例:regexp(predicate, /regular-expression/) 或者不区分大小写regexp(predicate, /regular-expression/i) Schema类型:string 索引要求:trigram

通过正则表达式匹配字符串。正则表达式语言是go正则表达式的语言。

查询示例:在根节点,在名称开头匹配Steven Sp,后面跟着任意字符。对于每个这样匹配的uid,匹配包含ryan的影片。注意allofterms的不同之处,它只匹配ryan,但是正则表达式搜索也会在terms内匹配,比如bryan:

{
  directors(func: regexp(name@en, /^Steven Sp.*$/)) {
    name@en
    director.film @filter(regexp(name@en, /ryan/i)) {
      name@en
    }
  }
}

将返回:

{
  "data": {
    "directors": [
      {
        "name@en": "Steven Spencer"
      },
      {
        "name@en": "Steven Spielberg",
        "director.film": [
          {
            "name@en": "Saving Private Ryan"
          }
        ]
      },
      {
        "name@en": "Steven Spielberg And The Return To Film School"
      },
      {
        "name@en": "Steven Spohn"
      },
      {
        "name@en": "Steven Spieldal"
      },
      {
        "name@en": "Steven Spielberg"
      },
      {
        "name@en": "Steven Sperling"
      },
      {
        "name@en": "Steven Spencer"
      },
      {
        "name@en": "Steven Spielberg"
      },
      {
        "name@en": "Steven Spurrier"
      },
      {
        "name@en": "Steven Spielberg"
      },
      {
        "name@en": "Steven Spurrier"
      }
    ]
  }
}

技术细节: Trigram是由三个连续的符文组成的子串。例如,Dgraph有三元组合Dgr, gra, rap, aph。 为了保证正则表达式匹配的效率,Dgraph使用了三元组索引。也就是说,Dgraph将正则表达式转换为trigram查询,使用trigram索引和trigram查询来查找可能的匹配,并仅对可能的匹配项应用完整的正则表达式搜索。

Dgraph中正则表达式的高效使用和限制

** TODO **

模糊匹配

解析规则:match(predicate, string, distance) Schema类型:string 索引类型:trigram

通过计算到字符串的Levenshtein距离来匹配谓词值,也称为模糊匹配。距离参数必须大于零(0)。使用更大的距离值可以产生更多但不太精确的结果。

查询示例:在根查询处,模糊匹配节点类似于Stephen,距离值小于等于8:

{
  directors(func: match(name@en, Stephen, 8)) {
    name@en
  }
}

将返回:

{
  "data": {
    "directors": [
      {
        "name@en": "Stephen Chen"
      },
      {
        "name@en": "Step Up 3D"
      },
      {
        "name@en": "Steven Bauer"
      },
      {
        "name@en": "Stephen Chow"
      },
      {
        "name@en": "Joseph"
      },
      {
        "name@en": "Ron Steinman"
      },
      {
        "name@en": "Steven Blum"
      },
      {
        "name@en": "The Chef"
      },
    ]
  }
}

全文搜索

解析规则:alloftext(predicate, "space-separated text")或者anyoftext(predicate, "space-separated text") Schema类型:string 索引要求:fulltext

应用带有词干分析和停止词的全文本搜索,以查找与所有或任何给定文本匹配的字符串。

以下步骤在索引生成和处理全文搜索参数时应用:

  1. 标记化(根据Unicode单词边界)。
  2. 转换为小写的。
  3. unicode规范化(到正规化形式KC)。
  4. 词干分析使用特定于语言的词干分析器(如果语言支持的话)。
  5. 停止单词删除(如果语言支持)。

Dgraph使用bleve进行全文搜索索引。请参阅bleve语言特定的停止单词列表

下表包含所有支持的语言,相应的国家代码,词干和停止词过滤支持:

todo

查询示例:所有包含dog, dogs, bark, bark, bark等的名称。停止字删除消除和which

{
  movie(func:alloftext(name@en, "the dog which barks")) {
    name@en
  }
}

将返回:

{
  "data": {
    "movie": [
      {
        "name@en": "Black Dogs Barking"
      },
      {
        "name@en": "Elliott Erwitt: 'I Bark at Dogs"
      },
      {
        "name@en": "Barking Dogs"
      },
      {
        "name@en": "Barking Dogs Never Bite"
      },
      {
        "name@en": "Do You Hear the Dogs Barking?"
      },
      {
        "name@en": "But The Word Dog Doesn’t Bark"
      }
      ...
    ]
  }
}

等于

解析示例:

  • eq(predicate, value)
  • eq(val(varName), value)
  • eq(predicate, val(varName))
  • eq(count(predicate), value)
  • eq(predicate, [val1, val2, val3])
  • eq(predicate, [$var1, "value2", ..., $varN])

Schema类型:int, float, bool, string, dateTime 索引要求:在查询根处使用eq(predicate, ...)形式时需要索引(见下表)。对于查询根的count(谓词),需要@count索引。对于变量,值是作为查询的一部分计算的,因此不需要索引:

类型索引要求
intint
floatfloat
boolbool
stringexact hash term fulltext
dateTimedateTime
布尔常量分别为truefalse,因此使用eq时,就变成了,例如,eq(boolPred, true)

查询示例:查询具有十三种流派的电影,并返回十三种流派的名字:

{
  me(func: eq(count(genre), 13)) {
    name@en
    genre {
      name@en
    }
  }
}

将返回:

{
  "data": {
    "me": [
      {
        "name@en": "Stay Tuned",
        "genre": [
          {
            "name@en": "Horror"
          },
          {
            "name@en": "Science Fiction"
          },
          {
            "name@en": "Thriller"
          },
          {
            "name@en": "Family"
          }
          ...
        ]
      },
       {
        "name@en": "Trollhunters",
        "genre": [
          {
            "name@en": "Horror"
          },
        ]
       }
       ...
    ]
  }
}

查询示例:导演叫史蒂文,他曾经导演过1、2或3部电影:

{
  steve as var(func: allofterms(name@en, "Steven")) {
    films as count(director.film)
  }
  stevens(func: uid(steve)) @filter(eq(val(films), [1,2,3])) {
    name@en
    numFilms: val(films)
  }
}

将返回:

{
  "data": {
    "stevens": [
      {
        "name@en": "Steven Wilsey",
        "numFilms": 1
      },
      {
        "name@en": "Steven Bratter",
        "numFilms": 1
      },
      {
        "name@en": "Steven Saussey",
        "numFilms": 1
      },
      {
        "name@en": "Steven Ray Morris",
        "numFilms": 1
      }
      ...
    ]
  }
}

小于、小于或等于、大于、大于或等于

解析示例:(假设不等号是IE)

  • IE(predicate, value)
  • IE(val(varName), value)
  • IE(predicate, val(varName))
  • IE(count(predicate), value)

IE的可能取值:

  • le: 小于或等于(less than or equal to)
  • lt: 小于(less than)
  • ge: 大于或等于(greater than or equal to)
  • gt: 大于(greater than)

Schema类型:int, float, string, dateTime

索引要求:当不等式查询IE用在根查询时,需要下表的索引要求。对于用在根查询的count(predicate),则需要@count索引。对于变量,因为变量是在查询中计算的一部分,所以不需要索引。 类型|索引要求 ---| --- int|int float| float string|string dateTime|dateTime

查询示例:列举Ridley Scott在1980年之前导演的电影:

{
  me(func: eq(name@en, "Ridley Scott")) {
    name@en
    director.film @filter(lt(initial_release_date, "1980-01-01"))  {
      initial_release_date
      name@en
    }
  }
}

将返回:

{
  "data": {
    "me": [
      {
        "name@en": "Ridley Scott",
        "director.film": [
          {
            "initial_release_date": "1979-05-25T00:00:00Z",
            "name@en": "Alien"
          },
          {
            "initial_release_date": "1977-12-01T00:00:00Z",
            "name@en": "The Duellists"
          }
        ]
      },
      {
        "name@en": "Ridley Scott"
      }
    ]
  }
}

查询示例:以史蒂文为导演的同名电影,曾执导过100多名演员。

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

  dirs(func: uid(ID)) @filter(gt(val(total), 100)) {
    name@en
    total_actors : val(total)
  }
}

将返回:

{
  "data": {
    "dirs": [
      {
        "name@en": "Steven Conrad",
        "total_actors": 122
      },
      {
        "name@en": "Steven Knight",
        "total_actors": 102
      },
      {
        "name@en": "Steven Zaillian",
        "total_actors": 123
      },
      {
        "name@en": "Steven Spielberg",
        "total_actors": 1665
      },
      {
        "name@en": "Steven Brill",
        "total_actors": 417
      }
      ...
    ]
  }
}

查询示例:每类电影超过30000部的电影。因为在类型上没有指定顺序,所以顺序将通过UID。count索引记录节点的边数,并使这样的查询更多:

{
  genre(func: gt(count(~genre), 30000)){
    name@en
    ~genre (first:1) {
      name@en
    }
  }
}

将返回:

{
  "data": {
    "genre": [
      {
        "name@en": "Documentary film",
        "~genre": [
          {
            "name@en": "Wild Things"
          }
        ]
      },
      {
        "name@en": "Drama",
        "~genre": [
          {
            "name@en": "José María y María José: Una pareja de hoy"
          }
        ]
      },
      {
        "name@en": "Comedy",
        "~genre": [
          {
            "name@en": "José María y María José: Una pareja de hoy"
          }
        ]
      },
      {
        "name@en": "Short Film",
        "~genre": [
          {
            "name@en": "Side Effects"
          }
        ]
      }
    ]
  }
}

查询示例:叫Steven的导演和他们的电影initial_release_date大于电影Minority Report的电影:

{
  var(func: eq(name@en,"Minority Report")) {
    d as initial_release_date
  }
  me(func: eq(name@en, "Steven Spielberg")) {
    name@en
    director.film @filter(ge(initial_release_date, val(d))) {
      initial_release_date
      name@en
    }
  }
}

将返回:

{
  "data": {
    "me": [
      {
        "name@en": "Steven Spielberg",
        "director.film": [
          {
            "initial_release_date": "2012-10-08T00:00:00Z",
            "name@en": "Lincoln"
          },
          {
            "initial_release_date": "2011-12-04T00:00:00Z",
            "name@en": "War Horse"
          },
          {
            "initial_release_date": "2005-06-13T00:00:00Z",
            "name@en": "War of the Worlds"
          },
          {
            "initial_release_date": "2002-06-17T00:00:00Z",
            "name@en": "Minority Report"
          }
          ...
        ]
      },
      {
        "name@en": "Steven Spielberg"
      },
      {
        "name@en": "Steven Spielberg"
      },
      {
        "name@en": "Steven Spielberg"
      }
    ]
  }
}

between

解析规则:between(predicate, startDateValue, endDateValue) Schema类型:Scalar Type, dateTime, int, float, string 索引要求:dateTime, int, float, exact

返回与索引值的包含范围相匹配的节点。between关键字对索引执行范围检查,以提高查询效率,有助于防止对大数据集进行大范围查询时运行缓慢。

between关键字的一个常见用例是在由dateTime索引的数据集中进行搜索。下面的示例查询演示了这个用例。

查询示例:1977年首次上映的电影,按类型列出:

{
  me(func: between(initial_release_date, "1977-01-01", "1977-12-31")) {
    name@en
    genre {
      name@en
    }
  }
}

将返回:

{
  "data": {
    "me": [
      {
        "name@en": "SST: Death Flight",
        "genre": [
          {
            "name@en": "Drama"
          }
        ]
      },
      {
        "name@en": "Lucio Flavio",
        "genre": [
          {
            "name@en": "Crime Fiction"
          },
          {
            "name@en": "Drama"
          }
        ]
      },
      {
        "name@en": "Uyarnthavargal",
        "genre": [
          {
            "name@en": "World cinema"
          },
          {
            "name@en": "Drama"
          },
          {
            "name@en": "Musical Drama"
          },
          {
            "name@en": "Backstage Musical"
          },
          {
            "name@en": "Tamil cinema"
          }
        ]
      }
      ...
    ]
  }
}

uid

解析示例:

  • q(func:uid(<uid>))
  • predicate @filter(uid(<uid1>, ..., <uidn>))
  • predicate @filter(uid(a)) 对变量a使用
  • q(func:uid(a, b)) 对变量ab使用
  • q(func:uid($uids)) 对一组uid使用,例如[0x1, 0x2, 0x3, ..., 0xn]

仅将当前查询级别的节点过滤为给定uid集合中的节点。

对于查询变量a, uid(a)表示存储在a中的uid集合,对于值变量b, uid(b)表示uidvalue映射的uid集合。对于两个或多个变量,uid(a,b,…)表示所有变量的并集。

uid(<uid>)与标识函数一样,即使节点没有任何边,也将返回所请求的uid

查询示例:按已知UID查询Priyanka Chopra的胶片:

{
  films(func: uid(0x2c964)) {
    name@hi
    actor.film {
      performance.film {
        name@hi
      }
    }
  }
}

将返回:

{
  "data": {
    "films": [
      {
        "name@hi": "प्रियंका चोपड़ा",
        "actor.film": [
          {
            "performance.film": [
              {
                "name@hi": "यकीन"
              }
            ]
          },
          {
            "performance.film": [
              {
                "name@hi": "सलाम-ए-इश्क़"
              }
            ]
          }
          ...
        ]
      }
    ]
  }
}

查询示例:塔拉吉·汉森的电影按类型分类:

{
  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
    }
  }
}

将返回:

{
  "data": {
    "Taraji_films_by_genre": [
      {
        "genre_name": "War film",
        "films": [
          {
            "film_name": "Talk to Me"
          }
        ]
      },
      {
        "genre_name": "Horror",
        "films": [
          {
            "film_name": "Satan's School for Girls"
          }
        ]
      },
      {
        "genre_name": "Indie film",
        "films": [
          {
            "film_name": "Once Fallen"
          },
          {
            "film_name": "Peep World"
          },
          {
            "film_name": "Hustle & Flow"
          },
          {
            "film_name": "All or Nothing"
          },
          {
            "film_name": "Hair Show"
          }
        ]
      },
      {
        "genre_name": "Crime",
        "films": [
          {
            "film_name": "Term Life"
          }
        ]
      }
      ...
    ]
  }
}

查询示例:Taraji Henson的电影按体裁数量排序,体裁列表按Taraji在每个体裁中制作的电影数量排序:

{
  var(func: allofterms(name@en, "Taraji Henson")) {
    actor.film {
      F as performance.film {
        G as count(genre)
        genre {
          C as count(~genre @filter(uid(F)))
        }
      }
    }
  }
  Taraji_films_by_genre_count(func: uid(G), orderdesc: val(G)) {
    film_name : name@en
    genres : genre (orderdesc: val(C)) {
      genre_name : name@en
    }
  }
}

将返回:

{
  "data": {
    "Taraji_films_by_genre_count": [
      {
        "film_name": "Date Night",
        "genres": [
          {
            "genre_name": "Comedy"
          },
          {
            "genre_name": "Crime Fiction"
          },
          {
            "genre_name": "Romance Film"
          },
          {
            "genre_name": "Thriller"
          },
          {
            "genre_name": "Action Film"
          },
          {
            "genre_name": "Romantic comedy"
          },
          {
            "genre_name": "Action/Adventure"
          },
          {
            "genre_name": "Action Comedy"
          },
          {
            "genre_name": "Chase Movie"
          },
          {
            "genre_name": "Screwball comedy"
          }
        ]
      },
      {
        "film_name": "I Can Do Bad All by Myself",
        "genres": [
          {
            "genre_name": "Drama"
          },
          {
            "genre_name": "Comedy"
          },
          {
            "genre_name": "Romance Film"
          },
          {
            "genre_name": "Romantic comedy"
          },
          {
            "genre_name": "Comedy-drama"
          },
          {
            "genre_name": "Musical Drama"
          },
          {
            "genre_name": "Backstage Musical"
          },
          {
            "genre_name": "Musical comedy"
          }
        ]
      }
      ...
    ]
  }
}

uid_in

解析示例:

  • q(func: ...) @filter(uid_in(predicate, <uid>))
  • predicate1 @filter(uid_in(predicate2, <uid>))
  • predicate1 @filter(uid_in(predicate2, [<uid1>, ..., <uid2>]))
  • predicate1 @filter(uid_in(predicate2, uid(myVariable))) Schema类型:UID 索引要求:无

虽然uid函数基于uid在当前级别上过滤节点,但uid_in函数允许沿着边缘向前查找,以检查它是否指向特定的uid。这通常可以节省额外的查询块,并避免返回边缘。

uid_in不能在根查询下使用。它接受多个UID作为它的参数,并接受一个UID变量(它可以包含UID的映射)。

查询示例:Marc CaroJean-Pierre Jeunet的协作关系(UID 0x99706)。如果Jean-Pierre JeunetUID是已知的,那么以这种方式查询就不需要用一个块将他的UID提取到一个变量中,也不需要对~director.film进行额外的边遍历和过滤器:

{
  caro(func: eq(name@en, "Marc Caro")) {
    name@en
    director.film @filter(uid_in(~director.film, 0x99706)) {
      name@en
    }
  }
}

将返回:

{
  "data": {
    "caro": [
      {
        "name@en": "Marc Caro"
      }
    ]
  }
}

如果不知道Jean-Pierre JeunetUID,还可以查询他的UID,并在UID变量中使用它:

{
  getJeunet as q(func: eq(name@fr, "Jean-Pierre Jeunet"))

  caro(func: eq(name@en, "Marc Caro")) {
    name@en
    director.film @filter(uid_in(~director.film, uid(getJeunet) )) {
      name@en
    }
  }
}

将返回:

{
  "data": {
    "q": [],
    "caro": [
      {
        "name@en": "Marc Caro",
        "director.film": [
          {
            "name@en": "The City of Lost Children"
          },
          {
            "name@en": "Delicatessen"
          },
          {
            "name@en": "The Bunker of the Last Gunshots"
          },
          {
            "name@en": "L'évasion"
          }
        ]
      }
    ]
  }
}

has

解析规则:has(predicate)

Schema使用类型:所有

确定节点是否具有特定谓词。

查询示例:前五位导演和他们所有的电影都有一个上映日期记录。导演已经指导了至少一个电影————等效语义gt(count(director.film), 0)

{
  me(func: has(director.film), first: 5) {
    name@en
    director.film @filter(has(initial_release_date))  {
      initial_release_date
      name@en
    }
  }
}

将返回:

{
  "data": {
    "me": [
      {
        "name@en": "Zehra Yiğit"
      },
      {
        "name@en": "Charlton Heston",
        "director.film": [
          {
            "initial_release_date": "1982-09-23T00:00:00Z",
            "name@en": "Mother Lode"
          },
          {
            "initial_release_date": "1972-03-02T00:00:00Z",
            "name@en": "Antony and Cleopatra"
          },
          {
            "initial_release_date": "1988-12-21T00:00:00Z",
            "name@en": "A Man for All Seasons"
          }
        ]
      },
      {
        "name@en": "Rajeev Sharma",
        "director.film": [
          {
            "initial_release_date": "2012-01-01T00:00:00Z",
            "name@en": "Nabar"
          },
          {
            "initial_release_date": "2014-05-30T00:00:00Z",
            "name@en": "47 to 84"
          }
        ]
      }
      ...
    ]
  }
}

Geolocation 地理位置

变更

要使用geo函数,您需要谓词上的索引。

loc: geo @index(geo) .

下面是如何添加一个点:

{
  set {
    <_:0xeb1dde9c> <loc> "{'type':'Point','coordinates':[-122.4220186,37.772318]}"^^<geo:geojson> .
    <_:0xeb1dde9c> <name> "Hamon Tower" .
    <_:0xeb1dde9c> <dgraph.type> "Location" .
  }
}

下面是如何将一个多边形与一个节点关联。添加多多边形也是类似的:

{
  set {
    <_:0xf76c276b> <loc> "{'type':'Polygon','coordinates':[[[-122.409869,37.7785442],[-122.4097444,37.7786443],[-122.4097544,37.7786521],[-122.4096334,37.7787494],[-122.4096233,37.7787416],[-122.4094004,37.7789207],[-122.4095818,37.7790617],[-122.4097883,37.7792189],[-122.4102599,37.7788413],[-122.409869,37.7785442]],[[-122.4097357,37.7787848],[-122.4098499,37.778693],[-122.4099025,37.7787339],[-122.4097882,37.7788257],[-122.4097357,37.7787848]]]}"^^<geo:geojson> .
    <_:0xf76c276b> <name> "Best Western Americana Hotel" .
    <_:0xf76c276b> <dgraph.type> "Location" .
  }
}

以上的例子是从我们的SF旅游数据集中挑选出来的。

查询

near 附近查询

解析示例:near(predicate, [long, lat], distance)

Schema类型:geo

索引要求:geo

匹配所有由谓词给出的位置在距离geojson坐标[long, lat]米范围内的实体。

查询示例:旧金山金门公园某点1000米(1公里)以内的旅游目的地:

{
  tourist(func: near(loc, [-122.469829, 37.771935], 1000) ) {
    name
  }
}

将返回:

{
  "data": {
    "tourist": [
      {
        "name": "Peace Lantern"
      },
      {
        "name": "De Young Museum"
      },
      {
        "name": "Buddha"
      },
      {
        "name": "Morrison Planetarium"
      },
      {
        "name": "Chinese Pavillion"
      },
      {
        "name": "Strawberry Hill"
      }
      ...
    ]
  }
}

within 在给定范围之内

contains 包含

intersects 相交

Connecting Filters 联合过滤

@filter中,多个函数可以与布尔连接符一起使用。

AND(与)、OR(或)、NOT(非)

连接词ANDORNOT连接过滤器,可以构建到任意复杂的过滤器中,如(NOT A OR B)(C AND NOT (D OR E))。注意,NOT绑定比AND绑定更紧密,AND绑定比OR绑定更紧密。

查询示例:所有史蒂芬·斯皮尔伯格(Steven Spielberg)同时包含“indiana”和“jones”或“jurassic”和“park”的电影:

{
  me(func: eq(name@en, "Steven Spielberg")) @filter(has(director.film)) {
    name@en
    director.film @filter(allofterms(name@en, "jones indiana") OR allofterms(name@en, "jurassic park"))  {
      uid
      name@en
    }
  }
}

将返回:

{
  "data": {
    "me": [
      {
        "name@en": "Steven Spielberg",
        "director.film": [
          {
            "uid": "0x9a41",
            "name@en": "Indiana Jones and the Temple of Doom"
          },
          {
            "uid": "0x1cde3",
            "name@en": "Jurassic Park"
          },
          {
            "uid": "0x2868c",
            "name@en": "Indiana Jones and the Raiders of the Lost Ark"
          },
          {
            "uid": "0x42164",
            "name@en": "Indiana Jones and the Kingdom of the Crystal Skull"
          },
          {
            "uid": "0x483f3",
            "name@en": "The Lost World: Jurassic Park"
          },
          {
            "uid": "0x54387",
            "name@en": "Indiana Jones and the Last Crusade"
          }
        ]
      }
    ]
  }
}

Aliases 别名

解析示例:

  • aliasName: predicate
  • aliasName: predicate { ... }
  • aliasName: varName as ...
  • aliasName: count(predicate)
  • aliasName: max(val(varName))

别名在结果中提供替代名称。谓词、变量和聚合可以通过以别名和:作为前缀来别名。别名不必与原始谓词名不同,但是,在一个块中,别名必须与谓词名和同一块中返回的其他别名不同。别名可用于在一个块内多次返回相同的谓词。

查询示例:姓名匹配词Steven的导演,他们的UID,英文名,每部电影的平均演员人数,电影总数,以及每部电影的英文和法文名称:

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

  films(func: uid(ID)) {
    director_id : uid
    english_name : name@en
    average_actors : val(average)
    num_films : count(director.film)

    films : director.film {
      name : name@en
      english_name : name@en
      french_name : name@fr
    }
  }
}

将返回:

{
  "data": {
    "films": [
      {
        "director_id": "0x33a6",
        "english_name": "Steven Wilsey",
        "average_actors": 0,
        "num_films": 1,
        "films": [
          {
            "name": "At Night I Was Beautiful",
            "english_name": "At Night I Was Beautiful"
          }
        ]
      },
      {
        "director_id": "0x3ce3",
        "english_name": "Steven Bratter",
        "average_actors": 10,
        "num_films": 1,
        "films": [
          {
            "name": "First Strike",
            "english_name": "First Strike"
          }
        ]
      },
      {
        "director_id": "0x57c8",
        "english_name": "Steven Saussey",
        "average_actors": 3,
        "num_films": 1,
        "films": [
          {
            "name": "Whisker",
            "english_name": "Whisker"
          }
        ]
      },
      {
        "director_id": "0x9740",
        "english_name": "Steven Ray Morris",
        "average_actors": 8,
        "num_films": 1,
        "films": [
          {
            "name": "The Premiere",
            "english_name": "The Premiere"
          }
        ]
      }
      ...
    ]
  }
}

Pagination 分页

分页允许你只获取一部分而不是整个结果集。这对于top-k样式的查询以及减少客户端处理的结果集的大小或允许对结果进行分页访问都很有用。

分页通常与排序一起使用。

如果没有指定排序顺序,结果将按照uid进行排序,uid是随机分配的。因此,虽然顺序是确定的,但可能不是你所期望的。

First 参数

解析示例:

  • q(func: ..., first: N)
  • predicate (first: N) { ... }
  • predicate @filter(...) (first: N) { ... }

对于N > 0, first: N按排序或UID顺序检索前N个结果。

对于N < 0, first: N按排序或UID顺序检索最后N个结果。目前,只有当没有应用顺序时,才支持负数。为了在排序中获得负数的效果,颠倒排序的顺序并使用正的N

查询示例:最后两部电影,按UID排序,由史蒂文·斯皮尔伯格执导,前三种类型的电影,按英文名的字母顺序排序:

{
  me(func: allofterms(name@en, "Steven Spielberg")) {
    director.film (first: -2) {
      name@en
      initial_release_date
      genre (orderasc: name@en) (first: 3) {
          name@en
      }
    }
  }
}

将返回:

{
  "data": {
    "me": [
      {
        "director.film": [
          {
            "name@en": "Firelight",
            "initial_release_date": "1964-03-24T00:00:00Z",
            "genre": [
              {
                "name@en": "Science Fiction"
              },
              {
                "name@en": "Thriller"
              }
            ]
          },
          {
            "name@en": "Amazing Stories: Book One",
            "genre": [
              {
                "name@en": "Comedy"
              },
              {
                "name@en": "Drama"
              }
            ]
          }
        ]
      }
    ]
  }
}

查询例:在所有叫史蒂文的导演中,导演演员最多的三位叫史蒂文的导演:

{
  ID as var(func: allofterms(name@en, "Steven")) @filter(has(director.film)) {
    director.film {
      stars as count(starring)
    }
    totalActors as sum(val(stars))
  }

  mostStars(func: uid(ID), orderdesc: val(totalActors), first: 3) {
    name@en
    stars : val(totalActors)

    director.film {
      name@en
    }
  }
}

将返回:

{
  "data": {
    "mostStars": [
      {
        "name@en": "Steven Spielberg",
        "stars": 1665,
        "director.film": [
          {
            "name@en": "Hook"
          },
          {
            "name@en": "The Color Purple"
          },
          {
            "name@en": "Schindler's List"
          },
          {
            "name@en": "Amistad"
          }
          ...
        ]
      }
    ]
  }
}

Offset 参数

语法示例:

  • q(func: ..., offset: N)
  • predicate (offset: N) { ... }
  • predicate (first: M, offset: N) { ... }
  • predicate @filter(...) (offset: N) { ... }

通过指定offset: N,查询结果的前N就不会返回在结果集中。结合前面介绍的first: M使用,例如first: M, offset: N,就能跳过前面N个结果,只返回接下来的M个结果。

查询示例:查询英文名字为Hark Tsui的影片,跳过前面4个,返回接下来的6个。

{
  me(func: allofterms(name@en, "Hark Tsui")) {
    name@zh
    name@en
    director.film (orderasc: name@en) (first:6, offset:4)  {
      genre {
        name@en
      }
      name@zh
      name@en
      initial_release_date
    }
  }
}
{
  "data": {
    "me": [
      {
        "name@zh": "徐克",
        "name@en": "Tsui Hark",
        "director.film": [
          {
            "genre": [
              {
                "name@en": "Science Fiction"
              },
              {
                "name@en": "Superhero movie"
              },
              {
                "name@en": "World cinema"
              },
              {
                "name@en": "Action/Adventure"
              },
              {
                "name@en": "Chinese Movies"
              },
              {
                "name@en": "Martial Arts Film"
              },
              {
                "name@en": "Action Film"
              }
            ],
            "name@en": "Black Mask 2: City of Masks",
            "initial_release_date": "2002-01-01T00:00:00Z"
          },
          {
            "genre": [
              {
                "name@en": "Adventure game"
              },
              {
                "name@en": "Drama"
              },
              {
                "name@en": "Action Film"
              }
            ],
            "name@zh": "狄仁杰之通天帝国",
            "name@en": "Detective Dee: Mystery of the Phantom Flame",
            "initial_release_date": "2010-09-05T00:00:00Z"
          },
          {
            "genre": [
              {
                "name@en": "Thriller"
              },
              {
                "name@en": "Crime Fiction"
              },
              {
                "name@en": "Action Film"
              }
            ],
            "name@en": "Don't Play with Fire",
            "initial_release_date": "1980-12-04T00:00:00Z"
          },
          {
            "genre": [
              {
                "name@en": "Thriller"
              },
              {
                "name@en": "Action/Adventure"
              },
              {
                "name@en": "Martial Arts Film"
              },
              {
                "name@en": "Action Thriller"
              },
              {
                "name@en": "Action Film"
              },
              {
                "name@en": "Buddy film"
              }
            ],
            "name@en": "Double Team",
            "initial_release_date": "1997-04-04T00:00:00Z"
          },
          {
            "genre": [
              {
                "name@en": "Adventure Film"
              },
              {
                "name@en": "Drama"
              },
              {
                "name@en": "Martial Arts Film"
              },
              {
                "name@en": "Action Film"
              }
            ],
            "name@zh": "龙门飞甲",
            "name@en": "Flying Swords of Dragon Gate",
            "initial_release_date": "2011-12-15T00:00:00Z"
          },
          {
            "genre": [
              {
                "name@en": "World cinema"
              },
              {
                "name@en": "Adventure Film"
              },
              {
                "name@en": "Romance Film"
              },
              {
                "name@en": "Action/Adventure"
              },
              {
                "name@en": "Drama"
              },
              {
                "name@en": "Fantasy"
              },
              {
                "name@en": "Chinese Movies"
              },
              {
                "name@en": "Romantic fantasy"
              },
              {
                "name@en": "Costume Adventure"
              },
              {
                "name@en": "Fantasy Adventure"
              }
            ],
            "name@zh": "青蛇",
            "name@en": "Green Snake",
            "initial_release_date": "1993-01-01T00:00:00Z"
          }
        ]
      },
      {
        "name@zh": "小倩",
        "name@en": "A Chinese Ghost Story: The Tsui Hark Animation"
      },
      {
        "name@en": "Tsui Hark Movie Studio"
      }
    ]
  }
}

After 参数

语法示例:

  • q(func: ..., after: UID)
  • predicate (first: N, after: UID) { ... }
  • predicate @filter(...) (first: N, after: UID) { ... }

另一个具有类似跳过功能的是使用默认UID排序时,能够直接指定after: UID,然后获取该UID之后的查询结果。例如,在一个常见的场景中,第一个查询是predicate (after: 0x00, first: N)predicate (first: N)的形式,接下来的查询都用predicate (after: <上一次查询的结果中的uid>, first: N)

查询示例:查询Baz Luhrmann的前5部影片,以UID排序:

{
  me(func: allofterms(name@en, "Baz Luhrmann")) {
    name@en
    director.film (first:5) {
      uid
      name@en
    }
  }
}
{
  "data": {
    "me": [
      {
        "name@en": "Baz Luhrmann",
        "director.film": [
          {
            "uid": "0x434",
            "name@en": "The Great Gatsby"
          },
          {
            "uid": "0x1e0d",
            "name@en": "Strictly Ballroom"
          },
          {
            "uid": "0x11bdb",
            "name@en": "Moulin Rouge!"
          },
          {
            "uid": "0x27c60",
            "name@en": "Romeo + Juliet"
          },
          {
            "uid": "0x2e760",
            "name@en": "Australia"
          }
        ]
      }
    ]
  }
}

第五部电影是澳大利亚经典电影Strictly Ballroom。它的UID0x99e44。在Strictly Ballroom影片之后的电影可以通过携带after: N参数的查询获取:

{
  me(func: allofterms(name@en, "Baz Luhrmann")) {
    name@en
    director.film (first:5, after: 0x99e44) {
      uid
      name@en
    }
  }
}
{
  "data": {
    "me": [
      {
        "name@en": "Baz Luhrmann",
        "director.film": [
          {
            "uid": "0xce247",
            "name@en": "Puccini: La Boheme (Sydney Opera)"
          },
          {
            "uid": "0xe40e5",
            "name@en": "No. 5 the Film"
          }
        ]
      }
    ]
  }
}

Count 计数

语法示例:

  • count(predicate)
  • count(uid)

count(predicate)计算节点的边的数量。

count(uid)统计在封闭块中匹配的uid的数量。

查询示例:每个演员以Orlando的名义出演的电影数量:

{
  me(func: allofterms(name@en, "Orlando")) @filter(has(actor.film)) {
    name@en
    count(actor.film)
  }
}
{
  "data": {
    "me": [
      {
        "name@en": "Orlando Seale",
        "count(actor.film)": 13
      },
      {
        "name@en": "Antonio Orlando",
        "count(actor.film)": 5
      },
      {
        "name@en": "Orlando Viera",
        "count(actor.film)": 1
      },
      {
        "name@en": "Silvio Orlando",
        "count(actor.film)": 32
      }
      ...
    ]
  }
}

Count 能用在根查询和别名Aliase中。

查询示例:导演超过5部电影的导演数。在查询根处使用时,需要count索引

{
  directors(func: gt(count(director.film), 5)) {
    totalDirectors : count(uid)
  }
}
{
  "data": {
    "directors": [
      {
        "totalDirectors": 7712
      }
    ]
  }
}

可以将Count赋值给值变量

查询示例:Ang LeeEat Drink Man Woman的演员按电影的数量返回结果:

{
  var(func: allofterms(name@en, "eat drink man woman")) {
    starring {
      actors as performance.actor {
        totalRoles as count(actor.film)
      }
    }
  }

  edmw(func: uid(actors), orderdesc: val(totalRoles)) {
    name@en
    name@zh
    totalRoles : val(totalRoles)
  }
}
{
  "data": {
    "edmw": [
      {
        "name@en": "Sylvia Chang",
        "name@zh": "张艾嘉",
        "totalRoles": 35
      },
      {
        "name@en": "Chien-lien Wu",
        "name@zh": "吴倩莲",
        "totalRoles": 20
      },
      {
        "name@en": "Yang Kuei-mei",
        "name@zh": "杨贵媚",
        "totalRoles": 14
      },
      {
        "name@en": "Winston Chao",
        "name@zh": "赵文瑄",
        "totalRoles": 11
      }
      ...
    ]
  }
}

Shorting 排序

语法示例:

  • q(func: ..., orderasc: predicate)
  • q(func: ..., orderdesc: val(name))
  • predicate (orderdesc: predicate) { ... }
  • predicate @filter(...) (orderasc: N) { ... }
  • q(func: ..., orderasc: predicate1, orderdesc: predicate2)

可排序类型:int, float, String, dateTime, default

结果可以按谓词(predicate)或变量(variable)的升序(orderasc)或降序(orderdesc)排序。

对于具有可排序索引的谓词的排序,Dgraph并行地对值(value)和索引(index)进行排序,并返回先计算完的结果。

注意,Dgraph在结果的末尾返回null,而不考虑其类型。这种行为在索引排序和非索引排序中是一致的。

默认情况下,排序查询最多可检索1000个结果。这可以用first更改。

查询示例:法国导演Jean-Pierre Jeunet按上映日期排序的电影:

{
  me(func: allofterms(name@en, "Jean-Pierre Jeunet")) {
    name@fr
    director.film(orderasc: initial_release_date) {
      name@fr
      name@en
      initial_release_date
    }
  }
}
{
  "data": {
    "me": [
      {
        "name@fr": "Jean-Pierre Jeunet",
        "director.film": [
          {
            "name@fr": "L'Évasion",
            "name@en": "L'évasion",
            "initial_release_date": "1978-01-01T00:00:00Z"
          },
          {
            "name@fr": "Le Manège",
            "name@en": "Le manège",
            "initial_release_date": "1980-01-01T00:00:00Z"
          }
          ...
        ]
      }
    ]
  }
}

排序可以在根查询或值变量上进行。

查询示例:所有流派按字母表排序,每个类型中类型最多的5部电影:

{
  genres as var(func: has(~genre)) {
    ~genre {
      numGenres as count(genre)
    }
  }

  genres(func: uid(genres), orderasc: name@en) {
    name@en
    ~genre (orderdesc: val(numGenres), first: 5) {
      name@en
      genres : val(numGenres)
    }
  }
}
{
  "data": {
    "genres": [
      {
        "name@en": "/m/04rlf",
        "~genre": [
          {
            "name@en": "Bookin'",
            "genres": 3
          },
          {
            "name@en": "La brune et moi",
            "genres": 3
          }
        ]
      },
      {
        "name@en": "3D film",
        "~genre": [
          {
            "name@en": "Mr. X",
            "genres": 4
          },
          {
            "name@en": "Sly Cooper",
            "genres": 4
          },
          {
            "name@en": "Everest",
            "genres": 3
          },
          {
            "name@en": "Aqaye Alef",
            "genres": 1
          }
        ]
      },
      {
        "name@en": "Abstract animation",
        "~genre": [
          {
            "name@en": "The Heart of the World",
            "genres": 7
          },
          {
            "name@en": "Outside Out",
            "genres": 6
          },
          {
            "name@en": "11 x 14",
            "genres": 5
          },
          {
            "name@en": "Acousticity",
            "genres": 4
          },
          {
            "name@en": "The Garden of Earthly Delights",
            "genres": 4
          }
        ]
      }
      ...
    ]
  }
}

排序也可以同时对多个谓语进行,如下所示。如果第一个谓词的值相等,则按第二个谓词对它们进行排序,以此类推。

查询示例:查找所有具有Person类型的节点,按first_name对其进行排序,first_name相同的节点按last_name降序进行排序。

{
  me(func: type("Person"), orderasc: first_name, orderdesc: last_name) {
    first_name
    last_name
  }
}

多查询块

在单个查询中,可以设置多个不同名字的查询块,这些查询块之间并行执行,它们之间不需要任何关联。

查询示例:自2008年以来Angelina Jolie所有类型的电影,以及Peter Jackson的电影:

{
 AngelinaInfo(func:allofterms(name@en, "angelina jolie")) {
  name@en
   actor.film {
    performance.film {
      genre {
        name@en
      }
    }
   }
  }

 DirectorInfo(func: eq(name@en, "Peter Jackson")) {
    name@en
    director.film @filter(ge(initial_release_date, "2008"))  {
        Release_date: initial_release_date
        Name: name@en
    }
  }
}
{
  "data": {
    "AngelinaInfo": [
      {
        "name@en": "Angelina Jolie",
        "actor.film": [
          {
            "performance.film": [
              {
                "genre": [
                  {
                    "name@en": "Short Film"
                  }
                ]
              }
            ]
          },
          {
            "performance.film": [
              {
                "genre": [
                  {
                    "name@en": "Slice of life"
                  },
                  {
                    "name@en": "Romance Film"
                  },
                  {
                    "name@en": "LGBT"
                  },
                  {
                    "name@en": "Drama"
                  },
                  {
                    "name@en": "Comedy"
                  },
                  {
                    "name@en": "Comedy-drama"
                  },
                  {
                    "name@en": "Ensemble Film"
                  }
                ]
              }
            ]
          },
          {
            "performance.film": [
              {
                "genre": [
                  {
                    "name@en": "Animation"
                  },
                  {
                    "name@en": "Romance Film"
                  },
                  {
                    "name@en": "Drama"
                  },
                  {
                    "name@en": "Comedy"
                  }
                ]
              }
            ]
          }
          ...
        ]
      },
      {
        "name@en": "Angelina Jolie"
      },
      {
        "name@en": "Angelina Jolie Look-a-Like"
      }
    ],
    "DirectorInfo": [
      {
        "name@en": "Peter Jackson",
        "director.film": [
          {
            "Release_date": "2012-11-28T00:00:00Z",
            "Name": "The Hobbit: An Unexpected Journey"
          },
          {
            "Release_date": "2009-11-24T00:00:00Z",
            "Name": "The Lovely Bones"
          }
          ... 
        ]
      },
      {
        "name@en": "Peter Jackson"
      },
      {
        "name@en": "Peter Jackson"
      },
      {
        "name@en": "Peter Jackson"
      }
    ]
  }
}

即使查询的答案中包含一些重叠部分,查询的结果集仍然是独立的。

查询示例:Mackenzie Crook演过的电影和Jack Davenport演过的电影, 两个结果集重叠,因为它们都在Pirates of the Caribbean电影中出演过,但结果是独立的,并且都包含完整的结果集:

{
  Mackenzie(func:allofterms(name@en, "Mackenzie Crook")) {
    name@en
    actor.film {
      performance.film {
        uid
        name@en
      }
      performance.character {
        name@en
      }
    }
  }

  Jack(func:allofterms(name@en, "Jack Davenport")) {
    name@en
    actor.film {
      performance.film {
        uid
        name@en
      }
      performance.character {
        name@en
      }
    }
  }
}
{
  "data": {
    "Mackenzie": [
      {
        "name@en": "Mackenzie Crook",
        "actor.film": [
          {
            "performance.film": [
              {
                "uid": "0x7f33f",
                "name@en": "Sex Lives of the Potato Men"
              }
            ]
          },
          {
            "performance.film": [
              {
                "uid": "0x5643",
                "name@en": "Still Crazy"
              }
            ],
            "performance.character": [
              {
                "name@en": "Dutch Kid"
              }
            ]
          },
          {
            "performance.film": [
              {
                "uid": "0x842bf",
                "name@en": "Solomon Kane"
              }
            ],
            "performance.character": [
              {
                "name@en": "Father Michael"
              }
            ]
          }
          ...
        ]
      }
    ],
    "Jack": [
      {
        "name@en": "Jack Davenport",
        "actor.film": [
          {
            "performance.film": [
              {
                "uid": "0x9367",
                "name@en": "The Libertine"
              }
            ],
            "performance.character": [
              {
                "name@en": "Harris"
              }
            ]
          }
          ...
        ]
      }
    ]
  }
}

变量块 var block

变量块由var关键字定义,不会返回在结果集,也不会影响查询的结果内容。

查询示例:Angelina Jolie的电影,通过流派排序:

{
  var(func: allofterms(name@en, "angelina jolie")) {
    name@en
    actor.film {
      A AS performance.film {
        B AS genre
      }
    }
  }

  films(func: uid(B), orderasc: name@en) {
    name@en
    ~genre @filter(uid(A)) {
      name@en
    }
  }
}
{
  "data": {
    "films": [
      {
        "name@en": "Action Film",
        "~genre": [
          {
            "name@en": "Gone in 60 Seconds"
          },
          {
            "name@en": "Alexander"
          }
          ...
        ]
      },
      {
        "name@en": "Action/Adventure",
        "~genre": [
          {
            "name@en": "Gone in 60 Seconds"
          },
          {
            "name@en": "The Bone Collector"
          }
          ...
        ]
      }
      ...
    ]
  }
}

多变量块 multiple var block

您还可以在单个查询操作中使用多个var块。你可以在任何后续的块中使用一个var块中的变量,但不能在同一个块中使用。

查询示例:包含angelina joliemorgan freeman的电影(按名字排序):

{
  var(func:allofterms(name@en, "angelina jolie")) {
    name@en
    actor.film {
      A AS performance.film
    }
  }
  var(func:allofterms(name@en, "morgan freeman")) {
    name@en
    actor.film {
      B as performance.film @filter(uid(A))
    }
  }
  
  films(func: uid(B), orderasc: name@en) {
    name@en
  }
}
{
  "data": {
    "films": [
      {
        "name@en": "Wanted"
      }
    ]
  }
}

在查询中组合使用多个变量块

你可以像下面这样在单个查询中使用逻辑连词组合多个变量块:

{
  var(func:allofterms(name@en, "angelina jolie")) {
    name@en
    actor.film {
      A AS performance.film
    }
  }
  var(func:allofterms(name@en, "morgan freeman")) {
    name@en
    actor.film {
      B as performance.film
    }
  }
  films(func: uid(A,B), orderasc: name@en) @filter(uid(A) AND uid(B)) {
    name@en
  }
}

根查询uid函数将var AB中的uid合并,因此您需要一个过滤器来与var AB中的uid相交。

查询变量

解析规则:

  • varName as q(func: ...) { ... }
  • varName as var(func: ...) { ... }
  • varName as predicate { ... }
  • varName as predicate @filter(...) { ... }

类型: uid

在查询中一个位置匹配的节点的uid可以存储在一个变量中,并在其它地方使用。查询变量可以在其他查询块中使用,也可以在定义块的子节点中使用。

查询变量在定义点不会影响查询的语义。查询变量被计算为所有与定义块匹配的节点。

通常,查询块是并行执行的,但是变量对一些块施加了计算顺序。由变量相关引起的循环是不允许的。

如果定义了变量,则必须在查询的其他地方使用它。

查询变量通过uid(var-name)提取其中的uid来使用。

语法func: uid(A, B)@filter(uid(A, B))表示变量ABuid的并集。

查询示例:安吉丽娜·朱莉和布拉德·皮特都出演过同一类型的电影。请注意,B和D匹配所有电影的所有类型,而不是每部电影的类型:

{
 var(func:allofterms(name@en, "angelina jolie")) {
   actor.film {
    A AS performance.film {  # All films acted in by Angelina Jolie
     B As genre  # Genres of all the films acted in by Angelina Jolie
    }
   }
  }

 var(func:allofterms(name@en, "brad pitt")) {
   actor.film {
    C AS performance.film {  # All films acted in by Brad Pitt
     D as genre  # Genres of all the films acted in by Brad Pitt
    }
   }
  }

 films(func: uid(D)) @filter(uid(B)) {   # Genres from both Angelina and Brad
  name@en
   ~genre @filter(uid(A, C)) {  # Movies in either A or C.
     name@en
   }
 }
}
{
  "data": {
    "films": [
      {
        "name@en": "War film",
        "~genre": [
          {
            "name@en": "Two-Fisted Tales"
          },
          {
            "name@en": "Alexander"
          }
          ...
        ]
      },
      {
        "name@en": "Indie film",
        "~genre": [
          {
            "name@en": "Babel"
          },
          {
            "name@en": "Seven"
          },
          {
            "name@en": "The Tree of Life"
          }
          ...
        ]
      }
      ...
    ]
  }
}

值变量

解析示例:

  • varName as scalarPredicate
  • varName as count(predicate)
  • varName as avg(...)
  • varName as math(...)

支持的类型:int, float, String, dateTime, geo, default, bool

值变量存储标量值。值变量是从封闭块的uid到相应值的映射。

因此,只有在匹配相同uid的上下文中使用值变量的值才有意义————如果在匹配不同uid的块中使用值变量是未定义的。

定义值变量而不在查询的其他地方使用它是错误的。

值变量是通过使用val(var-name)提取值,或使用uid(var-name)提取uid来使用的。

Facet值可以存储在值变量中。

查询示例:80年代经典电影《公主新娘》中,演员所扮演的电影角色数量。查询变量pbActors匹配电影中所有演员的uid。因此,值变量角色是一个从actor UID到角色数量的映射。值变量角色可以在totalRoles查询块中使用,因为该查询块也匹配pbActors uid,因此actor到角色数量的映射是可用的:

{
  var(func:allofterms(name@en, "The Princess Bride")) {
    starring {
      pbActors as performance.actor {
        roles as count(actor.film)
      }
    }
  }
  totalRoles(func: uid(pbActors), orderasc: val(roles)) {
    name@en
    numRoles : val(roles)
  }
}
{
  "data": {
    "totalRoles": [
      {
        "name@en": "Mark Knopfler",
        "numRoles": 1
      },
      {
        "name@en": "Annie Dyson",
        "numRoles": 2
      },
      {
        "name@en": "André the Giant",
        "numRoles": 7
      }
      ...
    ]
  }
}

值变量可以通过从映射中提取UID列表来代替UID变量。

查询示例:与前面的示例相同的查询,但是使用值变量角色来匹配totalRoles查询块中的uid

{
  var(func:allofterms(name@en, "The Princess Bride")) {
    starring {
      performance.actor {
        roles as count(actor.film)
      }
    }
  }
  totalRoles(func: uid(roles), orderasc: val(roles)) {
    name@en
    numRoles : val(roles)
  }
}
{
  "data": {
    "totalRoles": [
      {
        "name@en": "Mark Knopfler",
        "numRoles": 1
      },
      {
        "name@en": "Annie Dyson",
        "numRoles": 2
      },
      {
        "name@en": "André the Giant",
        "numRoles": 7
      }
      ...
    ]
  }
}

变量传播

与查询变量一样,值变量可以在其他查询块中使用,也可以在定义块内嵌套的块中使用。当在定义变量的块内嵌套的块中使用时,该值作为父节点到使用点的所有路径上的变量的和计算。这叫做变量传播:

{
  q(func: uid(0x01)) {
    myscore as math(1)          # A
    friends {                   # B
      friends {                 # C
        ...myscore...
      }
    }
  }
}

在第A行,一个值变量myscore被定义为UID0x01的节点映射到值1。在B处,每个朋友的值仍然是1:每个朋友只有一条路径。穿过朋友的边缘两次,就会到达朋友的朋友。变量myscore将被传播,这样每个朋友的朋友将收到它的父母值的总和:如果一个朋友的朋友只能从一个朋友处到达,那么这个值仍然是1,如果他们可以从两个朋友处到达,那么这个值是2,以此类推。也就是说,标记为C的块中每个朋友的朋友的myscore值将是到他们的路径数。

节点接收到的传播变量的值是其所有父节点值的和。

这种传播是有用的,例如,在标准化用户之间的和、查找节点之间的路径数量和通过图积累一个和时。

查询示例:每一部《哈利·波特》电影中,沃里克·戴维斯扮演的角色数量:

{
    num_roles(func: eq(name@en, "Warwick Davis")) @cascade @normalize {

    paths as math(1)  # records number of paths to each character

    actor : name@en

    actor.film {
      performance.film @filter(allofterms(name@en, "Harry Potter")) {
        film_name : name@en
        characters : math(paths)  # how many paths (i.e. characters) reach this film
      }
    }
  }
}
{
  "data": {
    "num_roles": [
      {
        "actor": "Warwick Davis",
        "film_name": "Harry Potter and the Prisoner of Azkaban",
        "characters": 1
      },
      {
        "actor": "Warwick Davis",
        "film_name": "Harry Potter and the Goblet of Fire",
        "characters": 1
      },
      {
        "actor": "Warwick Davis",
        "film_name": "Harry Potter and the Deathly Hallows – Part 2",
        "characters": 2
      },
      {
        "actor": "Warwick Davis",
        "film_name": "Harry Potter and the Deathly Hallows - Part I",
        "characters": 1
      },
      {
        "actor": "Warwick Davis",
        "film_name": "Harry Potter and the Order of the Phoenix",
        "characters": 1
      },
      {
        "actor": "Warwick Davis",
        "film_name": "Harry Potter and the Philosopher's Stone",
        "characters": 2
      },
      {
        "actor": "Warwick Davis",
        "film_name": "Harry Potter and the Philosopher's Stone",
        "characters": 2
      },
      {
        "actor": "Warwick Davis",
        "film_name": "Harry Potter and the Half-Blood Prince",
        "characters": 1
      },
      {
        "actor": "Warwick Davis",
        "film_name": "Harry Potter and the Deathly Hallows – Part 2",
        "characters": 2
      },
      {
        "actor": "Warwick Davis",
        "film_name": "Harry Potter and the Chamber of Secrets",
        "characters": 1
      }
    ]
  }
}

查询示例:在彼得·杰克逊的电影中出演过的每个演员以及他们在彼得·杰克逊的电影中出演的比例:

{
    movie_fraction(func:eq(name@en, "Peter Jackson")) @normalize {

    paths as math(1)
    total_films : num_films as count(director.film)
    director : name@en

    director.film {
      starring {
        performance.actor {
          fraction : math(paths / (num_films/paths))
          actor : name@en
        }
      }
    }
  }
}
{
  "data": {
    "movie_fraction": [
      {
        "total_films": 19,
        "director": "Peter Jackson",
        "fraction": 0,
        "actor": "Pete O'Herne"
      },
      {
        "total_films": 19,
        "director": "Peter Jackson",
        "fraction": 0,
        "actor": "Peter Jackson"
      },
      {
        "total_films": 19,
        "director": "Peter Jackson",
        "fraction": 0,
        "actor": "Peter Jackson"
      }
      ...
    ]
  }
}

更多的例子可以在两篇Dgraph博客文章中找到(post1, post2),这两篇文章是关于为推荐引擎使用变量传播的。

聚合操作 Aggregation

解析规则:

  • AG(val(varName))

AG的可能取值是:

  • min: 在值变量varName里选择最小值
  • max: 在值变量varName里选择最大值
  • sum: 计算总和
  • avg: 计算varName的平均数

Schema类型:

操作Schema类型
min/maxint float string dateTime default
sum/avgint float

聚合操作只能用在值变量上,不需要索引。

在包含变量定义的查询块上应用聚合。与查询变量和值变量(它们是全局的)不同,聚合是在本地计算的。例如:

A as predicateA {
  ...
  B as predicateB {
    x as ...some value...
  }
  min(val(x))
}

Min

用在根查询中

其他用法

查询示例:Steven导演的,并按第一部电影的上映日期升序排列的电影:

{
  stevens as var(func: allofterms(name@en, "steven")) {
    director.film {
      ird as initial_release_date
      # ird is a value variable mapping a film UID to its release date
    }
    minIRD as min(val(ird))
    # minIRD is a value variable mapping a director UID to their first release date
  }

  byIRD(func: uid(stevens), orderasc: val(minIRD)) {
    name@en
    firstRelease: val(minIRD)
  }
}
{
  "data": {
    "byIRD": [
      {
        "name@en": "Steven McMillan",
        "firstRelease": "0214-02-28T00:00:00Z"
      },
      {
        "name@en": "J. Steven Edwards",
        "firstRelease": "1929-01-01T00:00:00Z"
      },
      {
        "name@en": "Steven Spielberg",
        "firstRelease": "1964-03-24T00:00:00Z"
      }
      ...
    ]
  }
}

Max

用在根查询中

查询示例:获取名字包含Harry Potter的最新更新的电影, 更新日期被映射到一个变量(d),然后被聚合操作(max),最后附加到一个空的查询块中:

{
  var(func: allofterms(name@en, "Harry Potter")) {
    d as initial_release_date
  }
  me() {
    max(val(d))
  }
}
{
  "data": {
    "me": [
      {
        "max(val(d))": "2011-07-27T00:00:00Z"
      }
    ]
  }
}

其它用法

查询示例:Quentin Tarantino发布的电影,按时间最新的排列:

{
  director(func: allofterms(name@en, "Quentin Tarantino")) {
    director.film {
      name@en
      x as initial_release_date
    }
    max(val(x))
  }
}
{
  "data": {
    "director": [
      {
        "director.film": [
          {
            "name@en": "Kill Bill Volume 1",
            "initial_release_date": "2003-10-10T00:00:00Z"
          },
          {
            "name@en": "Django Unchained",
            "initial_release_date": "2012-12-25T00:00:00Z"
          },
          {
            "name@en": "Sin City",
            "initial_release_date": "2005-03-28T00:00:00Z"
          }
          ...
        ]
      }
    ]
  }
}

总和和平均数 Sum, Avg

用在根查询中

其它用法

Aggregating Aggregates

可以将聚合分配给值变量,因此可以依次对这些变量进行聚合。

查询示例:对于彼得·杰克逊电影中的每个演员,找出在任何电影中扮演的角色的数量。把这些数字加起来,就能算出所有演员在这部电影中所扮演的角色总数。然后把这些数字加起来,就能算出演员们在彼得·杰克逊的电影中扮演过的角色总数。注意,这演示了如何聚合聚合;不过,这个问题的答案并不十分精确,因为在彼得·杰克逊的多部电影中出现的演员被计算了不止一次。

{
  PJ as var(func:allofterms(name@en, "Peter Jackson")) {
    director.film {
      starring {  # starring an actor
        performance.actor {
          movies as count(actor.film)
          # number of roles for this actor
        }
        perf_total as sum(val(movies))
      }
      movie_total as sum(val(perf_total))
      # total roles for all actors in this movie
    }
    gt as sum(val(movie_total))
  }

  PJmovies(func: uid(PJ)) {
    name@en
    director.film (orderdesc: val(movie_total), first: 5) {
      name@en
      totalRoles : val(movie_total)
    }
    grandTotal : val(gt)
  }
}
{
  "data": {
    "PJmovies": [
      {
        "name@en": "Peter Jackson",
        "director.film": [
          {
            "name@en": "The Lord of the Rings: The Two Towers",
            "totalRoles": 1565
          },
          {
            "name@en": "The Lord of the Rings: The Return of the King",
            "totalRoles": 1518
          },
          {
            "name@en": "The Lord of the Rings: The Fellowship of the Ring",
            "totalRoles": 1335
          },
          {
            "name@en": "The Hobbit: An Unexpected Journey",
            "totalRoles": 1274
          },
          {
            "name@en": "The Hobbit: The Desolation of Smaug",
            "totalRoles": 1261
          }
        ],
        "grandTotal": 10592
      },
      {
        "name@en": "Peter Jackson"
      },
      {
        "name@en": "Peter Jackson"
      },
      {
        "name@en": "Peter Jackson"
      },
      {
        "name@en": "Sam Peter Jackson"
      }
    ]
  }
}

值变量上的数学操作

值变量可以用数学函数组合在一起。例如,这可以用于关联一个分数,然后该分数用于排序或执行其他操作,例如可能用于构建新闻提要、简单推荐系统等。

Math语句必须包含在Math(<exp>)中,并且必须存储到值变量中。

支持的操作符如下:

操作接受的类型作用
+ - * / %int float进行相应的运算
min max除了geo bool选择最大或最小值
< > <= >= == !=除了geo bool返回 true或者false
floor ceil ln exp sqrtint float进行相应的运算
sincedateTime返回从指定时间开始的浮点数秒数
pow(a, b)int float返回a的b次方
logbase(a, b)int floatlog(a)返回到基数b
cond(a, b, c)第一个操作数必须是bool如果a为真,选择b,否则选c

如果发生整数溢出,或将操作数传递给数学操作(如lnlogbasesqrtpow)导致非法操作,Dgraph将返回错误。

查询示例:将史蒂芬·斯皮尔伯格的每一部电影的演员数量、类型数量和国家数量相加,得出分数。按得分递减的顺序列出这类电影的前五名:

{
	var(func:allofterms(name@en, "steven spielberg")) {
		films as director.film {
			p as count(starring)
			q as count(genre)
			r as count(country)
			score as math(p + q + r)
		}
	}

	TopMovies(func: uid(films), orderdesc: val(score), first: 5){
		name@en
		val(score)
	}
}
{
  "data": {
    "TopMovies": [
      {
        "name@en": "Lincoln",
        "val(score)": 179
      },
      {
        "name@en": "Minority Report",
        "val(score)": 156
      },
      {
        "name@en": "Schindler's List",
        "val(score)": 145
      },
      {
        "name@en": "The Terminal",
        "val(score)": 118
      },
      {
        "name@en": "Saving Private Ryan",
        "val(score)": 99
      }
    ]
  }
}

可以在过滤器中使用值变量及其聚合。

查询示例:为每一部史蒂文·斯皮尔伯格Steven Spielberg的电影计算一个分数,并带有上映日期的条件,以惩罚10年以上的电影,过滤结果分数:

{
  var(func:allofterms(name@en, "steven spielberg")) {
    films as director.film {
      p as count(starring)
      q as count(genre)
      date as initial_release_date
      years as math(since(date)/(365*24*60*60))
      score as math(cond(years > 10, 0, ln(p)+q-ln(years)))
    }
  }

  TopMovies(func: uid(films), orderdesc: val(score)) @filter(gt(val(score), 2)){
    name@en
    val(score)
    val(date)
  }
}
{
  "data": {
    "TopMovies": [
      {
        "name@en": "Lincoln",
        "val(score)": 7.927418,
        "val(date)": "2012-10-08T00:00:00Z"
      }
    ]
  }
}

通过数学操作计算的值存储在值变量中,因此可以聚合。

查询示例:为Steven Spielberg的每一部电影计算一个分数,然后将分数相加:

{
	steven as var(func:eq(name@en, "Steven Spielberg")) @filter(has(director.film)) {
		director.film {
			p as count(starring)
			q as count(genre)
			r as count(country)
			score as math(p + q + r)
		}
		directorScore as sum(val(score))
	}

	score(func: uid(steven)){
		name@en
		val(directorScore)
	}
}
{
  "data": {
    "score": [
      {
        "name@en": "Steven Spielberg",
        "val(directorScore)": 1865
      }
    ]
  }
}

分组 GroupBy

解析示例:

  • q(func: ...) @groupBy(predicate) { min(...) }
  • predicate @groupBy(pred) { count(uid) }

groupby查询聚合给定一组属性的查询结果,在这些属性上对元素进行分组。例如,一个包含块好友@groupby(age) {count(uid)}的查询,查找沿好友边缘可到达的所有节点,根据年龄将这些节点划分为组,然后计算每个组中有多少个节点。返回的结果是分组的边和聚合。

在一个groupby块中,只允许聚合,并且count只能应用于uid

如果groupby应用于uid谓词,则生成的聚合可以保存在一个变量中(将分组的uid映射到聚合值),并在查询的其他地方使用它来提取分组或聚合的边缘以外的信息。

查询示例:对于Steven Spielberg的电影,计算每种类型的电影数量,并为每种类型返回类型名称和数量。名称不能在groupby中提取,因为它不是一个聚合,但是uid(a)可以用于从uid到值映射中提取uid,从而按类型uid组织byGenre查询:

{
  var(func:allofterms(name@en, "steven spielberg")) {
    director.film @groupby(genre) {
      a as count(uid)
      # a is a genre UID to count value variable
    }
  }

  byGenre(func: uid(a), orderdesc: val(a)) {
    name@en
    total_movies : val(a)
  }
}
{
  "data": {
    "byGenre": [
      {
        "name@en": "Drama",
        "total_movies": 21
      },
      {
        "name@en": "Adventure Film",
        "total_movies": 14
      },
      {
        "name@en": "Thriller",
        "total_movies": 13
      },
      {
        "name@en": "Action Film",
        "total_movies": 12
      }
      ...
    ]
  }
}

查询示例:蒂姆·伯顿电影中的演员以及他们在蒂姆·伯顿的电影中扮演了多少角色:

{
  var(func:allofterms(name@en, "Tim Burton")) {
    director.film {
      starring @groupby(performance.actor) {
        a as count(uid)
        # a is an actor UID to count value variable
      }
    }
  }

  byActor(func: uid(a), orderdesc: val(a)) {
    name@en
    val(a)
  }
}
{
  "data": {
    "byActor": [
      {
        "name@en": "Johnny Depp",
        "val(a)": 8
      },
      {
        "name@en": "Helena Bonham Carter",
        "val(a)": 7
      }
      ...
    ]
  }
}

Expand 函数

可以使用expand()函数在节点外展开谓词。要使用expand(),类型系统是必需的。请参阅关于类型系统的一节,以检查如何设置类型节点。本节的其余部分假设您熟悉该节。

使用expand函数有两种方法。

可以将类型传递给expand()以展开该类型中的所有谓词。

查询示例:列出哈利波特系列的电影:

{
  all(func: eq(name@en, "Harry Potter")) @filter(type(Series)) {
    name@en
    expand(Series) {
      name@en
      expand(Film)
    }
  }
}
{
  "data": {
    "all": [
      {
        "name@en": [
          "Harry Potter",
          "Harry Potter"
        ],
        "name@fi": "Harry Potter (elokuvasarja)",
        "name@hu": "Harry Potter filmsorozat",
        "name@sl": "Filmska serija Harry Potter",
        "name@da": "Harry Potter-filmserien",
        "name@ca": "Saga de Harry Potter",
        "series.films_in_series": [
          {
            "name@en": [
              "Harry Potter and the Chamber of Secrets",
              "Harry Potter and the Chamber of Secrets"
            ],
            "name@fi": "Harry Potter ja salaisuuksien kammio",
            "name@zh": "哈利·波特与密室",
            "name@pt-BR": "Harry Potter e a Câmara Secreta",
            "name@hu": "Harry Potter és a Titkok Kamrája",
            "name@sl": "Harry Potter in dvorana skrivnosti",
            "name@et": "Harry Potter ja saladuste kamber",
            "name@lv": "Harijs Poters un Noslēpumu kambaris",
            "name@de": "Harry Potter und die Kammer des Schreckens",
            "tagline@en": "Hogwarts is back in session.",
            "netflix_id": "60024925",
            "initial_release_date": "2002-11-03T00:00:00Z",
            "rottentomatoes_id": "harry_potter_and_the_chamber_of_secrets",
            "traileraddict_id": "harry-potter-and-the-chamber-of-secrets",
            "metacritic_id": "harrypotterandthechamberofsecrets"
          },
          {
            "name@en": [
              "Harry Potter and the Deathly Hallows - Part I",
              "Harry Potter and the Deathly Hallows - Part I"
            ],
            "name@fi": "Harry Potter ja kuoleman varjelukset, osa 1",
            "name@zh": "哈利·波特与死亡圣器(上)",
            "name@pt-BR": "Harry Potter e as Relíquias da Morte – Parte 1",
            "name@hu": "Harry Potter és a Halál ereklyéi 1.",
            "name@sl": "Harry Potter in Svetinje smrti - 1. del",
            "name@pt-PT": "Harry Potter e os Talismãs da Morte: Parte 1",
            "name@fr": "Harry Potter et les reliques de la mort - 1ère partie",
            "name@ja": "ハリー・ポッターと死の秘宝 PART1",
            "name@ro": "Harry Potter și Talismanele Morții. Partea 1",
            "name@uk": "Гаррі Поттер і смертельні реліквії: частина 1",
            "name@id": "Harry Potter and the Deathly Hallows - Bagian 1",
            "name@iw": "הארי פוטר ואוצרות המוות",
            "name@lt": "Haris Poteris ir Mirties relikvijos. 1 dalis",
            "name@zh-Hant": "哈利波特-死神的聖物1",
            "name@sr": "Хари Потер и реликвије Смрти: Први део",
            "name@el": "Ο Χάρι Πότερ και οι Κλήροι του Θανάτου - Μέρος 1",
            "name@sv": "Harry Potter och dödsrelikerna",
            "name@pl": "Harry Potter i Insygnia Śmierci: Część I",
            "name@ar": "هاري بوتر ومقدسات الموت – الجزء 1",
            "name@bg": "Хари Потър и Даровете на Смъртта: Първа част",
            "name@et": "Harry Potter ja surma vägised: osa 1",
            "name@lv": "Harijs Poters un Nāves dāvesti: Pirmā daļa",
          }
        ]
      }
    ]
  }
}

如果_all_作为参数传递给expand(),则要展开的谓词将是分配给给定节点的类型中的字段的联合。 _all_关键字要求节点具有类型。Dgraph将查找已分配给节点的所有类型,查询类型以检查它们具有哪些属性,并使用这些属性计算要展开的谓词列表。

例如,考虑一个具有AnimalPet类型的节点,它们有以下定义:

type Animal {
    name
    species
    dob
}

type Pet {
    owner
    veterinarian
}

当在这个节点上调用expand(_all_)时,Dgraph首先检查节点的类型AnimalPet。然后,它将获得AnimalPet的定义,并根据它们的类型定义构建谓词列表。

name
species
dob
owner
veterinarian

注意:对于字符串谓词,展开只返回没有标记语言的值(参见语言首选项)。所以通常需要添加name@frname@.以及扩展查询。

在过滤中展开

在传出边缘的类型上展开查询支持筛选器。例如,expand(_all_) @filter(type(Person))将在所有谓词上展开,但只包含目标节点类型为Person的边。因为只有uid类型的节点可以具有类型,所以该查询将过滤掉任何标量值。

请注意,expand函数目前不支持其他类型的过滤器和指令。过滤器需要使用type函数来允许过滤器。支持逻辑与或操作。例如,expand(_all_) @filter(type(Person) OR type(Animal))将只扩展指向任一类型节点的边。

级联指令

使用@cascade指令,没有在查询中指定所有谓词的节点将被删除。在应用了某些筛选器或节点可能没有列出所有谓词的情况下,这可能很有用。

查询例:《哈利波特》电影,每个演员和角色都扮演过。使用cascade,任何不是由名为沃里克的演员扮演的角色都会被删除,就像任何没有名为沃里克的演员的《哈利波特》电影一样。如果没有“级联”,所有角色都会回归,但只有那些由名为沃里克的演员扮演的角色才会有演员的名字:

{
  HP(func: allofterms(name@en, "Harry Potter")) @cascade {
    name@en
    starring{
        performance.character {
          name@en
        }
        performance.actor @filter(allofterms(name@en, "Warwick")){
            name@en
         }
    }
  }
}
{
  "data": {
    "HP": [
      {
        "name@en": "Harry Potter and the Chamber of Secrets",
        "starring": [
          {
            "performance.character": [
              {
                "name@en": "Professor Filius Flitwick"
              }
            ],
            "performance.actor": [
              {
                "name@en": "Warwick Davis"
              }
            ]
          }
        ]
      },
      {
        "name@en": "Harry Potter and the Deathly Hallows - Part I",
        "starring": [
          {
            "performance.character": [
              {
                "name@en": "Griphook"
              }
            ],
            "performance.actor": [
              {
                "name@en": "Warwick Davis"
              }
            ]
          }
        ]
      },
      {
        "name@en": "Harry Potter and the Deathly Hallows – Part 2",
        "starring": [
          {
            "performance.character": [
              {
                "name@en": "Professor Filius Flitwick"
              }
            ],
            "performance.actor": [
              {
                "name@en": "Warwick Davis"
              }
            ]
          },
          {
            "performance.character": [
              {
                "name@en": "Griphook"
              }
            ],
            "performance.actor": [
              {
                "name@en": "Warwick Davis"
              }
            ]
          }
        ]
      },
      {
        "name@en": "Harry Potter and the Prisoner of Azkaban",
        "starring": [
          {
            "performance.character": [
              {
                "name@en": "Professor Filius Flitwick"
              }
            ],
            "performance.actor": [
              {
                "name@en": "Warwick Davis"
              }
            ]
          }
        ]
      },
      {
        "name@en": "Harry Potter and the Order of the Phoenix",
        "starring": [
          {
            "performance.character": [
              {
                "name@en": "Professor Filius Flitwick"
              }
            ],
            "performance.actor": [
              {
                "name@en": "Warwick Davis"
              }
            ]
          }
        ]
      },
      {
        "name@en": "Harry Potter and the Goblet of Fire",
        "starring": [
          {
            "performance.character": [
              {
                "name@en": "Professor Filius Flitwick"
              }
            ],
            "performance.actor": [
              {
                "name@en": "Warwick Davis"
              }
            ]
          }
        ]
      },
      {
        "name@en": "Harry Potter and the Half-Blood Prince",
        "starring": [
          {
            "performance.character": [
              {
                "name@en": "Professor Filius Flitwick"
              }
            ],
            "performance.actor": [
              {
                "name@en": "Warwick Davis"
              }
            ]
          }
        ]
      },
      {
        "name@en": "Harry Potter and the Philosopher's Stone",
        "starring": [
          {
            "performance.character": [
              {
                "name@en": "Goblin Bank Teller"
              }
            ],
            "performance.actor": [
              {
                "name@en": "Warwick Davis"
              }
            ]
          },
          {
            "performance.character": [
              {
                "name@en": "Professor Filius Flitwick"
              }
            ],
            "performance.actor": [
              {
                "name@en": "Warwick Davis"
              }
            ]
          }
        ]
      }
    ]
  }
}

您也可以在内部查询块上应用@cascade

{
  HP(func: allofterms(name@en, "Harry Potter")) {
    name@en
    genre {
      name@en
    }
    starring @cascade {
        performance.character {
          name@en
        }
        performance.actor @filter(allofterms(name@en, "Warwick")){
            name@en
         }
    }
  }
}
{
  "data": {
    "HP": [
      {
        "name@en": "Harry Potter and the Chamber of Secrets",
        "genre": [
          {
            "name@en": "Family"
          },
          {
            "name@en": "Adventure Film"
          },
          {
            "name@en": "Fantasy"
          },
          {
            "name@en": "Mystery"
          }
        ],
        "starring": [
          {
            "performance.character": [
              {
                "name@en": "Professor Filius Flitwick"
              }
            ],
            "performance.actor": [
              {
                "name@en": "Warwick Davis"
              }
            ]
          }
        ]
      },
      {
        "name@en": "Harry Potter and the Deathly Hallows - Part I",
        "genre": [
          {
            "name@en": "Fiction"
          },
          {
            "name@en": "Family"
          },
          {
            "name@en": "Adventure Film"
          },
          {
            "name@en": "Drama"
          },
          {
            "name@en": "Fantasy"
          },
          {
            "name@en": "Action Film"
          },
          {
            "name@en": "Mystery"
          }
        ],
        "starring": [
          {
            "performance.character": [
              {
                "name@en": "Griphook"
              }
            ],
            "performance.actor": [
              {
                "name@en": "Warwick Davis"
              }
            ]
          }
        ]
      }
      ...
    ]
  }
}

参数化@cascade

@cascade指令可以选择一个字段列表作为参数。这将更改默认行为,只考虑提供的字段,而不是类型的所有字段。列出的字段自动级联为嵌套选择集的必需参数。参数化的级联作用于层次(例如根函数或更低的层次),所以你需要在你想要应用它的确切层次上指定@cascade(param)

提示:@cascade(predicate)的规则是,谓词需要在查询中处于与@cascade相同的级别。

以下查询为例:

{
  nodes(func: allofterms(name@en, "jones indiana")) {
    name@en
    genre @filter(anyofterms(name@en, "action adventure")) {
      name@en
    }
    produced_by {
      name@en
    }
  }
}
{
  "data": {
    "nodes": [
      {
        "name@en": "The Adventures of Young Indiana Jones: Passion for Life"
      },
      {
        "name@en": "Indiana Jones and the Temple of Doom",
        "genre": [
          {
            "name@en": "Adventure Film"
          },
          {
            "name@en": "Action/Adventure"
          },
          {
            "name@en": "Action Film"
          },
          {
            "name@en": "Costume Adventure"
          }
        ],
        "produced_by": [
          {
            "name@en": "Robert Watts"
          }
        ]
      },
      {
        "name@en": "The Adventures of Young Indiana Jones: The Perils of Cupid"
      },
      {
        "name@en": "The Adventures of Young Indiana Jones: Daredevils of the Desert",
        "genre": [
          {
            "name@en": "Adventure Film"
          }
        ]
      }
      ...
    ]
  }
}

该查询获取包含所有术语jones indiana的节点,然后遍历genreproduced_by。它还为游戏类型添加了一个额外的筛选条件,即只能获得名称中包含“动作”或“冒险”内容的游戏。结果包括没有类型的节点和没有类型和制作人的节点。

如果你使用没有参数的常规@级联,你就会失去那些有题材但没有制作人的游戏。

要获得具有遍历类型但可能没有produced_by的节点,你可以参数化级联:

{
  nodes(func: allofterms(name@en, "jones indiana")) @cascade(genre) {
    name@en
    genre @filter(anyofterms(name@en, "action adventure")) {
      name@en
    }
    produced_by {
      name@en
    }
    written_by {
      name@en
    }
  }
}
{
  "data": {
    "nodes": [
      {
        "name@en": "Indiana Jones and the Temple of Doom",
        "genre": [
          {
            "name@en": "Adventure Film"
          },
          {
            "name@en": "Action/Adventure"
          },
          {
            "name@en": "Action Film"
          },
          {
            "name@en": "Costume Adventure"
          }
        ],
        "produced_by": [
          {
            "name@en": "Robert Watts"
          }
        ],
        "written_by": [
          {
            "name@en": "Gloria Katz"
          },
          {
            "name@en": "Willard Huyck"
          }
        ]
      }
      ...
    ]
  }
}

如果您想检查多个字段,只需用逗号分隔它们。例如,级联produced_by和written_by:

{
  nodes(func: allofterms(name@en, "jones indiana")) @cascade(produced_by,written_by) {
    name@en
    genre @filter(anyofterms(name@en, "action adventure")) {
      name@en
    }
    produced_by {
      name@en
    }
    written_by {
      name@en
    }
  }
}
{
  "data": {
    "nodes": [
      {
        "name@en": "Indiana Jones and the Temple of Doom",
        "genre": [
          {
            "name@en": "Adventure Film"
          },
          {
            "name@en": "Action/Adventure"
          },
          {
            "name@en": "Action Film"
          },
          {
            "name@en": "Costume Adventure"
          }
        ],
        "produced_by": [
          {
            "name@en": "Robert Watts"
          }
        ],
        "written_by": [
          {
            "name@en": "Gloria Katz"
          },
          {
            "name@en": "Willard Huyck"
          }
        ]
      },
      {
        "name@en": "Indiana Jones and the Raiders of the Lost Ark",
        "genre": [
          {
            "name@en": "Adventure Film"
          },
          {
            "name@en": "Action Film"
          }
        ],
        "produced_by": [
          {
            "name@en": "Frank Marshall"
          }
        ],
        "written_by": [
          {
            "name@en": "Lawrence Kasdan"
          }
        ]
      },
      {
        "name@en": "Indiana Jones and the Kingdom of the Crystal Skull",
        "genre": [
          {
            "name@en": "Adventure Comedy"
          },
          {
            "name@en": "Adventure Film"
          },
          {
            "name@en": "Action Film"
          },
          {
            "name@en": "Costume Adventure"
          }
        ],
        "produced_by": [
          {
            "name@en": "Frank Marshall"
          },
          {
            "name@en": "Flávio R. Tambellini"
          }
        ],
        "written_by": [
          {
            "name@en": "David Koepp"
          }
        ]
      }
      ...
    ]
  }
}

嵌套和参数化级联

字段选择的级联特性被嵌套的@cascade覆盖。

前面的示例也可以沿链级联,并根据需要在每个级别上重写。

例如,如果你只想要“由制作《侏罗纪世界》的同一个人制作的印第安纳·琼斯电影”:

{
  nodes(func: allofterms(name@en, "jones indiana")) @cascade(produced_by) {
    name@en
    genre @filter(anyofterms(name@en, "action adventure")) {
      name@en
    }
    produced_by @cascade(producer.film) {
      name@en
      producer.film @filter(allofterms(name@en, "jurassic world")) {
        name@en
      }
    }
    written_by {
      name@en
    }
  }
}
{
  "data": {
    "nodes": [
      {
        "name@en": "Indiana Jones and the Raiders of the Lost Ark",
        "genre": [
          {
            "name@en": "Adventure Film"
          },
          {
            "name@en": "Action Film"
          }
        ],
        "produced_by": [
          {
            "name@en": "Frank Marshall",
            "producer.film": [
              {
                "name@en": "Jurassic World"
              }
            ]
          }
        ],
        "written_by": [
          {
            "name@en": "Lawrence Kasdan"
          }
        ]
      },
      {
        "name@en": "Indiana Jones and the Kingdom of the Crystal Skull",
        "genre": [
          {
            "name@en": "Adventure Comedy"
          },
          {
            "name@en": "Adventure Film"
          },
          {
            "name@en": "Action Film"
          },
          {
            "name@en": "Costume Adventure"
          }
        ],
        "produced_by": [
          {
            "name@en": "Frank Marshall",
            "producer.film": [
              {
                "name@en": "Jurassic World"
              }
            ]
          }
        ],
        "written_by": [
          {
            "name@en": "David Koepp"
          }
        ]
      }
    ]
  }
}

另一个嵌套的例子:找到《星球大战》和《侏罗纪世界》的编剧和制片人是同一个人的《印第安纳琼斯》电影:

{
  nodes(func: allofterms(name@en, "jones indiana")) @cascade(produced_by,written_by) {
    name@en
    genre @filter(anyofterms(name@en, "action adventure")) {
      name@en
    }
    produced_by @cascade(producer.film) {
      name@en
      producer.film @filter(allofterms(name@en, "jurassic world")) {
        name@en
      }
    }
    written_by @cascade(writer.film) {
      name@en
      writer.film @filter(allofterms(name@en, "star wars")) {
        name@en
      }
    }
  }
}
{
  "data": {
    "nodes": [
      {
        "name@en": "Indiana Jones and the Raiders of the Lost Ark",
        "genre": [
          {
            "name@en": "Adventure Film"
          },
          {
            "name@en": "Action Film"
          }
        ],
        "produced_by": [
          {
            "name@en": "Frank Marshall",
            "producer.film": [
              {
                "name@en": "Jurassic World"
              }
            ]
          }
        ],
        "written_by": [
          {
            "name@en": "Lawrence Kasdan",
            "writer.film": [
              {
                "name@en": "Star Wars Episode V: The Empire Strikes Back"
              },
              {
                "name@en": "Star Wars: The Force Awakens"
              }
            ]
          }
        ]
      }
    ]
  }
}

规范指令

使用@normalize指令,只返回有别名的谓词,并将结果平化以删除嵌套。

查询示例:Steven Spielberg每部电影的电影名,国家和前两位演员(按UID顺序),没有initial_release_date,因为没有给出别名并通过@normalize进行平化:

{
  director(func:allofterms(name@en, "steven spielberg")) @normalize {
    director: name@en
    director.film {
      film: name@en
      initial_release_date
      starring(first: 2) {
        performance.actor {
          actor: name@en
        }
        performance.character {
          character: name@en
        }
      }
      country {
        country: name@en
      }
    }
  }
}
{
  "data": {
    "director": [
      {
        "director": "Steven Spielberg",
        "film": "Hook",
        "actor": "John Michael",
        "character": "Doctor",
        "country": "United States of America"
      },
      {
        "director": "Steven Spielberg",
        "film": "Hook",
        "actor": "Brad Parker",
        "character": "Jim",
        "country": "United States of America"
      },
      {
        "director": "Steven Spielberg",
        "film": "The Color Purple",
        "actor": "Carl Anderson",
        "country": "United States of America"
      },
      {
        "director": "Steven Spielberg",
        "film": "The Color Purple",
        "actor": "Oprah Winfrey",
        "character": "Sofia",
        "country": "United States of America"
      }
      ...
    ]
  }
}

还可以在嵌套查询块上应用@normalize。它将以类似的方式工作,但只是使应用了@normalize的嵌套查询块的结果变平。@normalize将返回一个列表,不管它应用在哪个类型的属性上:

{
  director(func:allofterms(name@en, "steven spielberg")) {
    director: name@en
    director.film {
      film: name@en
      initial_release_date
      starring(first: 2) @normalize {
        performance.actor {
          actor: name@en
        }
        performance.character {
          character: name@en
        }
      }
      country {
        country: name@en
      }
    }
  }
}
{
  "data": {
    "director": [
      {
        "director": "Steven Spielberg",
        "director.film": [
          {
            "film": "Hook",
            "initial_release_date": "1991-12-08T00:00:00Z",
            "starring": [
              {
                "actor": "John Michael",
                "character": "Doctor"
              },
              {
                "actor": "Brad Parker",
                "character": "Jim"
              }
            ],
            "country": [
              {
                "country": "United States of America"
              }
            ]
          },
          {
            "film": "The Color Purple",
            "initial_release_date": "1985-12-16T00:00:00Z",
            "starring": [
              {
                "actor": "Carl Anderson"
              },
              {
                "actor": "Oprah Winfrey",
                "character": "Sofia"
              }
            ],
            "country": [
              {
                "country": "United States of America"
              }
            ]
          }
          ...
        ]
      }
    ]
  }
}

ignorereflex 指令

@ignorereflex指令强制通过查询结果中的任何路径删除作为父节点可访问的子节点

查询示例:Rutger Hauer的所有合作者。如果没有@ignorereflex,结果也会包括Rutger Hauer每一部电影:

{
  coactors(func: eq(name@en, "Rutger Hauer")) @ignorereflex {
    actor.film {
      performance.film {
        starring {
          performance.actor {
            name@en
          }
        }
      }
    }
  }
}
{
  "data": {
    "coactors": [
      {
        "actor.film": [
          {
            "performance.film": [
              {
                "starring": [
                  {
                    "performance.actor": [
                      {
                        "name@en": "John de Lancie"
                      }
                    ]
                  },
                  {
                    "performance.actor": [
                      {
                        "name@en": "Lauren Lee Smith"
                      }
                    ]
                  },
                  {
                    "performance.actor": [
                      {
                        "name@en": "Dan Callahan"
                      }
                    ]
                  }
                  ...
                ]
              }
            ]
          }
        ]
      }
    ]
  }
}

调试

出于调试的目的,可以将查询参数debug=true附加到查询。附加这个参数可以让您在响应的扩展键下检索所有实体的uid属性以及server_latencystart_ts信息。

  • parsing_ns: 解析查询的延迟(以纳秒为单位)。
  • processing_ns: 处理查询的延迟(以纳秒为单位)。
  • encoding_ns: 对JSON响应进行编码的延迟(以纳秒为单位)。
  • start_ts: 事务的逻辑开始时间戳。

以debug作为查询参数的查询:

curl -H "Content-Type: application/dql" http://localhost:8080/query?debug=true -XPOST -d $'{
  tbl(func: allofterms(name@en, "The Big Lebowski")) {
    name@en
  }
}' | python -m json.tool | less

返回uidserver_latency

{
  "data": {
    "tbl": [
      {
        "uid": "0x41434",
        "name@en": "The Big Lebowski"
      },
      {
        "uid": "0x145834",
        "name@en": "The Big Lebowski 2"
      },
      {
        "uid": "0x2c8a40",
        "name@en": "Jeffrey \"The Big\" Lebowski"
      },
      {
        "uid": "0x3454c4",
        "name@en": "The Big Lebowski"
      }
    ],
    "extensions": {
      "server_latency": {
        "parsing_ns": 18559,
        "processing_ns": 802990982,
        "encoding_ns": 1177565
      },
      "txn": {
        "start_ts": 40010
      }
    }
  }
}

注意GraphQL+-已重命名为Dgraph查询语言(DQL)。虽然application/dqlContent-Type头的首选值,但我们将继续支持Content-Type: application/graphql+-,以避免进行破坏性的更改。

Schema

对于每个谓词,模式指定目标的类型。如果谓词p的类型为T,那么对于所有主语-谓词-对象三元组s p o,对象o的模式类型为T

  • 在发生突变时,将检查标量类型,如果值不能转换为模式类型,则抛出错误。
  • 在查询时,根据谓词的模式类型返回值结果。

如果在突变为谓词添加三元组之前没有指定模式类型,则从第一次突变推断该类型。这种类型是:

  • 类型uid,如果谓词的第一个变异具有主语和宾语的节点,或者
  • 如果对象是文字且RDF类型出现在第一个变体中,则从RDF类型派生,或者
  • 默认类型

Schema类型

Dgraph支持标量类型和UID类型。

Scalar 标量类型

对于所有具有标量类型谓词的三元组,对象都是文字。

Dgraph类型Go类型
defaultstring
intint64
floatfloat
stringstring
boolbool
dateTimetime.Time(RFC3399 时间格式[可选的时区] 例如: 2006-01-02T15:04:05.999999999+10:00 或者 2006-01-02T15:04:05.999999999)
geogo-geom
passwordstring(被加密的)

注意,只有当dateTime标量类型兼容RFC 3339时,Dgraph才支持日期和时间格式,这与ISO 8601(在RDF规范中定义)不同。在将值发送到Dgraph之前,应该将其转换为RFC 3339格式。

UID 类型

uid类型表示节点-节点边;在内部,每个节点都表示为一个uint64 id

Dgraph 类型Go类型
uiduint64

添加或者修改 Schema

通过指定模式为列表类型,还可以为标准普尔添加多个标量值。下面示例中的occupation可以存储每个S P的字符串列表。

索引是用@index指定的,用参数指定标记器。当为谓词指定索引时,必须指定索引的类型。例如:

name: string @index(exact, fulltext) @count .
multiname: string @lang .
age: int @index(int) .
friend: [uid] @count .
dob: dateTime .
location: geo @index(geo) .
occupations: [string] @index(term) .

如果没有为谓词存储数据,模式突变将设置一个空模式,准备接收三元组。

如果数据在突变之前已经存储,则不会检查现有的值以符合新模式。在查询时,Dgraph尝试将现有值转换为新的模式类型,忽略任何转换失败的值。

如果数据存在,并且在模式变化中指定了新索引,则删除未在更新列表中的任何索引,并为每个指定的新标记器创建一个新索引。

如果由模式变异指定,反向边也会被计算。

注意,不能定义以dgraph开头的谓词名称。,它保留为Dgraph的内部类型/谓词的名称空间。例如,将dgraph.name定义为谓词是无效的。

在后台处理索引

根据数据的大小,索引的计算时间可能会很长。从Dgraph版本20.03.0开始,索引可以在后台计算,因此索引可能在Alter操作返回后仍在运行。这要求您在运行需要新创建索引的查询之前等待索引完成。这样的查询将失败,并出现一个错误,通知给定谓词没有索引或没有反向边。

如果已经在修改一个错误架构,那么alter操作也会失败。请重试。不过,在索引进行时,突变可以成功执行。

例如,假设我们用下面的模式执行一个Alter操作:

name: string @index(fulltext, term) .
age: int @index(int) @upsert .
friend: [uid] @count @reverse .

一旦Alter操作返回,Dgraph将报告以下模式,并启动后台任务来计算所有新的索引:

name: string .
age: int @upsert .
friend: [uid] .

当索引计算完成后,Dgraph将开始报告模式中的索引。在多节点集群中,alpha可能会在不同的时间完成计算索引。在这种情况下,alpha可能会返回不同的模式,直到在所有alpha上完成所有索引的计算。

如果在计算索引时出现意外错误,可能导致后台索引任务失败。您应该重试Alter操作,以便更新架构,或跨所有alpha同步架构。

要了解如何检查后台索引状态,请参见查询运行状况

HTTP API

todo

Grpc API

todo

Predicate 规则

Predicate允许任何字母数字组合。Dgraph还支持国际化资源标识符IRIs。您可以在谓词i18n中了解更多信息。

允许特殊字符

不接受单个特殊字符,包括来自IRIs的特殊字符。它们必须以字母数字字符作为前缀/后缀:

][&*()_-+=!#$%

注意:我们不会限制您使用@后缀,但是后缀字符将被忽略。

被禁止的特殊字符串

^}|{`\~

Predicate 国际化

如果predicateURI或具有特定于语言的字符,则在执行模式更改时用尖括号<>将其括起来。

注意,Dgraph支持谓词名称和值的国际化资源标识符(IRIs)。

Schema 解析:

<职业>: string @index(exact) .
<年龄>: int @index(int) .
<地点>: geo @index(geo) .
<公司>: string .

该语法允许国际化谓词名称,但全文索引默认仍为英语。要为您的语言使用正确的标记器,您需要使用@lang指令并使用语言标记输入值。

Schema:

<公司>: string @index(fulltext) @lang .

变更数据:

{
  set {
    _:a <公司> "Dgraph Labs Inc"@en .
    _:b <公司> "夏新科技有限责任公司"@zh .
    _:a <dgraph.type> "Company" .
  }
}

查询数据:

{
  q(func: alloftext(<公司>@zh, "夏新科技有限责任公司")) {
    uid
    <公司>@.
  }
}

插入指令 Upsert directive

要在predicate上使用upsert操作,请在模式中指定@upsert指令。当使用@upsert指令提交涉及predicate的事务时,Dgraph会检查索引键是否存在冲突,这有助于在运行并发upsert时执行唯一性约束。

这就是为predicate指定upsert指令的方法。

email: string @index(exact) @upsert .

Noconflict directive

NoConflict指令防止在predicate级别进行冲突检测。这是一个实验性的特性,不是一个推荐的指令,但它的存在是为了帮助避免不具有高正确性要求的谓词的冲突。这可能会导致数据丢失,特别是当使用带有count索引的谓词时。

这就是为谓词指定@noconflict指令的方式:

email: string @index(exact) @noconflict .

RDF 类型

Dgraph在变更中支持许多RDF类型。

除了在第一次更改时暗示模式类型外,RDF类型还可以覆盖用于存储的模式类型。

如果predicate具有模式类型,而变体具有具有不同基础Dgraph类型的RDF类型,则会检查模式类型的可转换性,如果它们不兼容,则会抛出错误,但值存储在RDF类型对应的Dgraph类型中。查询结果总是以模式类型返回。

例如,如果没有为年龄谓词设置模式。给出如下变更:

Dgraph:

  • 将模式类型设置为int,正如前一个三元组所暗示的那样。
  • 13在存储上转换为int
  • 检查14可以转换为int,但存储为字符串。
  • 为其余两个三元组抛出错误,因为14.5不能转换为int

扩展类型 Extended Types

Password 类型

通过将属性的模式设置为password类型来设置实体的密码。不能直接查询密码,只能通过checkpwd函数进行匹配检查。密码使用bcrypt加密。

例如:设置密码,首先设置schema,然后设置密码:

pass: password .

使用变更设置密码:

{
  set {
    <0x123> <name> "Password Example" .
    <0x123> <pass> "ThePassword" .
  }
}

测试密码是否正确:

{
  check(func: uid(0x123)) {
    name
    checkpwd(pass, "ThePassword")
  }
}

返回:

{
  "data": {
    "check": [
      {
        "name": "Password Example",
        "checkpwd(pass)": true
      }
    ]
  }
}

checkpwd函数上使用别名:

{
  check(func: uid(0x123)) {
    name
    secret: checkpwd(pass, "ThePassword")
  }
}

返回:

{
  "data": {
    "check": [
      {
        "name": "Password Example",
        "secret": true
      }
    ]
  }
}

Indexing 索引

在一个 predicate 上使用函数需要指定索引

当通过应用函数进行筛选时,Dgraph使用索引来高效地搜索可能较大的数据集。

所有标量类型都可以被索引。

类型int, float, boolgeo都只有一个默认索引:带有命名为int, float, boolgeo的标记器。

类型stringdateTime有许多索引。

String Indices

String类型的值可用下表的索引进行检索:

Dgraph函数需要的索引或tokenizer标注
eqhash exact term fulltexteq的最佳性能指标是哈希值。只有在您还需要术语或全文搜索时才使用termfulltext。如果您已经在使用term,那么也不需要使用hashexact
le ge lt gtexact允许更快地查询
allofterms amyoftermsterm允许在一个句子中使用短语查询
alloftext anyoftextfulltext匹配特定语言的词干和停止词。
regexptrigram正则表达式匹配。也可用于相等检查。

错误的索引选择可能会导致性能损失和增加事务冲突率。只使用应用程序需要的最小数量和最简单的索引。

DateTime Indices

DateTime类型的值可用下表地索引进行检索:

索引名字 tokenizer索引到的日期的部分
year索引到年份(默认)
month索引年份和月份
day索引年份、月份、天
hour索引年份、月份、天、小时

dateTime索引的选择允许选择索引的精度。应用程序,如这些文档中的电影示例,需要搜索日期,但每年的节点相对较少,可能更喜欢年份标记器;依赖于细粒度日期搜索(如实时传感器读数)的应用程序可能更喜欢小时索引。

所有的dateTime索引都是可排序的。

Sortable Indices

并不是所有的索引都在它们索引的值之间建立一个总的顺序。可排序索引允许不等式函数和排序。

  • 索引intfloat是可排序的。
  • 字符串索引精确是可排序的。
  • 所有的dateTime索引都是可排序的。

例如,给定一个字符串类型的边名,要按名称排序或对名称执行不等过滤,必须指定确切的索引。在这种情况下,模式查询将至少返回以下标记器:

{
  "predicate": "name",
  "type": "string",
  "index": true,
  "tokenizer": [
    "exact"
  ]
}

Count Indices

对于具有@countpredicate,将每个节点的边数索引为索引。这样可以快速查询表单:

{
  q(func: gt(count(pred), threshold)) {
    ...
  }
}

List 类型

如果在Schema中指定,变量类型的predicate也可以存储值的list。标量类型需要包含在[]中,以表明它是一个list类型。

occupations: [string] .
score: [int] .
  • set操作添加到值列表中。存储值的顺序是不确定的。
  • delete操作从列表中删除值。
  • 查询这些谓词将返回数组中的列表。
  • 索引可以应用于具有列表类型的谓词,并且可以对其使用函数。
  • 不允许使用这些谓词进行排序。
  • 这些列表就像一个无序的集合。例如:["e1", "e1", "e2"]可能会被存储为["e2", "e1"],也就是说,重复的值将不会被存储,顺序也不会被保留。

在List中使用过滤函数

Dgraph支持基于list的过滤。过滤的工作原理类似于它在edge上的工作原理,并且具有相同的可用函数。

例如,查询或父边的@filter(eq(occupation,"Teacher"))将显示数组中每个节点的列表中的所有职业,但只包含将Teacher作为职业之一的节点。但是,不支持值边过滤。

反转一条边

图的边是单向的。对于节点-节点边,有时建模需要反向边。如果只有一些主-谓词-对象三元组有反向,则必须手动添加它们。但是如果一个谓词总是有反向,如果在模式中指定了@reverse,则Dgraph会计算反向边。

anEdge的反向边是~anEdge

对于现有数据,Dgraph计算所有反向边。对于schema变更后添加的数据,Dgraph计算并存储每个添加的三元组的反向边。

type Person {
  name string
}
type Car {
  regnbr string
  owner Person
}
owner uid @reverse .
regnbr string @index(exact) .
name string @index(exact) .

这使得查询person和他们的车成为可能:

q(func type(Person)) {
  name
  ~owner { name }
}

为了在结果中得到一个不同于~owner的键,可以用想要的标签(在本例中是cars)来编写查询:

q(func type(Person)) {
  name
  cars: ~owner { name }
}

如果一辆车有多个“所有者”,这也适用:

owner [uid] @reverse .

在这两种情况下,owner边都应该设置在Car上:

_:p1 <name> "Mary" .
_:p1 <dgraph.type> "Person" .
_:c1 <regnbr> "ABC123" .
_:c1 <dgraph.type> "Car" .
_:c1 <owner> _:p1

查询 Schema

你可以使用下面的DQL语句查询整个Schema

schema {}

注意:与常规查询不同,模式查询没有被花括号包围。此外,模式查询和常规查询不能一起使用。

您可以在查询体中查询特定的模式字段:

schema {
  type
  index
  reverse
  tokenizer
  list
  count
  upsert
  lang
}

你也可以查询特定的谓词:

schema(pred: [name, friend]) {
  type
  index
  reverse
  tokenizer
  list
  count
  upsert
  lang
}

注意,如果启用了ACL,那么模式查询只返回登录的ACL用户具有读访问权限的谓词。

类型也可以查询。下面是一些示例查询:

schema(type: Movie) {}
schema(type: [Person, Animal]) {}

注意,类型查询不包含花括号之间的任何内容。输出将是请求类型的完整定义。

类型系统

Dgraph支持一个类型系统,该系统可用于对节点进行分类,并根据节点的类型查询节点。在展开(expand)查询期间也使用类型系统。

类型定义

类型是使用类似graphql的语法定义的。例如:

type Student {
  name
  dob
  home_address
  year
  friends
}

注意,不能定义以dgraph开头的类型名。,它保留为Dgraph的内部类型/predicate的名称空间。例如,定义dgraph.student类型无效。

类型与模式一起使用Alter端点声明。为了正确支持上述类型,还需要为该类型中的每个属性指定一个predicate,例如:

name: string @index(term) .
dob: datetime .
home_address: string .
year: int .
friends: [uid] .

反向谓词也可以包含在类型定义中。例如,如果有一个具有反向边的谓词子谓词(谓词名称周围的括号需要正确理解特殊字符~),那么上面的类型可以扩展为包含student的父谓词。

children: [uid] @reverse .

type Student {
  name
  dob
  home_address
  year
  friends
  <~children>
}

边可以用于多种类型:例如,名字可以同时用于人和宠物。但是,有时需要为每种类型使用不同的谓词来表示类似的概念。例如,如果学生名和图书名需要不同的索引,那么谓词必须不同。

type Student {
  student_name
}

type Textbook {
  textbook_name
}

student_name: string @index(exact) .
textbook_name: string @lang @index(fulltext) .

更改已经存在的类型的模式,将覆盖现有的定义。

设置一个节点的类型

标量节点不能具有类型,因为它们只有一个属性,且其类型是节点的类型。UID节点可以有一个类型。通过设置dgraph的值来设置类型。该节点的类型predicate。一个节点可以有多个类型。下面是一个如何设置节点类型的例子:

{
  set {
    _:a <name> "Garfield" .
    _:a <dgraph.type> "Pet" .
    _:a <dgraph.type> "Animal" .
  }
}

dgraph.type是保留谓词,不能删除或修改。

在查询过程中使用类型系统

可以在查询的顶层函数中使用类型系统:

{
  q(func: type(Animal)) {
    uid
    name
  }
}

这个查询将只返回类型设置为Animal的节点。

类型还可以用于过滤查询内部的结果。例如:

{
  q(func: has(parent)) {
    uid
    parent @filter(type(Person)) {
      uid
      name
    }
  }
}

该查询将返回具有父谓词且只具有Person类型的父谓词的节点。

您还可以查询dgraph.type获取实体类型。例如:

{
   people(func: eq(dgraph.type, "Person")) {
      name
      dgraph.type
   }
}

或者

{
   people(func: type(Person)) {
      name
      dgraph.type
   }
}

删除一个类型

可以使用Alter端点删除类型定义。所有需要做的就是发送一个带有字段DropOp(或drop_op,取决于客户端)的操作对象到enumTYPE,并将字段DropValue(或drop_value)发送到要删除的类型。

下面是一个使用Go客户端删除Person类型的例子:

err := c.Alter(context.Background(), &api.Operation {
    DropOp: api.Operation_TYPE,
    DropValue: "Person"})

展开查询与类型系统

使用expand的查询(例如:expand(_all_))要求要扩展的节点具有类型。

facet和Edge属性

Dgraph支持facet————edge上的键值对————是RDF三元组的扩展。也就是说,facet向边添加属性,而不是向节点添加属性。例如,两个节点之间的朋友边可能具有亲密友谊的bool属性。facet也可以用作边(edge)的权重。

虽然你可能会发现自己很多时候倾向于一些方面,但它们不应该被滥用。例如,给friend这条边(edge)添加一个date_of_birth属性可能不是一个正经的建模。对于friend这条边,你更应该添加比如start_of_friendship这个属性。然而,facetDgraph中并不像谓词(predicate)那样是一等公民。

Facet键是字符串,值可以是stringboolintfloatdateTime。对于intfloat,只接受32位有符号整数和64位浮点数。

下面这些对于facet的变更将会贯穿整章。该变更(mutation)为一些人添加了数据,例如,在mobilecar中记录了since facet,以记录Alice购买汽车并开始使用手机号码的时间。

首先,我们添加一些Schema

curl localhost:8080/alter -XPOST -d $'
    name: string @index(exact, term) .
    rated: [uid] @reverse @count .
' | python -m json.tool | less
curl -H "Content-Type: application/rdf" localhost:8080/mutate?commitNow=true -XPOST -d $'
{
  set {

    # -- Facets on scalar predicates
    _:alice <name> "Alice" .
    _:alice <dgraph.type> "Person" .
    _:alice <mobile> "040123456" (since=2006-01-02T15:04:05) .
    _:alice <car> "MA0123" (since=2006-02-02T13:01:09, first=true) .

    _:bob <name> "Bob" .
    _:bob <dgraph.type> "Person" .
    _:bob <car> "MA0134" (since=2006-02-02T13:01:09) .

    _:charlie <name> "Charlie" .
    _:charlie <dgraph.type> "Person" .
    _:dave <name> "Dave" .
    _:dave <dgraph.type> "Person" .


    # -- Facets on UID predicates
    _:alice <friend> _:bob (close=true, relative=false) .
    _:alice <friend> _:charlie (close=false, relative=true) .
    _:alice <friend> _:dave (close=true, relative=true) .


    # -- Facets for variable propagation
    _:movie1 <name> "Movie 1" .
    _:movie1 <dgraph.type> "Movie" .
    _:movie2 <name> "Movie 2" .
    _:movie2 <dgraph.type> "Movie" .
    _:movie3 <name> "Movie 3" .
    _:movie3 <dgraph.type> "Movie" .

    _:alice <rated> _:movie1 (rating=3) .
    _:alice <rated> _:movie2 (rating=2) .
    _:alice <rated> _:movie3 (rating=5) .

    _:bob <rated> _:movie1 (rating=5) .
    _:bob <rated> _:movie2 (rating=5) .
    _:bob <rated> _:movie3 (rating=5) .

    _:charlie <rated> _:movie1 (rating=2) .
    _:charlie <rated> _:movie2 (rating=5) .
    _:charlie <rated> _:movie3 (rating=1) .
  }
}' | python -m json.tool | less

标量(scalar)谓词(predicate)上的Facets

查询AlicenamemobileCar与不查询facet的结果相同:

{
  data(func: eq(name, "Alice")) {
     name
     mobile
     car
  }
}
{
  "data": {
    "data": [
      {
        "name": "Alice",
        "mobile": "040123456",
        "car": "MA0123"
      }
    ]
  }
}

语法@facet (facet-name)用于查询facet数据。对于Alice, mobilecarsince facet被查询如下:

{
  data(func: eq(name, "Alice")) {
     name
     mobile @facets(since)
     car @facets(since)
  }
}
{
  "data": {
    "data": [
      {
        "name": "Alice",
        "mobile|since": "2006-01-02T15:04:05Z",
        "mobile": "040123456",
        "car|since": "2006-02-02T13:01:09Z",
        "car": "MA0123"
      }
    ]
  }
}

facet在与对应的边(edge)相同的级别返回,并具有像edge|facet这样的键。

边(edge)上的所有facet都使用@facet查询:

{
  data(func: eq(name, "Alice")) {
     name
     mobile @facets
     car @facets
  }
}
{
  "data": {
    "data": [
      {
        "name": "Alice",
        "mobile|since": "2006-01-02T15:04:05Z",
        "mobile": "040123456",
        "car|first": true,
        "car|since": "2006-02-02T13:01:09Z",
        "car": "MA0123"
      }
    ]
  }
}

Facets 国际化

facet键和值在发生变化时可以直接使用特定于语言的字符。但是在查询时,facet键需要括在尖括号<>中。这类似于谓词。有关更多信息,请参阅谓词(predicate)i18n

注意,在查询时,Dgraph支持面向键的国际化资源标识符(IRIs)。

{
  set {
    _:person1 <name> "Daniel" (वंश="स्पेनी", ancestry="Español") .
    _:person1 <dgraph.type> "Person" .
    _:person2 <name> "Raj" (वंश="हिंदी", ancestry="हिंदी") .
    _:person2 <dgraph.type> "Person" .
    _:person3 <name> "Zhang Wei" (वंश="चीनी", ancestry="中文") .
    _:person3 <dgraph.type> "Person" .
  }
}

查询,注意<>

{
  q(func: has(name)) {
    name @facets(<वंश>)
  }
}

在Facets上使用别名

可以在请求特定谓词(predicate)时指定别名。语法类似于为其他谓词请求别名的方式。orderascorderdesc不允许作为别名,因为它们有特殊的含义。除此之外,其他任何东西都可以设置为别名。

这里我们分别为sinceclose facet设置car_sinceclose_friend别名:

{
   data(func: eq(name, "Alice")) {
     name
     mobile
     car @facets(car_since: since)
     friend @facets(close_friend: close) {
       name
     }
   }
}
{
  "data": {
    "data": [
      {
        "name": "Alice",
        "mobile": "040123456",
        "car_since": "2006-02-02T13:01:09Z",
        "car": "MA0123",
        "friend": [
          {
            "name": "Bob",
            "close_friend": true
          },
          {
            "name": "Charlie",
            "close_friend": false
          },
          {
            "name": "Dave",
            "close_friend": true
          }
        ]
      }
    ]
  }
}

UID谓词(predicate)上的Facets

UID边(edge)上的facet与值边(value edge)上的facet的工作方式类似。

例如,friend这条边拥有一个closeedgeAliceBobfriend边(edge)的close``facet被设置为true,AliceCharlie之间被设置为false

现在查询Alice的朋友:

{
  data(func: eq(name, "Alice")) {
    name
    friend {
      name
    }
  }
}
{
  "data": {
    "data": [
      {
        "name": "Alice",
        "friend": [
          {
            "name": "Bob"
          },
          {
            "name": "Charlie"
          },
          {
            "name": "Dave"
          }
        ]
      }
    ]
  }
}

查询Alice的朋友这条边(edge)上的close``facetfalse的朋友:

{
   data(func: eq(name, "Alice")) {
     name
     friend @facets(close) {
       name
     }
   }
}
{
  "data": {
    "data": [
      {
        "name": "Alice",
        "friend": [
          {
            "name": "Bob",
            "friend|close": true
          },
          {
            "name": "Charlie",
            "friend|close": false
          },
          {
            "name": "Dave",
            "friend|close": true
          }
        ]
      }
    ]
  }
}

对于像friend这样的UID边(edge),facet会转到相应子元素下。在上面的示例中,您可以看到AliceBob之间的边(edge)上的close``facet

{
  data(func: eq(name, "Alice")) {
    name
    friend @facets {
      name
      car @facets
    }
  }
}
{
  "data": {
    "data": [
      {
        "name": "Alice",
        "friend": [
          {
            "name": "Bob",
            "car|since": "2006-02-02T13:01:09Z",
            "car": "MA0134",
            "friend|close": true,
            "friend|relative": false
          },
          {
            "name": "Charlie",
            "friend|close": false,
            "friend|relative": true
          },
          {
            "name": "Dave",
            "friend|close": true,
            "friend|relative": true
          }
        ]
      }
    ]
  }
}

Bob有一辆车,它有一个facet since,在结果中,它是key car|sinceBob的同一个对象的一部分。此外,BobAlice之间的密切关系也是Bob的输出对象的一部分。Charlie没有Car边(edge),因此只有UID facets

在facets上使用过滤

Dgraph支持基于facet的边(edge)过滤。过滤的工作原理类似于它在没有facet的边(edge)上的工作原理,并且具有相同的可用函数。

找到爱丽丝的好朋友:

{
  data(func: eq(name, "Alice")) {
    friend @facets(eq(close, true)) {
      name
    }
  }
}
{
  "data": {
    "data": [
      {
        "friend": [
          {
            "name": "Bob"
          },
          {
            "name": "Dave"
          }
        ]
      }
    ]
  }
}

要返回facet和过滤器,请向查询添加另一个@facet (<facetname>)

{
  data(func: eq(name, "Alice")) {
    friend @facets(eq(close, true)) @facets(relative) { # filter close friends and give relative status
      name
    }
  }
}
{
  "data": {
    "data": [
      {
        "friend": [
          {
            "name": "Bob",
            "friend|relative": false
          },
          {
            "name": "Dave",
            "friend|relative": true
          }
        ]
      }
    ]
  }
}

Facet查询能够和AND, OR, NOT 组合使用:

{
  data(func: eq(name, "Alice")) {
    friend @facets(eq(close, true) AND eq(relative, true)) @facets(relative) { # filter close friends in my relation
      name
    }
  }
}
{
  "data": {
    "data": [
      {
        "friend": [
          {
            "name": "Dave",
            "friend|relative": true
          }
        ]
      }
    ]
  }
}

使用Facet排序

可以对uid边(edge)上的facet进行排序。在这里,我们将爱丽丝、鲍勃和查理对电影的评级进行分类,这是一个facet

{
  me(func: anyofterms(name, "Alice Bob Charlie")) {
    name
    rated @facets(orderdesc: rating) {
      name
    }
  }
}
{
  "data": {
    "me": [
      {
        "name": "Bob",
        "rated": [
          {
            "name": "Movie 1",
            "rated|rating": 5
          },
          {
            "name": "Movie 2",
            "rated|rating": 5
          },
          {
            "name": "Movie 3",
            "rated|rating": 5
          }
        ]
      },
      {
        "name": "Alice",
        "rated": [
          {
            "name": "Movie 3",
            "rated|rating": 5
          },
          {
            "name": "Movie 1",
            "rated|rating": 3
          },
          {
            "name": "Movie 2",
            "rated|rating": 2
          }
        ]
      },
      {
        "name": "Charlie",
        "rated": [
          {
            "name": "Movie 2",
            "rated|rating": 5
          },
          {
            "name": "Movie 1",
            "rated|rating": 2
          },
          {
            "name": "Movie 3",
            "rated|rating": 1
          }
        ]
      }
    ]
  }
}

Facet值赋值给变量

UID边(edge)上的facet可以存储在值变量(variables)中。这个变量是从边(edge)目标到facet的映射。

找到Alice被标记为closerelative的朋友:

{
  var(func: eq(name, "Alice")) {
    friend @facets(a as close, b as relative)
  }

  friend(func: uid(a)) {
    name
    val(a)
  }

  relative(func: uid(b)) {
    name
    val(b)
  }
}
{
  "data": {
    "friend": [
      {
        "name": "Bob",
        "val(a)": true
      },
      {
        "name": "Charlie",
        "val(a)": false
      },
      {
        "name": "Dave",
        "val(a)": true
      }
    ],
    "relative": [
      {
        "name": "Bob",
        "val(b)": false
      },
      {
        "name": "Charlie",
        "val(b)": true
      },
      {
        "name": "Dave",
        "val(b)": true
      }
    ]
  }
}

Facet和值传播

可以将intfloatFacet值赋给变量,从而使这些值(variables)传播。

爱丽丝、鲍勃和查理都给每部电影打分。facet评级上的值变量将电影映射到评级。通过多条路径到达电影的查询将对每条路径的评级进行求和。下面总结了爱丽丝、鲍勃和查理对这三部电影的评分:

{
  var(func: anyofterms(name, "Alice Bob Charlie")) {
    num_raters as math(1)
    rated @facets(r as rating) {
      total_rating as math(r) # sum of the 3 ratings
      average_rating as math(total_rating / num_raters)
    }
  }
  data(func: uid(total_rating)) {
    name
    val(total_rating)
    val(average_rating)
  }

}
{
  "data": {
    "data": [
      {
        "name": "Movie 1",
        "val(total_rating)": 10,
        "val(average_rating)": 3
      },
      {
        "name": "Movie 2",
        "val(total_rating)": 12,
        "val(average_rating)": 4
      },
      {
        "name": "Movie 3",
        "val(total_rating)": 11,
        "val(average_rating)": 3
      }
    ]
  }
}

Facet 和聚合(aggregation)

可以聚合分配给值变量的Facet值。

{
  data(func: eq(name, "Alice")) {
    name
    rated @facets(r as rating) {
      name
    }
    avg(val(r))
  }
}
{
  "data": {
    "data": [
      {
        "name": "Alice",
        "rated": [
          {
            "name": "Movie 1",
            "rated|rating": 3
          },
          {
            "name": "Movie 2",
            "rated|rating": 2
          },
          {
            "name": "Movie 3",
            "rated|rating": 5
          }
        ],
        "avg(val(r))": 3.333333
      }
    ]
  }
}

但是请注意,r是一个从电影到查询中到达电影的边缘上的评分总和的映射。因此,下面的代码不能正确地分别计算AliceBob的平均评级————它计算的是AliceBob评级平均值的2倍。

{
  data(func: anyofterms(name, "Alice Bob")) {
    name
    rated @facets(r as rating) {
      name
    }
    avg(val(r))
  }
}
{
  "data": {
    "data": [
      {
        "name": "Bob",
        "rated": [
          {
            "name": "Movie 1",
            "rated|rating": 5
          },
          {
            "name": "Movie 2",
            "rated|rating": 5
          },
          {
            "name": "Movie 3",
            "rated|rating": 5
          }
        ],
        "avg(val(r))": 8.333333
      },
      {
        "name": "Alice",
        "rated": [
          {
            "name": "Movie 1",
            "rated|rating": 3
          },
          {
            "name": "Movie 2",
            "rated|rating": 2
          },
          {
            "name": "Movie 3",
            "rated|rating": 5
          }
        ],
        "avg(val(r))": 8.333333
      }
    ]
  }
}

计算用户的平均评级需要一个将用户映射到其评级总和的变量:

{
  var(func: has(rated)) {
    num_rated as math(1)
    rated @facets(r as rating) {
      avg_rating as math(r / num_rated)
    }
  }

  data(func: uid(avg_rating)) {
    name
    val(avg_rating)
  }
}

{
  "data": {
    "data": [
      {
        "name": "Movie 1",
        "val(avg_rating)": 3
      },
      {
        "name": "Movie 2",
        "val(avg_rating)": 4
      },
      {
        "name": "Movie 3",
        "val(avg_rating)": 3
      }
    ]
  }
}

最短路径查询

通过查询块名称的关键字最短,可以找到源(from)节点和目的(to)节点之间的最短路径。它要求必须考虑遍历的源节点UID、目标节点UID和谓词(predicate)(至少一个)。最短查询块在查询响应中返回_path_下的最短路径。该路径也可以存储在其他查询块中使用的变量中。

K-最短路径查询

默认情况下,返回的是最短路径。使用numpaths: kk > 1,返回k最短路径。从k最短路径查询的结果中删除循环路径。对于depth: n,返回深度为n的路径。

请注意:

  • 如果在最短的块中没有指定谓词,则不会获取路径,因为没有遍历边界。
  • 如果查询需要很长时间,可以设置一个gRPC截止日期,在一段时间后停止查询。

例如:

curl localhost:8080/alter -XPOST -d $'
    name: string @index(exact) .
' | python -m json.tool | less
{
  set {
    _:a <friend> _:b (weight=0.1) .
    _:b <friend> _:c (weight=0.2) .
    _:c <friend> _:d (weight=0.3) .
    _:a <friend> _:d (weight=1) .
    _:a <name> "Alice" .
    _:a <dgraph.type> "Person" .
    _:b <name> "Bob" .
    _:b <dgraph.type> "Person" .
    _:c <name> "Tom" .
    _:c <dgraph.type> "Person" .
    _:d <name> "Mallory" .
    _:d <dgraph.type> "Person" .
  }
}

AliceMallory之间的最短路径(假设uid分别为0x20x5)可以通过下面的查询找到:

{
 path as shortest(from: 0x2, to: 0x5) {
  friend
 }
 path(func: uid(path)) {
   name
 }
}

注意,不考虑facet,每条边的权值为1

上面的DQL将返回:

{
  "data": {
    "path": [
      {
        "name": "Alice"
      },
      {
        "name": "Mallory"
      }
    ],
    "_path_": [
      {
        "uid": "0x2",
        "friend": [
          {
            "uid": "0x5"
          }
        ]
      }
    ]
  }
}

通过指定numpaths,可以返回更多路径。设置numpaths: 2返回最短的两条路径:

{

 A as var(func: eq(name, "Alice"))
 M as var(func: eq(name, "Mallory"))

 path as shortest(from: uid(A), to: uid(M), numpaths: 2) {
  friend
 }
 path(func: uid(path)) {
   name
 }
}

注意,在上面的查询中,我们使用var块和UID()函数来查询人,而不是使用UID字面量。还可以将它与GraphQL变量结合使用。

边的权重

Dgraph中的最短路径实现依赖于facet来提供权重。使用边(edge)上的facet可以让你定义如下的边(edge)的权重:

注意:在最短的查询块中,每个谓词(predicate)只允许有一个facet。

{
 path as shortest(from: 0x2, to: 0x5) {
  friend @facets(weight)
 }

 path(func: uid(path)) {
  name
 }
}
{
  "data": {
    "path": [
      {
        "name": "Alice"
      },
      {
        "name": "Bob"
      },
      {
        "name": "Tom"
      },
      {
        "name": "Mallory"
      }
    ],
    "_path_": [
      {
        "uid": "0x2",
        "friend": [
          {
            "uid": "0x3",
            "friend|weight": 0.1,
            "friend": [
              {
                "uid": "0x4",
                "friend|weight": 0.2,
                "friend": [
                  {
                    "uid": "0x5",
                    "friend|weight": 0.3
                  }
                ]
              }
            ]
          }
        ]
      }
    ]
  }
}

遍历的例子

下面是一个图遍历的例子,它允许您找到使用CarBus的朋友之间的最短路径:

每个关系的CarBus移动被建模为facet,并在最短的查询中指定

{
  set {
    _:a <friend> _:b (weightCar=10, weightBus=1 ) .
    _:b <friend> _:c (weightCar=20, weightBus=1) .
    _:c <friend> _:d (weightCar=11, weightBus=1.1) .
    _:a <friend> _:d (weightCar=70, weightBus=2) .
    _:a <name> "Alice" .
    _:a <dgraph.type> "Person" .
    _:b <name> "Bob" .
    _:b <dgraph.type> "Person" .
    _:c <name> "Tom" .
    _:c <dgraph.type> "Person" .
    _:d <name> "Mallory" .
    _:d <dgraph.type> "Person" .
  }
}

依赖CarBus查询最短路径:

{

 A as var(func: eq(name, "Alice"))
 M as var(func: eq(name, "Mallory"))

 sPathBus as shortest(from: uid(A), to: uid(M)) {  
  friend
  @facets(weightBus)
 }

 sPathCar as shortest(from: uid(A), to: uid(M)) {  
  friend
  @facets(weightCar)
 }  
  
 pathBus(func: uid(sPathBus)) {
   name   
 }
  
 pathCar(func: uid(sPathCar)) {
   name   
 }
}

响应包含以下符合指定权重的路径:

 "pathBus": [
      {
        "name": "Alice"
      },
      {
        "name": "Mallory"
      }
    ],
    "pathCar": [
      {
        "name": "Alice"
      },
      {
        "name": "Bob"
      },
      {
        "name": "Tom"
      },
      {
        "name": "Mallory"
      }
    ]

约束

可以对中间节点应用如下约束:

{
  path as shortest(from: 0x2, to: 0x5) {
    friend @filter(not eq(name, "Bob")) @facets(weight)
    relative @facets(liking)
  }

  relationship(func: uid(path)) {
    name
  }
}

k最短路径算法(在numpaths > 1时使用)也接受参数minweightmaxweight,它们的值为一个浮点数。当传递它们时,只有权重范围[minweight, maxweight]内的路径将被视为有效路径。例如,这可以用于查询在2到4个节点之间的最短路径:

{
 path as shortest(from: 0x2, to: 0x5, numpaths: 2, minweight: 2, maxweight: 4) {
  friend
 }
 path(func: uid(path)) {
   name
 }
}

笔记

对于最短路径查询,需要记住以下几点:

  • 权值必须是非负的。Dijkstra算法用于计算最短路径。
  • 在最短的查询块中,每个谓词只允许有一个facet。
  • 每个查询只允许一个最短路径块。结果中只返回一个_path_。对于numpaths > 1的查询,_path_包含所有路径。
  • 循环路径不包含在k最短路径查询结果中。
  • 对于k条最短路径(当numpaths >为1时),最短路径查询变量的结果将只返回一条路径,这将是k条路径中的最短路径。所有k条路径都以_path_形式返回。

递归查询

递归查询允许您遍历一组谓词(predicate)(使用过滤器、facet等),直到我们到达所有叶子节点,或者到达由depth参数指定的最大深度。

要从一个拥有超过30000部电影的类型中获得10部电影,然后为这些电影找两个演员,我们会做如下事情:

{
	me(func: gt(count(~genre), 30000), first: 1) @recurse(depth: 5, loop: true) {
		name@en
		~genre (first:10) @filter(gt(count(starring), 2))
		starring (first: 2)
		performance.actor
	}
}
{
  "data": {
    "me": [
      {
        "name@en": "Documentary film",
        "~genre": [
          {
            "name@en": "Casting Crowns: The Altar and the Door Live",
            "starring": [
              {
                "performance.actor": [
                  {
                    "name@en": "Megan Garrett"
                  }
                ]
              },
              {
                "performance.actor": [
                  {
                    "name@en": "Chris Huffman"
                  }
                ]
              }
            ]
          },
          {
            "name@en": "Heavy Water: A Film for Chernobyl",
            "starring": [
              {
                "performance.actor": [
                  {
                    "name@en": "David Bickerstaff"
                  }
                ]
              },
              {
                "performance.actor": [
                  {
                    "name@en": "Phil Grabsky"
                  }
                ]
              }
            ]
          }
          ...
        ]
      }
    ]
  }
}

使用递归查询时需要记住的几点是:

  • 您只能在root之后指定一个级别的谓词。它们将被递归地遍历。标量和实体节点的处理是相似的。
  • 建议每个查询只使用一个递归块。
  • 要小心,因为结果的大小可能会很快爆炸,如果结果集变得太大,就会返回一个错误。在这种情况下,使用更多的过滤器,使用分页限制结果,或在根处提供深度参数,如上面的示例所示。
  • loop参数可以设置为false,在这种情况下,导致循环的路径将在遍历时被忽略。
  • 如果未指定,则loop参数的值默认为false
  • 如果loop参数的值为false并且没有指定depth, depth将默认为math。maxxuint64,这意味着可能遍历整个图,直到到达所有叶节点。

Fragement 片段

fragment关键字允许您根据GraphQL规范的fragments部分定义可以在查询中引用的新片段。片段允许重用常见的重复字段选择,从而减少DQL文档中的重复文本。片段可以嵌套在片段中,但在这种情况下不允许循环。例如:

curl -H "Content-Type: application/dql" localhost:8080/query -XPOST -d $'
query {
  debug(func: uid(1)) {
    name@en
    ...TestFrag
  }
}
fragment TestFrag {
  initial_release_date
  ...TestFragB
}
fragment TestFragB {
  country
}' | python -m json.tool | less

注意GraphQL+-已重命名为Dgraph查询语言(DQL)。虽然application/dql是Content-Type头的首选值,但我们将继续支持Content-Type: application/graphql+-,以避免进行破坏性的更改。

GraphQL 变量

解析规则(使用默认值):

  • query title($name: string = "Bauman") { ... }
  • query title($age: int = "95") { ... }
  • query title($uids: string = "0x1") { ... }
  • query title($uids: string = "[0x1, 0x2, 0x3]") { ... }

可以在查询中定义和使用变量,这有助于查询重用,并通过传递单独的变量映射,避免运行时在客户端中构建代价高昂的字符串。变量以$符号开头。对于带有GraphQL变量的HTTP请求,我们必须使用Content-Type: application/json头,并通过包含查询和变量的json对象传递数据:

curl -H "Content-Type: application/json" localhost:8080/query -XPOST -d $'{
  "query": "query test($a: string) { test(func: eq(name, $a)) { \n uid \n name \n } }",
  "variables": { "$a": "Alice" }
}' | python -m json.tool | less
query test($a: int, $b: int, $name: string) {
  me(func: allofterms(name@en, $name)) {
    name@en
    director.film (first: $a, offset: $b) {
      name @en
      genre(first: $a) {
        name@en
      }
    }
  }
}
{
  "data": {
    "me": [
      {
        "name@en": "Steven Spielberg",
        "director.film": [
          {
            "name@en": "Close Encounters of the Third Kind",
            "genre": [
              {
                "name@en": "Science Fiction"
              },
              {
                "name@en": "Adventure Film"
              },
              {
                "name@en": "Drama"
              }
            ]
          }
          ...
        ]
      },
      {
        "name@en": "Steven Spielberg And The Return To Film School"
      },
      {
        "name@en": "Directors: Steven Spielberg"
      },
      {
        "name@en": "Steven Spielberg"
      }
      ...
    ]
  }
}
  • 变量可以有默认值。在下面的例子中,$a的默认值为2。因为变量映射中没有提供$a的值,所以$a采用默认值。
  • 类型以!不能有默认值,但必须有一个值作为变量映射的一部分。
  • 变量的值必须对给定的类型具有可解析性,如果不能,则抛出错误。
  • 目前支持的变量类型有:int、float、bool和string。
  • 任何正在使用的变量都必须在开头的命名查询子句中声明。
query test($a: int = 2, $b: int!, $name: string) {
  me(func: allofterms(name@en, $name)) {
    director.film (first: $a, offset: $b) {
      genre(first: $a) {
        name@en
      }
    }
  }
}
{
  "data": {
    "me": [
      {
        "director.film": [
          {
            "genre": [
              {
                "name@en": "Science Fiction"
              },
              {
                "name@en": "Adventure Film"
              }
            ]
          },
          {
            "genre": [
              {
                "name@en": "Horror"
              },
              {
                "name@en": "Science Fiction"
              }
            ]
          }
        ]
      }
    ]
  }
}

你也可以使用数组与GraphQL变量:

query test($a: int = 2, $b: int!, $aName: string, $bName: string) {
  me(func: eq(name@en, [$aName, $bName])) {
    director.film (first: $a, offset: $b) {
      genre(first: $a) {
        name@en
      }
    }
  }
}
{
  "data": {
    "me": [
      {
        "director.film": [
          {
            "genre": [
              {
                "name@en": "Indie film"
              },
              {
                "name@en": "Thriller"
              }
            ]
          },
          {
            "genre": [
              {
                "name@en": "Farce"
              },
              {
                "name@en": "Comedy"
              }
            ]
          }
        ]
      },
      {
        "director.film": [
          {
            "genre": [
              {
                "name@en": "Science Fiction"
              },
              {
                "name@en": "Adventure Film"
              }
            ]
          },
          {
            "genre": [
              {
                "name@en": "Horror"
              },
              {
                "name@en": "Science Fiction"
              }
            ]
          }
        ]
      }
    ]
  }
}

我们还支持facet变量替换:

query test($name: string = "Alice", $IsClose: string = "true") {
  data(func: eq(name, $name)) {
    friend @facets(eq(close, $IsClose)) {
      name
    }
      colleague : friend @facets(eq(close, false)) {
      name
    }
  }
}
{
  "data": {
    "data": [
      {
        "friend": [
          {
            "name": "Bob"
          },
          {
            "name": "Dave"
          }
        ],
        "colleague": [
          {
            "name": "Charlie"
          }
        ]
      }
    ]
  }
}

使用自定义 tokenizer 进行索引

Dgraph自带一个内置索引的大工具箱,但有时对于小众用例来说,它们总是不够用。

为了填补空白,Dgraph允许你通过插件系统实现定制的标记器。

Caveats

插件系统使用Gopkg/plugin。这给插件的使用带来了一些限制。

  • 插件必须用Go编写。
  • 从1.9开始,pkg/plugin只能在Linux上工作。因此,插件只能在部署在Linux环境中的Dgraph实例上工作。
  • 用于编译插件的Go版本应该与用于编译Dgraph本身的Go版本相同。Dgraph总是使用最新版本的Go(你也应该如此!)

实现一个插件

三元组 (triples)

一个变更通过set关键字添加三元组triples

{
  set {
    # 在这里添加三元组
  }
}

这里的三元组是W3C标准RDF N-Quad格式的三元组。

每个三元组都有这样的格式:

<subject> <predicate> <object> .

上面的格式表明由subject标识的每一个节点(node)都通过一条有向边(predicate)连接到一个对象object实体。三元组的subject总是graph图数据库中的节点nodeobject可以是一个值,也可以是一个节点(node)(字面量)。

比如,下面这些三元组:

<0x01> <name> "Alice" .
<0x01> <dgraph.type> "Person" .

表示图上ID0x01的节点(node)有一个值为Alice的属性name

然后下面这个三元组:

<0x01> <friend> <0x02> .

表示ID0x01的节点node通过friend这条边(edge)连接到ID0x02的节点(node)

Dgraph为变更中的每个空白节点创建一个惟一的64位标识符————节点的UID。变更可以包含一个空白节点作为subject或对象实体(object)的标识符(即是UID),或者一个来自先前变更的已知UID

三元组 (triples)

一个变更通过set关键字添加三元组triples

{
  set {
    # 在这里添加三元组
  }
}

这里的三元组是W3C标准RDF N-Quad格式的三元组。

每个三元组都有这样的格式:

<subject> <predicate> <object> .

上面的格式表明由subject标识的每一个节点(node)都通过一条有向边(predicate)连接到一个对象object实体。三元组的subject总是graph图数据库中的节点nodeobject可以是一个值,也可以是一个节点(node)(字面量)。

比如,下面这些三元组:

<0x01> <name> "Alice" .
<0x01> <dgraph.type> "Person" .

表示图上ID0x01的节点(node)有一个值为Alice的属性name

然后下面这个三元组:

<0x01> <friend> <0x02> .

表示ID0x01的节点node通过friend这条边(edge)连接到ID0x02的节点(node)

Dgraph为变更中的每个空白节点创建一个惟一的64位标识符————节点的UID。变更可以包含一个空白节点作为subject或对象实体(object)的标识符(即是UID),或者一个来自先前变更的已知UID

空白节点和UID

变更中的空白节点(node),写作_:identifier,用于标识变更中的节点。Dgraph为每一个空白节点创建一个UID以标识这个空白节点,并且在最后Dgraph会返回这些创建的UID集作为返回结果,例如,对于以下变更:

{
 set {
    _:class <student> _:x .
    _:class <student> _:y .
    _:class <name> "awesome class" .
    _:class <dgraph.type> "Class" .
    _:x <name> "Alice" .
    _:x <dgraph.type> "Person" .
    _:x <dgraph.type> "Student" .
    _:x <planet> "Mars" .
    _:x <friend> _:y .
    _:y <name> "Bob" .
    _:y <dgraph.type> "Person" .
    _:y <dgraph.type> "Student" .
 }
}

返回结果(实际的uid在每次执行该变更时都是不同的):

{
  "data": {
    "code": "Success",
    "message": "Done",
    "uids": {
      "class": "0x2712",
      "x": "0x2713",
      "y": "0x2714"
    }
  }
}

至此,数据库就像存储了三元组一样被更新了:

<0x6bc818dc89e78754> <student> <0xc3bcc578868b719d> .
<0x6bc818dc89e78754> <student> <0xb294fb8464357b0a> .
<0x6bc818dc89e78754> <name> "awesome class" .
<0x6bc818dc89e78754> <dgraph.type> "Class" .
<0xc3bcc578868b719d> <name> "Alice" .
<0xc3bcc578868b719d> <dgraph.type> "Person" .
<0xc3bcc578868b719d> <dgraph.type> "Student" .
<0xc3bcc578868b719d> <planet> "Mars" .
<0xc3bcc578868b719d> <friend> <0xb294fb8464357b0a> .
<0xb294fb8464357b0a> <name> "Bob" .
<0xb294fb8464357b0a> <dgraph.type> "Person" .
<0xb294fb8464357b0a> <dgraph.type> "Student" .

空节点标签_:class_:x_:y不再标识变更成功后的节点,因此可以安全地重用它们来标识以后发生变更时的新节点。

以后的变化可以更新现有uid的数据。例如,下面将向uid0x6bc818dc89e78754这个班级class中添加一个新学生:

{
 set {
    <0x6bc818dc89e78754> <student> _:x .
    _:x <name> "Chris" .
    _:x <dgraph.type> "Person" .
    _:x <dgraph.type> "Student" .
 }
}

查询也可以直接使用UID:

{
 class(func: uid(0x6bc818dc89e78754)) {
  name
  student {
   name
   planet
   friend {
    name
   }
  }
 }
}

使用外部ID

Dgraph的输入语言RDF也支持<a_fixed_identifier> <predicate> literal/node的三元组及其变体,其中标签a_fixed_identifier`是一个节点的唯一标识符。例如,混合使用schema.org标识符、电影数据库标识符和空节点:

_:userA <http://schema.org/type> <http://schema.org/Person> .
_:userA <dgraph.type> "Person" .
_:userA <http://schema.org/name> "FirstName LastName" .
<https://www.themoviedb.org/person/32-robin-wright> <http://schema.org/type> <http://schema.org/Person> .
<https://www.themoviedb.org/person/32-robin-wright> <http://schema.org/name> "Robin Wright" .

Dgraph原生不支持节点标识符这样的外部id,但外部id可以被存储为带有xid边的节点的属性。例如,在上面的例子中,谓词名称在Dgraph中是有效的,但是用<http: schema.org="" person="">标识的节点可以用UID(比如0x123)和一条边在Dgraph中标识:

<0x123> <xid> "http://schema.org/Person" .
<0x123> <dgraph.type> "ExternalType" .

Robin Wright可能获得的UID0x321和三元组:

<0x321> <xid> "https://www.themoviedb.org/person/32-robin-wright" .
<0x321> <http://schema.org/type> <0x123> .
<0x321> <http://schema.org/name> "Robin Wright" .
<0x321> <dgraph.type> "Person" .

相应的Schema应该是下面这样子的:

xid: string @index(exact) .
<http://schema.org/type>: [uid] @reverse .

查询所有人的例子:

{
  var(func: eq(xid, "http://schema.org/Person")) {
    allPeople as <~http://schema.org/type>
  }

  q(func: uid(allPeople)) {
    <http://schema.org/name>
  }
}

通过外部ID查询Robin Wright的例子:

{
  robin(func: eq(xid, "https://www.themoviedb.org/person/32-robin-wright")) {
    expand(_all_) { expand(_all_) }
  }
}

注意:xid边不会在变更中自动添加。一般来说,用户应该自己管理xid,并在必要时添加节点和xid边。Dgraph将所有此类xid的唯一性检查留给外部程序。

外部ID和插入块 (External IDs and Upsert Block)

upsert块使得管理外部id很容易。

首先,设置如下Schema

xid: string @index(exact) .
<http://schema.org/name>: string @index(exact) .
<http://schema.org/type>: [uid] @reverse .

然后设置类型:

{
  set {
    _:blank <xid> "http://schema.org/Person" .
    _:blank <dgraph.type> "ExternalType" .
  }
}

现在您可以创建一个新的person并使用upsert块添加其类型:

upsert {
    query {
      var(func: eq(xid, "http://schema.org/Person")) {
        Type as uid
      }
      var(func: eq(<http://schema.org/name>, "Robin Wright")) {
        Person as uid
      }
    }
    mutation {
        set {
          uid(Person) <xid> "https://www.themoviedb.org/person/32-robin-wright" .
          uid(Person) <http://schema.org/type> uid(Type) .
          uid(Person) <http://schema.org/name> "Robin Wright" .
          uid(Person) <dgraph.type> "Person" .
        }
    }
  }

还可以删除person并分离Typeperson节点之间的关系。这和上面是一样的,但是你使用了关键字delete而不是sethttp://schema.org/Person将保留,但Robin Wright将被删除:

upsert {
    query {
      var(func: eq(xid, "http://schema.org/Person")) {
        Type as uid
      }
      var(func: eq(<http://schema.org/name>, "Robin Wright")) {
        Person as uid
      }
    }
    mutation {
        delete {
          uid(Person) <xid> "https://www.themoviedb.org/person/32-robin-wright" .
          uid(Person) <http://schema.org/type> uid(Type) .
          uid(Person) <http://schema.org/name> "Robin Wright" .
          uid(Person) <dgraph.type> "Person" .
        }
    }
  }

再次查询一个用户:

{
  q(func: eq(<http://schema.org/name>, "Robin Wright")) {
    uid
    xid
    <http://schema.org/name>
    <http://schema.org/type> {
      uid
      xid
    }
  }
}

语言和RDF类型 (Language and RDF types)

RDF N-Quad允许为字符串值和RDF类型指定一种语言。语言是使用@lang指定的。例如:

<0x01> <name> "Adelaide"@en .
<0x01> <name> "Аделаида"@ru .
<0x01> <name> "Adélaïde"@fr .
<0x01> <dgraph.type> "Person" .

请参阅如何在查询中处理语言字符串

RDF类型通过标准的^^分隔符附加到字面量值上。例如:

<0x01> <age> "32"^^<xs:int> .
<0x01> <birthdate> "1985-06-08"^^<xs:dateTime> .

受支持的RDF数据类型和存储数据的相应内部类型如下所示:

存储类型Dgraph类型
<xs:string>string
<xs:dateTime>dateTime
<xs:date>dateTime
<xs:int>int
<xs:integer>int
<xs:boolean>bool
<xs:double>float
<xs:float>float
<geo:geojson>geo
<xs:password>password
<http://www.w3.org/2001/XMLSchema#string>string
<http://www.w3.org/2001/XMLSchema#dateTime>dateTime
<http://www.w3.org/2001/XMLSchema#date>dateTime
<http://www.w3.org/2001/XMLSchema#int>int
<http://www.w3.org/2001/XMLSchema#positiveInteger>int
<http://www.w3.org/2001/XMLSchema#integer>int
<http://www.w3.org/2001/XMLSchema#boolean>bool
<http://www.w3.org/2001/XMLSchema#double>float
<http://www.w3.org/2001/XMLSchema#float>float

请参阅RDF Schema类型一节,以了解RDF类型如何影响变更(mutation)和存储。

批量变更 (Batch Mutations)

每个变更可以包含多个RDF三元组。对于大量的数据上传,许多这样的变更可以并行批处理。命令dgraph live就是这样做的;默认情况下,将1000个RDF行批处理到一个查询中,同时并行运行100个这样的查询。

dgraph livegzipN-Quad文件(即不带{set{的三元组列表)作为输入,并为输入中的所有三元组批量处理变更。使用如下命令查看该工具的文档:

dgraph live --help

更多内容查看快速数据导入

删除 (delete)

delete关键字标识的删除变更能从数据库中删除三元组。

例如,如果该数据库包含以下内容:

<0xf11168064b01135b> <name> "Lewis Carrol"
<0xf11168064b01135b> <died> "1998"
<0xf11168064b01135b> <dgraph.type> "Person" .

然后,下面的delete变更删除指定谓语(predicate)的错误数据,并将其从任何索引中删除:

{
  delete {
    <0xf11168064b01135b> <died> "1998" .
  }
}

通配符删除 (Wildcard Delete)

在许多情况下,你可能需要删除一个谓词(predicate)的多个数据。对于给定的节点N,谓词P的所有数据(以及所有对应的索引)都用模式S P *删除:

{
  delete {
    <0xf11168064b01135b> <author.of> * .
  }
}

模式S * *删除节点中所有已知的边,与被删除的边对应的任何反向边,以及对被删除数据的任何索引:

注意:对于符合S * *模式的变更,只删除与给定节点(使用dgraph.type)关联的类型中的谓词。任何不匹配节点类型之一的谓词将在S * *删除变更后保留。

{
  delete {
    <0xf11168064b01135b> * * .
  }
}

如果删除模式S * *中的节点S只有几条边带有dgraph.type定义,则只删除那些带有dgraph.type定义的三元组。包含非dgraph.type定义的边的节点在S * *删除变更之后仍然存在。

注意,* P O* * O的删除模式不受支持,因为存储和查找所有传入边的效率很低。

非列表边的删除 (Deletion of non-list predicates)

删除非列表谓词(即1对1关系)的值可以通过两种方式完成:

  • 使用上一节中提到的通配符删除(星号表示法)。
  • 将对象设置为特定的值。如果传递的值不是当前值,则变更将成功,但不会产生任何影响。如果传递的值是当前值,则变更将成功,并将删除非列表谓词。

对于被语言标记的值,支持以下特殊语法:

{
  delete {
    <0x12345> <name@es> * .
  }
}

在本例中,使用语言标记es标记的name字段的值被删除,其他标记的值保持不变。

RDF中的边属性 (Facet Lists in RDF)

Schema:

<name>: string @index(exact).
<nickname>: [string] .

用RDF创建包含facet的列表非常简单:

{
  set {
    _:Julian <name> "Julian" .
    _:Julian <nickname> "Jay-Jay" (kind="first") .
    _:Julian <nickname> "Jules" (kind="official") .
    _:Julian <nickname> "JB" (kind="CS-GO") .
  }
}
{
  q(func: eq(name,"Julian")){
    name
    nickname @facets
  }
}

返回:

{
  "data": {
    "q": [
      {
        "name": "Julian",
        "nickname|kind": {
          "0": "first",
          "1": "official",
          "2": "CS-GO"
        },
        "nickname": [
          "Jay-Jay",
          "Jules",
          "JB"
        ]
      }
    ]
  }
}

使用cURL提交变更 (Mutations Using cURL)

变更可以通过向Alpha/mutate发出POST请求来实现。在命令行上,这可以通过curl完成。在URL中传递参数commitNow=true表明你是要提交变更。

提交一个set变更:

curl -H "Content-Type: application/rdf" -X POST localhost:8080/mutate?commitNow=true -d $'
{
  set {
    _:alice <name> "Alice" .
    _:alice <dgraph.type> "Person" .
  }
}'

提交一个delete变更:

curl -H "Content-Type: application/rdf" -X POST localhost:8080/mutate?commitNow=true -d $'
{
  delete {
    # Example: The UID of Alice is 0x56f33
    <0x56f33> <name> * .
  }
}'

提交一个存储在文件中的变更,使用--data-binary选项将不会对文件数据编码:

curl -H "Content-Type: application/rdf" -X POST localhost:8080/mutate?commitNow=true --data-binary @mutation.txt

JSON 变更格式 (JSON Mutation Format)

你也能够通过JSON对象指定一个变更。这可以让变更以更自然的方式表达出来。它还消除了应用程序自定义序列化代码的需要,因为大多数语言已经有了JSON处理库。

Dgraph接收到JSON对象的变更时,它首先将其转换为内部边格式,然后再将其处理为Dgraph

对于JSON

JSON -> Edges -> Posting list

对于RDF

RDF -> Edges -> Posting list

每个JSON对象表示图中的单个节点。

注意:JSON变更可以通过Go客户端、JavaScript客户端和Java客户端等gRPC客户端使用,也可以通过dgraph-js-httpcURLHTTP客户端使用。在这里可以看到更多关于cURL的信息。

设置字面量值

当设置新值时,变更消息中的set_json字段应该包含一个JSON对象。

字面量值可以直接通过向JSON对象设置key/value来进行。key将表示谓词(predicate),value将表示对象。

例如:

{
  "name": "diggy",
  "food": "pizza",
  "dgraph.type": "Mascot"
}

上面JSON对象将被转化为如下RDF

_:blank-0 <name> "diggy" .
_:blank-0 <food> "pizza" .
_:blank-0 <dgraph.type> "Mascot" .

变更的返回结果是一个Map,该Map具有一个uid,这个uid对应于键blank-0。你也可以像下面这样指定你自己的uid键:

{
  "uid": "_:diggy",
  "name": "diggy",
  "food": "pizza",
  "dgraph.type": "Mascot"
}

在这种情况下,分配的uid映射将有一个名为diggy的键,其值是分配给它的uid

禁止值 (Forbidden values)

使用JSON提交变更时,uid()val() 这两个字符串是不允许使用的。

例如:Dgraph不能处理以下提交的变更:

{
  "uid": "uid(t)",
  "user_name": "uid(s ia)",
  "name": "val (s kl)",
}

多语言支持 (Language Support)

RDF变更和JSON变更之间的一个重要区别在于指定字符串值的语言。在JSON中,语言标记被附加到边名,而不是像RDF那样的值。

例如,下面是一个使用JSON提交的变更:

{
  "food": "taco",
  "rating@en": "tastes good",
  "rating@es": "sabe bien",
  "rating@fr": "c'est bon",
  "rating@it": "è buono",
  "dgraph.type": "Food"
}

作为对比,使用RDF提交相同的变更需要:

_:blank-0 <food> "taco" .
_:blank-0 <dgraph.type> "Food" .
_:blank-0 <rating> "tastes good"@en .
_:blank-0 <rating> "sabe bien"@es .
_:blank-0 <rating> "c'est bon"@fr .
_:blank-0 <rating> "è buono"@it .

地理位置支持 (Geolocation support)

JSON变更支持地理位置数据。地理位置数据以JSON对象的形式输入,带有typecoordinates键。记住,我们只支持在PointPolygonMultiPolygon类型上建立索引,但是我们可以存储其他类型的地理位置数据。下面是一个例子:

{
  "food": "taco",
  "location": {
    "type": "Point",
    "coordinates": [1.0, 2.0]
  }
}

引用已存在的节点 (Referencing existing nodes)

如果JSON对象包含一个名为uid的字段,那么该字段将被解释为图中现有节点的uid。这种机制允许您引用现有的节点。

例如:

{
  "uid": "0x467ba0",
  "food": "taco",
  "rating": "tastes good",
  "dgraph.type": "Food"
}

上面的JSON对象将被转化为以下RDF

<0x467ba0> <food> "taco" .
<0x467ba0> <rating> "tastes good" .
<0x467ba0> <dgraph.type> "Food" .

节点之间的边 (Edges between nodes)

节点之间的边以类似于字面量值的方式表示,只是对象是一个JSON对象:

{
  "name": "Alice",
  "friend": {
    "name": "Betty"
  }
}

上面的JSON对象将会被转化为:

_:blank-0 <name> "Alice" .
_:blank-0 <friend> _:blank-1 .
_:blank-1 <name> "Betty" .

变更的结果将包含分配给blank-0blank-1节点的uid。如果您想在不同的键下返回这些uid,您可以将uid字段指定为一个空白节点:

{
  "uid": "_:alice",
  "name": "Alice",
  "friend": {
    "uid": "_:bob",
    "name": "Betty"
  }
}

上面的JSON对象将会被转化为:

_:alice <name> "Alice" .
_:alice <friend> _:bob .
_:bob <name> "Betty" .

可以使用与添加字面量值相同的方式引用现有节点。例如连接两个现有节点:

{
  "uid": "0x123",
  "link": {
    "uid": "0x456"
  }
}

将会被转化为:

<0x123> <link> <0x456> .

注意:一个常见的错误是尝试使用{"uid":"0x123","link":"0x456"}。这将导致一个错误:Dgraph将这个JSON对象解释为将链接谓词设置为字符串0x456,这通常不是你希望的。

删除字面量值 (Deleting literal values)

JSON也能提交删除变更。

要发送删除变更,在变更消息中使用delete_json字段而不是set_json字段。

注意:如果您使用dgraph-js-http客户端或Ratel UI,请检查JSON语法使用原始HTTPRatel UI部分。

在使用删除变更时,始终必须引用现有节点。因此,必须提供每个JSON对象的uid字段。应该删除的谓词应该设置为JSONnull

例如,要删除食物评级:

{
  "uid": "0x467ba0",
  "rating": null
}

删除边

删除一条边需要创建该边的相同JSON对象。例如,删除谓词链接0x1230x456

{
  "uid": "0x123",
  "link": {
    "uid": "0x456"
  }
}

从单个节点发出的谓词(predicate)的所有边都可以一次删除(相当于删除S P *):

{
  "uid": "0x123",
  "link": null
}

如果没有指定谓词,则删除节点的所有已知出站边(到其他节点和到字面量值)(对应于删除S * *)。要删除的谓词是使用类型系统派生的。有关更多信息,请参阅RDF格式文档类型系统部分:

{
  "uid": "0x123"
}

Facets

可以通过使用|字符来分隔JSON对象字段名中的谓词和facet键来创建facet。这与用于在查询结果中显示facet的编码模式相同。如:

{
  "name": "Carol",
  "name|initial": "C",
  "dgraph.type": "Person",
  "friend": {
    "name": "Daryl",
    "friend|close": "yes",
    "dgraph.type": "Person"
  }
}

等价于下面的RDF

_:blank-0 <name> "Carol" (initial="C") .
_:blank-0 <dgraph.type> "Person" .
_:blank-0 <friend> _:blank-1 (close="yes") .
_:blank-1 <name> "Daryl" .
_:blank-1 <dgraph.type> "Person" .

facet不包含类型信息,但是Dgraph将尝试从输入猜测类型。如果一个facet的值可以被解析为一个数字,那么它将被转换为floatint。如果它可以被解析为一个布尔值,那么它将被存储为一个布尔值。如果值是字符串,如果该字符串与Dgraph能识别的时间格式(YYYY, MM-YYYY, DD-MM-YYYY, RFC339等)中的一种相匹配,那么它将被存储为datetime,否则将被存储为双引号字符串。如果不想冒facet数据被误读为时间值的风险,最好将数值数据存储为intfloat类型。

删除 Facets

删除Facet最简单的方法是覆盖它。当您为没有facet的相同实体创建一个新的变更时,现有的facet将被自动删除:

<0x1> <name> "Carol" .
<0x1> <friend> <0x2> .

另一种方法是使用Upsert Block

在下面的查询中,我们将删除NameFriend谓词中的Facet。要覆盖,我们需要收集执行此操作的边的值,并使用val(var)函数来完成覆盖:

curl -H "Content-Type: application/rdf" -X POST localhost:8080/mutate?commitNow=true -d $'
upsert {
  query {
    user as var(func: eq(name, "Carol")){
      Name as name
      Friends as friend
    }
  }

  mutation {
    set {
      uid(user) <name> val(Name) .
      uid(user) <friend> uid(Friends) .
    }
  }
}' | jq

使用 JSON创建一个List然后对这个List操作:

Schema:

testList: [string] .

testList中添加一些数据:

{
  "testList": [
    "Grape",
    "Apple",
    "Strawberry",
    "Banana",
    "watermelon"
  ]
}

现在让我们把Apple从这个列表中删除(注意,List中的项是区分大小写的):

{
  q(func: has(testList)) {
    uid
    testList
  }
}
{
  "delete": {
    "uid": "0x6", #UID of the list.
    "testList": "Apple"
  }
}

当然,你也能够一次性删除多个值:

{
  "delete": {
    "uid": "0x6",
    "testList": [
      "Strawberry",
      "Banana",
      "watermelon"
    ]
  }
}

注意:如果您使用dgraph-js-http客户端或Ratel UI,请检查JSON语法使用原始HTTPRatel UI部分。

添加一个水果:

{
   "uid": "0x6", #UID of the list.
   "testList": "Pineapple"
}

JSON 与 List 类型的 facet

schema:

<name>: string @index(exact).
<nickname>: [string] .

要创建list类型的谓词(predicate),需要指定单个列表中的所有值。所有谓词值的facet应该一起指定。它是用map格式完成的,列表中的谓词值的索引是map键,它们各自的facet值是map值。不包含facet值的谓词值将从facet映射中丢失。如:

{
  "set": [
    {
      "uid": "_:Julian",
      "name": "Julian",
      "nickname": ["Jay-Jay", "Jules", "JB"],
      "nickname|kind": {
        "0": "first",
        "1": "official",
        "2": "CS-GO"
      }
    }
  ]
}

在上面您可以看到,我们有三个值,它们各自具有方面,可以进入列表。你可以运行这个查询来检查包含facet的列表:

{
  q(func: eq(name,"Julian")) {
  uid
  nickname @facets
  }
}

之后如果希望使用facet添加更多值,只需执行相同的过程,但现在必须使用相应节点的UID,而不是使用空白节点:

{
  "set": [
    {
      "uid": "0x3",
      "nickname|kind": "Internet",
      "nickname": "@JJ"
    }
  ]
}

最后返回的结果:

{
  "data": {
    "q": [
      {
        "uid": "0x3",
        "nickname|kind": {
          "0": "first",
          "1": "Internet",
          "2": "official",
          "3": "CS-GO"
        },
        "nickname": [
          "Jay-Jay",
          "@JJ",
          "Jules",
          "JB"
        ]
      }
    ]
  }
}

指定多个操作

当指定添加或删除变更时,可以使用JSON数组同时指定多个节点。

例如,下面的JSON对象可以用来添加两个新节点,每个节点都有一个名称:

[
  {
    "name": "Edward"
  },
  {
    "name": "Fredric"
  }
]

使用 Raw HTTPRatel UI 解析 JSON

这种语法可以在最新版本的Ratel中使用,也可以在dgraph-js-http客户端中使用,甚至可以通过cURL使用。

您也可以下载Ratel UI

变更:

{
  "set": [
    {
      # One JSON obj in here
    },
    {
      # Another JSON obj in here for multiple operations
    }
  ]
}

删除: 删除操作与删除字面量值和删除边相同。

{
  "delete": [
    {
      # One JSON obj in here
    },
    {
      # Another JSON obj in here for multiple operations
    }
  ]
}

通过 cURL 使用 JSON 操作

首先,你需要设置 HTTP 相应的请求头为 application/json

-H 'Content-Type: application/json'

注意:为了使用jq进行JSON格式化,你需要jq包。有关安装细节,请参阅jq下载页面。你也可以使用Python的内置json工具模块,使用python -m json

curl -H "Content-Type: application/json" -X POST localhost:8080/mutate?commitNow=true -d $'
    {
      "set": [
        {
          "name": "Alice"
        },
        {
          "name": "Bob"
        }
      ]
    }' | jq

删除操作:

curl -H "Content-Type: application/json" -X POST localhost:8080/mutate?commitNow=true -d $'
    {
      "delete": [
        {
          "uid": "0xa"
        }
      ]
    }' | jq

或者你也可以提交一个在文件中的变更:

curl -H "Content-Type: application/json" -X POST localhost:8080/mutate?commitNow=true -d @data.json

data.json 中文件内容类似于:

{
  "set": [
    {
      "name": "Alice"
    },
    {
      "name": "Bob"
    }
  ]
}

对于HTTP上的变更,JSON文件必须遵循相同的格式:一个带有setdelete键的JSON对象和一个用于变更的JSON对象数组。如果您已经拥有一个包含数据数组的文件,则可以使用jq将数据转换为适当的格式。例如,如果你的Json文件看起来像这样:

[
  {
    "name": "Alice"
  },
  {
    "name": "Bob"
  }
]

然后可以使用以下jq命令将数据转换为适当的格式,其中.jq '{set: .}'中表示data.json的内容:

cat data.json | jq '{set: .}'
{
  "set": [
    {
      "name": "Alice"
    },
    {
      "name": "Bob"
    }
  ]
}

插入块 (Upsert Block)

upsert块允许在单个请求中执行查询和变更。upsert块包含一个查询块和一个或多个变更块。在查询块中定义的变量可以使用uidval函数在变更块中使用。

一般情况下,Upsert Block的结构如下:

upsert {
  query <query block>
  [fragment <fragment block>]
  mutation <mutation block 1>
  [mutation <mutation block 2>]
  ...
}

upsert块的执行还返回在执行变更之前对数据库状态执行的查询的响应。为了获得最新的结果,我们应该提交变更并执行另一个查询。

uid 函数

uid函数允许从查询块中定义的变量中提取uid。根据查询块的执行结果,有两种可能的结果:

  • 如果变量是空的,即没有节点匹配查询,uid函数返回一个新的uid,在set操作的情况下,因此被视为类似于一个空节点。另一方面,对于delete/del操作,它不返回UID,因此该操作成为no-op,并被静默地忽略。空节点在所有变更块中获得相同的UID
  • 如果变量存储一个或多个uid, uid函数将返回存储在变量中的所有uid。在这种情况下,将对返回的所有uid执行操作,一次一个。

val 函数

val函数允许从值变量中提取值。值变量存储uid到对应值的映射。因此,val(v)被存储在N-QuadUID (Subject)映射中的值所替换。如果变量v没有给定UID的值,那么这种变更将被静默地忽略。val函数也可以用于聚合变量的结果,在这种情况下,变更中的所有uid都将用聚合值更新。

uid 函数的使用例子

考虑拥有下面Schema的例子:

curl localhost:8080/alter -X POST -d $'
  name: string @index(term) .
  email: string @index(exact, trigram) @upsert .
  age: int @index(int) .' | jq

现在,假设我们想要创建一个具有emailnameuser。我们还希望确保一个电子邮件在数据库中恰好有一个对应的用户。为了实现这一点,我们首先需要用给定的电子邮件查询数据库中是否存在用户。如果用户存在,则使用其UID更新name信息。如果用户不存在,我们将创建一个新用户并更新电子邮件和名称信息。

我们能够用下面的upsert block块达成上面的需求:

curl -H "Content-Type: application/rdf" -X POST localhost:8080/mutate?commitNow=true -d $'
upsert {
  query {
    q(func: eq(email, "user@company1.io")) {
      v as uid
      name
    }
  }

  mutation {
    set {
      uid(v) <name> "first last" .
      uid(v) <email> "user@company1.io" .
    }
  }
}' | jq

返回结果:

{
  "data": {
    "q": [],
    "code": "Success",
    "message": "Done",
    "uids": {
      "uid(v)": "0x1"
    }
  },
  "extensions": {...}
}

upsert块的查询部分将用户的UID和提供的电子邮件存储在变量v中。变更部分从变量v中提取UID,并将名称和电子邮件信息存储在数据库中。如果用户存在,则更新。如果用户不存在,uid(v)将被视为一个空白节点,并创建一个新用户,如上所述。

如果我们再次运行相同的变更,数据将会被覆盖,并且不会创建新的uid。请注意,当再次执行变更时,结果中的uid映射为空,并且数据映射(键q)包含在前一个upsert中创建的uid

{
  "data": {
    "q": [
      {
        "uid": "0x1",
        "name": "first last"
      }
    ],
    "code": "Success",
    "message": "Done",
    "uids": {}
  },
  "extensions": {...}
}

我们可以实现相同的结果使用json数据集如下:

curl -H "Content-Type: application/json" -X POST localhost:8080/mutate?commitNow=true -d '
{
  "query": "{ q(func: eq(email, \"user@company1.io\")) {v as uid, name} }",
  "set": {
    "uid": "uid(v)",
    "name": "first last",
    "email": "user@company1.io"
  }
}' | jq

现在,我们想为拥有相同电子邮件user@company1.io的相同用户添加年龄信息。我们可以使用upsert块做如下相同的事情:

curl -H "Content-Type: application/rdf" -X POST localhost:8080/mutate?commitNow=true -d $'
upsert {
  query {
    q(func: eq(email, "user@company1.io")) {
      v as uid
    }
  }

  mutation {
    set {
      uid(v) <age> "28" .
    }
  }
}' | jq

返回结果:

{
  "data": {
    "q": [
      {
        "uid": "0x1"
      }
    ],
    "code": "Success",
    "message": "Done",
    "uids": {}
  },
  "extensions": {...}
}

在这里,查询块查询电子邮件为user@company1.io的用户。它将用户的uid存储在变量v中,然后突变块使用uid函数从变量v中提取uid,从而更新用户的年龄。

我们可以实现相同的结果使用json数据集如下:

curl -H "Content-Type: application/json" -X POST localhost:8080/mutate?commitNow=true -d $'
{
  "query": "{ q(func: eq(email, \\"user@company1.io\\")) {v as uid} }",
  "set":{
    "uid": "uid(v)",
    "age": "28"
  }
}' | jq

如果我们只想在用户存在的时候执行变更,我们可以使用条件Upsert

val 函数示例

假设我们想把断言年龄迁移到其他年龄。我们可以使用以下突变来做到这一点:

curl -H "Content-Type: application/rdf" -X POST localhost:8080/mutate?commitNow=true -d $'
upsert {
  query {
    v as var(func: has(age)) {
      a as age
    }
  }

  mutation {
    # we copy the values from the old predicate
    set {
      uid(v) <other> val(a) .
    }

    # and we delete the old predicate
    delete {
      uid(v) <age> * .
    }
  }
}' | jq

返回结果:

{
  "data": {
    "code": "Success",
    "message": "Done",
    "uids": {}
  },
  "extensions": {...}
}

在这里,变量a将存储从所有uid到其年龄的映射。然后,变更块将每个UID对应的年龄值存储在另一个谓词中,并删除年龄谓词。

我们可以实现相同的结果使用json数据集如下:

curl -H "Content-Type: application/json" -X POST localhost:8080/mutate?commitNow=true -d $'{
  "query": "{ v as var(func: regexp(email, /.*@company1.io$/)) }",
  "delete": {
    "uid": "uid(v)",
    "age": null
  },
  "set": {
    "uid": "uid(v)",
    "other": "val(a)"
  }
}' | jq

批量删除示例 (Bulk Delete Exmaple)

假设我们想从数据库中删除company1的所有用户。这可以通过upsert块在一个查询中实现,如下所示:

curl -H "Content-Type: application/rdf" -X POST localhost:8080/mutate?commitNow=true -d $'
upsert {
  query {
    v as var(func: regexp(email, /.*@company1.io$/))
  }

  mutation {
    delete {
      uid(v) <name> * .
      uid(v) <email> * .
      uid(v) <age> * .
    }
  }
}' | jq

我们可以实现相同的结果使用json数据集如下:

curl -H "Content-Type: application/json" -X POST localhost:8080/mutate?commitNow=true -d '{
  "query": "{ v as var(func: regexp(email, /.*@company1.io$/)) }",
  "delete": {
    "uid": "uid(v)",
    "name": null,
    "email": null,
    "age": null
  }
}' | jq

条件插入

插入块(upsert block)支持使用@if指令指定一个条件变更块(mutation block)。这个变更块只有在指定的条件为true时才会执行。如果指定的条件是falseDgraph会静默忽略该变更块(mutation block),通常一个条件变更块有下面这样的结构:

upsert {
  query <query block>
  [fragment <fragment block>]
  mutation [@if(<condition>)] <mutation block 1>
  [mutation [@if(<condition>)] <mutation block 2>]
  ...
}

@if指令接受一个定义在查询块(query block)的条件变量,里面的条件变量能够和AND OR NOT 一起使用。

一个使用条件插入的小例子

假设在我们之前的例子中,我们知道公司1的员工少于100人。为了安全起见,我们希望只在变量v中存储的uid小于100但大于50时才执行变异。这可以实现如下:

curl -H "Content-Type: application/rdf" -X POST localhost:8080/mutate?commitNow=true -d  $'
upsert {
  query {
    v as var(func: regexp(email, /.*@company1.io$/))
  }

  mutation @if(lt(len(v), 100) AND gt(len(v), 50)) {
    delete {
      uid(v) <name> * .
      uid(v) <email> * .
      uid(v) <age> * .
    }
  }
}' | jq

我们可以实现相同的结果使用json数据集如下:

curl -H "Content-Type: application/json" -X POST localhost:8080/mutate?commitNow=true -d '{
  "query": "{ v as var(func: regexp(email, /.*@company1.io$/)) }",
  "cond": "@if(lt(len(v), 100) AND gt(len(v), 50))",
  "delete": {
    "uid": "uid(v)",
    "name": null,
    "email": null,
    "age": null
  }
}' | jq

关于多变更块的小例子

考虑以下Schema的一个例子:

curl localhost:8080/alter -X POST -d $'
  name: string @index(term) .
  email: [string] @index(exact) @upsert .' | jq

假设,我们的数据库中存储了许多用户,每个用户都有一个或多个电子邮件地址。现在,我们得到了属于同一用户的两个电子邮件地址。如果电子邮件地址属于数据库中的不同节点,我们希望删除现有节点,并创建一个新节点,将这两个电子邮件都附加到这个新节点。否则,我们将使用这两个电子邮件创建/更新新的/现有节点。

curl -H "Content-Type: application/rdf" -X POST localhost:8080/mutate?commitNow=true -d $'
upsert {
  query {
    # filter is needed to ensure that we do not get same UIDs in u1 and u2
    q1(func: eq(email, "user_email1@company1.io")) @filter(not(eq(email, "user_email2@company1.io"))) {
      u1 as uid
    }

    q2(func: eq(email, "user_email2@company1.io")) @filter(not(eq(email, "user_email1@company1.io"))) {
      u2 as uid
    }

    q3(func: eq(email, "user_email1@company1.io")) @filter(eq(email, "user_email2@company1.io")) {
      u3 as uid
    }
  }

  # case when both emails do not exist
  mutation @if(eq(len(u1), 0) AND eq(len(u2), 0) AND eq(len(u3), 0)) {
    set {
      _:user <name> "user" .
      _:user <dgraph.type> "Person" .
      _:user <email> "user_email1@company1.io" .
      _:user <email> "user_email2@company1.io" .
    }
  }

  # case when email1 exists but email2 does not
  mutation @if(eq(len(u1), 1) AND eq(len(u2), 0) AND eq(len(u3), 0)) {
    set {
      uid(u1) <email> "user_email2@company1.io" .
    }
  }

  # case when email1 does not exist but email2 exists
  mutation @if(eq(len(u1), 0) AND eq(len(u2), 1) AND eq(len(u3), 0)) {
    set {
      uid(u2) <email> "user_email1@company1.io" .
    }
  }

  # case when both emails exist and needs merging
  mutation @if(eq(len(u1), 1) AND eq(len(u2), 1) AND eq(len(u3), 0)) {
    set {
      _:user <name> "user" .
      _:user <dgraph.type> "Person" .
      _:user <email> "user_email1@company1.io" .
      _:user <email> "user_email2@company1.io" .
    }

    delete {
      uid(u1) <name> * .
      uid(u1) <email> * .
      uid(u2) <name> * .
      uid(u2) <email> * .
    }
  }
}' | jq

返回结果(当数据库都为空时):

{
  "data": {
    "q1": [],
    "q2": [],
    "q3": [],
    "code": "Success",
    "message": "Done",
    "uids": {
      "user": "0x1"
    }
  },
  "extensions": {...}
}

返回结果(两个邮件都存在,并附加到不同的节点):

{
  "data": {
    "q1": [
      {
        "uid": "0x2"
      }
    ],
    "q2": [
      {
        "uid": "0x3"
      }
    ],
    "q3": [],
    "code": "Success",
    "message": "Done",
    "uids": {
      "user": "0x4"
    }
  },
  "extensions": {...}
}

返回结果(当两个电子邮件存在并且已经附加到同一个节点):

{
  "data": {
    "q1": [],
    "q2": [],
    "q3": [
      {
        "uid": "0x4"
      }
    ],
    "code": "Success",
    "message": "Done",
    "uids": {}
  },
  "extensions": {...}
}

我们可以实现相同的结果使用json数据集如下:

curl -H "Content-Type: application/json" -X POST localhost:8080/mutate?commitNow=true -d '{
  "query": "{q1(func: eq(email, \"user_email1@company1.io\")) @filter(not(eq(email, \"user_email2@company1.io\"))) {u1 as uid} \n q2(func: eq(email, \"user_email2@company1.io\")) @filter(not(eq(email, \"user_email1@company1.io\"))) {u2 as uid} \n q3(func: eq(email, \"user_email1@company1.io\")) @filter(eq(email, \"user_email2@company1.io\")) {u3 as uid}}",
  "mutations": [
    {
      "cond": "@if(eq(len(u1), 0) AND eq(len(u2), 0) AND eq(len(u3), 0))",
      "set": [
        {
          "uid": "_:user",
          "name": "user",
          "dgraph.type": "Person"
        },
        {
          "uid": "_:user",
          "email": "user_email1@company1.io",
          "dgraph.type": "Person"
        },
        {
          "uid": "_:user",
          "email": "user_email2@company1.io",
          "dgraph.type": "Person"
        }
      ]
    },
    {
      "cond": "@if(eq(len(u1), 1) AND eq(len(u2), 0) AND eq(len(u3), 0))",
      "set": [
        {
          "uid": "uid(u1)",
          "email": "user_email2@company1.io",
          "dgraph.type": "Person"
        }
      ]
    },
    {
      "cond": "@if(eq(len(u1), 1) AND eq(len(u2), 0) AND eq(len(u3), 0))",
      "set": [
        {
          "uid": "uid(u2)",
          "email": "user_email1@company1.io",
          "dgraph.type": "Person"
        }
      ]
    },
    {
      "cond": "@if(eq(len(u1), 1) AND eq(len(u2), 1) AND eq(len(u3), 0))",
      "set": [
        {
          "uid": "_:user",
          "name": "user",
          "dgraph.type": "Person"
        },
        {
          "uid": "_:user",
          "email": "user_email1@company1.io",
          "dgraph.type": "Person"
        },
        {
          "uid": "_:user",
          "email": "user_email2@company1.io",
          "dgraph.type": "Person"
        }
      ],
      "delete": [
        {
          "uid": "uid(u1)",
          "name": null,
          "email": null
        },
        {
          "uid": "uid(u2)",
          "name": null,
          "email": null
        }
      ]
    }
  ]
}' | jq

反向边 (reverse edges)

Dgraph中的任何出边(outgoing edge)都可以使用Schema中的@reverse指令反向,并使用波浪号作为边名的前缀来查询。例如<~myEdge>

Dgraph是有向图模型,这意味着所有属性总是在一个方向上从一个实体指向另一个实体或值,即S P -> O

反向边是自动生成的边,不属于你的数据集的一部分。这意味着您不能直接在反向边上提交变更。改变正向边将会自动更新反向边的值。

正确地使用反向边

RDF中,三元组的排列已经定义了可以逆转的内容:

_:MyObject <myEdge> _:BlankNode  . #反向边正确的语法
_:BlankNode <dgraph.type> "Person" .

纠正和应用反向边的最简单方法是使用JSON。它只是将Schema上的指令放在所需的边上。在构建你的变更时,请记住JSON中没有反向边相关的语法。因此,您应该做的事情与RDF类似:更改JSON对象的排列。

因为MyObject实体在Person实体之上,所以在格式化变更时,MyObject必须在前面:

{
   "set": [
      {
         "uid": "_:MyObject",
         "dgraph.type": "Object",
         "myEdge": {
            "uid": "_:BlankNode",
            "dgraph.type": "Person"
         }
      }
   ]
}

另一种方法是分割成一些小块或小批,并使用空白节点作为引用。这有助于组织和重用引用:

{
   "set": [
      {
         "uid": "_:MyObject",
         "dgraph.type": "Object",
         "myEdge": [{"uid": "_:BlankNode"}]
      },
      {
         "uid": "_:BlankNode",
         "dgraph.type": "Person"
      }
   ]
}

更多的关于反向边的小例子

RDF中,想要正确地使用反向边非常简单:

name: String .
husband: uid @reverse .
wife: uid @reverse .
parent: [uid] @reverse .
{
  set {
    _:Megalosaurus <name> "Earl Sneed Sinclair" .
    _:Megalosaurus <dgraph.type> "Dinosaur" .
    _:Megalosaurus <wife> _:Allosaurus .
    _:Allosaurus <name> "Francis Johanna Phillips Sinclair" (short="Fran") .
    _:Allosaurus <dgraph.type> "Dinosaur" .
    _:Allosaurus <husband> _:Megalosaurus .
    _:Hypsilophodon <name> "Robert Mark Sinclair" (short="Robbie") .
    _:Hypsilophodon <dgraph.type> "Dinosaur" .
    _:Hypsilophodon <parent> _:Allosaurus (role="son") .
    _:Hypsilophodon <parent> _:Megalosaurus (role="son") .
    _:Protoceratops <name> "Charlene Fiona Sinclair" .
    _:Protoceratops <dgraph.type> "Dinosaur" .
    _:Protoceratops <parent> _:Allosaurus (role="daughter") .
    _:Protoceratops <parent> _:Megalosaurus (role="daughter") .
    _:MegalosaurusBaby <name> "Baby Sinclair" (short="Baby") .
    _:MegalosaurusBaby <dgraph.type> "Dinosaur" .
    _:MegalosaurusBaby <parent> _:Allosaurus (role="son") .
    _:MegalosaurusBaby <parent> _:Megalosaurus (role="son") .
  }
}

边的方向应该如下:

Exchanged hierarchy:
 Object -> Parent;
 Object <~ Parent; #Reverse
 Children to parents via "parent" edge.
 wife and husband bidirectional using reverse.
Normal hierarchy:
 Parent -> Object;
 Parent <~ Object; #Reverse
 This hierarchy is not part of the example, but is generally used in all graph models.
 To make this hierarchy we need to bring the hierarchical relationship starting from the parents and not from the children. Instead of using the edges "wife" and "husband" we switch to single edge called "married" to simplify the model.
    _:Megalosaurus <name> "Earl Sneed Sinclair" .
    _:Megalosaurus <dgraph.type> "Dinosaur" .
    _:Megalosaurus <married> _:Allosaurus .
    _:Megalosaurus <parent> _:Hypsilophodon (role="son") .
    _:Megalosaurus <parent> _:Protoceratops (role="daughter") .
    _:Megalosaurus <parent> _:MegalosaurusBaby (role="son") .
    _:Allosaurus <name> "Francis Johanna Phillips Sinclair" (short="Fran") .
    _:Allosaurus <dgraph.type> "Dinosaur" .
    _:Allosaurus <married> _:Megalosaurus .
    _:Allosaurus <parent> _:Hypsilophodon (role="son") .
    _:Allosaurus <parent> _:Protoceratops (role="daughter") .
    _:Allosaurus <parent> _:MegalosaurusBaby (role="son") .

查询

  1. wife_husbandwife的反向边。
  2. husband是一条正常的正向边。
{
  q(func: has(wife)) {
    name
    WF as wife {
      name
    }
  }
  reverseIt(func: uid(WF)) {
    name
    wife_husband : ~wife {
      name
    }
    husband {
      name
    }
  }
}
  1. Childrenparent 的反向边:
{
  q(func: has(name)) @filter(eq(name, "Earl Sneed Sinclair")){
    name
    Children : ~parent @facets {
      name
    }
  }
}

反向边和 facet

反向边上的facet与正向边上的相同。也就是说,如果您在一条边上设置或更新一个facet,那么它的反向边将具有相同的facet

{
  set {
    _:Megalosaurus <name> "Earl Sneed Sinclair" .
    _:Megalosaurus <dgraph.type> "Dinosaur" .
    _:Megalosaurus <wife> _:Allosaurus .
    _:Megalosaurus <parent> _:MegalosaurusBaby (role="parent -> child") .
    _:MegalosaurusBaby <name> "Baby Sinclair" (short="Baby -> parent") .
    _:MegalosaurusBaby <dgraph.type> "Dinosaur" .
    _:MegalosaurusBaby <parent> _:Megalosaurus (role="child -> parent") .
  }
}

使用前面例子中的类似查询:

{
  Parent(func: has(name)) @filter(eq(name, "Earl Sneed Sinclair")){
    name
    C as Children : parent @facets {
      name
    }
  }
    Child(func: uid(C)) {
      name
      parent @facets {
        name
      }
    }
}
{
  "data": {
    "Parent": [
      {
        "name": "Earl Sneed Sinclair",
        "Children": [
          {
            "name": "Baby Sinclair",
            "Children|role": "parent -> child"
          }
        ]
      }
    ],
    "Child": [
      {
        "name": "Baby Sinclair",
        "parent": [
          {
            "name": "Earl Sneed Sinclair",
            "parent|role": "child -> parent"
          }
        ]
      }
    ]
  }
}

数据加密

检索调试信息

每个Dgraph数据节点都在/debug/pprof端点上公开概要文件,在/debug/vars端点上公开度量。每个Dgraph数据节点都有自己的分析和度量信息。下面是Dgraph公开的调试信息列表,以及检索这些信息的相应命令。

Metrics 信息

如果想从Dgraph实例外部收集这些Metrics信息,则需要在Alpha启动时传递--expose_trace=true标志,否则仅可以通过localhost收集metrics信息。

curl http://<IP>:<HTTP_PORT>/debug/vars

Metrics信息也能在/debug/promethus_metrics通过Promethus的格式检索。详情查看链接

性能信息

性能信息能够通过GO内建的工具go tool pprof查看,可以浏览这篇帖子关于pprof工具的使用。

每一个Dgraph AlphaDgraph Zero都会通过HTTP端口在/debug/pprof/<profile>暴露性能信息:

go tool pprof http://<IP>:<HTTP_PORT>/debug/pprof/heap
Fetching profile from ...
Saved Profile in ...

上面的命令运行成功之后将输出性能信息的保存位置。

在交互式的pprof shell中,你可以使用像top这样的命令来获取配置文件中top函数的列表,web命令来获取在web浏览器中打开的配置文件的可视化图形,或者list命令来显示覆盖了配置信息的代码列表。

CPU Profile

go tool pprof http://<IP>:<HTTP_PORT>/debug/pprof/profile

内存 Profile

go tool pprof http://<IP>:<HTTP_PORT>/debug/pprof/heap

块 Profile

默认情况下,Dgraph不收集块配置文件。Dgraph必须以——profile_mode=block和——block_rate=和N> 1开始。

go tool pprof http://<IP>:<HTTP_PORT>/debug/pprof/block

检索调试信息

每个Dgraph数据节点都在/debug/pprof端点上公开概要文件,在/debug/vars端点上公开度量。每个Dgraph数据节点都有自己的分析和度量信息。下面是Dgraph公开的调试信息列表,以及检索这些信息的相应命令。

Metrics 信息

如果想从Dgraph实例外部收集这些Metrics信息,则需要在Alpha启动时传递--expose_trace=true标志,否则仅可以通过localhost收集metrics信息。

curl http://<IP>:<HTTP_PORT>/debug/vars

Metrics信息也能在/debug/promethus_metrics通过Promethus的格式检索。详情查看链接

性能信息

性能信息能够通过GO内建的工具go tool pprof查看,可以浏览这篇帖子关于pprof工具的使用。

每一个Dgraph AlphaDgraph Zero都会通过HTTP端口在/debug/pprof/<profile>暴露性能信息:

go tool pprof http://<IP>:<HTTP_PORT>/debug/pprof/heap
Fetching profile from ...
Saved Profile in ...

上面的命令运行成功之后将输出性能信息的保存位置。

在交互式的pprof shell中,你可以使用像top这样的命令来获取配置文件中top函数的列表,web命令来获取在web浏览器中打开的配置文件的可视化图形,或者list命令来显示覆盖了配置信息的代码列表。

CPU Profile

go tool pprof http://<IP>:<HTTP_PORT>/debug/pprof/profile

内存 Profile

go tool pprof http://<IP>:<HTTP_PORT>/debug/pprof/heap

块 Profile

默认情况下,Dgraph不收集块配置文件。Dgraph必须以——profile_mode=block和——block_rate=和N> 1开始。

go tool pprof http://<IP>:<HTTP_PORT>/debug/pprof/block

原则

原则

版本更新