Oracle Magazine - Русское издание (Октябрь 2007)

Стивен Ферстайн

Объектные типы в коллекциях
(On Object Types in Collections, by Steven Feuerstein)

Источник: журнал Oracle Magazine, #6 2006, http://www.oracle.com/technology/oramag/oracle/06-nov/o66plsql.html

Лучшие способы извлечения объектов и атрибутов объектов из других объектов

Я создал объектный тип, массив (varray), который имеет три столбца с типами данных number, varchar2 и другой объект, соответственно. Как мне извлечь третий атрибут (объектного типа) из массива?

Давайте сконструируем массив (это такой тип коллекции, в котором может содержаться максимальное число элементов, описанных в коллекции), который удовлетворяет вашим требованиям, и я покажу, как можно сослаться на каждый из них или на их часть. Так как мне нравится вкусно поесть, то для примера я возьму тему про еду.

Сначала создадим объектный тип для общих параметров некоего блюда; эти три атрибута позволяют узнать о названии блюда, его группе и основной его расцветке:

CREATE TYPE food_t AS OBJECT (
   name VARCHAR2 ( 100 )
 , food_group VARCHAR2 ( 100 ) 
 , color VARCHAR2 ( 100 )
);
/

Затем создадим объектный тип для трапезы, имеющей атрибуты: количество людей, на которое она рассчитана, тип трапезы, и её оформление:

CREATE TYPE meal_t AS OBJECT (
   number_served INTEGER
 , meal_type VARCHAR2 ( 100 )
 , food_served food_t
);
/

Теперь создадим массив из трех элементов для перечисления всех ежедневных трапез:

CREATE TYPE meals_vat 
IS VARRAY ( 3 ) OF meal_t;
/

Затем напишем код Листинга 1 для заполнения массива тремя трапезами: завтрак, обед и ужин.

Листинг 1: Заполнение массива

DECLARE
   -- Локально объявленный массив инициализирован без элементов.
   l_one_day_of_meals meals_vat := meals_vat ( );
BEGIN
   -- Выделяем память для трёх трапез.
   l_one_day_of_meals.EXTEND ( 3 );

   -- Добавляем завтрак, используя конструктор как для самой трапезы, 
   -- так и для экземпляра объектного типа, входящего в её состав.
   l_one_day_of_meals ( 1 ) :=
      meal_t ( 4, 'BREAKFAST'
                , food_t ( 'Scrambled Eggs', 'Protein', 'Yellow' ));

   -- Добавляем обед
   l_one_day_of_meals ( 2 ) :=
         meal_t ( 6, 'LUNCH'
                , food_t ( 'Deluxe Salad', 'Vegetables', 'Mostly Green' ));

   -- Добавляем ужин
   l_one_day_of_meals ( 3 ) :=
         meal_t ( 10, 'DINNER'
                , food_t ( 'Tofu and Rice', 'Protein', 'White' ));
END;

Заметьте, что функции-конструкторы (функции, которые имеют то же самое название, что и тип, для которого они предназначены, создаются Oracle Database) используются для создания экземпляра объектного типа в строке, где выполняется присваивание. Я объявил также локальные переменные для хранения внутренних объектов, как показано ниже:

DECLARE
   -- Локально объявленный массив 
   -- инициализированный без элементов.
   l_one_day_of_meals meals_vat 
                   := meals_vat ( );
   -- Локальный экземпляр объектного типа для завтрака
   l_breakfast food_t 
   := food_t ( 'Scrambled Eggs', 
                   'Protein', 'Yellow' );
BEGIN
   l_one_day_of_meals.EXTEND;
   l_one_day_of_meals ( 1 ) 
   := meal_t ( 4, 'BREAKFAST', 
                   l_breakfast );
END;
/

Этот пример показывает, как в массив добавляются элементы. Теперь попробуем обратиться к значениям внутри элемента массива, используя код, показанный на Листинге 2.

Листинг 2: Доступ к элементам массива

SQL>  DECLARE
 2     -- Локально объявленный массив, инициализированный одним элементом.
 3     l_one_day_of_meals meals_vat
 4        := meals_vat ( meal_t ( 4
 5                              , 'BREAKFAST'
 6                              , food_t ( 'Scrambled Eggs', 'Protein', 'Yellow' )
 7                              )
 8                     );
 9  BEGIN
