ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 검색기능 (URL query string, indexing, Search index)
    Node.js,MongoDB 2022. 4. 13. 13:42

    list.ejs파일에 이렇게 input과 검색버튼을 만들어주고, query string에 대해 알아보자.

    <div class="container input-group mb-2">
          <input type="text" class="form-control" id="search-input">
          <button class="input-group-append btn btn-danger" id="search">검색</button>
    </div>
    먼저, 검색버튼을 누르면 서버에서 post요청을 해야한다.
    그리고, 서버는 DB에서 데이터 꺼내줌 collection().findOne() 또는 collection().find().toArray()로 꺼낸다.
    근데 post요청 말고, 새로운방법으로 get요청으로 서버로 데이터 전달가능하다.
    get은 url만 잘 보내면되는데, url에 정보를 담아서 보내면 된다. 이걸 query string라고 부름
     
    query string 작성 방법은 url뒤에 ?데이터이름=데이터값 이렇게 쓰며, 데이터이름으로 데이터값이 서버로 전송된다.
    //검색버튼 누르면 서버로 GET요청을 한다. +query string으로 정보전달도 한다.
    $('#search').click(function(){
            var 입력한값 = $('#search-input').val();
            window.location.replace('/search?value='+입력한값) 
            //url을 replace안에있는걸로 갈아치워주세용 이건 실은 GET요청이랑 똑같음
    })​
    search버튼을 찾아서 클릭해주면 input태그에 입력한 값을 변수에 담아주고, window.location.replace라는 url을 바꿔주는 문법에 query string을 작성해준다. /search?value= 변수이름 이렇게 쓰면, input태그에 적힌 값을 value라는 이름으로 보내주게 된다.
     
    js에서 query string를 쉽게 할 수 있다.
    var 자료 = { 이름 : '값', 이름2 : '값2' }
    $.param(자료) //이름=값&이름2=값2
    
    $(폼태그 찾고).serialize()
    //이렇게 하면 폼태그 안에 있는 모든 input을 query stirng 으로 변환해준다. 대신 name이 input에 각각 있어야함
     
    이제, server.js에서 query string을 꺼내보자.
    app.get('/search', (요청, 응답) => {
        console.log(요청.query.value)//요청.query에 value값이 오브젝트로 담겨있음.
        //이제 요청받았으니까 input태그에 적힌 값을 제목으로 가진 게시물을 DB에서 찾아서 보내준다.
        db.collection('post').find({제목 : 요청.query.value}).toArray((에러, 결과)=>{
            console.log(결과);
            응답.render('search.ejs',{posts : 결과});
        })
    })

    요청.query를 입력하면 value라는 이름으로 검색한 값이 오브젝트 형식으로 담기게 된다. 그러므로, .value를 붙여주면 해당 값만 나오게된다. 

    이제, find.toArray를 이용해서 제목이 검색결과인값을 찾아서 search.ejs라는 파일을 만들고,

    <h4 class="ml-2 my-3 text-center">검색결과 페이지 입니다.</h4>
    
        <div class="container input-group mb-2">
          <input type="text" class="form-control" id="search-input">
          <button class="input-group-append btn btn-danger" id="search">검색</button>
        </div>
        
    
        <div class="container">
          <ul class="list-group">
            <% for (var i = 0; i < posts.length; i++) { %>
              <li class="list-group-item">
                <p>글번호 : <%= posts[i]._id %></p>
                <h4 class="title" data-id="<%= posts[i]._id %>">할일 제목 : <%= posts[i].제목 %></h4>
                <p>할일 마감날짜 : <%= posts[i].날짜 %></p>
                <button class="btn btn-danger delete" data-id="<%= posts[i]._id %>">삭제</button>
                <button class="btn btn-primary edit" data-id="<%= posts[i]._id %>">수정</button>
              </li>
            <% } %>
          </ul>
        </div>

    이런 식으로, 결과를 보여주는 페이지를 만들면 된다. 이런 방법을 full scan이라고 한다.

    만약, 글에 글쓰기, 장문글쓰기, 단문글쓰기 이렇게 글쓰기라는 단어가 여러개 있다면 문제가 생길 수 있다.

    정확히 일치하는 것만 찾아주는 문제가 생기는데,

    1. 정규식을 사용할 수 있다. 

    js 에선 /abc/라고 쓰면 abc가 포함되어 있는 문자라는 뜻인데

    db.collection('post').find({제목 : /요청.query.value/}).toArray((에러, 결과)=>{
            console.log(결과);
            응답.render('search.ejs',{posts : 결과});
    })

    이런 식으로 사용할 수 있다. 하지만 find()함수로 게시물이 10만개 될 때 다 찾는건 오래걸린다. 그래서,

    2. indexing을 해주면 된다.

    컴퓨터는 id가 1부터 100까지 있을 때, 70번좀 찾아봐 하면 1부터 순서대로 찾는다고 한다. 근데 비효율적이라

    Binary Search라는 개념을 적용시킨다. 반을 쪼개가면서 찾는것을 의미하는데, 바이너리서치를 적용하려면 미리 숫자순 정렬이 되어 있어야 한다. 몽고DB특성상 _id순으로 정렬이 미리 되어 있어서 쉽게 찾을 수 있다. 따라서 제목을 미리 정렬해두면(indexing)해두면 DB가 알아서 바이너리설치를 해준다.

    즉, index는 기본collection을 정렬해놓은 사본을 뜻한다.

    이제, mongoDB에서 index를 생성해보자.

    이렇게 indexes를 클릭 후, 오른쪽에 create index클릭 후,

    이렇게 제목을 기준으로 문자자료면 text, _id를 기준으로 숫자자료면 1 or -1을 입력해주면 된다.

     

    여기서, 문자자료를 index할 땐 한꺼번에 해야한다. 그리고 DB조작은 원래 터미널로 한다.

    이제 검색기능을 만들어보자

    find({ $text: { $search : 요청.query.value } })

    find에 이렇게 text와 search를 이용해 text index를 만들면된다.

    이러면 글쓰기만 검색해도 글쓰기글자가 포함된 모든 게시물이 나오고,

    이닦기 글쓰기 이렇게 검색하면 두개중에 하나라도 들어간 게시물이 나오는 or검색이 가능하다.

    그리고 이닦기 -글쓰기 이렇게 써서 글쓰기를 제외시키는 -제외기능도 가능하고,

    "이닦기 글쓰기" 이렇게 써서 정확히 일치하는 것만 검색할 수 있다.

     

    하지만 문제점도 있다. 띄어쓰기를 기준으로 단어를 저장하므로

    "글쓰기입니다만 글쓰기죠 글쓰기합니다" 이런 게시물이 있을 때 글쓰기라고 검색하면 나오지않는다.

    해결책으론,

    1. text index 안쓰고,

    검색할 문서의 양을 한 1000개중에 찾아라 이런 식으로 제한을 두면 속도를 신경쓰지 않아도 된다.

    예를 들면, 오늘부터 일주일간 발행된 게시물에서만 찾아라 이런 예이다. 여기서 날짠 new Date()라는 함수를 이용해서 사용해야한다.

    2. text index를 만들 때 다르게 만들 수 있다.

    몽고디비를 하드에 설치해서 셋팅하던가 다른 서비스를 사용해야한다. 띄어쓰기 단위로 indexing을 금지하면 된다.

    글자 두개 단위로 indexing해봐라 라는 nGram이란게 있다.

    3. 몽고디비 아틀라스에 다르게 만들 수 있다.(search index만들기)

    이렇게 search에 들어가서 create search index를 눌러준다. 첫번째는 next누르고, 두번째에 indexName을 작명해주고, 어떤 collection에서 index를 만들건지 설정해준다.

    그 다음, 세번째에서 locene.korean을 찾아서 적용하면 된다.

    그리고 저장하면 된다. 이것도 용량을 잡아먹기 때문에 꼭 필요한 것만 indexing해서 사용하면 된다.

    그리고 server.js에서 find 가 아닌 aggregate를 이용해서 코드를 짜준다. aggregate는 여러가지 검색조건을 달 수 있다.

    aggregate([ {}, {}, {} ]) 이런 식으로 가능하다.

    app.get('/search', (요청, 응답) => {
        console.log(요청.query.value)//요청.query에 value값이 오브젝트로 담겨있음.
        //이제 요청받았으니까 input태그에 적힌 값을 제목으로 가진 게시물을 DB에서 찾아서 보내준다.
        var 검색조건 = [
            {
              $search: {
                index: 'titleSearch',
                text: {
                  query: 요청.query.value,
                  path: '제목'  // 제목날짜 둘다 찾고 싶으면 ['제목', '날짜']
                }
              }
            },//만약 sort가 없으면 글쓰기를 검색했을때 글쓰기라는 단어가 가장 많이 들어간 애가 
            //search score가 높아서 그 순서대로 정렬 그걸 확인하려면 아래처럼 써서 확인
            { $project : { 제목: 1, _id: 0, score: { $meta: "searchScore" } } },
            { $sort : { _id : 1 } }, //검색조건을 더 만들기 가능 정렬기능인데 id순으로 1(오름차순), -1(내림차순)
            { $limit : 10 } //상위 5개 상위 10개 이렇게 제한을 둘 수도 있음
          ] 
        db.collection('post').aggregate(검색조건).toArray((에러, 결과)=>{
            console.log(결과);
            응답.render('search.ejs',{posts : 결과});
        })
    })

    검색조건이 길어서 미리변수에 만들어놓고 검색조건이란 변수를 aggregate에 집어넣어도 괜찮다.

    조건에 $search에 index에는 만들어놓은 index이름을 넣어주고, 제목을 찾으려고하니 제목을 넣어준다. 

    그리고 쉼표로 조건을 계속 넣을 수 있는데 sort limit등등이 있고, sort가 없으면 MongoDB에서 알아서 searchScore를 매겨서 정렬해준다. $project를 이용해서 제목만 가져오고 _id: 0을써서 id는 안가져오게하고 score를 입력해주면 다음처럼 score가 나온다.

    댓글

Designed by Tistory.