들어가면서
EXISTS 연산자에 대해서 기본인 조건에 존재하는 것이 있으면 'True' 결과 전달한다는 내용만 알고 어떤 상황에서 사용하는지를 정확하게 몰랐다고 생각됩니다.
그냥 INNER JOIN이나 OUTER JOIN을 주로 사용해서 exists를 거의 사용하지 않았습니다.
최근 쿼리를 개선하다보니 join이 아닌 exists를 사용하여 속도 개선이 많이 발생한 내용을 공유드립니다.
exists는 언제 사용해야할까?
위에서 적은 것처럼 존재하면 'True'로 반환하기 떄문에 조건에 따라 데이터를 걸러내어 결과를 조회할 때 사용되고 있습니다.
사실 IN 연산자를 이용하여 조회해도 동일한 결과를 얻을 수 있지만 서브쿼리 결과가 100개 이상 나올 가능성이 있으면 IN 연산자를 사용할 수 없습니다.
IN 연산자가 SELECT 절에 조회한 결과로 다시 비교하기 때문에 더 느리다고 합니다.
JOIN으로도 비슷한 결과를 얻을 수 있지만 동일한 데이터 중복이 발생할 수 있어서 DISTINCT, GROUP BY로 중복데이터를 제거해야합니다.
사용예시
간단하게 영화 테이블과 배우 테이블로 간단하게 예시를 보여드리겠습니다.
영화 테이블과 배우 테이블은 1:N 관계이고 저희는 조건절에 배우의 이름으로 배우가 참여한 영화를 찾는 경우입니다.
조건절에 있는 배우 이름은 null도 있고 실제 배우이름도 있습니다.
// Exists로 조회하는 경우 해당 경우는 영화 제목이 중복되지 않고 나옴
BooleanExpression actorNameEq = StringUtils.hasText(actorName) ?
JPAExpressions
.select(actor.id)
.from(actor)
.where(actor.name.eq(actorName)
,movie.id.eq(actor.movieId))
.exists() : null;
return jpaQueryFactory
.select(movie.title)
.from(movie)
.where(actorNameEq)
.limit(10)
.fetch();
// IN으로 조회하는 경우 해당 경우도 영화제목이 중복되지 않고 나옴
BooleanExpression actorNameEq = StringUtils.hasText(actorName) ?
movie.id.in(JPAExpressions
.select(actor.movieId)
.from(actor)
.where(actor.name.eq(actorName))) : null;
return jpaQueryFactory
.select(movie.title)
.from(movie)
.where(actorNameEq)
.limit(10)
.fetch();
// Join으로 조회하는 경우 해당 경우는 영화제목이 join한 배우의 수로 중복된 상태에서 나옴
BooleanExpression actorNameEq = StringUtils.hasText(actorName) ?actor.name.eq(actorName) :null;
return jpaQueryFactory
.select(movie.title)
.from(movie)
.join(movie.actors,actor)
.where(actorNameEq)
.limit(10)
.fetch();
3번 Join으로 데이터를 찾는 경우 중복된 데이터를 제거하기 위해서 Group by를 이용하거나 Java에서 추가적인 로직이 필요합니다.
저의 경우 3번과 같이 구현된 페이징 쿼리였고 해당 쿼리는 페이징쿼리였기 때문에 카운트를 하기위해서 조건에 맞는 모든 데이터를 조회하여 Java에서 중복제거 및 카운트를 하였습니다. 해당 과정에서 DB에서 너무 많은 데이터를 받아왔기 때문에서 속도가 느려지는 현상이 발생하였습니다.
exists 연산자로 조건절을 만들어야 했던 쿼리라고 생각되었고 exists는 조건에 맞는 행이 있으면 중단하고 True로 결과를 전달하기 때문에 페이지성 쿼리나 OLTP성의 서비스에 좋은 효율이 나온다고 생각됩니다.
exists 연산자가 필요한 상황에 대해서 작성하였고 혹시나 다른 케이스가 있다면 댓글로 공유부탁드립니다.
'DB' 카테고리의 다른 글
JPA Converter처럼, MyBatis TypeHandler로 변환 책임 한 곳에 모으기 (0) | 2025.09.02 |
---|---|
CaseBuilder 대신 BooleanBuilder를 써야 했던 이유(Feat: querydsl) (2) | 2025.07.27 |
Querydsl 다중 where 조건 만들기 (1) | 2025.04.23 |
[JPA] 엔티티 값을 변환하여 저장, 조회하기(Feat: @Converter) (0) | 2025.03.10 |
ORA-01502: 인덱스 인덱스 이름 또는 인덱스 분할영역은 사용할 수 없는 상태입니다. (0) | 2025.02.18 |