작성일자 : 2023-09-21
Ver 0.1.1
앞서 UNION을 사용하면 안좋은 경우에 대해서 살펴보았지만, UNION을 사용해야하는 경우도 있다.
UNION을 사용하는 것이 오히려 성능적으로 좋은 경우도 있다.
UNION을 사용할수 밖에 없는 경우
머지 대상이 되는 SELECT 구문들에서 사용하는 테이블이 다른 경우가 대표적이다.
SELECT col_1
FROM Table_A
WHERE col_2 = ‘A’
UNION ALL
SELECT col_3
FROM Table_B
WHERE col_4 = ‘B’;
물론 그렇다고 CASE 식을 사용할 수 없다는 것은 아니다. FROM 구에서 테이블을 결합하면 CASE 식을 사용해 원하는 결과를 구할 수 있다. 하지만 그렇게 하면 필요 없는 결합이 발생해서 성능적으로 악영향이 발생한다. (UNION을 사용한다면 발생하지 않는다.) 따라서 실행 계획 등을 확인해서 어떤 것이 더 좋은지 명확하게 확인해줘야한다.
UNION을 사용하는 것이 성능적으로 더 좋은 경우
인덱스와 관련된 경우, UNION을 사용했을 때 좋은 인덱스(압축을 잘 하는 인덱스)를 사용하지만, 이외의 경우에는 테이블 풀 스캔이 발생한다면, UNION을 사용한 방법이 성능적으로 더 좋을 수 있다.
ex) ThreeElements Table
key | name | date_1 | flg_1 | date_2 | flg_2 | date_3 | flg_3 |
1 | a | 2013-11-01 | T | ||||
2 | b | 2013-11-01 | T | ||||
3 | c | 2013-11-01 | F | ||||
4 | d | 2013-11-01 | T | ||||
5 | e | 2013-11-01 | T | ||||
6 | f | 2013-11-01 | F |
날짜가 있고 플래그가 T인 레코드 검색
key | name | date_1 | flg_1 | date_2 | flg_2 | date-3 | flg_3 |
1 | a | 2013-11-01 | T | ||||
2 | b | 2013-11-01 | T | ||||
5 | e | 2013-11-01 | T |
UNION을 사용한 방법
SELECT key, name,
date_1, flg_1,
date_2, flg_2,
date_3, flg_3,
FROM TreeElements
WHERE date_1 = ‘2013-11-01’
AND flg_1 = ‘T’
UNION ALL
SELECT key, name,
date_1, flg_1,
date_2, flg_2,
date_3, flg_3,
FROM TreeElements
WHERE date_2 = ‘2013-11-01’
AND flg_2 = ‘T’
UNION ALL
SELECT key, name,
date_1, flg_1,
date_2, flg_2,
date_3, flg_3,
FROM TreeElements
WHERE date_3 = ‘2013-11-01’
AND flg_3 = ‘T’
머지 되는 3개의 SELECT 구문에서 다른 것은 WHERE 구뿐이다. 쿼리는 원하는 결과를 만들므로 기능적으로 충분하다. 이럴 경우 문제는 바로 성능과 실행계획인데, 이때의 포인트는 인덱스이다. 위 쿼리를 최적의 성능으로 수행하려면 다음과 같은 필드 조합에 인덱스가 필요하다.
CREATE INDEX IDX_1 ON ThreeElements (date_1, flg_1);
CREATE INDEX IDX_2 ON ThreeElements (date_2, flg_2);
CREATE INDEX IDX_3 ON ThreeElements (date_3, flg_3);
인덱스가 WHERE 구에서 (date_n, flg_n)라는 필드 조합을 사용할 때 빠르게 만들어 준다.
이렇게 인덱스가 있으면 UNION을 사용했을 경우의 실행 계획을 보면 3개의 SELECT 구문 모두 IDX_1, IDX_2, IDX_3 라는 인덱스가 사용되는 것을 확인할 수 있다. 이렇게 되면 ThreeElements 테이블의 레코드 수가 많고, 각각의 WHERE 구의 검색 조건에서 레코드 수를 많이 압축할 수록, 테이블의 풀 스캔보다도 훨씬 빠른 접근 속도를 기대할 수 있다.
OR를 사용한 방법
이런한 문제를 UNION을 사용하지 않고 푼다면 ON을 사용해 조건을 연결할 수도 있다.
SELECT key, name,
date_1, flg_1,
date_2, flg_2,
date_3, flg_3
FROM TreeElements
WHERE (date_1 = ‘2013-11-01’ AND flg_1 = ‘T’)
OR (date_2 = ‘2013-11-01’ AND flg_2 = ‘T’)
OR (date_3 = ‘2013-11-01’ AND flg_3 = ‘T’)
위 쿼리는 UNION을 사용했을 때와 같은 결과를 도출하지만 실행 계획이 크게 다르다.
위 쿼리의 실행 계획은 SELECT 구문이 하나로 줄어들어 ThreeElements 테이블에 대한 접근이 1회로 줄어든다. 하지만 이때 인덱스가 사용되지 않고, 그냥 테이블 풀 스캔이 수행된다. 이렇게 WHERE 구문에서 OR을 사용하면 해당 필드에 부여된 인덱스를 사용할 수 없다.
따라서 이러한 경우 UNION과 OR의 성능 비교는 3회는 인덱스 스캔과 1회의 테이블 풀 스캔 중에서 어떤 것이 더 빠른지에 대한 문제가 된다. 이는 테이블 크기와 조건에 따른 선택 비율(레코드 히트율)에 따라 답이 달라진다. 하지만 테이블이 크고, WHERE 조건으로 선택되는 레코드의 수가 충분히 작다면 UNION이 더 빠르다. 따라서 UNION을 사용하는 경우가 더 빠를 수도 있다.
IN을 사용한 방법
OR 쿼리를 IN 쿼리를 변환해서 사용할 수도 있다.
SELECT key, name,
date_1, flg_1,
date_2, flg_2,
date_3, flg_3
FROM TreeElements
WHERE (‘2013-11-01’, ‘T’)
IN ((date_1, flg_1),
(date_2, flg_2),
(date_3, flg_3));
이는 다중 필드(multiple fields, 또는 행식(row expression))라는 기능을 사용한 방법이다. IN의 매개변수로는 단순한 스칼라 뿐만 아니라, 위 쿼리처럼 (a,b,c)와 같은 값의 리스트(배열)을 입력할 수도 있다. OR를 사용했을 때보다 간단하고 이해하기 쉬울 수 있지만 실행계획은 OR를 사용할 때와 같다. 따라서 성능적인 문제도 같다.
CASE 식을 사용한 방법
SELECT key, name,
date_1, flg_1,
date_2, flg_2,
date_3, flg_3,
FROM TreeElements
WHERE CASE WHEN date_1 = ‘2013-11-01 THEN flg_1
WHEN date_2 = ‘2013-11-01 THEN flg_2
WHEN date_3 = ‘2013-11-01 THEN flg_3
ELSE NULL END = ‘T’;
위 쿼리도 원하는 결과를 만들어 내지만 실행 계획은 OR, IN을 사용할 때와 같다. 따라서 성능적으로 같은 문제를 안게 된다. 그런데 이런 쿼리를 사용할 때는 주의점이 있다. 비즈니스 룰을 조금 변경하면 UNION, OR, IN을 사용할 때와 다른 결과가 나온다.