图遍历查询 #

一、图遍历概述 #

图遍历是在图中从一个或多个起点出发,沿着边访问顶点的过程。

1.1 遍历语法 #

aql
FOR v[, e[, p]] IN [min..max] DIRECTION startVertex edgeCollection
    RETURN result

1.2 遍历参数 #

参数 说明
v 当前顶点
e 当前边(可选)
p 完整路径(可选)
min…max 遍历深度范围
DIRECTION 遍历方向
startVertex 起始顶点
edgeCollection 边集合

1.3 遍历方向 #

方向 说明
OUTBOUND 从_from到_to方向
INBOUND 从_to到_from方向
ANY 双向遍历

二、基本遍历 #

2.1 单跳遍历 #

aql
FOR v IN 1..1 OUTBOUND "users/user_001" follows
    RETURN v.name

2.2 多跳遍历 #

aql
FOR v IN 2..2 OUTBOUND "users/user_001" follows
    RETURN v.name

2.3 范围遍历 #

aql
FOR v IN 1..3 OUTBOUND "users/user_001" follows
    RETURN v.name

2.4 获取边信息 #

aql
FOR v, e IN 1..2 OUTBOUND "users/user_001" follows
    RETURN {
        user: v.name,
        edgeType: e.type,
        createdAt: e.createdAt
    }

2.5 获取完整路径 #

aql
FOR v, e, p IN 1..3 OUTBOUND "users/user_001" follows
    RETURN {
        user: v.name,
        path: p.vertices[*].name,
        depth: LENGTH(p.edges)
    }

三、遍历方向 #

3.1 OUTBOUND遍历 #

查找用户关注的人:

aql
FOR v IN 1..1 OUTBOUND "users/user_001" follows
    RETURN v.name

3.2 INBOUND遍历 #

查找关注用户的人:

aql
FOR v IN 1..1 INBOUND "users/user_001" follows
    RETURN v.name

3.3 ANY遍历 #

双向遍历:

aql
FOR v, e IN 1..1 ANY "users/user_001" follows
    RETURN {
        name: v.name,
        direction: e._from == "users/user_001" ? "following" : "follower"
    }

3.4 多方向遍历 #

aql
FOR v, e, p IN 1..2 ANY "users/user_001" follows
    RETURN {
        name: v.name,
        depth: LENGTH(p.edges),
        path: p.vertices[*].name
    }

四、遍历选项 #

4.1 uniqueVertices #

顶点去重策略:

aql
FOR v IN 1..3 OUTBOUND "users/user_001" follows
    OPTIONS { uniqueVertices: "global" }
    RETURN v.name
说明
“path” 路径内不重复(默认)
“global” 全局不重复
“none” 允许重复

4.2 uniqueEdges #

边去重策略:

aql
FOR v IN 1..3 OUTBOUND "users/user_001" follows
    OPTIONS { uniqueEdges: "path" }
    RETURN v.name
说明
“path” 路径内不重复(默认)
“none” 允许重复

4.3 bfs #

广度优先搜索:

aql
FOR v IN 1..3 OUTBOUND "users/user_001" follows
    OPTIONS { bfs: true, uniqueVertices: "global" }
    RETURN v.name

4.4 完整选项示例 #

aql
FOR v, e, p IN 1..3 OUTBOUND "users/user_001" follows
    OPTIONS {
        bfs: true,
        uniqueVertices: "global",
        uniqueEdges: "path",
        parallelism: 4
    }
    RETURN {
        name: v.name,
        depth: LENGTH(p.edges)
    }

五、过滤遍历 #

5.1 顶点过滤 #

aql
FOR v, e, p IN 1..3 OUTBOUND "users/user_001" follows
    FILTER v.status == "active"
    RETURN v.name

5.2 边过滤 #

aql
FOR v, e, p IN 1..3 OUTBOUND "users/user_001" follows
    FILTER e.type == "friend"
    RETURN v.name

5.3 路径过滤 #

aql
FOR v, e, p IN 1..3 OUTBOUND "users/user_001" follows
    FILTER LENGTH(p.edges) == 2
    AND p.vertices[1].status == "active"
    RETURN v.name

5.4 深度过滤 #

