tl; dr
Không có giải pháp dễ dàng cho những gì bạn muốn, vì các truy vấn thông thường không thể sửa đổi các trường mà chúng trả về. Có một giải pháp (sử dụng nội tuyến mapReduce bên dưới thay vì thực hiện đầu ra cho một bộ sưu tập), nhưng ngoại trừ cơ sở dữ liệu rất nhỏ, không thể thực hiện việc này trong thời gian thực.
Sự cố
Như đã viết, một truy vấn thông thường không thể thực sự sửa đổi các trường mà nó trả về. Nhưng có những vấn đề khác. Nếu bạn muốn thực hiện tìm kiếm regex trong thời gian nửa chừng, bạn sẽ phải lập chỉ mục tất cả trường, sẽ cần một lượng RAM không cân xứng cho tính năng đó. Nếu bạn không lập chỉ mục tất cả , một tìm kiếm regex sẽ gây ra quá trình quét bộ sưu tập, có nghĩa là mọi tài liệu sẽ phải được tải từ đĩa, điều này sẽ mất quá nhiều thời gian để tự động hoàn thành được thuận tiện. Hơn nữa, nhiều người dùng đồng thời yêu cầu tự động hoàn thành sẽ tạo ra tải trọng đáng kể trên phần phụ trợ.
Giải pháp
Vấn đề khá giống với một vấn đề mà tôi đã trả lời:Chúng tôi cần trích xuất mọi từ trong nhiều trường, loại bỏ các từ dừng và lưu các từ còn lại cùng với một liên kết đến (các) tài liệu tương ứng mà từ được tìm thấy trong một bộ sưu tập . Bây giờ, để nhận danh sách tự động hoàn thành, chúng tôi chỉ cần truy vấn danh sách từ được lập chỉ mục.
Bước 1:Sử dụng bản đồ / công việc thu nhỏ để trích xuất các từ
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"
}
)
Chạy bản đồ này Giảm so với ví dụ của bạn sẽ dẫn đến db.words
trông như thế này:
{ "_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") ] } }
Lưu ý rằng các từ riêng lẻ là _id
của các tài liệu. _id
trường được MongoDB lập chỉ mục tự động. Vì các chỉ số được cố gắng giữ trong RAM, chúng tôi có thể thực hiện một số thủ thuật để vừa tăng tốc độ tự động hoàn thành vừa giảm tải cho máy chủ.
Bước 2:Truy vấn tự động hoàn thành
Để tự động hoàn thành, chúng tôi chỉ cần các từ mà không cần liên kết đến tài liệu. Vì các từ được lập chỉ mục, chúng tôi sử dụng một truy vấn được bao phủ - một truy vấn chỉ được trả lời từ chỉ mục, thường nằm trong RAM.
Để phù hợp với ví dụ của bạn, chúng tôi sẽ sử dụng truy vấn sau để lấy các ứng cử viên cho chức năng tự động hoàn thành:
db.words.find({_id:/^can/},{_id:1})
cho chúng tôi kết quả
{ "_id" : "can" }
{ "_id" : "canada" }
{ "_id" : "candid" }
{ "_id" : "candle" }
{ "_id" : "candy" }
{ "_id" : "cannister" }
{ "_id" : "canteen" }
{ "_id" : "canvas" }
Sử dụng .explain()
, chúng tôi có thể xác minh rằng truy vấn này chỉ sử dụng chỉ mục.
{
"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
}
Lưu ý indexOnly:true
trường.
Bước 3:Truy vấn tài liệu thực tế
Mặc dù chúng tôi sẽ phải thực hiện hai truy vấn để có được tài liệu thực tế, vì chúng tôi đẩy nhanh quá trình tổng thể, trải nghiệm người dùng phải đủ tốt.
Bước 3.1:Lấy tài liệu của các từ words
bộ sưu tập
Khi người dùng chọn một lựa chọn của tự động hoàn thành, chúng tôi phải truy vấn tài liệu hoàn chỉnh của các từ để tìm các tài liệu có nguồn gốc từ được chọn để tự động hoàn thành.
db.words.find({_id:"canteen"})
mà sẽ dẫn đến một tài liệu như thế này:
{ "_id" : "canteen", "value" : { "documents" : [ ObjectId("553e435f20e6afc4b8aa0efb") ] } }
Bước 3.2:Lấy tài liệu thực tế
Với tài liệu đó, giờ đây chúng tôi có thể hiển thị một trang có kết quả tìm kiếm hoặc, như trong trường hợp này, chuyển hướng đến tài liệu thực tế mà bạn có thể lấy bằng cách:
db.yourCollection.find({_id:ObjectId("553e435f20e6afc4b8aa0efb")})
Ghi chú
Mặc dù cách tiếp cận này thoạt đầu có vẻ phức tạp (tốt, mapReduce is một chút), nó thực sự khá dễ dàng về mặt khái niệm. Về cơ bản, bạn đang giao dịch kết quả theo thời gian thực (dù sao thì bạn cũng sẽ không có được trừ khi bạn chi tiêu rất nhiều của RAM) cho tốc độ. Imho, đó là một việc tốt. Để làm cho giai đoạn mapReduce khá tốn kém trở nên hiệu quả hơn, việc triển khai bản đồ tăng dần (Incremental mapReduce) có thể là một cách tiếp cận - cải thiện bản đồ bị tấn công đã thừa nhận của tôi cũng có thể là một phương pháp khác.
Cuối cùng nhưng không kém phần quan trọng, cách này hoàn toàn là một cách hack khá xấu xí. Bạn có thể muốn tìm hiểu về đàn hồi hoặc lucene. Những sản phẩm đó phù hợp hơn nhiều với những gì bạn muốn.