

 Amazon Redshift는 패치 198부터 새 Python UDF 생성을 더 이상 지원하지 않습니다. 기존 Python UDF는 2026년 6월 30일까지 계속 작동합니다. 자세한 내용은 [블로그 게시물](https://aws.amazon.com/blogs/big-data/amazon-redshift-python-user-defined-functions-will-reach-end-of-support-after-june-30-2026/)을 참조하세요.

# UNION, INTERSECT 및 EXCEPT
<a name="r_UNION"></a>

**Topics**
+ [구문](#r_UNION-synopsis)
+ [파라미터](#r_UNION-parameters)
+ [설정 연산자에 대한 평가 순서](#r_UNION-order-of-evaluation-for-set-operators)
+ [사용 노트](#r_UNION-usage-notes)
+ [UNION 쿼리 예](c_example_union_query.md)
+ [UNION ALL 쿼리 예](c_example_unionall_query.md)
+ [INTERSECT 쿼리 예](c_example_intersect_query.md)
+ [EXCEPT 쿼리 예](c_Example_MINUS_query.md)

UNION, INTERSECT 및 EXCEPT *설정 연산자*는 별개의 두 쿼리 표현식의 결과를 비교 및 병합하는 데 사용됩니다. 예를 들어, 웹사이트의 어떤 사용자가 구매자인 동시에 판매자인지 알고 싶지만 이런 사용자들의 사용자 이름이 별개의 열이나 테이블에 저장되어 있는 경우 이러한 두 가지 사용자 유형의 *교집합*을 찾을 수 있습니다. 어떤 웹사이트 사용자가 구매자이고 판매자는 아닌지 알고 싶으면 EXCEPT 연산자를 사용하여 두 사용자 목록 사이의 *차이*를 찾을 수 있습니다. 역할과는 상관없이 모든 사용자의 목록을 빌드하려면 UNION 연산자를 사용할 수 있습니다.

## 구문
<a name="r_UNION-synopsis"></a>

```
query
{ UNION [ ALL ] | INTERSECT | EXCEPT | MINUS }
query
```

## 파라미터
<a name="r_UNION-parameters"></a>

 *query*()   
UNION, INTERSECT 또는 EXCEPT 연산자 뒤에 쿼리 표현식의 선택 목록 형태로 제2의 쿼리 표현식에 상응하는 쿼리 표현식입니다. 이 두 표현식에는 호환 데이터 형식을 가진 같은 개수의 출력 열이 있어야 합니다. 그렇지 않으면 두 결과 집합을 비교 및 병합할 수 없습니다. 설정 연산은 서로 다른 범주의 데이터 형식 간의 암시적 변환을 허용하지 않습니다. 자세한 내용은 [형식 호환성 및 변환](c_Supported_data_types.md#r_Type_conversion) 섹션을 참조하세요.  
무제한 개수의 쿼리 표현식을 포함하는 쿼리를 빌드하고 임의의 조합으로 UNION, INTERSECT 및 EXCEPT 연산자와 연결할 수 있습니다. 예를 들어, 테이블 T1, T2 및 T3에 호환되는 열 집합이 포함되어 있다고 가정하면 다음 쿼리 구조가 유효합니다.  

```
select * from t1
union
select * from t2
except
select * from t3
order by c1;
```

UNION   
행이 한 표현식이나 두 표현식 모두에서 파생하는지에 상관없이, 두 쿼리 표현식에서 행을 반환하는 작업을 설정합니다.

INTERSECT   
두 쿼리 표현식에서 파생하는 행을 반환하는 작업을 설정합니다. 두 표현식에서 모두 반환되지 않는 행은 삭제됩니다.

EXCEPT \$1 MINUS   
두 쿼리 표현식 중 하나에서 파생하는 행을 반환하는 작업을 설정합니다. 첫 번째 결과 테이블에는 있지만 두 번째 결과 테이블에는 없는 행에 대한 결과가 반환될 수 있다. MINUS 및 EXCEPT는 정확히 동의어입니다.

ALL   
ALL 키워드는 UNION에 의해 생성되는 중복 행을 모두 유지합니다. ALL 키워드가 사용되지 않을 때의 기본 동작은 이러한 중복 항목을 삭제하는 것입니다. INTERSECT ALL, EXCEPT ALL 및 MINUS ALL은 지원되지 않습니다.

## 설정 연산자에 대한 평가 순서
<a name="r_UNION-order-of-evaluation-for-set-operators"></a>

UNION 및 EXCEPT 설정 연산자는 좌우선 결합 연산자입니다. 우선순위에 영향을 주기 위해 괄호가 지정되어 있지 않은 경우 이러한 설정 연산자의 조합은 왼쪽에서 오른쪽으로 계산됩니다. 예를 들어 다음 쿼리에서, T1 및 T2의 UNION이 먼저 계산된 다음 UNION 결과에 대해 EXCEPT 작업이 수행됩니다.

```
select * from t1
union
select * from t2
except
select * from t3
order by c1;
```

동일한 쿼리에 연산자 조합이 사용될 때 INTERSECT 연산자가 UNION 및 EXCEPT 연산자보다 우선합니다. 예를 들어 다음 쿼리는 T2 및 T3의 교집합을 계산한 다음 그 결과와 T1의 합집합을 구합니다.

```
select * from t1
union
select * from t2
intersect
select * from t3
order by c1;
```

괄호를 추가하면 다른 계산 순서를 적용할 수 있습니다. 다음 경우에는 T1 및 T2의 합집합 결과가 T3와 교집합을 이루고, 쿼리가 다른 결과를 낳을 가능성이 있습니다.

```
(select * from t1
union
select * from t2)
intersect
(select * from t3)
order by c1;
```

## 사용 노트
<a name="r_UNION-usage-notes"></a>
+ 설정 작업 쿼리의 결과에 반환되는 열 이름은 첫 번째 쿼리 표현식의 테이블에서 가져온 열 이름(또는 별칭)입니다. 열의 값이 설정 연산자의 어느 한쪽에 있는 테이블에서 파생한다는 점에서 이런 열 이름은 오해를 불러일으킬 가능성이 있으므로, 결과 집합에 대해 의미 있는 별칭을 부여하고 싶을 수도 있습니다.
+ 설정 연산자에 선행하는 쿼리 표현식에 ORDER BY 절을 포함하면 안 됩니다. ORDER BY 절은 설정 연산자를 포함하는 쿼리의 끝에 사용될 때만 의미 있게 정렬된 결과를 내놓습니다. 이 경우에는 ORDER BY 절이 모든 설정 작업의 최종 결과에 적용됩니다. 가장 바깥쪽 쿼리는 표준 LIMIT 및 OFFSET 절도 포함할 수 있습니다.
+ 설정 연산자 쿼리가 10진수 결과를 반환할 때 그에 상응하는 결과 열은 같은 정밀도와 규모를 반환하도록 승격됩니다. 예를 들어, 다음 쿼리에서 T1.REVENUE가 DECIMAL(10,2) 열이고 T2.REVENUE가 DECIMAL(8,4) 열인 경우 10진수 결과는 DECIMAL(12,4)로 승격됩니다.

  ```
  select t1.revenue union select t2.revenue;
  ```

  규모는 두 열의 최대 규모인 `4`입니다. T1.REVENUE는 소수점 왼쪽에 8자리가 필요하므로(12 - 4 = 8) 정밀도는 `12`입니다. 이러한 유형 승격은 UNION 양쪽 모두의 값이 전부 결과에 부합하도록 합니다. 64비트 값의 경우, 최대 결과 정밀도는 19이고 최대 결과 규모는 18입니다. 128비트 값의 경우, 최대 결과 정밀도는 38이고 최대 결과 규모는 37입니다.

  결과 데이터 형식이 Amazon Redshift 전체 자릿수 및 소수 자릿수 제한을 초과하는 경우 쿼리는 오류를 반환합니다.
+ 설정 작업의 경우, 각각 상응하는 열 쌍에 대해 두 데이터 값이 *equal* 또는 *both NULL*인 경우 두 행이 동일한 것으로 처리됩니다. 예를 들어, 테이블 T1과 T2에 모두 한 열과 한 행이 있고 그 행이 두 테이블에서 모두 NULL인 경우 두 테이블에 대해 INTERSECT 연산을 수행하면 바로 그 행이 반환됩니다.

# UNION 쿼리 예
<a name="c_example_union_query"></a>

다음 UNION 쿼리에서 SALES 테이블의 행은 LISTING 테이블의 행과 병합됩니다. 각각의 테이블에서 호환되는 3개의 열이 선택되며, 이 경우에는 해당하는 열들의 이름과 데이터 형식이 동일합니다.

최종 결과 집합은 LISTING 테이블의 첫 번째 열을 기준으로 정렬되고 LISTID 값이 가장 높은 5개의 행으로 제한됩니다.

```
select listid, sellerid, eventid from listing
union select listid, sellerid, eventid from sales
order by listid, sellerid, eventid desc limit 5;

listid | sellerid | eventid
--------+----------+---------
1 |    36861 |    7872
2 |    16002 |    4806
3 |    21461 |    4256
4 |     8117 |    4337
5 |     1616 |    8647
(5 rows)
```

다음 예는 결과 집합에서 어떤 쿼리 표현식이 각각의 행을 생성했는지 볼 수 있도록 UNION 쿼리의 출력에 리터럴 값을 추가할 수 있는 방법을 보여줍니다. 이 쿼리는 첫 번째 쿼리 표현식의 행을 "B"(buyer)로 식별하고 두 번째 쿼리 표현식의 행을 "S"(seller)로 식별합니다.

이 쿼리는 \$110,000 이상의 티켓 거래에 대해 구매자와 판매자를 식별합니다. UNION 연산자의 어느 한쪽에서 두 쿼리 표현식의 유일한 차이점은 SALES 테이블에 대한 조인 열입니다.

```
select listid, lastname, firstname, username,
pricepaid as price, 'S' as buyorsell
from sales, users
where sales.sellerid=users.userid
and pricepaid >=10000
union
select listid, lastname, firstname, username, pricepaid,
'B' as buyorsell
from sales, users
where sales.buyerid=users.userid
and pricepaid >=10000
order by 1, 2, 3, 4, 5;

listid | lastname | firstname | username |   price   | buyorsell
--------+----------+-----------+----------+-----------+-----------
209658 | Lamb     | Colette   | VOR15LYI |  10000.00 | B
209658 | West     | Kato      | ELU81XAA |  10000.00 | S
212395 | Greer    | Harlan    | GXO71KOC |  12624.00 | S
212395 | Perry    | Cora      | YWR73YNZ |  12624.00 | B
215156 | Banks    | Patrick   | ZNQ69CLT |  10000.00 | S
215156 | Hayden   | Malachi   | BBG56AKU |  10000.00 | B
(6 rows)
```

중복된 행이 발견되는 경우 결과에 이런 행을 유지해야 하므로, 다음 예에서는 UNION ALL 연산자를 사용합니다. 이벤트 ID의 특정 시리즈에 대해, 쿼리는 각 이벤트와 관련된 각각의 판매에 대해 0개 이상의 행을 반환하고 그 이벤트의 각 목록에 대해 0개 또는 1개의 행을 반환합니다. 이벤트 ID는 LISTING 및 EVENT 테이블에서 각각의 행에 고유하지만, SALES 테이블에서 이벤트 및 목록 ID의 동일한 조합에 대해 여러 개의 판매 건이 있을 수 있습니다.

결과 집합의 세 번째 열은 행의 원본을 식별합니다. 행의 출처가 SALES 테이블인 경우 SALESROW 열에 "YES"로 표시됩니다. (SALESROW는 SALES.LISTID의 별칭입니다.) 행의 출처가 LISTING 테이블인 경우 SALESROW 열에 "No"로 표시됩니다.

이 경우, 결과 집합은 목록 500, 이벤트 7787에 대해 3개의 판매 행으로 구성됩니다. 즉, 이 목록 및 이벤트 조합에 대해 3가지 다른 트랜잭션이 발생했습니다. 다른 두 목록 501 및 502에서는 어떤 판매도 생성되지 않았으므로, 쿼리가 이들 목록 ID에 대해 생성하는 유일한 행의 출처는 LISTING 테이블입니다(SALESROW = 'No').

```
select eventid, listid, 'Yes' as salesrow
from sales
where listid in(500,501,502)
union all
select eventid, listid, 'No'
from listing
where listid in(500,501,502)
order by listid asc;

eventid | listid | salesrow
---------+--------+----------
7787 |    500 | No
7787 |    500 | Yes
7787 |    500 | Yes
7787 |    500 | Yes
6473 |    501 | No
5108 |    502 | No
(6 rows)
```

ALL 키워드 없이 같은 쿼리를 실행하는 경우 결과에는 판매 거래 중 하나만 유지됩니다.

```
select eventid, listid, 'Yes' as salesrow
from sales
where listid in(500,501,502)
union
select eventid, listid, 'No'
from listing
where listid in(500,501,502)
order by listid asc;

eventid | listid | salesrow
---------+--------+----------
7787 |    500 | No
7787 |    500 | Yes
6473 |    501 | No
5108 |    502 | No
(4 rows)
```

# UNION ALL 쿼리 예
<a name="c_example_unionall_query"></a>

중복된 행이 발견되는 경우 결과에 이런 행을 유지해야 하므로, 다음 예에서는 UNION ALL 연산자를 사용합니다. 이벤트 ID의 특정 시리즈에 대해, 쿼리는 각 이벤트와 관련된 각각의 판매에 대해 0개 이상의 행을 반환하고 그 이벤트의 각 목록에 대해 0개 또는 1개의 행을 반환합니다. 이벤트 ID는 LISTING 및 EVENT 테이블에서 각각의 행에 고유하지만, SALES 테이블에서 이벤트 및 목록 ID의 동일한 조합에 대해 여러 개의 판매 건이 있을 수 있습니다.

결과 집합의 세 번째 열은 행의 원본을 식별합니다. 행의 출처가 SALES 테이블인 경우 SALESROW 열에 "YES"로 표시됩니다. (SALESROW는 SALES.LISTID의 별칭입니다.) 행의 출처가 LISTING 테이블인 경우 SALESROW 열에 "No"로 표시됩니다.

이 경우, 결과 집합은 목록 500, 이벤트 7787에 대해 3개의 판매 행으로 구성됩니다. 즉, 이 목록 및 이벤트 조합에 대해 3가지 다른 트랜잭션이 발생했습니다. 다른 두 목록 501 및 502에서는 어떤 판매도 생성되지 않았으므로, 쿼리가 이들 목록 ID에 대해 생성하는 유일한 행의 출처는 LISTING 테이블입니다(SALESROW = 'No').

```
select eventid, listid, 'Yes' as salesrow
from sales
where listid in(500,501,502)
union all
select eventid, listid, 'No'
from listing
where listid in(500,501,502)
order by listid asc;

eventid | listid | salesrow
---------+--------+----------
7787 |    500 | No
7787 |    500 | Yes
7787 |    500 | Yes
7787 |    500 | Yes
6473 |    501 | No
5108 |    502 | No
(6 rows)
```

ALL 키워드 없이 같은 쿼리를 실행하는 경우 결과에는 판매 거래 중 하나만 유지됩니다.

```
select eventid, listid, 'Yes' as salesrow
from sales
where listid in(500,501,502)
union
select eventid, listid, 'No'
from listing
where listid in(500,501,502)
order by listid asc;

eventid | listid | salesrow
---------+--------+----------
7787 |    500 | No
7787 |    500 | Yes
6473 |    501 | No
5108 |    502 | No
(4 rows)
```

# INTERSECT 쿼리 예
<a name="c_example_intersect_query"></a>

다음 예를 첫 번째 UNION 예와 비교해 보십시오. 두 예에서는 사용되는 설정 연산자만 다를 뿐이지만, 그 결과는 매우 상이합니다. 다음과 같이 행들 중 하나만 같습니다.

```
235494 |    23875 |    8771
```

 이 행이 양쪽 테이블에서 발견된 5개 행의 제한된 결과에 있는 유일한 행입니다.

```
select listid, sellerid, eventid from listing
intersect
select listid, sellerid, eventid from sales
order by listid desc, sellerid, eventid
limit 5;

listid | sellerid | eventid
--------+----------+---------
235494 |    23875 |    8771
235482 |     1067 |    2667
235479 |     1589 |    7303
235476 |    15550 |     793
235475 |    22306 |    7848
(5 rows)
```

다음 쿼리는 3월에 뉴욕과 로스앤젤레스의 두 도시에서 모두 현장에서 이루어진 (티켓이 판매된) 이벤트를 찾습니다. 두 쿼리 표현식의 차이점은 VENUECITY 열에 대한 제약 조건입니다.

```
select distinct eventname from event, sales, venue
where event.eventid=sales.eventid and event.venueid=venue.venueid
and date_part(month,starttime)=3 and venuecity='Los Angeles'
intersect
select distinct eventname from event, sales, venue
where event.eventid=sales.eventid and event.venueid=venue.venueid
and date_part(month,starttime)=3 and venuecity='New York City'
order by eventname asc;

eventname
----------------------------
A Streetcar Named Desire
Dirty Dancing
Electra
Running with Annalise
Hairspray
Mary Poppins
November
Oliver!
Return To Forever
Rhinoceros
South Pacific
The 39 Steps
The Bacchae
The Caucasian Chalk Circle
The Country Girl
Wicked
Woyzeck
(16 rows)
```

# EXCEPT 쿼리 예
<a name="c_Example_MINUS_query"></a>

TICKIT 데이터베이스의 CATEGORY 테이블은 다음 11개의 행을 포함합니다.

```
 catid | catgroup |  catname  |                  catdesc
-------+----------+-----------+--------------------------------------------
   1   | Sports   | MLB       | Major League Baseball
   2   | Sports   | NHL       | National Hockey League
   3   | Sports   | NFL       | National Football League
   4   | Sports   | NBA       | National Basketball Association
   5   | Sports   | MLS       | Major League Soccer
   6   | Shows    | Musicals  | Musical theatre
   7   | Shows    | Plays     | All non-musical theatre
   8   | Shows    | Opera     | All opera and light opera
   9   | Concerts | Pop       | All rock and pop music concerts
  10   | Concerts | Jazz      | All jazz singers and bands
  11   | Concerts | Classical | All symphony, concerto, and choir concerts
(11 rows)
```

CATEGORY\$1STAGE 테이블(스테이징 테이블)에 추가적인 행이 한 개 있다고 가정합니다.

```
 catid | catgroup |  catname  |                  catdesc
-------+----------+-----------+--------------------------------------------
1 | Sports   | MLB       | Major League Baseball
2 | Sports   | NHL       | National Hockey League
3 | Sports   | NFL       | National Football League
4 | Sports   | NBA       | National Basketball Association
5 | Sports   | MLS       | Major League Soccer
6 | Shows    | Musicals  | Musical theatre
7 | Shows    | Plays     | All non-musical theatre
8 | Shows    | Opera     | All opera and light opera
9 | Concerts | Pop       | All rock and pop music concerts
10 | Concerts | Jazz      | All jazz singers and bands
11 | Concerts | Classical | All symphony, concerto, and choir concerts
12 | Concerts | Comedy    | All stand up comedy performances
(12 rows)
```

두 테이블 사이의 차이점을 반환합니다. 다시 말해, CATEGORY\$1STAGE 테이블에는 있지만 CATEGORY 테이블에는 없는 행을 반환합니다.

```
select * from category_stage
except
select * from category;

catid | catgroup | catname |             catdesc
-------+----------+---------+----------------------------------
12 | Concerts | Comedy  | All stand up comedy performances
(1 row)
```

다음과 같은 동등한 쿼리는 동의어 MINUS를 사용합니다.

```
select * from category_stage
minus
select * from category;

catid | catgroup | catname |             catdesc
-------+----------+---------+----------------------------------
12 | Concerts | Comedy  | All stand up comedy performances
(1 row)
```

SELECT 표현식의 순서를 반대로 하면 쿼리가 아무런 행도 반환하지 않습니다.