DISTINCT로 중복 행 제거하기
SELECT는 기본적으로 조건에 맞는 모든 행을 그대로 돌려주기 때문에 중복된 데이터까지 전부 포함됩니다. 여기에 DISTINCT를 붙이면 SQLite가 선택한 컬럼들의 값이 똑같은 행을 하나로 합쳐서, 고유한 조합만 한 번씩 보여줍니다.
다섯 행이 들어가서 세 행이 나왔습니다. SQLite가 customer 컬럼을 보고 중복을 걸러낸 뒤, 고유한 값마다 한 행씩 반환한 거죠. 결과 순서는 보장되지 않으니, 정렬이 필요하다면 ORDER BY를 함께 써야 합니다.
DISTINCT는 SELECT 목록 전체에 적용된다
여기서 많이들 헷갈려 합니다. DISTINCT는 특정 한 컬럼만 골라서 중복을 제거하는 게 아니라, SELECT에 적은 모든 컬럼을 묶어서 행 단위로 중복을 판단합니다.
(customer, country) 조합이 각각 한 번씩만 나옵니다. 같은 고객이 서로 다른 국가로 두 번 등장한다면 두 행 모두 출력됩니다. SQLite 입장에서는 그게 중복이 아니거든요.
참고로 다른 컬럼은 무시하고 특정 컬럼만 기준으로 중복을 제거하는 DISTINCT(customer) 같은 문법은 없습니다. 괄호를 쓰면 뭔가 될 것 같지만, SELECT DISTINCT(customer), country는 SELECT DISTINCT customer, country와 똑같이 해석돼요. 여기서 괄호는 그냥 식을 묶는 역할일 뿐입니다. 고객별로 한 행씩, 그리고 국가는 그중 하나만 골라서 보고 싶다면 GROUP BY와 집계 함수를 함께 써야 합니다.
COUNT(DISTINCT col)로 고유값 개수 세기
자주 마주치는 상황이죠. 어떤 컬럼에 고유한 값이 몇 개나 들어 있는지 알고 싶을 때가 있습니다. COUNT(*)는 전체 행 수를 세고, COUNT(col)은 NULL이 아닌 값의 개수를 세며, COUNT(DISTINCT col)은 NULL을 제외한 고유 값의 개수를 셉니다.
주문 5건, 고유 고객 3명, 고유 국가 3곳. COUNT(DISTINCT ...)는 DISTINCT의 집계 형태 중 가장 활용도가 높은 패턴이다. "서로 다른 값이 몇 개나 나왔는지" 세고 싶을 때마다 자연스럽게 손이 가는 함수다.
한 가지 주의할 점은, SQLite에서는 COUNT(DISTINCT ...) 안에 컬럼을 하나만 넣을 수 있다는 것이다. 여러 컬럼의 고유 조합을 세고 싶다면 서브쿼리로 감싸면 된다: SELECT COUNT(*) FROM (SELECT DISTINCT a, b FROM t).
sqlite distinct가 NULL을 다루는 방식
SQL에서 NULL은 좀 까다로운 친구다. NULL = NULL이 TRUE가 아니라 NULL로 평가되기 때문이다. 그런데 DISTINCT는 여기서 예외를 둔다. 중복 제거 기준에서는 모든 NULL을 서로 같은 값으로 취급한다.
결과로 세 개의 행이 돌아옵니다. 'ada@example.com', 'dan@example.com', 그리고 NULL 하나죠. 원래 세 개였던 NULL 이메일이 하나로 합쳐진 겁니다. 이 규칙은 GROUP BY나 UNION 같은 집합 연산에도 똑같이 적용됩니다. "왜 NULL 행이 세 번이 아니라 한 번만 나오지?" 하고 머리를 쥐어뜯게 될 때 기억해두면 유용합니다.
DISTINCT는 ORDER BY나 LIMIT보다 먼저 동작한다
SELECT 문의 절들은 논리적으로 다음 순서로 처리됩니다. FROM → WHERE → GROUP BY → HAVING → SELECT/DISTINCT → ORDER BY → LIMIT. 즉, DISTINCT로 먼저 중복을 걸러낸 뒤, 남은 결과를 ORDER BY로 정렬하고, 마지막에 LIMIT으로 잘라내는 식이죠.
WHERE로 네 개의 행을 추리고, DISTINCT로 Boris의 중복을 정리한 뒤, ORDER BY로 알파벳순 정렬, 마지막에 LIMIT으로 앞의 두 개만 가져옵니다. 한 번쯤 직접 따라가 보길 권합니다. 결과 순서가 헷갈리는 건 대부분 어느 단계가 언제 실행되는지 잊어버려서 생기는 문제거든요.
DISTINCT vs GROUP BY 차이
순수하게 중복만 제거하는 용도라면, 아래 두 쿼리는 같은 행을 돌려줍니다:
결과는 같습니다. 차이는 그다음에 무엇을 할 수 있느냐죠.
DISTINCT는 오직 "중복 없는 행만 보여줘"라는 용도입니다. 그 이상은 안 됩니다.GROUP BY는 "행을 그룹으로 묶고 그룹별로 뭔가 계산해줘"에 쓰입니다.COUNT(*),SUM(amount),MAX(created_at)같은 집계 함수를 함께 쓸 수 있죠.
DISTINCT를 쓰다가 "아, 고객별 합계도 같이 뽑고 싶은데?" 하는 생각이 든다면, 그게 바로 GROUP BY로 갈아탈 타이밍입니다:
고객별로 한 줄씩, 원하던 집계값까지 함께 나옵니다. DISTINCT로는 절대 이렇게 못 합니다 — "그룹마다 한 줄을 뽑되 합계까지 같이"라는 표현 자체가 불가능하거든요.
사용할 때 주의할 점 몇 가지
- 성능 이슈.
DISTINCT는 중복을 찾기 위해 SQLite가 결과 행을 정렬하거나 해싱해야 하는 경우가 대부분입니다. 결과 집합이 크다면 중복 제거 대상 컬럼에 인덱스를 걸어두는 게 도움이 됩니다. 컬럼이 많은 테이블에서SELECT DISTINCT로 모든 컬럼을 뽑고 있다면, 정말 모든 컬럼이 필요한 건지 한 번 더 생각해 보세요. DISTINCT *는 거의 안 씁니다. 문법적으로는 됩니다 —SELECT DISTINCT * FROM t는 행 전체 단위로 중복을 제거합니다. 하지만 테이블에 기본 키가 있다면 모든 행이 이미 고유하므로 사실상 아무 의미 없는 쿼리가 됩니다.UNIQUE와 헷갈리지 마세요.UNIQUE는 애초에 중복 값이 들어오지 못하게 막는 테이블 제약조건이고,DISTINCT는 쿼리 실행 시점에 결과에서 중복을 가려주는 필터입니다. 역할이 완전히 다릅니다.
다음: CASE 표현식
SELECT, WHERE, ORDER BY, DISTINCT로 결과 행의 모양을 잡을 수 있게 됐다면, 다음 단계는 쿼리 안에서 조건 분기를 처리하는 겁니다. CASE 표현식을 쓰면 조건에 따라 서로 다른 값을 반환할 수 있는데, SQL판 if/else 사다리라고 보면 됩니다. 다음 페이지에서 자세히 다룹니다.
자주 묻는 질문
SQLite에서 SELECT DISTINCT는 어떻게 동작하나요?
SELECT DISTINCT는 결과 집합에서 중복된 행을 제거합니다. SQLite는 SELECT 절에 나열된 모든 컬럼을 비교해서 고유한 조합당 한 행만 남깁니다. 적용 순서는 WHERE와 JOIN 다음, ORDER BY와 LIMIT 이전입니다.
DISTINCT를 여러 컬럼에 같이 쓸 수 있나요?
네, 그런데 DISTINCT는 항상 SELECT 절 전체에 적용된다는 점을 기억해야 합니다. 즉 SELECT DISTINCT city, country FROM users는 고유한 (city, country) 조합을 반환합니다. 다른 컬럼은 무시하고 특정 컬럼만 중복 제거하는 DISTINCT(city) 같은 문법은 없으며, 그게 필요하다면 집계 함수와 함께 GROUP BY를 써야 합니다.
DISTINCT는 NULL 값을 어떻게 처리하나요?
DISTINCT는 중복 제거 목적에 한해 NULL끼리 같은 값으로 취급합니다. 그래서 NULL이 들어 있는 여러 행은 한 행으로 합쳐집니다. WHERE 절에서 NULL = NULL이 unknown으로 평가되는 동작과는 다른데, 이는 DISTINCT, GROUP BY, UNION에만 적용되는 특별 규칙입니다.
DISTINCT와 GROUP BY는 뭐가 다른가요?
단순히 중복만 없애는 용도라면 SELECT DISTINCT col과 SELECT col FROM t GROUP BY col은 결과가 같습니다. 차이는 의도에 있습니다. 고유한 행만 뽑고 싶다면 DISTINCT, 그룹별로 COUNT(*)나 SUM(amount) 같은 집계까지 계산하고 싶다면 GROUP BY를 쓰는 게 자연스럽습니다.