본 게시물의 내용과 이미지는 도서 Real MySQL의 내용을 재구성하여 작성되었습니다. 저자, 출판사에 의해 저작권 문제 발생시 게시물이 비공개 될 수 있음을 알립니다.
DISTINCT
특정 칼럼의 유니크한 값만을 조회하려면 SELECT 쿼리에 DISTINCT를 사용한다. DISTINCT는 MIN(), MAX() 또는 COUNT()와 같은 집합 함수와 함께 사용되는 경우와 집합 함수 없는 경우 두 가지로 구분된다.
집합 함수로 구분하는 이유는 DISTINCT 키워드가 영향을 미치는 범위가 달라지기 때문이다. 집합 함수와 같이 DISTINCT가 사용되는 쿼리의 실행계획에서 DISTINCT가 인덱스를 사용하지 못할 때는 항상 임시 테이블이 필요하다. 하지만 이런 경우 실행 계획 Extra 칼럼에 Using temporary 메시지가 출력되지 않는다.
SELECT DISTINCT
SELECT 의 레코드 중 유니크한 레코드만 가져오는 경우 SELECT DISTINCT 쿼리 를 사용한다. 이 경우 GROUP BY와 거의 같은 방식으로 처리된다. 단지 차이는 SELECT DISTINCT 의 경우 정렬이 보장되지 않는다는 것뿐이다. 다음의 두 쿼리는 정렬 관련 부분만 빼면 내부적으로 같은 작업을 수행한다. 두 쿼리 모두 인덱스를 사용하기 때문에 부가적인 정렬 작업이 필요하지 않으며 완전히 같은 쿼리다. 하지만 인덱스를 이용하지 못하는 DISTINCT는 정렬을 보장하지 않는다.
SELECT DISTINCT emp_no FROM salaries;
SELECT emp_no FROM salaries GROUP BY emp_no;
DISTINCT는 SELECT하는 레코드를 유니크하게 SELECT하는 것이지 칼럼을 유니크하게 조회하는 것이 아니다. 즉 다음 쿼리에서 SELECT 하는 결과는 first_name만 유니크한 것을 가져오는 것이 아니라 first_name, last_name 전체가 유니크한 레코드를 가져오는 것이다.
SELECT DISTINCT first_name, last_name, FROM employees;
가끔씩 DISTINCT를 다음과 같이 사용하는 경우도 있다.
SELECT DISTINCT(first_name), last_name FROM employees;
얼핏보면 first_name 만 유니크하게 조회하는 쿼리처럼 보인다. 하지만 MySQL 서버는 DISTINCT 뒤의 괄호를 의미없이 사용된 괄호로 해석하고 제거해 버린다.
SELECT DISTINCT first_name, last_name FROM employees; 로 파싱한다.
SELECT에 사용된 DISTINCT는 조회되는 모든 칼럼에 영향을 미친다. 절대로 SELECT하는 여러 칼럼 중 일부 칼럼만 유니크하게 조회하는 방법은 없다. 단, 집합 함수 내에서 사용된 경우는 다르다.
집합 함수와 함께 사용된 DISTINCT
COUNT() 또는 MIN(), MAX()와 같은 집합 함수 내에서 DISTINCT 키워드가 사용될 수 있는데, 이 경우에는 일반적으로 SELECT DISTINCT와 다른 형태로 해석된다. 집합 함수가 없는 SELECT 쿼리에서 DISTINCT는 조회하는 모든 칼럼의 조합이 유니크한 것들만 가져온다. 하지만 집합 함수 내에서 사용된 DISTINCT는 그 집합 함수의 인자로 전달된 칼럼 값들 중에서 중복을 제거하고 남은 값만을 가져온다.
EXPLAIN
SELECT COUNT(DISTINCT s.salary)
FROM employees e, salaries s
WHERE e.emp_no=s.emp_no
AND e.emp_no BETWEEN 100001 AND 100100;
위 쿼리는 내부적으로 COUNT(DISTINCT s.salary)를 처리하기 위해 임시 테이블을 사용한다. 쿼리의 실행 계획에는 임시 테이블을 사용한다는 메시지는 표시되지 않는다. employees 테이블과 salaries 테이블을 조인한 결과에서 salary 칼럼에는 유니크 인덱스가 생성되기 때문에 레코드 건수가 많아진다면 상당히 느려질 수 있는 형태의 쿼리다.
아래의 쿼리는 COUNT()함수가 두번 사용되어 2개의 임시 테이블을 사용한다.
EXPLAIN
SELECT COUNT(DISTINCT s.salary), COUNT(DISTINCT e.last_name)
FROM employees e, salaries s
WHERE e.emp_no=s.emp_no
AND e.emp_no BETWEEN 100001 AND 100100;
위의 쿼리는 DISTINCT 처리를 위해 인덱스를 이용할 수 없어 임시 테이블이 필요했다. 하지만 다음과 같이 인덱스 칼럼에 대한 DISTINCT 처리를 수행할 때는 인덱스를 풀 스캔하거나 레인지 스캔하면서 임시 테이블 없이 최적화된 처리가 가능하다.
SELECT COUNT(DISTINCT emp_no) FROM employees;
SELECT COUNT(DISTINCT emp_no) FROM dept_emp GROUP BY dept_no;