> 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/object-type.md).

# 오브젝트 타입

추상 데이터 타입인 오브젝트 타입에 대해서 설명합니다.

## **개요**

**오브젝트 타입(Object Type)**&#xC740; 데이터 타입의 데이터 구성요소와 서브 프로그램을 캡슐화(Encapsulation)한 추상 데이터 타입(Abstract Data Type)입니다.

데이터 구성요소는 Attribute라고 부르며, 서브 프로그램은 메소드(Method)라고 부릅니다. 사용자는 추상 데이터 타입을 이용하여 애플리케이션에 관련된 데이터와 서브 프로그램을 한 곳으로 모을 수 있어 애플리케이션 작성이 쉬워지고 가독성이 높아집니다.

tbPSM의 오브젝트 타입이 클래스나 패키지와 다른 점은 다음과 같습니다.

* 클래스와 마찬가지로 상속과 다형성을 지원합니다. NOT FINAL로 선언한 타입을 UNDER 절로 상속하고, OVERRIDING 절로 상위 타입의 메소드를 재정의합니다. 자세한 내용은 "[상속과 다형성](#undefined-1)"을 참고합니다.
* 패키지와 다른 점은 타입 실체화입니다.
  * 오브젝트 타입의 변수는 [컬렉션 타입](/tibero-manuals/7.2.6.manuals/tbpsm-guide/composite-types.md) 변수처럼 각각 다른 메모리 공간을 사용합니다.
  * 패키지 자체가 세션마다 유일한 데이터셋을 가지는 반면 오브젝트 타입 자체로는 데이터셋을 가지지 않으며 오브젝트 타입 실체화를 통해 각각의 인스턴스마다 데이터셋을 가집니다.
  * 오브젝트 타입의 실체화 방법은 컬렉션 타입과 동일하게 생성자를 호출하는 것이며, 생성자가 삭제되는 것은 변수의 생명 주기와 같습니다.
  * 변수의 Block Scope가 끝났을 경우 오브젝트 인스턴스에 대한 메모리가 해제됩니다. 단, 패키지 멤버 변수인 경우에는 패키지 인스턴스가 무효화되는 경우에 삭제됩니다.

{% hint style="info" %}
**참고**

오브젝트 타입은 tbPSM 블록 안에서만 쓰는 것이 아니라 테이블의 컬럼 타입과 오브젝트 테이블의 타입으로도 사용할 수 있고, SQL 문장에서 생성자와 메소드를 호출할 수 있습니다. 자세한 내용은 "[SQL과 테이블에서의 사용](#sql)"을 참고합니다.
{% endhint %}

다음은 오브젝트 타입을 이용한 Number Stack의 구현 예로 Stack의 데이터 배열과 Top 포인터, 그리고 푸시(Push), 팝(Pop), 탑(Top) 동작을 한 곳으로 모은 것을 확인할 수 있습니다.

```

-- Stack 내부에서 사용하는 데이터를 위한 최대 크기의 한계가 없는 배열 데이터 타입
CREATE OR REPLACE TYPE number_array AS TABLE OF NUMBER;
/
show err


-- number stack의 추상 데이터 타입
CREATE OR REPLACE TYPE type_nstack AS OBJECT(
    maxlen number,     -- Stack의 최대 크기 
    topptr number,     -- Stack의 TOP 
    array number_array, -- Stack 내부 데이터

    MEMBER PROCEDURE pop (self in out nocopy type_nstack),
    MEMBER FUNCTION top (self in out nocopy type_nstack) return NUMBER, 
    MEMBER PROCEDURE push (self in out nocopy type_nstack, input NUMBER),
    CONSTRUCTOR FUNCTION type_nstack(maxlen number) RETURN SELF AS RESULT
);
/
show err

CREATE OR REPLACE TYPE BODY type_nstack 
AS
    MEMBER PROCEDURE pop (self in out nocopy type_nstack) 
    AS
    BEGIN
        IF topptr <= 0 THEN
            raise_application_error(-20000, 'stack is empty', true); 
        ELSE
            array(topptr) := NULL; 
            topptr := topptr - 1;
        END IF; 
    END;

    MEMBER FUNCTION top (self in out nocopy type_nstack) return NUMBER 
    AS
    BEGIN
        IF topptr <= 0 THEN 
            return NULL;
        ELSE
            return array(topptr); 
        END IF;
        return NULL; 
    END;

    MEMBER PROCEDURE push (self in out nocopy type_nstack, input NUMBER) 
    AS
    BEGIN
        IF topptr = maxlen THEN
            raise_application_error(-20001, 'stack is full', true); 
        ELSE
            array(self.topptr + 1) := input; 
            topptr := topptr + 1;
        END IF; 
    END;

    CONSTRUCTOR FUNCTION type_nstack(maxlen number) RETURN SELF AS RESULT 
    AS
    BEGIN
        array := number_array(); 
        array.extend(maxlen); 
        topptr := 0;
        RETURN; 
    END;
END;
/
show err

set serveroutput on
-- 간단한 Stack Push/Pop 동작 테스트 애플리케이션
declare
    s1 type_nstack; 
begin

    s1 := type_nstack(4); -- 최대 크기 4짜리 Stack의 생성
    s1.push(10); -- 10을 Push 
    s1.push(20); -- 20을 Push 
    s1.push(30); -- 30을 Push 
    s1.push(40); -- 40을 Push

    dbms_output.put_line (s1.top);    -- Stack의 Top을 조회
    s1.pop;   -- stack pop

    dbms_output.put_line (s1.top);   -- Stack의 Top을 조회
    dbms_output.put_line (s1.top);   -- Stack의 Top을 조회

    s1.pop;   -- stack pop
    dbms_output.put_line (s1.top);   -- Stack의 Top을 조회

    s1.pop;	-- stack pop 
    s1.pop;	-- stack pop
    s1.pop;	-- stack pop : Stack이 비어 있으므로 에러
end;
/
```

