Использование позиционного оператора MongoDB $ в глубоко вложенном запросе документа

Можно ли использовать позиционный оператор '$' в сочетании с запросом к массиву глубоко вложенных документов?

Рассмотрим следующий вложенный документ, определяющий "пользователя":

{
   username: 'test',
   kingdoms: [

      {
          buildings: [

              {
                  type: 'castle'

              },
              {
                  type: 'treasury'
              },

              ...

          ]

      },

      ...

   ] 
}

Мы хотели бы вернуть "замки" для конкретного пользователя, например, в форме:

{
    kingdoms: [{

        buildings: [{

            type: 'castle'

        }]

    }]
}

Поскольку вы не можете использовать оператор $ дважды ( https://jira.mongodb.org/browse/server-831), я знаю, что не могу также запросить конкретное королевство, поэтому я пытаюсь написать оператор поиска для пятое королевство.

Кажется, это имеет смысл при обновлении глубоко вложенного поддокумента ( Mongodb - обновление глубоко вложенного поддокумента), но у меня меньше успеха с запросом поиска.

Я могу вернуть здания первого королевства с запросом:

db.users.findOne(
    { username: 'test' },
    { kingdoms: {$slice: [0, 1]}, 'kingdom.buildings': 1 }
);

Но это возвращает все здания этого королевства.

Следуя одноуровневым примерам оператора позиции, я пытаюсь выполнить такой запрос:

db.users.findOne(
    { username: 'test', 'kingdoms.buildings.type': 'castle' },
    { kingdoms: {$slice: [n, 1]}, 'kingdom.buildings.$': 1 }
);

чтобы быть в форме:

db.collection.find( { <array.field>: <value> ...}, { "<array>.$": 1 } ) 

как описано в документации http://docs.mongodb.org/manual/reference/operator/projection/positional/ S

Однако это не с ошибкой:

Positional operator does not match the query specifier

Предположительно, потому что kingdoms.buildings не считается массивом. Я также попробовал kingdoms.0.buildings

Это сбивает с толку, потому что это, кажется, работает для обновлений (согласно обновлению Mongodb глубоко вложенный документ)

Я неправильно понял синтаксис или он не поддерживается? Если да, есть ли способ достичь чего-то подобного?

1 ответ

Решение

Вы получаете ошибку от

db.users.findOne(
    { username: 'test', 'kingdoms.buildings.type': 'castle' },
    { kingdoms: {$slice: [n, 1]}, 'kingdom.buildings.$': 1 }
);

потому что есть орфографическая ошибка ("kingdom.buildings.$" должен быть "kingdoms.buildings. $").
Тем не менее, этот способ не может выполнить то, что вы ожидаете.
$ всегда нацелен на королевства на пути к королевствам. Билдинг - первый массив.

Это способ, который должен быть в состоянии решить проблему.
(Требуется V2.6+)

db.c.aggregate([ {
    $match : {
        username : 'test',
        'kingdoms.buildings.type' : 'castle'
    }
}, {
    $project : {
        _id : 0,
        kingdoms : 1
    }
}, {
    $redact : {
        $cond : {
            "if" : {
                $or : [ {
                    $gt : [ "$kingdoms", [] ]
                }, {
                    $gt : [ "$buildings", [] ]
                }, {
                    $eq : [ "$type", "castle" ]
                } ]
            },
            "then" : "$$DESCEND",
            "else" : "$$PRUNE"
        }
    }
} ]).pretty();

Чтобы зарезервировать только первый элемент королевств,

db.c.aggregate([ {
    $match : {
        username : 'test',
        'kingdoms.buildings.type' : 'castle'
    }
}, {
    $redact : {
        $cond : {
            "if" : {
                $or : [ {
                    $gt : [ "$kingdoms", [] ]
                }, {
                    $gt : [ "$buildings", [] ]
                }, {
                    $eq : [ "$type", "castle" ]
                } ]
            },
            "then" : "$$DESCEND",
            "else" : "$$PRUNE"
        }
    }
}, {
    $unwind : "$kingdoms"
}, {
    $group : {
        _id : "$_id",
        kingdom : {
            $first : "$kingdoms"
        }
    }
}, {
    $group : {
        _id : "$_id",
        kingdoms : {
            $push : "$kingdom"
        }
    }
}, {
    $project : {
        _id : 0,
        kingdoms : 1
    }
} ]).pretty();
Другие вопросы по тегам