aql
FOR v, e, p IN 1..3 OUTBOUND "users/user_001" follows
    LET depth = LENGTH(p.edges)
    FILTER depth >= 2
    RETURN {
        name: v.name,
        depth: depth
    }

5.5 复杂过滤 #

aql
FOR v, e, p IN 1..3 OUTBOUND "users/user_001" follows
    FILTER v.status == "active"
    FILTER e.type IN ["friend", "colleague"]
    FILTER DATE_DIFF(e.createdAt, DATE_NOW(), "days") < 365
    RETURN {
        name: v.name,
        type: e.type,
        daysSinceFollow: DATE_DIFF(e.createdAt, DATE_NOW(), "days")
    }

六、最短路径 #

6.1 单条最短路径 #

aql
FOR v, e IN OUTBOUND K_SHORTEST_PATHS "users/user_001" TO "users/user_005" follows
    RETURN {
        vertex: v.name,
        edge: e.type
    }

6.2 多条最短路径 #

aql
FOR p IN OUTBOUND K_PATHS "users/user_001" TO "users/user_005" follows
    LIMIT 3
    RETURN {
        path: p.vertices[*].name,
        length: LENGTH(p.edges)
    }

6.3 最短路径带权重 #

aql
FOR v, e, p IN OUTBOUND K_SHORTEST_PATHS "users/user_001" TO "users/user_005" follows
    LET weight = SUM(p.edges[*].weight)
    RETURN {
        path: p.vertices[*].name,
        weight: weight
    }

6.4 双向最短路径 #

aql
FOR v, e IN ANY K_SHORTEST_PATHS "users/user_001" TO "users/user_005" follows
    RETURN {
        vertex: v.name,
        edge: e.type
    }

七、图遍历聚合 #

7.1 统计各深度数量 #

aql
FOR v, e, p IN 1..3 OUTBOUND "users/user_001" follows
    COLLECT depth = LENGTH(p.edges) WITH COUNT INTO count
    RETURN {
        depth: depth,
        count: count
    }

7.2 统计边类型 #

aql
FOR v, e, p IN 1..2 OUTBOUND "users/user_001" follows
    COLLECT type = e.type WITH COUNT INTO count
    RETURN {
        type: type,
        count: count
    }

7.3 路径聚合 #

aql
FOR v, e, p IN 1..2 OUTBOUND "users/user_001" follows
    COLLECT depth = LENGTH(p.edges) AGGREGATE
        avgAge = AVG(v.age),
        count = COUNT()
    RETURN {
        depth: depth,
        avgAge: avgAge,
        count: count
    }

八、多起点遍历 #

8.1 多起点语法 #

aql
FOR start IN ["users/user_001", "users/user_002"]
    FOR v IN 1..2 OUTBOUND start follows
        RETURN {
            start: start,
            reached: v.name
        }

8.2 从查询结果遍历 #

aql
FOR user IN users
    FILTER user.city == "北京"
    FOR v IN 1..1 OUTBOUND user._id follows
        RETURN {
            from: user.name,
            to: v.name
        }

8.3 多起点聚合 #

aql
FOR user IN users
    LET followers = (
        FOR v IN 1..1 INBOUND user._id follows
            RETURN v
    )
    RETURN {
        user: user.name,
        followerCount: LENGTH(followers),
        followers: followers[*].name
    }

九、实战示例 #

9.1 社交网络分析 #

aql
LET userId = "users/user_001"
LET followers = (
    FOR v IN 1..1 INBOUND userId follows
        RETURN v
)
LET following = (
    FOR v IN 1..1 OUTBOUND userId follows
        RETURN v
)
LET mutualFollows = INTERSECTION(
    followers[*]._key,
    following[*]._key
)
LET friendsOfFriends = (
    FOR v IN 2..2 OUTBOUND userId follows
        FILTER v._key NOT IN followers[*]._key
        FILTER v._key NOT IN following[*]._key
        COLLECT user = v WITH COUNT INTO count
        SORT count DESC
        LIMIT 5
        RETURN { user: user.name, mutualFriends: count }
)
RETURN {
    userId: userId,
    followerCount: LENGTH(followers),
    followingCount: LENGTH(following),
    mutualFollowCount: LENGTH(mutualFollows),
    recommendations: friendsOfFriends
}

