MongoDB
 sql >> база данни >  >> NoSQL >> MongoDB

Внедрете функция за автоматично довършване с помощта на търсене в MongoDB

tl;dr

Няма лесно решение за това, което искате, тъй като нормалните заявки не могат да променят полетата, които връщат. Има решение (използвайки по-долу mapReduce inline вместо да правите изход към колекция), но с изключение на много малки бази данни, не е възможно да направите това в реално време.

Проблемът

Както е написано, нормалната заявка не може наистина да промени полетата, които връща. Но има и други проблеми. Ако искате да направите търсене на регулярни изрази за половин прилично време, ще трябва да индексирате всички полета, които ще се нуждаят от непропорционално количество RAM за тази функция. Ако не искате да индексирате всички полета, търсенето на регулярни изрази би предизвикало сканиране на колекция, което означава, че всеки документ ще трябва да бъде зареден от диск, което би отнело твърде много време, за да бъде удобно автоматичното довършване. Освен това, множество едновременни потребители, които искат автоматично довършване, биха създали значително натоварване на бекенда.

Решението

Проблемът е доста подобен на този, на който вече отговорих:Трябва да извлечем всяка дума от множество полета, да премахнем спиращите думи и да запазим останалите думи заедно с връзка към съответния(ите) документ(и) думата е намерена в колекция . Сега, за да получим списък за автоматично довършване, ние просто правим заявка за индексирания списък с думи.

Стъпка 1:Използвайте задача за карта/намаляване, за да извлечете думите

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"
  }
)

Изпълнението на този mapReduce спрямо вашия пример ще доведе до db.words изглежда така:

    { "_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") ] } }

Имайте предвид, че отделните думи са _id на документите. _id полето се индексира автоматично от MongoDB. Тъй като индексите се опитват да се съхраняват в RAM, можем да направим няколко трика, за да ускорим автоматичното довършване и да намалим натоварването на сървъра.

Стъпка 2:Заявка за автоматично довършване

За автоматично довършване ни трябват само думите, без връзките към документите. Тъй като думите са индексирани, ние използваме покрита заявка – заявка, на която се отговаря само от индекса, който обикновено се намира в RAM.

За да се придържаме към вашия пример, ще използваме следната заявка, за да получим кандидатите за автоматично довършване:

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

което ни дава резултат

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

Използване на .explain() метод, можем да проверим, че тази заявка използва само индекса.

        {
        "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
    }

Обърнете внимание на indexOnly:true поле.

Стъпка 3:Потърсете действителния документ

Въпреки че ще трябва да направим две заявки, за да получим действителния документ, тъй като ускоряваме цялостния процес, потребителското изживяване трябва да е достатъчно добро.

Стъпка 3.1:Вземете документа на words колекция

Когато потребителят избере избор на автоматично довършване, трябва да потърсим пълния документ от думи, за да намерим документите, от които произлиза избраната за автоматично довършване дума.

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

което би довело до документ като този:

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

Стъпка 3.2:Вземете действителния документ

С този документ вече можем или да покажем страница с резултати от търсенето, или, както в този случай, да пренасочим към действителния документ, който можете да получите:

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

Бележки

Макар че този подход може да изглежда сложен в началото (е, mapReduce е малко), всъщност е доста лесно концептуално. По принцип търгувате с резултати в реално време (които така или иначе няма да имате, освен ако не похарчите много на RAM) за скорост. Имхо, това е добра сделка. За да направим доста скъпата фаза mapReduce по-ефективна, прилагането на Incremental mapReduce може да бъде подход – подобряването на моя хакнат mapReduce може да бъде друг.

Не на последно място, този начин е доста грозен хак като цяло. Може да искате да се поразровите в elasticsearch или lucene. Тези продукти имхо са много, много по-подходящи за това, което искате.




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Как мога да използвам променлива с регулярни изрази в заявка за MongoDB

  2. Как да инсталирате MongoDB

  3. Контролен списък за разработка и операции за MongoDB

  4. Могат ли строги JSON $dates да се използват в заявка на MongoDB?

  5. Spring data mongodb - Опцията „курсор“ е задължителна