Согласование структуры агрегации mongodb по вложенным документам

У меня есть следующий список документов:

{
    "_id" : "Tvq579754r",
    "name": "Tom",
    "forms": {
           "PreOp":{
             "status":"closed"          
           },

           "Alert":{
             "status":"closed"          
           },

           "City":{
              "status":"closed"         
           },

          "Country":{
             "status":"closed"          
          } 
    }
},
....
{
    "_id" : "Tvq444454j",
    "name": "Jim",
    "forms": {
          "Jorney":{
             "status":"closed"          
           },

          "Women":{
             "status":"void"            
          },

         "Child":{
            "status":"closed"           
         },

         "Farm":{
           "status":"closed"            
         }  
     }
}

Я хочу отфильтровать их по полю 'status' ('forms.name_of_form.status'). Мне нужно получить все документы, у которых нет 'forms.name_of_form.status' equal 'void'.

Ожидаемый результат (документ без статуса аннулированной формы):

{
    "_id" : "Tvq579754r",
    "name": "Tom",
    "forms": {
           "PreOp":{
             "status":"closed"          
           },

           "Alert":{
             "status":"closed"          
           },

           "City":{
              "status":"closed"         
           },

          "Country":{
             "status":"closed"          
          } 
    }
}

Ответ 1

Запрос этой структуры для желаемых результатов невозможен без предварительного уведомления всех возможных имен forms и использования их в запросе. Во всяком случае, это было бы очень грязно. Тем не менее, читайте дальше, объясняя, как это можно сделать.

Существует проблема со структурой этих документов, которая будет препятствовать проведению разумного анализа запросов. В этом случае вам нужно будет знать все возможные поля имени формы, чтобы отфильтровывать что-либо.

В вашей текущей структуре есть формы, содержащие поддокумент, из которых каждый ключ содержит другой под-документ с единственным свойством, status. Это трудно выполнить, поскольку ваш элемент forms имеет произвольную структуру для каждого создаваемого вами документа. Это означает, что шаблон спускается к status информации, которую вы хотите сравнить с изменениями для каждого документа в вашей коллекции.

Вот что я имею в виду по пути. Чтобы получить статус в любом элементе, вам необходимо выполнить следующие действия

Формы → PreOp → статус

Формы → Оповещение → статус

При повторном изменении второго элемента. Существует no way, чтобы подстановить что-то вроде этого, поскольку именование считается явным.

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

{
    "_id" : "Tvq444454j",
    "name": "Jim",
    "forms": [
        {
             "name": "Jorney",
             "status":"closed"          
        },
        {
            "name": "Women",
            "status":"void"            
        },
        {
            "name": "Child",
            "status":"closed"           
        },
        {
            "name": "Farm",
            "status":"closed"            
        }  
    ]
}

Таким образом, структура документа изменяется, чтобы сделать элемент forms массивом, а вместо того, чтобы поместить поле состояния под ключ, который называет "поле формы", мы имеем каждый член массива в качестве поддокумента обозначая "поле формы" name и status. Таким образом, и идентификатор, и статус все еще соединены вместе, но представлены только как суб-документ. Это наиболее важно изменяет путь доступа к этим ключам, так как теперь для и имя поля и его статус мы можем сделать

Формы → статус

или

Формы → имя

Что означает этот, так это то, что вы можете запросить, чтобы найти имена всех полей в form или всех status в form или даже всех документах с определенное поле name и некоторые status. Это намного лучше, чем можно было бы сделать с исходной структурой.

Теперь в вашем конкретном случае вы хотите получить только документы, где все, поля не void. Теперь в одном запросе нет способа сделать это, так как нет никакого оператора для сравнения всех элементов в массиве таким образом и посмотреть, являются ли они одинаковыми. Но есть два подхода, которые вы можете предпринять:

Первый и, вероятно, не столь эффективный запрос к всем документам, содержащим элемент в forms, который имеет status of void. С полученным идентификатором документа вы можете выдать другой запрос, который возвращает документы, у которых не есть указанный идентификатор.

db.forms.find({ "forms.status": "void" },{ _id: 1})

db.forms.find({ _id: $not: { $in: [<Object1>,<Object2>,<Object3>,... ] } })

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

Другой подход заключается в использовании конвейера агрегации следующим образом:

db.forms.aggregate([
    { "$unwind": "$forms" },
    { "$group": { "_id": "$_id", "status": { "$addToSet": "$forms.status" }}},
    { "$unwind": "$status" },
    { "$sort": { "_id": 1, "status": -1 }},
    { "$group": { "_id": "$_id", "status": { "$first": "$status"}}},
    { "$match":{ "status": "closed" }}
])

Конечно, это приведет только к возврату _id для документов, которые соответствуют, но вы можете отправить запрос с помощью $in и вернуть все соответствующие документы. Это лучше, чем оператор исключения, используемый ранее, и теперь мы можем использовать индекс, чтобы избежать полного сканирования коллекции.

В качестве окончательного подхода и для оценки эффективности наилучшего вы можете снова изменить документ, чтобы на верхнем уровне вы сохраняли "статус" того, будет ли какое-либо поле в формах "пусто", или "закрыто". Таким образом, на верхнем уровне значение будет закрыто только в том случае, если все элементы были "закрыты" и "недействительны", если что-то было недействительным и т.д.

Этот последний будет означать дальнейшее изменение программы, и все изменения в элементах forms должны будут обновить это поле, а также сохранить "статус". Тем не менее, это самый эффективный способ найти нужные вам документы и заслуживает рассмотрения.


ИЗМЕНИТЬ

Помимо изменения документа для получения статуса мастера, самая быстрая форма запроса на пересмотренной структуре на самом деле:

db.forms.find({ "forms": { "$not": { "$elemMatch": { "status": "void" } } } })