COUNT(DISTINCT column)은 컬럼에 서로 다른 값이 몇 개 있는지 세요. 열 번 나오는 값도 한 번만 세고, NULL은 아예 세지 않아요. 예제는 PostgreSQL, MySQL, SQLite, SQL Server에서 동작해요.
COUNT DISTINCT 사용법
COUNT() 안에서 셀 컬럼이나 표현식 앞에 DISTINCT를 넣으세요:
COUNT(DISTINCT expression)
다른 두 가지 COUNT 형태와 나란히 놓고 보면 가장 이해하기 쉬워요. COUNT(*)는 행 수를, COUNT(column)은 컬럼이 NULL이 아닌 행 수를, COUNT(DISTINCT column)은 NULL이 아닌 서로 다른 값의 수를 세요:
-- Input: orders
-- order_id | customer_id
-- ---------+------------
-- 1 | 1042
-- 2 | 1077
-- 3 | 1042
-- 4 | NULL
SELECT COUNT(*) AS all_rows,
COUNT(customer_id) AS with_customer,
COUNT(DISTINCT customer_id) AS customers
FROM orders;
-- Output:
-- all_rows | with_customer | customers
-- ---------+---------------+----------
-- 4 | 3 | 2
주문 4건이 고객 2명에게서 들어왔어요. 1042는 두 번 주문했고, 주문 4에는 고객이 아예 없어요.
알아 둘 규칙
- NULL은 세지 않아요. 값이 없는 경우도 하나로 세야 한다면 직접 더하세요:
-- Input: the orders table above
SELECT COUNT(DISTINCT customer_id)
+ MAX(CASE WHEN customer_id IS NULL THEN 1 ELSE 0 END) AS customers_and_guests
FROM orders;
-- Output: 3
-
MySQL과 SQL Server는 기본적으로 대소문자를 구분하지 않아요. 그래서
'SQL'과'sql'을 하나의 값으로 세요. 대소문자를 구분하지 않아야 할 때는LOWER(tag)를 세세요:
-- Input: tags
-- post_id | tag
-- --------+---------
-- 1 | SQL
-- 2 | sql
-- 3 | Postgres
SELECT COUNT(DISTINCT tag) FROM tags;
-- Output: 3 (2 in MySQL and SQL Server)
SELECT COUNT(DISTINCT LOWER(tag)) FROM tags;
-- Output: 2
-
DISTINCT는
SUM과AVG안에서도 동작하지만, 중복된 행이 아니라 중복된 값을 제거해요. 가격이 같은 서로 다른 두 상품은 한 번만 더해져요:
-- Input: order_lines
-- product | price
-- ---------+------
-- Pen | 2.00
-- Pencil | 2.00
-- Notebook | 5.00
SELECT SUM(price) AS total, SUM(DISTINCT price) AS distinct_total FROM order_lines;
-- Output:
-- total | distinct_total
-- ------+---------------
-- 9.00 | 7.00
대체 문법
COUNT(DISTINCT ...)는 컬럼 하나만 받아요. 여러 컬럼의 서로 다른 조합을 세려면 DISTINCT 서브쿼리의 행 수를 세세요. PostgreSQL과 MySQL에는 축약 문법도 있어요:
-- Input: orders
-- order_id | customer_id | product_id
-- ---------+-------------+-----------
-- 1 | 1 | 23
-- 2 | 12 | 3
-- 3 | 1 | 23
-- 4 | 12 | 5
SELECT COUNT(*) FROM (SELECT DISTINCT customer_id, product_id FROM orders) AS pairs;
-- Output: 3
-- PostgreSQL
SELECT COUNT(DISTINCT (customer_id, product_id)) FROM orders;
-- Output: 3
-- MySQL
SELECT COUNT(DISTINCT customer_id, product_id) FROM orders;
-- Output: 3
CONCAT()으로 컬럼들을 하나의 문자열로 이어 붙이는 방식도 볼 수 있어요. 이때는 구분자가 꼭 필요해요. 구분자가 없으면 고객 1과 상품 23, 고객 12와 상품 3이 모두 '123'이 돼요:
-- Input: the orders table above
SELECT COUNT(DISTINCT CONCAT(customer_id, product_id)) FROM orders;
-- Output: 2
SELECT COUNT(DISTINCT CONCAT(customer_id, '_', product_id)) FROM orders;
-- Output: 3
정확한 distinct 카운트는 모든 값을 정렬하거나 해시해야 해서 아주 큰 테이블에서는 느려져요. SQL Server에는 APPROX_COUNT_DISTINCT()가 있는데, 더 빠르고 97%의 경우 정확한 값과 2% 이내의 오차를 보여요:
-- SQL Server
-- Input: the orders table above
SELECT APPROX_COUNT_DISTINCT(customer_id) AS customers FROM orders;
-- Output: 2
COUNT DISTINCT는 언제 써야 할까요?
질문이 “행이 몇 개인가?”가 아니라 “서로 다른 …이 몇 개인가?”라면 COUNT DISTINCT를 쓰세요. 이런 질문은 대부분 다음 네 가지 중 하나에 해당해요.
고유 방문자, 고객, 상품 수 세기
방문, 주문, 판매 로그에는 같은 방문자, 고객, 상품이 반복해서 기록돼요. ID에 COUNT DISTINCT를 쓰면 이 행들이 실제 인원수로 바뀌어요.
그룹별 고유 값 세기
GROUP BY를 추가하면 그룹마다 distinct 카운트를 하나씩 얻을 수 있어요. 과목별 학생 수, 판매자별 상품 수, 월별 고객 수처럼요.
컬럼 조합의 고유 값 세기
두 컬럼이 모두 일치해야 두 행이 “같은” 경우가 있어요. 같은 사용자의 같은 종류의 이슈나, 두 공항 사이의 같은 노선처럼요. 이런 조합은 “대체 문법”에 있는 다중 컬럼 방식 중 하나로 세세요.
조건을 만족하는 고유 값 세기
COUNT(DISTINCT ...) 안에 CASE 표현식을 넣으면 일부 행만 셀 수 있어요. CASE가 원하지 않는 행에 NULL을 반환하고 NULL은 세지 않으니, 쿼리 하나로 필터링된 카운트 여러 개를 나란히 얻을 수 있어요.
COUNT DISTINCT 실전 예제
월간 활성 사용자
9월에 무엇이든 활동한 사용자 수를 세요. 몇 번 다시 방문했든 상관없어요:
-- Input: events
-- event_id | user_id | event_date
-- ---------+---------+-----------
-- 1 | 301 | 2026-08-30
-- 2 | 302 | 2026-09-02
-- 3 | 302 | 2026-09-15
-- 4 | 305 | 2026-09-21
-- 5 | 302 | 2026-10-01
SELECT COUNT(DISTINCT user_id) AS active_users
FROM events
WHERE event_date >= '2026-09-01' AND event_date < '2026-10-01';
-- Output: 2
판매자별 판매 상품 수
COUNT(*)는 판매자별 판매 건수를, COUNT(DISTINCT product)는 그 판매에 포함된 서로 다른 상품 수를 알려 줘요:
-- Input: sales
-- vendor | product | color
-- -----------+----------+------
-- Acme | Mug | Red
-- Acme | Mug | Blue
-- Acme | Mug | Red
-- Acme | Tote Bag | Red
-- Brightside | Notebook | Black
SELECT vendor, COUNT(*) AS sales, COUNT(DISTINCT product) AS products
FROM sales
GROUP BY vendor;
-- Output:
-- vendor | sales | products
-- -----------+-------+---------
-- Acme | 4 | 2
-- Brightside | 1 | 1
항공사 운항 노선
노선은 출발지와 도착지의 조합이에요. LHR에서 JFK로 가는 항공편 두 개는 한 노선이지만, JFK에서 LHR로 가는 건 다른 노선이에요:
-- Input: flights
-- flight_no | origin | destination
-- ----------+--------+------------
-- 101 | LHR | JFK
-- 105 | LHR | JFK
-- 102 | JFK | LHR
-- 301 | LHR | LAX
SELECT COUNT(*) AS routes
FROM (SELECT DISTINCT origin, destination FROM flights) AS r;
-- Output: 3
구매 가격대별 고객 수
상품 100~200을 구매한 고객 수를 센 다음, 주문 가격대별로 나눠요. 고객 7은 저렴한 주문을 두 번 했지만 한 번만 세고, 고객 15의 상품은 범위를 벗어나요:
-- Input: orders
-- order_id | customer_id | product_id | price
-- ---------+-------------+------------+-------
-- 1 | 7 | 105 | 25.00
-- 2 | 7 | 142 | 40.00
-- 3 | 9 | 150 | 120.00
-- 4 | 12 | 188 | 310.00
-- 5 | 15 | 260 | 30.00
SELECT
COUNT(DISTINCT customer_id) AS customers,
COUNT(DISTINCT CASE WHEN price BETWEEN 1 AND 50 THEN customer_id END) AS low_spenders,
COUNT(DISTINCT CASE WHEN price BETWEEN 51 AND 150 THEN customer_id END) AS mid_spenders,
COUNT(DISTINCT CASE WHEN price > 150 THEN customer_id END) AS high_spenders
FROM orders
WHERE product_id BETWEEN 100 AND 200;
-- Output:
-- customers | low_spenders | mid_spenders | high_spenders
-- ----------+--------------+--------------+--------------
-- 3 | 1 | 1 | 1
두 가격대에 모두 주문이 있는 고객은 양쪽에서 모두 세요.
COUNT DISTINCT 더 알아보기
이 블로그의 글:
-
SQL GROUP BY 가이드: 그룹별
COUNT,SUM,AVG - SQL 중복 찾기 가이드: 두 번 이상 나오는 값 찾기
- SQL 중복 제거 가이드: 여분의 복사본 삭제하기
- SQL CASE 문 가이드: 조건부 카운트의 바탕이 되는 조건
-
SQL의 Having vs Where:
HAVING으로 카운트 필터링하기 - SQL 치트 시트: 집계 함수와 나머지 SQL을 한 페이지에
공식 문서:
Beekeeper Studio는 무료 오픈 소스 데이터베이스 GUI예요
지금까지 써 본 SQL 쿼리 & 에디터 도구 중 최고예요. 데이터베이스 관리에 필요한 모든 게 다 있어요. - ⭐⭐⭐⭐⭐ Mit
Beekeeper Studio는 빠르고 직관적이며 사용하기 쉬워요. 다양한 데이터베이스를 지원하고 Windows, Mac, Linux에서 모두 잘 작동해요.
Beekeeper Studio 사용자 후기
"Beekeeper Studio가 기존 SQL 작업 방식을 완전히 바꿔 놓았어요. 빠르고 직관적이라 데이터베이스 작업이 다시 즐거워졌어요."
"여러 데이터베이스 GUI를 써 봤지만, Beekeeper는 기능과 단순함 사이의 균형이 완벽해요. 그냥 잘 돼요."