4 분 소요

📌 Topic

  • 💡 서브 쿼리(Sub Query)
  • 💡 JPQL 타입 표현과 기타식
  • 💡 조건식 - CASE 식
  • 💡 JPQL 함수

⚡ 01. 서브 쿼리(Sub Query)

쿼리 안에 또 다른 쿼리를 작성하는 것

01-1. 나이가 평균보다 많은 회원

select m from Member m
where m.age > (select avg(m2.age) from Member m2)

01-2. 한 건이라도 주문한 회원

select m from Member m
where (select count(o) from Order o where m = o.member) > 0

01-3. 서브쿼리 지원 함수

EXISTS가 IN에 비해 성능 면에서 뛰어나다.
EXISTS : 메인 쿼리 -> 서브쿼리 연산
IN : 서브쿼리 -> 메인 쿼리 연산

  • [NOT] EXISTS (subquery) : 서브쿼리에 결과가 존재하면 참(true)
    • {ALL ANY SOME} (subquery)
    • ALL 모두 만족하면 참(true)
    • ANY, SOME : 같은 의미, 조건을 하나라도 만족하면 참(true)
  • [NOT] IN (subquery) : 서브쿼리의 결과 중 하나라도 같은 것이 있으면 참(true)

01-4. 서브쿼리 예제

https://lhoris.tistory.com/136

--팀 A 소속인 회원
select m from Member m
where EXISTS (select t from m.team t where t.name = '팀A')
--전체 상품 각각의 재고보다 주문량이 많은 주문들
select o from Order o
where o.orderAmount > ALL (select p.stockAmount from Product p)
--어떤 팀이든 소속된 회원
select m from Member m
where m.team = ANY (select t from Team t)

01-5. JPA 서브 쿼리 한계

// 스칼라 서브 쿼리
String sql = "select (select avg(m1.age) from Member m1) as avgAge from Member m";
TypeQuery<Member> member = em.createQuery(sql, Member.class).getResultList();
// 인라인뷰
String sql = "select mm.age, mm.username from (select m.age, m.username from Member m) as mm";
// 중략
  1. JPA는 WHERE, HAVING 절에서만 서브 쿼리 사용이 가능하다.
  2. SELECT 절도 가능(하이버네이트 지원).
  3. FROM 절의 서브 쿼리는 현재 JPQL에서 불가능하다.
  4. 조인으로 풀 수 있으면 풀어서 해결한다.
  5. FROM 절에서 사용하고 싶은 경우 Native SQL을 사용해야 한다.

⚡ 02. JPQL 타입 표현과 기타식

  • 문자 : ‘HELLO’, ‘She”s’
  • 숫자 : 10L(Long), 10D(Double), 10F(Float)
  • Boolean : TRUE, FALSE
  • ENUM : jpabook.MemberType.Admin(패키지명 포함)
  • 엔티티 타입 : TYPE(m) = Member(상속 관계에서 사용)

02-1. JPQL 기타

기본적으로 표준 SQL은 다 지원한다

  • SQL과 문법이 같은 식
  • EXISTS, IN
  • AND, OR, NOT
  • =, >, >=, <, <=, <>
  • BETWEEN, LIKE, IS NULL

⚡ 03. 조건식 - CASE 식

기본적으로 RDBMS에서 사용하는 SQL과 비슷한 것 같다.

03-1. 기본 CASE 식

select
    case when m.age <= 10 then '학생요금'
         when m.age >= 60 then '경로요금'
         else '일반요금'
    end
from Member m

03-2. 단순 CASE 식

select
    case t.name
        when '팀A' then '인센티브110%'
        when '팀B' then '인센티브120%'
        else '인센티브105%'
    end
from Team t

03-3. JPQL CASE 식 예제

try {
    JpqlTeam team = new JpqlTeam();
    team.setName("teamA");
    em.persist(team);

    JpqlMember member = new JpqlMember();
    member.setUsername("teamA");
    member.setAge(10);
    member.setType(MemberType.USER);

    member.setTeam(team);
    em.persist(member);

    em.flush();
    em.clear();

    String query =
            "select " +
                    "case when m.age <= 10 then '학생요금' " +
                    "     when m.age >= 60 then '경로요금' " +
                    "     else '일반요금' " +
                    "end " +
                    "from JpqlMember m";
    List<String> resultList = em.createQuery(query, String.class).getResultList();

    for (String s : resultList) {
        System.out.println("s  = " + s);
    }
    tx.commit();
} catch (Exception e) {
    tx.rollback();
} finally {
    em.close();
}
emf.close();

