쿼리 결합하기
목표
- UNION 연산자를 사용해 여러 개의 SELECT 문을 결합하여 하나의 결과를 얻는다.
목차
- [결합 쿼리 이해하기](#결합 쿼리 이해하기)
- [결합 쿼리 만들기](#결합 쿼리 만들기)
결합 쿼리 이해하기
대부분의 SQL 쿼리는 하나 이상의 테이블에서 데이터를 가져오는 단일 SELECT문인데, 여러 쿼리(여러 개의 SELECT 문)를 수행하여 하나의 결과로 가져올 수도 있다.
이런 결합 쿼리를 보통 집합(Union) 쿼리나 복합(Compound) 쿼리라고 부르고
기본적으로 결합 쿼리를 사용하는 두 가지 경우의 시나리오가 있다.
- 여러 테이블에 있는 비슷한 구조의 데이터를 하나의 쿼리로 가져오는 경우
- 한 개의 테이블에서 여러 개의 쿼리를 수행하고, 하나의 결과로 가져오는 경우
[!NOTE] 결합 쿼리와 다수 WHERE 조건
대부분 하나의 테이블에서 두 개의 쿼리를 결합하는 것은 여러 개의 WHERE 조건을 갖는 쿼리 한 개와 같다.
즉, 여러 개의 WHERE 조건이 있는 SELECT 문은 결합 쿼리로 만들 수 있다.
결합 쿼리 만들기
UNION
연산자를 이용하여 SQL 쿼리를 결합할 수 있다. 여러 개의 SELECT 문에 UNION을 지정하면, 그 결과가 하나로 결합된다.
UNION 사용하기
UNION을 사용하는 것은 매우 간단한데, 각각의 SELECT 문 사이에 UNION 키워드를 적는걸로 끝이다.
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_state IN ('IL', 'IN', 'MI');
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_name = 'Fun4All';
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_state IN ('IL', 'IN', 'MI')
UNION
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_name = 'Fun4All';
두 개의 SELECT 문을 UNION 키워드로 결합하면 SELECT 문이 실행한 결과를 하나로 합친다.
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_state IN ('IL', 'IN', 'MI')
UNION
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_name = 'Fun4All';
간단한 예제에서는 WHERE 절보다 UNION이 훨씬 복잡할 수 있지만 필터링 조건이 좀 더 복잡하거나 하나의 테이블이 아니라
여러 개의 테이블에서 데이터를 가져와야한다면 UNION을 사용해 훨씬 더 간단한 쿼리를 만들 수 있다.
[!NOTE] UNION 제한
표준 SQL은 UNION으로 결합하는 SELECT 문의 수에 제한을 두지 않지만,
DBMS에 따라 달라질 수 있다.
사용하는 DBMS 매뉴얼에서 UNION으로 결합할 수 있는 SELECT 문의 개 수를 확인하자.
[!NOTE] 성능 문제
좋은 DBMS는 SELECT 문을 결합하기 위해 내부적으로 쿼리 최적화기를 사용한다.
이론상으로는 성능 관점에서 여러 개의 WHERE 절 조건이나 UNION을 사용하는 것이 아무런 차이가 없어야 한다.
하지만 실제로 대부분의 쿼리 최적화기는 기대하는 만큼의 작업을 해내지 못하기 때문에 어떤 것이 더 좋은 결과를 내는지는
직접 테스트 할 필요가 있다.
UNION 규칙
UNION을 사용해 결합을 제대로 제어하려면 몇 가지 기본 규칙을 알아야 한다.
- UNION은 반드시 두 개 이상의 SELECT 문으로 구성되어야 하며, 각각의 명령문은 UNION이라는 키워드로 구분한다(만일 네 개의 SELECT 문이 있다면 UNION 키워드는 세 개)
- UNION에서 각 쿼리는 같은 열이나 수식, 그룹 함수를 가져야 한다.
- 열 데이터형은 호환될 수 있다. 정확히 같은 데이터형일 필요는 없지만, DBMS가 내부적으로 변환 할 수 있어야 한다.(예를 들어 다른 수치형이나 다른 날짜형)
[!NOTE] UNION 열 이름
UNION으로 결합한 SELECT문의 열 이름이 다르다면, 실제로는 첫 번째 열 이름을 반환하게된다.
만약 반환된 열 이름에 별칭을 사용한다면 첫 번째 열에 설정하면 된다.
다만 여기에는 약간의 부작용이 존재하는데, 첫 번째 열 이름이 사용되기 때문에 정렬할 때도 그 이름만 사용할 수 있다. (ORDER BY)
중복 행 포함하기와 제거하기
앞서 UNION은 중복된 행을 제거해 가져왔다.
이는 UNION이 쿼리 결과에서 자동으로 중복 행을 제거하기 때문이고 바꾸고싶다면 UNION 대신 UNION ALL을 사용하면 된다.
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_state IN ('IL', 'IN', 'MI')
UNION ALL
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_name = 'Fun4All';
[!NOTE] UNION vs WHERE
위 예제에서 UNION과 여러 개의 WHERE 조건을 사용한 결과가 같은걸 확인할 수 있었다.
UNION ALL은 WHERE 절에서 가져올 수 없는 결과를 반환한다.
만약 우리가 원하는게 중복된 행을 포함해서 모든 조건과 일치하는 행을 가져오는 것이라면 반드시 UNION ALL을 사용해야 한다.
결합 쿼리 결과 정렬하기
ORDER BY 절을 사용하여 SELECT 문의 결과를 정렬할 수 있다. UNION으로 쿼리를 결합할 때는 딱 하나의 ORDER BY 절을 사용할 수 있는데, 이 때 이 ORDER BY 절은 마지막 SELECT 구문 뒤에 위치 한다.
결과의 일부분은 한 방식으로 정렬하고 다른 일부분은 다른 방식으로 정렬하는 것은 의미가 없기 때문에 여러 개의 ORDER BY 절을 지원하지 않는다.
SELECT cust_name, cust_contact, cust_email
FROM Customers WHERE cust_state IN ('IL', 'IN', 'MI')
UNION
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_name = 'Fun4All'
ORDER BY cust_name, cust_contact;
[!NOTE] 다른 UNION 타입
일부 DBMS UNION과 비슷한 유형을 두 가지 더 지원한다 하나는 EXCEPT(혹은 MINUS)로 두 번째 테이블에는 없지만, 첫 번째 테이블에 있는 행을 가져올 때 사용한다.
두 번째는 INTERSECT로 두 개의 테이블에 모두 존재하는 행을 가져올 때 사용한다.
물론 JOIN을 사용하는 것과 같은 결과를 가져올 수 있어서 이런 타입은 거의 사용하지 않는다.
추가 예제
SELECT prod_id, quantity
FROM OrderItems
WHERE prod_id LIKE 'BNBG%'
UNION
SELECT prod_id, quantity
FROM OrderItems
WHERE quantity = 100
ORDER BY prod_id;
SELECT prod_id, quantity
FROM OrderItems
WHERE prod_id LIKE 'BNBG%' OR quantity = 100
ORDER BY prod_id;
SELECT prod_name
FROM Products
UNION
SELECT cust_name
FROM Customers
ORDER BY prod_name;
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_state IN ('IL', 'IN', 'MI')
UNION
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_name = 'Fun4All'
ORDER BY cust_name, cust_contact;