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.