Продолжается подписка на наши издания! Вы не забыли подписаться?

Изменение типа данных объекта, или закат солнца вручную

Код к статье

Если вам нужно изменить структуру типа данных, команда ALTER TYPE не сможет помочь - а вот такой обходной путь сработает.

Даже наилучшим образом спланированный дизайн базы данных иногда нуждается в изменении. В старом, чисто реляционном мире такое изменение зачастую было простой задачей. С появлением ограничений по внешним ключам процесс несколько усложнился - простой импорт/экспорт перестал работать. Теперь, с приходом объектно-ориентированных технологий, рост количества встроенных зависимостей в словаре данных Oracle еще сильнее усложнил процесс модификации. Из-за изобилия возможных связей между объектами в базе данных тип данных объекта не может быть просто изолирован для модификации - следовательно, неудивительно, что команда ALTER TYPE больше не позволит изменить структуру базы данных.

К счастью, имеется все же обходной путь, который можно использовать для изменения структуры типа данных и зависимых от него объектов. В основе этого подхода лежит конвертация объектно-ориентированных сущностей в эквивалентные им реляционные, и, впоследствии, обратная конвертация в объекты - в теории несложный, но на практике достаточно головоломный путь. В этой статье приведен пошаговый разбор такого процесса с примерами из образца объектно-ориентированной базы данных, содержащего REFs, встроенные объекты и функции-чле ны. По мере чтения статьи учтите, что часть ее может быть использована при переводе реляционных сущностей Oracle7 в объектные сущности Oracle8. Кроме того, примеры могут быть полезны как взгляд изнутри на работу с сущностями, построенными с использованием объектных типов.

Ограничения ALTER TYPE

Причина, усложняющая изменение объектных типов данных, кроется в запутанности связей между объектами в базе данных. (Если вы еще не знакомы с возможностями объектов Oracle8, прочитайте главы 4, 25, 26 и 31 Oracle8: The Complete Reference, George Koch и Kevin Loney, 1997, Oracle Press). Для любого создаваемого в базе данных объекта сервер баз данных Oracle создает уникальный идентификатор, objectId (OID), используемый внутренне при каждом обращении к объекту. Сами объекты могут содержать другие объекты, ассоциированные функции-чле ны и процедуры, а также указатели на другие объекты через оператор REF. Этот набор связей создает сложную структуру зависимостей, а внутренняя природа OID-связей между объектами создает дополнительные проблемы при изменении этой структуры.

После создания объектного типа данных возможности команды ALTER TYPE по его изменению ограничены. Эту команду можно использовать для добавления новых процедур и функций, но убрать уже существующие вы не сможете. А если вам вздумается изменить индивидуальные компоненты типа объекта, придется уничтожить тип как таковой и создать заново. Однако же, если вы уже успели создать реляционные таблицы или объекты (например, переменные массивы, VARRAYS или объектные таблицы), основанные на типе объекта, уничтожить тип вам не позволят. Конечно, можно решить проблему силовыми методами - но после удаления типа объекта зависимые таблицы или объекты станут полностью недоступны - даже если они все еще существуют.

Наша методика позволяет избежать такой потери доступа к зависимым объектам. Но прежде, чем перейти к описанию, несколько слов о нашей базе данных.

Пример объектно-ориентированной базы данных

Для иллюстрации работы с базой данных, содержащей объекты нескольких различных типов, использован пример объектно-ориентированной торговой системы из 7 главы Oracle8 Release 8.0 Application Developers Guide (1997, Oracle Press). Эта база данных включает информацию о покупателях, складских запасах и заказах. Рис. 1 показывает основную структуру типов данных и таблиц, а также их отношения. Тонкими стрелками отмечены встроенные объекты (стрелки ведут от встроенного или вложенного объекта к содержащему их объекту), а толстые - REFs к другим типам данных (от REF к объекту). Обратите внимание, что встроенные объекты в этой системе включают как VARRAY (PHONE_LIST_T), так и вложенную таблицу (PO_LINE_TAB).

Рис. 1. Эта диаграмма показывает типы данных и другие объекты нашего примера базы данных.
Стрелками отмечены встроенные объекты и ссылки (REF) на другие объекты.

Листинг 1 показывает SQL-код, создающий все типы данных, объекты, таблицы, функции, индексы и ограничения для системы. Изучение этого листинга - хороший способ ознакомиться с таблицами базы данных, а также с созданием элементов объектно-ориентированной базы данных (на самом деле листинг включает код и комментарии, иллюстрирующие управление объектами в мере, превышающей необходимую для данного примера).

