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

Terapkan fitur pelengkapan otomatis menggunakan pencarian MongoDB

tl;dr

Tidak ada solusi mudah untuk apa yang Anda inginkan, karena kueri normal tidak dapat mengubah bidang yang mereka kembalikan. Ada solusi (menggunakan inline mapReduce di bawah alih-alih melakukan output ke koleksi), tetapi kecuali untuk database yang sangat kecil, tidak mungkin melakukan ini secara realtime.

Masalahnya

Seperti yang tertulis, kueri normal tidak dapat benar-benar mengubah bidang yang dikembalikannya. Tapi ada masalah lain. Jika Anda ingin melakukan pencarian regex dalam setengah waktu yang layak, Anda harus mengindeks semua bidang, yang akan membutuhkan jumlah RAM yang tidak proporsional untuk fitur itu. Jika Anda tidak mengindeks semua bidang, pencarian regex akan menyebabkan pemindaian koleksi, yang berarti bahwa setiap dokumen harus dimuat dari disk, yang akan memakan terlalu banyak waktu untuk pelengkapan otomatis agar nyaman. Selain itu, beberapa pengguna simultan yang meminta pelengkapan otomatis akan membuat beban yang cukup besar di backend.

Solusinya

Masalahnya sangat mirip dengan yang sudah saya jawab:Kita perlu mengekstrak setiap kata dari beberapa bidang, menghapus kata-kata berhenti dan menyimpan kata-kata yang tersisa bersama-sama dengan tautan ke dokumen masing-masing kata itu ditemukan dalam koleksi . Sekarang, untuk mendapatkan daftar pelengkapan otomatis, kita cukup menanyakan daftar kata yang diindeks.

Langkah 1:Gunakan pekerjaan peta/kurangi untuk mengekstrak kata-kata

db.yourCollection.mapReduce(
  // Map function
  function() {

    // We need to save this in a local var as per scoping problems
    var document = this;

    // You need to expand this according to your needs
    var stopwords = ["the","this","and","or"];

    for(var prop in document) {

      // We are only interested in strings and explicitly not in _id
      if(prop === "_id" || typeof document[prop] !== 'string') {
        continue
      }

      (document[prop]).split(" ").forEach(
        function(word){

          // You might want to adjust this to your needs
          var cleaned = word.replace(/[;,.]/g,"")

          if(
            // We neither want stopwords...
            stopwords.indexOf(cleaned) > -1 ||
            // ...nor string which would evaluate to numbers
            !(isNaN(parseInt(cleaned))) ||
            !(isNaN(parseFloat(cleaned)))
          ) {
            return
          }
          emit(cleaned,document._id)
        }
      ) 
    }
  },
  // Reduce function
  function(k,v){

    // Kind of ugly, but works.
    // Improvements more than welcome!
    var values = { 'documents': []};
    v.forEach(
      function(vs){
        if(values.documents.indexOf(vs)>-1){
          return
        }
        values.documents.push(vs)
      }
    )
    return values
  },

  {
    // We need this for two reasons...
    finalize:

      function(key,reducedValue){

        // First, we ensure that each resulting document
        // has the documents field in order to unify access
        var finalValue = {documents:[]}

        // Second, we ensure that each document is unique in said field
        if(reducedValue.documents) {

          // We filter the existing documents array
          finalValue.documents = reducedValue.documents.filter(

            function(item,pos,self){

              // The default return value
              var loc = -1;

              for(var i=0;i<self.length;i++){
                // We have to do it this way since indexOf only works with primitives

                if(self[i].valueOf() === item.valueOf()){
                  // We have found the value of the current item...
                  loc = i;
                  //... so we are done for now
                  break
                }
              }

              // If the location we found equals the position of item, they are equal
              // If it isn't equal, we have a duplicate
              return loc === pos;
            }
          );
        } else {
          finalValue.documents.push(reducedValue)
        }
        // We have sanitized our data, now we can return it        
        return finalValue

      },
    // Our result are written to a collection called "words"
    out: "words"
  }
)

Menjalankan mapReduce ini terhadap contoh Anda akan menghasilkan db.words terlihat seperti ini:

    { "_id" : "can", "value" : { "documents" : [ ObjectId("553e435f20e6afc4b8aa0efb") ] } }
    { "_id" : "canada", "value" : { "documents" : [ ObjectId("553e435f20e6afc4b8aa0efb") ] } }
    { "_id" : "candid", "value" : { "documents" : [ ObjectId("553e435f20e6afc4b8aa0efb") ] } }
    { "_id" : "candle", "value" : { "documents" : [ ObjectId("553e435f20e6afc4b8aa0efb") ] } }
    { "_id" : "candy", "value" : { "documents" : [ ObjectId("553e435f20e6afc4b8aa0efb") ] } }
    { "_id" : "cannister", "value" : { "documents" : [ ObjectId("553e435f20e6afc4b8aa0efb") ] } }
    { "_id" : "canteen", "value" : { "documents" : [ ObjectId("553e435f20e6afc4b8aa0efb") ] } }
    { "_id" : "canvas", "value" : { "documents" : [ ObjectId("553e435f20e6afc4b8aa0efb") ] } }

