dbtable.png

 

위 처럼 주소가 들어있는 테이블 에서 "adr" 컬럼의 내용 중 "을지로" 가 포함되는 결과만 출력 하려면 어떻게 할까요? 

보통은 아래처럼 SELECT  LIKE  '%검색어%' 쿼리를 사용 하는것이 일반적 입니다.

 

// 주소정보 조회 쿼리
$search = '을지로';
$stmt = DB::getInstance()->query("SELECT code, adr FROM roadcode WHERE adr LIKE '%$search%'");
$raw_list = $stmt->fetchAll();

 

만약 수백~수천번 반복되는 반복문 안에서 매번 SELECT LIKE 쿼리를 날려줘야 하는 경우에는 부하가 늘어나고 출력이 느려질것 이므로 최초 로드시 한번만 SELECT 쿼리를 실행하여 배열에 저장해두고, 필요할때 마다 특정 배열 값을 LIKE 처럼 확인하여 추출하는것이 서버 부하도 줄이고 효율적이라고 생각하여 작성한 팁 입니다.

 

 

다만, 선행 조건은 DB에서 최초 데이터를 받아 올때 WHERE 절을 이용하여 필요한 데이터만 1차 필터링 해주거나

혹은 배열상에서 1차 필터링 하여 필요한 배열값만 남겨 주어야 합니다. 

원본 배열의 개수가 너무 많다면 오히려 매번 LIKE 쿼리를 날리는것 보다 더 느려질 수 있습니다 ㅠㅠ

 

 

예를들어 "서울특별시 중구" 의 하위주소인 "을지로" 를 문자열검색(LIKE) 을 이용하여 반복해서 찾아야 할 필요가 있다면

 

배열에 "서울특별시 중구" 가 포함되는 값들만 필터링 하여 1차 저장하고 그 하위 주소에 해당하는 값들을 검색할 때에 DB쿼리를 하지 않고 페이지 로드시 1차저장 해두었던 배열에서 바로 검색하여 처리하는 방법 입니다.

 

// 주소정보 조회 쿼리
$search = '서울특별시 중구';
$stmt = DB::getInstance()->query("SELECT code, adr FROM roadcode WHERE adr LIKE '%$search%'");");
$raw_list = $stmt->fetchAll();

// arrayLikeVal($raw_list, '검색대상컬럼명', '검색어');
$filter_list = arrayLikeVal($raw_list, 'adr', '을지로');

// [함수] 2차원배열 특정 값 LIKE 검색
function arrayLikeVal($array, $target, $search) {
$res = array();
   foreach($array as $val) {
      if (strpos($val->$target, $search) !== false) {
         array_push($res, $val);
      }
   }
return $res;
}

 

사용은 arrayLikeVal($raw_list, 'adr', '을지로') 앞에서 부터 순서대로 배열명, 컬럼명, 검색어 순으로 입력하시면 됩니다.

$변수 형태로 넣거나 따옴표로 감싸서 직접 값을 넘겨주시면 됩니다.

 

 

result.png

 

이렇게 $배열명->컬럼명 값에 검색어가 포함되는 결과만 필터링 되어 리턴 됩니다 ~

 

 

아마도 고수님들 께서는 더 효율적이고 쉬운 방법으로 처리 하실지도 모르겠지만

그동안 XETOWN에서 너무나도 많은 도움과 조언을 받아 왔기에..

혹시 누군가 저처럼 절실하게 이 내용을 필요로 하여 찾지는 않을까 하여 이렇게 팁을 작성 해봅니다 ^ㅡ^

 