Если у вас появится желание поэкспериментировать с этой базой данных - для тренировки в обращении с объектами - Листинг 2 содержит код для заполнения таблиц.

Изменение структуры типа данных

Что ж, база данных создана. Что случится, если нам захочется изменить один из типов данных? Скажем, добавить поле с названием Sales_mgr (VARCHAR2(30)) к типу данных CUSTOMER_INFO_T, который формирует базис объектной таблицы CUSTOMER_TAB - и на который идет ссылка через REF из PURCHASE_ORDER_T, типа данных, создающего базис объектной таблицы PURCHASE_TAB (которая заодно включает, чтоб жизнь медом не казалась, вложенную таблицу PO_LINE_TAB)?

Поскольку такие изменения простой командой ALTER TYPE не сделать, придется идти другим путем. Обходной путь состоит в перемещении данных из объектных таблиц в набор временных реляционных таблиц, уничтожении объектных таблиц и связанных с ними типов данных, создании таблиц и типов с измененными структурами и заполнением новых таблиц данными из временных реляционных таблиц. Процесс может осложниться из-за особой обработки вложенных таблиц и содержащихся в таблицах REF.

Следующий список содержит пошаговое описание процесса. Заметьте - часть его можно использовать для конвертации данных V7 в объектные таблицы Oracle8; в этом случае вы должны создать подходящие объектные типы и основанные на них сущности, а затем выполнить шаги 7-9 для заполнения объектных сущностей из эквивалентных реляционных. Итак:

1. Обратитесь к виду ALL_DEPENDENCIES для просмотра объектов, зависящих от изменяемого типа данных, и определения порядка их уничтожения и последующего воссоздания. Общее правило - зависимые типы данных уничтожаются раньше тех, от которых они зависят, а воссоздаются в обратном порядке. Например, вам нужно уничтожить типы данных, содержащие REFs до уничтожения типов, на которые они ссылаются. После определения последовательности выполните шаги 2-5 для уничтожения выбранного типа данных и прямо зависимых от него объектов, затем вернитесь к шагу 2 и повторите операции для следующего типа. Когда же все, что нужно, будет уничтожено, переходите к шагам 6-8 для воссоздания и заполнения каждого типа данных и его зависимых объектов, начиная с того типа, который уничтожался последним и двигаясь в обратном направлении.

В нашем примере объектами, зависимыми от типа данных CUSTOMER_INFO_T, являются объектная таблица CUSTOMER_TAB (прямо основанная на CUSTOMER_INFO_T), тип данных PURCHASE_ORDER_T (который включает REF на CUSTOMER_INFO_T) и объектная таблица PURCHASE_TAB (прямо основанная на PURCHASE_TAB). По правилу, с которого мы начали этот пункт, выходит, что первым следует удалять тип данных PURCHASE_ORDER_T (и его зависимую таблицу PURCHASE_TAB), поскольку он содержит REF на CUSTOMER_INFO_T. Проделав шаги 2-5 для удаления этих объектов, мы должны будем повторить их для удаления CUSTOMER_INFO_T и CUSTOMER_TAB. Затем следует поделать шаги 7-8 сначала для воссоздания и наполнения CUSTOMER_INFO_T и CUSTOMER_TAB, а затем уже для PURCHASE_ORDER_T и PURCHASE_TAB.

2. Создайте временные реляционные таблицы для хранения данных из объектных таблиц, основанных на изменяемом типе данных (за исключением данных из вложенных таблиц, которыми вы займетесь на третьем этапе).

2a) В первом применении шага 2 к нашей базе данных мы используем следующее выражение CREATE TABLE для создания временной таблицы с названием PM для хранения master details (то есть всего, кроме данных из вложенной таблицы) из родительской таблицы PURCHASE_TAB. Определение таблицы PM включает только ключевое поле (Custno) для CUSTOMER_TAB, и никаких других элементов CUSTOMER_TAB, поскольку PURCHASE_TAB не содержит CUSTOMER_TAB или CUSTOMER_INFO_T; в ней хранятся только указатели на тип данных CUSTOMER_INFO-T. И наоборот, PM включает все элементы вложенного объектного типа ADDRESS_T.

    CREATE TABLE PM (PONO NUMBER, CUSTNO NUMBER, ORDERDATE DATE, 
        SHIPDATE DATE,STREET VARCHAR2(200), 
        CITY VARCHAR2(200), STATE VARCHAR2(2), 
        ZIP VARCHAR2(20));

