[JPA] 서브쿼리, 타입표현과 기타식, CASE식, 함수
📌 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";
// 중략
- JPA는 WHERE, HAVING 절에서만 서브 쿼리 사용이 가능하다.
 - SELECT 절도 가능(하이버네이트 지원).
 - FROM 절의 서브 쿼리는 현재 JPQL에서 불가능하다.
 - 조인으로 풀 수 있으면 풀어서 해결한다.
 - 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 방언을 상속받고, 사용자 정의 함수를 등록한다.
 
      
댓글남기기