Шаг 56 - PL/SQL - составные типы

Познакомившись, достаточно близко с курсорами, я думаю, у вас хватило терпения разобраться со всем этим! :) Далее давайте разберем один довольно интересный тип данных, который очень напоминает структуры в языке C. Обычные скалярные типы, такие как VARCHAR2, NUMBER и т.д. предварительно определены в модуле STANDART. По этому для их использования программе требуется лишь объявить переменную, данного типа, например вот так:

DECLARE

	m_COMPANY VARCHAR2(30);
	m_CUST_REP INTEGER;
	m_CREDIT_LIMIT NUMBER;

Но иногда удобнее использовать, так называемый составной тип. Одним из таких типов, в языке PL/SQL является запись RECORD. Как можно было догадаться, предыдущий пример, очень напоминает своей структурой, нашу учебную таблицу CUSTOMERS. Посмотрим на синтаксис объявления записи:

--------	TYPE тип записи IS RECORD   (   -------------------
--------    поле1 тип1 [NOT NULL] [:= выражение1] -------------
--------    поле2 тип2 [NOT NULL] [:= выражение2] -------------	
--------    поле-n тип-n [NOT NULL] [:= выражение-n] ); -------			

Вот таким образом объявляется составной тип RECORD. Как видите для полей записи, можно указывать ограничение NOT NULL, но подобно описанию, переменной вне записи исходное значение и ограничение NOT NULL не обязательны! Давайте запишем для примера вот такой блок:

SET SERVEROUTPUT ON
DECLARE

TYPE is_SmplRec IS RECORD 
	(
	m_Fld1 VARCHAR2(10),
	m_Fld2 VARCHAR2(30)  := 'Buber',
	m_DtFld DATE,
	m_Fld3 INTEGER := 1000,
	m_Fld4 VARCHAR2(100) NOT NULL := 'System'
	);


MY_SMPL is_SmplRec;

BEGIN

DBMS_OUTPUT.enable;
DBMS_OUTPUT.put_line(MY_SMPL.m_Fld2||' '||MY_SMPL.m_Fld4);

END;
/

После его прогона в SQL*Plus получаем:

SQL> SET SERVEROUTPUT ON
SQL> DECLARE
  2  
  3  TYPE is_SmplRec IS RECORD
  4 	(
  5 	m_Fld1 VARCHAR2(10),
  6 	m_Fld2 VARCHAR2(30)  := 'Buber',
  7 	m_DtFld DATE,
  8 	m_Fld3 INTEGER := 1000,
  9 	m_Fld4 VARCHAR2(100) NOT NULL := 'System'
 10 	);
 11  
 12  
 13  MY_SMPL is_SmplRec;
 14  
 15  BEGIN
 16  
 17  DBMS_OUTPUT.enable;
 18  DBMS_OUTPUT.put_line(MY_SMPL.m_Fld2||' '||MY_SMPL.m_Fld4);
 19  
 20  END;
 21  /
Buber System                                                                    

Процедура PL/SQL успешно завершена.

Как видно процедура успешно выполнилась и вернула значения. Здесь хорошо видно, как мы проинициализировали переменные записи внутри объявления. Так же одно из полей объявленное как NOT NULL сразу получило значение, вследствие того, что при этих условиях это необходимо! Давайте так же рассмотрим блок, где показано, как с использованием правил обращения к полям записи, а именно через точечную нотацию (как в языке Pascal), так же можно переопределить исходные значения полей записи:

SET SERVEROUTPUT ON
DECLARE

TYPE is_SmplRec IS RECORD 
	(
	m_Fld1 VARCHAR2(10),
	m_Fld2 VARCHAR2(30)  := 'Buber',
	m_DtFld DATE,
	m_Fld3 INTEGER := 1000,
	m_Fld4 VARCHAR2(100) NOT NULL := 'System'
	);


MY_SMPL is_SmplRec;

BEGIN

MY_SMPL.m_DtFld := SYSDATE;
MY_SMPL.m_Fld4 := 'Unknown';

DBMS_OUTPUT.enable;
DBMS_OUTPUT.put_line(MY_SMPL.m_Fld2||' '||MY_SMPL.m_Fld4);

END;
/

После обработки получаем:

SQL> SET SERVEROUTPUT ON
SQL> 
SQL> DECLARE
  2  
  3  TYPE is_SmplRec IS RECORD
  4  	(
  5  	m_Fld1 VARCHAR2(10),
  6  	m_Fld2 VARCHAR2(30)  := 'Buber',
  7  	m_DtFld DATE,
  8  	m_Fld3 INTEGER := 1000,
  9  	m_Fld4 VARCHAR2(100) NOT NULL := 'System'
 10  	);
 11  
 12  
 13  MY_SMPL is_SmplRec;
 14  
 15  BEGIN
 16  
 17  MY_SMPL.m_DtFld := SYSDATE;
 18  MY_SMPL.m_Fld4 := 'Unknown';
 19  
 20  DBMS_OUTPUT.enable;
 21  DBMS_OUTPUT.put_line(MY_SMPL.m_Fld2||' '||MY_SMPL.m_Fld4);
 22  
 23  END;
 24  /
