[MariaDB] JOIN, UNION, 서브쿼리, GROUP BY, 집계 함수, HAVING

이지연·2025년 11월 25일

개요

JOIN, UNION, 서브쿼리, GROUP BY, 집계 함, HAVING를 대표 예제와 함께 정리!
+프로그래머스에서 풀이한 문제도 업로드 예정


1. JOIN 개념

  • JOIN은 여러 테이블에서 데이터를 공통 키를 기준으로 결합해 하나의 결과 집합으로 만드는 방법이다.
  • 테이블 간 데이터 연관성을 표현하기 위해 필수로 사용된다.

참고 예시 테이블

  • author 테이블: id (PK), name, email
  • post 테이블: id (PK), author_id (FK), title, contents

1-1. INNER JOIN (교집합)

  • 두 테이블 모두 조인 조건에 맞는 행만 결과에 포함.
SELECT *
FROM author
INNER JOIN post ON author.id = post.author_id;
  • alias 사용 예
SELECT a.*, p.*
FROM author a
INNER JOIN post p ON a.id = p.author_id;

1-2. LEFT JOIN (왼쪽 테이블 기준 전체 + 오른쪽 매칭 데이터)

  • 왼쪽 테이블의 모든 행이 결과에 포함되고,
  • 오른쪽 테이블에 매칭되는 행이 없으면 NULL로 표시됨.
SELECT *
FROM author a
LEFT JOIN post p ON a.id = p.author_id;

1-3. RIGHT JOIN (오른쪽 테이블 기준 전체 + 왼쪽 매칭 데이터)

  • RIGHT JOIN은 LEFT JOIN의 반대.
  • 오른쪽 테이블의 모든 행과 왼쪽의 매칭되는 데이터 포함.
SELECT *
FROM author a
RIGHT JOIN post p ON a.id = p.author_id;

1-4. 조인 요약 & 팁

조인 종류설명
INNER JOIN두 테이블 모두 조건에 맞는 행만 반환 (교집합)
LEFT JOIN왼쪽 테이블 전체 + 오른쪽 매칭 데이터, 매칭 없으면 NULL
RIGHT JOIN오른쪽 테이블 전체 + 왼쪽 매칭 데이터, 매칭 없으면 NULL
FULL OUTER JOIN왼쪽과 오른쪽 모두 포함 (MySQL은 지원하지 않음)
  • SELECT * 대신 a.*, p.* 처럼 alias와 컬럼 명시를 권장한다.
  • INNER JOIN은 양쪽 순서에 상관없이 결과 동일하나, LEFT/RIGHT JOIN은 기준 테이블에 따라 결과가 달라진다.

2. UNION (결과 행 결합)

  • 두 SELECT 결과를 행 단위로 합친다.
  • JOIN은 컬럼(종)을 합치고, UNION은 행(횡)을 합친다.
SELECT 컬럼1, 컬럼2 FROM TABLE1
UNION
SELECT 컬럼1, 컬럼2 FROM TABLE2;
  • 조건: 두 SELECT의 컬럼 개수와 타입이 같아야 한다.
  • UNION은 중복 제거, UNION ALL은 중복 포함.

3. 서브쿼리 (Subquery)

  • SELECT 문 안에 또 다른 SELECT를 포함한 쿼리.
  • JOIN보다 복잡하거나 조건에 따라 분리할 때 사용.

WHERE 절 서브쿼리 예

SELECT *
FROM author
WHERE id IN (
    SELECT author_id
    FROM post
);

SELECT 절 서브쿼리 예

SELECT email,
       (SELECT COUNT(*) FROM post p WHERE p.author_id = a.id) AS post_count
FROM author a;

FROM 절 서브쿼리 예 (테이블 역할)

SELECT a.*
FROM (SELECT * FROM author) AS a;

4. GROUP BY (데이터 그룹화)

  • 특정 컬럼 기준으로 데이터를 묶어 그룹별 통계 및 집계 함수 실행.
