边与关系 #
一、边概述 #
1.1 什么是边 #
边是图数据库中连接两个顶点的关系,用于表示实体之间的关联。在Neptune中,边是有方向的,从一个顶点指向另一个顶点。
text
边组成:
├── 唯一标识符(ID)
├── 标签(Label)
├── 方向(Direction)
│ ├── 出方向(Out)
│ └── 入方向(In)
├── 起始顶点(Out Vertex)
├── 结束顶点(In Vertex)
└── 属性集合(Properties)
1.2 边特点 #
text
边特点:
├── 有方向性
├── 可有属性
├── 连接两个顶点
├── 可有相同标签
└── 支持自环
1.3 边结构图 #
text
┌─────────────┐ ┌─────────────┐
│ Person │ │ Person │
│─────────────│ │─────────────│
│ id: "1" │──knows───────────▶│ id: "2" │
│ name: Tom │ since: 2020 │ name: Jerry │
│ │ weight: 0.8 │ │
└─────────────┘ └─────────────┘
│ ▲
│ │
│ works_at │
▼ │
┌─────────────┐ │
│ Company │ │
│─────────────│ │
│ id: "3" │ │
│ name: ACME │───────────────────────────┘
└─────────────┘ employs
二、创建边 #
2.1 Gremlin创建边 #
基本创建:
gremlin
// 创建边(需要指定起始和结束顶点)
g.addE('knows').from(V('1')).to(V('2'))
// 创建带属性的边
g.addE('knows').
from(V('1')).
to(V('2')).
property('since', 2020).
property('weight', 0.8)
// 从遍历创建边
g.V('1').addE('knows').to(V('2'))
// 使用as()标记创建
g.V('1').as('a').V('2').as('b').
addE('knows').from('a').to('b')
批量创建:
gremlin
// 批量创建边
g.V('1').as('a').
V('2', '3', '4').as('b').
addE('knows').from('a').to('b')
// 使用unfold创建
g.inject([
['from': '1', 'to': '2'],
['from': '1', 'to': '3'],
['from': '2', 'to': '3']
]).unfold().as('edge').
addE('knows').
from(V(select('edge').select('from'))).
to(V(select('edge').select('to')))
2.2 SPARQL创建边 #
创建三元组:
sparql
PREFIX ex: <http://example.org/>
INSERT DATA {
ex:Tom ex:knows ex:Jerry .
ex:Tom ex:knows ex:Mike .
}
带属性的关系:
sparql
PREFIX ex: <http://example.org/>
# RDF中边属性需要使用RDF-star或中间节点
INSERT DATA {
ex:Tom ex:knows ex:Jerry .
<< ex:Tom ex:knows ex:Jerry >> ex:since "2020" .
}
三、查询边 #
3.1 基本查询 #
gremlin
// 查询所有边
g.E()
// 查询指定ID的边
g.E('e1')
// 查询指定ID列表的边
g.E('e1', 'e2', 'e3')
// 限制返回数量
g.E().limit(10)
3.2 按标签查询 #
gremlin
// 查询指定标签的边
g.E().hasLabel('knows')
// 查询多个标签的边
g.E().hasLabel('knows', 'follows')
// 获取边标签
g.E().label()
3.3 按属性查询 #
gremlin
// 精确匹配
g.E().has('since', 2020)
// 数值比较
g.E().has('weight', gt(0.5))
g.E().has('since', inside(2018, 2022))
// 列表匹配
g.E().has('since', within(2019, 2020, 2021))
3.4 按顶点查询 #
gremlin
// 查询顶点的出边
g.V('1').outE()
// 查询顶点的入边
g.V('1').inE()
// 查询顶点的所有边
g.V('1').bothE()
// 查询指定标签的边
g.V('1').outE('knows')
g.V('1').inE('knows')
g.V('1').bothE('knows')
四、边的方向 #
4.1 出边(Out) #
gremlin
// 获取出边
g.V('1').outE()
// 获取出边连接的顶点
g.V('1').out()
// 获取指定标签的出边顶点
g.V('1').out('knows')
// 示例:Tom认识的人
g.V().has('name', 'Tom').out('knows').values('name')
4.2 入边(In) #
gremlin
// 获取入边
g.V('1').inE()
// 获取入边连接的顶点
g.V('1').in()
// 获取指定标签的入边顶点
g.V('1').in('knows')
// 示例:认识Tom的人
g.V().has('name', 'Tom').in('knows').values('name')
4.3 双向边(Both) #
gremlin
// 获取所有边
g.V('1').bothE()
// 获取所有边连接的顶点
g.V('1').both()
// 获取指定标签的双向顶点
g.V('1').both('knows')
4.4 方向示意图 #
text
出边(Out):从当前顶点出发
┌─────┐ ┌─────┐
│ A │──edge──▶│ B │
└─────┘ └─────┘
A.out() = B
入边(In):到达当前顶点
┌─────┐ ┌─────┐
│ A │──edge──▶│ B │
└─────┘ └─────┘
B.in() = A
双向(Both):两个方向
A.both() = B
B.both() = A
五、边属性 #
5.1 属性操作 #
gremlin
// 添加属性
g.E('e1').property('weight', 0.8)
// 添加多个属性
g.E('e1').
property('weight', 0.8).
property('since', 2020)
// 获取属性值
g.E('e1').values('weight')
// 获取所有属性值
g.E('e1').values()
// 获取属性映射
g.E('e1').valueMap()
// 获取属性对象
g.E('e1').properties()
5.2 更新属性 #
gremlin
// 更新属性值
g.E('e1').property('weight', 0.9)
// 条件更新
g.E('e1').property('weight', 0.9).has('weight', lt(0.9))
5.3 删除属性 #
gremlin
// 删除单个属性
g.E('e1').properties('weight').drop()
// 删除所有属性
g.E('e1').properties().drop()
六、边的端点 #
6.1 获取端点顶点 #
gremlin
// 获取起始顶点(出顶点)
g.E('e1').outV()
// 获取结束顶点(入顶点)
g.E('e1').inV()
// 获取两个端点顶点
g.E('e1').bothV()
// 获取另一端顶点
g.V('1').outE().inV() // 等同于 g.V('1').out()
g.V('1').inE().outV() // 等同于 g.V('1').in()
6.2 端点信息 #
gremlin
// 获取边的完整信息
g.E('e1').project('id', 'label', 'outV', 'inV', 'properties').
by(id).
by(label).
by(outV().id()).
by(inV().id()).
by(valueMap())
七、删除边 #
7.1 基本删除 #
gremlin
// 删除单个边
g.E('e1').drop()
// 删除多个边
g.E('e1', 'e2', 'e3').drop()
// 条件删除
g.E().has('since', lt(2010)).drop()
// 删除顶点的所有边
g.V('1').outE().drop()
g.V('1').inE().drop()
g.V('1').bothE().drop()
7.2 级联删除 #
gremlin
// 删除顶点时自动删除关联边
// Neptune会自动处理
g.V('1').drop()
// 手动删除特定边
g.V('1').outE('knows').drop()
八、边遍历 #
8.1 基本遍历 #
gremlin
// 从顶点遍历到边
g.V('1').outE()
// 从边遍历到顶点
g.E('e1').inV()
g.E('e1').outV()
// 链式遍历
g.V('1').outE('knows').inV().outE('knows').inV()
// 等同于
g.V('1').out('knows').out('knows')
8.2 路径遍历 #
gremlin
// 获取遍历路径
g.V('1').out('knows').path()
// 获取带边的路径
g.V('1').outE('knows').inV().path()
// 路径过滤
g.V('1').out('knows').path().by('name')
8.3 复杂遍历 #
gremlin
// 两跳遍历
g.V('1').out('knows').out('knows')
// 带条件的遍历
g.V('1').outE('knows').has('weight', gt(0.5)).inV()
// 递归遍历
g.V('1').repeat(out('knows')).times(3)
// 直到条件遍历
g.V('1').repeat(out('knows')).until(has('name', 'Jerry'))
九、边统计 #
9.1 计数操作 #
gremlin
// 统计所有边数量
g.E().count()
// 统计指定标签边数量
g.E().hasLabel('knows').count()
// 按标签分组计数
g.E().groupCount().by(label)
// 统计顶点的边数量
g.V('1').outE().count()
g.V('1').inE().count()
g.V('1').bothE().count()
9.2 度统计 #
gremlin
// 出度
g.V('1').outE().count()
// 入度
g.V('1').inE().count()
// 总度
g.V('1').bothE().count()
// 平均度
g.V().as('v').bothE().count().as('degree').
select('v', 'degree').
group().by(select('v').id()).by(select('degree'))
// 高度节点
g.V().order().by(bothE().count(), desc).limit(10)
十、边设计最佳实践 #
10.1 标签设计 #
text
边标签设计原则:
├── 使用动词命名:knows, follows, likes
├── 使用小写和下划线
├── 表示关系的语义
├── 考虑方向性
└── 保持命名一致性
10.2 属性设计 #
text
边属性设计原则:
├── 存储关系相关信息
├── 如:时间、权重、状态
├── 避免存储过多属性
├── 合理使用属性索引
└── 考虑查询模式
10.3 方向设计 #
text
方向设计原则:
├── 明确关系方向
├── 考虑双向查询需求
├── 使用合适的遍历方向
├── 避免冗余边
└── 考虑是否需要双向边
十一、实际应用示例 #
11.1 社交关系 #
gremlin
// 创建用户关系
g.addV('user').property('name', 'Tom').as('tom').
addV('user').property('name', 'Jerry').as('jerry').
addE('follows').
from('tom').
to('jerry').
property('since', datetime()).
property('type', 'friend')
// 查询关注关系
g.V().has('name', 'Tom').out('follows').values('name')
// 查询粉丝
g.V().has('name', 'Tom').in('follows').values('name')
// 查询互相关注
g.V().has('name', 'Tom').as('a').
out('follows').as('b').
where(in('follows').as('a')).
values('name')
11.2 商品关系 #
gremlin
// 创建商品关系
g.addV('product').property('name', 'iPhone').as('iphone').
addV('product').property('name', 'AirPods').as('airpods').
addE('related').
from('iphone').
to('airpods').
property('type', 'accessory').
property('score', 0.9)
// 查询相关商品
g.V().has('name', 'iPhone').out('related').values('name')
// 查询高相关度商品
g.V().has('name', 'iPhone').outE('related').has('score', gt(0.8)).inV()
11.3 组织关系 #
gremlin
// 创建组织关系
g.addV('person').property('name', 'Tom').as('tom').
addV('department').property('name', 'Engineering').as('eng').
addE('works_in').
from('tom').
to('eng').
property('since', '2020-01-01').
property('role', 'Engineer')
// 查询部门成员
g.V().has('name', 'Engineering').in('works_in').values('name')
// 查询员工部门
g.V().has('name', 'Tom').out('works_in').values('name')
十二、总结 #
边操作要点:
| 操作 | Gremlin语法 | 说明 |
|---|---|---|
| 创建 | addE(label).from().to() | 创建新边 |
| 查询 | E() / outE() / inE() | 查询边 |
| 更新 | property() | 更新属性 |
| 删除 | drop() | 删除边 |
| 遍历 | out()/in()/both() | 遍历关系 |
最佳实践:
- 使用动词命名边标签
- 合理设计边属性
- 考虑边的方向性
- 避免冗余边
- 使用属性加速查询
下一步,让我们学习属性!
最后更新:2026-03-27