MongoDB
 sql >> Teknologi Basis Data >  >> NoSQL >> MongoDB

Agregasi Akumulasi Objek Dalam

Sebagai catatan singkat, Anda perlu mengubah "value" bidang di dalam "values" menjadi numerik, karena saat ini string. Tapi pada jawabannya:

Jika Anda memiliki akses ke $reduce dari MongoDB 3.4, maka Anda sebenarnya dapat melakukan sesuatu seperti ini:

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" }
         }
       }
     }
  }}
])

Jika Anda memiliki MongoDB 3.6, Anda dapat membersihkannya sedikit dengan $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" } }
           ]
         }
       }
     }
  }}
])

Tapi kurang lebih sama kecuali kita menyimpan additionalData

Kembali sedikit sebelum itu, maka Anda selalu dapat $unwind "cities" untuk mengumpulkan:

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" }
         }
       }
     }
  }}
])

Semua mengembalikan (hampir) hal yang sama:

{
        "_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
                }
        ]
}

Dua formulir pertama tentu saja merupakan hal yang paling optimal untuk dilakukan karena hanya bekerja "di dalam" dokumen yang sama setiap saat.

Operator seperti $reduce izinkan ekspresi "akumulasi" pada array, sehingga kita dapat menggunakannya di sini untuk menyimpan array "dikurangi" yang kita uji untuk "_id" yang unik nilai menggunakan $indexOfArray untuk melihat apakah sudah ada akumulasi item yang cocok. Hasil dari -1 berarti tidak ada.

Untuk membuat "array tereduksi" kita mengambil "initialValue" dari [] sebagai array kosong dan kemudian menambahkannya melalui $concatArrays . Semua proses itu diputuskan melalui "ternary" $cond operator yang menganggap "if" kondisi dan "then" baik "bergabung" dengan output $filter pada $$value saat ini untuk mengecualikan indeks saat ini _id entri, dengan tentu saja "array" lain yang mewakili objek tunggal.

Untuk "objek" itu kita kembali menggunakan $indexOfArray untuk benar-benar mendapatkan indeks yang cocok karena kita tahu bahwa item "ada", dan menggunakannya untuk mengekstrak "visited" saat ini nilai dari entri tersebut melalui $arrayElemAt dan $add untuk meningkatkannya.

Di "else" kasus kita cukup menambahkan "array" sebagai "objek" yang hanya memiliki default "visited" nilai 1 . Menggunakan kedua kasus tersebut secara efektif mengakumulasikan nilai unik dalam larik ke output.

Di versi terakhir, kami hanya $unwind array dan gunakan berturut-turut $group tahapan untuk pertama-tama "menghitung" pada entri dalam yang unik, lalu "membangun ulang array" ke dalam bentuk yang serupa.

Menggunakan $unwind terlihat jauh lebih sederhana, tetapi karena apa yang sebenarnya dilakukan adalah mengambil salinan dokumen untuk setiap entri array, maka ini sebenarnya menambah banyak biaya untuk pemrosesan. Dalam versi modern umumnya ada operator array yang berarti Anda tidak perlu menggunakan ini kecuali niat Anda adalah untuk "berakumulasi di seluruh dokumen". Jadi, jika Anda benar-benar perlu $group pada nilai kunci dari "dalam" sebuah array, maka di situlah Anda benar-benar perlu menggunakannya.

Adapun "variables" maka kita cukup menggunakan $filter lagi di sini untuk mendapatkan "Budget" yang cocok pintu masuk. Kami melakukan ini sebagai masukan ke $map operator yang memungkinkan "membentuk kembali" konten array. Kami terutama menginginkannya agar Anda dapat mengambil konten "values" ( setelah Anda membuat semuanya numerik ) dan gunakan $avg operator, yang diberikan bahwa "notasi jalur bidang" langsung membentuk nilai larik karena sebenarnya ia dapat mengembalikan hasil dari input semacam itu.

Itu umumnya membuat tur hampir SEMUA "operator array" utama untuk pipa agregasi (tidak termasuk operator "set") semuanya dalam satu tahap pipa.

Juga jangan pernah lupa bahwa Anda selalu ingin $match dengan Operator Kueri reguler sebagai "tahap pertama" dari saluran agregasi apa pun untuk memilih dokumen yang Anda butuhkan. Idealnya menggunakan indeks.

Alternatif

Alternatif bekerja melalui dokumen dalam kode klien. Biasanya tidak direkomendasikan karena semua metode di atas menunjukkan bahwa mereka benar-benar "mengurangi" konten seperti yang dikembalikan dari server, seperti umumnya titik "agregasi server".

Ini "mungkin" dimungkinkan karena sifat "berbasis dokumen" bahwa kumpulan hasil yang lebih besar mungkin membutuhkan lebih banyak waktu menggunakan $unwind dan pemrosesan klien bisa menjadi opsi, tetapi saya akan menganggapnya jauh lebih mungkin

Di bawah ini adalah daftar yang menunjukkan penerapan transformasi ke aliran kursor saat hasil dikembalikan dengan melakukan hal yang sama. Ada tiga versi transformasi yang didemonstrasikan, menunjukkan "persis" logika yang sama seperti di atas, sebuah implementasi dengan lodash metode akumulasi, dan akumulasi "alami" pada Map implementasi:

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. Menyesuaikan nama kacang repositori Spring Data untuk digunakan dengan banyak sumber data

  2. Bagaimana cara kerja pengurutan dengan indeks di MongoDB?

  3. Mongodb upsert melempar DuplicateKeyException

  4. $proyek di $lookup mongodb

  5. Hasil Perulangan dengan Panggilan API Eksternal dan findOneAndUpdate