MongoDB
 sql >> Cơ Sở Dữ Liệu >  >> NoSQL >> MongoDB

Triển khai tính năng tự động hoàn thành bằng cách sử dụng tìm kiếm MongoDB

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.




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Làm thế nào để truy vấn các đối tượng lồng nhau?

  2. Phần mềm trung gian lưu trữ phiên tốt nhất cho Express + MongoDB

  3. chuyển đổi cơ sở dữ liệu từ mysql sang mongoDb

  4. Quản lý viết nhật ký trong MongoDB

  5. Làm cách nào để sử dụng $ elemMatch trên phép chiếu tổng hợp?