MongoDB
 sql >> база данни >  >> NoSQL >> MongoDB

Агрегиране Натрупване на вътрешни обекти

Като бърза бележка, трябва да промените своята "стойност" поле вътре в "стойности" да бъде числова, тъй като в момента е низ. Но към отговора:

Ако имате достъп до $reduce от MongoDB 3.4, тогава всъщност можете да направите нещо подобно:

db.collection.aggregate([
  { "$addFields": {
     "cities": {
       "$reduce": {
         "input": "$cities",
         "initialValue": [],
         "in": {
           "$cond": {
             "if": { "$ne": [{ "$indexOfArray": ["$$value._id", "$$this._id"] }, -1] },
             "then": {
               "$concatArrays": [
                 { "$filter": {
                   "input": "$$value",
                   "as": "v",
                   "cond": { "$ne": [ "$$this._id", "$$v._id" ] }
                 }},
                 [{
                   "_id": "$$this._id",
                   "name": "$$this.name",
                   "visited": {
                     "$add": [
                       { "$arrayElemAt": [
                         "$$value.visited",
                         { "$indexOfArray": [ "$$value._id", "$$this._id" ] }
                       ]},
                       1
                     ]
                   }
                 }]
               ]
             },
             "else": {
               "$concatArrays": [
                 "$$value",
                 [{
                   "_id": "$$this._id",
                   "name": "$$this.name",
                   "visited": 1
                 }]
               ]
             }
           }
         }
       }
     },
     "variables": {
       "$map": {
         "input": {
           "$filter": {
             "input": "$variables",
             "cond": { "$eq": ["$$this.name", "Budget"] } 
           }
         },
         "in": {
           "_id": "$$this._id",
           "name": "$$this.name",
           "defaultValue": "$$this.defaultValue",
           "lastValue": "$$this.lastValue",
           "value": { "$avg": "$$this.values.value" }
         }
       }
     }
  }}
])

Ако имате MongoDB 3.6, можете да го почистите малко с $mergeObjects :

db.collection.aggregate([
  { "$addFields": {
     "cities": {
       "$reduce": {
         "input": "$cities",
         "initialValue": [],
         "in": {
           "$cond": {
             "if": { "$ne": [{ "$indexOfArray": ["$$value._id", "$$this._id"] }, -1] },
             "then": {
               "$concatArrays": [
                 { "$filter": {
                   "input": "$$value",
                   "as": "v",
                   "cond": { "$ne": [ "$$this._id", "$$v._id" ] }
                 }},
                 [{
                   "_id": "$$this._id",
                   "name": "$$this.name",
                   "visited": {
                     "$add": [
                       { "$arrayElemAt": [
                         "$$value.visited",
                         { "$indexOfArray": [ "$$value._id", "$$this._id" ] }
                       ]},
                       1
                     ]
                   }
                 }]
               ]
             },
             "else": {
               "$concatArrays": [
                 "$$value",
                 [{
                   "_id": "$$this._id",
                   "name": "$$this.name",
                   "visited": 1
                 }]
               ]
             }
           }
         }
       }
     },
     "variables": {
       "$map": {
         "input": {
           "$filter": {
             "input": "$variables",
             "cond": { "$eq": ["$$this.name", "Budget"] } 
           }
         },
         "in": {
           "$mergeObjects": [
             "$$this",
             { "values": { "$avg": "$$this.values.value" } }
           ]
         }
       }
     }
  }}
])

Но това е повече или по-малко същото, освен че запазваме additionalData

Ако се върнете малко преди това, винаги можете да $unwind "градовете" за натрупване:

