Многократное использование позиционного оператора `$` для обновления вложенных массивов

Этот вопрос тесно связан с этим вопросом, и я рассмотрю совет, данный относительно дизайна схемы в контексте NoSQL, но мне любопытно понять это:

Актуальные вопросы

Предположим, у вас есть следующий документ:

    _id : 2      abcd
    name : 2     unittest.com
    paths : 4    
        0 : 3    
            path : 2     home
            queries : 4      
                0 : 3    
                    name : 2     query1
                    url : 2      www.unittest.com/home?query1
                    requests: 4

                1 : 3    
                    name : 2     query2
                    url : 2      www.unittest.com/home?query2
                    requests: 4

В основном, я хотел бы знать

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

    { <update operator>: { "paths.$.queries.$.requests" : value } } (не работает)

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

    { <update operator>: { "paths.$.queries.0.requests" : value } }) (работает)

  2. если вообще возможно, как будет выглядеть соответствующий синтаксис R

Ниже вы найдете воспроизводимый пример. Я старался быть максимально кратким.


Пример кода

Подключение к базе данных

require("rmongodb")
db  <- "__unittest" 
ns  <- paste(db, "hosts", sep=".")
# CONNCETION OBJECT
con <- mongo.create(db=db)
# ENSURE EMPTY DB
mongo.remove(mongo=con, ns=ns)

Пример документа

q <- list("_id"="abcd")
b <- list("_id"="abcd", name="unittest.com")
mongo.insert(mongo=con, ns=ns, b=b)
q <- list("_id"="abcd")
b <- list("$push"=list(paths=list(path="home")))
mongo.update(mongo=con, ns, criteria=q, objNew=b)
q <- list("_id"="abcd", paths.path="home")
b <- list("$push"=list("paths.$.queries"=list(
    name="query1", url="www.unittest.com/home?query1")))
mongo.update(mongo=con, ns, criteria=q, objNew=b)
b <- list("$push"=list("paths.$.queries"=list(
    name="query2", url="www.unittest.com/home?query2")))
mongo.update(mongo=con, ns, criteria=q, objNew=b)

Обновление вложенных массивов с явным индексом позиции (работает)

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

q <- list("_id"="abcd", paths.path="home", paths.queries.name="query1")
b <- list("$push"=list("paths.$.queries.0.requests"=list(time="2013-02-13")))
> mongo.bson.from.list(b)
    $push : 3    
        paths.$.queries.0.requests : 3   
            time : 2     2013-02-13

mongo.update(mongo=con, ns, criteria=q, objNew=b)
res <- mongo.find.one(mongo=con, ns=ns, query=q)
> res
    _id : 2      abcd
    name : 2     unittest.com
    paths : 4    
        0 : 3    
            path : 2     home
            queries : 4      
                0 : 3    
                    name : 2     query1
                    requests : 4     
                        0 : 3    
                            time : 2     2013-02-13


                    url : 2      www.unittest.com/home?query1

                1 : 3    
                    name : 2     query2
                    url : 2      www.unittest.com/home?query2

Обновление вложенных массивов с позиционными $ индексы (не работает)

Теперь я хотел бы заменить явное 0 с позиционным $ Оператор так же, как я сделал, чтобы сервер нашел нужный элемент subdoc массива paths (paths.$.queries).

AFAIU документация, это должно работать, так как важно указать "правильный" селектор запросов:

Позиционный оператор $, когда используется с методом update() и действует как заполнитель для первого совпадения селектора запроса на обновление:

Я думаю, что я указал селектор запросов, который находит правильный вложенный элемент (из-за paths.queries.name="query1" часть):

q <- list("_id"="abcd", paths.path="home", paths.queries.name="query1")

Я предполагаю, что в переводе на синтаксис "обычный MongoDB", селектор запросов выглядит примерно так

{ _id: abcd, paths.path: home, paths.queries.name: query1 }

что мне кажется действительным селектором запросов. Фактически это соответствует желаемому элементу / документу:

> !is.null(mongo.find.one(mongo=con, ns=ns, query=q))
[1] TRUE

Я думал, что если он работает на верхнем уровне, то почему он не должен работать и на более высоких уровнях (если селектор запросов указывает на правильные вложенные компоненты)?

Тем не менее, сервер, кажется, не любит вложенное или многократное использование $:

b <- list("$push"=list("paths.$.queries.$.requests"=list(time="2013-02-14")))
> mongo.bson.from.list(b)
    $push : 3    
        paths.$.queries.$.requests : 3   
            time : 2     2013-02-14

> mongo.update(mongo=con, ns, criteria=q, objNew=b)
[1] FALSE

Я не уверен, что это не сработает, потому что MongoDB не поддерживает это или я неправильно понял синтаксис R.

3 ответа

Решение

Позиционный оператор поддерживает только один уровень и только первый соответствующий элемент.

Здесь вы можете найти JIRA для такого поведения, которое вам нужно: https://jira.mongodb.org/browse/SERVER-831

Я не уверен, что это позволит проводить более одного матча, но я верю, что это произойдет из-за динамики того, как он должен будет работать.

В случае, если вы можете выполнить свой запрос из оболочки MongoDB, вы можете обойти это ограничение, воспользовавшись функцией forEach курсора MongoDB ( http://docs.mongodb.org/manual/reference/method/cursor.forEach/).

Вот пример с 3-мя вложенными массивами:

var collectionNameCursor = db.collection_name.find({...});

collectionNameCursor.forEach(function(collectionDocument) {
    var firstArray = collectionDocument.firstArray;
    for(var i = 0; i < firstArray.length; i++) {
        var secondArray = firstArray[i].secondArray;
        for(var j = 0; j < secondArray.length; j++) {
            var thirdArray = secondArray[j].thirdArray;
            for(var k = 0; k < thirdArray.length; k++) {
                //... do some logic here with thirdArray's elements
                db.collection_name.save(collectionDocument);
            }
        }
    }
});

Обратите внимание, что это скорее единовременное решение, чем производственный код, но оно выполнит свою работу, если вам придется написать сценарий исправления.

Как упоминалось @FooBar в комментариях к принятому ответу, эта функция была реализована в 2017 году с MongoDB 3.6.

Для этого необходимо использовать позиционные фильтры с условиями arrayFilters .
Применительно к вашему примеру:

      updateOne(
  { "paths.home": "home" },
  { $push : { 
      "paths.$.queries.$[q].requests": { time: "2022-11-15" } 
    }
  },
  { arrayFilters: [{ "q.name": "name" }] }
)

Почтовый оператор$относится к фильтру{ "paths.home": "home" }. Тогда позиционный фильтр$[q]относится к arrayFilter{ "q.name": "name" }.

Используя этот метод, вы можете добавить столько позиционных фильтров, сколько необходимо, если вы поместите условие вarrayFilters.

Однако, просматривая документацию rmongodb, использование arrayFilters на данный момент невозможно. В качестве альтернативы вы можете использовать другой пакет R, в котором реализована эта функция, например Mongolite .

Другие вопросы по тегам