2016-10-06 5 views
1

나는 모서리로 항목을 연결할 수있는 세 가지 컬렉션이있는 그래프가 있습니다. ItemA는 itemB의 부모이며, 이는 itemC의 부모입니다. 단지 방향으로 가장자리에 연결할 수 있습니다 요소Arangodb AQL 재귀 적 그래프 순회

현재
"_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" 
       } 
      } 
     ] 
    } 

그렇다면이 "계층 적"결과를 얻을 수 있습니까?

한 가지 더 : 리프 노드는 리프 노드가 발생할 때까지 실행되어야합니다. 따라서 순회의 깊이는 미리 알 수 없습니다.

+0

'for v, e, p for 1..3 inbound'를 참조하고'p'를 반환하는 몇 가지 관련 기술. 좀더 구체적인 것을 원한다면'p.vertices [0], p.vertices [1], p.vertices [2]'를 사용할 수 있습니다. 거기에서'p'가 이미 계층 적 형식으로되어 있지만 원하는 값을 표시하도록 구조를 구성 할 수 있습니다. –

+0

최대 중첩 깊이를 알고 있습니까? 아니면 예측할 수없는 재귀적인 것입니까? –

+0

결과가 왜 계층 적이어야합니까? 결과 집합에서 중복을 방지하기로되어 있습니까? – CoDEmanX

답변

2

해결책을 찾았습니다. 우리는 UDF (user defined functions)를 사용하기로 결정했습니다.

  1. 가 아란의 DB 펑션 등록

    여기서 적당한 계층 구조를 구성하는 몇 단계이다.

  2. 플랫 구조 (이 정점에 대한 정점 및 해당 경로)를 생성하는 aql 쿼리를 실행하십시오. 결과를 UDF 함수의 입력 매개 변수로 전달하십시오. 1) 아란의 DB에서 함수를 등록 여기 내 기능은 부모 내 경우

에 각 요소를 추가합니다.UDF와

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) 실행 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

나는 또한이 질문에 대한 답을 알아야 할 필요가 있으므로 여기서는 해결책이있다.

코드를 사용자 정의해야하고 몇 가지 개선 사항이있을 것이라고 확신합니다.이 샘플 답변에 해당하는 경우 적절하게 의견을 말하십시오.

해결 방법은 재귀를 지원하는 Foxx Microservice를 사용하고 트리를 작성하는 것입니다. 내가 가지고있는 문제는 루핑 경로를 둘러싼 것이지만 최대 심도 한계를 구현하여이 예를 10 단계로 하드 코딩했습니다.

폭스의 Microservice 만들려면 :

  • 디렉토리 스크립트를
  • 장소를 만들기

    1. 새 폴더를 만듭니다 (예 : 재귀 트리) 루트 디렉토리
    2. 장소에 파일 manifest.jsonindex.js 스크립트 디렉토리에있는 setup.js 파일
    3. 그런 다음이 세 파일로 새 zip 파일을 만듭니다 (예 :).)
    4. ArangoDB 관리 콘솔로 이동
    5. 서비스 | 서비스 추가
    6. 적절한 마운트 포인트를 입력하십시오./내/트리
    7. 우편 탭
    8. 클릭
    9. 만든 Foxx.zip 파일 드래그, 그것은 문제없이 작성해야
    10. 오류가 발생하면, myItemsmyConnections이 존재하지 않는 컬렉션, 그래프를 확인 myGraph은 존재하지 않습니다. 샘플 데이터로 생성하려고하기 때문입니다.
    11. 그런 다음 ArangoDB 관리 콘솔 인 서비스 | API에/내/트리
    12. 클릭
    13. 은/트리/{rootId}
    14. 이 ItemA의 rootId 매개 변수를 제공하고 당신은 제공된 루트 ID에서 결과를 볼 수
    15. '을 그것을 밖으로 시도'를 클릭를 확장 . rootId에 아이가없는 경우 rootId가 존재하지 않는 경우

    , 그것은 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); 
    

    는 이제 FO를 호출 할 수 있습니다 xx 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 또는 exclude 스타일 명령을 처리 할 때 강력한 것으로 대체되지 않았습니다.

    단순화 할 수 있다면 의견이나 의견을 보내고 싶습니다.

  • +0

    AQL은 다양한 깊이의 경로를 지원합니다 - 'path.vertices [n]'또는'path.edges [n]'을 사용하여 깊이 레이어를 검색 할 수 있습니다. 여기서 n은 깊이입니다. –

    +0

    예, 그렇지만 불행히도 n을 지정해야합니다. 와일드 카드를 사용할 수 없습니다. 예를 들어 노드에서 1..10 깊이의 모든 아웃 바운드 경로를 쿼리 한 다음 해당 경로의 가장자리 중 하나에 특정 특성이 있거나 경로의 정점에있는 경우 특수 동작을 수행한다고 가정 해 보겠습니다. 가치. 당신은 n 번 10 번을 지정하는 코드를 작성하게됩니다. 거대한 IF 명령이 있습니다. 나는 IF path.vertices [*]. myKey == 'trigger' '처럼 간단하고 그 그래프 쿼리에 대해 가능한 모든 깊이를 동적으로 처리하기를 바란다. 또한 트리거가 충족 될 경우 경로 처리를 취소 할 수 있습니다. –