SQL) 중복 행 제거하기 (DISTINCT, GROUP BY)

2025. 8. 4. 22:07·SQL

결과를 표시할 때, 중복된 데이터를 제거할 상황이 필요할 경우가 있다.
예를 들어, 병원에서 아래와 같은 진료기록 혹은 차트를 가지고 있다고 가정해보자.

CREATE TABLE medical_records (
    진료번호 INT,
    환자이름 VARCHAR(50),
    진단병명 VARCHAR(50),
    입원통원여부 VARCHAR(10),
    진료일자 DATE,
    입원일자 DATE,
    퇴원일자 DATE
);
진료번호 환자이름 진단병명 입원,통원여부 진료일자 입원일자 퇴원일자
1 홍길동 폐렴 입원 2025-06-25 2025-08-20
2 임감기 감기 통원 2025-07-22
3 김장염 장염 통원 2025-07-22
4 임감기 감기 통원 2025-07-25
5 김장염 장염 통원 2025-07-26
6 임감기 장염 통원 2025-07-27

가상의 상황으로, 보험의료공단에 7월 21일부터 27일까지 한 주간의 환자 명단 혹은 환자 수를 제출해야 하는 상황일때.
임감기, 김장염 환자같은 단기간 통원 환자들의 중복을 제거해야할것이다.

이럴때 SQL에서는 DISTINCT와, GROUP BY 두 가지 방식을 사용할 수 있다.

DISTINCT

먼저 DISTINCT에 대해 알아보자면, 진짜 비교적 단순하게 중복 행을 제거 하여 고유값만 반환하는 키워드다.

적용은 아래처럼 SELECT문에서 같이 조회할 컬럼 바로 앞에 넣어주면 된다.

SELECT DISTINCT name
FROM table_name;

내부동작

DISTINCT가 적용된 쿼리문이 실행되면 내부에서는 세가지 동작이 이루어진다.

  1. 조회: FROM절에 명시된 테이블 내부에서 WHERE절 조건을 만족하는 모든 데이터를 조회한다
  2. 정렬: SELECT절의 DISTINCT가 적용된 컬럼을 기준으로 정렬을 한다.
    이는 중복된 값을 서로 인접하게 만들어 효율적으로 제거할 수 있게 한다.
    이 과정에서 DBMS에 의해 다양한 상황에 따라 전통적인 정렬 외에도 해시를 사용하기도 한다.
  3. 중복제거: DISTINCT가 적용된 컬럼의 중복된 데이터 중 한개의 행(row)만 남긴다.
    이 때 DISTINCT가 2개 이상일 경우, 모든 값이 완전히 일치해야 중복된 값으로 간주한다.

이제 다시 예제로 돌아가서 다양한 경우에 대한 DISTINCT 적용을 알아보자.

단일 컬럼 중복 제거

고유한 환자 이름만 조회하는 경우

SELECT DISTINCT 환자이름
FROM medical_records;

결과

환자이름
홍길동
임감기
김장염

다중 컬럼조합 중복 제거

환자 이름과 진단병명 조합의 중복을 제거할 때

SELECT DISTiNCT 환자이름, 진단병명
FROM medical_records;

결과

환자이름 진단병명
홍길동 폐렴
임감기 감기
김장염 장염
임감기 장염

임감기의 row가 2개 유지된 이유는, DISTINCT로 설정한 컬럼인 환자이름, 진단병명 두개가 완전히 일치하지 않아서이다. (진단병명: 감기, 장염 2개 보유)

조건과 함께 사용 시

통원 환자들의 고유한 진단병명들을 분류하고 싶을때는

SELECT DISTINCT 진단병명
FROM medical_records
WHERE 입원통원여부 = '통원';
-- = 말고 LIKE를 사용해도 된다. 
-- WHERE 입원통원여부 LIKE '통원'

결과

진단병명
감기
장염

집계 함수 (COUNT)와 함께 사용 시

고유한 환자 수 (환자 중복이 제거된) 계산을 하기 위해서는

SELECT COUNT(DISTINCT 환자이름) as 총_환자수 -- as 생략 가능
FROM medical_records;

결과

총_환자수
3

단순히 중복을 제거하는 것이 목적이라면 DISTINCT가 적합하다. 하지만 그룹별로 통계나 집계 정보가 필요하다면 GROUP BY를 사용해야 한다.

