JPQL 조인과 서브 쿼리
참고
조인
- JPQL에서도 조인을 사용할 수가 있다.
- Inner Join
- Outer Join
- Self Join
- Cross Join
- 야구 선수들을 나타내는 Player 테이블
선수 이름 | 포지션 | 연봉 | 팀 |
---|---|---|---|
p1 | 투수 | 450 | t1 |
p2 | 포수 | 200 | t1 |
p3 | 유격수 | 50 | t2 |
p4 | 1루수 | 0 | - |
p5 | 투수 | 0 | - |
p6 | 투수 | 0 | - |
- 야구팀을 나타내는 Team 테이블
팀 이름 | 연봉 합계 |
---|---|
t1 | 1000 |
t2 | 2000 |
t3 | 500 |
t4 | 400 |
t5 | 2500 |
t6 | 1000 |
Inner Join
1
2
3
4
em.createQuery("SELECT p.name FROM Player p inner join Team t on p.team = t", Object[].class)
.getResultList();
- SELECT [조회할 멤버 필드] FROM [좌측 엔티티] (inner) join [우측 엔티티] on [조인 조건];
- inner join이라 팀이 있는 애들만 조회된 것을 볼 수 있다.
Outer Join
1
2
3
em.createQuery("SELECT ")
Self Join
1
2
3
4
List<Player> resultList = em.createQuery("SELECT p2 FROM Player p1 JOIN Player p2 ON p1.position = p2.position WHERE p1.name='p1'", Player.class)
.getResultList();
- 이름이 p1인 플레이어와 같은 포지션인 선수를 모두 조회해오는 셀프 조인 쿼리이다.
Cross Join
1
2
3
4
List<Object[]> resultList = em.createQuery("SELECT p.name, t.name FROM Player p, Team t", Object[].class)
.getResultList();
- 이렇게 하면 [모든 플레이어 이름] * [모든 팀 이름]의 크로스 조인 결과를 반환한다.
Theta Join
1
2
3
4
List<Player> resultList = em.createQuery("select p from Player p, Team t where p.name = t.name", Player.class)
.getResultList();
- 연관 관계는 없지만 양쪽 테이블의 조인 조건을 비교 연산자로 표현할 수 있는 조인을 말한다.
서브 쿼리
- JPQL도 서브 쿼리와 서브 쿼리 함수를 지원한다.
1
2
3
4
List<Team> resultList = em.createQuery("SELECT t1 FROM Team t1 WHERE t1.totalSalary > (SELECT AVG(t2.totalSalary) FROM Team t2)", Team.class)
.getResultList();
- 팀 샐러리 평균보다 높은 팀들을 반환한다.
- 메인 쿼리에서는 t1만 사용하고 서브 쿼리에서는 t2만 사용했다.
- 메인 쿼리와 서브 쿼리가 전혀 연관이 없다.
- 이렇게 짜야 성능이 좋다고 한다.
1
2
3
4
List<Team> resultList = em.createQuery("SELECT t FROM Team t WHERE t.totalSalary > (SELECT t.totalSalary FROM Team t WHERE t.name = 't1')", Team.class)
.getResultList();
- t1 팀보다 팀 연봉이 높은 팀들을 반환하는 쿼리이다. (where 절로 조건을 넣으면 되지만 마땅한 예제가 떠오르지 않아서..)
- 메인 쿼리에서 서브 쿼리로 같은 t를 사용한다.
- 이런 경우 성능이 안좋다고 한다.
- sql에서도 마찬가지라고 한다.
서브 쿼리 지원 함수
- sql과 마찬가지로 서브 쿼리 함수가 있다.
함수 | 기능 |
---|---|
(NOT) EXISTS | 서브 쿼리에 결과가 존재하면 true |
ALL | 조건이 모두 만족하면 true |
ANY, SOME | 조건 중 하나라도 만족하면 true |
(NOT) IN | 결과 중 하나라도 같은 것이 있으면 true |
EXISTS
1
2
3
4
em.createQuery("SELECT p FROM Player p WHERE EXISTS(SELECT t FROM Team t WHERE p.team = t)", Player.class)
.getResultList();
- 팀이 존재하는 플레이어들을 조회하는 쿼리이다.
- NOT EXISTS를 쓴다면 반대로 팀이 존재하지 않는 플레이어들을 조회하는 쿼리가 된다.
ALL, ANY, SOME
1
2
3
4
em.createQuery("SELECT p FROM Player p WHERE p.team = ANY(SELECT t FROM Team t WHERE t.totalSalary = 1000)", Player.class)
.getResultList();
- 팀 연봉이 1000인 팀들에 소속된 플레이어들을 조회한다.
- 팀 연봉이 1000인 팀은 t1과 t6이다.
- 따라서 서브 쿼리가 반환하는 레코드는 2개가 된다.
- 이 두 개의 팀 중에 아무거나 해당된다면 true가 된다.
- t1에 소속한 p1, p2가 조회된다.
- 만약 ANY가 아닌 ALL을 사용한다면,
- 서브 쿼리가 반환하는 두 개의 팀 p1과 p2에 모두 만족해야 true가 된다.
- 어느 플레이어 레코드도 t1과 t6 두 팀에 소속될 순 없다.
- 따라서 어느 것도 조회되지 않는다.
1
2
3
4
em.createQuery("SELECT p FROM Player p WHERE p.salary > ALL(SELECT t.totalSalary FROM Team t WHERE t.totalSalary < 1000)", Player.class)
.getResultList();
- 팀 연봉이 1000보다 낮은 팀들보다 연봉이 높은 선수를 찾는 쿼리이다.
- 팀 연봉이 1000보다 낮은 팀은 500인 t3와 400인 t4이다.
- 연봉이 400보다 높은 선수는 연봉이 450인 t1 뿐이다.
- 하지만 500인 t3보다는 연봉이 낮다.
- 모든 결과를 만족하지 못했으므로 t1도 조회에서 제외된다.
- 만약 ANY를 사용했다면 t1은 400인 t4보다 높기 때문에 true가 된다.
1
2
3
4
em.createQuery("SELECT p FROM Player p WHERE p.salary > ALL(SELECT t.totalSalary FROM Team t WHERE t.totalSalary < 500)", Player.class)
.getResultList();
- 팀 연봉이 500보다 낮은 팀들로 기준을 낮췄더니 t1이 모든 조건을 만족할 수 있었다.
IN
1
2
3
em.createQuery("SELECT p FROM Player p WHERE p.salary IN (450, 350, 200, 100)")
- 연봉 450, 100에 해당하는 p1과 p2가 조회되는 걸 볼 수 있다.
JPA 서브 쿼리의 한계
- JPA의 JPQL은 WHERE, HAVING 절에서만 서브 쿼리 사용이 가능하다.
- Hibernate의 JPQL은 SELECT 절에서도 사용 가능.
- FROM 절에서 서브 쿼리는 현재 JPQL에서 사용 불가능하다.
- 조인으로 풀 수 있으면 조인으로 푸는게 좋다.
- 아니라면 네이티브 쿼리를 사용해야 한다.
This post is licensed under CC BY 4.0 by the author.