10     -- Если в трапезе участвует 2 и более людей,
11     -- покажем название блюда.
12     IF l_one_day_of_meals ( 1 ).number_served > 2
13     THEN
14        DBMS_OUTPUT.put_line (
15           l_one_day_of_meals ( 1 ).food_served.name );
16     END IF;
17  END;

Отметим интересные строки в Листинге 2:
Строки 4-8. В этом блоке инициализируется массив с одним элементом, с помощью обеих функций-конструкторов meal_t и food_t для загрузки первой строки в массив с данными про завтрак.
Строка 12. Я выяснил количество людей, на которое рассчитан завтрак, указав массив и затем порядковый номер в этой коллекции:
l_one_day_of_meals ( 1 )
Строка 15. Показано название блюда, приготовленного на завтрак, с помощью точечной нотации (указав атрибут объекта, как будто бы столбец таблицы: таблица.столбец) для получения атрибута элемента массива:
l_one_day_of_meals ( 1 ).food_served
Атрибут food_served, однако, существенно отличается от number_served. Это не числовое значение, а целый объект. Таким образом, я могу вычислить значение атрибута этого объекта, ещё раз используя точку после названия этого атрибута:
l_one_day_of_meals ( 1 ).food_served.name

Этот пример позволяет понять, как ссылаться на элементы и подэлементы в этих сложных структурах.

Извлечение объектных атрибутов из объектов

Я объявил вложенную таблицу объектного типа, которая имеет три столбца с типами number, varchar2 и другим объектом, соответственно. Затем я описал реляционную таблицу с вложенной таблицей в качестве столбца. Как извлечь атрибут этого объекта из вложенной таблицы в строке реляционной таблицы? Предыдущий ответ этой статьи описывает, как использовать точечную нотацию для того, чтобы добраться до атрибутов объектов коллекции. Теперь давайте рассмотрим, как извлечь эти данные, если они хранятся в столбце таблицы. В этом ответе я работаю с вложенными таблицами вместо массивов. Всё, что я здесь демонстрирую, применимо и к обоим типам коллекций, за исключением отдельно указанных случаев.

Я создал таблицу на основании типов, описанных выше в предыдущем вопросе, и вот этот новый тип вложенной таблицы:

CREATE TYPE meals_nt IS 
TABLE OF meal_t;
/ 

CREATE TABLE all_my_meals (
   date_served DATE,
   name VARCHAR2(100),
   meals_served meals_nt
) NESTED TABLE meals_served 
  STORE AS i_meals_nt
/ 

Затем я вставил две строки в эту таблицу как показано на Листинге 3.

Листинг 3: Обращение к элементам массива

DECLARE
   -- Локально объявленный массив, инициализированный без элементов.
   l_one_day_of_meals meals_nt := meals_nt ( );
BEGIN
   -- Выделяем место для трёх трапез.
   l_one_day_of_meals.EXTEND ( 3 );
   -- Добавляем завтрак, используя конструктор как для трапезы
   -- так и для экземпляра объектного типа для блюда.
   l_one_day_of_meals ( 1 ) :=
      meal_t ( 4, 'BREAKFAST'
             , food_t ( 'Scrambled Eggs', 'Protein', 'Yellow' ));
   -- Добавляем обед
   l_one_day_of_meals ( 2 ) :=
      meal_t ( 6
             , 'LUNCH'
             , food_t ( 'Deluxe Salad', 'Vegetables', 'Mostly Green' )
             );
   -- Добавляем ужин
   l_one_day_of_meals ( 3 ) :=
          meal_t ( 10, 'DINNER', food_t ( 'Tofu and Rice', 'Protein', 'White' ));

   -- Помещаем трапезы в таблицу.
   INSERT INTO all_my_meals
        VALUES ( SYSDATE, 'YumYum', l_one_day_of_meals );

   -- На следующий день изменяем завтрак и обед
   l_one_day_of_meals ( 3 ) :=
                 meal_t ( 4, 'BREAKFAST', food_t ( 'Donuts', 'Sugar', 'White' ));
   l_one_day_of_meals ( 3 ) :=
                 meal_t ( 4, 'DINNER', food_t ( 'Big Thick Steak', 'Protein', 'Brown' ));

   INSERT INTO all_my_meals
        VALUES ( SYSDATE, 'Lumberjack', l_one_day_of_meals );

   COMMIT;
END;
/

Затем я могу извлечь данные из реляционной таблицы, как показано ниже:

SQL> select * from all_my_meals
  2  /

DATE_SERV
------------
MEALS_SERVED(NUMBER_SERVED, MEAL_TYPE, FOOD_SERVED(NAME, FOOD_GROUP, COLOR))
-----------------------------------------------------------------------------
22-JUL-06
MEALS_nt(MEAL_T(4, 'BREAKFAST', FOOD_T('Scrambled Eggs', 'Protein', 'Yellow')), 
MEAL_T(6, 'LUNCH', FOOD_T('Deluxe Salad', 'Vegetables', 'Mostly Green')),
MEAL_T(10, 'DINNER', FOOD_T('Tofu and Rice', 'Protein', 'White')))

23-JUL-06
MEALS_nt(MEAL_T(4, 'BREAKFAST', FOOD_T('Scrambled Eggs', 'Protein', 
.
.
.

Однако не хочется просто отображать все эти данные. Хочется добраться до атрибута объекта во вложенной таблице. Предположим, что надо увидеть тип трапезы для каждой строки вложенной таблицы.

Запрос в строках ниже выглядит, как если бы применить точечную нотацию:

SQL> SELECT amm.meals_served.meal_type
  2  FROM all_my_meals amm
  3  /

К сожалению, это не будет работать; вы получите следующую ошибку:

SELECT amm.meals_served.meal_type
       *
ERROR at line 1:
ORA-00904: "AMM"."MEALS_SERVED"."MEAL_TYPE": invalid identifier

Жизнь несколько сложнее, но не сильно.

Для получения соответствующего атрибута объекта вложенной таблицы к ней следует применить оператор TABLE:

SQL> SELECT ms.meal_type
  2    FROM TABLE ( 
  3      SELECT meals_served                  
  4        FROM all_my_meals                  
  5       WHERE name = 'YumYum' ) ms
  6  /

MEAL_TYPE
-------------------------------------
BREAKFAST
LUNCH
DINNER

Другими словами, я извлекаю именно вложенную таблицу из таблицы all_my_meals. Я применяю оператор TABLE к каждой из вложенных таблиц, и затем могу использовать точечную нотацию для получения типа трапезы.

Я могу даже сослаться на атрибуты объектов вложенной таблицы, чтобы ограничить строки, возвращаемые по запросу. Например: SQL> SELECT ms.meal_type 2 FROM TABLE ( 3 SELECT meals_served 4 FROM all_my_meals 5 WHERE name = 'YumYum' ) ms 6 WHERE ms.number_served > 4 7 / MEAL_TYPE ------------------------------------- LUNCH DINNER

Можно не только запросить данные из вложенной таблицы, но и изменить информацию во вложенной таблице с помощью предложения UPDATE. (Заметьте, что при работе с массивом нельзя изменить отдельные элементы массива. Изменить можно только все элементы сразу — то есть заменить массив.)

На основании предыдущих примеров можно ожидать, что изменится не сама таблица (meals_served), а вложенная таблица, извлечённая из этой таблицы.

Предположим, например, что надо изменить количество участвующих в завтраке для строки с наименованием "YumYum", на 15. Предложение для изменения будет выглядеть следующим образом:

SQL> UPDATE TABLE ( 
  2        SELECT meals_served
  3          FROM all_my_meals
  4         WHERE name = 'YumYum' )
  5     SET number_served = 15
  6   WHERE meal_type = 'BREAKFAST'
  7  /

1 row updated.

Существует, конечно, множество других вариантов SQL-предложений, но я надеюсь, что теперь вы лучше понимаете основной принцип, чтобы в дальнейшем выполнять их самостоятельно.

В документации по Oracle приводится очень примеров из моих разработок. Я надеюсь, что вы ознакомитесь с "Using PL/SQL Collections with SQL Object Types" in the Oracle Database PL/SQL User's Guide and Reference в download.oracle.com/docs/cd/B19306_01/appdev.102/b14261/objects.htm#sthref2434.


Стивен Фернстайн (Steven Feuerstein (steven@stevenfeuerstein.com)) – проповедник PL/SQL из Quest Software. Он опубликаовал 10 книг по языку Oracle программирования, включая книги “Oracle PL/SQL Programming” и “Oracle PL/SQL Best Practices” (изд. O'Reilly Media). Feuerstein ведет сайт инструментов тестирования программ на PL/SQL (www.unit-test.com).

E-mail this page