다음은 실행 결과입니다. 마지막 pop은 비어 있는 Stack에서 호출하므로 타입에 정의한 예외가 발생합니다.

```
40
30
30
20
TBR-20000: stack is empty.
TBR-15163: Unhandled exception at SYS.TYPE_NSTACK,  line 7.
TBR-15163: Unhandled exception at  line 22.
```

## 상속과 다형성

오브젝트 타입은 상속과 다형성을 지원합니다. 상속을 허용하려면 상위 타입을 **NOT FINAL**로 선언하고, 하위 타입은 **UNDER** 절로 상위 타입을 지정합니다. 하위 타입은 상위 타입의 Attribute와 메소드를 물려받으며, **OVERRIDING** 절로 상위 타입의 메소드를 재정의할 수 있습니다.

상위 타입 변수에는 하위 타입의 인스턴스를 대입할 수 있습니다. 이때 호출되는 메소드는 변수의 선언 타입이 아니라 인스턴스의 실제 타입에 따라 결정되며, tbPSM 블록과 SQL 문장에서 모두 같은 방식으로 동작합니다.

<table><thead><tr><th width="150">구성요소</th><th>설명</th></tr></thead><tbody><tr><td>NOT FINAL</td><td><ul><li>다른 타입이 이 타입을 상속할 수 있음</li><li>생략하면 상속할 수 없는 타입이 됨</li></ul></td></tr><tr><td>UNDER</td><td>상속할 상위 타입을 지정</td></tr><tr><td>OVERRIDING</td><td>상위 타입의 같은 이름 메소드를 재정의</td></tr><tr><td>IS OF</td><td><ul><li>인스턴스가 지정한 타입인지 여부를 반환</li><li>tbPSM 표현식에서 사용</li></ul></td></tr><tr><td>TREAT</td><td><ul><li>상위 타입 값을 하위 타입으로 변환</li><li>변환 대상이 그 하위 타입인지 IS OF 로 먼저 확인(아니면 TBR-15226)</li></ul></td></tr></tbody></table>

다음은 사원 타입을 상속해 관리자 타입을 정의하고, 재정의한 메소드가 호출되는 것을 확인하는 예입니다.

