test data


> db.restaurant.insert({"shop_name": "버거킹", "menu": "햄버거 감튀 콜라 와퍼 ", "location":"판교"})
WriteResult({ "nInserted" : 1 })
> db.restaurant.insert({"shop_name": "맥도날드", "menu": "햄버거 감튀 콜라", "location":"판교역 근처"})
WriteResult({ "nInserted" : 1 })
> db.restaurant.insert({"shop_name": "맥도날드2호점", "menu": "햄버거 감튀 콜라", "location":"판교역 근처"})
WriteResult({ "nInserted" : 1 })
> db.restaurant.insert({"shop_name": "셀렉티", "menu": "수제햄버거 감튀 콜라", "location":"상암"})
WriteResult({ "nInserted" : 1 })
> db.restaurant.insert({"shop_name": "버거킹2호점", "menu": "햄버거 감튀 콜라 와퍼 ", "location":"판교"})
WriteResult({ "nInserted" : 1 })

text index 생성


> db.restaurant.createIndex({"shop_name":"text"})
{
    "createdCollectionAutomatically" : false,
    "numIndexesBefore" : 1,
    "numIndexesAfter" : 2,
    "ok" : 1
}

> db.restaurant.createIndex({"shop_name":"text","menu":"text"})
{
    "ok" : 0,
    "errmsg" : "Index: { v: 2, key: { _fts: \"text\", _ftsx: 1 }, name: \"shop_name_text_menu_text\", ns: \"test.restaurant\", weights: { menu: 1, shop_name: 1 }, default_language: \"english\", language_override: \"language\", textIndexVersion: 3 } already exists with different options: { v: 2, key: { _fts: \"text\", _ftsx: 1 }, name: \"shop_name_text\", ns: \"test.restaurant\", weights: { shop_name: 1 }, default_language: \"english\", language_override: \"language\", textIndexVersion: 3 }",
    "code" : 85,
    "codeName" : "IndexOptionsConflict"
}

> db.restaurant.dropIndex("shop_name_text")
{ "nIndexesWas" : 2, "ok" : 1 }
>
> db.restaurant.createIndex({"shop_name":"text","menu":"text"})

> db.restaurant.getIndexes()
[
    {
        "v" : 2,
        "key" : {
            "_fts" : "text",
            "_ftsx" : 1
        },
        "name" : "shop_name_text_menu_text",
        "ns" : "test.restaurant",
        "weights" : {
            "menu" : 1,
            "shop_name" : 1
        },
        "default_language" : "english",
        "language_override" : "language",
        "textIndexVersion" : 3
    }
]

=> text index 는 컬렉션 당 최대 한개까지 가능
shop_name, menu field 에 대해 text index 생성 완료
아래와 같이 모든 필드에 대해 인덱스 생성도 가능하지만 dml 성능에 악영향이 있을 수 있음

> db.restaurant.createIndex({"$**":"text"})
> db.restaurant.getIndexes()
    {
        "v" : 2,
        "key" : {
            "_fts" : "text",
            "_ftsx" : 1
        },
        "name" : "$**_text",
        "ns" : "test.restaurant",
        "weights" : {
            "$**" : 1
        },
        "default_language" : "english",
        "language_override" : "language",
        "textIndexVersion" : 3
    }

조회

> db.restaurant.find({$text: {$search: "버거킹"}},{score:{$meta: "textScore"}}).sort({score:{$meta:"textScore"}})
{ "_id" : ObjectId("5dbd3647af77c89ebe0d8ed3"), "shop_name" : "버거킹", "menu" : "햄버거 감튀 콜라 와퍼 ", "location" : "판교", "score" : 1.1 }

=> 버거킹을 조회했는데 "shop_name":"버거킹2호점" 은 조회안됨


> db.restaurant.find({$text: {$search: "햄버거"}},{score:{$meta: "textScore"}}).sort({score:{$meta:"textScore"}})
{ "_id" : ObjectId("5dbd3647af77c89ebe0d8ed4"), "shop_name" : "맥도날드", "menu" : "햄버거 감튀 콜라", "location" : "판교역 근처", "score" : 0.6666666666666666 }
{ "_id" : ObjectId("5dbd3647af77c89ebe0d8ed5"), "shop_name" : "맥도날드2호점", "menu" : "햄버거 감튀 콜라", "location" : "판교역 근처", "score" : 0.6666666666666666 }
{ "_id" : ObjectId("5dbd3647af77c89ebe0d8ed3"), "shop_name" : "버거킹", "menu" : "햄버거 감튀 콜라 와퍼 ", "location" : "판교", "score" : 0.625 }
{ "_id" : ObjectId("5dbd3648af77c89ebe0d8ed7"), "shop_name" : "버거킹2호점", "menu" : "햄버거 감튀 콜라 와퍼 ", "location" : "판교", "score" : 0.625 }

=> 햄버거를 조회했는데 "수제햄버거"를 파는 셀렉티 는 조회안됨
mongodb에서 한글에 대해서 ngram, 형태소분석을 기본적으로 제공하지 않고 구분자 기반(공백문자) 기준으로 인덱싱 처리함
한 단어의 부분에 대해서도 검색을 가능하게 하려면 ngram full text index 기능을 사용해야함

