Foundry
데이터베이스
기초
핵심

조인 종류와 뷰

조인 종류별로 결과 행이 어떻게 결정되는지, 그리고 뷰가 무엇을 해주고 무엇을 해주지 않는지

조인 종류와 뷰

두 테이블의 행을 어떤 규칙으로 짝지을지 정하는 것이 조인이고, 그 쿼리에 이름을 붙인 것이 뷰다

조인 종류

A 테이블 3행, B 테이블 2행이고 그중 1행씩만 조건에 맞는다고 해봅시다.

종류남기는 것결과 행수
INNER양쪽 모두 맞는 것1
LEFT OUTER왼쪽 전부 + 맞는 오른쪽3
RIGHT OUTER오른쪽 전부 + 맞는 왼쪽2
FULL OUTER양쪽 전부4
CROSS조건 없이 모든 조합6

CROSS JOIN 은 조건이 없어서 두 테이블의 행수를 곱한 결과가 나옵니다. 10행과 5행이면 50행입니다. 이것을 카티전 곱이라 부릅니다.

CROSS JOIN 이 사고가 되는 경우

의도해서 쓰는 경우는 드물고, 조인 조건을 빠뜨렸을 때 실수로 발생합니다.

상황결과
10만 행과 10만 행100억 행. 쿼리가 끝나지 않는다
조건절에 조인 조건 누락옛 문법에서 WHERE 를 빼먹으면 그렇게 된다

의도적으로 쓰는 곳도 있습니다. 날짜 테이블과 상품 테이블을 곱해 빈 조합까지 포함한 집계표를 만드는 경우입니다.

조인이 행수를 늘리는 것

한 행이 여러 행과 맞으면 결과가 늘어납니다. 주문 1건에 주문상품 3건이 있으면 조인 결과는 3행이 되고, 주문 금액을 그대로 합하면 세 배가 됩니다.

문제대응
집계가 부풀려진다먼저 집계한 뒤 조인하거나, DISTINCT 를 쓴다
행이 예상보다 많다조인 키가 유일한지 확인한다

쿼리에 이름을 붙여 테이블처럼 쓰는 것입니다.

해주는 것내용
복잡한 쿼리 감추기여러 조인을 한 이름으로
권한 제어특정 컬럼만 노출하는 뷰를 주고 원본 접근은 막는다
호환 유지테이블 구조가 바뀌어도 뷰 이름으로 옛 형태를 유지한다
해주지 않는 것내용
성능 향상조회할 때마다 원래 쿼리가 실행된다. 결과를 저장하지 않는다
인덱스뷰 자체에는 인덱스를 만들 수 없다. 원본 테이블의 인덱스를 쓴다
무조건 갱신 가능집계나 여러 테이블 조인이 든 뷰는 갱신할 수 없다

첫 번째가 가장 자주 오해됩니다. 뷰는 저장된 쿼리일 뿐입니다. 결과를 저장하고 싶으면 구체화된 뷰를 써야 하고, 그것은 갱신 시점을 관리해야 합니다.

면접에서 이렇게 나옵니다

Q.CROSS JOIN 의 결과 행수는 어떻게 정해지나요?

조건이 없으므로 두 테이블 행수의 곱입니다. A 가 10행, B 가 5행이면 50행입니다.

종류결과 행수의 결정
CROSS왼쪽 행수 × 오른쪽 행수
INNER조건에 맞는 조합만
LEFT OUTER왼쪽 행수 이상. 오른쪽에 여러 개 맞으면 늘어난다

실무에서 의도적으로 쓰는 경우는 드뭅니다. 대개 조인 조건을 빠뜨린 실수로 나타납니다. 10만 행과 10만 행이면 100억 행이라 쿼리가 끝나지 않고 디스크와 메모리를 소진합니다.

의도해서 쓰는 곳도 있습니다.

용도
빈 조합까지 포함한 집계표날짜 × 상품으로 모든 칸을 만들고 실적을 붙인다
조합 생성사이즈 × 색상으로 가능한 옵션을 만든다
테스트 데이터행을 곱해 대량 데이터를 만든다

흔한 실수: LEFT JOIN 이 왼쪽 행수를 그대로 유지한다고 생각하는 것. 오른쪽에 여러 행이 맞으면 왼쪽 행이 그만큼 복제되어 늘어납니다.

