# 31강. 집합 연산
지금부터는 `복수의 테이블`을 사용해 데이터를 검색하는 방법에 관해 알아본다.

- `RDBMS`의 창시자인 에드거 커드는 `관계형 모델`을 고안한 인물이기도 하다.
- 관계형 모델을 채택한 데이터베이스를 관계형 데이터베이스라 부른다.
- 관계형 모델에서의 관계형은 수학 집합론의 관계형 이론에서 유래했다.
- 집합론이라고 거창하게 말하지만 실질적으로는 데이터베이스의 데이터를 집합으로 간주해 다루기 쉽게 하자는 것에 지나지 않는다.

---

## 1. SQL과 집합
집합이라 하면 `AND`와 `OR`를 설명할 때 등장했던 `벤 다이어그램`을 떠올리면 쉽다.
- 벤 다이어그램에서는 하나의 원이 곧 하나의 집합이다.
- 원 안에는 몇 가지 요소가 포함되는데, 원 안에 적혀있는 숫자가 요소에 해당한다.
- 한편 데이터베이스에서는 테이블의 행이 요소에 해당한다.
- 행은 여러 개의 열로 구성되는 경우도 있으므로, 수치 상으로는 복수의 값이 존재한다.
- 하지만 집합의 요소라는 측면에서 보면 하나의 행이 곧 하나의 요소가 된다.
- `SELECT` 명령을 실행하면 데이터베이스에 질의하며 그 결과 몇 개의 행이 반환된다.
- 이때 반환된 결과 전체를 하나의 집합이라고 생각하면 된다.

---

## 2. UNION으로 합집합 구하기
- 집합의 연산에는 `합집합`이라는 것이 있다. 이는 집합을 서로 더한 것을 말한다.
- 우선 간단한 숫자를 모아놓은 집합을 이용해 설명한다.
- A와 B라는 두 개의 집합이 존재한다고 했을 때, A 집합에는 [1, 2, 3]이라는 세 개의 요소가, B 집합에는 [2, 10, 11]이라는 세 개의 요소가 있다.
- 그중 2라는 요소는 A에도 B에도 모두 존재한다.
- 집합 A와 B의 합집합을 구하면 그 결과는 [1, 2, 3, 10, 11]이 된다.
- 벤 다이어 그램으로 설명하면 두 개의 집합을 모두 합한 부분이 합집합의 결과이다.
- 이때 두 개 집합에서 겹쳐지는 부분, 즉 '요소 2'가 계산 결과로는 한 개밖에 나타나지 않는다는 점이 핵심이다.
- 즉, 단순하게 서로 더하면 [1, 2, 3, 2, 10, 11]과 같이 6개의 요소가 되지만 합집합에서는 그렇지 않다.

### UNION
- SQL에서는 `SELECT` 명령의 실행 결과를 하나의 집합으로 다룰 수 있다.
- 합집합을 계산할 경우에는 수학에서 사용하던 U기호 대신 `UNION` 키워드를 사용한다.
- U는 `U`와 모양이 비슷해 기억하기 쉽다. 즉, 수학에서의 `A U B`는 SQL에서는 `A UNION B`라고 표현한다.
- A나 B로 표현했지만 실제로는 `SELECT` 명령이다.
- `SELECT` 명령의 결과를 집합으로 간주하고, `UNION`으로 합집합을 구할 수 있다.

**`두 개의 SELECT 명령을 UNION해서 합집합 구하기`**
```
SELECT * FROM smaple71_a
UNION
SELECT * FROM sample71_b;
```

- 이때 두 개의 `SELECT` 명령을 하나의 명령으로 합치는 만큼, `세미콜론(;)`은 맨 나중에 붙인다는 점에 주의해야 한다.
- 만약 sample71_a 뒤에 세미콜론을 붙이면 도중에 명령이 끝나버리므로 제대로 실행되지 않는다.
- 정리하면, 한 번의 쿼리 실행으로 두 개의 `SELECT` 명령이 내부적으로 실행되는 형식이다.
- 이때 각 `SELECT` 명령의 실행결과(집합)를 합집합(UNION)으로 계산하여 최종적으로 결과를 반환한다.

##### UNION으로 두 개의 SELECT 명령을 하나로 연계해 질의 결과를 얻을 수 있다!

- `UNION`을 이용하면 여러 개의 `SELECT` 명령을 하나로 묶을 수 있다.
- 1+2+3...처럼 연속해서 더하는 것과 같은 형식이다.
- 이때 각각의 `SELECT` 명령의 열의 내용은 서로 일치해야 한다.
- 예를 들어 sample71_a와 sample71_b의 경우, 열 이름은 서로 다르지만 열 개수와 자료형이 서로 같기 때문에 일치한다고 말할 수 있다.
- 반면 다음과 같이 완전히 열 구성이 다른 테이블을 `UNION`으로 묶을 수는 없다.

```
SELECT * FROM sample71_a
UNION
SELECT * FROM sample71_b
UNION
SELECT * FROM sample31;
```

- 다만 전체 데이터를 반환하는 `애스터리스크(*)`를 쓰지 않고, 열을 따로 지정하여 각 `SELECT` 명령에서 집합의 요소가 될 데이터를 서로 맞춰주면 `UNION`으로 실행할 수 있는 쿼리가 된다. 예를 들면 다음과 같다.

```
SELECT a FROM sample71_a
UNION
SELECT b FROM sample71_b
UNION
SELECT age FROM sample31;
```

- `SELECT` 명령들을 `UNION`으로 묶을 때 나열 순서는 합집합의 결과에 영향을 주지 않는다.
- 따라서 아래의 명령들은 결과가 모두 같다.
- 단, 결괏값의 나열 순서는 달라질 수도 있다.
- `ORDER BY`를 지정하지 않은 `SELECT` 명령은 결과가 내부처리의 상황에 따라 바뀌기 때문이다.

