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

Mencocokkan ObjectId ke String untuk $graphLookup

Saat ini Anda menggunakan versi pengembangan MongoDB yang memiliki beberapa fitur yang diaktifkan yang diharapkan akan dirilis dengan MongoDB 4.0 sebagai rilis resmi. Perhatikan bahwa beberapa fitur dapat berubah sebelum rilis final, jadi kode produksi harus mengetahui hal ini sebelum Anda berkomitmen.

Mengapa $convert gagal di sini

Mungkin cara terbaik untuk menjelaskan ini adalah dengan melihat sampel Anda yang diubah tetapi mengganti dengan ObjectId nilai untuk _id dan "string" untuk mereka yang berada di bawah array:

{
  "_id" : ObjectId("5afe5763419503c46544e272"),
   "name" : "cinco",
   "children" : [ { "_id" : "5afe5763419503c46544e273" } ]
},
{
  "_id" : ObjectId("5afe5763419503c46544e273"),
  "name" : "quatro",
  "ancestors" : [ { "_id" : "5afe5763419503c46544e272" } ],
  "children" : [ { "_id" : "5afe5763419503c46544e277" } ]
},
{ 
  "_id" : ObjectId("5afe5763419503c46544e274"),
  "name" : "seis",
  "children" : [ { "_id" : "5afe5763419503c46544e277" } ]
},
{ 
  "_id" : ObjectId("5afe5763419503c46544e275"),
  "name" : "um",
  "children" : [ { "_id" : "5afe5763419503c46544e276" } ]
}
{
  "_id" : ObjectId("5afe5763419503c46544e276"),
  "name" : "dois",
  "ancestors" : [ { "_id" : "5afe5763419503c46544e275" } ],
  "children" : [ { "_id" : "5afe5763419503c46544e277" } ]
},
{ 
  "_id" : ObjectId("5afe5763419503c46544e277"),
  "name" : "três",
  "ancestors" : [
    { "_id" : "5afe5763419503c46544e273" },
    { "_id" : "5afe5763419503c46544e274" },
    { "_id" : "5afe5763419503c46544e276" }
  ]
},
{ 
  "_id" : ObjectId("5afe5764419503c46544e278"),
  "name" : "sete",
  "children" : [ { "_id" : "5afe5763419503c46544e272" } ]
}

Itu akan memberikan simulasi umum tentang apa yang Anda coba kerjakan.

Apa yang Anda coba adalah mengonversi _id nilai menjadi "string" melalui $project sebelum memasukkan $graphLookup panggung. Alasan kegagalan ini adalah saat Anda melakukan $project awal "di dalam" pipeline ini, masalahnya adalah sumber untuk $graphLookup di "from" opsi masih merupakan koleksi yang tidak berubah dan oleh karena itu Anda tidak mendapatkan detail yang benar pada iterasi "pencarian" berikutnya.

db.strcoll.aggregate([
  { "$match": { "name": "três" } },
  { "$addFields": {
    "_id": { "$toString": "$_id" }
  }},
  { "$graphLookup": {
    "from": "strcoll",
    "startWith": "$ancestors._id",
    "connectFromField": "ancestors._id",
    "connectToField": "_id",
    "as": "ANCESTORS_FROM_BEGINNING"
  }},
  { "$project": {
    "name": 1,
    "ANCESTORS_FROM_BEGINNING": "$ANCESTORS_FROM_BEGINNING._id"
  }}
])

Tidak cocok dengan "pencarian" oleh karena itu:

{
        "_id" : "5afe5763419503c46544e277",
        "name" : "três",
        "ANCESTORS_FROM_BEGINNING" : [ ]
}

"Menambal" masalah

Namun itu adalah masalah inti dan bukan kegagalan $convert atau itu alias itu sendiri. Agar ini benar-benar berfungsi, kita dapat membuat "tampilan" yang menampilkan dirinya sebagai kumpulan untuk masukan.