db.collection.aggregate([
  { "$unwind": "$cities" },
  { "$group": {
     "_id": { 
       "_id": "$_id",
       "cities": {
         "_id": "$cities._id",
         "name": "$cities.name"
       }
     },
     "_class": { "$first": "$class" },
     "name": { "$first": "$name" },
     "startTimestamp": { "$first": "$startTimestamp" },
     "endTimestamp" : { "$first": "$endTimestamp" },
     "source" : { "$first": "$source" },
     "variables": { "$first": "$variables" },
     "visited": { "$sum": 1 }
  }},
  { "$group": {
     "_id": "$_id._id",
     "_class": { "$first": "$class" },
     "name": { "$first": "$name" },
     "startTimestamp": { "$first": "$startTimestamp" },
     "endTimestamp" : { "$first": "$endTimestamp" },
     "source" : { "$first": "$source" },
     "cities": {
       "$push": {
         "_id": "$_id.cities._id",
         "name": "$_id.cities.name",
         "visited": "$visited"
       }
     },
     "variables": { "$first": "$variables" },
  }},
  { "$addFields": {
     "variables": {
       "$map": {
         "input": {
           "$filter": {
             "input": "$variables",
             "cond": { "$eq": ["$$this.name", "Budget"] } 
           }
         },
         "in": {
           "_id": "$$this._id",
           "name": "$$this.name",
           "defaultValue": "$$this.defaultValue",
           "lastValue": "$$this.lastValue",
           "value": { "$avg": "$$this.values.value" }
         }
       }
     }
  }}
])

Всички връщат (почти) едно и също нещо:

{
        "_id" : ObjectId("5afc2f06e1da131c9802071e"),
        "_class" : "Traveler",
        "name" : "John Due",
        "startTimestamp" : 1526476550933,
        "endTimestamp" : 1526476554823,
        "source" : "istanbul",
        "cities" : [
                {
                        "_id" : "ef8f6b26328f-0663202f94faeaeb-1122",
                        "name" : "Cairo",
                        "visited" : 1
                },
                {
                        "_id" : "ef8f6b26328f-0663202f94faeaeb-3981",
                        "name" : "Moscow",
                        "visited" : 2
                }
        ],
        "variables" : [
                {
                        "_id" : "c8103687c1c8-97d749e349d785c8-9154",
                        "name" : "Budget",
                        "defaultValue" : "",
                        "lastValue" : "",
                        "value" : 3000
                }
        ]
}

Разбира се, първите два формуляра са най-оптималното нещо за правене, тъй като те просто работят „в рамките на“ един и същ документ по всяко време.

Оператори като $reduce позволяват изрази за "натрупване" върху масиви, така че можем да го използваме тук, за да запазим "намален" масив, който тестваме за уникалния "_id" стойност, използваща $indexOfArray за да видите дали вече има натрупан елемент, който съвпада. Резултат от -1 означава, че не е там.

За да изградим „намален масив“, вземаме „initialValue“ от [] като празен масив и след това добавете към него чрез $concatArrays . Целият този процес се решава чрез "троичния" $cond оператор, който разглежда "if" условие и "тогава" или се „присъединява“ към изхода на $filter върху текущата $$value за да изключите текущия индекс _id запис, разбира се с друг "масив", представляващ единствения обект.

За този „обект“ отново използваме $indexOfArray действително да получим съответстващия индекс, тъй като знаем, че елементът „е там“, и да го използваме, за да извлечем текущия „visited“ стойност от този запис чрез $arrayElemAt и $add към него, за да се увеличи.

В „друго“ в случай, че просто добавяме "масив" като "обект", който просто има "посетен" по подразбиране стойност 1 . Използването на тези два случая ефективно натрупва уникални стойности в масива за извеждане.

В последната версия ние просто $unwind масива и използвайте последователни $group етапи, за да "отчитаме" първо уникалните вътрешни записи и след това да "реконструираме масива" в подобна форма.

Използване на $unwind изглежда много по-просто, но тъй като това, което всъщност прави, е да вземе копие на документа за всеки запис в масив, тогава това всъщност добавя значителни разходи към обработката. В съвременните версии обикновено има оператори за масиви, което означава, че не е необходимо да използвате това, освен ако намерението ви не е да "натрупвате в документи". Така че, ако наистина трябва да $group на стойност на ключ от „вътрешността“ на масив, тогава това е мястото, където всъщност трябва да го използвате.

Що се отнася до "променливите" тогава можем просто да използваме $filter отново тук, за да получите съответстващия „Бюджет“ влизане. Правим това като вход към $map оператор, който позволява "преоформяне" на съдържанието на масива. Искаме това главно, за да можете да вземете съдържанието на „стойностите“ (след като направите всичко числово) и използвайте $avg оператор, който се доставя, че "нотацията на пътя на полето" се формира директно към стойностите на масива, защото всъщност може да върне резултат от такъв вход.