GROUP BY

GROUP BY는 동일한 값을 가진 행들을 그룹으로 묶어서 집계 함수와 함께 사용하는 SQL 절이다.
DISTINCT와 달리 단순한 중복 제거를 넘어서 그룹별 통계 정보를 얻을 수 있다는 점이 핵심이다.

적용은 아래처럼 SELECT문의 맨 마지막에 GROUP BY 절을 추가하면 된다.

SELECT column_name, COUNT(*)
FROM table_name
GROUP BY column_name;

내부동작

GROUP BY가 적용된 쿼리문이 실행되면 내부에서는 다음과 같은 동작이 이루어진다.

  1. 조회: FROM절에 명시된 테이블에서 WHERE절 조건을 만족하는 모든 데이터를 조회한다
  2. 그룹화: GROUP BY절에 명시된 컬럼의 값이 같은 행들을 하나의 그룹으로 묶는다
  3. 집계: 각 그룹에 대해 집계 함수(COUNT, SUM, AVG 등)를 적용한다
  4. 결과 반환: 각 그룹별로 하나의 결과 행을 반환한다

이제 예제로 돌아가서 다양한 경우에 대한 GROUP BY 적용을 알아보자.

기본 그룹화 (중복 제거 효과)

환자별로 그룹화하면 DISTINCT와 유사한 효과를 얻을 수 있다.

SELECT 환자이름
FROM medical_records
GROUP BY 환자이름;

결과

환자이름
홍길동
임감기
김장염

그룹별 개수 계산

각 환자별로 몇 번의 진료를 받았는지 확인할 때

SELECT 환자이름, COUNT(*) as 진료횟수
FROM medical_records
GROUP BY 환자이름;

결과

환자이름 진료횟수
홍길동 1
임감기 3
김장염 2

이제 임감기 환자가 3번, 김장염 환자가 2번 진료를 받았다는 것을 알 수 있다.

다중 컬럼 그룹화

진단병명별, 입원통원여부별로 그룹화해서 각각의 환자 수를 확인할 때

SELECT 진단병명, 입원통원여부, COUNT(*) as 환자수
FROM medical_records
GROUP BY 진단병명, 입원통원여부;

결과

진단병명 입원통원여부 환자수
폐렴 입원 1
감기 통원 2
장염 통원 2

다양한 집계 함수 활용

만약 진료비 컬럼이 있다고 가정하고, 진단병명별로 다양한 통계를 내고 싶을 때

-- 가상의 진료비 컬럼 추가 (예시용)
SELECT 진단병명,
       COUNT(*) as 진료건수,
       AVG(진료비) as 평균진료비,
       SUM(진료비) as 총진료비,
       MIN(진료일자) as 최초진료일,
       MAX(진료일자) as 최근진료일
FROM medical_records
WHERE 진료일자 IS NOT NULL
GROUP BY 진단병명;

HAVING 절과 함께 사용

GROUP BY와 함께 자주 사용되는 HAVING 절은 그룹화된 결과에 조건을 적용할 때 사용한다.
WHERE절은 그룹화 이전에 조건을 적용하지만, HAVING절은 그룹화 이후에 조건을 적용한다는 차이가 있다.

2회 이상 진료받은 환자만 조회하고 싶을 때

SELECT 환자이름, COUNT(*) as 진료횟수
FROM medical_records
GROUP BY 환자이름
HAVING COUNT(*) >= 2;

결과

환자이름 진료횟수
임감기 3
김장염 2

보험의료공단 제출용 쿼리 예시

이제 처음 상황으로 돌아가서, 7월 21일부터 27일까지 한 주간의 데이터를 보험의료공단에 제출해야 하는 상황을 해결해보자.

전체 환자 수 (중복 제거)

SELECT COUNT(DISTINCT 환자이름) as 총환자수
FROM medical_records
WHERE 진료일자 BETWEEN '2025-07-21' AND '2025-07-27'
   OR (입원통원여부 = '입원' AND 입원일자 <= '2025-07-27' AND 퇴원일자 >= '2025-07-21');

결과: 총환자수: 3

입원/통원별 환자 수