Saya akan melakukan ini sebaliknya dan mengonversi "string" menjadi ObjectId melalui $toObjectId :

db.createView("idview","strcoll",[
  { "$addFields": {
    "ancestors": {
      "$ifNull": [ 
        { "$map": {
          "input": "$ancestors",
          "in": { "_id": { "$toObjectId": "$$this._id" } }
        }},
        "$$REMOVE"
      ]
    },
    "children": {
      "$ifNull": [
        { "$map": {
          "input": "$children",
          "in": { "_id": { "$toObjectId": "$$this._id" } }
        }},
        "$$REMOVE"
      ]
    }
  }}
])

Namun, menggunakan "tampilan" berarti bahwa data dilihat secara konsisten dengan nilai yang dikonversi. Jadi agregasi berikut menggunakan tampilan:

db.idview.aggregate([
  { "$match": { "name": "três" } },
  { "$graphLookup": {
    "from": "idview",
    "startWith": "$ancestors._id",
    "connectFromField": "ancestors._id",
    "connectToField": "_id",
    "as": "ANCESTORS_FROM_BEGINNING"
  }},
  { "$project": {
    "name": 1,
    "ANCESTORS_FROM_BEGINNING": "$ANCESTORS_FROM_BEGINNING._id"
  }}
])

Mengembalikan output yang diharapkan:

{
    "_id" : ObjectId("5afe5763419503c46544e277"),
    "name" : "três",
    "ANCESTORS_FROM_BEGINNING" : [
        ObjectId("5afe5763419503c46544e275"),
        ObjectId("5afe5763419503c46544e273"),
        ObjectId("5afe5763419503c46544e274"),
        ObjectId("5afe5763419503c46544e276"),
        ObjectId("5afe5763419503c46544e272")
    ]
}

Memperbaiki masalah

Dengan semua itu, masalah sebenarnya di sini adalah Anda memiliki beberapa data yang "terlihat seperti" sebuah ObjectId nilai dan sebenarnya valid sebagai ObjectId , namun telah dicatat sebagai "string". Masalah dasar untuk semua yang berfungsi sebagaimana mestinya adalah bahwa kedua "jenis" tidak sama dan ini menghasilkan ketidakcocokan kesetaraan saat "penggabungan" dicoba.

Jadi perbaikan sebenarnya masih sama seperti dulu, yaitu melalui data dan memperbaikinya sehingga "string" sebenarnya juga ObjectId nilai-nilai. Ini kemudian akan cocok dengan _id kunci yang dimaksudkan untuk dirujuk, dan Anda menghemat banyak ruang penyimpanan karena ObjectId membutuhkan lebih sedikit ruang untuk disimpan daripada representasi string dalam karakter heksadesimal.

Menggunakan metode MongoDB 4.0, Anda "bisa" sebenarnya menggunakan "$toObjectId" untuk menulis koleksi baru, hampir sama dengan yang kita buat "tampilan" sebelumnya:

db.strcoll.aggregate([
  { "$addFields": {
    "ancestors": {
      "$ifNull": [ 
        { "$map": {
          "input": "$ancestors",
          "in": { "_id": { "$toObjectId": "$$this._id" } }
        }},
        "$$REMOVE"
      ]
    },
    "children": {
      "$ifNull": [
        { "$map": {
          "input": "$children",
          "in": { "_id": { "$toObjectId": "$$this._id" } }
        }},
        "$$REMOVE"
      ]
    }
  }}
  { "$out": "fixedcol" }
])

Atau tentu saja di mana Anda "perlu" untuk menyimpan koleksi yang sama, maka "loop and update" tradisional tetap sama seperti yang selalu diperlukan:

var updates = [];