Това обикновено прави обиколката на почти ВСИЧКИ основни „оператори на масиви“ за тръбопровода за агрегиране (с изключение на операторите „set“) в рамките на един етап на конвейера.

Също така никога не забравяйте, че почти винаги искате да $match с обикновени оператори на заявки като „първия етап“ от всеки тръбопровод за агрегиране, за да изберете само документите, от които се нуждаете. В идеалния случай използване на индекс.

Заместници

Заместниците работят с документите в клиентския код. Като цяло не би било препоръчително, тъй като всички методи по-горе показват, че всъщност „намаляват“ съдържанието, върнато от сървъра, както обикновено е смисълът на „сървърните агрегации“.

„Може“ да е възможно поради „базирания на документ“ характер по-големите набори от резултати да отнемат значително повече време при използване на $unwind и клиентската обработка може да бъде опция, но бих я сметнал за много по-вероятна

По-долу е даден списък, който демонстрира прилагането на трансформация към потока на курсора, тъй като резултатите се връщат, правейки същото. Има три демонстрирани версии на трансформацията, показващи "точно" същата логика като по-горе, реализация с lodash методи за натрупване и "естествено" натрупване на Картата изпълнение:

const { MongoClient } = require('mongodb');
const { chain } = require('lodash');

const uri = 'mongodb://localhost:27017';
const opts = { useNewUrlParser: true };

const log = data => console.log(JSON.stringify(data, undefined, 2));

const transform = ({ cities, variables, ...d }) => ({
  ...d,
  cities: cities.reduce((o,{ _id, name }) =>
    (o.map(i => i._id).indexOf(_id) != -1)
      ? [
          ...o.filter(i => i._id != _id),
          { _id, name, visited: o.find(e => e._id === _id).visited + 1 }
        ]
      : [ ...o, { _id, name, visited: 1 } ]
  , []).sort((a,b) => b.visited - a.visited),
  variables: variables.filter(v => v.name === "Budget")
    .map(({ values, additionalData, ...v }) => ({
      ...v,
      values: (values != undefined)
        ? values.reduce((o,e) => o + e.value, 0) / values.length
        : 0
    }))
});

const alternate = ({ cities, variables, ...d }) => ({
  ...d,
  cities: chain(cities)
    .groupBy("_id")
    .toPairs()
    .map(([k,v]) =>
      ({
        ...v.reduce((o,{ _id, name }) => ({ ...o, _id, name }),{}),
        visited: v.length
      })
    )
    .sort((a,b) => b.visited - a.visited)
    .value(),
  variables: variables.filter(v => v.name === "Budget")
    .map(({ values, additionalData, ...v }) => ({
      ...v,
      values: (values != undefined)
        ? values.reduce((o,e) => o + e.value, 0) / values.length
        : 0
    }))

});

const natural = ({ cities, variables, ...d }) => ({
  ...d,
  cities: [
    ...cities
      .reduce((o,{ _id, name }) => o.set(_id,
        [ ...(o.has(_id) ? o.get(_id) : []), { _id, name } ]), new Map())
      .entries()
  ]
  .map(([k,v]) =>
    ({
      ...v.reduce((o,{ _id, name }) => ({ ...o, _id, name }),{}),
      visited: v.length
    })
  )
  .sort((a,b) => b.visited - a.visited),
  variables: variables.filter(v => v.name === "Budget")
    .map(({ values, additionalData, ...v }) => ({
      ...v,
      values: (values != undefined)
        ? values.reduce((o,e) => o + e.value, 0) / values.length
        : 0
    }))

});

(async function() {

  try {

    const client = await MongoClient.connect(uri, opts);

    let db = client.db('test');
    let coll = db.collection('junk');

    let cursor = coll.find().map(natural);

    while (await cursor.hasNext()) {
      let doc = await cursor.next();
      log(doc);
    }

    client.close();

  } catch(e) {
    console.error(e)
  } finally {
    process.exit()
  }

})()



  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Как да разреша:„MongoError:$where не е разрешено в този слой на атласа“?

  2. socket.io net::ERR_CONNECTION_CLOSED

  3. Четене, писане и съхраняване на JSON с Node на Heroku

  4. Добавете стойности на масив в MongoDB, където елементът не е в масива

  5. заявка за извличане на множество обекти в масив в mongodb