03-4. 조건식 - CASE 식

--사용자 이름이 없으면 이름 없는 회원을 반환
select coalesce(m.username, '이름 없는 회원') from Member m
  • COALESCE: 하나씩 조회해서 null이 아닌면 반환.
--사용자 이름이 '관리자'면 null을 반환하고 나머지는 본인의 이름을 반환
select NULLIF(m.username, '관리자') from Member m
  • NULLIF: 두 값이 같으면 null 반환, 다르면 첫 번째 값 반환.
try {
    JpqlTeam team = new JpqlTeam();
    team.setName("teamA");
    em.persist(team);

    JpqlMember member = new JpqlMember();
    // member.setUsername("teamA");
    // member.setUsername(null); // coalesce 테스트
    // member.setUsername("관리자"); // NULLIF 테스트 => null 반환 해야함
    member.setUsername("졸리다"); // NULLIF 테스트 => null 반환 해야함
    member.setAge(10);
    member.setType(MemberType.USER);

    member.setTeam(team);
    em.persist(member);

    em.flush();
    em.clear();

    // 01. case 식 사용
    String query =
            "select " +
                    "case when m.age <= 10 then '학생요금' " +
                    "     when m.age >= 60 then '경로요금' " +
                    "     else '일반요금' " +
                    "end " +
                    "from JpqlMember m";
    List<String> resultList1 = em.createQuery(query, String.class).getResultList();

    // 02. coalesce :  하나씩 조회해서 null이 아니면 반환
    String query2 = "select coalesce(m.username, '이름 없는 회원') from JpqlMember m";
    List<String> resultList2 = em.createQuery(query, String.class).getResultList();

    // 03. NULLIF : 두 값이 같으면 null 반환, 다르면 첫 번째 값 반환
    String query3 = "select NULLIF(m.username, '관리자') from JpqlMember m";
    List<String> resultList3 = em.createQuery(query, String.class).getResultList();

    for (String s : resultList3) {
        System.out.println("s  = " + s);
    }
    tx.commit();
} catch (Exception e) {
    tx.rollback();
} finally {
    em.close();
}
  • Java 소스 내에서 해당 표준 함수 테스트.

⚡ 04. JPQL 기본 함수

  • CONCAT
  • SUBSTRING
  • TRIM
  • LOWER, UPPER
  • LENGTH
  • LOCATE
  • ABS, SORT, MOD
  • SIZE, INDEX(JPA 용도)

04-1. Concat

// 01. concat -> 문자열 붙히기
String query = "select concat('a', 'b') from JpqlMember m";
String query2 = "select 'a' || 'b' from JpqlMember m";

04-2. Substring

// 02. substring -> 문자열 위치 기반 자르기
String query3 = "select substring(m.username, 2, 3) from JpqlMember m";

04-3. Trim

// 03. trim -> 공백 제거
String query4 = "select trim(m.username) from JpqlMember m";

04-4. Locate

// 04. locate -> indexOf랑 비슷한 듯
String query5 = "select locate('de', 'abcdefg') from JpqlMember m";

04-5. size

// 05. size -> collection의 크기를 반환 한다.
String query6 = "select size(t.memberList) from JpqlTeam t";

04-6. index

// 06. index -> 사용 안하는게 좋음, 값 타입 컬렉션의 위치값을 반환 할 때 사용
String query7 = "select index(t.memberList) from JpqlTeam t";

04-7. 사용자 정의 함수

// 07. 사용자 정의 함수 등록
String query8 = "select function('group_concat', m.username) from JpqlMember m";
List<String> resultList = em.createQuery(query8, String.class).getResultList();
  • 하이버네이트 사용전 방언에 추가해야 한다.
  • 사용하는 DB 방언을 상속받고, 사용자 정의 함수를 등록한다.

참고 자료

댓글남기기