db.strcoll.find().forEach(doc => {
  var update = { '$set': {} };

  if ( doc.hasOwnProperty('children') )
    update.$set.children = doc.children.map(e => ({ _id: new ObjectId(e._id) }));
  if ( doc.hasOwnProperty('ancestors') )
    update.$set.ancestors = doc.ancestors.map(e => ({ _id: new ObjectId(e._id) }));

  updates.push({
    "updateOne": {
      "filter": { "_id": doc._id },
      update
    }
  });

  if ( updates.length > 1000 ) {
    db.strcoll.bulkWrite(updates);
    updates = [];
  }

})

if ( updates.length > 0 ) {
  db.strcoll.bulkWrite(updates);
  updates = [];
}

Yang sebenarnya sedikit "palu godam" karena benar-benar menimpa seluruh array dalam sekali jalan. Bukan ide bagus untuk lingkungan produksi, tetapi cukup sebagai demonstrasi untuk tujuan latihan ini.

Kesimpulan

Jadi sementara MongoDB 4.0 akan menambahkan fitur "casting" ini yang memang bisa sangat berguna, maksud sebenarnya mereka tidak benar-benar untuk kasus seperti ini. Mereka sebenarnya jauh lebih berguna seperti yang ditunjukkan dalam "konversi" ke koleksi baru menggunakan pipa agregasi daripada kebanyakan kemungkinan penggunaan lainnya.

Sementara kita "bisa" buat "tampilan" yang mengubah tipe data untuk mengaktifkan hal-hal seperti $lookup dan $graphLookup untuk bekerja di mana data pengumpulan yang sebenarnya berbeda, ini sebenarnya hanya "pembalut" pada masalah sebenarnya karena tipe data seharusnya tidak berbeda, dan sebenarnya harus dikonversi secara permanen.

Menggunakan "tampilan" sebenarnya berarti bahwa alur agregasi untuk konstruksi perlu dijalankan secara efektif setiap waktu "koleksi" ( sebenarnya "tampilan" ) diakses, yang menciptakan overhead nyata.

Menghindari overhead biasanya merupakan tujuan desain, oleh karena itu memperbaiki kesalahan penyimpanan data tersebut sangat penting untuk mendapatkan kinerja nyata dari aplikasi Anda, daripada hanya bekerja dengan "kekuatan kasar" yang hanya akan memperlambat segalanya.

Skrip "konversi" yang jauh lebih aman yang menerapkan pembaruan "cocok" ke setiap elemen larik. Kode di sini memerlukan NodeJS v10.x dan rilis terbaru driver node MongoDB 3.1.x:

const { MongoClient, ObjectID: ObjectId } = require('mongodb');
const EJSON = require('mongodb-extended-json');

const uri = 'mongodb://localhost/';

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

