실무에서 프로시저, 레포트 툴, 또는 화면 검색 조건을 처리하다 보면 여러 개의 ID나 코드가 ITEM001,ITEM002,ITEM003처럼 콤마(,)로 구분된 단일 문자열 형태로 넘어오는 경우가 자주 있습니다.
보통 애플리케이션(Java/Spring)단에서 split(",") 후 MyBatis의 <foreach>를 태우는 것이 가장 일반적이지만, 순수 SQL 환경, 프로시저, 쿼리 바인딩 제약 등으로 인해 오라클 쿼리 내부에서 직접 문자열을 분리해야 할 때가 있습니다.
이번 글에서는 오라클의 정규표현식(REGEXP_SUBSTR)과 계층형 쿼리(CONNECT BY LEVEL)를 활용해 단일 문자열을 행(Row)으로 분리하고 검색 조건에 매핑하는 기법을 상세히 분석해 보겠습니다.
💡 요약
- 오라클에서 콤마로 구분된 문자열을 자를 때는
REGEXP_SUBSTR+ `CONNECT BY LEVEL` 조합이 대표적인 패턴입니다. REGEXP_COUNT('&arg', ',') + 1을 통해 분리할 개수를 지정할 수 있습니다.- 파라미터의
NULL여부에 따른 분기 처리를 추가하면 동적 검색 조건으로 안전하게 사용할 수 있습니다.
1. 사용된 쿼리 및 전체 구조 분석
먼저 작성된 조건절 쿼리를 가독성 있게 정리한 구조입니다.
AND 'TRUE' = CASE
-- 1) 검색 파라미터가 없으면 조건 패스
WHEN '&arg_goods' IS NULL THEN 'TRUE'
-- 2) 파라미터가 있으면 콤마로 잘라낸 목록과 매칭 여부 검사
WHEN EXISTS (
SELECT 'X'
FROM (
SELECT DISTINCT REGEXP_SUBSTR(A.TXT, '[^,]+', 1, LEVEL) AS GOODS_CODE
FROM (SELECT '&arg_goods' AS TXT FROM DUAL) A
CONNECT BY LEVEL <= LENGTH(REGEXP_REPLACE(A.TXT, '[^,]+', '')) + 1
) H
WHERE H.GOODS_CODE = GD.GOODS_CODE
) THEN 'TRUE'
ELSE 'FALSE'
END
이 쿼리는 크게 3가지 핵심 동작으로 나뉩니다.
2. 쿼리 동작 원리 파헤치기
① 콤마 개수 세기 및 반복 횟수 계산
LENGTH(REGEXP_REPLACE(A.TXT, '[^,]+', '')) + 1
REGEXP_REPLACE(A.TXT, '[^,]+', ''): 콤마(,)가 아닌 모든 문자를 제거하여 오직 콤마만 남깁니다.LENGTH(...): 남은 콤마의 개수를 계산합니다.+ 1: 데이터의 개수는(콤마 개수 + 1)개이므로 최종 분리할 아이템의 총 개수가 됩니다.- (예:
A,B,C→ 콤마 2개 → 길이 2 + 1 = 3)*
② 문자열 행(Row) 분리
SELECT DISTINCT REGEXP_SUBSTR(A.TXT, '[^,]+', 1, LEVEL) AS GOODS_CODE
FROM (SELECT '&arg_goods' AS TXT FROM DUAL) A
CONNECT BY LEVEL <= (전체_아이템_수)
CONNECT BY LEVEL <= N: 1부터 N까지의 행(Row)을 동적으로 생성합니다.REGEXP_SUBSTR(A.TXT, '[^,]+', 1, LEVEL):[^,]+: 콤마를 제외한 1개 이상의 문자 패턴LEVEL:LEVEL번째에 매칭되는 문자열을 추출합니다. (1번째 행은 1번째 값, 2번째 행은 2번째 값...)
DISTINCT: 중복으로 입력된 코드가 있을 경우 중복 행을 제거합니다.
실행 결과 예시 ('A,B,C' 입력 시):
| GOODS_CODE |
|---|
| A |
| B |
| C |
③ NULL 처리 및 EXISTS 조건 결합
- 파라미터(
&arg_goods)가NULL로 들어오면 전체 조회를 위해 바로'TRUE'를 반환합니다. - 파라미터가 존재하면 인라인 뷰(
H)에서 분리된 행들과 기준 테이블의GD.GOODS_CODE를 비교하여 일치하는 데이터가 존재하는지(EXISTS) 확인합니다.
3. 실무 관점에서의 리팩토링 & 권장 팁
해당 쿼리는 단일 SQL 내에서 콤마 문자열을 깔끔하게 처리하는 좋은 방법이지만, 실무 적용 시 다음 사항들을 고려하면 더욱 좋습니다.
💡 1) CASE WHEN 대신 표준적인 WHERE 절 간소화
'TRUE' = CASE WHEN ... 형태는 쿼리가 다소 장황해질 수 있으므로 아래와 같이 직관적인 OR / IN 조건으로 단순화할 수 있습니다.
AND (
-- 파라미터가 NULL이거나 비어있으면 조건 전체 패스
'&arg_goods' IS NULL
OR GD.GOODS_CODE IN (
SELECT DISTINCT REGEXP_SUBSTR('&arg_goods', '[^,]+', 1, LEVEL)
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT('&arg_goods', ',') + 1
)
)
Tip (Oracle 11g 이상):
콤마 개수를 셀 때 복잡한LENGTH(REGEXP_REPLACE(...))대신REGEXP_COUNT(str, ',')함수를 사용하면 훨씬 간결합니다.
💡 2) 성능(Performance) 주의점
CONNECT BY LEVEL방식은 입력된 아이템 수가 수십~수백 개 수준일 때는 매우 편리하고 빠릅니다.- 하지만 수천 개 이상의 대량 문자열을 반복적으로 파싱해야 하거나, 인덱스를 타야 하는 대용량 테이블과 조인할 때는 CPU 부하가 발생할 수 있습니다.
- 대량 처리가 필요하다면 애플리케이션단에서 배열/리스트로 변환해 바인딩하거나, DB Global Temporary Table(GTT)을 사용하는 방안을 권장합니다.
2탄 # [Oracle] 콤마 문자열 파싱 4가지 방식 성능 및 실무 비교 (XMLTABLE vs JSON_TABLE vs CONNECT BY)
'DB > SQL 공통' 카테고리의 다른 글
| TOAD F5 그리드의 날짜형식 변경 (0) | 2024.05.06 |
|---|---|
| Toad error division by zero (0) | 2022.01.22 |
| 스칼라서브쿼리 인라인뷰 서브쿼리 차이점 (0) | 2018.12.01 |