SELECT name, COUNT(*)
FROM author
GROUP BY name;
  • GROUP BY에 포함되지 않은 컬럼을 SELECT 할 경우 오류가 발생할 수 있음.

예시: 글쓴이 ID별 그룹핑

SELECT author_id, COUNT(*) AS post_count
FROM post
GROUP BY author_id;

4-1. 그룹핑 + LEFT JOIN + NULL 처리 예

SELECT a.email, COUNT(p.id) AS post_count
FROM author a
LEFT JOIN post p ON a.id = p.author_id
GROUP BY a.email;
  • LEFT JOIN 활용 시, 글을 쓰지 않은 회원도 포함할 수 있다.

5. 집계 함수 (Aggregate Functions)

함수명설명예시
COUNT()행 개수 세기SELECT COUNT(*) FROM author;
SUM()합계SELECT SUM(age) FROM author;
AVG()평균SELECT AVG(age) FROM author;
ROUND()소수점 반올림SELECT ROUND(AVG(age), 3) FROM author;
MIN()최솟값SELECT MIN(age) FROM author;
MAX()최댓값SELECT MAX(age) FROM author;

집계 함수 실전 예시

SELECT name,
       COUNT(*) AS '동명이인 수',
       AVG(age) AS '동명이인의 평균 나이'
FROM author
GROUP BY name;

날짜별 게시글 수 출력 예시

SELECT DATE_FORMAT(created_time, '%Y-%m-%d') AS '날짜',
       COUNT(*) AS '게시글 수'
FROM post
WHERE created_time IS NOT NULL
GROUP BY DATE_FORMAT(created_time, '%Y-%m-%d');

6. HAVING 절 (그룹화 후 조건 지정)

  • HAVING 절은 GROUP BY로 그룹화된 결과에 조건을 지정할 때 사용한다.
  • 반면 WHERE 절은 그룹화 전에 전체 데이터를 필터링하는 역할을 한다.
-- 글을 2번 이상 쓴 author_id 조회
SELECT author_id, COUNT(*)
FROM post
GROUP BY author_id
HAVING COUNT(*) >= 2;
  • HAVING은 집계 함수(COUNT, SUM, AVG 등)를 조건으로 직접 사용할 수 있다.

7. 다중 컬럼 GROUP BY

  • 여러 컬럼을 순차적으로 그룹화할 수 있다.
  • 먼저 첫 번째 컬럼을 기준으로 그룹핑하고, 그 다음 두 번째 컬럼을 기준으로 그룹핑하며 집계한다.
-- 작성자(author_id)별로, 같은 제목(title)을 가진 글 개수 출력
SELECT author_id, title, COUNT(*)
FROM post
GROUP BY author_id, title;
  • 그룹화 순서를 명확히 이해하고 쿼리를 작성하는 것이 중요하다.

사용 시 주의점 및 팁

  • HAVING은 반드시 GROUP BY와 함께 사용해야 한다.
  • HAVING 조건에는 집계 함수뿐 아니라 컬럼 별칭도 사용할 수 있다(다만 DBMS에 따라 다를 수 있음).
  • 다중 컬럼 GROUP BY시 그룹화 순서에 따라 결과가 달라지므로 결과 의도를 명확히 해야 한다.

