2016-10-06 3 views
1

У меня есть график с тремя коллекциями, элементы которого могут быть связаны ребрами. ItemA является родителем элемента B, который, в свою очередь, является родителем элемента C. элементы только могут быть соединены ребрами в направлении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" 
       } 
      } 
     ] 
    } 

Итак, могу ли я получить этот «иерархический» результат, выполняющий запрос aql?

Еще одна вещь: обход должен выполняться до тех пор, пока не будут обнаружены узлы листа. Так что глубина обхода неизвестна заранее.

+0

Пара соответствующих методов, которые могут вам помочь: 'для v, e, p в 1..3 входящего и возврата' p'. Если вы хотите более конкретно, вы можете использовать 'p.vertices [0], p.vertices [1], p.vertices [2]'. Оттуда вы можете структурировать свое возвращение, чтобы отобразить нужные значения, хотя 'p' уже находится в иерархическом формате. –

+0

Известна ли максимальная глубина гнездования? Или это рекурсивный без предсказуемой глубины? –

+0

Почему результат должен быть иерархическим? Предполагается ли предотвратить дублирование в результирующем наборе? – CoDEmanX

ответ

2

Я нашел решение. Мы решили использовать UDF (user defined functions).

Вот несколько шагов, чтобы построить правильную иерархическую структуру:

  1. Регистрация функцию в Аранго дб.
  2. Запустите свой запрос aql, который строит плоскую структуру (вершину и соответствующий путь для этой вершины). И передайте результат в качестве входного параметра вашей функции UDF. Вот моя функция просто добавить каждый элемент своего родителя

В моем случае: 1) зарегистрировать функцию в Аранго дб.

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 с UDF:

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 в приведенном ниже примере.

Чтобы создать Foxx Microservice:

  1. Создать новую папку (например, рекурсивного дерева)
  2. создаются сценарии директории
  3. Поместите файлы manifest.json и index.js в корневой каталог
  4. Place файл setup.js в каталоге скриптов
  5. Затем создайте новый zip-файл с этими тремя файлами в нем (например, Foxx.zip)
  6. Перейти к администратору консоли ArangoDB
  7. Нажмите на Услуги | Добавить услугу
  8. Введите подходящую точку монтирования, например./Моя/дерево
  9. Нажмите на вкладке Zip
  10. Перетащите файл Foxx.zip вы создали, он должен создать без проблем
  11. Если вы получите сообщение об ошибке, убедитесь, коллекции myItems и myConnections не существует, и граф myGraph не существует, поскольку он попытается создать их с образцами данных.
  12. Затем перейдите к консоли администратора ArangoDB, Services |/Моя/дерево
  13. Нажмите на API
  14. Развернуть/дерево/{RootID}
  15. Обеспечить параметр RootID из Itema и нажмите кнопку 'Попробуйте It Out'
  16. Вы должны увидеть результат, из предоставленного корневого идентификатора ,

Если 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 конечная точка API Microservice, предоставляющая 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, вы не можете использовать wild card. Например, скажем, вы хотите запросить все исходящие пути из узла, 1..10 в глубину, а затем выполнить специальное действие, если на одном из ребер этого пути есть определенный атрибут, или вершина в пути имеет ценность. Вы в конечном итоге пишете код, который указывает n 10 раз, у вас есть огромные команды IF. Мне жаль, что это было так просто, как 'IF path.vertices [*]. MyKey == 'trigger'' и динамически обрабатывается на каждой возможной глубине для этого запроса графа. Также возможность отменить обработку пути, если встречается триггер. –

Смежные вопросы