> db.restaurant.find({$text: {$search: "콜라 와퍼"}},{score:{$meta: "textScore"}}).sort({score:{$meta:"textScore"}})
{ "_id" : ObjectId("5dbd3647af77c89ebe0d8ed3"), "shop_name" : "버거킹", "menu" : "햄버거 감튀 콜라 와퍼 ", "location" : "판교", "score" : 1.25 }
{ "_id" : ObjectId("5dbd3648af77c89ebe0d8ed7"), "shop_name" : "버거킹2호점", "menu" : "햄버거 감튀 콜라 와퍼 ", "location" : "판교", "score" : 1.25 }
{ "_id" : ObjectId("5dbd3647af77c89ebe0d8ed4"), "shop_name" : "맥도날드", "menu" : "햄버거 감튀 콜라", "location" : "판교역 근처", "score" : 0.6666666666666666 }
{ "_id" : ObjectId("5dbd3647af77c89ebe0d8ed5"), "shop_name" : "맥도날드2호점", "menu" : "햄버거 감튀 콜라", "location" : "판교역 근처", "score" : 0.6666666666666666 }
{ "_id" : ObjectId("5dbd3647af77c89ebe0d8ed6"), "shop_name" : "셀렉티", "menu" : "수제햄버거 감튀 콜라", "location" : "상암", "score" : 0.6666666666666666 }
>

=> "콜라 와퍼" 를 검색했더니 와퍼를 팔지 않는 맥도날드,맥도날드2호점,셀렉티도 조회됨
"콜라 와퍼" => 구분자 기반(공백문자) 기준으로 인덱싱 되어 "콜라", "와퍼" 두개로 조회되기 때문

> db.restaurant.find({$text: {$search: "\"콜라 와퍼\""}},{score:{$meta: "textScore"}}).sort({score:{$meta:"textScore"}})
{ "_id" : ObjectId("5dbd3647af77c89ebe0d8ed3"), "shop_name" : "버거킹", "menu" : "햄버거 감튀 콜라 와퍼 ", "location" : "판교", "score" : 1.25 }
{ "_id" : ObjectId("5dbd3648af77c89ebe0d8ed7"), "shop_name" : "버거킹2호점", "menu" : "햄버거 감튀 콜라 와퍼 ", "location" : "판교", "score" : 1.25 }

=>입력한 문장 자체를 포함한 것을 찾을 땐 \"검색어\"

> db.restaurant.find({$text: {$search: "햄버거 -와퍼"}},{score:{$meta: "textScore"}}).sort({score:{$meta:"textScore"}})
{ "_id" : ObjectId("5dbd3647af77c89ebe0d8ed4"), "shop_name" : "맥도날드", "menu" : "햄버거 감튀 콜라", "location" : "판교역 근처", "score" : 0.6666666666666666 }
{ "_id" : ObjectId("5dbd3647af77c89ebe0d8ed5"), "shop_name" : "맥도날드2호점", "menu" : "햄버거 감튀 콜라", "location" : "판교역 근처", "score" : 0.6666666666666666 }

=> 특정 검색어를 포함하지 않는 것 -제외어

> db.restaurant.find({$text: {$search:/맥도/}}, {score:{$meta:"textScore"}}).sort({score:{$meta:"textScore"}})
Error: error: {
    "ok" : 0,
    "errmsg" : "\"$search\" had the wrong type. Expected string, found regex",
    "code" : 14,
    "codeName" : "TypeMismatch"
}

=> like 검색은 안됨

가중치 설정

> db.restaurant.createIndex({"$**":"text"}, {"weight":{"shop_name":3,"location":1}})

=> weight 3인 shop_name 은 location 보다 3배 더 많은 score를 받음, default weight 1

index partition

> db.restaurant.createIndex({"location":1, "$**":"text"})

> db.restaurant.find({"location":"판교",$text:{$search:"버거킹"}})
{ "_id" : ObjectId("5dbd3647af77c89ebe0d8ed3"), "shop_name" : "버거킹", "menu" : "햄버거 감튀 콜라 와퍼 ", "location" : "판교" }

=> rdbms에서의 partition pruning 과 동일함 (= indexPrefix)

"winningPlan" : {
            "stage" : "TEXT",
            "indexPrefix" : {
                "location" : "판교"
            },
            "indexName" : "location_1_$**_text",

index 생성 시 partition field 에 1을 설정하고 query에 파티션 키를 꼭 명시해야함

> db.restaurant.find({$text:{$search:"버거킹"}})
Error: error: {
    "ok" : 0,
    "errmsg" : "error processing query: ns=test.restaurantTree: TEXT : query=버거킹, language=english, caseSensitive=0, diacriticSensitive=0, tag=NULL\nSort: {}\nProj: {}\n planner returned error :: caused by :: failed to use text index to satisfy $text query (if text index is compound, are equality predicates given for all prefix fields?)",
    "code" : 2,
    "codeName" : "BadValue"
}

=> partition key 조건 미포함시 에러