항상 감사드립니다. 저 또한 팁으로 작성할만한 내용이 있다면 또 작성토록 해보겠습니다!!

 
  • profile

    흥미로운 방법이네요.

    그런데 사실상 풀스캔을 한다는 점에서 LIKE 쿼리와 근본적으로 다른 점이 없어서...

    LIKE 쿼리를 하는 것과 속도를 한 번 비교해 보면 재미있을 것 같습니다. ㅎㅎ

  • profile profile

    본문에서는 쿼리를 간단하게 표시하기 위해 모든 레코드를 가져 오는것으로 설명 되었는데

    저는 SELECT 할때 WHERE 조건으로 한번 필터링을 걸어서 필요 범위내의 사용할 데이터만 가져옵니다!

    실제로 DB에서 가져와서 배열에 저장하여 사용하는 값은 보통 100개 정도 나오네요

     

    저의 경우에는 다른 데이터에 연계되어 사용하는 정보라 실제로 반복문 (약 300 ~ 1,000회 정도) 내부에서

    특정 컬럼의 값을 계속 바꾸면서 참고하여서 값을 받아와야 되서요 ㅠ.ㅠ

    매번 DB에 요청하기는 좀 부담 스럽더라구요

     

    그러고보니.. 저처럼 대량의 데이터를 LIKE 해서 사용하는 경우가 보통은 없을것 같기도 하군요ㅎㅎ

     

    result.png

    Phpmyadmin 상에서 테스트 해보니 이 정도의 시간차가 나오네요!!

  • profile profile

    아, 반복 횟수가 상당히 많군요. 그렇다면 0.01초 절약도 의미가 있을 것 같습니다.

    한 번 필터링해서 필요한 범위내의 데이터만 갖고 온다고 하셨는데, 그 조건과 adr LIKE 조건을 조합하면 어떻게 될지도 궁금하네요. 물론 다른 조건이 인덱스를 잘 타서 검색 범위를 확실하게 좁혀 준다는 가정 하에요.

     

    P.S. strpos()의 결과를 true와 비교하는 것도 좀 특이한 사용법입니다.^^

  • profile profile

    저도 갑자기 궁금해져서 실제 PHP상에서 테스트를 해봤네요 ㅎㅎ

     

    사실 과거 LIKE를 사용했을때 부하가 늘어나고 느렸던 기억이 있어서 최근의 LIKE 성능은 딱히 모르는 상태에서 비교 테스트는 해보지 않고 바로 본문의 방법으로 코드를 작성 하였습니다.

     

    주소 테이블의 경우 공공데이터포탈 에서 엑셀 파일로 받아서 별도의 컬럼별로 나누지 않고 mysql 테이블로 바로 올린거라 adr컬럼에 지번주소들이 통째로 들어있는 형태 이구요

     

     

    주소 데이터의 경우 페이지 접속 시점에 해당하는 시군구 (예: 익산시) 기준으로 SELECT LIKE 하여 1차로 배열에 저장하고 있습니다.  그후 시군구 하위 주소에 해당하는 데이터들을 반복문으로 출력하는 과정에 배열에 저장했던 값을 다시 필터링 하여 해당하는 데이터에 붙여주고 있습니다.

     

     

    아래는 연계되는 데이터 없이 단순히 SELECT LIKE 쿼리 및 반복문을 이용하여 테스트 해본 결과인데...

    생각보다 실행 시간이 엄청나게 차이나네요... 뭔가 제가 만든 디비 구조부터 문제가 있는거 같기도 합니다 ㅋ.ㅋ

     

    제목 없음.png

     

     

     

    추가

    기진님 께서 첫 댓글에 말씀하신 부분이 무슨 의견을 전해 주시려 한건지 지금 바로 이해 되었습니다. ㅠㅠ

    DB에서 아무 조건없이 2만여개의 데이터를 받아와서 테스트 해보니 DB에서 바로 LIKE 하는것 보다도 훨씬 느리네요... 배열 2만개 * 반복문 1,000회 만 해도... 함수를 20,000,000회 로드하는 꼴이 되네요

     

    더 느리네.png

     

    DB에서 SELECT시 아무런 WHERE 없이 대량의 데이터를 받아오는 경우 본문의 방법이 오히려 더 느려지게 만드네요

     

    저 처럼 1차적으로 받아오는 데이터를 필터링 하고, 배열내에서 반복적으로 특정 문자열을 찾아야 되는 분들께 해당 되는 팁이네요

     

  • profile profile
    네, DB에서 불러올 때 1차 필터링이 있고 그 후에 PHP에서 2차 필터링을 하시려는 것 같은데, 1차 필터링으로 걸러지는 갯수와 2차 필터링하는 루프 횟수에 따라 어느 쪽이 더 효율적인지가 달라질 것 같네요. 님 같은 사례에서는 어느 정도 PHP에 기대는 것이 확실히 도움이 되어 보입니댜.
  • profile

    이 글 내용대로 처리하는 것이 좋습니다. 이론적으로는 LIKE 검색이 좋겠지만 이것은 풀스캔을 필요로 합니다.
    WHERE 조건을 인덱싱 설정한 필드만으로 하거나 조건을 주지 않으면 쿼리캐시되어 DB부하가 줄어들고 매우 빠른 응답이 일어납니다.
    특히, 한 컬럼에 다양한 조건이 걸릴 경우, 예를들어 (마포구 || 동작구 || 관악구) 로 불러오는것 같은 경우 DB로 하면 부하가 매우큰데, application 단에서 처리하면 거의 즉시 처리가 가능합니다. application 은 스케일아웃 작업이 용이하기 때문에 DB부하를 분산시키면 더 큰 볼륨을 처리할 수 있습니다.
    또는 row의 수가 고정되어 있기 때문에, 적절한 CPU 사양의 DB를 두고 REGEXP 같은 적절한 쿼리를 사용하면 DB만으로도 빠르게 처리가능합니다.


    strpos 함수는 문자를 찾아서 숫자 인덱스를 반환하는 함수입니다.
    https://www.php.net/manual/en/function.strpos.php
    리턴값은 int 또는 false 입니다. 즉, == true 보다는 !== false 라고 쓰는것이 좋습니다.

    위의 예제는 잘못된 사용법으로, 예를 들어 "서울" 이라고 검색하면 아무것도 결과를 얻지 못할겁니다.

  • profile profile
    앗.. 다른 함수에서 반환되는 리턴값과 착각 하였습니다. 작동이 되기에 전혀 문제를 못느꼈네요
    정정 해주셔서 감사합니다. 본문에도 !==false 로 바꾸었습니다 ㅎ