Buber Unknown                                                                   

Процедура PL/SQL успешно завершена.

Как видно переменная MY_SMPL.m_DtFld получила значение текущей даты, с применением функции SYSDATE, которая возвращает текущую дату и время системы, а переменная MY_SMPL.m_Fld4, сменила строковый литерал. Вот таким образом, записи как составной тип данных способны к изменению своего внутреннего содержимого. Теперь давайте рассмотрим, как производится присваивание записей. В языке PL/SQL при присвоении значения одной записи другой используется, так называемая семантика копирования. Хотя присвоить одну запись непосредственно другой не допускается, даже если поля в обоих записях одинаковы. Для PL/SQL это разные типы и по этому происходит следующее:

SET SERVEROUTPUT ON
DECLARE

TYPE is_SmplRecOne IS RECORD 
	(
	m_Fld1 VARCHAR2(10),
	m_Fld2 VARCHAR2(30),
	m_DtFld DATE,
	m_Fld3 INTEGER,
	m_Fld4 VARCHAR2(100)
	);

TYPE is_SmplRecTwo IS RECORD 
	(
	m_Fld1 VARCHAR2(10),
	m_Fld2 VARCHAR2(30),
	m_DtFld DATE,
	m_Fld3 INTEGER,
	m_Fld4 VARCHAR2(100)
	);


MY_SMPLONE is_SmplRecOne;
MY_SMPLTWO is_SmplRecTwo;

BEGIN

MY_SMPLONE := MY_SMPLTWO;

END;
/

Получаем:

SQL> SET SERVEROUTPUT ON
SQL>
SQL> DECLARE
  2  
  3  TYPE is_SmplRecOne IS RECORD
  4  	(
  5  	m_Fld1 VARCHAR2(10),
  6  	m_Fld2 VARCHAR2(30),
  7  	m_DtFld DATE,
  8  	m_Fld3 INTEGER,
  9  	m_Fld4 VARCHAR2(100)
 10  	);
 11  
 12  TYPE is_SmplRecTwo IS RECORD
 13  	(
 14  	m_Fld1 VARCHAR2(10),
 15  	m_Fld2 VARCHAR2(30),
 16  	m_DtFld DATE,
 17  	m_Fld3 INTEGER,
 18  	m_Fld4 VARCHAR2(100)
 19  	);
 20  
 21  
 22  MY_SMPLONE is_SmplRecOne;
 23  MY_SMPLTWO is_SmplRecTwo;
 24  
 25  BEGIN
 26  
 27  MY_SMPLONE := MY_SMPLTWO;
 28  
 29  END;
 30  /
MY_SMPLONE := MY_SMPLTWO;
              *
ошибка в строке 27:
ORA-06550: Строка 27, столбец 15: 
PLS-00382: выражение неправильного типа 
ORA-06550: Строка 27, столбец 1: 
PL/SQL: Statement ignored 

Что и следовало ожидать, получаем "PLS-00382: выражение неправильного типа"! Все верно, типы то разные! И такое представление не проходит! Но вот присвоение полей этих записей между собой вполне приемлимо! Вот так:

SET SERVEROUTPUT ON

DECLARE

TYPE is_SmplRecOne IS RECORD 
    (
    m_Fld1 VARCHAR2(10),
    m_Fld2 VARCHAR2(30),
    m_DtFld DATE,
    m_Fld3 INTEGER,
	m_Fld4 VARCHAR2(100)
	);

TYPE is_SmplRecTwo IS RECORD 
    (
    m_Fld1 VARCHAR2(10),
    m_Fld2 VARCHAR2(30),
    m_DtFld DATE,
    m_Fld3 INTEGER,
	m_Fld4 VARCHAR2(100)
	);


MY_SMPLONE is_SmplRecOne;
MY_SMPLTWO is_SmplRecTwo;

BEGIN

MY_SMPLONE.m_Fld3 := 100;
MY_SMPLONE.m_Fld4 := 'Buber';

MY_SMPLTWO.m_Fld3 := MY_SMPLONE.m_Fld3;
MY_SMPLTWO.m_Fld4 := MY_SMPLONE.m_Fld4;

DBMS_OUTPUT.enable;
DBMS_OUTPUT.put_line(TO_CHAR(MY_SMPLTWO.m_Fld3));
DBMS_OUTPUT.put_line(MY_SMPLTWO.m_Fld4);

END;
/

Получаем после обработки:

SQL> SET SERVEROUTPUT ON
SQL> 
SQL> DECLARE
  2  
  3  TYPE is_SmplRecOne IS RECORD
  4  	(
  5  	m_Fld1 VARCHAR2(10),
  6  	m_Fld2 VARCHAR2(30),
  7  	m_DtFld DATE,
  8  	m_Fld3 INTEGER,
  9  	m_Fld4 VARCHAR2(100)
 10  	);
 11  
 12  TYPE is_SmplRecTwo IS RECORD
 13  	(
 14  	m_Fld1 VARCHAR2(10),
 15  	m_Fld2 VARCHAR2(30),
 16  	m_DtFld DATE,
 17  	m_Fld3 INTEGER,
 18  	m_Fld4 VARCHAR2(100)
 19  	);
 20  
 21  
 22  MY_SMPLONE is_SmplRecOne;
 23  MY_SMPLTWO is_SmplRecTwo;
 24  
 25  BEGIN
 26  
 27  MY_SMPLONE.m_Fld3 := 100;
 28  MY_SMPLONE.m_Fld4 := 'Buber';
 29  
 30  MY_SMPLTWO.m_Fld3 := MY_SMPLONE.m_Fld3;
 31  MY_SMPLTWO.m_Fld4 := MY_SMPLONE.m_Fld4;
 32  
 33  DBMS_OUTPUT.enable;
 34  DBMS_OUTPUT.put_line(TO_CHAR(MY_SMPLTWO.m_Fld3));
 35  DBMS_OUTPUT.put_line(MY_SMPLTWO.m_Fld4);
 36  
 37  END;
 38  /
100                                                                             
Buber                                                                           

Процедура PL/SQL успешно завершена.

Вот теперь все правильно! Типы полей совпадают и присвоение проходит нормально! Присвоить значения полям, базы можно и с помощью оператора SELECT, выбрав одну запись из таблицы. Например, давайте запишем такой блок:

DECLARE

TYPE is_Customers IS RECORD 
	(
	m_COMPANY CUSTOMERS.COMPANY%TYPE,
	m_CUST_REP CUSTOMERS.CUST_REP%TYPE,
	m_CREDIT_LIMIT CUSTOMERS.CREDIT_LIMIT%TYPE
	);

MY_CUST is_Customers;

BEGIN

SELECT COMPANY, CUST_REP, CREDIT_LIMIT INTO MY_CUST 
FROM CUSTOMERS WHERE CUST_NUM = 2108;

DBMS_OUTPUT.enable;
DBMS_OUTPUT.put_line(MY_CUST.m_COMPANY||' '||
TO_CHAR(MY_CUST.m_CUST_REP)||' '||TO_CHAR(MY_CUST.m_CREDIT_LIMIT));

END;
/
Здесь объявлена запись на основе типов таблицы CUSTOMERS, и выбрана одна запись из этой таблицы с помощью, оператора INTO, все это отправлено в переменные записи:
SQL> DECLARE
  2  
  3  TYPE is_Customers IS RECORD
  4  	(
  5  	m_COMPANY CUSTOMERS.COMPANY%TYPE,
  6  	m_CUST_REP CUSTOMERS.CUST_REP%TYPE,
  7  	m_CREDIT_LIMIT CUSTOMERS.CREDIT_LIMIT%TYPE
  8  	);
  9  
 10  MY_CUST is_Customers;
 11  
 12  BEGIN
 13  
 14  SELECT COMPANY, CUST_REP, CREDIT_LIMIT INTO MY_CUST
 15  FROM CUSTOMERS WHERE CUST_NUM = 2108;
 16  
 17  DBMS_OUTPUT.enable;
 18  DBMS_OUTPUT.put_line(MY_CUST.m_COMPANY||' '||
 19  TO_CHAR(MY_CUST.m_CUST_REP)||' '||TO_CHAR(MY_CUST.m_CREDIT_LIMIT));
 20  
 21  END;
 22  /
Унесенные ветром 109 55,323                                                     

Процедура PL/SQL успешно завершена.

Вот таким образом, работает составной тип RECORD. Да, кстати я тут нашел, кое-какие ошибочки и недочеты в прошлых уроках. Я за них извиняюсь, так как материал, очень объемный и сложный! Вы сами можете мне на них указать, я буду рад! При этом сразу оговорюсь ДЕЛАЮ КАК МОГУ И СТАРАЮСЬ ПОМОЧЬ ВСЕМ В ИЗУЧЕНИИ ORACLE! А, отпускать в мой адрес ехидные заковырки, может каждый, а вот реально что-то сделать в этой жизни способны не многие! По этому ошибки в частности в шаге 17 исправлены и ничего страшного я в этом не вижу!!! :) Что ж, удачи всем и жду конструктивных советов и замечаний!


Предыдущий Шаг | Следующий Шаг | Оглавление
Автор Летучий Сергей - 02.11.2003