Spring/QueryDSL

서브 쿼리

느리지만 꾸준하게 2022. 4. 20. 04:50

쿼리안에 쿼리를 넣는작업을 해보자

  • com.querydsl.jpa.JPAExpressions 사용

나이가 가장 많은 회원을 조회해보면

/**
 * 나이가 가장 많은 회원 조회
 */
@Test
public void subQuery() {
    QMember memberSub = new QMember("memberSub");

    List<Member> result = queryFactory
            // member 테이블을 가져오고
            .selectFrom(member)
            // member의 나이가 같은데
            .where(member.age.eq(
                    // member의 나이가 가장 큰사람이랑 같다. => 40이라는 값이 나올꺼다
                    JPAExpressions
                            .select(memberSub.age.max())
                            .from(memberSub)
            ))
            .fetch();
    assertThat(result).extracting("age")
            .containsExactly(40);
}

 

실행결과는 아래와 같다.

// membersub로 해서 alias가 들어가고

/* select
        member1 
    from
        Member member1 
    where
        member1.age = (
            select
                max(memberSub.age) 
            from
                Member memberSub
        ) */ 
        
        
        // member의 나이는 subquery로 들어간다.
        
        select
            member0_.id as id1_1_,
            member0_.age as age2_1_,
            member0_.team_id as team_id4_1_,
            member0_.username as username3_1_ 
        from
            member member0_ 
        where
            member0_.age=(
                select
                    max(member1_.age) 
                from
                    member member1_
            )

 

 

 

나이가 평균이상인 회원을 조회해보면

/**
 * 나이가 평균 이상인 회원
 */
@Test
// 크거나 equal
public void subQueryGoe() {
    QMember memberSub = new QMember("memberSub");

    List<Member> result = queryFactory
            .selectFrom(member)
            .where(member.age.goe(
                    JPAExpressions
                            .select(memberSub.age.avg())
                            .from(memberSub)
            ))
            .fetch();
    assertThat(result).extracting("age")
            .containsExactly(30, 40);
}

 

goe 실행결과를 볼 수 있다.

// 나머지 쿼리는 위와 옹일

/* select
        member1 
    from
        Member member1 
    where
        member1.age >= (
            select
                avg(memberSub.age) 
            from
                Member memberSub
        ) */ 
        
        select
            member0_.id as id1_1_,
            member0_.age as age2_1_,
            member0_.team_id as team_id4_1_,
            member0_.username as username3_1_ 
        from
            member member0_ 
            
            // goe이고
        where
            member0_.age>=(
                select
                    avg(cast(member1_.age as double)) 
                from
                    member member1_
            )

 

 

 

subQueryIn을 써보면

@Test
    public void subQueryIn() {

        QMember memberSub = new QMember("memberSub");

        List<Member> result = queryFactory
                .selectFrom(member)
                .where(member.age.in(
                        JPAExpressions
                                .select(memberSub.age)
                                .from(memberSub)
                                // 20 30 40 => 10살 초과
                                .where(memberSub.age.gt(10))
                ))
                .fetch();
        assertThat(result).extracting("age")
                .containsExactly(20, 30, 40);
    }

실행결과가 아래와 같다.

// in으로 해서 나오게 된다.

/* select
        member1 
    from
        Member member1 
    where
        member1.age in (
            select
                memberSub.age 
            from
                Member memberSub 
            where
                memberSub.age > ?1
        ) */ 
        
        
        
        select
            member0_.id as id1_1_,
            member0_.age as age2_1_,
            member0_.team_id as team_id4_1_,
            member0_.username as username3_1_ 
        from
            member member0_ 
        where
            member0_.age in (
                select
                    member1_.age 
                from
                    member member1_ 
                where
                    member1_.age>?
            )

 

 

 

 

select절의 subQuery를 보면 

selectSubQuery
// select 절 subquery
@Test
public void selectSubQuery() {
    QMember memberSub = new QMember("memberSub");

    List<Tuple> result = queryFactory
            // username 다뽑고 평균 나이를 출력
            .select(member.username,
                    JPAExpressions
                            .select(memberSub.age.avg())
                            .from(memberSub))
            .from(member)
            .fetch();

    for (Tuple tuple : result) {
        System.out.println("tuple = " + tuple);
    }
}

 

member들의 전체 평균 나이가 나온다.

tuple = [member1, 25.0]
tuple = [member2, 25.0]
tuple = [member3, 25.0]
tuple = [member4, 25.0]

 

 

JPAExpressions을 static import를 해서 표현가능하다.

// select 절 subquery
@Test
public void selectSubQuery() {
    QMember memberSub = new QMember("memberSub");

    List<Tuple> result = queryFactory
            // username 다뽑고 평균 나이를 출력
            .select(member.username,
                    // static import 가능
                    select(memberSub.age.avg())
                            .from(memberSub))
            .from(member)
            .fetch();

    for (Tuple tuple : result) {
        System.out.println("tuple = " + tuple);
    }
}

 

JPA 서브쿼리는 select절이나 where절에서는 되지만 from절에서는 안된다.

from절의 서브쿼리 한계인데

  • JPA JPQL  / Querydsl  둘다 from절의 서브쿼리는 지원하지 않는다. 하이버네이트 구현체를 사용하면 select절의 서브쿼리는 지원한다.
  • Querydsl도 하이버네이트 구현체를 사용하면 select 절의 서브쿼리를 지원한다.

 

 

from절의 서브쿼리 해결방안으로는

  • 서브쿼리를 join으로 변경한다.(가능한 상황도 있고, 불가능한 상황도 있다.)
  • 애플리케이션이 엄청 중요한 기능을 가지지 않는 이상 애플리케이션에서 쿼리를 2번 분리해서 실행해도 괜찮다.
  • from절에서 서브쿼리를 죽어도 써야한다 그러면 마지막 수단으로 네이티브 SQL을 사용한다.

 

db라는 것은 쿼리를 엄청 복잡하게 짜는 경우가 많은데  화면안에 로직에 맞추다 보니까 어쩔수 없이 from절 안에 from절이 연속으로 들어가는 경우가 많다. SQL은 데이터를 가져오는 거랑 grouping하는거에 집중을 하고 애플리케이션에서 비즈니스 로직을 끝내야 한다. 

 

  • SQL AntiPatterns
  • 자바 ORM 표준 JPA 프로그래밍 이 두 책은 사서 보자.

 

 

 

 

 

 

 

 

 

 

 

 

<출처 김영한: 실전! Querydsl >

https://www.inflearn.com/course/Querydsl-%EC%8B%A4%EC%A0%84/dashboard

 

실전! Querydsl - 인프런 | 강의

Querydsl의 기초부터 실무 활용까지, 한번에 해결해보세요!, - 강의 소개 | 인프런...

www.inflearn.com