```
SELECT * FROM sample71_a UNION SELECT * FROM sample71_b;
SELECT * FROM sample71_b UNION SELECT * FROM sample71_a;
```

- `UNION`을 사용할 때에는 `ORDER BY`를 지정하는 방법에 주의해야 한다.

### UNION을 사용할 때의 ORDER BY
- `UNION`으로 `SELECT` 명령을 결합해 합집합을 구하는 경우, 각 `SELECT` 명령에 `ORDER BY`를 지정해 정렬할 수는 없다.
- `ORDER BY`를 지정할 때는 마지막 `SELECT` 명령에만 지정하도록 한다.
- 예를 들어 다음과 같은 쿼리를 실행하면 에러가 발생한다.

**`첫 번째 SELECT 명령에 ORDER BY를 지정할 수 없다`**
```
SELECT a FROM sample71_a ORDER BY a
UNION
SELECT b FROM sample71_b;
```

- `ORDER BY`로 정렬할 수 없다는 뜻이 아니다. 합집합의 결과를 정렬하므로, 가장 마지막의 `SELECT` 명령에 `ORDER BY`를 지정해야 한다는 의미이다.

**`마지막의 SELECT 명령에 ORDER BY를 지정한다`**
```
SELECT a FROM sample71_a
UNION
SELECT b FROM sample71_b ORDER BY b;
```

- 하지만 이 쿼리에서도 에러가 발생한다. `ORDER BY`를 지정할 수 있다고 해도 마지막의 `SELECT` 명령의 결과만 정렬하는 것이 아니고 합집합의 결과를 정렬하는 것이기 때문이다.
- 이때 두 개의 `SELECT` 명령에서 열 이름이 서로 일치한다면 문제가 없겠지만 앞의 예제에서처럼 반드시 그렇다는 보장이 없다.
- 이런 경우 서로 동일하게 별명을 붙여 정렬할 수 있다.

```
SELECT a AS c FROM sample71_a
UNION
SELECT b AS c FROM sample71_b ORDER BY c;
```

##### UNION으로 SELECT 명령을 연결하는 경우, 가장 마지막 SELECT 명령에 대해서만 ORDER BY 구를 지정할 수 있다!
##### ORDER BY 구에 지정하는 열은 별명을 붙여 이름을 일치시킨다!

### UNION ALL
- `UNION`은 합집합을 구하는 것이므로 두 개의 집합에서 겹치는 부분은 공통 요소가 된다.
- 예를 들어 앞에서 살펴본 sample71_a와 sample71_b 예제에서는 양쪽 모두 2가 포함되어 있었다. 그리고 이들을 합집합 하면 2는 하나만 존재한다.
- 두 명령의 실행 결과에 `DISTINCT`를 걸어 중복을 제거한 것과 같다고 생각하면 이해하기 쉬울 것이다.
- 수학에서 말하는 집합은 중복값이 존재하지 않는 것을 전제로 한다.
- 그래서 `UNION`을 한 결과에도 중복값이 제거되어 있다.
- 하지만 경우에 따라서는 중복을 제거하지 않고 2개의 `SELECT` 명령의 결과를 그냥 합치고 싶은 때도 있을 것이다. 이러한 경우에는 `UNION ALL`을 사용한다.
- `SELECT` 명령에서 중복을 제거할 때는 `SELECT` 구에 `DISTINCT`를 지정한다.
- 이때 기본값은 `ALL`로, 명시적으로 지정하거나 생략할 수도 있다.
- 즉, 중복을 제거하는 경우에는 `DISTINCT`, 중복을 제거하지 않고 모두를 반환하는 경우에는 `ALL`을 추가적으로 지정한다.
- 즉, `DISTINCT`나 `ALL`로 중복제거 여부를 지정할 수 있다는 점은 똑같지만, `UNION`의 기본동작은 `ALL`이 아닌 `DISTINCT`라는 점이 다르다.
- 또한 `UNION DISTINCT`라는 문법은 허용되지 않으므로 주의해야 한다.

**`두 개의 SELECT 명령에 UNION ALL을 적용해 합집합 구하기`**
```
SELECT * FROM sample71_a
UNION ALL
SELECT * FROM sample71_b;
```

- 위의 명령을 실행하면 2라는 값을 가진 행이 중복되어 표시된다.
- `UNION ALL`은 두 개의 집합을 단순하게 합치는 것이다.
- `UNION`에서는 이미 존재하는 값인지를 검사하는 처리가 필요한 만큼, `UNION ALL` 쪽이 성능적으로는 유리할 경우가 있다.
- 즉, 중복값이 없는 경우에는 `UNION ALL`을 사용하는 편이 좋은 성능을 보여준다.

---

## 3. 교집합과 차집합
- `MySQL`에서는 지원되지 않는다.
- SQL을 이용해 교집합, 차집합도 구할 수 있다. 교집합은 `INTERSECT`를, 차집합은 `EXCEPT`를(`Oracle`의 경우는 `MINUS`) 사용한다.
- 교집합이란 두 개의 집합이 겹치는 부분을 말하며, `공통 부분`이라 불리기도 한다.
- 차집합은 집합에서 다른 쪽의 집합을 제거하고 남은 부분이다.
- 계산 대상이 되는 두 개의 집합에 공통부분이 존재하지 않으면 차집합을 구해도 결과는 바뀌지 않는다.
- 또한 완전히 같은 집합끼리 차집합을 계산하면 아무런 요소도 존재하지 않는 공집합이 된다.
- 차집합의 결과가 공집합인지 아닌지에 따라 두 개의 집합이 동일한지 아닌지를 알 수 있다.

---