Подтвердить что ты не робот

Получить глубокий субдокумент n-уровня в MongoDB

У меня есть глубоко вложенный документ в mongoDB, и я хотел бы получить отдельные под-объекты.

Пример:

{
   "schoolName": "Cool School",
   "principal": "Joe Banks",
   "rooms": [
      {
         "number": 100
         "teacher": "Alvin Melvin"
         "students": [
            {
               "name": "Bort"
               "currentGrade": "A"
            },
            // ... many more students
         ]
      },
      // ... many more rooms
   ]
}

Недавно Mongo обновился, чтобы обеспечить получение подпоследовательного объекта с 1-уровневым уровнем, используя $elemMatch:

var projection = { _id: 0, rooms: { $elemMatch: { number: 100 } } };
db.schools.find({"schoolName": "Cool School"}, projection);
// returns { "rooms": [ /* array containing only the matching room */ ]  }

Но когда я пытаюсь получить студента (2 уровня в глубину) таким же образом, я получаю сообщение об ошибке:

var projection = { _id: 0, "rooms.students": { $elemMatch: { name: "Bort" } } };
db.schools.find({"schoolName": "Cool School"}, projection);
// "$err": "Cannot use $elemMatch projection on a nested field (currently unsupported).", "code": 16344

Есть ли способ получить произвольно глубокие под-объекты в документе mongoDB?

Я использую Mongo 2.2.1

4b9b3361

Ответ 1

Недавно я задал аналогичный вопрос и могу дать общий ответ (см. Использование позиционного оператора MongoDB $в глубоко вложенном запросе документа)

Это решение поддерживается только для Mongo 2.6+, но с этого момента вы можете использовать функцию преобразования $aggregation framework.

Вот пример запроса, который должен вернуть только ваш ученик Bort.

db.users.aggregate({

    $match: { schoolName: 'Cool School' }

}, {

    $project: {

        _id: 0,
        'schoolName': 1,
        'rooms.number': 1,
        'rooms.students': 1

    }

}, {

    $redact: {
        $cond: {
            "if": {
                $or: [{
                    $gte: ['$schoolName', '']
                }, {
                    $eq: ['$number', 100]
                }]
            },
            "then": "$$DESCEND",
            "else": {
                $cond: {
                    "if": {
                        $eq: ['$name', 'Bort']
                    },
                    "then": "$$KEEP",
                    "else": "$$PRUNE"
                }
            }
        }
    }

});

$redact может использоваться для создания подзапросов путем согласования или обрезки суб-документов рекурсивно в согласованных документах.

Здесь вы можете прочитать о $redact, чтобы лучше понять, что происходит, но шаблон дизайна, который я определил, имеет следующие требования:

  • Условие redact применяется на уровне каждого суб-документа, поэтому вам нужно уникальное поле на каждом уровне, например. вы не можете иметь номер в качестве ключа в обеих комнатах, и студенты говорят
  • Он работает только с полями данных, а не с индексами массивов, поэтому, если вы хотите узнать возвращенную позицию вложенного документа (например, чтобы обновить его), вам необходимо включить и поддерживайте его в своих документах.
  • Каждая часть инструкции $или в $redact должна соответствовать документам, которые вы хотите на определенном уровне.
  • Поэтому каждая часть оператора $или должна содержать совпадение с уникальным полем документа на этом уровне. Например, $eq: ['$number', 100] соответствует комнате с номером 100
  • Если вы не указываете запрос на уровне, вам нужно по-прежнему включать уникальное поле. Например, если это строка, вы можете сопоставить ее с $gte: ['$uniqueField': '']
  • Последний уровень документа находится в выражении второй, чтобы сохранить весь этот документ.

Ответ 2

В настоящий момент у меня нет mongodb 2.2, поэтому я не могу проверить это, но вы пробовали?

var projection = { _id: 0, rooms: { $elemMatch: { "students.name": "Bort" } } };
db.schools.find({"schoolName": "Cool School"}, projection);