ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [SQL] JOIN
    SQL 공부 2023. 10. 26. 10:11
    728x90

    조인 ( join )

    두개 이상의 테이블이나 데이터 베이스를 연결하여 데이터를 검색하는 방법

    테이블을 연결하려면, 적어도 하나의 칼럼을 서로 공유하고 있어야 하므로 이를 이용하여 데이터 검색에 활용한다.

     

     

    INNER JOIN ( 교집합 )

    기존 테이블과 join 테이블의 중복된 값을 보여준다

    inner join

    SELECT A테이블.컬럼, B테이블.컬럼 from A테이블 join B테이블 on A테이블.컬럼 = B테이블.컬럼;
    
    A.NAME, B.AGE
    FROM EX_TABLE A
    INNER JOIN JOIN_TABLE B ON A.NO_EMP = B.NO_EMP
    • on 대신에 where을 사용할 수 있다
    • 결합 조건을 만족하는 데이터만 선택 ( on 뒤에 이렇게 쓰면 된다 )

     

     

    Outer Join

    Outer Join은 결합 조건을 만족하지 않아도, 그 종류에 따라 특정 테이블의 데이터를 모두 선택해 테이블을 결합한다
    • 첫 번째 테이블의 데이터를 모두 선택한 후, 두 번째 테이블의 데이터를 결합 조건에 따라 매칭한다. 이때 매칭되는 데이터가 없는 경우 그 값을 null로 표시한다.

     

     

    LEFT OUTER JOIN

    기준 테이블과 조인테이블과 중복된 값을 보여준다 ( 왼쪽 테이블 기준으로 join )

    left outer join

    SELECT A테이블.컬럼, B테이블.컬럼 from A테이블 left outer join B테이블 on A테이블.컬럼 = B테이블.컬럼;
    
    SELECT
    A.NAME, B.AGE
    FROM EX_TABLE A
    LEFT OUTER JOIN JOIN_TABLE B ON A.NO_EMP = B.NO_EMP

     

    RIGHT OUTER JOIN

    Left 랑 반대로 오른쪽 테이블 기준으로 조인 

    right outer join

    SELECT A테이블.컬럼, B테이블.컬럼 from A테이블 right outer join B테이블 on 
    A테이블.컬럼 = B테이블.컬럼;
    

     

     

    FULL OUTER JOIN ( 합집합 )

    A와 B 테이블의 모든 데이터가 검색된다.

    full outer join

     

    SELECT A테이블.컬럼, B테이블.컬럼 from A테이블 full outer join B테이블 on A테이블.컬럼 = B테이블.컬럼;
    
    SELECT
    A.NAME, B.AGE
    FROM EX_TABLE A
    FULL OUTER JOIN JOIN_TABLE B ON A.NO_EMP = B.NO_EMP
    
    • 두 테이블의 데이터를 모두 선택하고, 결합 조건에 따라 데이터를 매칭한다. 이때 매칭되는 데이터가 없는 경우 그 값을 null로 표시한다

     

    CROSS JOIN

    모든 경우의 수를 전부 표현하는 방식 (상호 조인이라고 부른다 )

    cross join

     

    on 구문을 사용할 수 없다. ( 대용량 테이블을 생성할 경우에 사용한다 대신에 시스템이 다운되거나 디스크 용량이 꽉 차 버릴 수도 있으니 조심하기 )

    SELECT A테이블.컬럼, B테이블.컬럼 from A테이블 cross join B테이블;
    
    SELECT
    A.NAME, B.AGE
    FROM EX_TABLE A
    CROSS JOIN JOIN_TABLE B
    

     

    SELF JOIN

    자기자신과 자기자신을 조인하는 것이다. ( 하나의 테이블 여러번 복사해서 조인한다고 생각하면 된다. 자신이 갖고 있는 컬럼을 다양하게 변형시켜 활용할 때 자주 사용한다 )

    sef join

     

    SELECT A테이블.컬럼, B테이블.컬럼 from A테이블 B테이블
    
    SELECT
    A.NAME, B.AGE
    FROM EX_TABLE A, EX_TABLE B
    

     

    FETCH JOIN

     

    다대일, 일대일 관계에서 Sql query 문을 증가하지 않고, JPQL에서 조회하는데 성능을 최적화 시켜주는 join 으로 연관된 엔티티나 컬렉션을 SQL 한번에 조회하기 위해 사용되는 것이다
    fetch join -> [LFET[OUTER] INSERT] JOIN FETCH
    
    -- JPQL 
    select m from Member m join fetch m.team
    
    -- DB JOIN
    select M.*, T.* from Member m inner join Team t on m.tead_id = T.id

     

    fetch join 사용하지 않은 예시와 사용한 예시

    // 아래 예시는 fetch join을 사용하지 않은 예제 입니다
    try {
        JpqlTeam teamA = new JpqlTeam();
        teamA.setName("팀A");
        em.persist(teamA);
    
        JpqlTeam teamB = new JpqlTeam();
        teamB.setName("팀B");
        em.persist(teamB);
    
        JpqlMember member1 = new JpqlMember();
        member1.setUsername("회원1");
        member1.setTeam(teamA);
        em.persist(member1);
    
        JpqlMember member2 = new JpqlMember();
        member2.setUsername("회원2");
        member2.setTeam(teamA);
        em.persist(member2);
    
        JpqlMember member3 = new JpqlMember();
        member3.setUsername("회원3");
        member3.setTeam(teamB); //팀B에 속하는 회원
        em.persist(member3);
    
        em.flush();
        em.clear();
    
        // 회원 조회
        String query = "select m FROM JpqlMember m";
        List<JpqlMember> memberList = em.createQuery(query, JpqlMember.class)
                                        .getResultList();
    
        // 회원1, 팀A(DB에 SQL을 날려서 조회)
        // 회원2, 팀A(DB에 SQL을 날리지 않고 1차캐시에서 조회)
        // 회원3, 팀B(DB에 SQL을 날려서 조회)
        for (JpqlMember member : memberList) {
            System.out.println("member = " + member.getUsername()
                                           + ", " + member.getTeam().getName());
        }

    Loop 문에서 발생되는 상황

    1. 회원1, 팀A( DB에 SQL을 날려서 조회 )

    - 첫번째, 두번째 SQL을 의미한다.

     

    2. 회원2, 팀A( DB에 SQL을 날리지 않고 1차 캐시에서 조회 )

    - 영속성 컨텍스트에 올라와 있기에 DB조회를 안해도 된다.

     

    3. 회원3, 팀B( DB에 SQL을 날려서 조회 )

    - 영속성 컨텍스트에 존재하지 않기에 쿼리를 날린다

     

    => 이러면 쿼리가 3번 나간다

    // fetch join
    String query = "select m FROM JpqlMember m join fetch m.team";
    List<JpqlMember> memberList = em.createQuery(query, JpqlMember.class)
                                    .getResultList();
    
    for (JpqlMember member : memberList) {
        System.out.println("member = " + member.getUsername()
                                        + ", " + member.getTeam().getName());
    }
    
    // 이런식으로 쓰인다
    @Query("select DISTINCT o from Owner o join fetch o.pets")

    지연 로딩을 설정해도, fetch join이 항상 우선시 된다.

     

    fetch join을 사용하면 위와 다르게 JPQL이 하이버네이트를 통해서 SQL로 바뀐걸 보면 join fetch 키워드가 들어가고 일반 join과 다르게 한방에 데이터를 조회할 수 있다. 또한 여기서 보면 Member랑 Team을 조인해서 가져올 때 프록시가 아니라 실제 데이터를 가져온다.

     

     

    컬렉션 fetch join

    @Entity
    public class Member {
    
        //..중략
    
        // @OneToOne, @ManyToOne은 default가 즉시 로딩이기 때문에, 반드시 지연로딩으로 설정해야 한다
        @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "TEAM_ID")
        private Team team;
    
        //..중략
    }
    
    
    @Entity
    public class Team {
    
        //..중략
    
        @OneToMany(mappedBy = "team")
        private List<Member> memberList = new ArrayList<>();
    
        //..중략
    }

    - @OneToMany관계에선, JPQL에서 데이터를 조회할 때 target을 Team으로 한다는 의미이다

    - DB입장에서 1:N 조인이 일어나면 데이터가 뻥튀기될 수 있다

    - N:1은 데이터 뻥튀기가 일어나지 않는다

    -- JPQL
    select t
    from Team t join fetch t.memberList -- memberList => List<Member> => Collection fetch join
    where t.name = '팀A'
    
    
    -- DB
    SELECT T.*, M.*
    FROM TEAM T
    INNER JOIN MEMBER M
    ON T.ID=M.TEAM_ID
    WHERE T.NAME = '팀A'

     

    - JPA는 이렇게 묶인 (2개의 row) 데이터를 알 수가 없다.

    컬렉션 페치 조인을 사용하면 코드 중복이 발생 가능하니 distinct랑 같이 쓰기

     

    fetch join & DISTINCT

    - SQL의 중복된 결과를 제거한다

    -- JPQL
    select distinct t
    from JpqlTeam t join fetch t.memberList
    where t.name = '팀A'

    -> 같은 식별자를 가진 Team 엔티티를 제거한다.

     

    fetch join & 일반 join의 차이

    - 일반 조인은 연관된 엔티티를 함께 조회하지 않는다.

    - 단지 select절에 지정한 엔티티만 조회한다

    -- JPQL
    select t
    from Team t join t.memberList m
    where t.name = '팀A'
    
    -- DB
    SELECT T.*
    FROM TEAM T
    INNER JOIN MEMBER M ON T.ID=M.TEAM_ID
    WHERE T.NAME = '팀A'

    -> 여기서는 팀엔티티만 조회하고, 회원 엔티티는 조회하지 않는다.

     

    - fetch join은 즉시로딩으로 연관된 엔티티를 함께 조회한다

    - fetch join은 객체 그래프를 SQL한번에 조회하는 개념이다

     

     

    fetch join 의 단점

     

    • 쿼리 한번에 모든 데이터를 가져오기 때문에 JPA가 제공하는 Paging API 사용 불가능(Pageable 사용 불가)
    • 1:N 관계가 두 개 이상인 경우 사용 불가
    • 패치 조인 대상에게 별칭(as) 부여 불가능
    • 번거롭게 쿼리문을 작성해야 함

     

    참고자료

    https://ym1085.github.io/jpa/JPA-페치조인-기본/

Designed by Tistory.