```

CREATE OR REPLACE TYPE emp_base AS OBJECT (
    empno   NUMBER,
    ename   VARCHAR2(20),
    MEMBER FUNCTION title RETURN VARCHAR2
) NOT FINAL;
/
CREATE OR REPLACE TYPE BODY emp_base AS
    MEMBER FUNCTION title RETURN VARCHAR2 IS
    BEGIN
        RETURN ename || ' (사원)';
    END;
END;
/
CREATE OR REPLACE TYPE emp_mgr UNDER emp_base (
    dept    VARCHAR2(20),
    OVERRIDING MEMBER FUNCTION title RETURN VARCHAR2
);
/
CREATE OR REPLACE TYPE BODY emp_mgr AS
    OVERRIDING MEMBER FUNCTION title RETURN VARCHAR2 IS
    BEGIN
        RETURN ename || ' (' || dept || ' 관리자)';
    END;
END;
/
set serveroutput on
DECLARE
    e emp_base;
    m emp_mgr;
BEGIN
    e := emp_base(1, 'KIM');
    DBMS_OUTPUT.PUT_LINE(e.title());
    e := emp_mgr(2, 'LEE', '영업');      -- 상위 타입 변수에 하위 타입 인스턴스 대입
    DBMS_OUTPUT.PUT_LINE(e.title());     -- 재정의한 메소드가 호출됨
    IF e IS OF (emp_mgr) THEN
        m := TREAT(e AS emp_mgr);
        DBMS_OUTPUT.PUT_LINE('dept = ' || m.dept);
    END IF;
END;
/
col title format a24
SELECT emp_mgr(3, 'PARK', '인사').title() AS title FROM dual;
```

다음은 실행 결과입니다. 상위 타입 변수로 호출해도 하위 타입에서 재정의한 메소드가 실행됩니다.

```
KIM (사원)
LEE (영업 관리자)
dept = 영업

TITLE
------------------------
PARK (인사 관리자)
```

{% hint style="info" %}
**참고**

tbPSM 블록에서는 TREAT 함수의 결과를 하위 타입 변수에 대입한 다음 Attribute에 접근합니다. 블록 안에서 함수 결과에 점 표기를 바로 붙이면 파싱 단계에서 TBR-15002로 처리됩니다. SQL 문장에서는 `TREAT(컬럼 AS 하위타입).Attribute` 형태로 Attribute를 바로 읽을 수 있습니다.
{% endhint %}

## **생성자**

생성자는 디폴트 생성자와 사용자 정의 생성자가 있습니다.

### **디폴트 생성자**

사용자 정의 생성자가 없는 경우 디폴트 생성자가 호출되며, 디폴트 생성자의 함수 시그니처는 Attribute의 순서와 동일합니다. 호출자가 argument로 다른 타입을 가지고 생성자를 호출하는 경우 tbPSM implicit type conversion rule을 따릅니다. 오브젝트 인스턴스가 NULL인 상태에서 Attribute를 읽으면 NULL이 반환되고, Attribute에 값을 대입하려면 먼저 생성자로 인스턴스를 만듭니다(초기화하지 않은 인스턴스에 대입하면 TBR-15172).

다음은 디폴트 생성자를 호출하는 예입니다.

```
create or replace type default_constructor as object 
    ( a1 number,
    a2 varchar2(1024)
);
/

declare
    var default_constructor; 
begin
    var := default_constructor(10, 'default');

    dbms_output.put_line (var.a1); 
    dbms_output.put_line (var.a2);
end;
/
```

### 사용자 정의 생성자

