Базы данных Oracle - статьи

         

Архитектура


На рис. 1 изображено представление архитектуры, стоящей за Oracle TimesTen. В контексте Oracle TimesTen базу данных принято называть хранилищем данных. Представление хранилища данных на диске называют файлом контрольной точки. Когда хранилище данных открывается в первый раз, все его содержимое считывается в память из файла контрольной точки. Последующие операции INSERT, DELETE, SELECT, UPDATE и другие операции базы данных выполняются в памяти. Изменения хранилища данных, обусловленные выполнением этих операций, периодически и асинхронно записываются в дисковый файл контрольной точки. Когда хранилище данных останавливается, любые остающиеся незаписанными изменения записываются в файл контрольной точки прежде, чем хранилище данных будет закрыто.

Рисунок 1.Архитектура Oracle TimesTen

Кроме того, Oracle TimesTen использует память на диске как механизм восстановления для защиты транзакций. Транзакции регистрируются в журналах. Если система “упала” и должна быть перезапущена, файл контрольной точки будет прочитан в память, затем будут применены журналы транзакций и хранилище данных будет снова открыто для бизнес-деятельности. Периодическая запись изменений базы данных в файл контрольной точки во время обычной деятельности сводит к минимуму время, необходимое для любой операции восстановления, которая могла бы произойти.

Как было только что описано, Oracle TimesTen может выполняться как автономная база данных, но при желании ее могло также выполнять как ускоритель производительности для данных, перемещаемых в/из Oracle Database. На рис. 1 показан агент кэша и сервис тиражирования, который может подключить Oracle TimesTen к Oracle Database. В сценарии этой статьи некоторые данные хранятся в Oracle TimesTen, как бы для иллюстрации автономного подхода, а другие данные хранятся в Oracle Database, чтобы проиллюстрировать повышение производительности. В случае необходимости вы можете объединить оба подхода.



Действительно ли память является ключом к решению всех проблем?


Поскольку Oracle TimesTen получает свое преимущество в производительности благодаря хранению всего хранилища данных в памяти, почему бы не попробовать просто запустить Oracle Database и сконфигурировать буферный кэш настолько большим, чтобы он мог удержать в памяти всю базу данных? Разве в таком случае Oracle Database не будет работать точно так же, как Oracle TimesTen? Это хороший и справедливый вопрос, достойный того, чтобы уделить ему внимание.

Продукт Oracle TimesTen с самого начала был спроектирован как размещающаяся в оперативной памяти база данных. В нем полностью отсутствует логический ввод/вывод в том смысле, который наличествует в Oracle Database. Нет никаких блоков базы данных. Нет никакого буферного кэша. Статьи индекса вместо того, чтобы содержать идентификаторы логических строк, указывают непосредственно на адреса памяти, где могут быть найдены эти целевые строки. Для перехода от статьи индекса к строке в Oracle TimesTen требуется простое разыменование указателя. Благодаря использованию драйверов TimesTen Data Manager 6.0, ваше приложение имеет прямой доступ к памяти, в которой хранятся данные; отпадает необходимость в каких-либо дорогостоящих контекстных переключателях.

Однако, память не является ключом к решению всех проблем. За последние несколько лет было написано очень много о ненулевой стоимости логического ввода/вывода в Oracle Database, и хотя Oracle TimesTen может сделать стоимость обращения к находящимся в оперативной памяти данным намного более близкой к нулю, какая-то стоимость все таки остается. По-прежнему очень важен хороший дизайн. По-прежнему остается применимой теория очередей. Все еще важна оптимизация производительности, чтобы сократить ненужную обработку.

Следующие шаги:

ПРОЧТИТЕ дополнительно:

Oracle TimesTen

Краткий архитектурный обзор Oracle TimesTen In-Memory Database

Руководство по инсталляции Oracle TimesTen In-Memory Database

ЗАГРУЗИТЕ

Oracle TimesTen

демонстрационные скрипты этой статьи



Кэширование данных “только для чтения”


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

Прежде, чем будет создан кэш данных телефонов/адресов только для чтения, следует создать пользователя – администратора кэша в Oracle Database. Этот пользователь владеет триггерами и таблицами, которые Oracle TimesTen создает в Oracle Database для отслеживания изменений в данных таким образом, чтобы они могли быть использованы для эффективного обновления кэшированных данных в Oracle TimesTen. Например, войдя в систему как пользователь system, выполните следующий оператор, чтобы создать администратора кэша с именем ttdispatch:

CREATE USER ttdispatch identified by ttdispatch default tablespace users quota unlimited on users;

Затем выполните следующие операции предоставления привилегий, чтобы позволить администратору кэша создавать триггеры для таблиц, принадлежащих другим пользователям (например, владельцу схемы DISPATCH):

grant create any trigger to ttdispatch;

Затем войдите в систему, как владелец схемы отправки, и предоставьте администратору кэша доступ SELECT к таблице TELEPHONES:

GRANT SELECT ON phones TO ttdispatch;

Теперь переключитесь к Oracle TimesTen и создайте группу кэша. Чтобы сделать это:

Находясь в ttIsql, соединитесь с вашим хранилищем данных.

Сделайте вызов встроенной процедуры ttCacheUidPwdSet() и специфицируйте для Oracle TimesTen имя пользователя/пароль, чтобы использовать их при соединении с Oracle Database как администратор кэша.

Вызовите ttCacheStart(), чтобы запустить агент кэширования TimesTen, который является ответственным за выполнение фактической работы по поиску данных из Oracle Database и кэширование этих данных в Oracle TimesTen.


Выполните оператор CREATE CACHE GROUP, чтобы определить группу связанных таблиц – в этом случае, только одну таблицу – для кэширования в Oracle TimesTen.

В листинге 2 показаны все четыре этих шага.

Листинг 2.Создание группы кэша “только для чтения”

Command> connect dsn=ttdispatch; Connection successful: . . . Command> call ttCacheUidPwdSet('ttdispatch','ttdispatch'); Command> call ttCacheStart(); Command> Command> CREATE READONLY

> CACHE GROUP phone_data > AUTOREFRESH > INTERVAL 5 MINUTES > FROM dispatch.phones ( > phone_num VARCHAR(8) NOT NULL PRIMARY KEY, > street_addr VARCHAR(20), > city VARCHAR(15) > ); Warning 5112: Cache table DISPATCH.PHONES contains VARCHAR column(s). Oracle VARCHAR comparison rule is different. (Предупреждение 5112: Кэшированная таблица DISPATCH.PHONES содержит столбец(цы) VARCHAR. Правила сравнения для VARCHAR в Oracle отличаются.)