SELECT 입원통원여부, COUNT(DISTINCT 환자이름) as 환자수
FROM medical_records
WHERE 진료일자 BETWEEN '2025-07-21' AND '2025-07-27'
   OR (입원통원여부 = '입원' AND 입원일자 <= '2025-07-27' AND 퇴원일자 >= '2025-07-21')
GROUP BY 입원통원여부;

결과

입원통원여부 환자수
입원 1
통원 2

환자별 진료 현황

SELECT 환자이름, COUNT(*) as 진료횟수, 
       GROUP_CONCAT(DISTINCT 진단병명) as 진단병명들
FROM medical_records
WHERE 진료일자 BETWEEN '2025-07-21' AND '2025-07-27'
GROUP BY 환자이름;

| GROUP_CONCAT: 일반 문자열 연결해주는 CONCAT과는 달리, 그룹화 된(GROUP BY) 여러 행의 값들을 구분자를 포함한 하나의 문자열로 연결해준다.

결과

환자이름 진료횟수 진단병명들
임감기 3 감기,장염
김장염 2 장염

DISTINCT vs GROUP BY 언제 사용할까?

DISTINCT를 사용하는 경우

  • 단순 중복 제거가 목적일 때
  • 집계 정보가 필요 없을 때
  • 성능이 중요한 단순 조회일 때
-- 병원에서 치료하는 모든 질병 종류
SELECT DISTINCT 진단병명 FROM medical_records;

-- 7월에 내원한 환자 명단
SELECT DISTINCT 환자이름 
FROM medical_records 
WHERE 진료일자 BETWEEN '2025-07-01' AND '2025-07-31';

GROUP BY를 사용하는 경우

  • 그룹별 통계나 집계가 필요할 때
  • HAVING 절로 그룹 조건을 설정해야 할 때
  • 복합적인 분석이 필요할 때
-- 월별 환자 수 통계
SELECT MONTH(진료일자) as 월, COUNT(*) as 환자수
FROM medical_records
WHERE 진료일자 IS NOT NULL
GROUP BY MONTH(진료일자);

-- 진료횟수가 많은 환자 찾기
SELECT 환자이름, COUNT(*) as 진료횟수
FROM medical_records
GROUP BY 환자이름
HAVING COUNT(*) > 1
ORDER BY 진료횟수 DESC;

정리

  • DISTINCT: 중복 제거만 필요할 때, 간단하고 직관적
  • GROUP BY: 그룹별 집계나 통계가 필요할 때, 더 유연함
  • 성능: 단순 중복 제거는 DISTINCT가 오버헤드가 더 적지만, 집계가 포함되면 GROUP BY가 적합
  • 확장성: GROUP BY는 HAVING, 다양한 집계 함수와 조합 가능

상황에 맞는 선택이 필요하다. 복잡한 분석에는 GROUP BY를, 단순한 중복 제거에는 DISTINCT를 사용하는 것이 효율적이라고 한다.

728x90

'SQL' 카테고리의 다른 글

관계형 데이터 베이스 (용어, 명령어 등 개요)  (0) 2025.07.29
'SQL' 카테고리의 다른 글
  • 관계형 데이터 베이스 (용어, 명령어 등 개요)
ahpicl64
ahpicl64
in the clouds
  • ahpicl64
    구름
    ahpicl64
  • 전체
    오늘
    어제
    • 분류 전체보기 (95)
      • WIL (4)
      • Jungle (36)
      • AWS (2)
      • SQL (2)
      • CS:APP (17)
      • Algorithm (10)
      • K8s (7)
      • 자료 구조 (10)
      • Spring (4)
      • React (0)
      • 운영체제 (1)
      • 기타등등 (2)
      • 이야기 (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • github
  • 공지사항

  • 인기 글

  • 태그

    어셈블리
    알고리즘
    CSAPP
    트러블슈팅
    Spring boot
    CloudFront
    DB
    자료구조
    컴퓨터시스템
    github actions
    python
    S3
    queue
    Spring
    IAM
    AWS
    DevOps
    k8s
    부하테스트
    EC2
  • 02-21 15:43
  • hELLO· Designed By정상우.v4.10.3
ahpicl64
SQL) 중복 행 제거하기 (DISTINCT, GROUP BY)
상단으로

티스토리툴바