Q.뷰에 대해 사실이 아닌 설명은 무엇인가요?

"뷰가 성능을 향상시킨다"가 사실이 아닙니다. 뷰는 저장된 쿼리이고 결과를 저장하지 않습니다.

사실사실이 아닌 것
복잡한 쿼리를 이름으로 감춘다결과를 저장해 조회가 빨라진다
권한을 컬럼 단위로 제한할 수 있다뷰에 인덱스를 만들 수 있다
원본이 바뀌면 뷰 결과도 바뀐다항상 갱신할 수 있다
조건에 맞으면 갱신도 가능하다별도 저장 공간을 차지한다

뷰를 조회하면 그 안의 쿼리가 그때 실행됩니다. 느린 쿼리를 뷰로 감싸면 여전히 느립니다. 오히려 느린 것을 감춰서 원인을 찾기 어려워지는 부작용이 있습니다.

결과를 저장하려면 구체화된 뷰를 써야 하고, 그러면 갱신 시점을 관리해야 합니다. 그것은 실질적으로 집계 테이블을 두는 것과 같습니다.

흔한 실수: 뷰를 여러 겹으로 쌓는 것. 뷰가 뷰를 참조하고 그것이 또 뷰를 참조하면, 실행 계획이 예측하기 어려워지고 불필요한 조인이 실행됩니다.

Q.조인 때문에 집계가 부풀려지는 문제를 어떻게 막나요?

한 행이 여러 행과 맞으면 원본 행이 복제되어 합계가 늘어납니다.

주문 1건(금액 10,000)에 주문상품 3건이 있는 경우입니다.

쿼리결과
조인 후 주문금액 SUM30,000. 세 배가 된다
먼저 집계 후 조인10,000. 정확하다
대응내용
먼저 집계하고 조인서브쿼리에서 집계한 뒤 붙인다. 가장 안전하다
창 함수조인 상태에서 중복 없이 집계한다
DISTINCT 집계SUM(DISTINCT ...) 는 값이 같으면 하나로 세므로 위험하다
조인 키 유일성 확인일대일인지 일대다인지 먼저 파악한다

세 번째는 함정입니다. 금액이 우연히 같은 두 주문이 있으면 하나로 세어 결과가 틀어집니다.

네 번째가 근본입니다. 조인하기 전에 어느 쪽이 일대다인지 알면 부풀려지는 것을 예상할 수 있습니다.

흔한 실수: 행수만 보고 정상이라 판단하는 것. 일대일 조인이라도 원본에 중복이 있으면 늘어납니다. 조인 키에 유일 제약이 있는지 확인해야 합니다.

Q.LEFT JOIN 인데 결과가 왼쪽 행수보다 적어지는 경우가 있나요?

조인 자체로는 줄지 않지만, WHERE 절에 오른쪽 테이블 조건을 걸면 줄어듭니다.

쿼리결과
LEFT JOIN b ON a.id=b.a_id왼쪽 전부 유지
LEFT JOIN b ON a.id=b.a_id WHERE b.status='X'오른쪽이 없는 행은 status 가 NULL 이라 걸러진다
LEFT JOIN b ON a.id=b.a_id AND b.status='X'왼쪽 전부 유지. 조건은 짝짓기에만 쓰인다

두 번째가 사실상 INNER JOIN 이 됩니다. LEFT JOIN 을 썼는데 결과가 왼쪽보다 적으면 이 경우를 의심합니다.

차이는 조건이 언제 적용되는지입니다.

위치적용 시점
ON 절짝을 지을 때. 안 맞으면 오른쪽이 NULL 로 남는다
WHERE 절조인이 끝난 결과에서 걸러낸다

NULL 을 허용하려면 WHERE b.status='X' OR b.a_id IS NULL 처럼 명시해야 합니다.

흔한 실수: 필터를 습관적으로 WHERE 에 쓰는 것. INNER JOIN 에서는 ON 과 WHERE 가 같은 결과를 주지만, OUTER JOIN 에서는 완전히 다릅니다.

먼저 스스로 답해보고 아래 답변과 견줘보세요. 막히는 부분은 문제로 확인할 수 있어요.

더 깊이 공부하기

읽었으면 문제로 확인해보세요

데이터베이스 문제를 풀면 틀린 문제가 자동으로 노트에 쌓입니다. 가입 없이 5문제를 먼저 풀어볼 수도 있어요.