Arangodb AQL递归图遍历

时间:2016-10-06 13:56:51

标签: recursion arangodb graph-traversal aql

我有一个包含三个集合的图表,这些集合可以通过边连接。 ItemA是itemB的父级,而itemB又是itemC的父级。 元素只能通过方向

的边连接
"_from : child, _to : parent"

目前我只能获得"线性"此AQL查询的结果:

LET contains = (FOR v IN 1..? INBOUND 'collectionA/itemA' GRAPH 'myGraph' RETURN v)

     RETURN {
        "root": {
            "id": "ItemA",
            "contains": contains
       }
   }

结果如下:

"root": {
    "id": "itemA",
    "contains": [
        {
            "id": "itemB"
        },
        {
            "id": "itemC"
        }
    ]
}

但我需要得到一个"等级"图遍历的结果如下:

"root": {
    "id": "itemA",
    "contains": [
        {
            "id": "itemB",
            "contains": [
                {
                    "id": "itemC"
                }
            }
        ]
    }

所以,我可以得到这个"等级"结果运行aql查询?

还有一件事:遍历应该运行直到遇到叶节点。因此,遍历的深度是未知的。

2 个答案:

答案 0 :(得分:2)

我找到了解决方案。我们决定使用UDF(user defined functions)。

以下是构建适当层次结构的几个步骤:

  1. 在arango db中注册该功能。
  2. 运行您的aql查询,构建一个平面结构(此顶点的顶点和相应路径)。并将结果作为UDF函数的输入参数传递。 在这里,我的函数只是将每个元素附加到其父元素
  3. 在我的情况下: 1)在arango db中注册该功能。

    db.createFunction(
            'GO::LOCATED_IN::APPENT_CHILD_STRUCTURE',
                String(function (root, flatStructure) {
                    if (root && root.id) {
                        var elsById = {};
                        elsById[root.id] = root;
    
                        flatStructure.forEach(function (element) {
                            elsById[element.id] = element;
                            var parentElId = element.path[element.path.length - 2];
                            var parentEl = elsById[parentElId];
    
                            if (!parentEl.contains)
                                parentEl.contains = new Array();
    
                            parentEl.contains.push(element);
                            delete element.path;
                        });
                    }
                    return root;
                })
            );
    

    2)使用udf运行AQL:

        LET flatStructure = (FOR v,e,p IN 1..? INBOUND 'collectionA/itemA' GRAPH 'myGraph' 
           LET childPath = (FOR pv IN p.vertices RETURN pv.id_source)
        RETURN MERGE(v, childPath))
    
        LET root = {"id": "ItemA"} 
    
        RETURN GO::LOCATED_IN::APPENT_CHILD_STRUCTURE(root, flatStructure)
    

    注意:执行功能时请不要忘记the naming convention

答案 1 :(得分:1)

我还需要知道这个问题的答案,所以这里有一个有效的解决方案。

我确定代码需要为您量身定制并且可以进行一些改进,如果适合此示例答案,请进行相应的评论。

解决方案是使用支持递归并构建树的Foxx微服务。我遇到的问题是循环路径,但是我实现了一个最大深度限制来阻止它,在下面的例子中硬编码为10。

