边与关系 #

一、边概述 #

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() 遍历关系

最佳实践:

  1. 使用动词命名边标签
  2. 合理设计边属性
  3. 考虑边的方向性
  4. 避免冗余边
  5. 使用属性加速查询

下一步,让我们学习属性!

最后更新:2026-03-27