9.2 商品推荐 #

aql
LET userId = "users/user_001"
LET purchasedProducts = (
    FOR v IN 1..1 OUTBOUND userId purchased
        RETURN v._key
)
LET recommendedProducts = (
    FOR v, e, p IN 2..2 OUTBOUND userId purchased, purchased
        FILTER v._key NOT IN purchasedProducts
        COLLECT product = v WITH COUNT INTO count
        SORT count DESC
        LIMIT 10
        RETURN {
            product: product,
            recommendationScore: count
        }
)
RETURN recommendedProducts

9.3 知识图谱查询 #

aql
LET concept = "concepts/machine_learning"
LET relatedConcepts = (
    FOR v, e, p IN 1..2 ANY concept is_a, related_to
        OPTIONS { uniqueVertices: "global" }
        RETURN DISTINCT {
            concept: v.name,
            relation: e.type,
            depth: LENGTH(p.edges)
        }
)
LET subConcepts = (
    FOR v IN 1..1 INBOUND concept is_a
        RETURN v.name
)
LET superConcepts = (
    FOR v IN 1..1 OUTBOUND concept is_a
        RETURN v.name
)
RETURN {
    concept: concept,
    subConcepts: subConcepts,
    superConcepts: superConcepts,
    relatedConcepts: relatedConcepts
}

9.4 欺诈检测 #

aql
LET suspiciousAccount = "accounts/account_001"
LET relatedAccounts = (
    FOR v, e, p IN 1..3 ANY suspiciousAccount transferred, shared_device
        OPTIONS { uniqueVertices: "global" }
        COLLECT account = v WITH COUNT INTO connections
        FILTER connections >= 3
        RETURN {
            account: account._key,
            connections: connections,
            risk: connections >= 5 ? "high" : "medium"
        }
)
RETURN {
    suspiciousAccount: suspiciousAccount,
    relatedAccounts: relatedAccounts
}

9.5 组织架构查询 #

aql
LET employee = "employees/emp_001"
LET managers = (
    FOR v, e, p IN 1..5 INBOUND employee reports_to
        RETURN {
            name: v.name,
            level: LENGTH(p.edges),
            position: v.position
        }
)
LET subordinates = (
    FOR v, e, p IN 1..5 OUTBOUND employee reports_to
        RETURN {
            name: v.name,
            level: LENGTH(p.edges),
            position: v.position
        }
)
LET colleagues = (
    FOR v IN 1..1 INBOUND employee reports_to
        FOR colleague IN 1..1 OUTBOUND v._id reports_to
            FILTER colleague._key != employee._key
            RETURN colleague.name
)
RETURN {
    employee: employee,
    managers: managers,
    subordinates: subordinates,
    colleagues: colleagues
}

十、性能优化 #

10.1 限制遍历深度 #

aql
FOR v IN 1..2 OUTBOUND "users/user_001" follows
    RETURN v.name

10.2 使用过滤条件 #

aql
FOR v, e IN 1..3 OUTBOUND "users/user_001" follows
    FILTER v.status == "active"
    LIMIT 100
    RETURN v.name

10.3 使用遍历选项 #

aql
FOR v IN 1..3 OUTBOUND "users/user_001" follows
    OPTIONS {
        bfs: true,
        uniqueVertices: "global",
        parallelism: 4
    }
    RETURN v.name

10.4 使用索引 #

javascript
db.follows.ensureHashIndex(["_from"]);
db.follows.ensureHashIndex(["_to"]);

10.5 分析查询计划 #

javascript
db._explain(`
    FOR v IN 1..3 OUTBOUND "users/user_001" follows
        RETURN v.name
`);

十一、总结 #

图遍历查询要点:

  1. 基本遍历:FOR v IN min…max DIRECTION startVertex edges
  2. 遍历方向:OUTBOUND、INBOUND、ANY
  3. 遍历选项:uniqueVertices、bfs、parallelism
  4. 过滤条件:顶点过滤、边过滤、路径过滤
  5. 最短路径:K_SHORTEST_PATHS、K_PATHS

下一步,让我们学习索引管理!

最后更新:2026-03-27