Gremlin遍历中如何使用sideEffect并实现邻居全局去重?
背景
我们需要构建一个查询,针对给定的label,获取id列表对应顶点的所有该label类型的邻居。我们共有5种不同的label,希望在单条遍历中完成全部查询。
目标
我们希望对指定的每个label,查询N个邻居,且对idsToBatchQuery列表中所有顶点的聚合邻居做全局去重。
我们当前遇到的问题是sideEffect仅能针对单个顶点做去重。例如:若idsToBatchQuery列表中有10个顶点,每个顶点有3个邻居,当前查询仅会对每个顶点的3个邻居去重,无法对全部30个邻居做全局去重。
问题
如何修改下方的查询,实现从idsToBatchQuery的所有顶点中获取前maxBreadth个唯一邻居?
var traversal = g.V(idsToBatchQuery.toArray()) for (String label : labels) { traversal = traversal.sideEffect( outE(EDGE_LABEL) .inV() .hasLabel(label) .dedup() .by(T.id) .limit(maxBreadth) .aggregate(label)); }
解决方案
问题原因
原有写法的核心问题是sideEffect的作用域绑定在单个输入顶点的遍历上下文里:每处理一个idsToBatchQuery中的顶点,都会单独执行一次sideEffect内的遍历逻辑,dedup和limit都只作用于当前顶点的邻居集合,自然无法实现全局去重。
修复方案
方案一:保留循环逻辑(适配原有写法习惯)
先统一拉取所有输入顶点的邻居,再针对每个label做全局级别的去重、截断和聚合:
var traversal = g.V(idsToBatchQuery.toArray()) // 先统一遍历所有出边指向的邻居顶点,拉到全局上下文 .outE(EDGE_LABEL) .inV() .as("allNeighbors"); for (String label : labels) { traversal = traversal .sideEffect( // 从全局邻居集合中筛选对应label的顶点 select("allNeighbors") .hasLabel(label) // 全局去重 .dedup().by(T.id) // 全局截断取前N个 .limit(maxBreadth) .aggregate(label) ); } // 最终可通过cap获取所有label的聚合结果 // traversal = traversal.cap(labels.toArray());
方案二:分组聚合(更高效的单遍历实现)
不需要循环,直接通过分组算子一步完成所有label的处理,性能更优:
import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.*; // ... var traversal = g.V(idsToBatchQuery.toArray()) .outE(EDGE_LABEL) .inV() // 先对所有邻居做全局去重 .dedup().by(T.id) // 按label分组 .group().by(T.label) // 展开分组,对每个label的邻居列表做截断 .unfold() .filter(select(keys).is(within(labels))) .project("label", "neighbors") .by(keys) .by(select(values).limit(maxBreadth)) // 转为 label -> 邻居列表的Map结构返回 .group().by("label").by("neighbors");
注意事项
如果maxBreadth的限制是全局所有label的总数量而非每个label单独的数量,只需要把limit步骤移到全局去重之后、分组之前即可。
内容的提问来源于stack exchange,提问作者Ryan
相关产品推荐
相关产品推荐

