关系操作
|
这是 GraphQL Library 7 版本的文档。对于长期支持 (LTS) 版本 5,请参考 GraphQL Library 5 LTS 版本。 |
关系是 Neo4j 数据库的重要组成部分,Neo4j GraphQL 库为此生成了一套全面的操作方法。这些操作可以组合使用,使您能够在一次变更(mutation)中执行复杂的图操作。
本页面通过一系列场景和相应的变更操作,演示了可用的各种关系操作以及如何在 GraphQL 模式中有效使用它们来更新“城市数据库”。
编写类型定义
按照关系页面中的描述定义节点和关系,如果需要,请确保包含关系属性。
type Person @node {
name: String!
friends: [Person!]! @relationship(type: "HAS_FRIEND", direction: OUT, properties: "Friendship")
acquaintances: [Person!]! @relationship(type: "KNOWS", direction: OUT)
}
type Friendship @relationshipProperties {
since: Int
}
查询
queryDirection
所有关系都有方向。但是,在查询它们时,可以执行无向查询,即忽略关系方向值的查询。
要设置关系的此种查询行为,可以使用 queryDirection 参数
queryDirection 参数更新 KNOWS 关系字段type Person @node {
name: String!
friends: [Person!]! @relationship(type: "HAS_FRIEND", direction: OUT)
acquaintances: [Person!]! @relationship(type: "KNOWS", direction: OUT, queryDirection: UNDIRECTED)
}
queryDirection 可以具有以下值
-
DIRECTED:只能对此关系执行有向查询。这是默认值。 -
UNDIRECTED:只能对此关系执行无向查询。
获取数据
考虑数据库中的以下示例数据
如上图所示,关系在数据库中必须具有方向。但这并不意味着在查询关系时必须考虑方向,因为这由 @relationship 指令的 queryDirection 参数决定。
请注意查询两个关系字段时响应的差异:friends 考虑了 HAS_FRIEND 关系的方向,而 acquaintances 忽略了 KNOWS 关系的方向,并返回所有相关节点,而不管数据库中该关系的方向如何。
query {
people {
name
friends {
name
}
acquaintances {
name
}
}
}
{
"data": {
"people": [
{
"name": "Alice",
"friends": [{ "name": "Bob" }],
"acquaintances": [{ "name": "Bob" }]
},
{
"name": "Bob",
"friends": [],
"acquaintances": [{ "name": "Alice" }]
}
]
}
}
为了使 Alice 出现在 Bob 的 friends 列表中,数据库中必须存在另一个从 Bob 指向 Alice 的 HAS_FRIEND 类型关系。
设置
MATCH (alice:Person {name: "Alice"})
MATCH (bob:Person {name: "Bob"})
MERGE (bob)-[:HAS_FRIEND]->(alice)
再次运行相同的查询,现在 Alice 出现在了 Bob 的 friends 列表中
{
"data": {
"people": [
{
"name": "Alice",
"friends": [{ "name": "Bob" }],
"acquaintances": [{ "name": "Bob" }]
},
{
"name": "Bob",
"friends": [{ "name": "Alice" }],
"acquaintances": [{ "name": "Alice" }]
}
]
}
}
使用连接查询获取数据
GraphQL 库可以为关系字段生成连接(connection)字段,允许您查询关系属性以及关于该关系的其他元数据。这些字段符合 Relay 风格的连接规范。
以下示例中的查询请求与上述相同的数据,但对两个关系都使用了连接字段。
query {
peopleConnection {
edges {
node {
name
friendsConnection {
edges {
node {
name
}
# can now request relationship properties
properties {
since
}
}
}
acquaintancesConnection {
edges {
node {
name
}
}
}
}
}
}
}
|
|
变更(Mutations)
顶级操作始终引用数据库内的节点。对于上述仅包含 Person 类型的类型定义,生成的顶级操作将为:
createPeople(input: [PersonCreateInput!]!)!: CreatePeopleMutationResponse!
updatePeople(where: PersonWhere!, update: PersonUpdateInput!)!: UpdatePeopleMutationResponse!
deletePeople(where: PersonWhere!, delete: PersonDeleteInput!)!: DeleteInfo!
作为顶级操作的参数,Neo4j GraphQL 库生成了一组嵌套操作,用于管理数据库中节点之间的关系。
术语
-
顶级操作的目标成为关系的“源节点”。
-
通过嵌套操作,创建/遍历的关系将顶级匹配的节点作为源节点。
-
关系另一侧的节点称为“目标节点”。
-
嵌套操作允许对目标节点以及源节点与目标节点之间的关系进行 CRUD 操作。
-
节点的创建和删除使用术语“create”和“delete”,而关系的创建和删除使用术语“connect”和“disconnect”。
通过嵌套操作,您可以创建、更新或删除目标节点,以及源节点和目标节点之间的关系
-
create操作创建目标节点,并同时在同一次操作中创建源节点和目标节点之间的关系。 -
connect操作仅在数据库中已存在的源节点和目标节点之间创建关系。 -
delete操作删除目标节点,以及与之连接的所有关系。 -
disconnect操作仅删除源节点和目标节点之间的关系,而不删除任何节点。 -
update操作更新目标节点的属性,以及源节点和目标节点之间的关系。
每个顶级操作都支持其自己的一组嵌套操作
# Top level Create operation supports nested:
# * create
# * connect
input PersonCreateInput {
# ...other fields
friends: PersonFriendsFieldInput
}
input PersonFriendsFieldInput {
connect : [PersonFriendsConnectFieldInput!]
create : [PersonFriendsCreateFieldInput!]
}
# Top level Update operation supports nested:
# * create
# * connect
# * update
# * disconnect
# * delete
input PersonUpdateInput {
# ...other fields
friends: [PersonFriendsUpdateFieldInput!]
}
input PersonFriendsUpdateFieldInput {
connect : [PersonFriendsConnectFieldInput!]
create : [PersonFriendsCreateFieldInput!]
delete : [PersonFriendsDeleteFieldInput!]
disconnect : [PersonFriendsDisconnectFieldInput!]
update : PersonFriendsUpdateConnectionInput
}
# Top level Delete operation supports nested:
# * delete
input PersonDeleteInput {
# ...other fields
friends: [PersonFriendsDeleteFieldInput!]
}
input PersonFriendsDeleteFieldInput {
delete: PersonDeleteInput
}
本节通过一系列场景和更新城市数据库的等效变更操作,进一步说明了不同的操作及其组合方式。
创建与连接
场景: Bob 去参加聚会,遇到了 Charlie 和 Dave。当被问及时,Charlie 说他认为 Bob 是朋友,但 Dave 说话太少,所以目前只是熟人。
Charlie 不在数据库中,因此需要创建 Person 节点。这意味着可以使用 create 顶级操作。
请注意,在查询时,acquaintances 关系是无向的,因此当 Charlie 说他与 Dave 结识时,反之亦然,无需运行其他查询。
mutation {
createPeople(input: [{
name: "Charlie",
# Charlie is the source node
# creates the relationship HAS_FRIEND
friends: { connect: { where: { node: { name: { eq: "Bob" } } } } },
# creates the node Dave and the relationship KNOWS
acquaintances: { create: { node: { name: "Dave" } } }
}]) {
people {
name
friends {
name
}
acquaintances {
name
}
}
}
}
场景: Bob 同意 Charlie 的看法,也认为他是朋友,而 Dave 是熟人。
Bob 的节点已存在于数据库中,因此必须更新他的节点以反映这些新关系。这表明了顶级 update 操作的用例。
mutation {
updatePeople(
where: { name: "Bob" },
update: {
# Bob is the source node
friends: {
connect: { where: { node: { name: { eq: "Charlie" } } } },
create: { node: { name: "Camilla" } }
},
acquaintances: { connect: { where: { node: { name: { eq: "Dave" } } } } }
}
) {
people {
name
friends {
name
}
acquaintances {
name
}
}
}
}
更新
场景: Bob 和 Camilla 要结婚了,并将姓氏改为 B。
两个节点都已存在于数据库中,表明了顶级 update 操作的用例。
由于他们的节点通过 friends 关系相关联,您可以在同一次变更中执行这两项更改,从顶级 update 操作开始。
|
注意:数据库中只包含一个从 Bob 指向 Camilla 的 friends 关系。这意味着必须在 Bob 的节点上执行顶级 update 操作,因为 Camilla 的节点不符合嵌套 update 操作的过滤条件。 阅读 |
mutation {
updatePeople(
where: { name: { eq: "Bob" } },
update: {
# update Bob node's fields
name: { set: "Bob B." },
# Bob is the source node
friends: [{
update: {
# Camilla is the target node
where: {
node: { name: { eq: "Camilla" } },
},
# update Camilla node's fields
node: { name: { set: "Camilla B." } },
# update the relationship between Bob and Camilla
edge: { since: { set: 2022 } },
},
}],
}
) {
info {
nodesCreated
relationshipsCreated
}
}
}
删除与断开连接
场景: Alice 通知聚会上的每个人她要搬走了。Bob 不喜欢 Charlie 听到消息时的表现,想和他断绝关系。
Alice 的节点需要从数据库中删除,表明了顶级 delete 操作的用例。
但是,场景中的两个动作都有 Bob 参与,因此一个有效的替代方案是在 Bob 的节点上进行顶级 update 操作,并使用嵌套操作对 Alice 和 Charlie 的节点执行正确的更改。
请注意,这之所以可能,是因为 Bob 与两者都有关系。必须存在从 Bob 到 Alice 的 HAS_FRIEND 类型关系,Alice 节点才能满足嵌套 delete 操作的过滤条件,Charlie 和嵌套 disconnect 操作也是如此。
mutation {
updatePeople(
where: { name: { eq: "Bob B." } },
update: {
# Bob B. is the source node
friends: [
{
# deletes Alice and all relationships between Alice and all other nodes (including the KNOWS relationships)
delete: [ { where: { node: { name: { eq: "Alice" } } } } ],
},
{
# deletes the relationship between Bob B. and Charlie, without deleting either node
disconnect: [ { where: { node: { name: { eq: "Charlie" } } } } ],
},
],
}
) {
info {
nodesDeleted
relationshipsDeleted
}
}
}
场景: Bob 和 Camilla 要一起搬走。
两个节点都需要被删除,连同所有与它们相连的关系,这表明了顶级 delete 操作的用例。
由于节点通过 friends 关系相关联,因此可以在同一次变更中执行两次删除,从 delete 操作开始。
就像上面的更新部分一样,有一个注意事项:数据库中只包含一个从 Bob 指向 Camilla 的 friends 关系。这意味着必须在 Bob 的节点上执行顶级 delete 操作,因为 Camilla 的节点不符合嵌套 delete 操作的过滤条件。
让我们探讨在两个方向上运行相同变更的结果
mutation {
deletePeople(
# deletes Camilla and all relationships between Camilla and all other nodes
where: { name: { eq: "Camilla B." } },
delete: {
# Camilla is the source node
friends: [
{
# no relationship of type HAS_FRIEND matching this criteria exists
where: { node: { name: { eq: "Bob B." } } }
},
],
}
) {
nodesDeleted
relationshipsDeleted
}
}
此查询的结果是删除 Camilla 节点以及 Bob 和 Camilla 之间的关系
{
"data": {
"deletePeople": {
"nodesDeleted": 1,
"relationshipsDeleted": 1
}
}
}
mutation {
deletePeople(
# deletes Bob and all relationships between Bob and all other nodes
where: { name: { eq: "Bob B." } },
delete: {
# Bob is the source node
friends: [
{
# deletes Camilla and all relationships between Camilla and all other nodes (including the KNOWS relationships)
where: { node: { name: { eq: "Camilla B." } } }
},
],
}
) {
nodesDeleted
relationshipsDeleted
}
}
此查询的结果是删除 Bob 和 Camilla 两个节点以及它们之间的关系
{
"data": {
"deletePeople": {
"nodesDeleted": 2,
"relationshipsDeleted": 1
}
}
}
阅读queryDirection 和变更部分,了解 @relationship 指令的 queryDirection 参数如何影响这些变更的结果。
queryDirection 和变更
@relationship 指令的 queryDirection 参数会影响数据库查询。
就 GraphQL 操作而言,这意味着查询和变更都会受到其值的影响。
执行变更时,首先需要从数据库中查询变更输入中指定的节点和关系。
正是在这个阶段,queryDirection 参数变得相关,因为它决定了在执行变更时,匹配节点与其相关节点之间的关系是否以有向方式被考虑。
重申上面的例子,将从 Bob 到 Camilla 的 friends 关系更改为 acquaintances 关系,将会如下改变变更的结果
设置
CREATE (bob:Person {name: "Bob B."})
CREATE (camilla:Person {name: "Camilla B."})
MERGE (bob)-[:KNOWS]->(camilla)
变更
mutation {
deletePeople(
# deletes Camilla and all relationships between Camilla and all other nodes
where: { name: { eq: "Camilla B." } },
delete: {
# Camilla is the source node
acquaintances: [
{
# because the relationship is undirected, this filter matches the relationship between Bob and Camilla, even if it is directed from Bob to Camilla in the database
where: { node: { name: { eq: "Bob B." } } }
},
],
}
) {
nodesDeleted
relationshipsDeleted
}
}
此查询的结果是删除 Bob 和 Camilla 两个节点以及它们之间的关系
{
"data": {
"deletePeople": {
"nodesDeleted": 2,
"relationshipsDeleted": 1
}
}
}