(async function() {

  try {

    const client = await MongoClient.connect(uri);
    let db = client.db('test');
    let coll = db.collection('strcoll');

    let fields = ["ancestors", "children"];

    let cursor = coll.find({
      $or: fields.map(f => ({ [`${f}._id`]: { "$type": "string" } }))
    }).project(fields.reduce((o,f) => ({ ...o, [f]: 1 }),{}));

    let batch = [];

    for await ( let { _id, ...doc } of cursor ) {

      let $set = {};
      let arrayFilters = [];

      for ( const f of fields ) {
        if ( doc.hasOwnProperty(f) ) {
          $set = { ...$set,
            ...doc[f].reduce((o,{ _id },i) =>
              ({ ...o, [`${f}.$[${f.substr(0,1)}${i}]._id`]: ObjectId(_id) }),
              {})
          };

          arrayFilters = [ ...arrayFilters,
            ...doc[f].map(({ _id },i) =>
              ({ [`${f.substr(0,1)}${i}._id`]: _id }))
          ];
        }
      }

      if (arrayFilters.length > 0)
        batch = [ ...batch,
          { updateOne: { filter: { _id }, update: { $set }, arrayFilters } }
        ];

      if ( batch.length > 1000 ) {
        let result = await coll.bulkWrite(batch);
        batch = [];
      }

    }

    if ( batch.length > 0 ) {
      log({ batch });
      let result = await coll.bulkWrite(batch);
      log({ result });
    }

    await client.close();

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

})()

Menghasilkan dan menjalankan operasi massal seperti ini untuk tujuh dokumen:

{
  "updateOne": {
    "filter": {
      "_id": {
        "$oid": "5afe5763419503c46544e272"
      }
    },
    "update": {
      "$set": {
        "children.$[c0]._id": {
          "$oid": "5afe5763419503c46544e273"
        }
      }
    },
    "arrayFilters": [
      {
        "c0._id": "5afe5763419503c46544e273"
      }
    ]
  }
},
{
  "updateOne": {
    "filter": {
      "_id": {
        "$oid": "5afe5763419503c46544e273"
      }
    },
    "update": {
      "$set": {
        "ancestors.$[a0]._id": {
          "$oid": "5afe5763419503c46544e272"
        },
        "children.$[c0]._id": {
          "$oid": "5afe5763419503c46544e277"
        }
      }
    },
    "arrayFilters": [
      {
        "a0._id": "5afe5763419503c46544e272"
      },
      {
        "c0._id": "5afe5763419503c46544e277"
      }
    ]
  }
},
{
  "updateOne": {
    "filter": {
      "_id": {
        "$oid": "5afe5763419503c46544e274"
      }
    },
    "update": {
      "$set": {
        "children.$[c0]._id": {
          "$oid": "5afe5763419503c46544e277"
        }
      }
    },
    "arrayFilters": [
      {
        "c0._id": "5afe5763419503c46544e277"
      }
    ]
  }
},
{
  "updateOne": {
    "filter": {
      "_id": {
        "$oid": "5afe5763419503c46544e275"
      }
    },
    "update": {
      "$set": {
        "children.$[c0]._id": {
          "$oid": "5afe5763419503c46544e276"
        }
      }
    },
    "arrayFilters": [
      {
        "c0._id": "5afe5763419503c46544e276"
      }
    ]
  }
},
{
  "updateOne": {
    "filter": {
      "_id": {
        "$oid": "5afe5763419503c46544e276"
      }
    },
    "update": {
      "$set": {
        "ancestors.$[a0]._id": {
          "$oid": "5afe5763419503c46544e275"
        },
        "children.$[c0]._id": {
          "$oid": "5afe5763419503c46544e277"
        }
      }
    },
    "arrayFilters": [
      {
        "a0._id": "5afe5763419503c46544e275"
      },
      {
        "c0._id": "5afe5763419503c46544e277"
      }
    ]
  }
},
{
  "updateOne": {
    "filter": {
      "_id": {
        "$oid": "5afe5763419503c46544e277"
      }
    },
    "update": {
      "$set": {
        "ancestors.$[a0]._id": {
          "$oid": "5afe5763419503c46544e273"
        },
        "ancestors.$[a1]._id": {
          "$oid": "5afe5763419503c46544e274"
        },
        "ancestors.$[a2]._id": {
          "$oid": "5afe5763419503c46544e276"
        }
      }
    },
    "arrayFilters": [
      {
        "a0._id": "5afe5763419503c46544e273"
      },
      {
        "a1._id": "5afe5763419503c46544e274"
      },
      {
        "a2._id": "5afe5763419503c46544e276"
      }
    ]
  }
},
{
  "updateOne": {
    "filter": {
      "_id": {
        "$oid": "5afe5764419503c46544e278"
      }
    },
    "update": {
      "$set": {
        "children.$[c0]._id": {
          "$oid": "5afe5763419503c46544e272"
        }
      }
    },
    "arrayFilters": [
      {
        "c0._id": "5afe5763419503c46544e272"
      }
    ]
  }
}



  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Bagaimana cara menampilkan gambar base64 dalam reaksi?

  2. Bagaimana cara mengumpulkan jumlah di MongoDB untuk mendapatkan jumlah total?

  3. Bagaimana cara melihat bidang dokumen di mongo shell?

  4. Hapus gaya kaskade di Mongoose

  5. Pilih panjang string di mongodb