

 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/)을 참조하세요.

# 튜토리얼: Amazon Redshift Spectrum을 사용한 중첩 데이터 쿼리
<a name="tutorial-query-nested-data"></a>

이 자습서는 Redshift Spectrum을 사용하여 중첩된 데이터를 쿼리하는 방법을 보여줍니다. 중첩 데이터는 중첩된 필드를 포함하는 데이터입니다. 중첩 필드는 배열, 구조체 또는 객체와 같은 단일 엔터티로 함께 조인되는 필드입니다.

**Topics**
+ [개요](#tutorial-nested-data-overview)
+ [1단계: 중첩 데이터가 포함된 외부 테이블 만들기](#tutorial-nested-data-create-table)
+ [2단계: SQL 확장을 통한 Amazon S3의 중첩 데이터 쿼리](#tutorial-query-nested-data-sqlextensions)
+ [중첩 데이터 사용 사례](nested-data-use-cases.md)
+ [중첩된 데이터 제한(미리 보기)](nested-data-restrictions.md)
+ [복잡한 중첩 JSON 직렬화](serializing-complex-JSON.md)

## 개요
<a name="tutorial-nested-data-overview"></a>

Amazon Redshift Spectrum은 Parquet, ORC, JSON 및 Ion 파일 형식에서 중첩 데이터에 대한 쿼리를 지원합니다. Redshift Spectrum은 외부 테이블을 사용하여 데이터에 액세스합니다. 복합 데이터 형식인 `struct`, `array` 및 `map`을 사용하는 외부 테이블을 생성할 수 있습니다.

예를 들어 `customers`라는 폴더에 다음과 같은 Amazon S3 데이터가 저장된 데이터 파일이 있다고 가정하겠습니다. 단일 루트 요소는 없지만 이 샘플 데이터의 각 JSON 객체는 테이블의 행을 나타냅니다.

```
{"id": 1,
 "name": {"given": "John", "family": "Smith"},
 "phones": ["123-457789"],
 "orders": [{"shipdate": "2018-03-01T11:59:59.000Z", "price": 100.50},
            {"shipdate": "2018-03-01T09:10:00.000Z", "price": 99.12}]
}
{"id": 2,
 "name": {"given": "Jenny", "family": "Doe"},
 "phones": ["858-8675309", "415-9876543"],
 "orders": []
}
{"id": 3,
 "name": {"given": "Andy", "family": "Jones"},
 "phones": [],
 "orders": [{"shipdate": "2018-03-02T08:02:15.000Z", "price": 13.50}]
}
```

Amazon Redshift Spectrum을 사용하여 파일의 중첩된 데이터를 쿼리할 수 있습니다. 다음은 이 방법으로 Apache Parquet 데이터를 쿼리하는 방법을 나타내는 자습서입니다.

### 사전 조건
<a name="tutorial-nested-data-prereq"></a>

Redshift Spectrum을 아직 사용하지 않고 있다면 계속 진행하기 전에 [Amazon Redshift Spectrum 시작하기](c-getting-started-using-spectrum.md)에서 다음 단계를 따르세요.

외부 스키마를 만들려면 다음 명령에서 IAM 역할 ARN을 [IAM 역할 생성](c-getting-started-using-spectrum.md#c-getting-started-using-spectrum-create-role)에서 생성한 역할 ARN으로 대체합니다. 그런 다음 SQL 클라이언트에서 명령을 실행합니다.

```
create external schema spectrum 
from data catalog 
database 'myspectrum_db' 
iam_role 'arn:aws:iam::123456789012:role/myspectrum_role'
create external database if not exists;
```

## 1단계: 중첩 데이터가 포함된 외부 테이블 만들기
<a name="tutorial-nested-data-create-table"></a>

Amazon S3에서 [소스 데이터](https://s3.amazonaws.com/redshift-downloads/tickit/spectrum/customers/customer_file1)를 다운로드하여 볼 수 있습니다. 

이번 자습서에서 사용할 외부 테이블을 만들려면 다음과 같이 명령을 실행합니다.

```
CREATE EXTERNAL TABLE spectrum.customers (
  id     int,
  name   struct<given:varchar(20), family:varchar(20)>,
  phones array<varchar(20)>,
  orders array<struct<shipdate:timestamp, price:double precision>>
)
STORED AS PARQUET
LOCATION 's3://redshift-downloads/tickit/spectrum/customers/';
```

위의 예에서 외부 테이블 `spectrum.customers`는 데이터 형식으로 `struct`와 `array`를 사용하여 중첩 데이터가 포함된 열을 정의합니다. Amazon Redshift Spectrum은 Parquet, ORC, JSON 및 Ion 파일 형식에서 중첩 데이터에 대한 쿼리를 지원합니다. `STORED AS` 파라미터는 Apache Parquet 파일의 경우 `PARQUET`입니다. `LOCATION` 파라미터는 중첩 데이터 또는 파일이 들어 있는 Amazon S3 폴더를 참조해야 합니다. 자세한 내용은 [CREATE EXTERNAL TABLE](r_CREATE_EXTERNAL_TABLE.md) 섹션을 참조하세요.

`array`와 `struct` 형식은 모든 수준에서 중첩이 가능합니다. 예를 들어 다음 예와 같이 `toparray`라는 이름으로 열을 정의할 수 있습니다.

```
toparray array<struct<nestedarray:
         array<struct<morenestedarray: 
         array<string>>>>>
```

또한 다음 예의 `struct` 열처럼 `x` 형식을 중첩시킬 수도 있습니다.

```
x struct<a: string,
         b: struct<c: integer,
                   d: struct<e: string>
                  >
        >
```

## 2단계: SQL 확장을 통한 Amazon S3의 중첩 데이터 쿼리
<a name="tutorial-query-nested-data-sqlextensions"></a>

Redshift Spectrum은 Amazon Redshift SQL 구문 확장을 통해 복합 형식인 `array`, `map` 및 `struct`에 대한 쿼리를 지원합니다.

### 확장 1: 구조체 열에 대한 액세스
<a name="nested-data-sqlextension1"></a>

필드 이름을 경로로 연결하는 점 표기법을 사용해 `struct` 열에서 데이터를 추출할 수 있습니다. 예를 들어 다음은 고객의 성과 이름을 반환하는 쿼리입니다. 이름은 긴 경로인 `c.name.given`을 통해 액세스하고, 성은 긴 경로인 `c.name.family`을 통해 액세스합니다.

```
SELECT c.id, c.name.given, c.name.family
FROM   spectrum.customers c;
```

위의 쿼리는 다음과 같은 데이터를 반환합니다.

```
id | given | family
---|-------|-------
1  | John  | Smith
2  | Jenny | Doe
3  | Andy  | Jones
(3 rows)
```

`struct`는 어떤 수준에서든지 다른 `struct`의 열이 될 수 있고, 이 열은 또다시 다른 `struct`의 열이 될 수 있습니다. 이렇게 깊게 중첩되는 `struct`의 열에 액세스하는 경로는 계속해서 길어질 수 있습니다. 예를 들어 다음 예에서 `x` 열에 대한 정의를 보십시오.

```
x struct<a: string,
         b: struct<c: integer, 
                      d: struct<e: string>
                  >
        >
```

`e`로 `x.b.d.e`의 데이터에 액세스할 수 있습니다.

### 확장 2: FROM 절의 포괄적 배열
<a name="nested-data-sqlextension2"></a>

`array` 절에서 테이블 이름이 아닌 `map` 열을 지정하여 `array` 열(확장 시 `FROM` 열까지도)의 데이터를 추출할 수 있습니다. 이러한 확장은 기본 쿼리의 `FROM` 절은 물론이고 하위 쿼리의 `FROM` 절에도 적용됩니다.

`array` 요소는 `c.orders[0]`와 같이 위치로 참조할 수 있습니다(미리 보기).

포괄적 `arrays`를 조인과 함께 사용하면 다음 사용 사례에서 설명하겠지만 다양한 유형의 중첩 해제가 가능합니다.

#### 내부 조인을 사용한 중첩 해제
<a name="unnest-inner-joins"></a>

다음은 고객 ID와 주문한 고객의 주문 발송일을 선택하는 쿼리입니다. FROM 절의 SQL 확장인 `c.orders o`는 별칭인 `c`에 따라 달라집니다.

```
SELECT c.id, o.shipdate
FROM   spectrum.customers c, c.orders o
```

주문한 고객 `c`마다 `FROM` 절이 고객 `o`의 각 주문 `c`에 대해 행을 하나씩 반환합니다. 각 행은 고객 행 `c`와 주문 행 `o`가 함께 표시됩니다. 그러면 `SELECT` 절에는 `c.id`와 `o.shipdate`만 유지됩니다. 결과는 다음과 같습니다.

```
id|      shipdate
--|----------------------
1 |2018-03-01  11:59:59
1 |2018-03-01  09:10:00
3 |2018-03-02  08:02:15
(3 rows)
```

별칭 `c`는 고객 필드에 대한 액세스를, 그리고 별칭 `o`는 주문 필드에 대한 액세스를 제공합니다.

시맨틱은 표준 SQL과 비슷합니다. `FROM` 절을 다음 중첩 루프를 실행하는 것으로 생각할 수 있습니다. 이후에는 출력할 필드를 선택하는 `SELECT` 절이 나옵니다.

```
for each customer c in spectrum.customers
  for each order o in c.orders
     output c.id and o.shipdate
```

따라서 주문하지 않은 고객은 결과에서도 표시되지 않습니다.

또한 이것을 `FROM` 테이블과 `JOIN` 배열을 사용해 `customers`을 실행하는 `orders` 절이라고 생각할 수도 있습니다. 실제로 다음 예와 같이 쿼리를 작성하는 것도 가능합니다.

```
SELECT c.id, o.shipdate
FROM   spectrum.customers c INNER JOIN c.orders o ON true
```

**참고**  
이름이 `c`인 테이블에 `orders`라는 스키마가 존재한다면 `c.orders`는 `orders`의 배열 열이 아닌 `customers` 테이블을 참조합니다.

#### 왼쪽 조인을 사용한 중첩 해제
<a name="unnest-left-joins"></a>

다음은 모든 고객 이름과 고객의 주문을 출력하는 쿼리입니다. 따라서 주문하지 않은 고객의 이름까지 모두 반환됩니다. 하지만 이 경우에는 아래 Jenny Doe의 예와 같이 주문 열이 NULL 값을 갖습니다.

```
SELECT c.id, c.name.given, c.name.family, o.shipdate, o.price
FROM   spectrum.customers c LEFT JOIN c.orders o ON true
```

위의 쿼리는 다음과 같은 데이터를 반환합니다.

```
id  |  given  | family  |    shipdate          | price
----|---------|---------|----------------------|--------
 1  |  John   | Smith   | 2018-03-01  11:59:59 | 100.5
 1  |  John   | Smith   | 2018-03-01  09:10:00 |  99.12
 2  |  Jenny  | Doe     |                      |
 3  |  Andy   | Jones   | 2018-03-02  08:02:15 |  13.5
 (4 rows)
```

### 확장 3: 별칭을 사용해 스칼라 배열에 직접 액세스
<a name="nested-data-sqlextension3"></a>

`FROM` 절의 별칭 `p`가 스칼라 배열을 포괄하는 경우에는 쿼리가 `p` 값을 `p`로 참조합니다. 예를 들어 다음은 고객 이름과 전화 번호를 쌍으로 반환하는 쿼리입니다.

```
SELECT c.name.given, c.name.family, p AS phone
FROM   spectrum.customers c LEFT JOIN c.phones p ON true
```

위의 쿼리는 다음과 같은 데이터를 반환합니다.

```
given  |  family  |  phone
-------|----------|-----------
John   | Smith    | 123-4577891
Jenny  | Doe      | 858-8675309
Jenny  | Doe      | 415-9876543
Andy   | Jones    | 
(4 rows)
```

### 확장 4: 맵 요소에 대한 액세스
<a name="nested-data-sqlextension4"></a>

Redshift Spectrum은 `map` 데이터 형식을 `array` 열과 `struct` 열로 구성된 `key` 형식이 포함된 `value` 형식으로 처리합니다. `key`는 `scalar`가 되어야 하고, 값은 어떤 데이터 형식이든 될 수 있습니다.

예를 들어 다음은 전화 번호를 저장할 목적으로 `map`이 포함된 외부 테이블을 만드는 코드입니다.

```
CREATE EXTERNAL TABLE spectrum.customers2 (
  id     int,
  name   struct<given:varchar(20), family:varchar(20)>,
  phones map<varchar(20), varchar(20)>,
  orders array<struct<shipdate:timestamp, price:double precision>>
)
STORED AS PARQUET
LOCATION 's3://redshift-downloads/tickit/spectrum/customers/';
```

`map` 형식은 `array` 열과 `key` 열로 구성된 `value` 형식처럼 처리되기 때문에 앞에 나오는 스키마를 마치 뒤에 나오는 스키마라고 생각할 수 있습니다.

```
CREATE EXTERNAL TABLE spectrum.customers3 (
  id     int,
  name   struct<given:varchar(20), family:varchar(20)>,
  phones array<struct<key:varchar(20), value:varchar(20)>>,
  orders array<struct<shipdate:timestamp, price:double precision>>
)
STORED AS PARQUET
LOCATION 's3://redshift-downloads/tickit/spectrum/customers/';
```

다음은 고객 이름과 휴대 전화 번호를 함께 반환한 후 각 이름의 번호를 반환하는 쿼리입니다. 맵 쿼리는 `array` 형식의 중첩 `struct`를 쿼리하는 것과 동일하게 처리됩니다. 다음은 앞에서 설명한 외부 테이블을 만든 경우에만 데이터를 반환하는 쿼리입니다.

```
SELECT c.name.given, c.name.family, p.value 
FROM   spectrum.customers c, c.phones p 
WHERE  p.key = 'mobile';
```

**참고**  
`key`에 대한 `map`는 Ion 및 JSON 파일 형식에 대한 `string`입니다.