사용자 정의 생성자가 디폴트 생성자와 시그니처가 다르면 함수 오버로딩 규칙에 따라 타입 변환이 가장 적고 정보 손실이 가장 적은 쪽을 우선적으로 선택합니다. 만약 선택하지 못하는 경우 컴파일 에러가 발생합니다. 사용자 정의 생성자를 생성할 때 오브젝트 변수는 기본으로 NULL 값을 가집니다. 사용자 정의 생성자에 대한 자세한 설명은 “[메소드](#undefined-5)”의 “[생성자 메소드](#undefined-6)”를 참고합니다.

다음은 사용자 정의 생성자를 호출하는 예입니다.

```
create or replace type constructor_body_test as object
    ( a1 number,
    a2 number,
    constructor function constructor_body_test (param1 number)
    return self as result
);
/

create or replace type body constructor_body_test
as
    constructor function constructor_body_test (param1 number)
    return self as result
    as
    begin
        a1 := param1 + 1000;
        a2 := param1;
        return;
    end;
end;
/

declare
    var1 constructor_body_test;
    var2 constructor_body_test;
begin
    var1 := constructor_body_test (5);       -- 사용자 정의 생성자(인자 1개)
    var2 := constructor_body_test (5, 60);   -- 디폴트 생성자(Attribute 순서)
    dbms_output.put_line( var1.a1 || ', ' || var1.a2 );
    dbms_output.put_line( var2.a1 || ', ' || var2.a2 );
end;
/
```

다음은 실행 결과입니다.

```
1005, 5
5, 60
```

사용자 정의 생성자의 파라미터 개수와 타입은 디폴트 생성자와 구분되게 정의합니다. 시그니처가 겹치면 호출 시점에 어느 생성자인지 결정하지 못해 TBR-15041이 발생합니다.

## **Attribute**

**Attribute**는 오브젝트 타입의 구성요소인 데이터들입니다.

* 원시 데이터 타입(Primitive Data Type)
* 오브젝트 타입(Object Type)
* 컬렉션 타입(Collection Type) 중 인덱스 테이블 타입(Index By Table Type)을 제외한 배열 타입(Varray Type)
* 네스티드 테이블 타입(Nested Table Type)

정수 Attribute는 NUMBER로 선언합니다. PLS\_INTEGER와 BINARY\_INTEGER를 Attribute 타입으로 쓰면 TBR-15204가 발생합니다.

레코드 타입(Record Type)은 Attribute 타입으로 사용될 수 없습니다. 레코드 타입과 인덱스 테이블 타입은 tbPSM 로컬 타입(tbPSM BLOCK 안에서 선언 가능)이기 때문입니다. 즉, 원시 데이터 타입과 CREATE TYPE DDL 문으로 선언하는 타입은 Attribute 타입으로 사용 가능합니다. Attribute의 이름은 타입 안에서 유일해야 합니다(중복하면 TBR-15225). 메소드와 이름이 같은 Attribute도 선언할 수 있으며, 이때 점 표기는 Attribute를 가리킵니다.

다음은 오브젝트 타입을 다른 오브젝트 타입의 Attribute로 사용한 예입니다.

```
-- 국가 타입
create type o_nation as object 
    ( name	varchar2(16), 
    continent varchar2(16), 
    climate	varchar2(16)
);
/
-- 주소 타입
create type o_address as object 
    ( nation o_nation,
    city varchar2(16), 
    street varchar2(16), 
    zipcode varchar2(16)
);
/

-- 사람 타입
create type o_person as object 
    ( name	        varchar2(16),
    height	        number,
    weight	        number,
    living_place	o_address,
    working_place	o_address,
    gender	        char(1), 
    ex_military_service char(1)
);
/

-- 이직 프러시저
create procedure change_working_place (p in out o_person, a in o_address) 
as
begin
    p.working_place := a; 
end;
/
-- 직장을 출력하는 프러시저
create procedure print_working_place (p in o_person) 
as
begin
    dbms_output.put_line('>' || p.working_place.nation.name); 
    dbms_output.put_line('>' || p.working_place.nation.continent); 
    dbms_output.put_line('>' || p.working_place.nation.climate);
    dbms_output.put_line('>' || p.working_place.city); 
    dbms_output.put_line('>' || p.working_place.street); 
    dbms_output.put_line('>' || p.working_place.zipcode);
end;
/

-- 그 나라의 수도를 기본 주소로 주소를 생성하는 함수
create function default_address (nation varchar2) return o_address 
as
begin
    if nation = 'Korea' then
        return o_address(o_nation(nation, null, null), 'Seoul', null, null); 
    elsif nation = 'USA' then
        return o_address(o_nation(nation, null, null), 'Washington', null, null); 
    elsif nation = 'France' then
        return o_address(o_nation(nation, null, null), 'Paris', null, null); 
    else
        return o_address(o_nation(nation, null, null), null, null, null); 
    end if;
end;
/
-- 미국 피닉스에서 태어난 "인피니데이터"의 직장위치를
-- 크로아티아의 수도에서 한국의 수도로 변경하는 애플리케이션
set serveroutput on

declare
    my_person o_person; 
begin
    my_person := o_person('Infinidata', '180', '72', 
        o_address(o_nation('USA', null, null), 'Phoenix', null, null),
        o_address(o_nation('Croatia', null, null), 'Zagreb', null, null),'M', 'Y'); 
    print_working_place (my_person);
    change_working_place (my_person, default_address('Korea')); 
    print_working_place (my_person);
end;
/
```

## **메소드**

메소드는 오브젝트 타입에 필요한 서브 프로그램입니다.

* 생성자 메소드
* 정적 메소드
* ORDER 메소드
* MAP 메소드
* 멤버 메소드

정적 메소드와 멤버 메소드는 함수나 프러시저로 선언할 수 있습니다. **ORDER 메소드와 MAP 메소드는 반드시 함수로 선언합니다.** 오브젝트 타입은 ORDER 메소드나 MAP 메소드 둘 중 하나만 가집니다(둘을 함께 선언하면 TBR-15203).

### 생성자 메소드

사용자 정의 생성자를 생성할 때 리턴 문에 리턴 값을 써주지 않더라도 암묵적으로 새 인스턴스를 반환합니다. 사용자 정의 생성자의 첫 번째 파라미터로 SELF라는 이름의 자기 자신의 타입을 가지는 파라미터를 명시 가능하지만 지정하지 않는 것과 동일합니다. 첫 번째 파라미터를 SELF로 지정하지 않더라도 암묵적으로 SELF가 있는 것과 같기 때문입니다(암묵적인 SELF는 IN/OUT NOCOPY 파라미터입니다).

### **정적 메소드**

정적 메소드는 오브젝트 실체화를 하지 않고도 호출이 가능한 메소드로 다음과 같은 형태로 호출합니다.

```
"오브젝트 타입 이름"."정적 메소드 이름" (아규먼트들)
```

정적 메소드는 인스턴스가 아니라 타입 이름으로 호출하므로 인스턴스 데이터를 참조하지 않습니다. 따라서 인스턴스 데이터와 무관한 동작에 사용합니다.

다음은 정적 메소드의 사용 예입니다.

```
-- 스머프 타입

create type smuff as object(
    name varchar(1024), -- 스머프 이름
    height number, -- 스머프 키
    static function eat(blueberry in number, sizeofmeal in number) 
    return number
);
/

create type body smuff 
as
    -- 스머프가 밥을 먹는다고 키가 크지 않는다.
    -- 이름이 바뀌지도 않는다.
    static function eat(blueberry in number, sizeofmeal in number) 
    return number
    as
    begin
        return blueberry - sizeofmeal; 
    end;
end;
/
show err

set serveroutput on 
declare
    fafa_smuff smuff; 
    blueberry_amount number := 10000;
begin
    -- 8cm 키의 파파 스머프 생성
    fafa_smuff := smuff('Fafa', 8);
    
    -- 키와 상관없이 모두 하루에 100개의 블루베리를 먹는다.
    -- 스머프 식사와 스머프 데이터와 관련이 없고,
    -- 블루베리의 양만 줄어들고 있으므로,
    -- static method 사용이 적절하다.
    blueberry_amount := smuff.eat(blueberry_amount, 100);
    
    dbms_output.put_line ('# of blueberry:' || blueberry_amount); 
end;
/
```

#### **ORDER 메소드**

ORDER 메소드는 두 개의 오브젝트 인스턴스를 비교하기 위해 사용되는 메소드입니다. 매개변수로 SELF를 제외하고 유일한 자기 자신의 타입을 가지는 파라미터 하나가 있어야 합니다. 또한 리턴 값은 숫자 타입 (Number, Bin\_Integer, Binary\_double, Binary\_float와 그에 따른 Sub Type)만을 가져야 합니다.

만약 이항 비교 연산자가 표현식에 존재하는 경우 **a "비교연산자" b**는 다음과 같이 변환되어 오브젝트 인스턴스의 비교가 가능하도록 합니다. SQL 문장의 비교 연산에도 그대로 사용할 수 있습니다. SQL 문장의 ORDER BY 절에 쓸 타입에는 MAP 메소드를 정의합니다. 연산별로 필요한 메소드는 "[SQL과 테이블에서의 사용](#sql)"의 표를 참고합니다.

```
a.order_method(b) "비교연산자" 0
```

MAP 메소드와 ORDER 메소드의 파라미터와 반환값 비교는 다음과 같습니다.

<table><thead><tr><th width="142">메소드</th><th width="372">파라미터</th><th>반환값</th></tr></thead><tbody><tr><td>MAP</td><td>SELF만 사용(그 외 파라미터 없음)</td><td>Primitive Data Type</td></tr><tr><td>ORDER</td><td>SELF를 제외하고 자기 자신 타입을 가지는 1개의 IN 파라미터 사용이 가능</td><td><p>숫자 타입</p><ul><li>Number</li><li>PLS_INTEGER</li><li>BINARY_INTEGER</li><li>Binary_double</li><li>Binary_float</li><li>그에 따른 Sub Type</li></ul></td></tr></tbody></table>

사용자는 두 개의 파라미터를 비교하여 다음과 같이 메소드를 작성해야 합니다.

* SELF가 더 큰 경우: 양수를 리턴합니다.
* 두 값이 같은 경우: 0을 리턴합니다.
* SELF가 더 작은 경우: 음수를 리턴합니다.

다음은 Stack 타입의 ORDER 메소드 작성을 이용하여 두 개의 Stack을 비교하는 예입니다.\
(Stack의 element 개수가 많은 것이 더 값이 크다고 가정)

```

CREATE OR REPLACE TYPE number_array AS TABLE OF NUMBER;
/
show err


CREATE OR REPLACE TYPE type_nstack AS OBJECT(
    maxlen number,     -- Stack의 최대 크기 
    topptr number,     -- Stack의 TOP 
    array number_array, -- Stack 내부 데이터

    MEMBER PROCEDURE pop (self in out nocopy type_nstack),
    MEMBER FUNCTION top (self in out nocopy type_nstack) return NUMBER, 
    MEMBER PROCEDURE push (self in out nocopy type_nstack, input NUMBER),

    CONSTRUCTOR FUNCTION type_nstack(maxlen number) RETURN SELF AS RESULT, 
    ORDER MEMBER FUNCTION compare(v in type_nstack) RETURN NUMBER
);
/
show err

CREATE OR REPLACE TYPE BODY type_nstack 
AS
    MEMBER PROCEDURE pop (self in out nocopy type_nstack) 
    AS
    BEGIN
        IF topptr <= 0 THEN
            raise_application_error(-20000, 'stack is empty', true); 
        ELSE
            array(topptr) := NULL; 
            topptr := topptr - 1;
        END IF; 
    END;

    MEMBER FUNCTION top (self in out nocopy type_nstack) return NUMBER 
    AS
    BEGIN
        IF topptr <= 0 THEN 
            return NULL;
        ELSE
            return array(topptr); 
        END IF;
        return NULL; 
    END;

    MEMBER PROCEDURE push (self in out nocopy type_nstack, input NUMBER) 
    AS
    BEGIN
        IF topptr = maxlen THEN
            raise_application_error(-20001, 'stack is full', true); 
        ELSE
            array(self.topptr + 1) := input; 
            topptr := topptr + 1;
        END IF; 
    END;

    CONSTRUCTOR FUNCTION type_nstack(maxlen number) RETURN SELF AS RESULT 
    AS
    BEGIN
        array := number_array(); 
        array.extend(maxlen); 
        topptr := 0;

        RETURN; 
    END;

    ORDER MEMBER FUNCTION compare(v in type_nstack) RETURN NUMBER 
    AS
    BEGIN
        IF		self.array.count = v.array.count THEN 
            RETURN 0;
        ELSIF	self.array.count < v.array.count THEN 
            RETURN -1;
        ELSE
            RETURN 1; 
        END IF;
    END;
END;
/

set serveroutput on

-- 두 개의 Stack을 비교하는 애플리케이션
declare
    s1 type_nstack; 
    s2 type_nstack;
begin
    s1 := type_nstack(8); 
    s2 := type_nstack(4); 
    if s2 < s1 then
        dbms_output.put_line('s2 is smaller than s1.'); 
    end if;

    s1 := type_nstack(4); 
    s2 := type_nstack(8); 
    if s2 > s1 then
        dbms_output.put_line('s2 is bigger than s1.'); 
    end if;

    s1 := type_nstack(4); 
    s2 := type_nstack(4); 
    if s2 = s1 then
        dbms_output.put_line('s2 is equal to s1.'); 
    end if;

end;
/
```

다음은 실행 결과입니다. Stack의 element 개수를 기준으로 두 인스턴스가 비교됩니다.

```
s2 is smaller than s1.
s2 is bigger than s1.
s2 is equal to s1.
```

### **MAP** 메소드

MAP 메소드는 인스턴스에서 primitive 타입의 scalar 값을 리턴하는 메소드입니다. 함수는 SELF 외에 아무런 파라미터가 있어서는 안 되며 primitive 타입을 리턴해야 합니다. 만약 이항 연산자가 있는 경우 **a < b**는 다음과 같이 변환되어 오브젝트 인스턴스의 비교가 가능하도록 합니다. MAP 메소드를 정의한 타입은 SQL 문장의 ORDER BY 절에도 사용할 수 있습니다.

```
a.map_method() < b.map_method()
```

다음은 MAP 메소드의 사용 예입니다.

```
CREATE OR REPLACE TYPE some_type AS OBJECT (
    key_value	number, 
    divider	number,

    MAP MEMBER FUNCTION get_hashval RETURN NUMBER
    );
/

CREATE OR REPLACE TYPE BODY some_type 
AS
    MAP MEMBER FUNCTION get_hashval RETURN NUMBER 
    AS
    BEGIN
        return mod (key_value, divider); 
    END;
END;
/



DECLARE
    var1 some_type; 
    var2 some_type;
BEGIN
    var1 := some_type (15, 10);
    var2 := some_type (16, 10); 
    dbms_output.put_line ( var1.get_hashval ); 
    dbms_output.put_line ( var2.get_hashval );

    IF var1 < var2 THEN
        -- var1 < var2에서 암묵적으로 MAP 메소드가 호출된다.
        -- 즉, var1.get_hashval() < var2.get_hashval()로 변환된다. 
        dbms_output.put_line ('var2 is bigger than var1.');
    ELSE
        dbms_output.put_line ('var1 is bigger than var2.'); 
    END IF;
END;
/
```

### 멤버 메소드

멤버 메소드는 다음과 같은 형태로 호출합니다.

```
"인스턴스 이름"."메소드 이름"
```

멤버 메소드는 첫 번째 파라미터가 암묵적인 SELF 파라미터로 자기 자신의 타입을 가지는 파라미터입니다. 멤버 프러시저는 SELF를 명시하지 않은 기본 상태에서 Attribute에 값을 대입할 수 있습니다. 멤버 함수에서 값을 바꿀 때는 결과를 반환해 호출자가 대입합니다(함수 안에서 Attribute에 대입하면 TBR-15055). SELF를 IN으로 명시하면 읽기 전용이 됩니다. SELF 파라미터는 인스턴스 데이터에 접근할 수 있도록 해주며 "SELF"."Attribute 이름"으로 사용하거나 "Attribute 이름"으로 직접 사용이 가능합니다.

멤버 메소드는 OVERLOADING이 가능합니다. 비교에 사용하는 MAP과 ORDER 메소드는 타입마다 하나만 정의합니다. 같은 이름으로 OVERLOADING하거나 MAP과 ORDER를 함께 정의하면 TBR-15203과 함께 타입이 컴파일 오류 상태로 생성됩니다. 선언부에 기술한 메소드는 BODY에도 같은 시그니처로 구현합니다(빠지면 TBR-15206). BODY에만 둔 메소드가 있으면 선언부에도 기술합니다(빠지면 TBR-15205).

다음은 Number Stack의 사용 예입니다.

```
declare
    s1 type_nstack; 
begin
    s1 := type_nstack (16);
    
    s1.push (1);                         -- 멤버 메소드 호출(procedure) 
    dbms_output.put_line (s1.top());     -- 멤버 메소드 호출(function)
    s1.pop;                              -- 멤버 메소드 호출(procedure)
end;
/
```

다음은 실행 결과입니다.

```
1
```

## SQL과 테이블에서의 사용

오브젝트 타입은 tbPSM 블록 안에서만 쓰는 것이 아니라 다음과 같이 SQL 에서도 사용할 수 있습니다.

* 테이블의 컬럼 타입
* 오브젝트 테이블(CREATE TABLE ... OF 타입)의 타입
* SQL 문장 안의 생성자 호출, 메소드 호출, Attribute 접근

컬럼의 Attribute는 테이블 별칭을 붙여 **별칭.컬럼.Attribute** 형태로 조회하고 갱신합니다. Attribute에 접근할 때는 별칭을 반드시 붙입니다(별칭이 없으면 TBR-8026). 컬럼 전체를 조회하면 생성자 형태의 문자열로 표시됩니다.

다음은 오브젝트 타입을 컬럼 타입으로 사용하고 MAP 메소드로 정렬과 비교를 수행하는 예입니다.

```

CREATE OR REPLACE TYPE money_type AS OBJECT (
    amount   NUMBER,
    MAP MEMBER FUNCTION to_number RETURN NUMBER
);
/
CREATE OR REPLACE TYPE BODY money_type AS
    MAP MEMBER FUNCTION to_number RETURN NUMBER IS
    BEGIN
        RETURN amount;
    END;
END;
/
CREATE TABLE emp_salary (empno NUMBER, salary money_type);

INSERT INTO emp_salary VALUES (1, money_type(3000));
INSERT INTO emp_salary VALUES (2, money_type(1000));
INSERT INTO emp_salary VALUES (3, money_type(2000));

SELECT empno, e.salary.amount AS amount FROM emp_salary e ORDER BY e.salary;

UPDATE emp_salary e SET e.salary.amount = 3500 WHERE empno = 1;

SELECT empno FROM emp_salary e WHERE e.salary > money_type(2000) ORDER BY empno;

CREATE TABLE salary_only OF money_type;
INSERT INTO salary_only VALUES (money_type(500));
SELECT amount FROM salary_only;
```

다음은 실행 결과입니다. MAP 메소드가 반환하는 값을 기준으로 정렬과 비교가 수행됩니다.

```
     EMPNO     AMOUNT
---------- ----------
         2       1000
         3       2000
         1       3000

     EMPNO
----------
         1

    AMOUNT
----------
       500
```

비교와 정렬에 필요한 메소드는 연산에 따라 다릅니다. 다음은 연산별로 정의해야 하는 메소드입니다.

<table><thead><tr><th width="190">연산</th><th width="220">정의해야 하는 메소드</th><th>참고</th></tr></thead><tbody><tr><td>tbPSM 비교 연산</td><td>MAP 또는 ORDER</td><td>메소드가 없으면 TBR-15208</td></tr><tr><td>SQL 등호 비교</td><td>없어도 됨</td><td>Attribute 단위로 비교</td></tr><tr><td>SQL 부등호 비교</td><td>MAP 또는 ORDER</td><td>메소드가 없으면 TBR-11023</td></tr><tr><td>SQL ORDER BY 절</td><td>MAP</td><td>ORDER만 정의한 경우는 TBR-8154</td></tr></tbody></table>

### 상위 타입 컬럼에 하위 타입 저장

상위 타입으로 선언한 컬럼에는 하위 타입의 인스턴스를 저장할 수 있습니다. 저장된 인스턴스의 실제 타입에 따라 메소드가 호출되므로 SQL 문장에서도 다형성이 그대로 적용됩니다. 오브젝트 타입을 반환하는 함수도 SQL 문장에서 호출할 수 있습니다.

다음은 "상속과 다형성"에서 만든 emp\_base, emp\_mgr 타입을 그대로 사용하는 예입니다.

```
CREATE TABLE emp_all (id NUMBER, emp emp_base);

INSERT INTO emp_all VALUES (1, emp_base(1, 'KIM'));
INSERT INTO emp_all VALUES (2, emp_mgr(2, 'LEE', '영업'));

col title format a24
SELECT id, a.emp.title() AS title FROM emp_all a ORDER BY id;

CREATE OR REPLACE FUNCTION make_mgr(p_no NUMBER, p_name VARCHAR2, p_dept VARCHAR2)
RETURN emp_mgr IS
BEGIN
    RETURN emp_mgr(p_no, p_name, p_dept);
END;
/
SELECT make_mgr(3, 'PARK', '인사').title() AS title FROM dual;
```

다음은 실행 결과입니다. 2번 행에는 하위 타입 인스턴스가 저장되어 재정의한 메소드가 실행됩니다.

```
        ID TITLE
---------- ------------------------
         1 KIM (사원)
         2 LEE (영업 관리자)

TITLE
------------------------
PARK (인사 관리자)
```

## **오브젝트 관련 정적 뷰**

다음은 오브젝트 관련 정적 뷰입니다.

* ALL\_TYPES, DBA\_TYPES, USER\_TYPES
* ALL\_COLL\_TYPES, DBA\_COLL\_TYPES, USER\_COLL\_TYPES
* ALL\_TYPE\_ATTRS, DBA\_TYPE\_ATTRS, USER\_TYPE\_ATTRS
* ALL\_TYPE\_METHODS, DBA\_TYPE\_METHODS, USER\_TYPE\_METHODS
* ALL\_METHOD\_PARAMS, DBA\_METHOD\_PARAMS, USER\_METHOD\_PARAMS
* ALL\_METHOD\_RESULTS, DBA\_METHOD\_RESULTS, USER\_METHOD\_RESULTS

{% hint style="info" %}
참고

해당 뷰에 대한 자세한 내용은 "[Tibero 참조 안내서](/tibero-manuals/7.2.6.manuals/reference-guide.md)"의 "[정적 뷰](/tibero-manuals/7.2.6.manuals/reference-guide/static-views.md)"를 참고합니다.
{% endhint %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.tibero.com/tibero-manuals/7.2.6.manuals/tbpsm-guide/object-type.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