2b) Вторым заходом мы применим следующее выражение CREATE TABLE для создания реляционной таблицы с названием CT и определениями полей Custno и Custname из CUSTOMER_TAB. Следует создать и поля для Street, City, State и Zip из ADDRESS_T, а также поля с p1 по p10 как varchar2(20), так как каждая запись в CUSTOMER_TAB может содержать до 10 телефонных номеров в VARRAY PHONE_LIST_T.

CREATE TABLE CT (CNO NUMBER, CNAME VARCHAR2(200),
          STREET VARCHAR2(200), CITY VARCHAR2(200), 
          STATE CHAR(2), ZIP VARCHAR2(20), p1 varchar2(20), 
          p2 varchar2(20),p3 varchar2(20),p4 varchar2(20), 
          p5 varchar2(20),p6 varchar2(20),p7 varchar2(20), 
          p8 varchar2(20),p9 varchar2(20),p10 varchar2(20));

3. Если имеются вложенные таблицы, нужно создать отдельные дочерние реляционные таблицы (с копией родительского поля, содержащего primary key) для хранения данных из вложенных таблиц.

3a) Сперва используем следующее выражение CREATE TABLE для создания временной таблицы с названием PC для хранения информации из вложенной таблицы PO_LINE_TAB. Обратите внимание на дополнительное поле PONO, служащее первичным ключом для родительской таблицы PM. Другие поля дочерней таблицы являются элементами вложенного табличного типа LINE_ITEM_T.

 CREATE TABLE PC (LINO NUMBER, STOCKNO NUMBER, 
         COST NUMBER, TAX_CODE NUMBER, QUANTITY NUMBER, 
         DISCOUNT NUMBER, PONO NUMBER); 

3b) Второго захода не будет - вложенных таблиц больше нет.

4. Заполните временные реляционные таблицы данными, используя в целом выражение INSERT, а для особых случаев, например, при вставке данных из VARRAY, код PL/SQL. Процедуры PL/SQL следует использовать и для исключения проблем при попытках использования deref при доступе к отдельным элементам deref'ed объектов в том же SQL-выражении. При работе с VARRAY процедуры PL/SQL необходимы, поскольку единственным способом доступа к VARRAY из SQL*Plus является создание вложенной таблицы со структурой, эквивалентной VARRAY, сброс VARRAY во вложенную таблицу оператором CAST и последующее применение к ней оператора.

4a) Сперва с помощью INSERT для заполнения таблицы PM данными из PURCHASE_TAB:

      insert into pm (pono, custno, orderdate, shipdate, 
      street, city, state, zip)
      select p.pono, p.custref.custno, p.orderdate,
      p.shipdate,p.shiptoaddr.street,
      p.shiptoaddr.city, p.shiptoaddr.state, 
      p.shiptoaddr.zip from purchase_tab p 
      / 

Затем , используя приведенную в Листинге 3 процедуру PL/SQL, заполним таблицу PC данными из вложенной таблицы PO_LINE_TAB.

4b) Вторым применением шага 4 будет заполнение таблицы СТ данными из CUSTOMER_TAB с помощью выражения INSERT:

         insert into ct (cno, cname, street, city, state,zip) 
            select c.custno, c.custname, c.address.street, 
              c.address.city, c.address.state, c.address.zip 
              from customer_tab c; 

Затем, используя приведенную в Листинге 4 процедуру PL/SQL, заполним СТ значениями из PHONE_LIST VARRAY в CUSTOMER_TAB.

5. Теперь можно и уничтожить оригиналы таблиц и типов данных, сперва таблицы, а затем уж и типы, на которых эти таблицы основаны. Удалив тип данных, возвращайтесь к шагу 2, если нужно удалить что-нибудь еще. Наудалявшись всласть, переходите к шагу 6.

5a) Сначала удалим таблицу PURCHASE_TAB (это действие автоматически удалит вложенную таблицу PO_LINE_TAB), а затем тип данных PURCHASE_ORDER_T.

5b) Вторым применением шага 5 будет удаление таблицы CUSTOMER_TAB и, вслед за этим, типа данных CUSTOMER_INFO_T.

