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