프로그래머스 문제 풀이

  • [조건에 맞는 도서와 저자 리스트 출력하기]
    https://school.programmers.co.kr/learn/courses/30/lessons/144854

  • [없어진 기록 찾기]
    https://school.programmers.co.kr/learn/courses/30/lessons/59042

    # JOIN만 사용
    SELECT o.ANIMAL_ID, o.NAME 
    from ANIMAL_OUTS o
    left join ANIMAL_INS i
    on i.ANIMAL_ID=o.ANIMAL_ID
    where i.ANIMAL_ID is null 
    order by o.ANIMAL_ID;
    
    # 서브쿼리 풀이법(1)
    SELECT ANIMAL_OUTS.ANIMAL_ID, ANIMAL_OUTS.NAME
    FROM ANIMAL_OUTS
    WHERE ANIMAL_OUTS.ANIMAL_ID NOT IN (
        SELECT ANIMAL_INS.ANIMAL_ID
        FROM ANIMAL_OUTS
        INNER JOIN ANIMAL_INS
        ON ANIMAL_OUTS.ANIMAL_ID = ANIMAL_INS.ANIMAL_ID
    );
    
    # 서브쿼리 풀이법(2)
    select ANIMAL_OUTS.ANIMAL_ID, ANIMAL_OUTS.NAME
    from ANIMAL_OUTS
    where ANIMAL_OUTS.ANIMAL_ID not in(select ANIMAL_ID from ANIMAL_INS);
  • [자동차 종류 별 특정 옵션이 포함된 자동차 수 구하기]
    https://school.programmers.co.kr/learn/courses/30/lessons/151137

  • [입양 시각 구하기(1)]00000000
    https://school.programmers.co.kr/learn/courses/30/lessons/59412

    # CAST 사용
    SELECT HOUR(DATETIME) AS HOUR, COUNT(*) AS COUNT
    FROM ANIMAL_OUTS
    WHERE HOUR(DATETIME) >= 9 
      AND HOUR(DATETIME) < 20
    GROUP BY HOUR(DATETIME)
    ORDER BY HOUR(DATETIME);
    
    #CAST  미사용
    select CAST(date_format(DATETIME, '%H'), UNSIGNED) as HOUR, COUNT(*) as COUNT
    from ANIMAL_OUTS
    where CAST(date_format(DATETIME, '%H'), UNSIGNED) >= '09' and CAST(date_format(DATETIME, '%H'), UNSIGNED) < '20'
    group by CAST(date_format(DATETIME, '%H'), UNSIGNED)
    order by CAST(date_format(DATETIME, '%H'), UNSIGNED);
    
  • [동명 동물 수 찾기]
    https://school.programmers.co.kr/learn/courses/30/lessons/59041

  • [카테고리 별 도서 판매량 집계하기]
    https://school.programmers.co.kr/learn/courses/30/lessons/144855

        # 모든책이 판매량이 있다면 inner join을 쓸 수 있지만 left join이 더 논리적으로 맞다고 생각함
        -- inner join 풀이
        select b.CATEGORY, sum(b_s.SALES) as TOTAL_SALES
        from BOOK_SALES as b_s
        inner join BOOK as b
        on b_s.BOOK_ID = b.BOOK_ID
        where date_format(b_s.SALES_DATE, '%Y-%m') = '2022-01'
        group by b.CATEGORY
        order by b.CATEGORY;
        -- left join 풀이
        select b.CATEGORY, sum(b_s.SALES) as TOTAL_SALES
        from BOOK_SALES as b_s
        left join BOOK as b
        on b_s.BOOK_ID = b.BOOK_ID
        where date_format(b_s.SALES_DATE, '%Y-%m') = '2022-01'
        group by b.CATEGORY
        order by b.CATEGORY;
  • [조건에 맞는 사용자와 총 거래금액 조회하기]
    https://school.programmers.co.kr/learn/courses/30/lessons/164668


마치며

프로그래머스에서 다양한 예제 문제를 풀어보고 다른 사람들의 예제도 함께 보니 학습에 많은 도움이 되는듯
유연한 사고력을 기르는 연습이 필요할 듯!

profile
Eazy하게

3개의 댓글

comment-user-thumbnail
2025년 11월 26일

이게 제일 복잡한듯요 정리감사

1개의 답글
comment-user-thumbnail
2025년 12월 11일

🌈존💕 ㉯ 🦄 쉽네요 ㅋㅋ 1초컷띠 수고염 ㅋㅋ

답글 달기