6. Начнем с последнего из удаленных типов данных. Создадим заново его и прямо зависимые от него объекты, внеся желаемые изменения. До воссоздания других типов проделайте над этим типом данных шаги 7 и 8.

6a) Сперва воссоздадим тип CUSTOMER_INFO_T с помощью кода, приведенного в Листинге 5 (добавив поле Sales_mgr) и таблицу CUSTOMER-TAB, с функцией cust_order, определенной как часть типа CUSTOMER_INFO_T.

6b) Второе применение шага 6 - восстановление типа данных PURCHASE_ORDER_T, вложенной таблицы PO_LINE_TAB и объектной таблицы PURCHASE_TAB. Поскольку изменений в структуре этих объектов не производится, создающий их код идентичен приведенному в Листинге 1.

7. Заполните новые объектные таблицы (то есть все, кроме вложенных, если таковые имеются) данными из временных реляционных таблиц, при необходимости обновляя REF'ы.

7a) Сперва с помощью выражения INSERT заполним таблицу CUSTOMER_TAB значениями из CT. Заметьте, что значения Sales_mgr - нулевые; мы, конечно, определили в этом упражнении колонку Sales_mgr, но вопрос ее заполнения не рассматривали.

     insert into customer_tab 
          select cno, cname, 
          address_t(street, city, state,zip), 
          phone_list_t(p1,p2,p3,p4,p5,p6,p7,p8,p9,p10), null from ct; 

Также обратите внимание - в новой таблице имеются все значения phone_list VARRAY, чего не было в оригинальной таблице. Когда мы создавали таблицу CP в Листинге 3, мы инициализировали значения p1 - p10 как нулевые и затем заполнили их из VARRAY. Запись в CUSTOMER_TAB может содержать только один телефонный номер (одно вхождение в VARRAY), как custno = 1 в нашем примере. И еще, VARRAY вполне может быть пуст. Для записей с одним телефонным номером мы получим значение для p1 и нули для p2 - p10. При воссоздании таблицы CUSTOMER_TAB мы просто слепо кладем назад все имевшиеся значения. VARRAY с нулевыми значениями для всех сущностей (p1 - p10) - это не то же самое, что элементарно пустой VARRAY (определенный как phone_list_t(), но не имеющий никаких значений). Если по каким-либо причинам вы хотите восстановить оригинальную конфигурацию VARRAY, с пустым VARRAY, потребуется более сложная PL/SQL-процедура.

7b) Второе применение шага 7 - использование выражений INSERT и UPDATE для помещения данных из РМ в PURCHASE_TAB и обновления Custref REF из CUSTOMER_TAB. Заметьте: мы определяем вложенную таблицу LINE_ITEM_LIST как "LINE_ITEM_LIST()", что делает ее atomically null (определенной, но не содержащей никаких значений), а не просто null. Это делается потому, что нельзя создать записей во вложенной таблице для некой записи в PURCHASE_TAB, если LINE_ITEM_LIST равен просто null.

      INSERT INTO PURCHASE_TAB SELECT A.PONO,NULL, 
           A.ORDERDATE, A.SHIPDATE, LINE_ITEM_LIST(), 
           ADDRESS_T(A.STREET, A.CITY, A.STATE, A.ZIP) 
      FROM PM A; 
      UPDATE PURCHASE_TAB P 
           SET CUSTREF = (SELECT REF(C) FROM CUSTOMER_TAB C, 
           PM A 
      WHERE A.CUSTONO=C.CUSTNO AND A.PONO = P.PONO); 

8. Заполните вложенные таблицы данными из временных таблиц, обновляя REF'ы по мере надобности. Если у вас еще есть невоссозданные типы данных или незаполненные таблицы, вернитесь к шагу 6. Если же таковых не осталось, переходите к следующему шагу.

8a) В первом случае делать ничего не надо - в CUSTOMER_INFO_T и CUSTOMER_TAB нет вложенных таблиц.

8b) Второй случай требует использования описанной в Листинге 6 PL/SQL-процедуры для заполнения таблицы PO_LINE_TAB данными из временной таблицы PC и обновления REF на таблицу STOCK_TAB. Процедура разбивает процесс на 2 части: сперва заполняется вложенная таблица, затем обновляется REF.

9. Удалите временные реляционные таблицы - теперь они без надобности.

В нашем случае - уничтожьте таблицы CT, PM, and PC (в любом порядке).

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

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

Рей Панде (Raj Pande)


Copyright © 1994-2016 ООО "К-Пресс"