MySQL ELT · LOCATE · INSTR —
위치·값 찾기 완전 정리
MySQL에는 "위치"와 "값"을 서로 바꿔가며 찾아주는 함수가 5개 있다. 겉으로는 비슷해 보이지만 인덱스 → 값인지 값 → 인덱스인지, 리스트 안인지 문자열 안인지에 따라 완전히 다른 함수다. 원본 코드를 그대로 분석해본다.
| ELT(2,…) | FIELD('둘',…) | FIND_IN_SET('둘',…) | INSTR('하나둘셋','둘') | LOCATE('둘','하나둘셋') |
|---|---|---|---|---|
'둘' |
2 |
2 |
3 |
3 |
한 줄 결과지만 의미는 전부 다르다 — 앞의 세 개는 "리스트 안 몇 번째", 뒤의 두 개는 "문자열 안 몇 번째 글자"를 묻는다.
MySQL에는 PHP의 배열 같은 자료형이 없다. 그래서 "여러 값 중 N번째"를 다루고 싶을 때는 ELT(N, v1, v2, v3, …)처럼 값을 인자로 쭉 나열하고, 첫 번째 인자 N으로 그중 하나를 골라낸다. ELT(2, '하나', '둘', '셋')은 "2번째 인자를 달라"는 뜻이므로 결과는 '둘'이다.
ELT(1, …)이 첫 번째 값이고, ELT(0, …)은 존재하지 않는 위치라 NULL을 반환한다.N이 인자 개수보다 크거나(ELT(5, '하나','둘','셋')), 음수이거나, 0일 때도 결과는 전부 NULL이다. 이건 "범위를 벗어난 인덱스 접근"을 에러로 죽이지 않고 조용히 NULL로 흘려보내는 SQL 특유의 관용이다.
ELT가 "인덱스를 주면 값을 돌려주는" 함수라면, FIELD는 정반대로 "값을 주면 인덱스를 돌려주는" 함수다. FIELD('둘', '하나', '둘', '셋')은 "'둘'이 몇 번째 자리에 있느냐"를 묻고 2를 반환한다.
입력: 번호 2
출력:
'둘'입력:
'둘'출력: 번호 2
FIND_IN_SET은 FIELD와 하는 일이 비슷해 보이지만 입력 형태가 다르다. FIELD는 값을 인자 여러 개로 나열하는 반면, FIND_IN_SET은 하나의 문자열 안에 쉼표로 항목을 구분해 넣는다. 함수 내부적으로 이 문자열을 쉼표 기준으로 쪼개서(split) 순서를 센 뒤, 찾는 값과 일치하는 조각의 번호를 돌려준다.
- 구분자가 쉼표가 아니면(예:
'하나|둘|셋') 통째로 한 덩어리로 취급되어 못 찾는다 - MySQL의 SET 자료형과 궁합이 맞게 설계된 함수라 항목이 너무 많은 리스트에는 적합하지 않다
- 정규화된 테이블(1개 값 = 1개 행) 대신 쉼표 구분 문자열을 저장하는 설계 자체가 안티패턴으로 꼽힌다
여기서부터는 "리스트 안 순번"이 아니라 "문자열 안 글자 위치"를 찾는 함수로 넘어간다. INSTR('하나둘셋', '둘')은 '하나둘셋'이라는 문자열에서 '둘'이 몇 번째 글자부터 시작하는지를 센다. '하'가 1번째, '나'가 2번째, '둘'이 3번째 글자이므로 결과는 3이다.
strpos(), JavaScript의 indexOf() 등 대부분의 프로그래밍 언어는 위치를 0부터 센다. 반면 SQL 계열은 사람이 "몇 번째 글자"라고 셀 때 쓰는 방식 그대로 1부터 센다. PHP와 MySQL을 오가며 개발할 때 가장 헷갈리는 포인트다.찾는 문자가 문자열 안에 없으면 INSTR은 0을 반환한다. NULL이 아니라 0이라는 점이 중요한데, 이는 이어지는 6번 섹션에서 따로 다룬다.
LOCATE('둘', '하나둘셋')은 INSTR('하나둘셋', '둘')과 결과가 완전히 같다(둘 다 3). 차이는 딱 하나, 인자를 쓰는 순서뿐이다. INSTR은 "문자열, 찾을값" 순서고 LOCATE는 "찾을값, 문자열" 순서다.
INSTR('하나둘셋','둘')시작 위치 지정 불가
LOCATE('둘','하나둘셋')3번째 인자로 검색 시작 위치 지정 가능
두 함수가 공존하는 이유는 호환성이다. INSTR은 Oracle류 SQL 방언에서 넘어온 이름이고, LOCATE는 원래 Sybase/SQL Server 계열 관용을 따른 이름이다. MySQL은 둘 다 지원해서 어느 쪽 문법에 익숙한 사람이 와도 쓸 수 있게 해뒀다. 대신 LOCATE는 LOCATE(substr, str, pos)처럼 세 번째 인자로 검색을 시작할 위치를 지정할 수 있어 "이전 위치 이후부터 다시 찾기" 같은 반복 검색에 더 유리하다.
FIELD·FIND_IN_SET·INSTR·LOCATE 네 함수 모두 찾는 대상이 없을 때 0을 반환한다. 만약 NULL을 반환했다면, 이 값을 조건문에 쓸 때마다 IS NULL 체크를 추가로 해야 하고 산술 연산(+, 비교 등)에서도 NULL 전파 때문에 예상치 못한 결과가 튀어나온다. 0은 "숫자 위치가 아니다"를 나타내는 안전한 값으로 쓰기 편해서, MySQL은 일관되게 0을 택했다.
| 실수 | 증상 | 해결 |
|---|---|---|
| INSTR과 LOCATE의 인자 순서를 혼동 | 엉뚱한 값이 인자로 들어가 문법 에러 또는 잘못된 결과 | INSTR(문자열,찾을값) / LOCATE(찾을값,문자열)로 암기 |
| 위치를 0부터 셀 거라 착각 | PHP strpos 결과와 1 차이가 나서 로직이 어긋남 | MySQL 위치 함수는 전부 1-based임을 기억 |
| FIND_IN_SET에 쉼표 아닌 구분자 사용 | 있는 값인데도 0(못 찾음)이 반환됨 | 반드시 쉼표(,)로 구분된 문자열만 전달 |
| ELT(N,…)에서 N이 0 또는 범위 초과 | 에러 없이 조용히 NULL이 반환돼 원인 파악이 늦음 | N의 유효 범위(1~인자개수)를 미리 검증 |
| 못 찾은 결과 0을 NULL과 같은 것으로 취급 | IS NULL 조건으로 걸러도 안 걸러짐 | "없음" 판정은 = 0으로, NULL은 별도로 체크 |
| FIND_IN_SET을 대용량 리스트에 사용 | 인덱스를 못 타서 풀스캔, 성능 저하 | 값이 많아지면 별도 매핑 테이블로 정규화 |
핵심 한 줄 요약
Tags
'php' 카테고리의 다른 글
| MySQL ADDDATE · SUBDATE — 날짜 더하기·빼기 완전 정리 (0) | 2026.06.27 |
|---|---|
| MySQL CONCAT_WS — 구분자 지정 문자열 연결 완전 정리 (0) | 2026.06.27 |
| MySQL SUBSTRING · SPACE · SUBSTRING_INDEX — 문자열 추출 완전 정리 (0) | 2026.06.27 |
| MySQL PAD · TRIM — 패딩·공백 제거 완전 정리 (0) | 2026.06.27 |
| MySQL CASE WHEN — 다중 조건 분기 완전 정리 (0) | 2026.06.27 |
왕진 블로그

댓글