创建Foxx微服务:

  1. 创建一个新文件夹(例如递归树)
  2. 创建目录脚本
  3. 将文件manifest.jsonindex.js放入根目录
  4. 将文件setup.js放在脚本目录
  5. 然后创建一个包含这三个文件的新zip文件(例如Foxx.zip
  6. 导航至ArangoDB管理控制台
  7. 点击服务|添加服务
  8. 输入合适的挂载点,例如/我/树
  9. 点击Zip标签
  10. 拖动您创建的Foxx.zip文件,它应该创建没有问题
  11. 如果您收到错误,请确保收藏集myItemsmyConnections不存在,并且名为myGraph的图表不存在,因为它会尝试创建它们样本数据。
  12. 然后导航到ArangoDB管理控制台,服务| /我/树
  13. 点击API
  14. 展开/ tree / {rootId}
  15. 提供ItemA的rootId参数,然后点击“试一试”
  16. 您应该从提供的根ID中看到结果。
  17. 如果rootId不存在,则不返回任何内容 如果rootId没有子节点,则返回一个空数组,用于' contains' 如果rootId已经循环播放'包含'值,它返回嵌套深度限制,我希望有一种更清晰的方法来阻止它。

    以下是三个文件: setup.js(位于脚本文件夹中):

    'use strict';
    const db = require('@arangodb').db;
    const graph_module =  require("org/arangodb/general-graph");
    
    const itemCollectionName = 'myItems';
    const edgeCollectionName = 'myConnections';
    const graphName = 'myGraph';
    
    if (!db._collection(itemCollectionName)) {
      const itemCollection = db._createDocumentCollection(itemCollectionName);
      itemCollection.save({_key: "ItemA" });
      itemCollection.save({_key: "ItemB" });
      itemCollection.save({_key: "ItemC" });
      itemCollection.save({_key: "ItemD" });
      itemCollection.save({_key: "ItemE" });
    
      if (!db._collection(edgeCollectionName)) {
        const edgeCollection = db._createEdgeCollection(edgeCollectionName);
        edgeCollection.save({_from: itemCollectionName + '/ItemA', _to: itemCollectionName + '/ItemB'});
        edgeCollection.save({_from: itemCollectionName + '/ItemB', _to: itemCollectionName + '/ItemC'});
        edgeCollection.save({_from: itemCollectionName + '/ItemB', _to: itemCollectionName + '/ItemD'});
        edgeCollection.save({_from: itemCollectionName + '/ItemD', _to: itemCollectionName + '/ItemE'});
      }
    
      const graphDefinition = [ 
        { 
          "collection": edgeCollectionName, 
          "from":[itemCollectionName], 
          "to":[itemCollectionName]
        }
      ];
    
      const graph = graph_module._create(graphName, graphDefinition);
    }
    

    mainfest.json(位于根文件夹中):

    {
      "engines": {
        "arangodb": "^3.0.0"
      },
      "main": "index.js",
      "scripts": {
        "setup": "scripts/setup.js"
      }
    }
    

    index.js(位于根文件夹中):

    'use strict';
    const createRouter = require('@arangodb/foxx/router');
    const router = createRouter();
    const joi = require('joi');
    
    const db = require('@arangodb').db;
    const aql = require('@arangodb').aql;
    
    const recursionQuery = function(itemId, tree, depth) {
      const result = db._query(aql`
        FOR d IN myItems
        FILTER d._id == ${itemId}
        LET contains = (
          FOR c IN 1..1 OUTBOUND ${itemId} GRAPH 'myGraph' RETURN { "_id": c._id }
        )
        RETURN MERGE({"_id": d._id}, {"contains": contains})
      `);
    
      tree = result._documents[0];
    
      if (depth < 10) {
        if ((result._documents[0]) && (result._documents[0].contains) && (result._documents[0].contains.length > 0)) {
            for (var i = 0; i < result._documents[0].contains.length; i++) {
            tree.contains[i] = recursionQuery(result._documents[0].contains[i]._id, tree.contains[i], depth + 1);
            }
        }
      }
      return tree;
    }
    
    router.get('/tree/:rootId', function(req, res) {
      let myResult = recursionQuery('myItems/' + req.pathParams.rootId, {}, 0);
      res.send(myResult);
    })
      .response(joi.object().required(), 'Tree of child nodes.')
      .summary('Tree of child nodes')
      .description('Tree of child nodes underneath the provided node.');
    
    module.context.use(router);
    

    现在您可以调用Foxx Microservice API端点,提供rootId它将返回完整的树。它很快。

    ItemA的示例输出是:

    {
      "_id": "myItems/ItemA",
      "contains": [
        {
          "_id": "myItems/ItemB",
          "contains": [
            {
              "_id": "myItems/ItemC",
              "contains": []
            },
            {
              "_id": "myItems/ItemD",
              "contains": [
                {
                  "_id": "myItems/ItemE",
                  "contains": []
                }
              ]
            }
          ]
        }
      ]
    }
    

    您可以看到项目B包含两个子项,ItemC和ItemD,然后ItemD也包含ItemE。

    我不能等到ArangoDB AQL改进了FOR v, e, p IN 1..100 OUTBOUND 'abc/def' GRAPH 'someGraph'样式查询中可变深度路径的处理。不建议在3.x中使用自定义访问者,但实际上不能替换为在路径中的顶点深度处理通配符查询或处理prune或{{1路径遍历上的样式命令。

    如果可以简化,我们很乐意收到意见/反馈。