Объединение значений вложенных полей, если они существуют

#mongodb #aggregation-framework

#mongodb #агрегация-фреймворк

Вопрос:

Я работаю над объединением значений вложенных полей, но не могу добиться успеха с помощью агрегации

Я написал код для объединения 5 вложенных полей, которые находятся в моей базе данных

Поле адреса в коллекции с именем ‘level’ выглядит следующим образом

 "address":{
    "flatNo" : "101",
    "street" : "amprapali",
    "city" : "jaipur",
    "zip" : "123456",
    "state" : "rajasthan",
    "country" : "INDIA"
}
 

Какой результат я хочу

 "address":"#101, amprapali, jaipur, 123456, rajasthan, INDIA"
 

Мой код:

 aggregation_pipeline = [ 
    {
        "$project":
        {
            "address": { "$concat": [ "#", "$address.flatNo", ", ", "$address.street", ", ", "$address.city", ", ", "$address.zip", ", ", "$address.state", ", ", "$address.country" ] }
        }


    },
    {
        "$out": "mod_collection"
    }
]
cursor = db['level'].aggregate(aggregation_pipeline, allowDiskUse=True)
cursor.close()
 

Я получаю этот ожидаемый результат, но если какое-либо из 5 полей отсутствует в исходной базе данных, поле адреса заполняется null

Мое требование — объединить все существующие значения полей адресов, разделенных символом ‘,’

Как я могу опустить null значения, если не все поля в разделе адрес существуют?

Ответ №1:

Здесь у вас есть несколько вариантов в зависимости от вашей версии MongoDB:

Для версий 3.4.4 и выше используйте $objectToArray и выбирайте поля динамически:

 db.collection.aggregate([
  { "$addFields": {
     "address": {
       "$let": {
         "vars": { 
           "address": { 
             "$reduce": {
               "input": { "$objectToArray": "$address" },
               "initialValue": "",
               "in": { "$concat": [ "$value", "$this.v", ", " ] }
             }
           }
         },
         "in": {
           "$concat": [ "#", 
             { "$substrCP": [
               "$address",
               0,
               { "$subtract": [{ "$strLenCP": "$address" }, 2] }
             ]}
           ]
         }
       }
     }
  }},
  { "$out": "newcollection" }
])
 

Для версии 3.4, предшествующей выпуску minor, используйте a $filter для удаления null значений

 db.collection.aggregate([
  { "$addFields": {
     "address": {
       "$let": {
         "vars": { 
           "address": { 
             "$reduce": {
               "input": { 
                 "$filter": {
                   "input": [
                     "$address.flatNo", "$address.street", "$address.city",
                     "$address.zip","$address.state","$address.country"
                   ],
                   "cond": { "$ne": [ "$this", null ] }
                 }
               },
               "initialValue": "",
               "in": { "$concat": [ "$value", "$this", ", " ] }
             }
           }
         },
         "in": {
           "$concat": [ "#", 
             { "$substrCP": [
               "$address",
               0,
               { "$subtract": [{ "$strLenCP": "$address" }, 2] }
             ]}
           ]
         }
       }
     }
  }},
  { "$out": "newcollection" }
])
 

До версии 3.4 у вас не было $reduce или $strLenCP , что позволяет «объединять» динамическим способом. Поэтому вы, вероятно, хотите сделать это в коде вместо этого:

 var batch = [];

db.collection.find({}, { _id: 0, address: 1 }).forEach(doc => {

   doc.address =  "#"   Object.keys(doc.address).map(k => doc.address[k]).join( ", ");
   batch.push(doc);

   if ( batch.length >= 1000 ) {
     db.newcollection.insertMany(batch);
     batch = [];
   }
})

if ( batch.length > 0 ) {
  db.newcollection.insertMany(batch);
   batch = [];
}
 

Или действительно длинный с $ifNull и $cond :

 db.collection.aggregate([
  { "$project": {
    "address": {
      "$concat": [
        "#",
        { "$ifNull": [ "$address.flatNo", ""] },
        { "$cond": [{ "$ifNull": [ "$address.flatNo", false ] }, ", ", ""] },
        { "$ifNull": [ "$address.street", "" ] },
        { "$cond": [{ "$ifNull": [ "$address.street", false ] }, ", ", ""] },
        { "$ifNull": [ "$address.city", "" ] },
        { "$cond": [{ "$ifNull": [ "$address.city", false ] }, ", ", ""] },
        { "$ifNull": [ "$address.zip", "" ] },
        { "$cond": [{ "$ifNull": [ "$address.zip", false ] }, ", ", ""] },
        { "$ifNull": [ "$address.state", "" ] },
        { "$cond": [{ "$ifNull": [ "$address.state", false ] }, ", ", ""] },
        { "$ifNull": [ "$address.country", "" ] }
      ]
    }
  }}
])
 

Подход с кодом был бы более чистым, но если вы записываете в другую коллекцию, то $ifNull , по $cond крайней мере, with позволяет использовать $out , чтобы избежать возврата всех документов «по проводам» перед их повторной записью.

Комментарии:

1. Я использую MongoDB 4.0, и ваш ответ для версий 3.4.4 и выше в значительной степени соответствовал моим требованиям. Спасибо!!

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

3. Мои извинения. Я новичок на этом сайте, и это был первый вопрос, который я опубликовал. Я принял ответ, как вы сказали, и в дальнейшем буду следовать соглашениям сайта. Спасибо.