> For the complete documentation index, see [llms.txt](https://docs.tibero.com/tibero-manuals/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.tibero.com/tibero-manuals/7.2.6.manuals/tbpsm-guide/composite-types/collections.md).

# 컬렉션 타입

컬렉션 타입인 네스티드 테이블, 인덱스 테이블, 배열을 선언하고 사용하는 방법을 설명합니다.

컬렉션이란 같은 타입을 갖는 구성요소의 집합입니다. 일반적인 프로그래밍 언어에서 사용하는 배열이나 리스트와 비슷한 개념입니다.

tbPSM에서 지원하는 컬렉션 타입에는 세 가지가 있습니다. 즉, 네스티드 테이블, 인덱스 테이블, 그리고 배열입니다. 세 타입은 크기 제한, 초기화 필요 여부, 인덱스의 형태가 서로 다릅니다. 차이를 정리하면 다음과 같습니다.

<table><thead><tr><th width="180">구분</th><th width="170">네스티드 테이블</th><th width="170">인덱스 테이블</th><th>배열</th></tr></thead><tbody><tr><td>선언할 때 크기 지정</td><td>없음</td><td>없음</td><td>필수</td></tr><tr><td>초기화(생성자 호출)</td><td>필요</td><td>불필요</td><td>필요</td></tr><tr><td>인덱스</td><td>1 이상의 정수</td><td>정수 또는 문자열. 0과 음수도 사용</td><td>1 이상의 정수</td></tr><tr><td>구성요소의 연속성</td><td>DELETE 프러시저로 중간이 비워질 수 있음</td><td>키가 있는 자리에만 존재</td><td>항상 연속</td></tr><tr><td>MULTISET 연산자와 집합 조건</td><td>지원</td><td>미지원</td><td>미지원</td></tr></tbody></table>

## **네스티드 테이블**

네스티드 테이블은 구성요소의 길이 제한이 없고, 각각의 구성요소에 접근할 때는 배열과 마찬가지로 인덱스로 접근합니다. 그러나 배열과 다르게 구성요소가 연속적으로 존재하지 않을 수 있습니다. 즉, 어떤 구성요소의 다음 인덱스에 값이 반드시 존재한다는 보장이 없습니다.

네스티드 테이블의 구성요소는 항상 같은 타입을 가지며, REF CURSOR를 제외한 모든 타입이 될 수 있습니다. 다른 컬렉션 타입이나 레코드 타입도 구성요소 타입으로 지정할 수 있습니다. tbPSM에서 네스티드 테이블을 선언하는 방법은 다음과 같습니다.

```
TYPE name IS TABLE OF type [NOT NULL];
```

구성요소 타입으로 REF CURSOR를 지정하면 컴파일 단계에서 TBR-15247이 발생합니다.

### **네스티드 테이블의 초기화**

네스티드 테이블은 초기화라는 과정을 거치게 되는데, 이 과정을 거치지 않은 변수는 항상 NULL을 가지며, 구성요소도 존재하지 않게 됩니다. 만약 초기화되지 않은 네스티드 테이블에 접근할 경우 **COLLECTION\_IS\_NULL** 예외 상황이 발생합니다. 예외 상황을 처리하는 방법은 [에러 처리](/tibero-manuals/7.2.6.manuals/tbpsm-guide/error-handling.md)를 참고합니다.

네스티드 테이블을 초기화하는 방법은 다음과 같습니다.

```
set serveroutput on
DECLARE
    TYPE kind_tab IS TABLE OF VARCHAR2(10) NOT NULL;
    kinds kind_tab;
BEGIN
    IF kinds IS NULL THEN
        DBMS_OUTPUT.PUT_LINE('before : kinds IS NULL');
    END IF;
    kinds := kind_tab('math', 'physics', 'history', 'science');
    DBMS_OUTPUT.PUT_LINE('after  : COUNT = ' || kinds.COUNT);
END;
/
```

다음은 실행 결과입니다.

```
before : kinds IS NULL
after  : COUNT = 4
```

### **네스티드 테이블의 구성요소**

네스티드 테이블은 최대 크기의 제한이 없기 때문에 초기 값으로 주어진 구성요소가 현재의 최대 길이가 됩니다. 만약 네스티드 테이블에서 NOT NULL 제약조건이 없을 경우 NULL을 구성요소로 사용할 수 있습니다.

```
set serveroutput on
DECLARE
    TYPE kind_tab IS TABLE OF VARCHAR2(10);
    kinds kind_tab := kind_tab('math', NULL, 'history', 'science');
BEGIN
    DBMS_OUTPUT.PUT_LINE('COUNT = ' || kinds.COUNT);
    IF kinds(2) IS NULL THEN
        DBMS_OUTPUT.PUT_LINE('kinds(2) IS NULL');
    END IF;
END;
/
```

다음은 실행 결과입니다.

```
COUNT = 4
kinds(2) IS NULL
```

네스티드 테이블의 구성요소는 하나의 독립적인 변수처럼 사용할 수 있으며, 해당 변수의 값을 추출할 수 있습니다. 또한 대입도 가능합니다.

예를 들면 다음과 같습니다.

```
set serveroutput on
DECLARE
    TYPE class_tab IS TABLE OF VARCHAR2(10);
    classes class_tab := class_tab('math', 'physics', 'science');
BEGIN
    classes(3) := 'history';
    IF classes(1) IS NULL THEN
        DBMS_OUTPUT.PUT_LINE('I hate this class');
    END IF;
    DBMS_OUTPUT.PUT_LINE(classes(3) || ' is my favorite class');
END;
/
```

다음은 실행 결과입니다.

```
history is my favorite class
```

네스티드 테이블의 구성요소를 참조하기 위해서는 다음과 같이 사용해야 합니다.

```
table_name(index)
```

index에는 1 이상 2^31 - 1 이하의 정수를 사용합니다. 0이나 음수를 지정하면 **SUBSCRIPT\_OUTSIDE\_LIMIT** 예외 상황이 발생하고(TBR-15111), 2^31 - 1을 넘는 값을 지정하면 **VALUE\_ERROR** 예외 상황이 발생합니다(TBR-15115).

### **MULTISET 연산자**

네스티드 테이블 형태의 변수에는 MULTISET 연산자의 결과를 대입할 수 있습니다. MULTISET 연산자로는 UNION, INTERSECT, EXCEPT 세 가지가 있습니다.

연산자마다 중복값의 제거 여부를 옵션으로 결정할 수 있습니다. 옵션을 생략하면 중복값을 유지하고, DISTINCT 옵션을 지정하면 중복값을 제거합니다. MULTISET UNION에는 중복값을 유지하는 ALL 옵션을 함께 쓸 수 있습니다. MULTISET INTERSECT와 MULTISET EXCEPT에는 옵션을 생략하거나 DISTINCT를 지정합니다(이 두 연산자 뒤에 ALL을 적으면 TBR-15002가 발생합니다).

연산자별 결과는 다음과 같습니다.

<table><thead><tr><th width="220">연산자</th><th width="150">옵션</th><th>결과</th></tr></thead><tbody><tr><td>MULTISET UNION</td><td>생략, ALL</td><td>두 네스티드 테이블의 구성요소를 모두 이어 붙임. 중복값 유지</td></tr><tr><td></td><td>DISTINCT</td><td>이어 붙인 결과에서 중복값 제거</td></tr><tr><td>MULTISET INTERSECT</td><td>생략</td><td>두 네스티드 테이블에 공통으로 있는 구성요소. 중복값 유지</td></tr><tr><td></td><td>DISTINCT</td><td>공통 구성요소에서 중복값 제거</td></tr><tr><td>MULTISET EXCEPT</td><td>생략</td><td>앞 네스티드 테이블에는 있고 뒤 네스티드 테이블에는 없는 구성요소. 중복값 유지</td></tr><tr><td></td><td>DISTINCT</td><td>앞 네스티드 테이블에만 있는 구성요소에서 중복값 제거</td></tr></tbody></table>

두 연산 대상과 결과를 대입받는 변수는 모두 같은 네스티드 테이블 타입으로 선언되어야 합니다. 구성요소 타입이 같아도 서로 다른 타입으로 선언한 변수를 연산하면 컴파일 단계에서 TBR-15038이 발생합니다. 한쪽 대상이 초기화되지 않은 상태라면 연산 결과도 초기화되지 않은 상태가 됩니다.

다음은 MULTISET 연산자의 예입니다.

```
set serveroutput on
DECLARE
    TYPE kind_tab IS TABLE OF NUMBER;
    tab1   kind_tab := kind_tab(1, 2, 3);
    tab2   kind_tab := kind_tab(1, 4, 5);
    tab3   kind_tab := kind_tab(1, 1, 2, 3);
    tab4   kind_tab := kind_tab(1, 1, 2, 4);
    tab5   kind_tab := kind_tab(1, 2, 3, 3);
    tab6   kind_tab := kind_tab(1, 2, 4, 5);
    result kind_tab;
    PROCEDURE print_tab(tag VARCHAR2, t kind_tab) IS
        buf VARCHAR2(200) := '';
    BEGIN
        FOR i IN 1 .. t.COUNT LOOP
            buf := buf || t(i) || ' ';
        END LOOP;
        DBMS_OUTPUT.PUT_LINE(tag || ' COUNT=' || t.COUNT || ' [' || buf || ']');
    END;
BEGIN
    result := tab1 MULTISET UNION ALL tab2;
    print_tab('UNION ALL         ', result);
    result := tab1 MULTISET UNION tab2;
    print_tab('UNION             ', result);
    result := tab1 MULTISET UNION DISTINCT tab2;
    print_tab('UNION DISTINCT    ', result);
    result := tab3 MULTISET INTERSECT tab4;
    print_tab('INTERSECT         ', result);
    result := tab3 MULTISET INTERSECT DISTINCT tab4;
    print_tab('INTERSECT DISTINCT', result);
    result := tab5 MULTISET EXCEPT tab6;
    print_tab('EXCEPT            ', result);
    result := tab5 MULTISET EXCEPT DISTINCT tab6;
    print_tab('EXCEPT DISTINCT   ', result);
END;
/
```

다음은 실행 결과입니다.

```
UNION ALL          COUNT=6 [1 2 3 1 4 5 ]
UNION              COUNT=6 [1 2 3 1 4 5 ]
UNION DISTINCT     COUNT=5 [1 2 3 4 5 ]
INTERSECT          COUNT=3 [1 1 2 ]
INTERSECT DISTINCT COUNT=2 [1 2 ]
EXCEPT             COUNT=2 [3 3 ]
EXCEPT DISTINCT    COUNT=1 [3 ]
```

### **집합 조건과 함수**

네스티드 테이블 형태의 변수에는 집합 연산을 위한 조건과 함수를 사용할 수 있습니다. 종류는 다음과 같습니다.

<table><thead><tr><th width="230">이름</th><th>설명</th></tr></thead><tbody><tr><td>CARDINALITY(t)</td><td>t의 구성요소 개수를 반환하는 함수. 초기화되지 않은 네스티드 테이블에는 NULL을 반환</td></tr><tr><td>SET(t)</td><td>t에서 중복값을 제거한 네스티드 테이블을 반환하는 함수</td></tr><tr><td>expr MEMBER OF t</td><td>expr이 t의 구성요소인지 판정하는 조건</td></tr><tr><td>t1 SUBMULTISET OF t2</td><td>t1의 구성요소가 모두 t2에 있는지 판정하는 조건</td></tr><tr><td>t IS A SET</td><td>t에 중복된 구성요소가 없는지 판정하는 조건</td></tr><tr><td>t IS EMPTY</td><td>t에 구성요소가 없는지 판정하는 조건</td></tr></tbody></table>

조건에는 NOT을 붙여 `NOT MEMBER OF`, `NOT SUBMULTISET OF`, `IS NOT A SET`, `IS NOT EMPTY`로 사용할 수 있습니다.

이 조건과 함수는 네스티드 테이블 타입에 사용합니다. 인덱스 테이블이나 배열에 사용하면 컴파일 단계에서 TBR-15038이 발생합니다.

다음은 집합 조건과 함수의 예입니다.

```
set serveroutput on
DECLARE
    TYPE kind_tab IS TABLE OF NUMBER;
    tab1 kind_tab := kind_tab(1, 2, 2, 3, 3, 3);
    tab2 kind_tab := kind_tab(1, 2);
    uniq kind_tab;
BEGIN
    DBMS_OUTPUT.PUT_LINE('CARDINALITY(tab1) = ' || CARDINALITY(tab1));
    uniq := SET(tab1);
    DBMS_OUTPUT.PUT_LINE('CARDINALITY(SET(tab1)) = ' || CARDINALITY(uniq));
    IF 2 MEMBER OF tab1 THEN
        DBMS_OUTPUT.PUT_LINE('2 MEMBER OF tab1');
    END IF;
    IF 9 NOT MEMBER OF tab1 THEN
        DBMS_OUTPUT.PUT_LINE('9 NOT MEMBER OF tab1');
    END IF;
    IF tab2 SUBMULTISET OF tab1 THEN
        DBMS_OUTPUT.PUT_LINE('tab2 SUBMULTISET OF tab1');
    END IF;
    IF tab1 IS NOT A SET THEN
        DBMS_OUTPUT.PUT_LINE('tab1 IS NOT A SET');
    END IF;
    IF uniq IS A SET THEN
        DBMS_OUTPUT.PUT_LINE('uniq IS A SET');
    END IF;
    IF kind_tab() IS EMPTY THEN
        DBMS_OUTPUT.PUT_LINE('kind_tab() IS EMPTY');
    END IF;
END;
/
```

다음은 실행 결과입니다.

```
CARDINALITY(tab1) = 6
CARDINALITY(SET(tab1)) = 3
2 MEMBER OF tab1
9 NOT MEMBER OF tab1
tab2 SUBMULTISET OF tab1
tab1 IS NOT A SET
uniq IS A SET
kind_tab() IS EMPTY
```

## **인덱스 테이블**

인덱스 테이블은 키와 값이 합쳐진 구성요소를 갖는 테이블입니다. 각각의 구성요소에 접근할 때에는 숫자 또는 문자열을 이용하여 인덱스로 접근합니다. 인덱스로 사용할 수 있는 타입은 PLS\_INTEGER, BINARY\_INTEGER, VARCHAR2, STRING, LONG입니다.

tbPSM에서 인덱스 테이블을 선언하는 방법은 다음과 같습니다.

```
TYPE name IS TABLE OF type [NOT NULL] INDEX BY index_type;
```

다음은 인덱스 테이블 선언의 예입니다.

```
TYPE index_table1 IS TABLE OF PLS_INTEGER NOT NULL INDEX BY PLS_INTEGER;
TYPE index_table2 IS TABLE OF VARCHAR2(10) INDEX BY STRING(1);
```

VARCHAR2와 STRING을 인덱스 타입으로 쓸 때는 길이를 함께 지정합니다(길이를 생략하면 TBR-15013이 발생합니다). 위 목록에 없는 NUMBER나 DATE를 지정하면 TBR-15071이 발생합니다.

### **인덱스 테이블의 사용**

인덱스 테이블은 선언할 때 최대 크기의 제한이 없습니다. 또한 네스티드 테이블이나 배열과 달리 초기화 과정을 거치지 않고 키에 값을 대입하는 것만으로 구성요소가 만들어집니다. 인덱스 테이블의 크기는 인덱스가 동일하게 설정된 키에 값을 삽입하는 경우 이전 값이 변경되므로 증가하지 않습니다. 반면에 인덱스가 다르게 설정된 키에 값을 삽입하는 경우 인덱스 테이블의 크기는 증가하게 됩니다.

인덱스 타입이 정수 계열일 때는 0과 음수도 키로 사용할 수 있습니다. 이 점이 1 이상의 인덱스만 사용하는 네스티드 테이블, 배열과 다릅니다.

```
set serveroutput on
DECLARE
    TYPE kind_tab IS TABLE OF NUMBER INDEX BY VARCHAR2(10);
    fruit_table kind_tab;  -- 과일 개수 테이블, 인덱스는 과일명
BEGIN
    fruit_table('apple'):= 10;
    fruit_table('orange'):= 100;
    fruit_table('strawberry'):= 35;
    fruit_table('pineapple'):= 80;
    fruit_table('watermelon'):= 20;
    DBMS_OUTPUT.PUT_LINE('COUNT = ' || fruit_table.COUNT);
END;
/
```

다음은 실행 결과입니다.

```
COUNT = 5
```

### **인덱스 테이블의 순회**

인덱스 테이블의 키는 연속적이지 않으므로 FOR 문의 범위로 순회하기 어렵습니다. 이때는 FIRST 함수로 첫 키를 얻고 NEXT 함수로 다음 키를 차례로 얻어 전체 구성요소를 읽습니다. 키의 순서는 키값의 바이너리 값을 비교하여 정해집니다.

다음은 인덱스 테이블을 순회하는 예입니다.

```
set serveroutput on
DECLARE
    TYPE kind_tab IS TABLE OF NUMBER INDEX BY VARCHAR2(10);
    fruit_table kind_tab;
    key         VARCHAR2(10);
BEGIN
    fruit_table('apple') := 10;
    fruit_table('orange') := 100;
    fruit_table('strawberry') := 35;
    fruit_table('pineapple') := 80;
    fruit_table('watermelon') := 20;
    DBMS_OUTPUT.PUT_LINE('COUNT = ' || fruit_table.COUNT);
    DBMS_OUTPUT.PUT_LINE('FIRST = ' || fruit_table.FIRST ||
                         ', LAST = ' || fruit_table.LAST);
    key := fruit_table.FIRST;
    WHILE key IS NOT NULL LOOP
        DBMS_OUTPUT.PUT_LINE('  ' || key || ' = ' || fruit_table(key));
        key := fruit_table.NEXT(key);
    END LOOP;
    fruit_table.DELETE('orange');
    DBMS_OUTPUT.PUT_LINE('after DELETE : COUNT = ' || fruit_table.COUNT);
END;
/
```

다음은 실행 결과입니다.

```
COUNT = 5
FIRST = apple, LAST = watermelon
  apple = 10
  orange = 100
  pineapple = 80
  strawberry = 35
  watermelon = 20
after DELETE : COUNT = 4
```

## **배열**

배열은 네스티드 테이블과 달리 선언할 때 길이의 제한이 있습니다. 따라서 선언할 때 반드시 길이를 지정해야 합니다. 그리고 각각의 구성요소는 항상 연속적으로 존재하며, 구성요소에 접근할 때에는 인덱스로 합니다.

배열은 모든 구성요소가 서로 동일한 타입이어야 합니다. REF CURSOR를 제외한 모든 타입이 될 수 있습니다. 또한 배열을 구성요소 타입으로 갖는 배열을 선언할 수 있습니다.

tbPSM에서 배열을 선언하는 방법은 다음과 같습니다.

```
TYPE name IS VARRAY(limit) OF type [NOT NULL];
```

### **배열의 초기화**

배열은 초기화라는 과정을 거치게 되는데, 이 과정을 거치지 않은 변수는 항상 NULL을 가지며, 구성요소도 존재하지 않게 됩니다. 만약 초기화되지 않은 배열에 접근할 경우 **COLLECTION\_IS\_NULL** 예외 상황이 발생합니다.

배열을 초기화하는 방법은 다음과 같습니다.

```
set serveroutput on
DECLARE
    TYPE class_arr IS VARRAY(10) OF VARCHAR2(10) NOT NULL;
    classes class_arr;
BEGIN
    IF classes IS NULL THEN
        DBMS_OUTPUT.PUT_LINE('before : classes IS NULL');
    END IF;
    classes := class_arr('math', 'physics', 'history', 'science');
    DBMS_OUTPUT.PUT_LINE('after  : COUNT = ' || classes.COUNT ||
                         ', LIMIT = ' || classes.LIMIT);
END;
/
```

다음은 실행 결과입니다.

```
before : classes IS NULL
after  : COUNT = 4, LIMIT = 10
```

### **배열의 구성요소**

배열은 선언할 때 지정한 길이가 구성요소의 최대 길이가 되므로, 초기화되지 않은 나머지 구성요소는 존재하지 않는 값이 됩니다. 만약 초기화되지 않은 구성요소에 접근할 경우에는 **SUBSCRIPT\_BEYOND\_COUNT** 예외 상황이 발생하고(TBR-15110), 선언한 길이를 넘는 인덱스로 접근할 경우에는 **SUBSCRIPT\_OUTSIDE\_LIMIT** 예외 상황이 발생합니다(TBR-15111). 만약 배열에서 NOT NULL 제약조건이 없을 경우에는 NULL을 구성요소로 사용할 수 있습니다.

```
set serveroutput on
DECLARE
    TYPE class_arr IS VARRAY(5) OF VARCHAR2(10);
    classes class_arr := class_arr('math', NULL, 'science');
BEGIN
    DBMS_OUTPUT.PUT_LINE('COUNT = ' || classes.COUNT);
    IF classes(2) IS NULL THEN
        DBMS_OUTPUT.PUT_LINE('classes(2) IS NULL');
    END IF;
    DBMS_OUTPUT.PUT_LINE(classes(4));
EXCEPTION
    WHEN SUBSCRIPT_BEYOND_COUNT THEN
        DBMS_OUTPUT.PUT_LINE('classes(4) : SQLCODE = ' || SQLCODE);
END;
/
```

다음은 실행 결과입니다.

```
COUNT = 3
classes(2) IS NULL
classes(4) : SQLCODE = -15110
```

배열의 구성요소는 하나의 독립적인 변수처럼 사용할 수 있으며, 해당 변수의 값을 추출할 수 있습니다. 또한 대입도 가능합니다.

예를 들면 다음과 같습니다.

```
set serveroutput on
DECLARE
    TYPE class_arr IS VARRAY(10) OF VARCHAR2(10);
    classes class_arr := class_arr('math', 'physics', 'art');
BEGIN
    classes(3) := 'history';
    IF classes(1) IS NULL THEN
        DBMS_OUTPUT.PUT_LINE('I hate this class');
    END IF;
    DBMS_OUTPUT.PUT_LINE(classes(3) || ' is my favorite class');
END;
/
```

다음은 실행 결과입니다.

```
history is my favorite class
```

배열의 구성요소를 참조하기 위해서는 다음과 같이 사용해야 합니다.

```
varray_name(index)
```

index에는 1 이상 2^31 - 1 이하의 정수를 사용합니다.

## **컬렉션의 대입과 비교**

같은 타입으로 선언한 두 컬렉션 변수는 서로 대입할 수 있습니다. 이때 구성요소가 복사되므로 대입받은 변수를 수정해도 원래 변수는 영향을 받지 않습니다. 구성요소 타입이 같아도 서로 다른 타입으로 선언한 변수끼리 대입하면 컴파일 단계에서 TBR-15038이 발생합니다.

두 컬렉션을 견줄 때는 IS NULL과 등호(=), 부등호(`<>`, `!=`)를 사용합니다. 아래 표는 네스티드 테이블 타입과 배열 타입에 적용됩니다. 인덱스 테이블은 대입만 사용하며, 등호나 부등호로 견주면 컴파일 단계에서 TBR-15038이 발생하고 IS NULL은 구성요소가 없을 때도 FALSE를 반환합니다.

비교 결과는 다음과 같습니다.

<table><thead><tr><th width="330">비교</th><th>결과</th></tr></thead><tbody><tr><td>초기화하지 않은 컬렉션 <code>IS NULL</code></td><td>TRUE</td></tr><tr><td>구성요소가 없는 생성자로 초기화한 컬렉션 <code>IS NULL</code></td><td>FALSE</td></tr><tr><td>구성요소의 나열 순서만 다른 두 컬렉션 <code>=</code></td><td>TRUE</td></tr><tr><td>초기화한 컬렉션과 초기화하지 않은 컬렉션 <code>=</code></td><td>NULL</td></tr></tbody></table>

크기 순서로 견주어야 할 때는 COUNT 함수나 CARDINALITY 함수로 구성요소 개수를 비교합니다. 구성요소를 하나씩 견주어야 할 때는 두 컬렉션을 파라미터로 전달받아 인덱스 순서대로 비교하는 함수를 작성합니다.

다음은 컬렉션을 대입하고 비교하는 예입니다.

```
set serveroutput on
DECLARE
    TYPE kind_tab IS TABLE OF NUMBER;
    tab1 kind_tab := kind_tab(1, 2, 3);
    tab2 kind_tab := kind_tab(3, 2, 1);
    copied kind_tab;
    empty_tab kind_tab := kind_tab();
    null_tab kind_tab;
    FUNCTION show(cond BOOLEAN) RETURN VARCHAR2 IS
    BEGIN
        IF cond THEN
            RETURN 'TRUE';
        ELSIF NOT cond THEN
            RETURN 'FALSE';
        ELSE
            RETURN 'NULL';
        END IF;
    END;
BEGIN
    copied := tab1;
    copied(1) := 99;
    DBMS_OUTPUT.PUT_LINE('tab1(1) = ' || tab1(1) || ', copied(1) = ' || copied(1));
    DBMS_OUTPUT.PUT_LINE('null_tab IS NULL      : ' || show(null_tab IS NULL));
    DBMS_OUTPUT.PUT_LINE('empty_tab IS NULL     : ' || show(empty_tab IS NULL));
    DBMS_OUTPUT.PUT_LINE('tab1 = tab2           : ' || show(tab1 = tab2));
    DBMS_OUTPUT.PUT_LINE('tab1 = null_tab       : ' || show(tab1 = null_tab));
END;
/
```

다음은 실행 결과입니다.

```
tab1(1) = 1, copied(1) = 99
null_tab IS NULL      : TRUE
empty_tab IS NULL     : FALSE
tab1 = tab2           : TRUE
tab1 = null_tab       : NULL
```

## **SQL 문에서 컬렉션 사용**

CREATE TYPE 문으로 만든 네스티드 테이블 타입은 SQL 문에서 TABLE 연산자로 펼쳐 사용할 수 있습니다. 펼쳐진 각 구성요소는 COLUMN\_VALUE라는 이름의 컬럼으로 참조합니다. TABLE 연산자에는 CREATE TYPE 문으로 만든 스키마 레벨 타입을 사용합니다. 선언부에서 만든 타입을 지정하면 TBR-3006이 발생합니다.

다음은 TABLE 연산자를 사용하는 예입니다.

```
set serveroutput on
CREATE TYPE name_tab AS TABLE OF VARCHAR2(20);
/

CREATE TABLE emp_comp (empno NUMBER, ename VARCHAR2(20));
INSERT INTO emp_comp VALUES (10, 'kim');
INSERT INTO emp_comp VALUES (20, 'lee');
INSERT INTO emp_comp VALUES (30, 'park');
COMMIT;

DECLARE
    names name_tab := name_tab('kim', 'park');
    cnt   NUMBER;
BEGIN
    SELECT COUNT(*) INTO cnt FROM TABLE(names);
    DBMS_OUTPUT.PUT_LINE('TABLE(names) count = ' || cnt);
    FOR rec IN (SELECT COLUMN_VALUE nm FROM TABLE(names) ORDER BY 1) LOOP
        DBMS_OUTPUT.PUT_LINE('  ' || rec.nm);
    END LOOP;
    SELECT COUNT(*) INTO cnt
      FROM emp_comp e, TABLE(names) n
     WHERE e.ename = n.COLUMN_VALUE;
    DBMS_OUTPUT.PUT_LINE('joined count = ' || cnt);
END;
/
```

다음은 실행 결과입니다.

```
TABLE(names) count = 2
  kim
  park
joined count = 2
```

반대 방향으로는 SELECT 문의 결과를 BULK COLLECT INTO 절로 네스티드 테이블 변수에 한 번에 담을 수 있습니다. 자세한 내용은 [BULK SQL](/tibero-manuals/7.2.6.manuals/tbpsm-guide/bulk-sql.md)을 참고합니다.

```
set serveroutput on
DECLARE
    TYPE ename_tab IS TABLE OF emp_comp.ename%TYPE;
    names ename_tab;
BEGIN
    SELECT ename BULK COLLECT INTO names FROM emp_comp ORDER BY empno;
    DBMS_OUTPUT.PUT_LINE('COUNT = ' || names.COUNT);
    DBMS_OUTPUT.PUT_LINE('FIRST = ' || names(names.FIRST) ||
                         ', LAST = ' || names(names.LAST));
END;
/

```

다음은 실행 결과입니다.

```
COUNT = 3
FIRST = kim, LAST = park
```

## **예외 상황**

컬렉션 타입과 관련된 예외 상황을 정리하면 다음과 같습니다.

<table><thead><tr><th width="260">예외 상황</th><th width="130">오류 코드</th><th>발생 조건</th></tr></thead><tbody><tr><td>COLLECTION_IS_NULL</td><td>TBR-15100</td><td>초기화되지 않은 컬렉션 변수에 접근하거나, EXISTS를 제외한 컬렉션 함수와 프러시저를 호출하는 경우</td></tr><tr><td>NO_DATA_FOUND</td><td>TBR-15104</td><td><ul><li>DELETE 프러시저에 의해 제거된 구성요소에 접근하는 경우</li><li>인덱스 테이블에 존재하지 않는 키로 접근하는 경우</li></ul></td></tr><tr><td>SUBSCRIPT_BEYOND_COUNT</td><td>TBR-15110</td><td><ul><li>인덱스가 구성요소의 개수를 초과한 값으로 주어진 경우</li><li>TRIM(n)의 n이 구성요소의 개수보다 큰 경우</li></ul></td></tr><tr><td>SUBSCRIPT_OUTSIDE_LIMIT</td><td>TBR-15111</td><td><ul><li>인덱스가 1보다 작은 경우</li><li>배열에서 인덱스나 EXTEND 결과가 선언한 크기를 넘는 경우</li></ul></td></tr><tr><td>VALUE_ERROR</td><td>TBR-15115</td><td><ul><li>인덱스가 NULL이거나 숫자의 형태로 변환이 안 되는 경우</li><li>NOT NULL로 선언한 구성요소에 NULL을 대입하는 경우</li></ul></td></tr></tbody></table>

NO\_DATA\_FOUND의 SQLCODE는 100이고, 나머지 예외 상황의 SQLCODE는 오류 코드에 음수 부호를 붙인 값입니다.

다음은 컬렉션 관련 예외 상황을 확인하는 예입니다.

```
set serveroutput on
DECLARE
    TYPE kind_tab IS TABLE OF NUMBER;
    TYPE kind_arr IS VARRAY(3) OF NUMBER;
    null_tab kind_tab;
    tab1     kind_tab := kind_tab(1, 2, 3);
    arr1     kind_arr := kind_arr(1, 2, 3);
    dummy    NUMBER;
    idx      PLS_INTEGER;
BEGIN
    BEGIN
        dummy := null_tab.COUNT;
    EXCEPTION
        WHEN COLLECTION_IS_NULL THEN
            DBMS_OUTPUT.PUT_LINE('COLLECTION_IS_NULL      : ' || SQLCODE);
    END;
    BEGIN
        tab1.DELETE(2);
        dummy := tab1(2);
    EXCEPTION
        WHEN NO_DATA_FOUND THEN
            DBMS_OUTPUT.PUT_LINE('NO_DATA_FOUND           : ' || SQLCODE);
    END;
    BEGIN
        dummy := tab1(10);
    EXCEPTION
        WHEN SUBSCRIPT_BEYOND_COUNT THEN
            DBMS_OUTPUT.PUT_LINE('SUBSCRIPT_BEYOND_COUNT  : ' || SQLCODE);
    END;
    BEGIN
        dummy := arr1(4);
    EXCEPTION
        WHEN SUBSCRIPT_OUTSIDE_LIMIT THEN
            DBMS_OUTPUT.PUT_LINE('SUBSCRIPT_OUTSIDE_LIMIT : ' || SQLCODE);
    END;
    BEGIN
        dummy := tab1(idx);
    EXCEPTION
        WHEN VALUE_ERROR THEN
            DBMS_OUTPUT.PUT_LINE('VALUE_ERROR             : ' || SQLCODE);
    END;
END;
/
```

다음은 실행 결과입니다.

```
COLLECTION_IS_NULL      : -15100
NO_DATA_FOUND           : 100
SUBSCRIPT_BEYOND_COUNT  : -15110
SUBSCRIPT_OUTSIDE_LIMIT : -15111
VALUE_ERROR             : -15115
```
