쇼핑몰이나 게시판을 만들다 보면 상품에 해시태그를 등록하고, 사용자가 여러 태그를 선택했을 때 가장 많이 매칭되는 상품을 상위에 노출해야 하는 경우가 많습니다.
이번 글에서는 실제 테스트한 SQL을 기반으로 DB에 해시태그를 저장하고 n:n 형태로 검색하는 방법을 정리해보겠습니다.
특히 아래와 같은 기능을 구현할 때 유용합니다.
- 쇼핑몰 상품 추천
- 태그 기반 검색
- 콘텐츠 유사도 검색
- 관심사 매칭
- 필터 검색
왜 이런 검색 방식이 필요한가
일반적인 검색은 보통 다음과 같습니다.
WHERE hash_tags LIKE '%감자%'
하지만 이렇게 하면 문제가 있습니다.
- 검색 정확도가 낮음
- 일부 문자열도 매칭됨
- 여러 태그의 연관성을 계산하기 어려움
- 어떤 상품이 더 적합한지 정렬 불가능
예를 들어 사용자가 아래 조건으로 검색했다고 가정해봅시다.
봉지과자 + 짠맛 + 감자
단순 LIKE 검색은
“감자” 하나만 포함되어도 같은 우선순위로 나올 수 있습니다.
그래서 필요한 것이 바로 매칭 개수 기반 정렬 방식입니다.
원인은 무엇인가
문제의 핵심은 DB에 저장된 해시태그가 문자열이라는 점입니다.
예: 봉지과자,안주스낵,짠맛,감자
이 데이터를 제대로 검색하려면 다음이 필요합니다.
- 단어 단위 매칭
- 여러 키워드 동시 검색
- 몇 개가 일치했는지 계산
- 일치 수 기준 정렬
실제 테스트용 테이블 생성
snack_test 테이블 생성
CREATE TABLE `snack_test` (
`idx` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL DEFAULT '' COMMENT '과자 이름',
`hash_tags` text COMMENT '헤시태그',
PRIMARY KEY (`idx`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='과자 테스트';
테스트 데이터 등록
INSERT INTO `snack_test` (`name`, `hash_tags`) VALUES
('감자깡', '봉지과자,안주스낵,짠맛,감자'),
('포카칩', '봉지과자,안주스낵,짠맛,감자'),
('구운감자', '곽과자,안주스낵,짠맛,감자'),
('꼬깔콘', '봉지과자,안주스낵,짠맛'),
('아이비', '곽과자,안주스낵,짠맛'),
('꼬북칩', '봉지과자,안주스낵,짠맛'),
('콘칲', '봉지과자,안주스낵,짠맛'),
('ZEC', '곽과자,안주스낵,짠맛'),
('에이스', '곽과자,짠맛'),
('칸쵸', '곽과자,달달,초코'),
('초코송이', '곽과자,달달,초코'),
('바나나킥', '봉지과자,달달,바나나'),
('빼빼로', '곽과자,달달,초코'),
('초코파이', '곽과자,달달,초코'),
('홈런볼', '곽과자,달달,초코');
해결 방법
핵심은 REGEXP를 이용해 각 태그가 포함되었는지 체크하고 합산하는 것입니다.
매칭 수 계산 쿼리
SELECT
`name`,
`hash_tags`,
(
(`hash_tags` REGEXP '[[:<:]]봉지과자[[:>:]]') +
(`hash_tags` REGEXP '[[:<:]]짠맛[[:>:]]') +
(`hash_tags` REGEXP '[[:<:]]감자[[:>:]]')
) AS `matching_count`
FROM
`snack_test`
WHERE
`hash_tags` REGEXP '[[:<:]](봉지과자|짠맛|감자)[[:>:]]'
ORDER BY
`matching_count` DESC;
왜 문제가 되는가
만약 단순 LIKE 검색을 사용하면:
LIKE '%감자%'
다음 같은 문제가 발생할 수 있습니다.
- “감자”만 포함된 상품도 동일 우선순위
- “봉지과자 + 짠맛 + 감자”를 모두 만족하는 상품 구분 어려움
- 추천 정확도 저하
반면 현재 방식은:
| 상품 | 매칭 개수 |
| 감자깡 | 3 |
| 포카칩 | 3 |
| 꼬깔콘 | 2 |
| 에이스 | 1 |
이런 식으로 정렬됩니다.
즉 사용자 의도와 더 가까운 결과를 상단에 노출 가능
실제 사례
제가 실제로 쇼핑몰 필터 검색을 만들 때 사용했던 방식입니다.
처음에는 LIKE 기반 검색을 사용했는데
상품 수가 많아질수록 검색 품질이 떨어졌습니다.
특히:
- 여름
- 반팔
- 오버핏
같은 태그 검색 시
한 개만 포함된 상품도 상단에 노출되는 문제가 있었습니다.
그래서 현재처럼:
- 태그별 REGEXP 체크
- 합산
- matching_count 정렬
구조로 변경했더니
검색 정확도가 훨씬 좋아졌습니다.
추가 팁
PHP에서 동적 쿼리 생성
$tags = ['봉지과자', '짠맛', '감자'];
$match_sql = [];
$regexp_arr = [];
foreach ($tags as $tag) {
$match_sql[] = "(`hash_tags` REGEXP '[[:<:]]{$tag}[[:>:]]')";
$regexp_arr[] = $tag;
}
$match_query = implode(' + ', $match_sql);
$regexp_query = implode('|', $regexp_arr);
$sql = "
SELECT
`name`,
`hash_tags`,
({$match_query}) AS `matching_count`
FROM
`snack_test`
WHERE
`hash_tags` REGEXP '[[:<:]]({$regexp_query})[[:>:]]'
ORDER BY
`matching_count` DESC
";
FULLTEXT보다 단순 구현이 쉬움
물론 태그 전용 테이블을 분리하는 방식이 가장 정규화된 구조입니다.
하지만:
- 관리자 개발 속도
- 유지보수
- 소규모 쇼핑몰
환경에서는 현재 방식이 훨씬 빠르고 단순합니다.
정리
- 해시태그를 문자열로 저장해도 검색 가능
- REGEXP + 합산 방식으로 유사도 검색 구현 가능
- matching_count 기준 정렬이 핵심
- 쇼핑몰 필터/추천 기능에 매우 유용
특히 중소형 프로젝트에서는 “태그 테이블 분리 없이 빠르게 구현 가능” 하다는 장점이 큽니다.