복붙노트

[MONGODB] MongoDB의 검색을 사용하여 자동 완성 기능을 구현

MONGODB

MongoDB의 검색을 사용하여 자동 완성 기능을 구현

나는 형태의 문서 MongoDB를 수집해야

{
    "id": 42,
    "title": "candy can",
    "description": "canada candy canteen",
    "brand": "cannister candid",
    "manufacturer": "candle canvas"
}

나는 ID를 제외한 분야에서 일치하여 입력 검색어에 따라 자동 완성 기능을 구현해야합니다. 입력 용어 수 있으면 예를 들어, I는 문서와 일치하는 모든 단어를 반환해야

{ hints: ["candy", "can", "canada", "canteen", ...]

이 질문 보았다하지만 도움이되지 않았다. 나는 또한 어떻게 MongoDB를 텍스트 검색에서 여러 필드를 추출 일치하는 토큰 또는 추출 일치하는 토큰에서 정규식 검색을 수행하는 검색을 시도했지만 도움을 찾을 수 없습니다.

해결법

  1. ==============================

    1.일반 쿼리 필드를 수정할 수 없기 때문에 당신이 원하는에 대한 쉬운 해결책이 없다, 그들은 돌아갑니다. 이 (맵리 듀스 아래를 사용하여 인라인 대신 컬렉션에 출력을하는) 솔루션이 있지만, 매우 작은 데이터베이스를 제외하고는 실시간으로이 작업을 수행 할 수 없습니다.

    일반 쿼리 필드를 수정할 수 없기 때문에 당신이 원하는에 대한 쉬운 해결책이 없다, 그들은 돌아갑니다. 이 (맵리 듀스 아래를 사용하여 인라인 대신 컬렉션에 출력을하는) 솔루션이 있지만, 매우 작은 데이터베이스를 제외하고는 실시간으로이 작업을 수행 할 수 없습니다.

    기록으로, 정상적인 쿼리는 정말 반환 필드를 수정할 수 없습니다. 그러나 다른 문제가있다. 당신이 중간 알맞은 시간에 정규식 검색을 수행하려면 해당 기능에 대한 RAM의 불균형 양을 필요로하는 모든 분야, 인덱스 것이다. 당신은 인덱스 모든 필드, 정규식 검색을 편리하게하기 위해 자동 완성을 위해 너무 많은 시간이 걸릴 것 모든 문서가 디스크에서로드해야하는 것을 의미 수집 스캔, 원인이하지 않을 경우. 또한, 자동 완성을 요청하는 다수의 동시 사용자가 백엔드에 상당한 부하를 만들 것입니다.

    우리는 중지 단어를 제거, 여러 필드의 모든 단어를 추출해야하고 각각의 문서 (들)에 대한 링크와 함께 남아있는 단어 저장 단어가 컬렉션의 발견 : 문제는 하나 매우 유사 이미 대답 한 것입니다 . 이제 자동 완성 목록을 얻기 위해, 우리는 단순히 인덱스 단어 목록을 조회.

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

    다음과 같이 귀하의 예제에 대해이 맵리 듀스는 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에 보관하려고 노력하고 있기 때문에, 우리는 자동 완성까지 모두 속도에 약간의 트릭을하고 서버에 넣어 부하를 줄일 수 있습니다.

    자동 완성을 위해, 우리는 단지 문서에 링크하지 않고, 말을해야합니다. 단어를 색인하고 있기 때문에, 우리는 커버 쿼리를 사용 - 쿼리는 일반적으로 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 참고 : 사실 필드.

    우리는 전체 과정을 빠르게하기 때문에, 실제 문서를 얻기 위해 두 개의 쿼리를해야 할 것이기는하지만, 사용자 경험을 충분히해야한다.

    사용자가 자동 ​​완성의 선택을 선택하면, 우리는 자동 완성을 위해 선택된 단어에서 유래 문서를 찾기 위해 단어의 전체 문서를 조회 할 수 있습니다.

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

    이는이 같은 문서가 발생할 것입니다 :

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

    이 경우, 당신은에 의해 얻을 수있는 실제 문서로 리디렉션처럼 해당 문서, 우리는 이제 하나, 검색 결과 페이지를 표시하거나 할 수 있습니다 :

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

    이 방법은 (물론, 맵리 듀스는 비트가) 처음에는 복잡하게 보일 수도 있지만, 그것은 아주 쉽게 개념적으로 실제입니다. 기본적으로, 당신은 속도를 (당신이 RAM을 많이 소비하지 않는 어쨌든 필요가 없습니다) 실시간 결과에 거래되고있다. 좋은 거래입니다 이럴. 내 인정 하듯이 해킹 맵리 듀스가 아니라 다른이 될 수 있습니다 개선 - 증분 맵리 듀스는 접근 방식이 될 수 구현, 오히려 비용이 많이 드는 맵리 듀스 단계를보다 효율적으로하기 위해.

    마지막으로,이 방법은 모두 다소 추한 해킹입니다. 당신은 elasticsearch 또는 루씬으로 발굴 할 수 있습니다. 많이 있습니다 이럴 이들 제품은 훨씬 더 당신이 원하는에 적합합니다.

  2. from https://stackoverflow.com/questions/29892947/implement-auto-complete-feature-using-mongodb-search by cc-by-sa and MIT license