Perhatikan bahwa setiap kata adalah _id dari dokumen. _id bidang diindeks secara otomatis oleh MongoDB. Karena indeks dicoba untuk disimpan dalam RAM, kita dapat melakukan beberapa trik untuk mempercepat pelengkapan otomatis dan mengurangi beban yang dimasukkan ke server.

Langkah 2:Kueri untuk pelengkapan otomatis

Untuk pelengkapan otomatis, kami hanya membutuhkan kata-kata, tanpa tautan ke dokumen. Karena kata-kata diindeks, kami menggunakan kueri tertutup – kueri yang dijawab hanya dari indeks, yang biasanya berada di RAM.

Untuk tetap menggunakan contoh Anda, kami akan menggunakan kueri berikut untuk mendapatkan kandidat pelengkapan otomatis:

db.words.find({_id:/^can/},{_id:1})

yang memberi kita hasilnya

    { "_id" : "can" }
    { "_id" : "canada" }
    { "_id" : "candid" }
    { "_id" : "candle" }
    { "_id" : "candy" }
    { "_id" : "cannister" }
    { "_id" : "canteen" }
    { "_id" : "canvas" }

Menggunakan .explain() metode, kami dapat memverifikasi bahwa kueri ini hanya menggunakan indeks.

        {
        "cursor" : "BtreeCursor _id_",
        "isMultiKey" : false,
        "n" : 8,
        "nscannedObjects" : 0,
        "nscanned" : 8,
        "nscannedObjectsAllPlans" : 0,
        "nscannedAllPlans" : 8,
        "scanAndOrder" : false,
        "indexOnly" : true,
        "nYields" : 0,
        "nChunkSkips" : 0,
        "millis" : 0,
        "indexBounds" : {
            "_id" : [
                [
                    "can",
                    "cao"
                ],
                [
                    /^can/,
                    /^can/
                ]
            ]
        },
        "server" : "32a63f87666f:27017",
        "filterSet" : false
    }

Perhatikan indexOnly:true lapangan.

Langkah 3:Kueri dokumen yang sebenarnya

Meskipun kami harus melakukan dua kueri untuk mendapatkan dokumen yang sebenarnya, karena kami mempercepat keseluruhan proses, pengalaman pengguna harus cukup baik.

Langkah 3.1:Dapatkan dokumen words koleksi

Saat pengguna memilih pilihan pelengkapan otomatis, kita harus menanyakan dokumen lengkap kata untuk menemukan dokumen asal kata yang dipilih untuk pelengkapan otomatis.

db.words.find({_id:"canteen"})

yang akan menghasilkan dokumen seperti ini:

{ "_id" : "canteen", "value" : { "documents" : [ ObjectId("553e435f20e6afc4b8aa0efb") ] } }

Langkah 3.2:Dapatkan dokumen yang sebenarnya

Dengan dokumen itu, kami sekarang dapat menampilkan halaman dengan hasil pencarian atau, seperti dalam kasus ini, mengarahkan ulang ke dokumen sebenarnya yang bisa Anda dapatkan dengan:

db.yourCollection.find({_id:ObjectId("553e435f20e6afc4b8aa0efb")})

Catatan

Meskipun pendekatan ini mungkin tampak rumit pada awalnya (well, mapReduce adalah sedikit), sebenarnya cukup mudah secara konseptual. Pada dasarnya, Anda memperdagangkan hasil waktu nyata (yang bagaimanapun juga tidak akan Anda dapatkan kecuali Anda menghabiskan lot RAM) untuk kecepatan. Imho, itu bagus. Untuk membuat fase mapReduce yang agak mahal menjadi lebih efisien, menerapkan Incremental mapReduce bisa menjadi pendekatan – meningkatkan mapReduce saya yang memang diretas mungkin adalah cara lain.

Last but not least, cara ini adalah hack yang agak jelek sama sekali. Anda mungkin ingin menggali elasticsearch atau lucene. Produk-produk itu jauh lebih cocok untuk apa yang Anda inginkan.




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Kunci dinamis setelah $group by

  2. mongodb.conf bind_ip =127.0.0.1 tidak berfungsi tetapi 0.0.0.0 berfungsi

  3. Bagaimana cara menjatuhkan database dengan Mongoose?

  4. MongoDB $acos

  5. cara mengonversi string ke nilai numerik di mongodb