Как получить родителя и родных братьев в запросе MongoDB - PullRequest
4 голосов
/ 03 апреля 2020

В проекте MongoDB / NodeJS я упорядочил элементы своей коллекции как семейное древо. В пределах определенного маршрута я пытаюсь получить родительский элемент, но также родных братьев и сестер с заданной глубиной, как показано на рисунке ниже:

Collection tree, items wanted through the request

В коллекции для каждого элемента я храню, помимо прочих данных:

  • parentId
  • grandParentId
  • равен Root (логическое значение)

Я пытался что-то сделать с GraphLookup , основывая свой запрос на привязке parentId к grandParentId, например:

db.arguments.aggregate([
    {$match: { _id: mongoose.Types.ObjectId(id) }},
    ,
    {$graphLookup: {
        from: 'arguments',
        startWith: '$grandParentId',
        connectFromField: 'grandParentId',
        connectToField: 'parentId',
        maxDepth: Number(parentsDepth),
        as: 'parentsHierarchy',
        depthField: 'depth',
        restrictSearchWithMatch: { isDeleted: false }
    }}
])

It работает довольно хорошо, но проблема в том, что он не может получить элемент root, у которого нет parentId. Я думал о создании двух отдельных представлений, содержащих каждое из них GraphLookup (одно на основе parentId / grandParentId, другое на id / parentId), затем объединение обоих представлений при удалении дубликатов, но это выглядит странно выполнять два потенциально больших запроса, чтобы получить только элемент root.

Я хотел бы найти надежное решение, поскольку планирую разрешить элементу иметь несколько родителей.

1 Ответ

1 голос
/ 14 апреля 2020

Вы можете изменить его на несколько шагов:

  1. Найти массив прямых асцендентов (родитель, прародитель и т. Д. c.)
  2. Найти непосредственных потомков каждого прародителя и «выше» (тем самым давая вам родных братьев и сестер, родных братьев и сестер и т. д. c.)
  3. Объедините два массива в один набор (таким образом, обеспечивая уникальность)
db.arguments.aggregate([
    {$match: { _id: mongoose.Types.ObjectId(id) }},
    {$graphLookup: {
        from: 'arguments',
        startWith: '$parentId',
        connectFromField: 'parentId',
        connectToField: '_id',
        maxDepth: Number(parentsDepth),
        as: 'parentsHierarchy',
        depthField: 'depth',
        restrictSearchWithMatch: { isDeleted: false }
    }},
    {$unwind: "$parentsHierarchy"},
    {$lookup: {
        from: 'arguments',
        let: { id: '$parentsHierarchy._id', depth: '$parentsHierarchy.depth' },
        pipeline: [
        {$match:{$expr:{
            $and: [{
                $eq: ['$$id', '$parentId']
            },{
                $gte: ["$$depth", 2]
            }]
        }}},
        {$addFields:{
            depth: {$sum: ["$$depth", -1]}
        }}],
        as: 'children'
    }},
    {$group:{
        _id: "$_id",
        parentsHierarchy: {$addToSet: "$parentsHierarchy"},
        children: {$push: "$children"}
        // The rest of your root fields will have to be added here (someField: {$first: "$someField"})
    }},
    {$addFields:{
        hierarchy: {$setUnion: ["$children", "$parentsHierarchy"]}
    }}
])

См. Как вычислить sh значение нескольких столбцов в группе относительно $setUnion.

Оригинальный ответ:

Если вас интересует только родитель и его братья и сестры, вы можете использовать $lookup этап вместо $graphLookup, поскольку вам не нужна рекурсия, которую дает вам график.

Ваш $lookup можно сделать так:

db.test.aggregate([
    {$lookup: {
        from: 'arguments',
        let: { parentId: '$parentId', grandParentId: '$grandParentId' },
        pipeline: [{$match:{$expr:{
            $or: [{
                $eq: ['$_id', '$$parentId']
            },{
                $eq: [{$ifNull: ['$parentId', 'xyz']}, '$$grandParentId']
            }]
        }}}],
        as: 'parentsAndTheirSiblings'
    }}
])

Таким образом, ваш root элемент все еще должен быть найден первой частью $match в pipeline.

Обратите внимание, что я использую $ifNull во второй части, чтобы отфильтровать элементы "root", так как $parentId и $$grandparentId будут иметь значение null или un определяется при поиске элемента на глубине 1. Если предполагается, что все элементы root должны быть найдены для любого элемента глубины 1 (если элементы root считаются братьями и сестрами), то вы можете избавиться от него и просто сравнить $parentId и $$grandparentId прямо вверх.

Документы для поиска: https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...