Заметьте, что предупреждающее сообщение в конце листинга 2 служит напоминанием о том, что Oracle TimesTen считает пустую (незаполненную) строку (") не пустой (non-null), тогда как Oracle Database трактует пустую (empty) строку как пустую (null). При написании запросов с участием столбцов VARCHAR вы должны обращать внимание на подобные различия в поведении.

Теперь у вас имеется группа кэша, названная phone_data. В составе этой группы имеется единственная пустая таблица по имени TELEPHONES. Для группы кэша устанавливается автоматическое обновление, для чего с пятиминутным интервалом производится опрос Oracle Database относительно изменений в данных. Однако, такое автоматическое обновление первоначально выполняется в приостановленном состоянии (во время паузы). Выполните оператор LOAD CACHE GROUP, показанный в листинге 3, чтобы инициализировать кэш текущими данными из Oracle Database и вывести группу кэша из паузы. Начиная с этого момента, каждые пять минут Oracle TimesTen будет делать запросы к Oracle Database (вы можете указать большие или меньшие интервалы) об изменениях в таблице TELEPHONES. Триггеры и таблицы поддержки, принадлежащие администратору кэша (в этом случае, ttdispatch) делают такой опрос весьма эффективным.

Листинг 3. Выполнение начальной загрузки

Command> SELECT * FROM phones; 0 rows found.

Command> LOAD CACHE GROUP phone_data > COMMIT EVERY 100 ROWS; 6 rows affected.

Command> select * from phones; < 555-1234, 100 W. Munising Ave, Munising > < 555-2345, 101 E. Varnum, Munising >

< 555-3456, E2904 S. First, Trenary > < 555-4567, N3284 M-67, Limestone > < 555-5678, N7569 Spruce St., AuTrain > < 555-6789, 112 Colwell, Grand Marais > 6 rows found.


Когда счет идет на микросекунды


Джонатан Генник, перевод (Май-Июнь 2007)
Источник: , журнал Oracle Magazine, №6 (ноябрь/декабрь) 2006.

Постоянно находящаяся в оперативной памяти база данных Oracle TimesTen всегда наготове.

За пару прошлых лет корпорация Oracle сделала несколько стратегических приобретений, и одним из наиболее интересных из них является постоянно находящаяся в оперативной памяти база данных, которая называется TimesTen. База данных Oracle TimesTen позволяет разрабатывать в реальном времени стратегически важные приложения, в которых время реакции должно измеряться даже не миллисекундами, а микросекундами. Она может использоваться автономно или как быстрый двунаправленный кэш для данных, к которым часто обращаются из Oracle Database.

Хорошим примером приложения, которое может многое выиграть от применения Oracle TimesTen, является диспетчерская система реагирования на чрезвычайные ситуации, типа системы, к которой в США можно подключиться, набирая номер 911. (Фактически, БД Oracle TimesTen уже развернута, по крайней мере, в одном из приложений, связанных со службой 911.) Время ответа в таких системах является более чем критическим; это – вопрос жизни и смерти.

Когда в большинстве регионов США вы набираете номер 911, диспетчер сразу же видит на экране ваш номер телефона и адрес, а часто система предлагает карту, показывающую ваше местоположение и, возможно, даже информацию об известных проблемах со здоровьем в вашем местоположении. Например, пациент с серьезной аллергией, которому прописано иметь при себе автоинжектор эпинэфрина, может захотеть зарегистрировать эту информацию у диспетчеров группы реагирования на чрезвычайные ситуации. Немедленный доступ к адресу и другой критической информации является ключевым для быстрого получения помощи.

В предлагаемой статье описывается реализация элементарной системы баз данных, которая могла бы использоваться для отображения критической информации диспетчерам группы реагирования на чрезвычайные ситуации. Тем самым будет показано, как Oracle TimesTen может работать с Oracle Database, чтобы обеспечить надежное, порядка микросекунд, время отклика, гарантируя при этом, что критические данные всегда будут немедленно доступными.



Ошибки при тиражировании


Если вы не проявите осторожность при проектировании своего приложения, то подвергнетесь риску, что попытка зафиксировать в Oracle Database вставки в таблицу CALL_LOG, которые были успешно зафиксированы в Oracle TimesTen, потерпит неудачу. Любые такие ошибки тиражирования регистрируются в имеющем расширение .awterr асинхронном файле ошибок со сквозной записью, в том же самом каталоге, в котором содержатся файлы контрольной точки.

Один из способов избежать проблем при тиражировании состоит в том, чтобы тщательно продумать, какая база данных будет "владельцем", или управляющей, для заданной таблицы или для набора из связанных таблиц. В сценарии диспетчирования все вставки в таблицу CALL_LOG делаются в Oracle TimesTen. Не позволяется вносить никаких изменений данных со стороны Oracle Database. Таким образом, все вставки должны распространяться из Oracle TimesTen в Oracle Database без ошибок.



Преимущества TimesTen


Гибкость Oracle TimesTen намного превышает то, что было здесь показано. Кроме того, с Oracle TimesTen

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

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

Вы можете использовать тиражирование из Oracle TimesTen в Oracle TimesTen, чтобы создавать резервные базы данных для обеспечения высокой доступности или для зеркалирования баз данных для выравнивания загрузки.

Вы можете автоматически пропускать запросы к Oracle Database, когда они обращаются к таблицам, найденным в Oracle Database, а не в Oracle TimesTen.

Если объединить ее с нормальной базой данных и хорошим дизайном приложения в оперативной памяти, производительность Oracle TimesTen делает возможной работу как стратегически важных, так и критичных ко времени поддерживаемых базой данных приложений. Когда минимизация микросекунд может сэкономить деньги или жизни, может пригодиться Oracle TimesTen In-Memory Database.

Благодарю Сэма Дрейка (Sam Drake) и Саймона Лоу (Simon Law) из Oracle за их долготерпение при ответах на многочисленные вопросы относительно этой статьи. Отдельные благодарности отделу шерифа округа Олжер, шт. Мичиган, за предоставление возможности детального знакомства с диспетчерским центром округа; а также службе скорой медицинской помощи округа Олжер, предложившей использованный в этой статье сценарий примера.

Джонатан Генник () – опытный профессионал Oracle и зарегистрированный член сети Oak Table Network. Он написал пользующиеся спросом книги – “Карманное руководство по SQL” и “Карманный справочник по Oracle SQL*Plus”, опубликованные издательством O'Reilly Media.



Создание базы данных Oracle TimesTen


Возможно выполнение Oracle TimesTen на нескольких платформах, в том числе, на различных дистрибутивах Linux, Windows XP и Windows Server 2003, Solaris и HP-UX. Руководство по инсталляции постоянно находящейся в памяти базы данных Oracle TimesTen обеспечивает команды инсталляции для всех поддерживаемых платформ. Инсталляция Windows является очень простой, и именно ее использовали при выполнении примера для этой статьи.

Доступ к Oracle TimesTen, в конечном счете, осуществляется через ODBC. (Приложения Java используют JDBC, а драйвер JDBC TimesTen, в свою очередь, использует ODBC.) Создание хранилища данных Oracle TimesTen столь же просто, как определение источника данных ODBC и последующее соединение с ним. В целях этой статьи примем, что у вас имеется схема Oracle Database, которая называется DISPATCH, с таблицами, указанными в листинге 1. Далее предположим, что вы установили Oracle TimesTen и ваше приложение для реагирования на чрезвычайные ситуации – то самое, которое сообщает критическую информацию на сервер Windows, когда поступает запрос. Чтобы создать хранилище данных Oracle TimesTen для поддержки приложения для отправки и кэширования критических данных из Oracle Database используйте следующие шаги:

Листинг 1. Схема DISPATCH в Oracle Database 10g

CREATE TABLE phones ( phone_num VARCHAR2(8), street_addr VARCHAR2(20), city VARCHAR2(15), PRIMARY KEY (phone_num));

CREATE TABLE call_log ( call_num NUMBER(9), event_time TIMESTAMP, event VARCHAR2(80), PRIMARY KEY (call_num, event_time));

GRANT SELECT, UPDATE, INSERT ON call_log TO ttdispatch;

INSERT INTO phones VALUES ('555-1234','100 W. Munising Ave', 'Munising'); INSERT INTO phones VALUES ('555-2345','101 E. Varnum', 'Munising'); INSERT INTO phones VALUES ('555-3456','E2904 S. First', 'Trenary'); INSERT INTO phones VALUES ('555-4567','N3284 M-67', 'Limestone'); INSERT INTO phones VALUES ('555-5678','N7569 Spruce St.', 'AuTrain'); INSERT INTO phones VALUES ('555-6789','112 Colwell', 'Grand Marais');

COMMIT;


Перейдите к панели управления (Control Panel) и откройте администратор источника данных ODBC (ODFBC Data Source Administrator). Выберите Старт -> Параметры настройки -> Панель управления-> Инструментальные средства Администрирования -> Источники данных (ODBC) (Start -> Settings -> Control Panel -> Administrative Tools -> Data Sources (ODBC)).

Добавьте системный источник данных, используя драйвер TimesTen Data Manager 6.0.

Из вкладки Data Store (см. рис. 2) введите имя источника данных (DSN), путь к хранилищу данных и каталог с журнальными файлами. Путь к хранилищу данных должен заканчиваться именем файла без указания расширения.



Рисунок 2. Настройка источника данных Oracle TimesTen

Перейдите к вкладке General Connection, найдите поле User ID и введите имя пользователя Oracle Database, обладающего правами доступа к таблицам, перечисленным в Листинге 1. Здесь вы можете использовать имя владельца схемы.

Перейдите к вкладке Cache Connect, найдите там поле Oracle Password и введите пароль для имени пользователя, использованного на шаге 4. Затем найдите поле Oracle ID и введите имя сетевого сервиса (из tnsnames.ora) для Oracle TimesTen для использования при соединении с Oracle Database.

Нажмите OK, чтобы создать источник данных ODBC.

Примечание: при выполнении в среде Linux или других систем на основе UNIX, вы определяете источник данных путем редактирования файла .odbc.ini. Детали можно найти в “Руководстве по эксплуатации Oracle TimesTen In-Memory Database”.

Соединитесь с вашим недавно созданным источником данных ODBC, используя интерактивную SQL-утилиту Oracle TimesTen ttIsql (аналогичную SQL*Plus для Oracle Database). После этого выполните команду CONNECT, чтобы открыть источник данных, создавая, таким образом, хранилище данных. Например:

C:\A>ttisql

Copyright (c) 1996-2006, Oracle. . . . Command> CONNECT dsn=ttdispatch; . . . Connection successful:

Теперь у вас в памяти имеется выполняющееся хранилище данных Oracle TimesTen. По умолчанию Oracle TimesTen автоматически создает хранилище данных, когда оно должно быть использовано в первый раз. Чтобы увидеть перечень системных таблиц для этого хранилища данных, введите команду TABLES. Чтобы получить перечень всех доступных команд ttIsql, наберите HELP. Убедитесь, что после любой выполняемой вами команды ttIsql вы не забыли набрать точку с запятой (;).



В вашем путевом каталоге хранилища данных (c:\a\timesten на рис. 2) вы будете видеть две копии файла контрольной точки с расширениями .ds0 и .ds1. Имя этих файлов – это то самое имя, которое вы предоставили ранее на шаге 3. В вашем каталоге с журнальными файлами (также c:\a\timesten в этом примере), вы сможете увидеть журналы с расширением .log.

При использовании драйверов TimesTen Data Manager 6.0 (на шаге 2) вы даете вашему приложению возможность взаимодействовать непосредственно с хранилищем данных. Нет никаких контекстных переключателей и никаких запросов, отсылаемых по сети – только прямой, быстрый доступ к данным. Несколько приложений, совместно использующих хранилище данных, пользуются общим доступом к хранилищу данных через коллективный сегмент памяти.

По умолчанию Oracle TimesTen считывает все данные в память, когда первый пользователь соединяется с хранилищем данных, и записывает данные назад на диск, когда отключается последний пользователь. Это один из способов приближения Oracle TimesTen к минимальному обслуживанию (Zero Maintenance). Однако и здесь у вас имеются дополнительные возможности. Например, можно сделать так, чтобы Oracle TimesTen считывал хранилище данных в память сразу после запуска сервера, чтобы данные уже были там, когда подключится первый пользователь.


Создание кэша со сквозной записью


Принимая во внимание, что источником данных о телефонах/адресах является Oracle Database, а адресатом – Oracle TimesTen, вы хотите, чтобы зарегистрированные данные текли в другом направлении. Диспетчеры должны регистрировать события, имеющие отношение к звонкам в 911, в Oracle TimesTen. Однако, вы хотите, чтобы данные журнала были переданы для долгосрочного хранения в Oracle Database. С этой целью можно создать асинхронную группу кэша со сквозной записью. В листинге 4 показан предназначенный для этой цели оператор Oracle TimesTen.

Листинг 4. Создание кэша со сквозной записью

CREATE ASYNCHRONOUS WRITETHROUGH CACHE GROUP call_log_data FROM dispatch.call_log ( call_num BIGINT, event_time TIMESTAMP, event VARCHAR(80), PRIMARY KEY (call_num, event_time) ) UNIQUE HASH ON (call_num, event_time) PAGES=1000;

После создания группы, запустите сервис тиражирования Oracle TimesTen, вызывая для этого из ttIsql встроенную процедуру ttRepStart:

CALL ttRepStart();

Начиная с этого момента, все строки, которые ваше диспетчерское приложение вставляет в таблицу CALL_LOG Oracle TimesTen, будут вставлены в таблицу CALL_LOG в Oracle Database. Поскольку группа кэша является асинхронной, Oracle TimesTen зафиксирует транзакции, использующие вставки в CALL_LOG, не дожидаясь, пока соответствующие вставки будут зафиксированы в Oracle Database. Если подключение к Oracle Database будет прервано, то Oracle TimesTen будет отслеживать недавно вставленные записи журнала и отправлять их в Oracle Database, когда подключение будет установлено вновь.

Асинхронный кэш со сквозной записью дает вам лучшую производительность. Однако, вы можете создать синхронную группу кэша, если вы хотите гарантировать, что когда транзакция, использующая таблицу CALL_LOG, фиксируется в Oracle TimesTen, любые изменения в таблице CALL_LOG будут также зафиксированы и в Oracle Database. Выбор полностью остается за вами и определяется только вашими деловыми потребностями.



Создание таблиц


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

CREATE TABLE emergency_info ( phone_num VARCHAR(8), info VARCHAR(160), PRIMARY KEY (phone_num));

Теперь вы можете записать комментарии, которые могут оказаться полезными для диспетчеров и респондентов, оказавшихся в критическом положении:

INSERT INTO emergency_info VALUES ('555-1234', 'Child Jeff allergic to egg white. Epinephrine autoinjector in orange box near refrigerator.');

INSERT INTO emergency_info VALUES ('555-2345', 'Bedridden resident needs help exiting home in case of fire.');

Типы данных Oracle TimesTen не совпадают в точности с типами данных из Oracle Database. Например, в Oracle TimesTen используется тип данных VARCHAR, а не VARCHAR2. См. документацию по Oracle TimesTen In-Memory Database API и Справочное руководство по SQL, где можно найти подробную информацию о типах данных, включая то, как типы данных Oracle Database могут быть отображены на типы данных Oracle TimesTen.



Удаление устаревших записей


Таблица типа CALL_LOG будет постоянно расти. Вы не сможете сохранить всю таблицу подобного типа в размещенной в оперативной памяти базе данных. Вам придется предусмотреть условия, в соответствии с которыми должна производиться очистка устаревших журнальных записей. Один из подходов, которым можно было бы воспользоваться, состоит в том, чтобы сохранять в Oracle TimesTen только записи журнала за текущий день, оставляя Oracle Database всю работу по долговременному, архивному хранению. С этой целью можно ежедневно выполнять приведенный в листинге 5 оператор UNLOAD CACHE GROUP. Оператор удалит из Oracle TimesTen все записи, кроме записей текущего дня, оставив при этом нетронутой таблицу CALL_LOG в Oracle Database. В таком случае диспетчеры смогут получить доступ к журнальным записям текущего дня непосредственно из Oracle TimesTen. Любой из тех, кому потребуется просмотреть журнальные записи прошлых дней, может сделать запрос к Oracle Database.

Листинг 5.Удаление старых записей журналов

UNLOAD CACHE GROUP call_log_data WHERE NOT( EXTRACT(YEAR FROM EVENT_TIME) = EXTRACT(YEAR FROM SYSDATE) AND EXTRACT(MONTH FROM EVENT_TIME) = EXTRACT(MONTH FROM SYSDATE) AND EXTRACT(DAY FROM EVENT_TIME) = EXTRACT(DAY FROM SYSDATE) );



Аннотация


Давнее средство SQL Trace позволяет следить за выполнением запросов SQL серверными процессами, обслуживающими сеансы связи с СУБД. В версии Oracle 10 появился системный пакет DBMS_MONITOR, разрешающий отслеживать выдачу запросов более разнообразно, в том числе, на уровнях службы БД, приложения или его фрагмента, или же узла кластера. В статье показано, как это делается.



Единицы слежения


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

Понятие служба БД (SERVICE) формировалось в Oracle, начиная с версии 8, и к настоящему времени используется для логического группирования сеансов с целью как раз таки иметь обобщенную единицу слежения и управления при использовании общей БД разными приложениями. Например, сеансы одной и той же службы БД могут присутствовать одновременно на разных узлах кластера в конфигурации RAC, а в рамках одной и той же СУБД могут одновременно исполняться сеансы разных служб. Службу предлагается связывать с набором приложений, объединенных общими свойствами, пороговыми характеристиками или правилами потребления ресурсов СУБД.

Понятие модуль (MODULE) используется для обозначения периода работы конкретного приложения в течение сеанса, а понятие действия (ACTION) &#x2013; конкретного фрагмента приложения, то есть эпизода выдачи приложением определенной последовательности команд SQL.

Назначение опознавателя клиента (CLIENT IDENTIFIER) &#x2013; метить сеансы, например, установленные по схеме использования общих серверных процессов (shared server), или сеансы конечных пользователей при работе через сервер приложений (например, приложений для web).



Избирательное слежение за выполнением запросов SQL и загрузкой СУБД средствами пакета DBMS_MONITOR


Давнее средство Oracle SQL Trace позволяет фиксировать &#xab;профиль запросов SQL&#xbb;, выдаваемых серверными процессами, обслуживающими сеансы связи с СУБД, и представляет собой полезный инструмент для выявления проблемных запросов и анализа загрузки СУБД приложениями. До версии 10 оно могло включаться и выключаться только для конкретных сеансов связи с СУБД. Это не всегда удобно, поскольку в жизни более насущны профилирование и отладка приложения, или даже его фрагментов, а между приложением и сеансом чаще всего нет взаимнооднозначной связи.

Новый для версии 10 пакет DBMS_MONITOR расширил ранее имевшуюся возможность профилирования SQL-действий сеанса (своего либо чужого) слежением за действиями отдельных приложений и их частей. Для этого используется модель &#xab;служба БД&#xbb; &#x2014; &#xab;модуль&#xbb; &#x2014; &#xab;действие&#xbb;. Между этими понятиями нет формальной зависимости, но методически предлагается сопоставлять &#xab;службе БД&#xbb; группу логически связанных сеансов, &#xab;модулю&#xbb; &#x2014; приложение, работающее с данными, а &#xab;действию&#xbb; &#x2014; эпизод работы приложения. Точнее, средствами пакета можно отслеживать выдачу запросов SQL с точностью до следующих единиц:

&#xab;служб&#xbb;, отождествляемых значением SERVICE_NAME, &#xab;модулей&#xbb;, отождествляемых значением MODULE, &#xab;действий&#xbb;, отождествляемых значением ACTION, сеансов, отождествляемых значением CLIENT_IDENTIFIER, сеансов, отождествляемых значениями sid, serial#>, экземпляров СУБД, отождествляемых значением INSTANCE_NAME.

Просмотр и установка единиц слежения рассматриваются ниже.

Вдобавок пакет DBMS_MONITOR позволяет собирать обобщенную статистику выполняемых сеансами или приложениями запросов.



Не только слежение и не только из программы


Из указанных выше единиц слежения наиболее разработанной является служба. Она не только позволяет следить за действиями приложений, но и является единицей управления. Вот пример возможностей ее употребления в этом качестве на настоящий момент:

В планировщике заданий со службой можно связать конкретный класс заданий (пакет DBMS_SCHEDULER, процедура CREATE_JOBCLASS).

Сеансы, осуществляемые в рамках службы, можно указать в определении группы потребителей ресурсов СУБД при работе с распределителем (диспетчером) ресурсов (пакет DBMS_RESOURCE_MANAGER, процедура SET_CONSUMER_GROUP_MAPPING).

В системе предупредительной сигнализации именно для конкретной службы можно устанавливать пороговые значения для общего времени (ELAPSED_TIME_PER_CALL) и процессорного времени (CPU_TIME_PER_CALL) обработки запросов SQL, о превышении которых СУБД будет докладывать автоматически (пакет DBMS_SERVER_ALERT, процедура SET_THRESHOLD).

Процедура DISCONNECT_SESSION из пакета DBMS_SERVICE останавливает все сеансы требуемой службы.

Упомянутые пакеты DBMS_SCHEDULER, DBMS_SERVER_ALERT и DBMS_SERVICE, а также процедура SET_CONSUMER_GROUP_MAPPING старого пакета DBMS_RESOURCE_MANAGER появились в версии 10.

Кроме того, все реализованные программно возможности слежения и управления широко представлены соответствующими web-страницами OEM.



Пример отслеживания запросов SQL со стороны приложения и его элементов


Процедура SERV_MOD_ACT_TRACE_ENABLE пакета DBMS_MONITOR позволяет следить за выдачей запросов SQL отдельными &#xab;приложениями&#xbb;, &#xab;модулями&#xbb; в составе приложений и &#xab;действиями&#xbb; в рамках модулей. Пример:

DBMS_MONITOR.SERV_MOD_ACT_TRACE_ENABLE ( service_name => 'prima.class' , module_name => 'SQL*Plus' , action_name => DBMS_MONITOR.ALL_ACTIONS

, waits => TRUE , binds => TRUE , instance_name => NULL ); /* * ... выжидаем, пока длится работа

*/ DBMS_MONITOR.SERV_MOD_ACT_TRACE_DISABLE ( service_name => 'prima.class' , module_name => 'SQL*Plus' , action_name => DBMS_MONITOR.ALL_ACTIONS , instance name => NULL );

Для параметра MODULE_NAME по аналогии можно указать ALL_MODULES.

Таким же образом включается трассирование запросов, поступающих из текущего или чужого сеансов (SESSION_TRACE_ENABLE/DISABLE), сеансов, помеченных CLIENT_IDENTIFIER (CLIENT_ID_TRACE_ENABLE/DISABLE) и из всех сеансов подряд, или же порождаемых определенным экземпляром СУБД, например, из конфигурации RAC (DATABASE_TRACE_ENABLE/DISABLE).

Просмотр профиля выдачи запросов SQL выполняется с помощью программ tkprof и trcsess. Последняя программа стала поставляться в ПО версии Oracle 10 и как раз таки и выбирает из трассировочных файлов разных процессов данные, относящиеся к указанным единицам слежения. Вот пример, как может выглядеть совместное использование этих программ:

>trcsess output=out.txt module=SQL*Plus c:\oracle\admin\prima\udump\* >tkprof out.txt final.txt

Упражнение. Включить трассирование всех запросов SQL, поступающих от сеансов SQL*Plus, и наблюдать накопление профиля выдачи запросов. Отключить трассирование.



Пример сбора статистики о запросах SQL в приложении


Для сеансов, помеченных CLIENT_IDENTIFIER, и для приложений, помеченных комбинациями SERVICE_NAME &#x2013; MODULE &#x2013; ACTION, пакет DBMS_MONITOR позволяет собирать не только профиль выдачи запросов SQL, но и статистику затрат СУБД на обработку запросов.

Пример со сбором статистики о работе определенных клиентов. Выдадим:

CONNECT / as sysdba EXECUTE DBMS_MONITOR.CLIENT_ID_STAT_ENABLE ( 'Web client' ) HOST sqlplus scott/tiger EXECUTE DBMS_SESSION.SET_IDENTIFIER ( 'Web client' ) SELECT COUNT ( * ) FROM dual; EXIT

Здесь с помощью команды HOST программы SQL*Plus был осуществлен короткий запуск сеанса, помеченного значением CLIENT_IDENTIFIER = 'Web client'.

Продолжим в изначальном сеансе от имени SYS:

SYS> SELECT aggregation_type, primary_id 2> FROM dba_enabled_aggregations;

AGGREGATION_TYPE PRIMARY_ID ------------------------------------------------------------------------ CLIENT_ID Web client

SYS> COLUMN client_identifier FORMAT A20 SYS> COLUMN stat_name FORMAT A35 SYS> COLUMN value FORMAT 99999999 SYS> SELECT client_identifier, stat_name, value 2> FROM v$client_stats

3> ;

CLIENT_IDENTIFIER STAT_NAME VALUE ----------------------------------------------------------------------- Web client user calls 4 Web client DB time 3807 Web client DB CPU 3807 Web client parse count (total) 2 Web client parse time elapsed 421 Web client execute count 4 Web client sql execute elapsed time 1940 Web client opened cursors cumulative 2 ...

SYS> EXECUTE DBMS_MONITOR.CLIENT_ID_STAT_DISABLE ( 'Web client' )

PL/SQL procedure successfully completed.

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

DBA_ENABLED_AGGREGATIONS

V$CLIENT_STATS

V$SERVICE_STATS

V$SERV_MOD_ACT_STATS

V$SERVICEMETRIC

V$SERVICEMETRIC_HISTORY

Последние две дают подробную информацию о расходовании процессорного времени.

Это был пример с использованием процедур CLIENT_ID_STAT_ENABLE/DISABLE. Процедуры SERV_MOD_ACT_STAT_ENABLE/DISABLE используются аналогично.

Упражнение. Включить сбор обобщенной статистики выполнения запросов SQL всех соединений к СУБД по SQL*Plus и наблюдать ее накопление. Отключить сбор статистики.



Пример установки и просмотра CLIENT_IDENTIFIER


Значение CLIENT_IDENTIFIER выставляется программно с помощью пакета DBMS_SESSION или же вызовом OCI setClientIdentifier (только JDBC в версии 9). Пример:

SQL> @userenv CLIENT_IDENTIFIER

CLIENT IDENTIFIER ------------------------------------------------------------------------

SQL> EXECUTE DBMS_SESSION.SET_IDENTIFIER ( 'Web client' )

PL/SQL procedure successfully completed.

/ CLIENT_IDENTIFIER ------------------------------------------------------------------------ Web client



Пример установки и просмотра MODULE и ACTION


Значения MODULE и ACTION устанавливаются в программе с помощью пакета DBMS_APPLICATION_INFO. Пример:

SQL> EXECUTE DBMS_APPLICATION_INFO.SET_MODULE &#x2013; 2( 'some module', 'some action' )

PL/SQL procedure successfully completed.

SQL> @userenv MODULE

MODULE ------------------------------------------------------------------------ some module

SQL> @userenv ACTION

ACTION ------------------------------------------------------------------------ some action

SQL> EXECUTE DBMS_APPLICATION_INFO.SET_ACTION ( '' )

PL/SQL procedure successfully completed.

SCOTT> /

ACTION ------------------------------------------------------------------------

SQL>



Пример установки и просмотра SERVICE_NAME


Значение SERVICE_NAME выставляется автоматически при установлении соединения, а управление службами происходит посредством пакета DBMS_SERVICE.CREATE_SERVICE.

Простые примеры создания, включения, подключения, отключения и удаления:

EXECUTE DBMS_SERVICE.CREATE_SERVICE ( 'secunda', 'secunda.class' ) EXECUTE DBMS_SERVICE.START_SERVICE ( 'secunda' ) CONNECT /@localhost:1521/ secunda.class AS SYSDBA EXECUTE DBMS_SERVICE.STOP_SERVICE ( 'secunda' ) EXECUTE DBMS_SERVICE.DELETE_SERVICE ( 'secunda' )

Еще один способ создания служб &#x2013; через командный интерфейс с помощью программы srvctl.

Возможные значения SERVICE_NAME указываются в сетевых установках Oracle и регистрируются в качестве службы БД процессом listener. Значения зарегистрированных в рамках БД служб можно наблюдать в таблице V$SERVICES. Помимо сконфигурированных, всегда имеются две внутренние службы: SYS$BACKGROUND используется внутренними процессами СУБД, а к SYS$USERS причисляются соединения пользователей, не указавших желаемую им службу.

Пример просмотра:

SQL> CONNECT scott/tiger@prima.class

Connected. SQL> @userenv SERVICE_NAME

SERVICE_NAME ------------------------------------------------------------------------ prima.class

Упражнение. Просмотреть список служб, зарегистрированных для рабочей БД. Объяснить наличие служб SYS$USERS и SYS$BACKGROUND.



Установка единиц слежения и просмотр существующих значений


Упомянутые значения единиц слежения за работой приложения наблюдаемы в программе, либо из полей таблиц словаря-справочника, либо из встроенного контекста сеанса с именем USERENV:

Значение Поле таблиц Параметр контекста USERENV сеанса
SERVICE_NAME V$SESSION, V$SERVICES SERVICE_NAME
MODULE V$SESSION MODULE
ACTION V$SESSION ACTION
CLIENT_IDENTIFIER V$SESSION CLIENT_IDENTIFIER
SID V$SESSION SID
SERIAL# V$SESSION
INSTANCE_NAME V$INSTANCE INSTANCE_NAME

Например, все значения, кроме SERIAL#, наблюдаемы в соответствующих параметрах контекста USERENV сеанса связи клиентской программы с СУБД.

Для дальнейшего просмотра этих параметров в SQL*Plus удобно подготовить файл с параметризованным обращением к контексту USERENV посредством системной функции SYS_CONTEXT:

SELECT SYS_CONTEXT ( 'userenv', '&1' ) AS &1 FROM dual SAVE userenv REPLACE SET VERIFY OFF



За чем следить и чем управлять при работе приложений с Oracle


,
преподаватель технологий Oracle



... Паллада Афина,

За руку взявши, воскликнула к бурному богу
Ярею: "... не бросим ли мы и Троян и ахеян

Спорить одних, да Кронид промыслитель их славу присудит ?... "

Гомер, Илиада

Одним из бедствий времени был застарелый

произвол доносчиков и их подстрекателей...

Светоний, Жизнь Двенадцати Цезарей:
Божественный Тит



Допущения


Речь не идет о разработке генератора отчетов Говоря об экспорте данных, имеется в виду именно экспорт данных, а не создание готового отчета с заголовками, формулами, красивым форматированием. Не нужно думать о пользователях плохо. Получив "сырые" данные в Excel они смогут в течении пары минут "раскрасить" их до готового отчета. Передача данных обратно из Excel в Forms - не требуется В противном случае непонятно зачем же нам нужен Forms, если данные могут корректироваться в Excel. Forms становится просто не нужной прослойкой между БД и Excel.

Но, как говорится, лучше один раз увидеть. Так вот, нам нужна утилита которая позволяет, имея вот это:

В результате получить вот это:



Oracle Forms. Экспорт данных в Excel


Павел Лузанов Источник: сайт П.Лузанова, 01 Ноября 2003г.,

http://www.geocities.com/luzanovp/frm2xls.html,
,Январь/Февраль 2004

Как и следует из названия, речь пойдёт о создании утилиты для экспорта данных из приложений Oracle Forms в MS Excel.

Введение или Почему пользователя так любят Excel

Постановка задачи. Требования и допущения Технические аспекты решения Решение Приложение



Постановка задачи. Требования и допущения


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



Здесь описаны некоторые советы по


Здесь описаны некоторые советы по возможному дальнейшему совершенствованию предложенного механизма экспорта данных в Excel.
Вместо предложенного формата файла, можно попробовать использовать sylk формат, описание которого можно найти в Интернет. Excel также понимает этот формат, предоставляющий большие возможности по форматированию данных.
В качестве первой строки в файл можно записывать заголовок окна формы. Получится своеобразный заголовок отчета в Excel.
Блок формы может содержать десятки, сотни тысяч записей, а то и больше. В таких случаях выгружать все записи блока - задача не реальная. Для таких случаев можно реализовать ограничитель на количество выгружаемых записей. Т.е. условием окончания записи в файл сделать не только достижение последней записи в блоке, но и дополнительно к этому, достижение максимально возможного количества записей для выгрузки, если таковое установлено.
В качестве значения ячейки можно передавать формулы. Для этого значение должно начинаться с символа "=". В формулах можно использовать арифметические операции и ссылки на ячейки, например вот так =A2+B2+C2. Но почему-то не получается использовать в формулах функции, например =СУММ(A2:A10). В приведенной реализации использование формул запрещено. Если значение начинается с символа "=", то оно дополняется пробелом слева.

у вас есть уже всё,


В принципе, у вас есть уже всё, что необходимо для реализации задуманного. И написать подобную утилиту вы наверняка сможете сами. И возможно даже лучше чем то, что предложено ниже.
Собственно решением является plsql пакет fp_exp с единственной public процедурой to_excel, которая вызывается без всяких параметров, например, в пункте меню "Экспорт в Excel": BEGIN fp_exp.to_excel; END;
Перед началом использования нужно выполнить предварительную настройку пакета, установив значения константам, находящимся в спецификации пакета.
Утилита изначально разрабатывалась для использования с Forms Server, работающим под Linux. Затем была перенесена в среду Oracle Applications (та же архитектура, но под SUN Solaris). Поэтому приводится вариант пакета для web реализации. Для работы в клиент-сервер, тело процедуры to_excel нужно лишь чуть-чуть подправить, что не должно вызвать у вас затруднений. PACKAGE fp_exp /* -- fp_exp 15-JUL-2003 by pal (Pavel Luzanovv) -- НАЗНАЧЕНИЕ -- Экспорт данных текущего блока в MS Excell -- МОДИФИКАЦИЯ -- 15-JUL-2003 pal Создание */ IS -- На что заменять символ табуляции в
значении элемента формы chr9subst_pc CONSTANT VARCHAR2(3) := ' ';
-- На что заменять символ перевода строки
в значении элемента формы chr10subst_pc CONSTANT VARCHAR2(1) := ' ';
-- В каком формате выводить элементы типа даты date_format_pc CONSTANT VARCHAR2(8) := 'DD.MM.YY';
-- каталог на сервере приложения, где
будут создаваться файлы экспорта tempdir_pc CONSTANT VARCHAR2(255) := '/oraapp/tempdata/';
-- относительный путь до cgi-скрипта,
для использования -- в WEB.SHOW_DOCUMENT cgi_script_pc CONSTANT VARCHAR2(255) :=
'/cgi-bin/exp2xls';
PROCEDURE to_excel; END fp_exp;
PACKAGE BODY fp_exp IS TYPE item_rectype IS RECORD ( name VARCHAR2(30), prompt VARCHAR2(255), datatype VARCHAR2(255) ); TYPE item_tabtype IS TABLE OF item_rectype
INDEX BY BINARY_INTEGER; item_tab item_tabtype;
/* --------- LOCAL MODULES --------- */ PROCEDURE show_progress (message_in IN VARCHAR2) IS BEGIN MESSAGE(message_in, NO_ACKNOWLEDGE); SYNCHRONIZE; END show_progress;


FUNCTION tempfilename RETURN VARCHAR2 IS BEGIN RETURN (USERDBMS_RANDOM.STRING('U', 10)); END tempfilename;
FUNCTION setup_items (blk_in IN VARCHAR2)
RETURN PLS_INTEGER /* Определим находящиеся на экране
столбцы текущего блока */ IS cur_item_v VARCHAR2(30); last_item_v VARCHAR2(30); index_v PLS_INTEGER := 0; BEGIN cur_item_v := GET_BLOCK_PROPERTY (blk_in, FIRST_ITEM); last_item_v := GET_BLOCK_PROPERTY (blk_in, LAST_ITEM); item_tab.DELETE;
LOOP IF GET_ITEM_PROPERTY (blk_in'.'
cur_item_v, DISPLAYED) = 'TRUE' AND GET_ITEM_PROPERTY (blk_in'.'
cur_item_v, VISIBLE) = 'TRUE' AND GET_ITEM_PROPERTY (blk_in'.'
cur_item_v, ITEM_CANVAS) IS NOT NULL AND GET_ITEM_PROPERTY (blk_in'.'
cur_item_v, ITEM_TYPE) IN ('TEXT ITEM', 'LIST',
'DISPLAY ITEM','CHECKBOX', 'RADIO GROUP') THEN index_v := index_v + 1; item_tab(index_v).name :=
GET_ITEM_PROPERTY(blk_in'.'cur_item_v, ITEM_NAME); IF GET_ITEM_PROPERTY (blk_in
'.'cur_item_v, ITEM_TYPE) = 'CHECKBOX' THEN item_tab(index_v).prompt :=
GET_ITEM_PROPERTY(blk_in'.'cur_item_v, LABEL); ELSE item_tab(index_v).prompt :=
GET_ITEM_PROPERTY(blk_in'.'cur_item_v, PROMPT_TEXT); END IF; item_tab(index_v).datatype := GET_ITEM_PROPERTY(blk_in'.'
cur_item_v, DATATYPE); END IF;
EXIT WHEN cur_item_v = last_item_v; cur_item_v := GET_ITEM_PROPERTY (blk_in
'.'cur_item_v, NEXTITEM); END LOOP;
RETURN (index_v); END setup_items;
FUNCTION format_value ( value_in IN VARCHAR2, datatype_in IN VARCHAR2 DEFAULT NULL ) RETURN VARCHAR2 IS retval VARCHAR2(32767) := value_in; BEGIN IF datatype_in = 'DATE' THEN retval := TO_CHAR(TO_DATE(retval),
date_format_pc); ELSE retval := REPLACE (retval, CHR(9),
chr9subst_pc); retval := REPLACE (retval, CHR(10),
chr10subst_pc); IF SUBSTR(retval, 1, 1) IN
('-', '+', '=') THEN retval := ' ' retval; END IF; END IF;
RETURN (retval); END format_value; /* ---------- PUBLIC MODULES ----------- */ PROCEDURE to_excel IS cur_block_c CONSTANT VARCHAR2(30)
:= NAME_IN ('SYSTEM.CURSOR_BLOCK'); cur_item_c CONSTANT VARCHAR2(61)
:= NAME_IN ('SYSTEM.CURSOR_ITEM'); cur_record_c CONSTANT VARCHAR2(30)
:= NAME_IN ('SYSTEM.CURSOR_RECORD'); outfilename_v VARCHAR2(255); -- := 'c:\tmp\'USER'.xls';
-- test in C-S mode outfile_v TEXT_IO.FILE_TYPE; num_items_v PLS_INTEGER; line_v VARCHAR2(32767); counter_v PLS_INTEGER := 0;


uncommited_changes EXCEPTION; BEGIN IF NAME_IN('SYSTEM.FORM_STATUS') <> 'QUERY' THEN RAISE uncommited_changes; END IF;
FIRST_RECORD;
show_progress ('Формирование
списка полей для экспорта...'); num_items_v := setup_items
(cur_block_c);
show_progress ('Открытие временного файла...'); -- for C- S mode make adjustments below outfilename_v := tempfilename; outfile_v := TEXT_IO.FOPEN
(tempdir_pcoutfilename_v, 'w');
show_progress ('Формирование
строки заголовков столбцов...'); line_v := NULL; FOR i IN 1 .. num_items_v LOOP line_v := line_v format_value
(item_tab(i).prompt); IF i < num_items_v THEN line_v
:= line_v CHR(9); END IF; END LOOP; TEXT_IO.PUT_LINE(outfile_v, line_v);
LOOP IF MOD (counter_v, 100) = 0 THEN show_progress ( 'Форматирование записей блока
(' TO_CHAR(counter_v,'99990') ')...' ); END IF;
counter_v := counter_v + 1; line_v := NULL; FOR i IN 1 .. num_items_v LOOP line_v := line_v format_value ( NAME_IN (cur_block_c'.
'item_tab(i).name), item_tab(i).datatype ); IF i < num_items_v THEN line_v
:= line_v CHR(9); END IF; END LOOP; TEXT_IO.PUT_LINE(outfile_v, line_v);
EXIT WHEN NAME_IN('SYSTEM.LAST_RECORD')
= 'TRUE'; NEXT_RECORD; END LOOP; TEXT_IO.FCLOSE(outfile_v); item_tab.DELETE; GO_RECORD(TO_NUMBER(cur_record_c)); GO_ITEM (cur_item_c);
show_progress ( 'Всего экспортировано '
TO_CHAR(counter_v) ' записей' ); WEB.SHOW_DOCUMENT(cgi_script_pc
'?' outfilename_v, '_blank'); --host('cmd /C start excel '
outfilename_v); -- test in C-S mode EXCEPTION WHEN uncommited_changes THEN message('Перед выполнеием экспорта,
сохраните сделанные изменения!'); WHEN OTHERS THEN IF TEXT_IO.IS_OPEN(outfile_v)
THEN TEXT_IO.FCLOSE(outfile_v); END IF; RAISE; END to_excel; END fp_exp;
Не забудьте про cgi скрипт!

Технические аспекты решения


Итак, теперь нужно выбрать конкретный вариант реализации.

Существует два базовых подхода к созданию файлов Excel из внешних приложений.

Первый подразумевает использование "родных" технологий Microsoft (DDE, OLE, VBX, ActiveX, ...). И Forms6i позволяет их использовать, но этот вариант не годится из-за требования совместимости с последующими версиями Forms. А, как известно, в версии 9i пакетов VBX, OLE2, DDE больше нет.

Второй подход заключается в том, чтобы создать файл в формате, который MS Excel сможет прочитать. Такими форматами являются: html, csv|tsv (comma|tab separated values).

Вот этим то мы и воспользуемся! Из предложенных форматов выбираем tsv (html требует больше места, а "," может встречаться в данных).

Экспортировать будем данные текущего блока формы (в котором находится курсор). Именно это позволит сделать утилиту универсальной. Ведь всегда можно программно определить первый, последний, следующий, предыдущий элементы текущего блока и перемещаться по записям вне зависимости от того, в каком блоке, какой формы, какого приложения Oracle Forms мы находимся!

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

тип элемента один из TEXT ITEM, DISPLAY ITEM, LIST, CHECKBOX, RADIO GROUP элемент отображается на какой-либо канве и имеет свойства VISIBLE, DISPLAYED установленными в TRUE

В качестве первой строки экспортного файла было бы не плохо выгружать заголовки (PROMPT) элементов, тогда пользователь сможет визуально оценить какому элементу в блоке формы, соответствует какой столбец в таблице Excel. При таком подходе, это не всегда очевидное соответствие, ведь элементы могут располагаться даже в разных окнах одной формы.

Если вы используете общее меню для всего приложения, то для вызова утилиты удобнее всего создать специальный пункт меню "Экспорт в Excel". При желании его можно вывести как иконку в панель инструментов (Visible In Horizontal Menu Toolbar = Yes).

Для формирования файлов будем пользоваться пакетом TEXT_IO. Сами файлы должны иметь расширение xls для того, чтобы их можно было сразу открыть: host('cmd /C start excel filename.xls');


Но это будет работать только для клиент-серверной эксплуатации. Как же быть с Forms Server, да и еще скажем на Solaris? Ведь TEXT_IO будет писать на сервер приложения, а не на клиентскую машину.

Здесь, опять же, возможны варианты.

Счастливые обладатели Forms9i могут попробовать новую утилиту WebUtil для доступа к клиентскому компьютеру. Хотя ни что не мешает вам написать, что-то подобное на Java и в версии Forms6i.

А можно пойти и другим путем.

Будем исходить из того, что раз мы используем Forms Server, значит здесь же имеется работающий http сервер (Apache). Мы можем написать небольшой cgi скрипт, который будет брать выгружаемый из Forms файл и выдавать его содержимое пользователю, предварительно указав, что это файл в формате Excel: ----- Start script -------------------------------------- #!/bin/sh echo Content-type: application/vnd.ms-excel echo cat /tempdata/$1 rm -f /tempdata/$1 ----- End script -------------------------

Если назвать такой скрипт exp2xls и положить в каталог $ORACLE_HOME/Apache/Apache/cgi-bin , то вызывать его из Forms можно примерно так: WEB.SHOW_DOCUMENT('/cgi-bin/exp2xls' '?'
filename.xls, '_blank');

Те, кто используют Windows в качестве OS для сервера приложений, думаю, без труда смогут переписать этот скрипт в bat или cmd файл. Нужно лишь заменить cat на type и rm -f на del

Имя экспортного файла в такой многопользовательской среде лучше генерировать произвольно. Для этого можно воспользоваться генератором случайных чисел.

Остается лишь позаботиться, чтобы Forms и cgi-скрипт "договорились" куда (каталог) один будет выкладывать файл и откуда второй его будет брать.

А пользовательский браузер понимает, что для показа такой страницы нужно запускать Excel.

Нужно заметить, что у данного способа есть один недостаток. Ведь теоретически, для того, чтобы получить в браузере excel-файл, и, соответственно, получить доступ к данным, необязательно заходить в систему. Можно просто открыть браузер и пытаться подбирать url таким образом, чтобы угадать имя временного файла. Это, конечно, маловероятно, но, тем не менее, стоит подумать, допустим ли для вас такой вариант.


Требования


Совместимость с последующими версиями Forms Разработка велась для версии 6i, но с "прицелом" на возможный переход на 9i(10G) Независимость от платформы и способа эксплуатации Утилита должна работать как для режима клиент-сервер, так и для эксплуатации через Forms Server, причем не важно на какой платформе: будь то Windows или *nux Единственное условие, это наличие на клиентском компьютере MS Excel (а иначе куда экспортировать? :-) Универсальность Утилита должна работать для всех существующих форм и для всех форм, которые будут написаны в будущем



Введение или Почему пользователя так любят Excel


Ну почему пользователи так любят Excel!

Почему, после того как им установили новую систему (разработанную собственными силами или купленную) они непременно хотят получить данные в Excel.

Казалось бы, в системе предусмотрено множество отчетных форм (причем по количеству и внешнему виду согласованные с этими же пользователями!), но они всё равно "хотят Excel". Им, видите ли, так привычнее. Издеваются, не иначе :-)

Однако попробуем разобраться, для чего же вот этот Excel может быть действительно полезен.

В любой системе есть потребность в так называемых нерегламентированных отчетах. Т.е. в таких отчетах, которые заранее не предусмотрены в системе в связи с тем, что требования к ним заранее невозможно формализовать (какие данные требуются, в каком виде, в какое время...)

Каким образом, пользователь может сформулировать, какие данные ему нужны? Да во многих случаях, он просто будет исходить из того, как он видит эти данные в экранных формах. Учитывая, что экранные формы Oracle Forms, как правило, позволяют выполнять нерегламентированные запросы (режим ввода запроса), а экранных форм в серьёзной системе не мало, то получается, что данные, которые пользователь может получить в экранных формах, представляют собой солидный источник данных для нерегламентированных отчетов.

Исходя из этого, экспорт данных из экранной формы в Excel выглядит весьма перспективно. Ведь чтобы не говорили противники Microsoft, Excel, действительно, весьма удобный продукт для обработки табличных данных.

Это особенно актуально для такой системы как Oracle Applications (Oracle E-Business Suite), где подавляющее большинство экранных форм используют однозаписные блоки данных, где в каждый момент времени на экране находится только одна запись, а работа с формой начинается с ввода и выполнения запроса. Желание посмотреть на все отобранные записи в виде списка - весьма законное!

Еще одной распространенной задачей, которую мог бы решить экспорт данных в Excel, является печать справочных данных (справочников). В любой системе существует большое количество различного рода справочников. Экранные формы для ввода в них данных - всегда присутствуют (иначе, как данные будут попадать в систему), а вот про печать справочных данных частенько забывают.

Итак, возможность экспорта данных из экранных форм в Excel, не такая уж и плохая идея. Она позволяет решить проблемы с выводом на печать справочников, с просмотром в виде списка данных из однозаписных блоков Oracle Forms и в какой-то степени помогла бы решить проблему с нерегламентированными отчетами.

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



Некоторые методы разрешения проблем мутации


Полтавский П. В., разработчик ПО, компания

Постановка задачи

В процессе эксплуатации автоматизированной системы учета операций с ценными бумагами возникла необходимость создания таблицы для хранения детальной информации (истории сделок). Для её заполнения был разработан механизм, реализуемый набором триггеров БД и серверных пакетных процедур, вызываемых из триггеров. Но при тестировании данного механизма возникла следующая ситуация: при срабатывании триггера вызываемая им серверная процедура запрашивает данные из таблицы, на которой создан триггер. В результате возникает ошибка мутации:

ORA-04091: table <имя_таблицы> is mutating, trigger/function may not see it

(описание этого явления выходит за рамки данной статьи). Для решения проблемы были использованы следующие методы:

использование триггеров различных типов (FOR EACH ROW и STATEMENT) и глобальных переменных пакета; применение заданий серверных процессов (JOBs).

Использование триггеров различных типов и глобальных переменных пакета

При вставке или изменении записи из таблицы документов выполняется процедура инициализации глобальной переменной пакета значением столбца внутреннего идентификатора записи сделки. Это происходит при срабатывании следующего триггера (FOR EACH ROW trigger):

CREATE OR REPLACE TRIGGER DB_DOC_T1 after insert or update on DB_DOC FOR EACH ROW

begin if (inserting or updating) then DP_STNDS.Set_Id_Deal(:new.id_deal); else DP_STNDS.Set_Id_Deal(:old.id_deal); end if; end;

Пакетная процедура DP_STNDS.Set_Id_Deal и функция DP_STNDS.Get_Id_Deal чтения инициализированного значения пакетной переменной определяются следующим образом: … PROCEDURE Set_Id_deal(p_id_deal in db_doc.id_deal%type) is begin dp_id_deal:= p_id_deal; end;

FUNCTION Get_Id_Deal_No RETURN dp_id_deal db_doc.id_deal%type is begin return dp_id_deal ; end; …

Переменная dp_id_deal является глобальной переменной пакета DP_STNDS и объявляется в теле пакета:

CREATE OR REPLACE PACKAGE BODY DP_STNDS AS -- Переменные пакета dp_id_deal db_doc.id_deal%type; … END PACKAGE BODY DP_STNDS;


После инициализации этой переменной происходит выполнение процедуры генерации истории сделки. Это обеспечивается срабатыванием следующего триггера (STATEMENT trigger):

CREATE OR REPLACE TRIGGER DB_DOC_T2 after insert or update on DB_DOC declare v_id_deal DB_DOC.id_deal%type; begin v_id_deal:=DP_STNDS.Get_Id_Deal; -- Генерация истории сделки DC_DEAL_HIST.Gen_Deal_Hist(v_id_deal); end;

Процедура DC_DEAL_HIST.Gen_Deal_Hist обращается к таблице документов DB_DOC, но эффекта мутации не возникает, т. к. триггер DB_DOC_T2 является триггером типа STATEMENT (операторный).



ОГРАНИЧЕНИЯ.

Операторы DML, изменяющие записи таблицы (UPDATE), должны применяться к одной, а не к нескольким записям. Например, вместо оператора

UPDATE DB_DOC SET amount = 1000 WHERE id_doc in (1,2);

необходимо использовать операторы

UPDATE DB_DOC SET amount = 1000 WHERE id_doc = 1; UPDATE DB_DOC SET amount = 1000 WHERE id_doc = 2;

В первом случае триггер DB_DOC_T2 срабатывает только один раз, во втором – для каждой обновляемой записи (соответственно, для второго случая расчет истории сделки происходит также для каждой обновляемой записи, в отличие от первого случая).

Применение заданий серверных процессов (JOBs)

Рассмотрим альтернативный вариант механизма для генерации истории сделок. Триггер DB_DOC_T1 будет выглядеть следующим образом:

CREATE OR REPLACE TRIGGER DB_DOC_T1 after insert or update on DB_DOC FOR EACH ROW begin DC_DEAL_HIST_SUPP.Create_DP_Job_Deals_Rec(:new.id_deal); end;

Процедура DC_DEAL_HIST_SUPP.Create_DP_Job_Deals_Rec формирует в регистрационной таблице DP_JOB_DEALS запись, содержащую значение внутреннего идентификатора записи сделки - :new.id_deal. Таким образом, данная таблице постоянно пополняется актуальной информацией, необходимой для генерации истории сделок. Для выполнения процедуры генерации истории сделки DC_DEAL_HIST.Gen_Deal используется задание серверного процесса – job. Ниже приводится PL/SQL блок определения задания:

VARIABLE jobno number; begin DBMS_JOB.SUBMIT(:jobno, ‘dc_deal_hist_supp.gen_deals_by_job;', SYSDATE,'SYSDATE + 1/86400'); commit; end; /

Задание, выполняясь периодически с интервалом в 1 сек, запускает пакетную процедуру DC_DEAL_HIST_SUPP.GEN_DEALS_BY_JOB, которая последовательно обрабатывает все записи регистрационной таблицы DP_JOB_DEALS и выполняет генерацию истории для каждой сделки (процедура DC_DEAL_HIST.Gen_Deal). По завершении работы процедура DC_DEAL_HIST_SUPP.GEN_DEALS_BY_JOB очищает регистрационную таблицу.

Результаты

Данная технология была неоднократно реализована в нескольких промышленных системах. Использование приемов, описанных в данной статъе, позволяет:

активно использовать базовые триггера при проектировании серверной части приложений; упростить программный код на уровне базовых триггеров; централизовать логику приложения на уровне серверной части.


Люди у проходной


И посетители, и сотрудники проходят через проходную, где отмечаются в таблице CHECKPOINT:

CREATE TYPE checkpoint_typ AS OBJECT ( entrytime DATE , person person_typ ) /

CREATE TABLE checkpoint OF checkpoint_typ;

Вот как они могут "проходить":

INSERT INTO checkpoint VALUES ( SYSDATE , employee_typ ( 'Scott', 1111 ) );

INSERT INTO checkpoint VALUES ( SYSDATE , visitor_typ ( 'Adams', 333 ) );

INSERT INTO checkpoint VALUES ( SYSDATE , doctor_typ ( 'Smith', 2222, 'Therapeutist', '7778899' ) );

INSERT INTO checkpoint VALUES ( SYSDATE , stuff_typ ( 'Alice', 4444, 'Office-cleaner' ) );

Можно проверить, кто прошел:

SELECT * FROM checkpoint;

Обратите внимание на использованное упрощение: никто не запретил пройти через проходную просто сотруднику (Scott), то есть не врачу и не обслуживающему персоналу. Желание запретить такого рода вставки в таблицу во многих случаях возникает вполне законно. Для запрета следовало было описать тип EMPLOYEE_TYP (а заодно и PERSON_TYP) как "абстрактный" (это термин объектно-ориентированного подхода):

CREATE TYPE employee_typ UNDER person_typ ( empid NUMBER ) NOT FINAL NOT INSTANTIABLE /



Наследование типов объектов в Oracle



,

Если бы мне принадлежал весь этот край, я бы
отдал его за то, чтобы сама королева Англии лежала в моих объятиях.

Carmina Burana, если верить Карлу Орфу



Плата за свободный проход или эволюция типов


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

Выше упоминалась содержательно некорректная возможность добавить в таблицу CHECKPOINT сотрудника Scott, для которого не указано, то ли он доктор, то ли принадлежит обслуживающему персоналу. Говорилось, как можно исправить ситуацию, придав типу EMPLOYEE_TYP свойство NOT INSTANTIABLE. Если мы захотим сделать это сейчас, то у нас ничего не выйдет:

SQL> ALTER TYPE employee_typ NOT INSTANTIABLE; ALTER TYPE employee_typ NOT INSTANTIABLE * ERROR at line 1: ORA-22327: cannot change a type to NOT INSTANTIABLE if
it has dependent tables

К сожалению дело не в том, что в таблице CHECKPOINT отмечен сотрудник, противоречащий изменению свойства типа EMPLOYEE_TYP. Сообщение об ошибке говорит, что изменение свойства типа INSTANTIABLE на NOT INSTANTIABLE возможно лишь при отсутствии таблиц с полями этого типа. Увы, но нам придется пожертвовать таблицей:

DROP TABLE CHECKPOINT;

ALTER TYPE employee_typ NOT INSTANTIABLE CASCADE;

Кроме того, поскольку у EMPLOYEE_TYP есть подтипы, потребовалось указать слово CASCADE, чтобы изменения распространились на них (можете проверить, что указание CASCADE не спасет ситуацию с ошибкой ORA-22327 выше).

К счастью обратное изменение свойства типа, с NOT INSTANTIABLE на INSTANTIABLE, не потребует никаких жертв и срабатывает всегда.



Просмотр входивших


Пример просмотра (SELECT * …) уже приводился. Однако для получения данных в табличном виде запрос придется усложнить. Проблема, которую придется учитывать в таком запросе - наличие в поле PERSON разных строк одной и той же таблицы значений разных типов. Бороться с проблемой позволяет специальное расширение SQL:

SELECT TREAT (person AS person_typ).name FROM checkpoint;

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

Обратите внимание, что столбец для выдачи можно формировать, трактуя поле PERSON отправной таблицы и как, например, EMPLOYEE_TYP, несмотря на то, что в CHECKPOINT имеются и строки другого типа:

SELECT TREAT (person AS employee_typ).empid FROM checkpoint;

(Действительно, строки выводятся даже для персон, не являющихся сотрудниками, вследствие чего не имеющих табельного номера EMPID).

Отбор входивших персон конкретного типа можно сформулировать во фразе WHERE, для которой придуманы другие специальные конструкции:

SELECT * FROM checkpoint WHERE person IS OF (employee_typ);

Обратите внимание, что выдались строки с данными типа EMPLOYEE_TYP и всех его подтипов. Если же строки с данными подтипов выдавать не нужно, можно указать следующее:

SELECT * FROM checkpoint WHERE person IS OF (ONLY employee_typ);

Вот как можно объединить два приведенные выше варианта отбора - строк по типам во фразе WHERE и столбцов с атрибутами, возможно других, типов:

SELECT SYSDATE, c.person.name, TREAT (c.person AS doctor_typ).phone FROM checkpoint c WHERE person IS OF (employee_typ);

Обратите внимание, что по синтаксису, принятом в Oracle написать во фразе SELECT выдачу C.PERSON.PHONE, наподобие C.PERSON.NAME, не получится. Не получится даже написать SELECT c.person.empid …, несмотря на то, что фразой WHERE отбираются сотрудники, которые имеют табельный номер EMPID. Для обращения к свойствам подтипов вам все равно придется написать SELECT TREAT (c.person AS doctor_typ).empid …, указав явно подтип, где возникло это свойство. Для свойства NAME приведения типа не требуется, так как оно задано для ("основного") типа столбца PERSONS, то есть PERSONS_TYP, а не для подтипов PERSONS_TYP.



Типы в поликлинике


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

CREATE TYPE person_typ AS OBJECT ( name VARCHAR2(30) ) NOT FINAL /

CREATE TYPE employee_typ UNDER person_typ ( empid NUMBER ) NOT FINAL /

CREATE TYPE visitor_typ UNDER person_typ ( regid NUMBER ) NOT FINAL /

Если бы фраза NOT FINAL в определении PERSON_TYP отсутствовала, не удалось бы создать подтипы EMPLOYEE_TYP и VISITOR_TYP. У подтипов же эта фраза оставлена на всякий случай, который сейчас представится. Например, пусть нужно среди сотрудников отличать врачей от обслуживающего персонала:

CREATE TYPE doctor_typ UNDER employee_typ ( speciality VARCHAR2(30) , phone VARCHAR2(7) ) /

CREATE TYPE stuff_typ UNDER employee_typ ( job VARCHAR2(30) ) /

Данные о взаимозависимостях типов можно посмотреть в USER_TYPES:

SELECT supertype_name, type_name FROM user_types ORDER BY 1, 2;



В жизни сложнее


В завершение нужно заметить, что пример в этой статье был сознательно упрощен. Конечно, в жизни вряд ли было бы целесообразно в таблицу CHECKPOINT включать объектное поле, хранящее все моделируемые свойства людей. (Это соответствует и "физике процесса": едва ли требуется сообщать вахтеру специализацию входящего врача и рабочий телефон, достаточно табельного номера). Персоны скорее всего должны бы рассматриваться как самостоятельные объекты, а в таблицу CHECKPOINT следовало бы включить ссылки на них. Но для понимания темы, затронутой в статье, это не существенно.



в конце концов его реализация


Наследование типов объектов - важнейшее свойство объектного подхода. В Oracle оно появилось с опозданием "на 1,2 версии", то есть в версии 9.2, а не сразу в 8.0. Но в конце концов его реализация оказалась достаточно полной. Это единичное (не множественное) наследование, и некоторые подробности его исполнения в Oracle иллюстрируются на примере ниже.

Наследование конструкторов


Очередная трудность связана с тем, что в PL/SQL не поддерживает прямой вызов унаследованного конструктора. (Проще говоря, конструкторы базового типа не наследуются!). Например: пусть у нас есть класс t_ParentType в котором определен пользовательский (user-defined) конструктор:

---------------------------------------------------------------- --спецификация объектного типа t_ParentType: - ---------------------------------------------------------------- create or replace type t_ParentType as object ( v_Field1 varchar2(32), constructor function t_ParentType(v_pName varchar2) return self as result ) not final; ---------------------------------------------------------------- --тело объектного типа t_ParentType: - ---------------------------------------------------------------- create or replace type body t_ParentType as constructor function t_ParentType(v_pName varchar2) return self as result is begin self.v_Field1 := v_pName; return; end; end;

Теперь мы определяем объектный тип t_ChildType, который является наследником t_ParentType. В типе t_ChildType также определен пользовательский конструктор: ---------------------------------------------------------------- --Спецификация объектного типа t_ChildType, - --который является наследником : t_ParentType - ---------------------------------------------------------------- create or replace type t_ChildType under t_ParentType ( v_Field2 varchar2(64), constructor function t_ChildType(v_pName varchar2, v_pDescription varchar2) return self as result );

В реализации конструктора типа t_ChildType попытаемся вызвать унаследованный конструктор:

---------------------------------------------------------------- --Тело объектного типа t_ChildType - --в конструкторе необходимо вызвать унаследованный конструктор - ---------------------------------------------------------------- create or replace type body t_ChildType is constructor function t_ChildType(v_pName varchar2, v_pDescription varchar2) return self as result is begin t_ParentType(v_pName => v_pName); self.v_Field2 := v_pDescription; return; end; end;


Выясняется, что сделать это не удается:

liNE/COL ERROR -------- ----------------------------------------------------------------- 6/5 PLS-00306: wrong number or types of arguments in call to 'T_PARENTTYPE'

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

Предлагается примерно тот же самый метод, что и в предыдущем разделе: создание экземпляра типа-предка, с последующим присвоением его полей полям текущего экземпляра. Для этого нам понадобится метод присвоения assign:

---------------------------------------------------------------- --Спецификация объектного типа t_ChildType, - --который является наследником : t_ParentType - --добавлен метод присваивания assign - ---------------------------------------------------------------- create or replace type t_ChildType under t_ParentType ( v_Field2 varchar2(64), constructor function t_ChildType(v_pName varchar2, v_pDescription varchar2) return self as result, member procedure assign(self in out nocopy t_ChildType, v_pObject in out nocopy t_ParentType), member function getName return varchar2 ); ---------------------------------------------------------------- --Тело объектного типа t_ChildType - --в конструкторе вызывается конструктор базового типа - ---------------------------------------------------------------- create or replace type body t_ChildType is constructor function t_ChildType(v_pName varchar2, v_pDescription varchar2) return self as result is --экземпляр объекта-предка v_xInheritedObject t_ParentType; begin --вызов конструктора базового типа v_xInheritedObject := new t_ParentType(v_pName => v_pName); -передача данных текущему экземпляру self.assign(v_xInheritedObject); - self.v_Field2 := v_pDescription; return; end; -------------------------------------------------------------- --метод присваивания экземпляра базового типа текущему - --оьъекту (self) - -------------------------------------------------------------- member procedure assign(self in out nocopy t_ChildType, v_pObject in out nocopy t_ParentType) is begin self.v_Field1 := v_pObject.v_Field1; end; member function getName return varchar2 is begin return self.v_Field1 ' - ' self.v_Field2; end; end;

Вышеописанная методика демонстрируется в данном примере.


Объектно-ориентированный PL/SQL: проблемы и методы их решения


Игорь Мельников, Oracle 8i/9i Certified Professional DBA, группа компаний TopS Business Integrator


Статья была опубликована в журнале ORACLE MAGAZINE Русское издание



Реализация констант-атрибутов типа


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

create or replace type t_ParentType as object ( v_Name varchar2(50), static v_Const varchar2(32) := 'Scott Tiger' );

Увы, мы получаем ошибку:

ORA-06545: PL/SQL: compilation error - compilation aborted ORA-06550: line 5, column 12: PLS-00103: Encountered the symbol "V_CONST" when expecting one of the following: function procedure

Для реализации таких атрибутов можно использовать статический метод, который бы возвращал требуемое значение. Если значение атрибута также имеет объектный тип, то в качестве места хранения значения такого атрибута можно использовать вспомогательный пакет. Для защиты переменной от модификации необходимо поместить её объявление в тело пакета.

Следующий листинг реализует данный подход:

-------------------------------------------------------------- --Значение данного типа должен иметь атрибут объектного типа - -------------------------------------------------------------- create or replace type t_DictConst as object ( v_Id number(9), v_Name varchar2(50), v_Code varchar2(15), v_Description varchar2(250) ); -------------------------------------------------------------- --Спецификация вспомогательного пакета для типа t_ParentType:- --функция getConst возвращает объект типа t_DictConst - -------------------------------------------------------------- create or replace package serv$ParentType is function getConst return t_DictConst; end; -------------------------------------------------------------- --Тело пакета: объект-константа формируется в процедуре init - -------------------------------------------------------------- create or replace package body serv$ParentType is

v_gDictConst t_DictConst; function getConst return t_DictConst is begin return v_gDictConst; end; procedure init is begin v_gDictConst := new t_DictConst(1,'Scott Tiger', '01','Scott Tiger - Oracle demo-user'); end; begin init; end;

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

-------------------------------------------------------------- --Спецификация объектного типа t_ParentType - --Статический метод возвращает константу - -------------------------------------------------------------- create or replace type t_ParentType as object ( v_Name varchar2(50),

static function getConst return t_DictConst ); create or replace type body t_ParentType is

static function getConst return t_DictConst is begin return serv$ParentType.getConst; end; end;

Вышеописанная методика демонстрируется в данном примере .



Ссылки


Oracle10g Rel.1 (10.1) PL/SQL User's Guide and Reference

Oracle10g Rel.1 (10.1) Application Developer's Guide - Object-Relational Features



и далее Oracle10g Database начали


Многие из нас с появлением Oracle9i Database и далее Oracle10g Database начали активно разрабатывать приложения с помощью объектно-ориентированного PL/SQL. Однако вскоре выяснилось, что корпорация Oracle не полностью реализовала возможности присущие объектно-ориентированным языкам. В результате многие разработчики приложений на Oracle Database "охладели" к объектным возможностям PL/SQL.
В предлагаемой вашему вниманию статье предлагается ряд решений проблем, с которыми сталкиваются разработчики. Я уверен, что Oracle9i PL/SQL позволяет реализовывать развитую объектную модель, и, надеюсь, мое мнение разделит читатель.
Каждый раздел статьи сопровождается исходными текстами скриптов, демонстрирующими сответствующий подход. Все скрипты запускались и проверялись с помощью последней доступной на текущий момент версии Oracle10g Database - 10.1.0.2 Скрипты тестировались на следующей версии: Oracle 10.1.0.2 Enterprise Edition for linux x86 (Intel Xeon)

Вызов переопределенного метода в типе-потомке


С этой проблемой PL/SQL-программисты сталкиваются наиболее часто. Проблема связана с тем, что в PL/SQL отсутствует синтаксическая конструкция для вызова метода типа-предка. В случае порождения нового объектного типа от родительского, виртуальный метод переопределеяется, далее в этом порожденном типе нам необходимо вызвать данный метод типа-предка.

Например: пусть у нас есть класс t_ParentType в котором определен метод getName:

------------------------------------------------------------ --спецификация объектного типа t_ParentType: - ------------------------------------------------------------ create or replace type t_ParentType as object ( v_Field1 varchar2(32), member function getName return varchar2 ) not final; ------------------------------------------------------------ --тело объектного типа t_ParentType: - ------------------------------------------------------------ create or replace type body t_ParentType as member function getName return varchar2 is begin return self.v_Field1; end; end;

Теперь мы определяем объектный тип t_ChildType, который является наследником t_ParentType. В типе t_ChildType метод getName является виртуальным и переопределен. Для этого использовано ключевое слово OVERRIDING: ------------------------------------------------------------ --Спецификация объектного типа t_ChildType, - --который является наследником : t_ParentType - --Внимание: метод getName переопределен - ------------------------------------------------------------ create or replace type t_ChildType under t_ParentType ( v_Field2 varchar2(64),

overriding member function getName return varchar2 ) not final;

В реализации метода getName попытаемся вызвать унаследованный метод getName (объектного типа t_ParentType)

------------------------------------------------------------ --Тело объектного типа t_ChildType, - --в методе getName необходимо вызвать унаследованный метод - ------------------------------------------------------------ create or replace type body t_ChildType is overriding member function getName return varchar2 is begin return (???) getName ' ' v_Field2; -- как вызвать -- метод предка ??? end; end;


Таким образом, выясняется, что в PL/ SQL нет синтаксической конструкции, для того чтобы сослаться на метод типа-предка.

В объектно-ориентированных языках для этого существуют специальные языковые конструкции. В Java это ключевое слово super (супер-класс), в Object Pascal - Inherited. Данный механизм обеспечивает доступ к унаследованной логике и устраняет избыточность кода. Документация по языку PL/SQL (Oracle10g Rel.1 PL/SQL User's Guide and Reference, Oracle10g Rel.1 Application Developer's Guide - Object-Relational Features) хранит по этому поводу молчание.

Для решения этой проблемы предлагается следующий алгоритм:

Создается экземпляр типа-предка Затем копируется в него содержимое полей текущего экземпляра Происходит вызов нужного метода экземпляра типа-предка Поля экземпляра типа-предка копируются в текущий экземпляра.

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

------------------------------------------------------------ --спецификация объектного типа t_ParentType, - --добавлен копирующий конструктор - ------------------------------------------------------------ create or replace type t_ParentType as object ( v_Field1 varchar2(32), --копирующий конструктор: constructor function t_ParentType(v_pObject in out nocopy t_ParentType) return self as result, member function getName(self in out nocopy t_ParentType) return varchar2 ) not final; ------------------------------------------------------------ --тело объектного типа t_ParentType - ------------------------------------------------------------ create or replace type body t_ParentType is constructor function t_ParentType(v_pObject in out nocopy t_ParentType) return self as result is begin self.v_Field1 := v_pObject.v_Field1; return; end; member function getName(self in out nocopy t_ParentType) return varchar2 is begin return self.v_Field1; end; end;

В типе-потомке нам также будет необходим метод присваивания, который будет копировать все поля переменной экземпляра типа в текущий экземпляр, - назовем его assign. Далее добавим функцию inherited_getName, которая будет реализовывать алгоритм вызова функции getName родительского типа t_ParentType. Фактически метод inherited_getName представляет собой оболочку для метода getName типа-предка t_ParentType.



------------------------------------------------------------ --Спецификация объектного типа t_ChildType, - --который является наследником : t_ParentType - --Добавлен метод присваивания - assign - ------------------------------------------------------------ create or replace type t_ChildType under t_ParentType ( v_Field2 varchar2(64), constructor function t_ChildType(v_pField1 varchar2, v_pField2 varchar2) return self as result, --метод для вызова унаследованного метода getName: member function inherited_getName(self in out nocopy t_ChildType) return varchar2, --метод присваивания: member procedure assign(self in out nocopy t_ChildType, v_pObject in out nocopy t_ChildType),

overriding member function getName(self in out nocopy t_ChildType) return varchar2 ) not final; ------------------------------------------------------------ --Тело объектного типа t_ChildType - ------------------------------------------------------------ create or replace type body t_ChildType is constructor function t_ChildType(v_pField1 varchar2, v_pField2 varchar2) return self as result is begin self.v_Field1 := v_pField1; self.v_Field2 := v_pField2; return; end; member function inherited_getName(self in out nocopy t_ChildType) return varchar2 is v_xInheritedObject t_ParentType; --экземпляр объекта-предка v_xRes varchar2(32); begin -- создаем экземпляр предка с помощью копирующего конструктора v_xInheritedObject := new t_ParentType(self); -- вызываем метод getName класса-предка v_xRes := v_xInheritedObject.getName; -- в общем случае вызов метода предка мог изменить поля self.assign(v_xInheritedObject); -- поэтому необходимо обратно скопировать их в текущий объект (self) return v_xRes; end; ---------------------------------------------------------- -- метод присваивания: - -- просто копируем все поля-объекта источника в текущий - -- экземпляр (self) - ---------------------------------------------------------- member procedure assign(v_pObject in out nocopy t_ChildType) is begin self.v_Field1 := v_pObject.v_Field1; self.v_Field2 := v_pObject.v_Field2; end; ---------------------------------------------------------- -- переопределенный метод getName: - -- через вызов inherited_getName происходит обращение к - -- унаследованному методу getName - ---------------------------------------------------------- overriding member function getName(self in out nocopy t_ChildType) return varchar2 is begin return inherited_getName '-' v_Field2; end; end;

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

Например: в следующей иерархии классов: t_ParentType -> t_ChildType -> t_SubChildType для вызова метода произвольного типа-предка можно использовать следующие правило: к имени метода добавляется цифра - номер уровня в иерархии. В этом случае имена методов-оболочек соответственно будут выглядеть следующим образом:

getName0->getName1->getName2

Вышеописанная методика демонстрируется в данном примере .


Мы рассмотрели методы решения наиболее


Мы рассмотрели методы решения наиболее часто встречающихся проблем при использовании объектно-ориентированных возможностей PL/SQL. Конечно, многие проблемы могут быть решены только самими разработчика корпорации Oracle. Например, отсутствие защищенных полей объектного типа (так называемых private-полей), отсутствие поддержки интерфейсов и т.д.
Будем надеяться, что в следующих версиях Oracle Database эти недоработки будут устранены.

Клиент-сервер – это еще не все


Приведенный пример обращения к Oracle относится к архитектуре клиент-сервер. Ею возможности Perl не исчерпываются. Когда вы познакомитесь с технологиями Perl подробнее, вы обнаружите целый ряд дополнительных способов использования языка:

Технологии Oracle позволяют организовать обращение из PL/SQL к внешним процедурам на Perl, наподобие как для C. Чтобы это сделать, нужно иметь определенную квалификацию в Oracle, и это экзотика, но это возможно, а вследствие своеобразных языковых возможностей Perl не бесполезно ! Дополнительный модуль Apache под названием mod-perl позволяет запускать из браузера программы на Perl гораздо эффективнее, чем посредством CGI. Одна из привлекательных возможностей при такой работе – обращение к БД посредством модуля Perl Apache::DBI. В этом случае соединения с Oracle будут браться из «пула соединений» совсем как при работе с сервлетами на Java и с теми же выгодами. Два конкурирующих решения позволяют использовать Perl в качестве языка, встроенного в HTML, наподобие страницам JavaServer или ASP, выполняющего вычисления на сервере web. Это так называемые системы Embperl и Mason, реализованные в Perl посредством модулей соответственно HTML::Embperl и HTML::Mason. Оба варианта обладают развитой функциональностью и подобно всем прочим разработкам для Perl общедоступны и открыты.



Непосредственное обращение к БД


Доступ к БД под управлением Oracle может через sqlplus указанным выше способом. Способностей Perl вполне хватит отбросить из выходного файла лишние строки и символы и извлечь в свои внутренние структуры содержательную часть. Иногда этого достаточно, но чаще вам потребуется обращение в БД их программы на Perl напрямую, например потому, что это эффективнее, или потому что sqlplus в наличии нет.

Perl позволяет общаться напрямую со многими типами БД, и общение с СУБД Oracle реализовано в этом языке как частный случай общего решения. Правда для «настоящих» СУБД (не «персональных») история этого общего решения началась в Perl с 90-ми годами именно с частного случая Oracle. С другой стороны для Oracle в Perl есть и сугубо собственное решение, общение через OCI, которое здесь на рассматривается (для справки: это реализуется с помощью модуля Oracle::OCI в Perl).

Общение с БД «вообще» реализуется в Perl посредством специального интерфейса под названием DBI (буква i – independent – означает независимость от конкретного типа БД). Для работы с конкретной БД вдобавок требуется установить драйвер DBD, расчитанный на этот тип базы (буква d – dependent – означает зависимость от конкретного типа БД). Интерфейс DBI и драйвер Oracle DBD реализованы в виде модулей Perl, и их можно установить со страниц архивов Perl, называемых CPAN (Comprehensive Perl Archive Network): . В ActivePerl это можно сделать проще с помощью программы ppm. Достаточно войти в нее и выполнить команды:

ppm PPM> install DBI PPM> install DBD-Oracle PPM> query PPM> exit

Команда query позволит просмотреть все установленные модули и удостовериться в наличии DBI и DBD-Oracle.

Теперь все готово к проверке работы с Oracle. Составим файл HelloMiller.pl:

use strict; use DBI;

my $dbh = DBI ->connect ( 'dbi:Oracle:orcl', 'scott', 'tiger', {RaiseError => 1, AutoCommit => 0} );;

my $sql = qq{ SELECT ename, sal, COMM FROM emp };

my $sth = $dbh->prepare( $sql );

$sth->execute();

while ( my($ename, $sal, $comm) = $sth->fetchrow_array) { printf ("%-10s %7.2d %7.2d", $ename, $sal, $comm); if ($ename =~ /MILLER/) { print ", Hello Miller !"; } print "\n"; }

$dbh->disconnect();

Запустим программу:

HelloMiller.pl или perl HelloMiller.pl

Идею поприветствовать Миллера я заимствовал из книги .



Oracle и Perl – это очень просто


, Преподаватель технологий Oracle

... «Нужен мне работник: Повар, конюх и плотник. А где найти мне такого Служителя не слишком дорогого ?» А. С. Пушкин, Сказка о попе и его работнике Балде



Первые опыты с Perl


Наберите в консольном окошке perl –v и убедитесь, что сиcтема Perl установлена и нужной версии.

Многие языки программирования используются не так, как мыслились их создателям – отчасти или даже полностью. Perl задумывался как язык сценариев для командной оболочки OC, более развитый и переносимый. Сейчас он развился до языка программирования, но полностью цели своего далекого прошлого не изменил. Составим файл scriptexample.pl: print "this is a perl scripting example:\n";

$resultfile = "scott.tables"; $tempfile = "temp.file";

open(TMP, ">$tempfile");

print TMP <<EOF; SET PAGESIZE 0 SET FEEDBACK OFF SET LINESIZE 30 SPOOL $resultfile SELECT table_name FROM user_tables; SPOOL OFF EXIT EOF

print `sqlplus -s scott/tiger <$tempfile`;

print "\nreturn code = ", system "sqlplus -s scott/tiger <$tempfile";

open (TABLES, $resultfile); print "\n\n", <TABLES>;

print "\nend of example\n";

Обратите внимание на следующие особенности:

При формировании текста задания вместо простого spool scott.tables указан для примера более общий вариант, указывающий на возможность динамического формирования текста. sqlplus в программе вызывается двумя способами. Первый использует специальные кавычки `...`, а второй – оператор языка system. Второй чуть более длинен, но зато позволяет обрабатывать код завершения программы. Текст, полученный в sqlplus, тут же в программе пожет прочитываться и обрабатываться (print "\n\n", <TABLES>;). В случае Unix в качестве первой строки потребуется добавить что-то вроде #!/usr/bin/perl. Это упростит запуск программы, но в Windows этого можно и не делать.

Наберем в командной строке консольного окошка ОС:

scriptexample.pl

или же

perl scriptexample.pl

Именно благодаря возможностям компоновки сценариев (только чуть затронутым приведенным примером) Perl иногда называют «клейкой лентой информационных систем».

Другую известную особенность языка составляют возможности разбора регулярных выражений. Возьмем пример со страницы и чуть упростим его. Подготовим файл href.pl:

print "$2\n" while m{ < \s* A \s+ HREF \s* = \s* (["']) (.*?) \1 \s* >

}gsix;

Наберем (в синтаксисе Windows):

href.pl C:\Perl\html\release.html

(Файл RELEASE.html входит в стандартную поставку ActivePerl). Обратите внимание, что на экране появились как глобальные адреса ссылок (http, ftp, mailto), так и локальные.



Установка Perl


Вообще-то, если вы установили Oracle вместе с сервером HTTP (то есть Apache), то Perl в вашем распоряжении уже есть. Например, для версий 8.1.6 – 9.2 он находится в каталоге [ORACLE_HOME]/Apache/perl/5.00503/bin (замените косые черточки на обратные для Windows). Если спуститься на каталог ниже и выдать perl –v, станет окончательно ясно, то в штатной поставке Oracle использована версия Perl 5.0. Она не самая последняя и к тому же представленна тут в ограниченном варианте. Он достаточен для решения внутренних задач, поставленных здесь перед Perl фирмой Oracle, а вам для реальной работы лучше установить у себя более свежую версию.

Отправной страницей web для установки Perl служит . Тут можно найти исходные тексты для построения исполняемых модулей Perl на разных платформах. Но часто проще воспользоваться уже готовыми исполняемыми модулями, имеющимися для многих платформ, например:

– для AIX
– для Solaris
– для HP MPE/iX
– для Windows, Linux и Solaris.

Perl по духу близок к Unix, но по иронии судьбы проще всего его установить именно для Windows с указанной выше страницы web фирмы ActiveState. (Правда, в последнее время ActiveState распространила эту простоту и на Linux с Solaris). Если вы воспользуетесь услугами этой страницы, уступите соблазну выкачать из интернета более позднюю версию 5.8.4, а остановите лучше свой выбор на 5.6.1. Это сделает удобнее работу с Oracle, так как по состоянию на лето 2004 года для более поздней версии готовый драйвер для DBI для Oracle имелся, а для более новой отсутствовал.

Полученный из интернета файл Windows, например ActivePerl-5.6.1.638-MSWin32-x86.msi, нужно запустить (или распаковать, если выбрали файл формата zip), и через полминуты Perl установлен. В Unix потребуется выполнить традиционную, но несложную процедуру построения (make).

Дальнейшее изложение для определенности будет предполагать установку ActivePerl на Windows; подгонка сказанного под другие платформы потребует некоторых навыков, но не глубоких познаний.



общедоступный язык программирования, существующий на


Perl – общедоступный язык программирования, существующий на разных платформах ОС. Общедоступность его как в возможности любого желающего законно и даром получить программы и сопутствующие материалы по интернету, чтобы использовать как угодно, так и в возможности любого желающего поучаствовать в его разработке, не прекращающейся с 1987 года по сию пору. Иными словами, Perl – это пример разработки, ведомой методом открытых текстов (open source), и причем один из наиболее ярких успешных примеров. Разработчики Perl – программисты, создают этот язык для своих же нужд, и поэтому этот язык способен решать на удивление широкое разнообразие задач. Если вы работаете с Oracle, Perl поможет вам и администрировать БД и СУБД, и разрабатывать приложение, не исключая приложение для web, и связывать компоненты ИС, разработанные в разных средах, друг с другом. Так что же мешает вам последовать примеру вполне коммерческой фирмы Oracle, которая использует Perl на установочных дисках своих продуктов ?