레코드(Record)
레코드를 사용하면 데이터 타입이나 길이가 다른 여러 변수들을 논리적으로 하나의 그룹으로 묶을 수 있다.
로우 앵커(%ROWTYPE)를 사용하면 기존의 테이블 또는 뷰와 돌일한 구조의 레코드를 쉽게 정의하고 사용할 수 있다.
또한 레코드를 사용하면 SQL문을 훨씬 간단하게 작성할 수 있다.
레코드 사용
레코드를 사용하기 위해서는 컬렉션과 마찬가지로 먼저 레코드 타입을 선언하고 이 타입을 이용해서 레코드 변수(인스턴스)를 선언해야 한다.
TYPE 타입명 IS RECORD (필드목록);
레코드변수명 타입명;
여기에서 필드목록은 레코드의 필드 목록을 선언하는 부분으로 형식은 CREATE TABLE문의 컬럼선언과 유사한 형태를 가진다.
DECLARE
TYPE emp_type IS RECORD( --세 개의 필드를 가지는 레코드 선언
empno NUMBER(4) NOT NULL := 0, -- NOT NULL 필드는 반드시 초기값을 선언해야 함
ename emp.ename%TYPE, -- 컬럼 앵커를 사용한 필드 선언
job VARCHAR2(9) -- 필드의 데이터 타입을 직접 지정
);
v_emp emp_type; -- 타입을 사용하여 변수(인스턴스) 선언
BEGIN
-- 레코드의 필드에 값을 할당
v_emp.empno := 9000;
v_emp.ename := '홍길동';
v_emp.job := '의적';
DBMS_OUTPUT_PUT_LINE('EMPNO = '||v_emp_empno);
DBMS_OUTPUT_PUT_LINE('ENAME = '||v_emp_ename);
DBMS_OUTPUT_PUT_LINE('JOB = '||v_emp_job);
END;
레코드 선언 시 각 필드에는 반드시 데이터 타입을 지정해야 한다. 데이터 타입은 직접 지정할 수도 있고, 앵커를 사용하여 지정할 수도 있다. 필드에는 옵션으로 초기값을 지정할 수 있는데, 위 프로그램에서와 같이 empno의 초기값을 0으로 주면 모든 emp_type 레코드 인스턴스의 empno 컬럼은 최초에 항상 초기값 0을 가지게 된다. NOT NULL 필드의 경우에는 반드시 초기값을 지정해야 오류가 나지 않는다.
DECLARE
v_emp emp%ROWTYPE; -- 로우 앵커를 사용한 레코드 변수 선언
BEGIN
-- 레코드의 필드에 값을 할당
v_emp.empno := 9000;
v_emp.ename := '홍길동';
v_emp.job := '의적';
DBMS_OUTPUT_PUT_LINE('EMPNO = '||v_emp_empno);
DBMS_OUTPUT_PUT_LINE('ENAME = '||v_emp_ename);
DBMS_OUTPUT_PUT_LINE('JOB = '||v_emp_job);
END;
주의할 것은 %ROWTYPE을 사용하여 선언한 레코드 변수 v_emp에는 예제에서 사용된 세 개의 필드 empno, ename, job뿐만 아니라 테이블 emp에 정의되어 있는 다른 다섯 개의 컬럼까지 레코드의 필드로 선언된다는 사실이다.
레코드를 SQL에 사용하기
레코드는 관련을 가지는 변수들을 하나의 자료구조로 묶어서 프로그램을 보다 구조적으로 만들어 줄 뿐만 아니라 레코드 단위의 연산을 가능하게 해준다. 레코드를 사용하면 SELECT, INSERT, UPDATE문이 훨씬 간단해진다.
DECLARE
TYPE emp_type IS RECORD(
empno NUMBER(4) NOT NULL := 0, -- NOT NULL필드는 반드시 초기값 지정
ename emp.ename%TYPE,
job VARCHAR2(9)
);
BEGIN
-- INTO절에 세 개의 필드를 지정하는 대신 레코드 변수를 지정할 수 있다.
SELECT empno, ename, job
INTO v_emp
-- INTO v_emp.empno, v_emp.ename, v_emp.job -- 세 개의 변수를 윗 줄의 레코드 하나로 대체
FROM emp
WHERE empno = 7788;
DBMS_OUTPUT_PUT_LINE('EMPNO = '||v_emp_empno);
DBMS_OUTPUT_PUT_LINE('ENAME = '||v_emp_ename);
DBMS_OUTPUT_PUT_LINE('JOB = '||v_emp_job);
END;
레코드를 SQL에 사용할 때 필드의 순서는 중요한데 필드와 순번이 동일한 SELECT 컬럼은 동일하거나 호환되는 데이터 타입을 가져야 한다. 위 프로그램에서는 레코드의 필드 이름을 SELECT될 컬럼 이름과 동일하게 사용했지만 이름이 동일할 필요는 없다. 마찬가지로 데이터 타입도 정확히 일치해야할 필요는 없지만 묵시적 변환에 문제가 되지는 않아야 한다.
SQL문에 레코드 변수를 사용할 때 주의할 점은 일단 레코드 변수를 사용하게 되면 오로지 레코드 변수 하나만 지정 가능하며 다른 레코드 변수나 스칼라 변수, 상수, 리터럴등을 같이 사용할 수 없다는 점이다.
DECLARE
-- 테이블 emp의 일부 컬럼을 레코드로 선언
TYPE emp_basic_info_type IS RECORD (
empno emp.empno %TYPE,
ename emp.ename %TYPE,
job emp.job %TYPE,
mgr emp.mgr %TYPE,
hiredate emp.hiredate%TYPE,
deptno emp.deptno %TYPE
);
-- 테이블 emp의 나머지 칼럼을 레코드로 선언
TYPE emp_salary_info_type IS RECORD (
sal emp.sal %TYPE,
comm emp.comm %TYPE
);
-- 레코드 변수
v_emp_basic emp_basic_info_type;
v_emp_salary emp_salary_info_type;
-- 개별 스칼라 변수
v_sal emp.sal %TYPE;
v_comm emp.comm %TYPE;
BEGIN
-- 두 개의 레코드 변수를 INTO절에 사용할 수는 없다.
-- 파싱 단계에서 오류 발생
SELECT empno, ename, job, mgr, hiredate, deptno, sal, comm
INTO v_emp_basic, v_emp_salary
FROM emp
WHERE empno = 7788;
-- 레코드 변수와 스칼라 변수를 혼합하여 INTO절에 사용할 수도 없다
-- 파싱 단계에서 오류 발생
SELECT empno, ename, job, mgr, hiredate, deptno, sal, comm
INTO v_emp_basic, v_sal, v_comm
FROM emp
WHERE empno = 7788;
END;
레코드 변수에 값 할당
레코드 변수의 각 필드에 값을 할당할 때에는 각 필드별로 값을 할당하거나 할당 연산자 :=를 사용하여 다른 레코드를 복사하거나 쿼리를 실행한 결과를 한 번에 레코드 변수의 여러 필드에 담는 방법을 사용할 수 있다.
DECLARE
TYPE emp_rec IS RECORD (
empno emp.empno%TYPE;
ename emp.ename%TYPE;
);
v_emp1 emp_rec;
v_emp2 emp_rec;
v_emp3 emp_rec;
BEGIN
v_emp1.empno := 9000; v_emp1.ename := '홍길동'; -- 1. 필드별로 값을 할당
v_emp2 := v_emp1; -- 2. 다른 레코드를 복사
SELECT empno, ename INTO v_emp3 -- 3. 쿼리 결과를 레코드에 저장
FROM emp
WHERE empno = 7788;
END;
컬렉션에서 동일 구조의 변수이더라도 타입명이 다르면 할당 연산자를 사용할 수 없었던 것처럼 레코드에서도 동일한 구조이더라도, 타입명이 다르면 할당 연산자를 사용할 수 없다.
컬렉션에서의 생성자와 같이 한 문장으로 레코드의 모든 필드에 값읗 할당하는 방법은 기본적으로 지원하지 않는다.
유사한 기능을 원한다면 생성자와 유사한 함수를 만들어 해결하는 방법밖에 없다.
DECLARE
TYPE emp_rec IS RECORD (
empno emp.empno%TYPE;
ename emp.ename%TYPE;
);
v_emp emp_rec;
-- 생성자 역할을 하는 함수를 만든다.
FUNCTION make_emp_rec(a_empno emp.empno%TYPE,
a_ename emp.ename%TYPE) RETURN emp_rec
IS
v_rec emp_rec;
BEGIN
v_rec.empno := a_empno;
v_rec.ename := a_ename;
RETURN v_rec;
END;
BEGIN
v_emp := make_emp_rec('9000', '아무개'); -- 생성자 역할의 함수를 사용
END;
함수 make_emp_rec는 레코드의 두 필드에 저장될 값을 매개변수로 받아서 emp_rec레코드를 구성한 후 이를 반환하고, 사용 시에는 함수의 결과를 레코드 변수에 할당한다.
레코드와 컬렉션의 혼합
레코드와 컬렉션은 혼합하여 동시에 사용할 수 있다. 둘을 동시에 사용한다는 것은 레코드 컬렉션(레코드의 컬렉션)이나 컬렉션 레코드(컬렉션의 레코드), 또는 레코드의 필드로 레코드를 사용할 수 있다는 것을 뜻한다.
레코드의 컬렉션을 사용하면 여러 개의 로우를 반환하는 SELECT문의 결과를 배열에다 한 번에 담을 수 있고, FORALL문을 사용하여 레코드 컬렉션을 사용한 INSERT, UPDATEM문을 실행할 수 있다.
DECLARE
TYPE city_tab_type IS TABLE OF VARCHAR2(64) INDEX BY PLS_INTEGER; -- 컬렉션
TYPE name_rec IS RECORD -- 레코드
first_name VARCHAR2(30),
last_name VARCHAR2(30)
);
TYPE emp_rec IS RECORD ( -- 컬렉션과 레코드의 혼합
empno emp.empno%TYPE DEFAULT 1000,
ename name_rec, -- 레코드가 레코드의 필드가 될 수 있다.
city city_tab_type -- 컬렉션이 레코드의 필드가 될 수 있다.
);
TYPE people_type IS VARRAY(10) OF name_rec; --레코드의 컬렉션이 가능
TYPE emp_type IS VARRAY(10) OF emp_rec; --레코드의 컬렉션
BEGIN
NULL;
END;