DATABASE/SQL, PL-SQL

레코드(Record)

devstep88 2021. 2. 6. 23:26

레코드를 사용하면 데이터 타입이나 길이가 다른 여러 변수들을 논리적으로 하나의 그룹으로 묶을 수 있다.

로우 앵커(%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;