Sau khi suy nghĩ về điều này một thời gian dài, tôi nghĩ rằng có thể thực hiện những gì bạn muốn. Tuy nhiên, nó không phù hợp với cơ sở dữ liệu quá lớn và tôi vẫn chưa tìm ra cách tiếp cận gia tăng. Nó thiếu gốc và các từ dừng phải được xác định theo cách thủ công.
Ý tưởng là sử dụng mapReduce để tạo một bộ sưu tập các từ tìm kiếm với các tham chiếu đến tài liệu gốc và trường bắt nguồn từ tìm kiếm. Sau đó, truy vấn thực tế cho tự động hoàn thành được thực hiện bằng cách sử dụng một tập hợp đơn giản sử dụng một chỉ mục và vì vậy sẽ khá nhanh.
Vì vậy, chúng tôi sẽ làm việc với ba tài liệu sau
{
"name" : "John F. Kennedy",
"address" : "Kenson Street 1, 12345 Footown, TX, USA",
"note" : "loves Kendo and Sushi"
}
và
{
"name" : "Robert F. Kennedy",
"address" : "High Street 1, 54321 Bartown, FL, USA",
"note" : "loves Ethel and cigars"
}
và
{
"name" : "Robert F. Sushi",
"address" : "Sushi Street 1, 54321 Bartown, FL, USA",
"note" : "loves Sushi and more Sushi"
}
trong một bộ sưu tập có tên là textsearch
.
Bản đồ / giai đoạn thu nhỏ
Những gì chúng tôi làm về cơ bản là chúng tôi sẽ xử lý từng từ trong một trong ba trường, xóa các từ dừng và số, đồng thời lưu từng từ bằng _id
của tài liệu và trường xảy ra trong bảng trung gian.
Mã được chú thích:
db.textsearch.mapReduce(
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"];
// This denotes the fields which should be processed
var fields = ["name","address","note"];
// For each field...
fields.forEach(
function(field){
// ... we split the field into single words...
var words = (document[field]).split(" ");
words.forEach(
function(word){
// ...and remove unwanted characters.
// Please note that this regex may well need to be enhanced
var cleaned = word.replace(/[;,.]/g,"")
// Next we check...
if(
// ...wether the current word is in the stopwords list,...
(stopwords.indexOf(word)>-1) ||
// ...is either a float or an integer...
!(isNaN(parseInt(cleaned))) ||
!(isNaN(parseFloat(cleaned))) ||
// or is only one character.
cleaned.length < 2
)
{
// In any of those cases, we do not want to have the current word in our list.
return
}
// Otherwise, we want to have the current word processed.
// Note that we have to use a multikey id and a static field in order
// to overcome one of MongoDB's mapReduce limitations:
// it can not have multiple values assigned to a key.
emit({'word':cleaned,'doc':document._id,'field':field},1)
}
)
}
)
},
function(key,values) {
// We sum up each occurence of each word
// in each field in every document...
return Array.sum(values);
},
// ..and write the result to a collection
{out: "searchtst" }
)
Chạy điều này sẽ dẫn đến việc tạo bộ sưu tập searchtst
. Nếu nó đã tồn tại, tất cả nội dung của nó sẽ được thay thế.
Nó sẽ trông giống như thế này:
{ "_id" : { "word" : "Bartown", "doc" : ObjectId("544b9811fd9270c1492f5835"), "field" : "address" }, "value" : 1 }
{ "_id" : { "word" : "Bartown", "doc" : ObjectId("544bb320fd9270c1492f583c"), "field" : "address" }, "value" : 1 }
{ "_id" : { "word" : "Ethel", "doc" : ObjectId("544b9811fd9270c1492f5835"), "field" : "note" }, "value" : 1 }
{ "_id" : { "word" : "FL", "doc" : ObjectId("544b9811fd9270c1492f5835"), "field" : "address" }, "value" : 1 }
{ "_id" : { "word" : "FL", "doc" : ObjectId("544bb320fd9270c1492f583c"), "field" : "address" }, "value" : 1 }
{ "_id" : { "word" : "Footown", "doc" : ObjectId("544b7e44fd9270c1492f5834"), "field" : "address" }, "value" : 1 }
[...]
{ "_id" : { "word" : "Sushi", "doc" : ObjectId("544bb320fd9270c1492f583c"), "field" : "name" }, "value" : 1 }
{ "_id" : { "word" : "Sushi", "doc" : ObjectId("544bb320fd9270c1492f583c"), "field" : "note" }, "value" : 2 }
[...]
Có một số điều cần lưu ý ở đây. Trước hết, một từ có thể có nhiều lần xuất hiện, ví dụ với "FL". Tuy nhiên, nó có thể nằm trong các tài liệu khác nhau, như trường hợp ở đây. Mặt khác, một từ cũng có thể có nhiều lần xuất hiện trong một trường của một tài liệu. Chúng tôi sẽ sử dụng điều này để có lợi cho mình sau này.
Thứ hai, chúng tôi có tất cả các trường, đáng chú ý nhất là word
trong một chỉ mục kết hợp cho _id
, điều này sẽ làm cho các truy vấn sắp tới khá nhanh. Tuy nhiên, điều này cũng có nghĩa là chỉ số sẽ khá lớn và - đối với tất cả các chỉ số - có xu hướng ngốn RAM.
Giai đoạn tổng hợp
Vì vậy, chúng tôi đã giảm danh sách các từ. Bây giờ chúng ta truy vấn một chuỗi (con), những gì chúng ta cần làm là tìm tất cả các từ bắt đầu bằng chuỗi mà người dùng đã nhập từ trước đến nay, trả về một danh sách các từ khớp với chuỗi đó. Để có thể thực hiện điều này và nhận được kết quả ở dạng phù hợp với chúng tôi, chúng tôi sử dụng tổng hợp.
Việc tổng hợp này sẽ diễn ra khá nhanh, vì tất cả các trường cần thiết để truy vấn đều là một phần của chỉ mục kết hợp.
Đây là tổng hợp có chú thích cho trường hợp người dùng nhập vào ký tự S
:
db.searchtst.aggregate(
// We match case insensitive ("i") as we want to prevent
// typos to reduce our search results
{ $match:{"_id.word":/^S/i} },
{ $group:{
// Here is where the magic happens:
// we create a list of distinct words...
_id:"$_id.word",
occurrences:{
// ...add each occurrence to an array...
$push:{
doc:"$_id.doc",
field:"$_id.field"
}
},
// ...and add up all occurrences to a score
// Note that this is optional and might be skipped
// to speed up things, as we should have a covered query
// when not accessing $value, though I am not too sure about that
score:{$sum:"$value"}
}
},
{
// Optional. See above
$sort:{_id:-1,score:1}
}
)
Kết quả của truy vấn này trông giống như thế này và phải khá dễ hiểu:
{
"_id" : "Sushi",
"occurences" : [
{ "doc" : ObjectId("544b7e44fd9270c1492f5834"), "field" : "note" },
{ "doc" : ObjectId("544bb320fd9270c1492f583c"), "field" : "address" },
{ "doc" : ObjectId("544bb320fd9270c1492f583c"), "field" : "name" },
{ "doc" : ObjectId("544bb320fd9270c1492f583c"), "field" : "note" }
],
"score" : 5
}
{
"_id" : "Street",
"occurences" : [
{ "doc" : ObjectId("544b7e44fd9270c1492f5834"), "field" : "address" },
{ "doc" : ObjectId("544b9811fd9270c1492f5835"), "field" : "address" },
{ "doc" : ObjectId("544bb320fd9270c1492f583c"), "field" : "address" }
],
"score" : 3
}
Điểm số 5 cho Sushi xuất phát từ việc từ Sushi xuất hiện hai lần trong trường ghi chú của một trong các tài liệu. Đây là hành vi dự kiến.
Mặc dù đây có thể là giải pháp của một người nghèo, cần được tối ưu hóa cho vô số trường hợp sử dụng có thể suy nghĩ và sẽ cần một bản đồ gia tăng hth.
Chỉnh sửa
Tất nhiên, người ta có thể bỏ $match
giai đoạn và thêm một $out
trong giai đoạn tổng hợp để có kết quả được xử lý trước:
db.searchtst.aggregate(
{
$group:{
_id:"$_id.word",
occurences:{ $push:{doc:"$_id.doc",field:"$_id.field"}},
score:{$sum:"$value"}
}
},{
$out:"search"
})
Bây giờ, chúng ta có thể truy vấn search
kết quả bộ sưu tập để tăng tốc độ. Về cơ bản, bạn đánh đổi kết quả thời gian thực để lấy tốc độ.
Chỉnh sửa 2 :Trong trường hợp sử dụng phương pháp xử lý trước, searchtst
bộ sưu tập của ví dụ sẽ bị xóa sau khi tổng hợp kết thúc để tiết kiệm cả dung lượng ổ đĩa và - quan trọng hơn - RAM quý giá.