앱 사용 이력 테이블처럼 같은 값이 여러 번 쌓이는 테이블에서 "어떤 앱 이름들이 있었나"만 알고 싶을 때가 있습니다. 행이 수천 개라도 앱 이름은 몇 개뿐이니, 중복을 걷어 낸 목록만 받으면 됩니다. 이때 쓰는 게 DISTINCT 입니다.
select distinct `app_name` from `g5_app_history`
SELECT 바로 뒤에 DISTINCT 를 붙이면 결과에서 완전히 같은 행은 하나만 남깁니다. 위 쿼리는 app_name 컬럼 하나만 고르니까 앱 이름이 겹치지 않는 목록이 나옵니다.
컬럼을 여러 개 고르면 기준이 달라진다
DISTINCT 는 특정 컬럼 하나가 아니라 조회한 컬럼 전체의 조합을 기준으로 중복을 판단합니다. 그래서 아래처럼 두 컬럼을 고르면, nation_code 와 address 가 둘 다 같은 행만 하나로 합쳐집니다.
SELECT DISTINCT nation_code, address
FROM stadium
WHERE nation_code='KOR';
국가 코드가 KOR인 경기장들 중에서 주소가 같은 줄은 하나만 나오고, 주소가 다르면 각각 나옵니다. 처음 쓸 때 DISTINCT nation_code, address 를 "nation_code만 중복 제거하고 address는 따라 나오는 것"으로 오해하기 쉬운데, 그렇게 동작하지 않습니다.
결과 순서에 대해서도 한 가지. 중복을 걸러 내는 과정에서 정렬된 것처럼 보이는 결과가 나올 때가 있지만, MySQL은 DISTINCT 결과의 순서를 보장하지 않습니다. 인덱스를 타느냐, 임시 테이블을 쓰느냐에 따라 달라지니 순서가 필요하면 ORDER BY 를 꼭 붙이세요.
값마다 한 행씩 골라야 할 때는 GROUP BY
앱 이름만이 아니라 "앱별로 가장 최근 기록의 uid"가 필요하다면 DISTINCT 로는 안 됩니다. 이럴 때는 GROUP BY 로 묶고 집계 함수를 씁니다.
select max(`uid`) from `g5_app_history` group by `app_name`
app_name 별로 그룹을 만들고 그 안에서 가장 큰 uid 를 돌려줍니다. uid 가 자동 증가 키라면 앱마다 마지막에 들어간 행 번호가 되는 셈이라, 이 값으로 다시 조인하면 앱별 최신 기록 전체를 가져올 수 있습니다. 결과에 어떤 앱의 값인지도 보고 싶다면 select app_name, max(uid) ... 처럼 그룹 컬럼을 같이 적으면 됩니다.
어느 쪽을 쓸까
단순히 중복 없는 값 목록이면 DISTINCT, 그룹마다 개수·합계·최댓값 같은 계산이 필요하면 GROUP BY 입니다. 목록만 뽑을 때 GROUP BY app_name 으로 써도 결과는 같지만, 의도가 드러나는 건 DISTINCT 쪽이라 저는 목록 용도에는 DISTINCT 를 씁니다.
속도는 두 방식 모두 대상 컬럼에 인덱스가 있느냐에 크게 좌우됩니다. 인덱스가 없으면 전체 행을 읽어 임시 테이블에서 중복을 걸러야 해서, 이력 테이블처럼 행이 많아지면 느려집니다. 자주 돌리는 쿼리라면 app_name 에 인덱스를 걸어 두고 EXPLAIN 으로 임시 테이블(Using temporary)을 쓰는지 확인해 보는 게 좋습니다.