Книга
Книга
3-Е ИЗДАНИЕ
Oracle PL/SQL
ДЛЯ ПРОФЕССИОНАЛОВ
С. Фейерштейн, Б. Прибыл
^Ьм С^ППТЕР^
Москва ・ Санкт-Петербург - Нижний Новгород - ВоронежРостов-на-Дону ・ Екатеринбург ・ СамараКиев ■ Харьков - Минск
2003
THIRD EDITION
Oracle PL/SQL Programming
Stewen Feuerstein wint Bill Pribyl
,bREILLY8
Beijing ・ Cambridge ・ Famham • Kbln ■ Paris ■ Sebastopol ■ Taipei - Tokyo
ББК 32.973-018
УДК 681.3.06
Ф28
Ф28 Oracle PL/SQL для профессионалов 3-е изд. / С. Фейерштейн, Б. Прибыл.— СПб.: Питер, 2003. — 941 с.: ил.
ISBN 5-318-00528-4
Данная книга являете» наиболее полным руководством по языку PL/SQL, представляющему собой процедурное языковое расширение для SQL. В ней детально рассмотрены ochoru PL/SQL, структура программы, основные принципы работы с программными данными, а также методика применения операторов н инструкций для доступа к реляционным базам данных. Большое внимание уделяется вопросам безопасности, влиянию объектных технологий на PL/SQL к интеграции PL/SQL с XML и Java.
ББК 32.973-018
УДК 681.3.06
Информация, содержащаяся в данной книге, получена из источников, рассматриваемых издательством как надежные. Тем не менее, имея в виду возможные человеческие или технические ошибки, издательство не может гарантировать абсолютную точность и полноту приводимых сведений и не несет ответственности за возможные ошибки, связанные с использованием книги.
©2002 by O'Reilly
ISBN 0-596-00381-1 (англ.)© Перевод на русский язык, ЗАО Издательский дом «Питер», 2003
ISBN 5-318-00528-4© Издание нв русском языке, оформление. ЗАО Издательский дом «Питер», 2003
Краткое содержание
Предисловие -21
Часть I. Программирование на PL/SQL
Глава 1. Введение в PL/SQL32
Глава 2, Написание и запуск кода PL/SQL57
Глава 3_ Основы языка87
Часть II. Структура программы PL/SQL
Глава 4・ Условные операторы и переходы 110
Глава 5. Циклы132
Глава 6. Обработка исключений149
Часть III. Работа с данными в PL/SQL
Глава 7. Работа с программными данными174
Глава 8. Строки196
Глава 9, Числа243
Глава 10. Дата и время271
Глава 11. Записи и коллекции334
Глава 12. Другие типы данных403
Часть IV, SQL и PL/SQL
Глава 13, DML и управление транзакциями444
Глава 14. Выборка данных481
Глава 15. Динамический SQL и динамический PL/SQL536
Часть V- Создание приложений PL/SQL
Глава 16. Процедуры,функции и параметры568
Глава 17. Пакеты616
Глава 18. Триггеры645
Глава 19. УправлениеприложениямиРЦ/SQL700
Часть VL Особые возможности PL/SQL
Глава 20. Выполнение программ PL/SQL740
Глава 21. Объектно-ориентированные возможности PL/SQL799
Глава 22. Взаимодействие Java и PUSQL857
Глава 23. Внешние процедуры892
Алфавитный указатель919
Содержание
Предисловие -21
Задачи этой книги22
Структура книги23
Используемые соглашения27
Платформа и версия28
О программном коде28
Комментарии и вопросы28
Благодарности29
От издательства30
Часть I. Программирование на PL/SQL
Глава 1. Введение в PL/SQL.... 32
История PL/SQL 33
Истоки PL/SQL33
Повышение переносимости приложений34
Улучшенная защита приложений и защита цетостности транзакций34
Скромное начало, постоянное усовершенствование35
Так вот что такое PL/SQL35
Интеграция с SQL36
Последовательность и условия выполнения строк программы37
Когда что-то пошло не так38
О версиях PL/SQL39
Новые возможности Oracle8l40
Новые возможности Orade9l45
Работа с несколькими версиями PL/SQL49
Ресурсы для разработчиков PUSQL49
Книги о PL/SQL от O'Reilly50
Другие печатные ресурсы51
PL/SQL в Интернете51
Средства разработки и утилиты52
Несколько советов53
Не стоит торопиться!53
Не бойтесь обращаться за помощью54
Поощряйте творческий, и даже радикальный, подход к разработке кода55
Глава 2. Написание и запуск кода PL/SQL57
SQL*Plus58
Запуск SQL*Plus59
Выполнение SQL-инструкции62
Запуск программы на языке PL/SQL63
Запуск сценария64
Другие задачи SQL*Plus65
Обработка ошибок в SQL*Plus70
Достоинства и недостатки SQL*Plus70
Выполнение базовых операций PL/SQL71
Создание хранимой программы 72
Выполнение хранимой программы75
Вывод хранимых программ75
Управление привилегиями и создание синонимов хранимых программ76
Удаление хранимой программы 77
Как скрыть исходный код хранимой программы77
Средства разработки Oracle PL/SQL78
Перенос программ на PL/SQL между клиентским компьютером и сервером80
Вызов PL/SQL из других языков81
С, с использованием прекомпилятора Oracle (Рго*С)82
Java, с использованием JDBC 83
Peri, с использованием Perl DBI и DBD::Oracle84
PL/SQL Server Pages85
Что же дальше86
Глава 3. Основы языка 87
Структуре блока PL/SQL87
Разделы блока PL/SQL88
Анонимные блоки89
Именованные блоки92
Вложенные блоки92
Область действия93
Видимость переменной94
Набор символов PL/SQL96
Идентификаторы97
Зарезервированные слдва99
Отступы и ключевые слова Ю1
Литералы102
Одинарные кавычки внутри строки102
Числовые литералыЮЗ
Логические (булевы) литералыЮЗ
Разделитель э виде точки с запятой104
Комментарии104
Однострочные комментарии104
Многострочные комментарии
Ключевое слово PRAGMA
МеткиЮб
Часть II. Структура программы PL/SQL
Глава 4. Условные операторы и переходы110
Операторы IFНО
Конструкция IF...THENIll
Конструкция IF,..THEN...ELSE112
Конструкция IFeTHEN…ELSIF 114
Вложенные операторы IF116
Оператор CASE118
Простые операторы CASE119
Поисковый оператор CASE120
Вложенные операторы CASE122
Выражения CASE123
Оператор GOTO125
Ограничения на использование126
Оператор NULL129
Повышение читабельности программы129
Обработка исключений с помощью оператораNULL130
Использование оператора NULL после метки131
Глава 5. Циклы132
Основы циклов;132
Примеры разных циклов132
Структура циклов PL/SQL133
Простой цикл134
Завершение простого цикла: операторы ЕХГГиEXIT WHEN135
Эмуляция цикла REPEAT UNTIL136
Цикл WHILE136
Использование счетчика в цикле FOR138
Правила для циклов FOR с числовым счетчиком138
Примеры циклов FOR с числовым счетчиком139
Обработка нетривиальных приращений140
Использование курсора в цикле FOR140
Примеры цикла FOR с курсором141
Метки цикла142
Полезные советы144
Использование для счетчиков циклов понятныхимен144
Как правильно попрощаться145
Получение информации о выполнении циклаFOR146
SQL-инсгрукция в качестве цикла146
Глава 6. Обработка исключений149
Как в PL/SQL обрабатываются ошибки:150
Стратегия обработки исключений150
Концепции и терминология обработки исключений151
Определение исключений152
Объявление именованнь<х исключений152
Как связать имя исключения с кодом ошибки154
Об именованных системных исключениях156
Область действия исключения158
Как инициировать исключение160
Оператор RAISE 160
Использование процедуры RAISE__APPLICATION_ERROR162
Обработка исключений163
Объединение нескольких исключений в одном обработчике164
Необработанные исключения165
Использование функций SQLCODE и SQLERRM в обработчиках исключений165
Продолжение работы после инициирования исключения 166
Передача необработанного исключения168
Использование стандартизированных программ обработки ошибок171
Часть III. Работа с данными в PL/SQL
Глава 7. Работа с программными данными174
Именование Программных данных175
Обзор типов данных PL/SQL 176
Символьные типы данных177
Числовые типы данных177
Типы данных DATE TIMESTAMP и INTERVAL178
Тип данных BOOLEAN 178
Двоичные данные178
Типы данных ROWID и UROWID179
Тип данных REF CURSOR179
Типы данных для поддержки Интернета179
Типы данных «апу»180
Типы данных, определяемые пользователем180
Объявление программных данных180
Объявление переменной181
Объявление константы 181
Объявления с ограничениями182
NOT NULL182
Объявления с привязкой183
Привязка к курсорам и таблицам185
Преимущества объявлений с привязкой185
Объявления с 叩ивязк曲 и ограничение NOT NULL 187
Подтипы данных, определяемые программистом187
Преобразование типов данных
Неявное преобразование типов
Явное преобразование типов 190
Глава 8. Строки196
Наборы символов196
Что такое набор символов196
Типы наборов символов197
Набор символов базы данных и набор символов национального языка198
Вопросы, связанные с наборами символов199
Строковые типы данных204
Тип данных VARCHAR2205
Тип данных CHAR206
Типы данных NCHAR и NVARCHAR2208
Строковые подтипы208
О работе со строками209
Пустая строка — это NULL209
Смешение значений CHAR и VARCHAR2210
Определение строковых констант213
Строковые функции214
Глава 9. Числа243
Числовые типы данных243
Тип данных NUMBER244
Тип данных PLS」NTEGER248
Тип данных BINARY」NTEGER248
Числовые подтипы248
Числовые преобразования 250
Модели форматирования чисел250
Функция TO_CHAR255
Использование функции CAST260
Неявные преобразования261
Числовые функции262
Глава 10. Дата и время271
Типы данных даты и времени 272
Тип данных DATE 272
Типы данных TIMESTAMP273
Типы данных INTERVAL277
Преобразование типов данных DATE и TIMESTAMP280
Маски форматирования даты и времени281
Преобразование строк в даты285
Функция TO—DATE286
Преобразование даты в строку295
Функции CAST и EXTRACT304
Арифметические операции над значениями даты-времени306
Традиционные арифметические операции306
Арифметические действия с интервалами309
Функции обработки даты-времени317
Функции получения даты и времени318
Функции обработки информации о часовом поясе320
Функция ADD_MONTHS322
функция FROM_TZ324
функция LASTLDAY325
Функция MONTH_BETWEEN326
Функции ROUND и TRUNC327
Функция NEWJTIME330
Функция NEXT_DAY332
Глава 11. Записи и коллекции 334
Записи в PL/SQL 334
Эффективность использования записей335
Объявление записей336
Коллекции в PL/SQL347
Определение типов коллекций и их объявление 352
Использование коллекций356
Встроенные методы коллекций360
Рабата с коллекциями368
Псевдофункции коллекций393
Сопутствующие операции399
Выбор типа коллекции401
Глава 12. Другие типы данных403
Тип данных BOOLEAN403
Тип данных RAW405
Типы данных UROWID и ROWID405
Получение идентификаторов строк406
Использование идентификаторов строк407
Большие объекты данных410
Тип данных BFILE411
Тип данных BLOB411
Тип данных CLOB412
Тип данных NCLOB 412
Обработка больших объектов413
Понятие локатора LOB414
Большие объекты ——пустые и равные NULL415
Создание больших объектов417
Запись данных в объеш-ы типа LOB418
Считывание данных из объектов типа LOB420
Особенности данных типа BFILE422
Временные объекты типа LOB426
Встроенные операции над объектами типа LOB в Oracle9i43。
Функции преобразования объектов типа LOB434
Предопределенные объектные типы434
Тип данных XMLType435
Типы данных (JRI438
Типы данных «апу»
Часть IV. SQL и PL/SQL
Глава 13- DML и управление транзакциями444
DML в PU5QL445
Краткое введение в DML445
Атрибуты курсора для операций DML448
Предложение RETURNING в DML-инструкции450
DML и обработка исключений451
DML и записи452
Пакетные DML-операции и оператор FORALL456
Оператор FORALL457
Проблемы, вызываемые переключением контекста458
Примеры использования оператора FORALL460
Атрибуты курсора для оператора FORALL461
Откат изменений, выполняемых с помощью оператора FORALL462
Продолжение работы программы после исключения, инициированного при выполнении оператора FORALL463
Управление транзакциями464
Инструкция COMMIT465
Инструкция ROLLBACK,...466
Инструкция SAVEPOINT.-466
Инструкция SET TRANSACTION467
Инструкция LOCK TABLE468
Автономные транзакции468
Определение автономной транзакции469
В каких случаях следует применять автономные транзакции470
Правила и ограничения на использование автономных транзакций470
Примеры автономных транзакций475
Глава 14. Выборка данных481
Основы курсоров482
Некоторые термины, связанные с выборкой данных483
Типичные операции с запросами и курсорами484
Знакомство с атрибутами курсора485
Ссылки на переменные PL/SQL в курсоре488
Выбор между явным и неявным курсорами490
Работа с неявными курсорами491
Примеры неявных курсоров492
Обработка ошибок, связанных с неявными курсорами493
Атрибуты неявных курсоров495
Работа с явными курсорами497
Объявление явного курсора498
Открытие явного курсора501
Выборка данных из явного курсора502
Псевдонимы столбцов явного курсора503
Закрытие явного курсора504
Атрибуты явных курсоров506
Параметры кураэра507
Предложение BULK COLLECT510
Ограничение на количество строк, возвращаемых инструкцией с предложением BULK COLLECT512
Выборка более одного столбца513
Совместное использование предложений RETURNING и BULK COLLECT514
Инструкция SELECT с предложением FOR UPDATE516
Снятие блокировок инструкцией COMMIT517
Предложение WHERE CURRENT OF 518
Курсорные переменные520
Для чего нужны курсорные переменные521
Сходство со статическими курсорами522
Объявление типов REF CURSOR523
Объявление курсорной переменной523
Открытие курсорной переменной524
Выборка данных из курсорной переменной525
Правила использования курсорных переменных 528
Передача курсорных переменных в качестве аргументов531
Ограничения, связанные с курсорными переменными532
Курсорные выражения533
Использование курсорных выражений534
Ограничения, связанные с курсорными выражениями535
Глава 15. Динамический 5QL и динамический PL/SQL.536
Инструкции NDS537
Инструкция EXECUTE IMMEDIATE537
Инструкция OPEN FOR540
Запросы с переменными-курсорами, возвращающие набор строк541
Считывание данных в переменные или записи543
Предложение USING в инструкции OPEN/FOR544
Универсальная процедура для выполнения запросов с предложением GROUP BY545
Универсальный пакет для выполнения запросов с предложением GROUP 8Y546
Передача значений параметров 548
Подстановка и конкатенация548
Ограничения на использование метода подстановки549
Режимы использования параметров550
Дублирование формальных параметров552
Передача значения NULL553
Работа с объектами и коллекциями553
Разработка приложений с использованием NDS556
Совместное применение программ с правами вызывающего556
Обработка ошибок557
Динамический PL/SQL559
Пакет NDS Utility,563
в90222333466788890112479ю144^-678в s67777777777777778888888994^?999g 務 55555555555555555455555555555555в90222333466788890112479ю144^-678в s67777777777777778888888994^?999g 務 55555555555555555455555555555555^ ^ W ^ ^^ ^ W ^ ^13 513 5
в90222333466788890112479ю144^-678в s67777777777777778888888994^?999g 務 55555555555555555455555555555555
в90222333466788890112479ю144^-678в s67777777777777778888888994^?999g 務 55555555555555555455555555555555
^ ^ W ^ ^
^ ^ W ^ ^
13 5
13 5
Сравнение возможностей динамического SQL и пакета DBMS_SQL.. Визуальное сравнение эквивалентных программ Какую технологию в каких случаях предпочтительнее использовать
Часть V. Создание приложений PL/SQL
Глава 16. Процедуры, функции и параметры
Модульный код Процедура
Вызов процедуры Заголовок процедуры
Тело процедуры Дескриптор END Оператор RETURN
Функции
Структура функции
Типы возвращаемых данных Дескриптор END
Вызов функции
Функции без параметров Заголовок функции Тело функции Оператор RETURN Параметры
Объявление параметров
Формальные и фактические параметры
Связывание формальных и фактических параметров Режимы использования параметра
Методы передачи параметров Значения по умолчанию
Локальные модули
Преимущества локальной модуляризации Область видимости локальных модулей Локальные модули как средство повышения качества кода Перегрузка модулей
Использование перегрузки
Поддержка разных типов данных
Ограничения на использование перегрузки
Предобъявления
Дополнительные вопросы
Вызов пользовательских функций в SQL
Ограничения для пользовательских функций, взываемых в SQI Директива RESTRICT_REFERENCE (Огас1е8 и более ранние версии).. Табличные функции Вызов функций в предложении FROM Конвейерные функции
Функции трансформации60В
Функции, доступные для параллельного выполнения612
Детерминированные функции614
Модульный подход — в жизнь!615
Глава 17. Пакеты616
Для чего нужны пакеты616
Демонстрация возможностей пакетов617
Понятия и концепции, связанные с пакетами620
Диаграмма Буча621
Правила построения пакетов622
Спецификация пакета622
Тело пакета624
Инициализация пакетов625
Правила вызова элементов пакета629
Работа с данными пакета630
Глобальные данные в одном сеансе Oracle631
Глобальные общие данные631
Пакетные курсоры632
Повторно инициализируемые пакеты636
В каких случаях следует использовать пакеты638
Инкапсуляция операций с данными639
Исключение жесткого кодирования литералов641
Устранение недостатков встроенных функций642
Группировка логически связанных функций642
Кэширование статических данных сеанса643
Пакеты и объектные типы644
Глава 18. Триггеры645
Триггеры уровня инструкций DML?646
Концепции триггеров647
Создание триггера649
Пример аудита, выполняемого с помощью триггера654
Триггеры одного типа660
Ошибки, возникающие при изменении таблицы: проблема и решение662
Триггеры уровня DDL669
Создание триггера669
Собьгтя триггеров671
Параметры триггеров672
Применение событий и параметров674
Удаление неудаляемого триггера677
Триггеры событий базы данных678
Создание триггера события базы данных678
Триггеры STARTUP679
Триггеры SHUTDOWN679
Триггеры LOGON680
Триггеры LOGOFF680
Триггеры SERVER.ER.ROR.680
Неработоспособные триггеры684
Триггеры INSTEAD OF684
Триггер INSTEAD OF INSERT666
Триггер INSTEAD OF UPDATE688
Триггер INSTEAD OF DELETE669
Заполнение таблиц689
Триггеры AFTER SUSPEND690
Предпосылки создания триггера690
Пример реализации691
Создание триггера694
Функция ORA_SPACE_ER.R.OR._INFO694
Пакет DBMS_RESUMABLE695
Многократный запуск триггера696
Сопровождение триггеров697
Отключение, включение и удаление триггеров697
Просмотр триггеров698
Проверка состояния триггера VAUD699
Глава 19. Управление приложениями PL/SQL 700
Управление программным кодом и его анализ в базе данных701
Представления словаря данных для программистов PL/SQL701
Вывод информации о хранимых объектах702
Вывод и поиск исходного кода704
Защита кода хранимой программы706
Как скрыть исходный код706
Работа со скрытым кодом707
Встроенная компиляция708
Настройка, выполняемая администратором базы данных709
Сравнение интерпретируемого и компилируемого режимов710
Тестирование программ PL/SQL711
Неэффективные технологии тестирования712
Где найти дополнительную информацию717
Отладка программ PL/SQL717
Неупорядоченная отладка718
Иррациональная отладка719
Советы и стратегии отладки719
Оптимизация программ PL/SQL723
Анализ производительноЕ PL/SQL724
Трассировка выполнения кода727
Повышение производительности приложения730
Как избежать выполнения ненужного кода730
Умение слушать734
Использование пакетных данных для минимизации SQL-обращений к базе данных736
Использование предложения BULK COLLECT и оператора FORALL737
Часть VI. Особые возможности PL/SQL
Глава 20. Выполнение программ PL/SQL .„„„>„„.,„.....740
Заглянем внутрь741
Концепции PL/SQL741
Хранение PL/SQL-кода на сервере747
Слишком большой DIANA-код75°
Управление зависимостями751
Зависимости в серверном PIVSQL751
Восстановление работоспособности755
Взаимосвязи в клиентском PL/SQL758
Удаленные зависимости 76°
Как Ply SQL использует память сервера Oracle 761
Память сервера762
Курсоры и память764
Советы по экономии памяти765
Трассировка памяти774
Выполнение серверного кода PL/SQL776
Компиляция анонимного блока776
Компиляция хранимого объекта777
Выполнение программ PIVSQL778
Клиентский код PUSQL779
Поддерживаемые версии и функциональные возможности780
Ограничения, касающиеся удаленных вызовов в Oracle782
Клиентские библиотеки PUSQL784
Модели разрешений787
Модель разрешений владельца787
Модель разрешений на выполнение программ792
Комбинированная модель разрешенийк794
Аппаратное обеспечение для PL/SQL: больше — лучше?795
Однопроцессорная система796
Симметричные мультипроцессорные системы796
Кластерные системы797
Что вам нужно знать797
Глава 21. Объектно-ориентированные возможности PL/SQL799
Обзор объектных возможностей Oracle800
Пример объектного приложения802
Иерархия типов802
Методы805
Запп% извлечение и использование хранимых объектов811
Эволюция и создание типов818
И снова указатели820
Типы данных аапу»828
Все делаем сами 832
Сравнение объектов835
Объектные представления839
Существующие реляционные системы840
Объектное представление с атрибутом-коллекцией842
Объектные подпредсгавления845
Объектное представление, реализующее обратное отношение846
Триггеры INSTEAD OF847
Различие между объектными представлениями и объектными таблицами849
Сопровождение объектных типов и объектных представлений850
Привилегии852
О целесообразности применения объектно-ориентированного подхода854
Глава 22. Взаимодействие Java и PL/SQL 857
Oracle и Java857
Подготовка к использованию Java в Oracle859
Установка Java859
Проектирование и компиляция кода Java860
Определение привилегий, необходимых для разработки и выполнения Java-кода860
Примеры использования Java862
Выбор функциональных элементов863
Создание пользовательского класса Java863
Компиляция и загрузка класса в Oracle865
Создание оболочки PL/SQL866
Удаление файлов из PL/SQL867
Утилита loadjava867
Утилита dropjava871
Управление объектами Java в базе данных872
Пространство имен Java в Oracle872
Пакет DBMS JAVA873
Функция LONGNAME: преобразование длинных имен Java874
Функция GET_ COMPILER_OPTION и процедуры SET_ и RESET^COMPILER_OP7ION: чтение и установка параметров компилятора875
Процедура SET_OUTPUT: управление выводом из Java876
Процедуры EXPORT_SOURCE, EXPORT_RESOURCE и EXPORT_CLASS: экспорт объектов схемы876
Как сделать методы Java доступными в PL/SQL878
Спецификация вызова878
Некоторые правила написания оболочек для Java-методов880
Сопоставление типов данных 880
Вызов метода Java в SQL 882
Обработка исключений с помощью Java882
Новые возможности файлового ввода-вывода885
Другие примеры использования Java890
Глава 23. Внешние процедуры 892
Концепция внешних процедур893
Пример выполнения команды операционной системы893
Механизм вызова внешних процедур895
Особенности выполнения внешних процедур в Oracle896
Конфигурирование Oracle Net897
Настройка параметров конфигурации лисгенера897
Уровень защиты, установленный в данной конфигурации899
Создание библиотеки Oracle 900
Вызов внешней функции901
Спецификация вызова 902
Список внешних параметров903
Сопоставление параметров905
Синтаксис предложения PARAMETERS907
Свойства параметров908
Инициирование исключений из программ на языке С911
Агенты, отличные от используемых по умолчанию914
.Сопровождение внешних процедур917
Удаление библиотек917
Словарь данных917
Советы по использованию внешних процедур917
Алфавитный указатель 919
Предисловие
Миллионы разработчиков приложений и администраторов баз данных во всем мире используют программное обеспечение компании Oracle для создания комплексных систем управления большими объемами данных. Сердцем программного обеспечения Oracle является PL/SQL 一 язык программирования, представляющий собой процедурное расширение Oracle-версии языка SQL (Structured Query Lan^ guage) и использующийся для разработки приложений в среде Oracle Developer, самыми известными компонентами которой являются Forms Developer и Reports Developer.
PL/SQL включается в состав практически каждого нового продукта, выпускаемого Oracle. Профессиональные разработчики программного обеспечения пользуются им для выполнения самых разнообразных задач:
О реализации бизнес-логики в Oracle Server с помощью хранимых процедур PL/SQL и триггеров базы данных;
О формирования XML-документов, их хранения в базе данных, а также для выполнения операций с этими документами;
О связывания web-страниц с базой данных Oracle;
О выполнения и автоматизации задач администрирования базы данных - от защиты на уровне строк до управления сегментами отката в программах PL/SQL PL/SQL создан по образцу языка Ada, разработанного в свое время для целей Министерства обороны США. Это высокоуровневый язык, в основу которого положены концепции абстракции данных, сокрытия информации и другие современные стратегии разработки программного обеспечения. Oracle включила в него самые прогрессивные элементы процедурных языков программирования (причем в очень удачном сочетании!), благодаря чему получился исключительно мощный, гибкий и в то же время простой язык. Перечислим главные из них:
О полны访 диапазон типов данных, от чисел и строк до таких комплексных структур, как записи (аналогичны строкам таблиц реляционной базы данных), коллекции (Oracle-версия массивов) и даже тип XMLType, предназначенный для управления XML-документами, которые обынно хранятся в базе данных Oracle;
О явно определяемая и наглядная структура блоков, облегчающая расширение и сопровождение приложений PL/SQL;
О операторы управления потоком, в том числе операторы условного перехода, оператор CASE и три вида циклов;
О обработчики исключений для обработки ошибок;
О повторно используемые именованные блоки кода, такие как функции, процедуры, триггеры, объектные типы (подобные классам в объектно-ориентированных языках прохраммирования) и пакеты (наборы логически связанных программ и переменных).
PL/SQL тесно интирирован в язык SQL Oracle: SQL-инструкции можно выполнять непосредственно из процедурной программы, без помощи какого-либо промежуточного API (программного интерфейса) вроде JDBC (Java DataBase Connectivity) или ODBC (Open DataBase Connectivity). Более того, с помощью инструкций SQL можно вызывать собственноручно написанные функпии PL/SQL.
Разработчики приложений Oracle, не желающие отставать от времени, должны максимально полно использовать возможности языка PL/SQL. Для этого им сначала нужно освоить отдельные функциональные компоненты PL/SQL, набор которых постоянно расширяется, а затем научиться их совместному применению при разработке сложных приложений.
Стала очевидной потребность разработчиков приложении Oracle в учебнике с подробнейшим изложением основ PL/SQ, где содержались бы не только описания базовых блоков языка, но и примеры программ и приложений с их использованием. Лишь при этом условии читатели смогут осваивать язык не методом проб и ошибок, а наиболее быстрым и эффективным способом — опираясь на опыт, наработанный другими программистами. Как и в любом другом языке, в PL/SQL практически любую задачу можно выполнить несколькими способами, но не все они подходят для конкретного случая. Настоящая книга призвана помочь вам сориентироваться в море возможностей, предоставляемых этим языком, научиться наиболее эффективному и продуктивному его использованию.
Задачи этой книги
Приступая к работе над книгой, авторы составили план и впоследствии старались неуклонно ему следовать. Ниже описаны цели и задачи, которые были определены как основные.
Q Изучение всех возможностей PL/SQL. В документации Oracle описаны все функциональные компоненты PL/S QL)но ничего не говорится о том, как их следует применять для достижения максимального эффекта. Обычно в учебных пособиях и руководствах освещаются одни и те же стандартные темы одним и тем же, часто весьма ограниченным, образом. Мы же постарались выйти за пределы этих ограничений и рассмотреть наряду со стандартными и нетривиальные, часто более эффективные методы решения тех или иных задач.
Q Использование PL/SQL для решения понседневньпс задач. В книге проанализирован ряд интересных и наиболее целесообразных подходов к решению ти- пичных задач, с которыми ежедневно приходится сталкиваться разработчикам.
Поэтому здесь приведено так много примеров, причем не только небольших фрагментов кода, но и достаточно крупных компонентов приложений, которые, возможно с определенными модификациями, читатели смогут интегрировать в собственные проекты. Очень много кода содержится в самой книге, но еще больше представлено на web-узле издательства O'Reilly, откуда его можно свободно загрузить на свой компьютер. Многие примеры демонстрируют, можно сказать, процесс выработки эффективных решений. Вы поймете, как на практике применять предлагаемые PL/SQL средства, пользуясь нестандартными или недокументированными возможностями и методами.
О Создание эффективного кода, который легко сопровождать. PL/SQL обладает большим потенциалом, позволяющим в рекордно быстрые сроки разрабатывать достаточно мощные приложения. Однако после создания приложения его жизненный цикл не заканчивается, а, наоборот, часто лишь начинается. И уже готовое приложение в дальнейшем может претерпевать множество изменений, связанных с его совершенствованием, адаптацией к изменяющимся требованиям и условиям, расширением и т. д. Поэтому мы не посчитали бы свою задачу выполненной, если бы научили вас только писать в предельно короткие сроки необходимые программы. Мы хотим помочь вам освоить принципы и приобрести навыки построения гибких приложений, программный код которых легко читается, понятен, без труда модифицируется — одним словом, требует минимальных затрат на сопровождение и доработку.
Структура книги
Оба автора книги и издательство O'Reilly & Associates приложили максимум усилий, чтобы как можно более полно осветить процесс развития PL/SQL. В первом издании книги рассматривалось большинство функций PL/SQL, реализованных в PL/SQL Release 2.3. Второе издание было дополнено описанием новых элементов языка, введенных в Oracle8, но представленным отдельно от остального содержимого книги. В настоящем, третьем, издании принят другой подход. Здесь описаны функции PL/SQL версии 9.2. РСУБД Oracle9i Release 2 (9.2). Поэтому независимо от используемой вами версии Oracle - будь то Огас1е7.3.4 (хотя мы надеемся, что у вас установлена более современная версия) или Orac]e9i 一 книга поможет найти ответ на любой вопрос. Вся представленная информация тесно связана; здесь нет отдельного раздела об Oracle9i. Теперь, заинтересовавшись какой-либо конкретной функцией языка, откройте соответствующий раздел книги, и вы найдете там исчерпывающую информацию о доступности и степени реализации этой функции в разных версиях Oracle.
Еще в 1994 году, когда Стивен Фейерпггейн только начал писать о PL/SQL, данный язык был настолько «скромен», что его полное описание можно было поместить в одну книгу. К 1997 году это сделать стало уже невозможным, и в издательстве O'Reilly вышло несколько книг, посвященных разным аспектам использования PL/SQL, и в частности книга о встроенных пакетах Oracle.
Работая над структурой данного издания, мы решили внести ряд изменений и дополнений.
О Прежде всего, и что наиболее существенно, в книгу добавлены важные сведения, которых в ней недоставало, в том числе информация о принципах разработки триггеров базы данных.
О Теперь более полно используются возможности Интернета. Вместо того чтобы включать в книгу огромное количество сложных примеров, мы выделили их в отдельную группу и разместили на web-узле издательства O'Reilly, расположенном но адресу http://www.oreilly.com/catalog/oraclep3 (подробнее о них рассказывается в разделе《0 программном коде»). Но не волнуйтесь — в книге все равно осталось очень много примеров.
О После долгих размышлений мы вынесли на web-узел и несколько глав второго издания. Речь идет о главе 17, которая называется《Вызов функций PL/SQL из SQL», и главе 22)имеющей название «Советы по разработке кода». (Вопросы разработки кода также подробно освещены в отдельной книге, Oracle PL/SQL Best Practices.)
О Чтобы улучшить общую структуру книги и уменьшить ее объем, многие главы пришлось реорганизовать. Например, вместо того чтобы по отдельности рассматривать символьные данные и символьные функции, мы объединили в одной главе всю информацию, связанную со строками. Мы также объединили несколько других глав, и в частности главы 24- 26 предыдущего издания, посвященные отладке, повышению производительности и трассировке приложений. Большие фрагменты текста, не имеющие принципиального значения для книги, и в первую очередь анекдоты также были перемещены на web-узел.
Авторы очень довольны результатами своего труда и надеются, что ими будете довольны и вы. Книга теперь несет гораздо больше информации, чем раньше, и мы постарались при изложении материала сохранить присущий всем нашим публикациям юмор и разговорный тон, которые, судя пб отзывам читателей, облегчают чтение и способствуют восприятию содержимого.
Так как у нас появилось несколько соавторов, вклад которых в третье издание книги весьма значителен, приведем краткую сводку о том, какая глава кем была написана.
Глава
Автор
Глава
Автор
Глава
Автор
Глава
Автор
Глава
Автор
1
Стивен
6
Стивен
11
Стивен и Билл
16
Стивен
21
Билл
2
Билл
7
Стивен
12
Джонатан
17
Стивен
22
Стивен
3
Билл и Стивен
8
Джонатгн и Стивен
13
Стивен
18
Дарил
23
Билл
4
Джонатан и Стивен
9
Джонатгн и Стивен
14
Стивен
19
Стивен
S
Стивен
10
Джонатан и Стивен
15
Стивен
20
Билл
О содержании
Теперь наша книга состоит из шести частей.
О Часть I. Программирование иа PL/SQL. Главу 1 мы начнем с рассказа об истории языка и основных его возможностях. Затем приведем краткий обзор некоторых главных функций PL/SQL. Глава 2 построена таким образом, чтобы вы с ходу могли начать программировать: она содержит четкие и простые инструкции по выполнению кода PL/SQL в среде разработки SQL*Plus и описание ряда распространенных инструментальных средств. В главе 3 рассказывается об основах языка PL/SQL: что такое оператор, какова структура блока, как создавать комментарии и т. п.
О Часть II. Структура программы PL/SQL. В главах 4-6 рассматриваются условные (IF и CASE) и последовательные (GOTO и NULL) операторы управления потоком, циклы и методика обработки исключений в языке PL/SQL. Ознакомившись с этой частью книги, вы научитесь составлять блоки кода, соответствующие сложной логике приложения.
О Часть III. Работа с данными в PL/SQL. Почти любая написанная вами программа должна выполнять те или иные операции с данными, и часто эти данные являются локальными для процедуры или функции PL/SQL. Представленные здесь главы 7-12 посвящены различным типам программных данных, которые можно определять прямо в PL/SQL, таким как числа, строки, записи и коллекции. Вы узнаете о новых типах данных, введенных в Oracle9z, и в частности об INTERVAL, TIMESTAMP, XMLType. Кроме того, мы расскажем о встроенных функциях, предоставляемых Oracle в ваше распоряжение для выполнения различных операций с данными.
О Часть IV. SQL и PL/SQL. В главах 13-15 рассказывается об организации взаимодействия приложений с базами данных Oracle, осуществляемого посредством SQL. Из них вы узнаете, как определить транзакции, обновляющие, дополняющие и удаляющие данные таблиц, как запросить из базы данных информацию для обработки в программах PL/SQL, как динамически выполнить SQL-инструкцию, используя возможности NDS, введенного в Oracle8t.
О Часть V. Разработка приложений PL/SQL. В этой части книги обобщается весь материал, изложенный ранее. Приступая к ее изучению, вы уже будете знать, как объявлять переменные и как с ними работать, освоите важнейшие принципы обработки ошибок и построения циклов. В главах 16-19 рассказывается о самых крупных «строительных блоках?» приложений: процедурах, функциях, триггерах и пакетах. В главе 19 также обсуждаются вопросы управления кодом PL/SQL, его оптимизации и отладки.
Q Часть VI. Особые возможности PL/SQL. Язык PL/SQL, столь мощный и богатый, предоставляет немало функциональных возможностей и структурных элементов, которые, хотя используются и не очень часто, но позволяют максимально просто и эффективно решать задачи, справиться с которыми при помощи других средств было бы очень трудно или вообще невозможно. В главе 20 рассказывается об этапах выполнения кода PL/SQL, об использовании памяти, описано различие между серверным и клиентским PL/SQL. В главе 21 рассматриваются объектно-ориентированные элементы Oracle (объектные типы
и объектные представления). А в главах 22 и 23 показано, как вызывать код Java и С из приложений PL/SQL.
Если вам уже приходилось заниматься программированием, но с PL/SQL вы еще не знакомы, прочтите нашу книгу от начала до конца. НЬ если вы являетесь профессиональным разработчиком приложений PL/SQL, то, возможно, вам будет достаточно ознакомиться лишь с отдельными ее разделами, с тем чтобы освоить новые технологии. Но независимо от того, в каком качестве 一 учебника или справочного пособия — вы будете использовать эту книгу, надеемся, что она поможет вам научиться с максимальной отдачей применять все возможности, предоставляемые PL/SQL.
Какие темы нами не рассматриваются
Какой бы объемной ни была книга, в ней невозможно рассказать обо всем, что касается излагаемой темы. Oracle - огромная и сложная система, и мы рассматриваем только входящий в ее состав язык PL/SQL. Ниже перечислены вопросы, которые, к сожалению, выходят за рамки нашего издания, поэтому мы упоминаем о них лишь поверхностно.
О Язык SQL. Мы предполагаем, что вы знаете язык SQL и даже имеете опыт его использования, в частности, можете составлять инструкции SELECT, INSERT, UPDATE и DELETE.
О Администрирование бг^ данных Oracle. Ознакомившись с книгой, администраторы баз данных, конечно же, почерпнут из нее много полезного и интересного, в частности научатся писать PL/SQL-программы для создания и обслуживания баз данных. Но по известной вам причине у нас не было возможности рассказать о нюансах языка определения данных (Data Definition Language, DDL), входящего в состав Oracle SQL.'
Q Оптимизация приложений и баз данных. Тема оптимизации также рассмотрена нами лишь поверхностно: некоторые из относящихся к ней вопросов (в частности, процессы трассировки, отладки.и оптимизации программ PL/SQL), освещаются в главе 19. Те же читатели, кому такого рода информация необходима, могут обратиться к другим книгам, специально посвященным оптимизации приложений Oracle.
Q Технологии разработки приложений Oracle, не зависимые от PL/SQL. Очень мало мы рассказали и о методике разработки приложений с помощью таких инструментальных средств, как, скажем, Oracle Forms Developer, и это несмотря на то, что в них также используется язык PL/SQL. Мы решили сконцентрировать ваше внимание на фундаментальных возможностях языка и на тех операциях, которые с его помощью можно выполнять в базах данных. Однако практически все сказанное здесь относится к использованию PL/SQL и в Forms Developer, и в Reports Developer.
Q Поддержка национальных языков в Oracle. В книге отсутствует и описание встроенных в Oracle средств поддержки национальных языков (National Тдп- guage Support, NLS), предназначенных для разработки приложений на разных языках.
Используемые соглашения
Нюке перечислены соглашения, которым мы старались строго следовать при изложении материала.
О Стиль interface. Так выделяются имена файлов, URL, элементы интерфейса и команды среды разработки.
О Стиль command. Так в книги выделяются фрагменты программного кода, элементы языка и различные идентификаторы.
О Курсив. Так выделяются новые термины и слова, на которых в тексте делается акцент.
О ВЕРХНИЙ РЕГИСТР. В примерах кода ключевые слова PL/SQL, как правило, представлены символами верхнего регистра.
О нижний регистр. Символами нижнего регистра в примерах кода набраны идентификаторы, определяемые пользователем, а именно имена переменных, параметров и т. п.
Знаки препинания. При наборе примеров кода знаки препинания нужно вводить в точности так, как указано в книге.
О Отступы. Используемые в примерах кода отступы помогают отразить структуру программ, но они не являются обязательными.
О Квадратные скобки ([ ]). В описаниях синтаксиса в квадратные скобки заключаются необязательные элементы.
Фигурные скобки ({}). В описаниях синтаксиса в фигурные скобки заключается набор элементов, из которых нужно выбрать только один.
Вертикальная черта (|). В описаниях синтаксиса вертикальная черта разделяет элементы, заключенные в фигурные скобки, например: {TRUE | FALSE}.
О Многоточие (...). В описаниях синтаксиса многоточие обозначает повторяющиеся элементы, а в примерах кода — фрагменты программ, которые не относятся к обсуждаемой теме и потому опущены.
ПРИМЕЧАНИЕ
данном абзаце обычно содержатся советы^ предложения и примечания. Таким образом может быть помечена информация, специфическая для конкретмой версии Grade.
ВНИМАНИЕ
Абзац с этим заглавием содержит всякого рода предупреждения. В частности, здесь могут упоминаться установки, которые оказывают отрицательное влияние на операционную систему или Grade.
Платформа и версия
Излагаемый материал и представленные в этом издании книги примеры, как правило, не относятся к какой-либо конкретной машине или операционной системе. Что касается версий Oracle, то в тех случаях, когда приведенное описание касается только Oracle8i или Огас1е9:, об этом говорится явно.
Существует множество версий PL/SQL, и не исключено, что вы будете пользоваться даже несколькими из них. В главе 1 опксываются наиболее широко используемые в настоящее время версии этого языка, о которых вам следует знать (см. раздел <Работа с несколькими версиями PL/SQL»).
О программном коде
Весь приведенный в книге программный код мы поместили и на ее web-страницу, представленную на узле издательства O'Reilly. Чтобы попасть на нее, воспользуйтесь адресом:
http://www.oreilly.com/catalog/oraclep3
и щелкните на ссылке Examples.
Как уже упоминалось выихе, помимо примеров по указанному адресу вы найдете несколько глав второго издания книги, изъятых из третьего издания или значительно сокращенных. Они могут быть особенно полезны читателям, которые работают со старыми версиями Oracle.
В тех случаях, когда приведенный в книге пример имеется на узле (совсем маленькие фрагменты кода мы на узел не помещали), в первой строке примера приведено имя соответствующего файла. Например: /* Файл в web: Файл.pkg */.
Комментарии и вопросы
Мы постарались самым тщательным образом проверить всю приведенную в книге информацию и протестировать программный код, но при таком объеме и разнообразии ипформации, при такой скорости развития новых технологий могли быть допущены некоторые ошибки. Если вы обнаружите таковые в тексте или примерах, пожалуйста, напишите нам об этом или сообщите по телефону.
Сообщаем наши координаты:
O'Reilly & Associates
1005 Gravenstein Highway
Sebastopol, CA 95472
8000-998-9938 (в США или Канаде)
707-829-0515 (международный или локальный)
707-8 29-0104 (факс)
Вы, конечно же, можете послать нам сообщение и по электронной почте. А если хотите, чтобы вас включили в список рассылки, или желаете заказать каталог, обязательно напишите по адресу:
info@oreilly.com
Задать вопросы, прислать собственные комментарии к книге можно по адресу: bookquestions@oreilly.com
Как упоминалось в предыдущих разделах, у книги имеется web-страница, где представлены программный код, обновленные ссылки, главы из предыдущих из- даннй и информация об ошибках. Адрес этой страницы;
ht^;//www.orei)ly.corn/catalog/oraclep3
За дополнительной информацией об этой и других книгах издательства O'Reilly можно обратиться по адресу:
http://www.orellly.com
Благодарности
За время, прошедшее после выхода из печати в 1995 году первого издания этой книги, мы получили столько откликов, что прежде всего хотим выразить признательность нашим читателям 一 за их поддержку, конструктивные замечания, за помощь в устранении замеченных ошибок.
Для того чтобы наша книга содержала точную, доступную и актуальную информацию, в нее вложен офомный труд, причем не только ее авторов, но и множества других специалистов по Oracle, сотрудников издательства O'Reilly & Associates, а также наших друзей и членов семей.
Ниже мы постарались перечислить всех, кто помогал нам в работе над третьим изданием книги. Далее приведены имена тех, кто внес значительный вклад в ее два первых издания.
Прежде всего мы хотели бы поблагодарить за большой вклад в создание этой книги четырех замечательных специалистов ио технологиям Oracle, а именно Джонатана Генника, Дарил Харлей, Брина Левелина и Стива Адамса. Джонатан существенно переработал пять старых глав, а Дарил написал интереснейшую главу о триггерах базы данных, помог нам рассказать о средствах поддержки национальных языков и стандартов в Oracle. Брин Левелин, менеджер продукта Oracle PL/SQL, предоставил важную информацию о новых функциях Oracle9z, отвечал на наши бесконечные вопросы о различных особенностях языка PL/SQL, проявляя при этом безграничное терпение, прорецензировал многие главы книги. Стив написал превосходный и достаточно подробный обзор всех этапов выполнения кода PL/SQL.
Для рецензирования такой большой книги потребовалось, как вы понимаете, немало специалистов, причем мы просили их протестировать каждый фрагмент кода и каждую программу, с тем чтобы в печатной версии осталось как можно меньше ошибок. Мы глубоко признательны следующим профессионалам по Oracle PL/SQL, которые, не жалея ни сил ни времени, работали вместе над совершенствованием нашего труда. Это Дон Бейле, Дик Болз, Дэн Клемидж, Стив Ксюнер, Жерард Хартгерс, Двейн Кинг, Лери Элкинс, Чандрасекхаран Ийер, Вадим Лоев- ски, Джованни Жаремильо, Ракеш Пател, Джеймс Пэдфилд, Питер Линслей, Кристофер Рекикот, Алекс Романкевич, Скотт Сауерс, Джей Томас, Эдвард Ван Хат- тен, Саймон Лаурент, Тони Грофорд, Джеф Честер и Андре Вергисон.
Но издание любой книги требует участия и других профессионалов. Команда литературных, технических и художественных редакторов во главе с нашим добрым другом Деборой Рассел сделала все возможное, чтобы книга получилась достойной столь уважаемого издательства, как O'Reilly. Огромная благодарность Робу Романо, создавшему превосходные иллюстрации, Джулии Фланеган, помощнику редактора, и всем остальным членам команды.
Ниже перечислены имена людей, которым мы очень благодарны за их вклад в первое и второе издания книги.
Дженифер Блейр, Ерик Камплин, Джо Селко, Авери Коен, Томас Дунбар, Р. Джеймс Форстайз, Майк Ганглер, Габриел Хофман, Карен Пейзер, Пит Шаффер, Девид Томпсон, Кейлейн Барклей, Сунил Бхаргава, Борис Бурштейн, Грей Глос- смаи, Радхакрипша Хари, Джеймс Мэлори, Нимеш Мехта, Джеф Мюллер, Дейв Поснер, Крис Рекикот, Питер Вастерд, Зона Валкотт, Сохаиб Абасси, Пер Брон- дум, Иван Чонг, Билл Двайт, Стив Эрлич, Бушан Фоутдар, Кэн Джакобс, Нимит
Мехта, Стив Мьюенч, Шри Раждан, Марк Рихтер, Билл Хннман, Тони Заэмба, Джон Кордел, Беверли Гибсон, Майк Сьерра, Гиги Эстабрук, Эдди Фидман, Донна Вунтейлер, Крис Рейли, Мишель Вилэй, Дебби Кунха, Микаэл Дойч, Джон Файлз, Джульетта Мьюельнер, Кори Виллинг, Сет Мешплин, Кисмет МакДона- хью Чен, Клэрмари Фишер О'Леари, Эрик Гилвер, Берт Скалзо, Джон Березневич, Том Уайт, Стив Хилкер, Томас Керейн, Шириш Пураник, Кэнон Мутхукарруп- пан, Джейн Эллин, Кимо Картер, Мадлен Ньюел, Роб Романо, Нэнси Прайест, Сет Мейшлин, Фред Полизо, Дональд Херкимер, Эрван Дарнел, Гарри Серносек, Патрик Прибил, Бил Уаткинс, Дебора Льюик и Лео Локк.
Напоследок Стивен благодарит свою жену, Веву Сильва, и двух сыновей, Криса Сильва и Эли, за их поддержку и терпение.
Билл выражает сердечную признательность своей семье за неизменную любовь и хорошее настроение, несмотря на его почти круглосуточную занятость. (Как часто в его доме можно было услышать фразу:《Мама, папа снова заснул за компьютером!»)
От издательства
Уважаемые читатели! Ваши замечания, предложения, вопросы отправляйте по адресу электронной почты comp@piter.com (издательство <Питер», компьютерная редакция).
Мы будем рады узнать ваше мнение!
Информацию о других книгах издательств «Питер» и «Издательская труппа BHV* вы найдете на web-узлах http://www.piter.com и http://www.bhv.kiev.ua.
Часть I
Программирование на PL/SQL
В первой части книги вы познакомитесь с языком PL/SQL, освоите его фундаментальные концв1щии, узнаете, как выполнять код PL/SQJ^ 冃 гласе рассказывается о создании PL/SQ^t его достоинствах и основных особенностях. Глава 2 поможет вам научиться писать и запускать простейшие программы. В ней содержатся инструкции по выполнению кода PL/SQJ^ в 5Q4 *Rus и некоторых других средах. В главе 3 рассматриваются структура и ключевые слова языка. Из нее вы узнаете, что такое оператор PL/SQLt что такое блок и какова его структура, как организуются комментарии к программам.
□ Глава 1. Введение в PL/SQL
П Глава 2. Написание и запуск кода PL/SQL
П Глава 3. Основы языка
Введение в PL/SQL
А История PL/SQL
А Так вот что такое PL/SQL
> О версиях PUSQL
А Ресурсы для разработчиков PL/SQ
А Несколько советов
PL/SQL — это аббревиатура от Procedural Language extensions to the Structured Query Language, что в переводе с английского означает «процедурные языковые расширения для SQL». SQL представляет собой повсеместно распространенный язык формирования запросов, с помощью которых можно получать и обновлять информацию в реляционных базах данных. PL/SQL был разработан компанией Oracle Corporation с целью преодоления некоторых ограничений SQL и обеспечения разработчиков приложений баз данных Oracle более совершенным инструментарием для программирования.
Перечислим основные особенности и достоинства PL/SQL.
О Высокоструктурированный, читабельный, удобный для сопровождения язык. Основанный на той же модели, что и язык Ада, PL/SQL, тем не мене% обладает всеми достоинствами современных языков программирования. Если вы начинающий программист, то постигать азы этой профессии с PL/SQL будет очень легко. Ну а опытному программисту освоить его синтаксис ничего не стоит. Программный код на языке PL/SQL легко читается, что значительно упрощает его сопровождение и поддержку — а это важная характеристика качественного программного обеспечения.
О Стандартизированный и переносимый язык разработки приложений для баз данных Oracle. Если вы создали процедуру или функцию на PL/SQL для базы данных Personal Oracle, находящейся на портативном компьютере, то эту же процедуру можно применить и для базы данных, установленной на компьютере корпоративной сети, причем без каких-либо изменений (конечно, при условии совместимости версий Oracle). «Написать один раз и использовать везде» —
таков основной принцип функционирования PL/SQL, принятый задолго до появления H3HKaJava. <Везде» в данном случае означает <при работе с любой базой данных Oracle».
О Встроенный язык. PL/SQL не используется как самостоятельный язык про- храммирования. Это встроенный язык, функционирующий только в конкретной <хост-среде». Программы на PL/SQL запускаются из базы данных (с помощью приложения SQL*Plus). Их можно также выполнять из формы, созданной в среде разработки приложений клиент-сервер Oracle Developer (клиентский PL/SQP). Однако создать исполняемый файл ггрохраммы на PL/SQL и запускать его автономно вы не сможете. О разработке и выполнении программ на PL/SQL рассказывается в главе 2.
О Высокопроизводительный, высокоинтегрированный язык доступа к базам данных. В настоящее время имеется множество средств для разработки программного обеспечения, взаимодействующего с базами данных Oracle. Например, можно использовать Java и JDBC (или SQLJ), Visual Basic и ODBC, а можно воспользоваться, скажем, Delphi и C++. Однако для написания высо- коэффектавного кода для доступа к базе данных Oracle больше всего подходит язык PL/SQL В частности, Oracle имеет несколько расширений, предназначенных специально для PL/SQL, таких как инструкция FORALL, позволяющая на порядок повысить производительность запросов.
История PL/SQL
В индустрии программного обеспечения компания Oracle является лидером в использовании декларативного, непроцедурного подхода к разработке баз данных и приложений. Технология реляционной базы данных Oracle Server считается наиболее прогрессивной, мощной и надежной. Средства разработки приложений от Oracle, подобные Oracle Forms, обеспечивают очень высокую производительность за счет применения визуального проектирования — подхода, основанного на определении большинства характеристик программ и их элементов по умолчанию, что избавляет программиста от огромного объема рутинной работы.
Истоки PL/SQL
Вначале Oracle-разработчиков в полной мере удовлетворял декларативный подход к созданию баз данных и приложений в сочетании с мощной и основательной реляционной технологией. Но с развитием баз данных возрастали и требования к средствам разработки. Все чаще у программистов стала возникать потребность «проникнуть внутрь» продуктов. Им нужно было интегрировать в хранимые процедуры и формы разнообразные и достаточно сложные формулы, исключения и правила.
В 1991 году была выпущена версия Огас1е6.0, что явилось важным шагом в развитии технологии баз данных Oracle. Ключевым компонентом новой версии стало так называемое процедурное дополнение, или PL/SQL. Примерно в то же время появилось долгожданное обновление для среды разработки SQL*Fonns
версии 2.3 (сейчас этот продукт называется Oracle.Forms). В SQL*Forms 3.0 был впервые интегрирован язык PL/SQL, позволяющий разработчикам создавать процедуры таким естественным способом, как программирование.
Возможности первой версии PL/SQL были очень ограниченны. На стороне сервера этот язык использовался только для написания сценариев пакетной обработки данных, состоящих из последовательности операторов и инструкций языка SQL. Иными словами, в то время еще нельзя было сохранять процедуры и функции для их дальнейшего применения. SQL*Forms 3.0 позволяла создавать процедуры и функции на стороне клиента, хотя поддержка функций еще не была документирована, и поэтому многие разработчики ими не пользовались. Кроме того, в этой версии PL/SQL не поддерживались массивы и отсутствовало взаимодействие с операционной системой (для ввода и вывода). Так что до полноценного языка программирования ему было еще очень и очень далеко.
Однако несмотря на все эти ограничения PL/SQL был принят разработчиками с большим энтузиазмом. Уж очень остро к тому времени стала ощущаться потребность хотя бы в элементарных средствах программного управления запросами, таких как условный оператор IF в SQL*Forms. Корпорация Oracle быстро определила слабые места SQL*Forms — недостаточную переносимость и наличие проблем, связанных с полномочиями пользователей на выполнение программ.
Повышение переносимости приложений
Тем, кто знаком с маркетинговой и технической стратегией Oracle Corporation, может, показаться странным то, что вообще мог возникнуть вопрос о недостаточной переносимости приложений. Ведь с начала 1980-х годов именно переносимость была одной из сильных сторон Oracle. Когда появился PL/SQL, реляционные СУБД (РСУБД) на основе языка С работали во многих операционных системах и на множестве аппаратных платформ. SQL*Plus и SQ,L*Forms легко адаптировались к разнообразным терминальным конфигурациям. Однако для решения большого числа задач по-прежнему требовались средства таких языков, как COBOL, С и FORTRAN. И стоило разработчику выйти за рамки инструментария Oracle, приложение тут же утрачивало переносимость.
Язык PL/SQL призван был расширить диапазон приложений, реализуемых с помощью не зависимых от операционной системы программных средств. В настоящее время для создания таких легко переносимых приложений активно используются специальные языки (например, Java). А PL/SQL по-прежнему занимает особую нишу как один из пионеров в этой области и применяется разработчиками для создания легко переносимого кода приложений баз данных.
Улучшенная защита приложений и защита целостности транзакций
Язык PL/SQL должен был обеспечить строгий контроль за выполнением логических транзакций. Одним из средств такого контроля являются полномочия на выполнение. Вместо разрешения на обновление отдельных таблиц пользователи получают разрешение только на выполнение процедуры, с помощью которой осуществляется доступ к необходимым структурам данных. Владельцем этой процедуры является пользователь с учетной записью РСУБД Oracle, который, в свою
очередь, может предоставлять разрешения на обновление таблиц, участвующих в выполняемых процедурой транзакциях. Таким образом, процедура становится 《привратником» транзакции. Единственным способом выполнения транзакции программой (будь то приложение Oracle Forms или исполняемый файл Рго*С) является вызов процедуры, и это гарантирует целостность транзакции.
Скромное начало, постоянное усовершенствование
Корпорация Oracle последовательно вводила в PL/SQL все новые и новые усовершенствования. Было разработано множество дополнительных пакетов, расширяющих функциональность PL/SQL. В частности, этот язык был дополнен объектно-ориентированными возможностями, разнообразными структурами данных (в том числе массивами), а также в значительной мере углублен и расширен.
Язык SQL, каким бы мощным он ни был в той области использования, для которой предназначен, не обладает программной гибкостью и мощью, необходимой для создания полноценных приложений. В то же время PL/SQL, оставаясь в пределах не зависимого от операционной системы окружение Oracle, позволяет разрабатывать выЬокоэффективные приложения, полностью удовлетворяющие потребностям пользователей.
Со времени своего появления PL/SQL прошел очень длинный путь. Разработчики, которые пользовались его первой версией, слишком уж часто вынуждены были говорить: <Этого PL/SQL не позволяете. Если же теперь вы столкнетесь с задачей, которую, как вам кажется, нельзя решить с помощью PL/SQL, то не отчаивайтесь. Копайте глубже, исследуйте возможности самого языка и встроенных пакетов-дополнений, и необходимые средства, скорее всего, найдутся.
Для знакомства с основами программирования на языке PL/SQL мы рассмотрим несколько примеров программ.
Так вот что такое PL/SQL...
Если вы только приступаете к программированию и еще, конечно же, не освоили ни PL/SQL, ни даже SQL, может показаться, что перед вами очень сложная задача. Но это не так. Она наверняка окажется гораздо более простой, чем вы думаете. Для нашего оптимизма есть два основания.
О Выучить компьютерный язык значительно проще, чем второй или третий <че- ловечесхий» язык. Почему? Да потому, что компьютеры не особенно умны (они «думают» - выполняют операции — быстро, но отнюдь не творчески). Чтобы указать компьютеру, что он должен делать, приходится пользоваться очень жестким синтаксисом. Следовательно, язык, на котором мы с ним общаемся, тоже очень жесткий (не допускает никаких исключений), и потому выучить его очень легко.
О Язык PL/SQL прост и в сравнении с другими языками программирования. В основе его структуры лежат «блоки» с разными разделами, четко идентифицируемыми с помощью ключевых слов.
Рассмотрим несколько примеров, в которых демонстрируются принципы применения ключевых элементов структуры и функционирования PL/SQL.
Интеграция с SQL
Одной из важнейших характеристик PL/SQL является его тесная интеграция с SQL. Для выполнения SQL-инструкций в программах на PL/SQL не требуется промежуточного программного обеспечения, например ODBC (Open DataBase Connectivity - открытый интерфейс доступа к базам данных) или JDBC (Java DataBase Connectivity — средство организации доступа Java-приложений к базам данных). Вы можете просто вставить инструкцию UPDATE или SELECT в программный код, как в приведенном ниже примере:
DECLARE
l_book_count INTEGER;
-
BEGIN
SELECT COUNK*)
IНТО l_book_count
FROM books -
WHERE author LIKE'«FEUERSTEIN,STEVENX';
DBMS_OUTPUT.PUT_LINE (
'Стивен является авторомилисоавтором' ||
l_book_count 11
' книг.'):
14
-- Я решил изменить написание своего имени...
UPDATE books
SET author = REPLACE (author. 'STEVEN'. 'STEPHEN')
WHERE author LIKE 'XFEUERSTEIN. STEVEN%':
END;■
Теперь давайте посмотрим, что делает этот код. Его подробное описание дано в следующей таблице.
Строки Списание
1-3 Раздел объявления так называемого анонимного блока PL/SQL, в котором объявляется целочисленная переменная для хранения данных о количестве кны「, автором или соавтором которых является Стивен Фернстайн. СПодробнее о структуре блока в PL/SQL рассказывается в главе 3)
4Ключевое слово BEGIN указывает на начало исполняемого раздела — кода,
который будет реализован при вводе данного блока в SQL*Plus
5-8 Делается запрос, определяющий общее количество книг, автором или соавтором которых является Стивен Фернстайн. Особенно интересна строка 6: используемое в ней предложение INTO на самом деле не является частью инструкции SQL, оно служит «мостом» между базой данных и /юкальными переменными PL/SQL
10-13 Для вывода количества блоков используется встроенная процедура DBMS_OUTPUT.PUT_LINE (то есть процедура из пакета DBMS_OUTPUT, входящего в состав Oracle)
15Однострочный комментарий, объясняющий назначение инструкции UPDATE
16-18 Чтобы изменить написание имени автора на Stephen, обновляется таблица книг.
Для поиска всех вхождений слова STEVEN и замены их на STEPHEN используется встроенная функция REPLACE
Последовательность и условия выполнения строк программы
PL/SQ.L содержит специальный набор операторов, предназначенных для управления последовательностью выполнения строк программы. В него входят такие операторы.
Q IF и CASE. Эти операторы реализуют логику условного выполнения. Например: «Если количество книг больше 1000, тогда...».
О Полный набор операторов для реализации циклической обработки и итераций. В их число входят операторы цикла FOR, WHILE и LOOP.
О Оператор GOTO. Да-да, в PL/SQL есть даже оператор GOTO, позволяющий выполнить переход из одного места программы в другое. Однако это не означает, что им следует пользоваться.
Приведем процедуру (многократно используемый блок кода, который можно вызывать по имени), демонстрирующую работу отдельных операторов:
CREATE OR REPLACE PROCEDURE pay_out_balance ('
account id in IN accounts.id2;fYPEj
IS ' "■
]_balance_rem3ining NUMBER:■
BEGIN 一
LOOP
1 balance_rema1ning :- account_balance (account_1d_in):
IF l_balance_remaining < 1000
THEN "-
EXIT;
ELSE
叩ply balance (account id_in. l_balance_remaining);
END IF;-
END LOOP:
END pay_out_balance;
Проанализируем этот блок кода, воспользовавшись следующей таблицей.
Строки
Описание
1,2
Заголовок процедуры, уменьшающей баланс банковского счета с целью оплаты счетов. В строке 2 задан список параметров процедуры, состоящий из одного входного значения (идентификационного номера банковского счета)
Х4
Раздел объявлений процедуры. Обратите внимание^ что вместо типичного ключевого слова DECLARE здесь для отделения заголовка процедуры от раздела объявлений используется ключевое слово IS (или AS)
6-15
Пример простого цикла LOOP. Окончание цикла определяется оператором EXIT LOOP (строка 15); в циклах FOR и WHILE условие окончания цикла задается по-другому
7
Вызов функции account_ballance, определяющей баланс счета. Это пример вызова одной многократно используемой программы из другой. Вызов одной процедуры из другой продемонстрирован в строке 13
9-14
Оператор IF, который можно интерпретировать так: «Если баланс счета окажется меньше 1000 долларов, прекратить оплату счетов. В противном случае оплатить следующий счет»
Когда что-то пошло не так
Язык PL/SQL предоставляет разработчикам мощный механизм обработки ошибок. В следующей процедуре мы получим имя и баланс счета на основе его идентификатора. Затем проверим баланс. Если он низок, явно инициируется исключение, которое прекращает выполнение программы:
CREATE OR REPLACE PROCEDURE checkjccount (
account id in IN accounts.idXTYPE)
IS ~
I_ba1ance_remain1ng NUMBER;
l_balance_be 1 ow_mirriinuiTi EXCEPTION;
1_account_nameaccounts .патеШРЕ;
BEGIN -
SELECT name
INTO l_account_name
FROM accounts
WHERE Id - account id in;
~
l_balance_remaining 产 account balance Caccountjdjn):
一 ~~
DBMS_OUTPUT.put_line (
'Баланс смета' jj ^account^ame ]]
' - ' 11 l_balance_remaining);
"
IF l_balance_rena1ning < 1000
THEN" -
RAISE 1 balance below minimum;
END IF:"~
23
EXCEPTION
WHEN NO_DATA FOUND
THEN "-
--Ошибочный идентификатор счета
log error (…);
"
WHEN 1_balance below_m1nlM
THEN -"
log error (...):
RAISE:
END:
Рассмотрим подробнее ту часть кода, которая связана с обработкой ошибок.
Строки
Описание
5
Объявляем собственное исключение с именем l_balance_below_mlnlmum. В Oracle имеется набор заранее определенных исключений, таких как DUP_VAL_ON_INDEX, но для данного приложения нужно нечто более специализированное
8-11
Запрос извлекает имя счета. Если счета с указанным идентификатором не окажется, Oracle инициирует стандартное исключение NO.DATA-FOUND, что ведет к завершению программы
19-22
Если баланс слишком низок, явно инициируется собственное исключение,
24
поскольку это свидетельствует о наличии серьезных проблем, связанньа со счетом Ключевое слово EXCEPTION отмечает конец исполняемого раздела и начало раздела исключений, в котором обрабатываются ошибки
Строки
Описание
25-28
Блок обработки ошибок для ситуации, когда счет не найден. Если инициировано исключение NO_DATA_FOUND, здесь оно перехватывается и ошибка записывается • в журнал
30-33
Блок обработки ошибок для ситуации, когда баланс счета оказался слишком низким (специальное исключение для данного приложения), Если инициировано исключение l_balance_below_mlnlmum, оно перехватыаается и ошибка записывается в журнал. Затем ввиду серьезности ошибки мы повторно инициируем то же исключение, в результате чего ошибка выйдет за пределы текущей процедуры, в блок PLVSQL, из которого эта процедура вызвана
Ознакомившись с этими примерами, вы получили общее представление о том, что собой представляет код PL/SQL, каковы его важнейшие синтаксические элементы и каким образом создавать и читать такой программный код. О механизмах обработки ошибок PL/SQL подробно рассказывается в главе 6.
О версиях PL/SQL
Каждая версия базы данных Oracle выходит с собственной версией PL/SQL, увеличивающей набор его функциональных возможностей. Поэтому одна из важнейших задач программиста, использующего PL/SOL, — следить за появлением очередных версий. Необходимо постоянно осваивать новые возможности каждой из них, чтобы знать, как ими пользоваться в приложениях, и решать, насколько они могут быть полезными при модификации уже существующих приложений.
Основные средства и возможности всех версий PL/SQL перечислены в табл. 1.1. Правда, приведен лишь их самый поверхностный обзор. Но в следующих разделах о нововведениях последних версий PL/SQL, входящих в Oracle8i и Oracle9t, рассказывается более подробно.
ПРИМЕЧАНИЕ
Каждый пакет Oracle Developer содержит собственную версию PL/SQL, которая обычно появляется через некоторое время после выхода самой СУБД. В этой главе (как и во всей книге) рассказывается прежде всего о программировании на стороне сервера.
Таблица 1.1. СУБД Oracle и соответствующие версии РЦ/SQL
Версия Oracle
Версия PL/SQL
Характеристики
Oracle6
1.0
Исходная версия PUSQL, использовавшаяся главным образом как язык сценариев в SQL*Plus (она еще не позволяла создавать многократно используемые вызываемые программы) и как язык программирования в SQL*Forms3.
Oracle?
2.0
Обновление версии 1. Добавлена поддержка хранимых процедур, функций, пакетов, определяемых программистом записей, таблиц PIVSQL и множества расширений, включая DBMS_OUTPUT и DBMS_PIPE
Orade7.1
2.1
Поддерживает определяемые программистом подтипы данных, позволяет использовать в SQL-инструкциях хранимые функции и поддерживает динамический SQL (посредством пакета DBMS_SQL). С появлением версии 2.1 стало возможным выполнять инструкции DDL из программ PL/SQL
продолжение^
Таблица 1.1 (продолжение)
Версия Версия
Oracle PL/SQL
Характеристики
Огас1е7.3 2.3
Расширяет возможности таблиц PL/SQL, включает улуншенную поддержку удаленного управления взаимосвязями между таблицами и курсорных переменных. Добавлены средства файлового ввода-вывода (пакет UTL_FILE)
OradeB 8.0
Новый номер версии отражает стремление Oracle синхронизировать номера версий PL/SQL с соответствующими номерами версий СУБД. PL/SQL 8 поддерживает многие усовершенствования Огас1е8, включая большие объекты (LOB), объектно-ориентированную структуру и определенные принципы разработки, коллекции (VARRAY и вложенные таблицы), а также средство Oracle/AQ (Oracle/Advanced Queuing), реализующее надежную систему организации очередей
OracieH B.l
Это первая из i-серий Oracle (базы данных для Интернета).
В соответствующую версию PL/SQL включен впечатляющий набор новых средств и возможностей, в том числе новая версия динамического SQL (встроенный динамический SQL), поддержка Java для доступа к базе данных, процедуры с правами вызывающего, возможность разрешения на выполнение, высокопроизводительный «пакетный» язык DML, запросы и автономные транзакции
Orade9i 9.0
Release 1
OradeSi появился вскоре после OracleBi. В его первой версии реализованы следующие возможности для разработчиков PL/SQL: поддержка наследования типов объектов, табличные функции и выражения с курсорами (предназначенные для распараллеливания процесса выполнения функций PL/SQL), многоуровневые коллекции (коллекции внутри коллекции), операторы и выражения CASE
Orade9i 9.2
Release 2
В самой последней версии Oracle, Oracle9i Release 2, главный акцент аделан на языке XML (Extensible Markup Language). Краме того, добавлены ассоциативные массивы (таблицы, которые можно индексировать не только целыми значениями, но и строками VARCHAR2), появилась возможность использовать основанный на записях язык DML (позволяющий, например, выполнять вставку с применением записи), полностью переписан пакет UTL_FILE, дающий теперь возможность копировать, удалять и переименовывать файлы
Новые возможности Oracle8i
Для многих разработчиков Oracle8i остается все еще относительно новым продуктом, так что мы сочли полезным описать некоторые из его наиболее важных нововведений. О них коротко рассказывается в следующих разделах, а более подробно —в указанных в этих разделах главах. ”
Автономные транзакции
Одной из давних потребностей разработчиков PL/SQL была возможность выполнять и затем сохранять или отменять инструкции DML (Data Manipulation Language — язык манипулирования данными) INSERT, UPDATE и DELETE безотносительно к основной транзакции сеанса. Это можно делать с помощью автономных транзакций.
Приведем несколько примеров, где применение автономных транзакций будет наиболее уместным.
О Механизм регистрации опшбок. Это классический пример использования автономных транзакций. Вам нужно записать информацию о зарегистрированной ошибке в таблицу базы данных, но вы не хотите, чтобы запись в журнал была частью логической транзакции.
О Многократно используемые компоненты приложения. Вы разрабатываете интернет-приложение. При этом нужно объединить компоненты от нескольких производителей и несколько программных уровней, обеспечить их взаимодействие строго определенным образом. Если завершение транзакции одним компонентом влияет на все остальные компоненты приложения, такое приложение не будет надежно функционировать в указанной среде. Эта проблема решается при помощи автономных транзакций.
Определяя блок PL/SQL (анонимный блок, процедуру, функцию, пакетную процедуру, пакетную функцию или триггер базы данных) как автономную транзакцию, вы изолируете инструкции DML в этом блоке от остальной части сеанса. Такой блок становится независимой транзакцией, начинаемой другой транзакцией, которая называется главной. В блоке автономной транзакции выполнение главной транзакции приостанавливается. После выполнения операций SQL вы сохраняете результаты или производите их откат, а затем возобновляете главную транзакцию.
Очень просто определить блок PL/SQL как автономную транзакцию. Для этого нужно только включить в раздел объявлений блока следующее выражение:
PRAGMA AUTONOMOUSJRANSACTION;
Механизм регистрации с использованием автономной транзакции позволяет сохранить изменения в журнале независимо от основной транзакции сеанса:
PROCEDURE wr1te_log (
code IN INTEGER, text IN VARCHAR2)
IS
PRAGMA AUTONOMOUS_TRANSACTION;'
BEGIN
INSERT INTO log VALUES (
code, text,'
USER. SYSDATE
):
COMMIT:
END;
Конечно, существуют определенные правила, которые при этом следует соблюдать. Они описаны в главе 13.
Процедуры с правами вызывающего
Когда-то, во времена Oracle? и Oracle 8.0, каждая вызываемая пользователем хранимая программа выполнялась от имени владельца программы и с его правами. Эта модель разрешений называлась правами создателя (definer rights). Однако лишь 0,5 % приложений создавались с одной учетной записью Oracle. В остальных 99,5 % случаев процесс регулирования прав создателя становился кошмаром для разработчиков, поскольку обычно код хранился в одной схеме и с помощью
инструкций GRANT EXECUTE разрешения на его выполнение предоставлялись другим пользователям (непосредственно или через роли).
Такое централизованное хранение кода не обеспечивает автоматического переноса прав пользователя (называемого вызывающим) на объекты кода. Например, может оказаться так, что пользователь не имеет права (привилегии) применить инструкцию DELETE к таблице, но вызванный им код имеет такое право, и удаление происходит! В одних случаях именно этого вы и хотите, но в других, особенно когда в программах используется динамический SQ.L, это может привести к крайне нежелательным последствиям.
В Oracle 8.1 PL/SQL был расширен таким образом, чтобы во время компиляции вы могли решить, должна программа (или все программы пакета) выполняться с правами ее создателя или того, кто ее вызывает.
Синтаксис, поддерживающий права вызывающего (invoker rights), очень прост. Продемонстрируем обычный механизм запуска процедуры rundl 1, основанный на новой динамической SQL-инструкции EXECUTE IMMEDIATE:
CREATE OR REPLACE PROCEDURE runddl (ddljn in VARCHAR2)
AUTHID CURRENT USER
IS
BEGIN
EXECUTE IMMEDIATE ddljn;
END: /
Предложение AUTHID CURRENTJJSER перед ключевым словом IS указывает, что процедура runddl должна выполняться с правами вызывающего или «текущего» пользователя, а не с правами создателя. Подробнее о моделях с правами вызывающего и правами создателя рассказывается в главе 20.
Динамический SQL
Со времени появления Oracle?. 1 разработчики PL/SQL могут использовать для реализации динамического SQL и PL/SQL встроенный пакет DBMS_SQL. Это зна- Чит】 что запрос типа DELETE TABLE или CREATE TABLE либо даже блок PL/SQL можно реализовать в виде строки непосредственно во время работы программы и тут же его выполнить. Динамический SQL исключительно удобен для разработки систем, позволяющих пользователю осуществлять произвольные запросы, при выполне- нии инструкций языка определения данных (Data Definition Language, DDL) из к°Да PL/SQL, а также в тех случаях, когда заранее не известно, какой запрос дол- жен быть произведен программой. Особенно часто динамический SQL используется в web-приложениях.
Однако с динамическим SQL, выполняемым средствами из пакета D8MS_SQL, свя- зан ряд проблем. К ним относятся, в частности, сложность пакета, способность работать только с типами данных СУБД Oracle 7, его небольшая скорость функционирования.
Поэтому сотрудтсики компании PL/SQL Central разработали еще одну реализацию динамического SQL прямо в языке PL/SQL, которая называется встроен- kw динамическим SQ£ (Native Dynamic SQL, NDS).
Для поддержки динамических многострочных запросов язык NDS добавляет в PL/SQL инструкцию EXECUTE IMMEDIATE и расширяет инструкцию OPEN FOR (для курсорных переменных). Следующая функция с помощью NDS возвращает набор строк из заданной таблицы:
CREATE OR REPLACE FUNCTION tabCount ( tab IN VARCHAR2, whr IN VARCHAR2 := NULL) RETURN INTEGER
IS
retval INTEGER;
BEGIN
EXECUTE IMMEDIATE
'SELECT COUNTX*)
FROM' II tab I-
'WHERE' И NVL (whr. '1-Г)
INTO retval:
RETURN retval;
END;
Если вы уже знакомы с пакетом DBMS_SQL, то, взглянув на данный код, без сомнения, вздохнете с облепением. Более подробный рассказ об этой замечательной возможности вы найдете в главе 15.
Пакетные операции
Среди важнейших задач команды разработчиков Oracle PL/SQL — повышение производительности языка. Годами жалуясь на недостаточную скорость выполнения кода, мы наконец получили долгожданный результат. (Хотя, возможно, заметное повышение производительности Oracle 8.0 связано отчасти с тем, что без существенной оптимизации языка нельзя было бы включить в него поддержку объектных типов.)
Одно из усовершенствований заключается в появлении возможности выполнять пакетный DML из PL/SQL. Рассмотрим в качестве примера следующий код, который удаляет записи о служащих, идентифицируемых с помощью номеров заданного вложенного списка:
CREATE TYPE empnos_list_t IS VARRAYdOOl OF NUMBER: /一
CREATE OR REPLACE PROCEDURE del_emps (listjn IN empnos_list_t)
IS~~~~■
BEGIN
FOR listnum IN 11st_in,FIRST..list_in.LAST
LOOP——
DELETE FROM emp WHERE empno - list_1n (llstnum);
END LOOP:"
END;.
Просто пишется, легко читается, но как насчет производительности? Ведь для каждой инструкции DELETE выполняется переключение контекста с PL/SQL на SQL. Если в списке 100 элементов, а следовательно, придется выполнить порядка 100 переключений, то это существенно отразится на производительности.
Ввиду того что подобные операции выполняются часто и с ними связаны значительные издержки, Oracle предложила нам пакетную разновидность цикла
FOR — инструкцию FORALL. С ее использованием приведенную выше процедуру del_emps можно переписать так:
CREATE OR REPLACE PROCEDURE de】_emps (llstjn IN empnos_Hst_t)
IS'
BEGIN
FORALL Hstnum IN llstjn.FIRST, .listjn.LAST
DELETE FROM emp WHERE empno - 11st_in (listnim): END;
Теперь количество переключений контекста уменьшится. Все операции DELETE будут объединены в одну пакетную операцию и переданы уровню SQL.
В дополнение к пакетной DML-инструкции FORALL в Oracle 8.1 введено предложение BULK COLLECT, являющееся разновидностью предложения INTO явного запроса. Эта операция позволяет извлечь несколько строк результатов запроса посредством одного переключения контекста.
Инструкция FORALL подробно рассматривается в главе 13, а предложение BULK COLLECT — в главе 14.
Новые возможности триггеров
В Oracle8i для администрирования базы данных и публикации информации о происходящих в ней событиях значительно шире стали применяться триггеры. Написав триггеры для новых системных событий и применяя их с приложением Oracle Advanced Queuing (AQ), вы сможете воспользоваться преимуществами новой технологии — публикации-подписки.
Функция публикации событий базы данных позволяет подписываться на события базы данных, равно как эти события могут подписываться на сообщения от других приложений. Синтаксис триггеров расширен для поддержки системных событий и других происходящих в базе данных или схеме событий, связанных с данными. Этот синтаксис теперь допускает вызов процедуры, являющейся телом триггера.
Программные триггеры можно определить для:
о инструкций DML (DELETE, INSERT и UPDATE);
О инструкций DDL (CREATE, DROP и ALTER);
О событий базы данных (SERVERERROR, LOGON, LOGOFF, STARTUP и SHUTDOWN).
В Oracle8z область применения триггеров заметно расширена.
• О Триггеры для столбцов вложенных таблиц. Операция CAST.. .MULTISET позволяет выполнять определяемые триггером действия только в ответ на изменение атрибута во вложенной таблице.
Q Триггеры событий уровня базы данных. Можно определить триггеры для реакции на такие системные события, как LOGON, DATABASE SHUTDOWN и даже SERVERERROR.
Q Триггеры еобытий уровня схемы. Можно определить триггеры для обработ- ки таких событий уровня пользователя или схемы, как CREATE, DROP и ALTER.
В главе 18 все эти новые функции триггеров рассматриваются наряду с традиционными возможностями триггеров DML, позволяющими им реагировать на события, которые совершаются при выполнении инструкций INSERT, DELETE и UPDATE.
Вызов Java из PL/SQL
Java 一 очень мощный язык и во многих отношениях более надежный, чем PL/SQL Он включает сотни классов, обеспечивающих понятный и удобный программный интерфейс приложения (Application Programming Interface, API). В Oracle8i и более поздних версиях появилась возможность вызывать из приложений PL/SQL хранимые процедуры Java, которые на самом деле являются хранящимися в базе данных Java-методами, Для этого создается «оболочка», или уровень PL/SQL, инкапсулирующий вызов Java-метода и делающий его доступным любой среде, из которой могут вызываться программы PL/SQL.
С помощью такой «оболочки» файлы можно удалять, например, непосредственно из кода программы PL/SQL:
CREATE OR REPLACE PACKAGE xflle
IS
FUNCTION delete (file IN VARCHAR2) RETURN INTEGER
AS LANGUAGE JAVA
NAME 'Jfilei.delete (Java.lang.String) return inf;
END:
Использование Java в базах данных Oracle - это очень обширная тема, включающая, безусловно, программирование на языке Java, о котором в настоящей книге речь не идет. Однако в главе 22 приводится необходимый объем информации для выполнения Java-кода из программ PL/SQL.
Новые возможности Oracle9i
Ряд замечательных нововведений в PL/SQL появился также с выходом версий Oracle9z Release 1 и Release 2. Мы поговорим о них в следующих разделах.
DML с ориентацией на записи
Начиная с Огас1е9г Release 2 в инструкциях INSERT и UPDATE можно использовать записи. Например:
CREATE OR REPLACE PROCEDURE set_book_info (
book in IN books^RQKTYPE)
IS ~
BEGIN
INSERT INTO books VALUES (book_in);
EXCEPTION~
WHEN OUP VAL ON INDEX
THEN 一 一
UPDATE books SET ROW = book_1n
WHERE isbn = bookjn.isbn;
END:
Очевидно, что работать так гораздо удобнее, чем задавать отдельные переменные или поля в записи. Преимущества очевидны.
О Код получается более лаконичным. Теперь мы можем работать на уровне записей. Больше не нужно объявлять отдельные переменные или, разделять запись на поля для передачи данных инструкции DML.
О Код получается более надежным. Если вы работаете с записями IROWTIPE, а не оперируете отдельными полями этих записей) то снижается вероятность возникновения ошибки в момент внесения изменений в таблицы и представления, на которых основаны эти записи.
Дополнительные сведения о языке DML, ориентированном на записи, вы найдете в главе 13.
Табличные функции
Табличная функция, вызываемая из предложения FROM запроса, возвращает результирующий набор строк данных в форме коллекции PL/SQL. Такие функции были доступны и в Огас1е8:, правда, с большими ограничениями) но в Oracle9t их использование значительно расширено. Перечислим дополнительные операции, которые теперь выполняются с их помощью.
О Возврат строк результирующего набора данных «конвейерным> способом. Данные возвращаются во время выполнения функции.
О Параллельное выполнение запроса. Функция может обрабатываться в нескольких «подчиненных» процессах, использующих разные данные.
Приведем пример заголовка конвейерной табличной функции, которая может работать s параллельном режиме и определена таким образом, что все строки для заданного отделения направляются одним «подчиненным» процессом и передаются последовательно:
CREATE OR REPLACE FUNCTION my_transform_fn (
p_1nput_rows in employee_info.recur_t)
RETURN employee^info.transformed_t
PIPELINED --
CLUSTER P_INPUT_ROWS BY Cdepartment)
parallelJnable
(PARTITION P_INPUT^ROWS BY HASH (department))
Дополнительную информацию о табличных функциях, конвейеризации и параллельном выполнении кода вы найдете в главе 16.
Новые и усовершенствованные типы данных
В последней версии Oracle кардинально улучшена поддержка отметок времени, а также расширены возможности управления временем с учетом часовых поясов н вычисления временных интервалов (времени между двумя датами или отметками времени). В следующем блоке отражены новые возможности этих типов данных и показано, как используются соответствующие функции: DECLARE
b05S_free TIMESTAMP(O) WITH TIME ZONE:
stevenjeaves TIMESTAMPCO) WITH TIME ZONE:
window_for_conversation INTERVAL DAY(3) TO SEC0NDC3);
BEGIN
boss free TO_TIMESTAMP_TZ (
'29-JAN-200212:00;万0US/Pac1f1cPST1.
1DD-MON-YYYYHH24:MI:SSTZRTZD');
steven_leaves :■ TO TIMESTAMP_T2 (
'29-JAN-200216:45:00US/CentralCST1.
'DD-MON-YYYYHH24:MI:SSTZRTZD')
wlndoWifojconversatlon :- stevenjeaves - boss_free:
D0MS_OUTPUT.PUT_LINE (
TO_CHAR (w1ndow_for_conversation, 'HH^LSSXFF')):
--Неявное преобразован雁 из TimeZone в Date...
DBMS_OUTPUT.PUT_LINE (ADDJONTHS (bossjree. -5)):
END;
А теперь поговорим еще об одном аспекте Oracle. Средства XML уже интегрированы прямо в базы данных, и для их поддержки имеется новый тип данных — XMLType (реализованный как тип объекта). СУБД Oracle явно ориентирована на усиленную поддержку этого языка, и встроенный тип данных XML должен установить связь между SQL и XML. В Огас】е9£ появилась возможность выполнять SQL-запросы к XML-документам, хранящимся в столбцах таблицы. Кроме того, для поиска содержимого этих документов можно использовать синтаксис XPath.
Приведем пример вставки XML-документа в таблицу (обратите внимание на использование метода CREATEXML объектного типа XMLType, предназначенного для преобразования XML-документа в строку таблицы):
INSERT INTO env analysis
VALUES ('ACME SILVERPLATING'.
TO DATE ('15-02-200Г. 'DD-MM-YYYY' xmltypeTcreatexml ( '<?xml vers1on=H],0"?>
<report>
<site>1105 5th Street</s1te> <substance>PCP</substance> <evel>1054</level>
</report>')):
Следующая инструкция CREATE с помощью синтаксиса XPath создает индекс из первых 26 символов имени клиента в заказе:
CREATE UNIQUE INDEX i_purchase_order_reference
ON purchaseorder p (
SUBSTR(XMLTYPE.GETSTRINGVAL(
XMLTYPE. EXTRACT p.PDDDCUMENT.
'/PurchaseOrder/Customer/text()')). 1,26)
Подробнее о типе данных XMLType рассказывается в главе 12.
Наследование объектных типов
Объектный тип (эквивалент «класса» в традиционной терминологии объектно- ориентированного подхода) впервые был введен в Огас1е8, но ему недоставало стандартных объектных функций, и прежде всего наследования.
В Огас1е9г появилась возможность определить иерархию объектных типов, в которой атрибуты и методы родительского типа наследуются подтипами-потомками. Следующие три определения объектных типов являются примером такой иерархии (пища, food, здесь является родительским типом; десерт, dessert 一 типом пищи, а пирожное, саке — типом десерта):
CREATE TYPE food_t AS OBJECT (
name VARCHAR2(100), food_group VARCHAR2 (100). grown_in VARCHAR2 (100), MEMBER FUNCTION price RETURN NUMBER )
NOT FINAL;
CREME ПРЕ dessert_t UNDER food_t (
contains_chocolate CHAR(l),“
year_created NUMBER⑷,
OVERRIDING MEMBER FUNCTION price RETURN NUMBER
)
NOT FINAL:
CREATE TYPE cake_t UNDER dessert_t C diameter KJMBER.
inscription VARCHAR2C200)
Для типа cake_t цена вычисляется не явно.
);"
/
Обратите внимание, что тип dessert_t подменяет функцию price корневого типа, а тип cdkjt наследует функцию price типа dessert_t-
С введением поддержки наследования появились более основательные причины для использования в приложениях объектных типов. Об объектных типах и наследовании рассказывается в главе 21.
Усовершенствования коллекций PL/SQL
Коллекция PL/SQL представляет собой массивоподобную структуру, позволяющую работать со списками информации. Эти списки могут состоять из простых данных, таких как строки, или более сложных структур, таких как записи. В Oracle9t возможности коллекций несколько расширены. Во-первых, введена поддержка многоуровневых коллекций, то есть коллекций внутри коллекций. Во-вторых, появилась возможность индексировать содержимое одного из типов коллекции — а именно ассоциативного массива, называемого также индексной таблицей, 一 не только целыми числами, но и строками.
Приведем пример использования строк в качестве значении индекса или идентификаторов строк в коллекции:
DECLARE
TYPE population^ IS TABLE OF NUMBER INDEX BY VARCHAR2(64): country_populatTon population^: continent_populat1on population^; howmany NUMBER: rowjd VARCHAR2(64):
BEGIN
country_populat1on('Colymph1a,) :- 100000;
country_population('Ribalizia') := 750000;
howmany :- country_population('Co^ymphla'): rowjd :- continent_populatlon.FIRST;
DBMS_OUTPUT.PUT_LINE (continent_population(rowjd)):
END: ~一
/
Многоуровневые коллекции позволяют эмулировать многомерные массивы, а также другие сложные типы данных. Подробно о них будет рассказано в главе И.
Встроенная компиляция кода PL/SQL
До появления Oracle9i исходный код PL/SQL всегда преобразовывался в специальное представление, обычно называемое байт-кодом (bytecode), которое хранилось в базе данных и интерпретировалось во время выполнения виртуальной машиной, входящей в состав Oracle. В Oracle9z введен новый подход. Теперь исходный код PL/SQL можно откомпилировать во встроенный объектный код, интегрированный в Oracle. Такая компиляция позволяет заметно повысить производительность приложений, что особенно важно для программ, в которых осуществляется большое количество вычислений, но не так отражается па выполнении SQL-запросов. О встроенной компиляции кода подробно рассказывается в главе 19.
Работа с несколькими версиями PL/SQL
Следует всегда знать, с какой версией Oracle вы работаете, — лишь при этом условии можно оптимальным образом использовать поддерживаемые ею возможности. Иногда для работы в разных версиях Oracle приходится писать специальные программы на PL/SQL. В этом случае можно пойти двумя путями.
О Избегать использования каких-либо функций PL/SQL, появившихся в более поздних версиях Oracle, чем те, для которых пишется программа. Этот подход называется «наименьшим общим знаменателем».
О Поддерживать несколько версий программы — по одной для каждой версии Oracle (если вы хотите пользоваться теми новыми функпиями, которые появились в более поздних версиях). Такой код трудно реализовать, но когда это все же необходимо, попробуйте воспользоваться сценарием файла oneversion.sql, имеющегося на web-узле издательства O'Reilly. Этот сценарий позволяет определить версию Oracle и с помощью переменных подстановки SQL*Plus включать и отключать соответстаующие части программного кода.
Ресурсы для разработчиков PL/SQL
Первое издание данной книги вышло в 1995 году. Тогда это событие стало настоящей сенсацией - впервые была написана книга о PL/SQL, издание которой не связано с компанией Oracle. Ее давно ожидали разработчики во всем мире. С тех пор появилось множество PL/SQL-ресурсов, среди которых различного рода
публикации, среды разработки, утилиты и web-узлы. Однако настоящая книга по-прежнему остается самым важным и значительным из них.
О важнейших из перечисленных ресурсов коротка рассказывается в следующих разделах. Пользуясь этими ресурсами, многие из которых относительно недороги, а часть вообще предоставляется бесплатна, вы сможете значительно повысить свою квалификацию.
Книги о PL/SQL от O'Reilly
В настоящее время серия книг о PL/SQL издательства O'Reilly & Associates представлена уже довольно внушительным списком. За более подробной информацией об этих изданиях вы можете обратиться на web-узел издательства, расположенный по адресу: http://oracle.orellly.com.
О Oracle PL/SQJL Programming (авторы Steven Feuerstein, Bill Pribyl). Книга, которую вы сейчас читаете. Это настольная книга для многих профессиональных программистов PL/SQL, в которой рассматривается каждый элемент базового языка PL/SQL. Во втором издании описывались версии Oracle вплоть до Огас1е8, а в настоящем, третьем, издании — вплоть до версии Огас1е9:.
О Learning Oracle PL/SQJL (авторы Bill Pribyl, Steven Feuerstein). Упрощенное введение^ язык PL/SQL для новичков в программировании и для тех, кто переходит к PL/SQL от других языков программирования.
О Oracle PL/SQL Best Practices (автор Steven Feuerstein). Небольшая книжка, включающая в себя более 100 лучших советов, рекомендаций, приемов, которые помогут читателю повысить свои навыки в разработке программ на PL/SQL. Эта книга является чем-то вроде краткого конспекта по PL/SQL. В ней рассматриваются возможности языка до версии Огас1е8: включительно.
О Oracle PL/SQL Developer's Workbook (авторы Steven Feuerstein, Andrew Ode- wahn). Содержит серию вопросов и ответов, помогающих разработчику проверить и дополнить свои знания о языке. В книге рассматриваются возможности языка вплоть до версии Oracle8i.
Q Oracle PL/SQJL Language Pocket Reference (авторы Steven Feuerstein, Bill Pribyl, Chip Dawes) и Oracle PL/SQJL Built-ins Pocket Reference (авторы Steven Feuerstein, John Beresnievicz, Chip Dawes). Два маленьких карманных справочника по Огас1е8г и Огас1е8 соответственно.
Q Oracle PL/SQJL Built-in Packages (авторы Steven Feuerstein, Charles Dye, John Beresnievicz). Справочное руководство по всем стандартным пакетам, входящим в комплект поставки Oracle. Эти пакеты позволяют упростить трудное задание, а иногда даже выполнить невозможное. Рассматриваются версии до Огас1е8 включительно.
° Oracle PL/SQJL Programming: Guide to Oracle8i Features (автор Steven Feuerstein). Дополнение к книге Oracle PL/SQL Programming, содержащее обзор новых элементов PL/SQL, появившихся в Огас1е8г.
О Advanced Oracle PL/SQJ. Programming with Packages (автор Steven Feuerstein). В книге рассказывается о том, как можно усовершенствовать свои программы путем разработки собственных пакетов PL/SQL. Рассматривается Oracle?.
О Oracle Web Applications: PL/SQJ. Developers Introduction (автор Andrew Ode- wahn). Книга, призванная помочь читателю научиться создавать web-приложения баз данных. Содержит некоторый исторический материал о PL/SQL и программировании для Web. Рассматриваются версии до Огас1е8Г включительно.
О Oracle PL/SQJ. CD Bookshelf. Компакт-диск, который содержит электронные версии большинства перечисленных выше книг, а также копию книги Guide to Oracle8i Features. Рассматриваются версии до Oracle8z включительно.
Другие печатные ресурсы
Помимо книг издательства O'Reilly, перечисленных в предыдущем разделе, существуют и другие печатные ресурсы, о которых вам следует знать.
О Документация Oracle. Со времени появления первых версий Oracle с фрагментарной и откровенно слабой документацией многое изменилось. HTML- документы, входящие в состав СУБД, являются прекрасным источником информации об огромном количестве технологий, предоставляемых сейчас Oracle.
О Книги Oracle Press и других издательств. Oracle Press выпускает собственную серию книг о PL/SQL, написанных Скоттом Урманом. Другие издательства технической литературы предлагают как вводные описания PL/SQL; так и более полные книги по Oracle.
Q Oracle Professional от Pinnacle Publishing. Этот печатный ежемесячный информационный бюллетень (и его online-версия) содержит материалы по Oracle. В ка' ждом номере имеется статья о PL/SQ.L (ее автором обычно является Стивен Фернстайн), в которой рассматриваются какие-то новые возможности или приложения PL/SQL. Обращайтесь по адресу: http://www.oracleprofesslonalnewslet- ter.com/.
О Oracle Magazine от Oracle Corporation. Ежемесячное издание, охватывающее широкий спектр технологий Oracle. Oracle Magazine (http://www.oramag.com/) часто освещает новые функции PL/SQ.L и помогает понять принципы взаимодействия различных компонентов Oracle.
PL/SQL в Интернете
Для программистов, работающих с PL/SQL, существует несколько превосходных web-узлон.
О Oracle Technology Network. Посетите web-узел Oracle Technology Network (OTN), иногда называемый TechNet, где «имеются сервисы и ресурсы, необходимые разработчикам для создания, тестирования и распространения приложений» на основе технологии Oracle. Он знаком миллионам разработчиков: здесь можно загрузить программное обеспечение, документацию и код множества примеров. Итак, обращайтесь по адресу: http://technet.us.orade.com/ или прямо к странице PL/SQL: http.7/otn.orade,com/tech/pl_sq)/content.htm)1
О PL/SQJ. Pipeline. Мы также рекомендуем присоединиться к PL/SQf. Pipeline, «свободному сообществу интернет-портала, поддерживаемого компанией Quest Software. Этот узел предназначен для информирования, обучения и поддержки профессионалов в области информационных технологий со всего мира. Его регулярно просматривают множество известных авторов и экспертов». PL/SQL Pipeline содержит архивы с учебным материалом и полезным кодом. На форуме ведутся дискуссии, предусмотрены также бесплатные консультации для разработчиков и администраторов баз данных. Его адрес: http://www.quest-pipe- lines.com.
q PL/Net.org. Узел содержит <собрания» программного обеспечения с открытым кодом, которое либо написано на PL/SQL, либо используется разработчиками, отдающими предпочтение этому языку. Здесь вы можете найти его более подробное описание, просмотреть ответы на часто задаваемые вопросы (FAQ). Еще на данном узле имеются ссылки на множество полезных утилит, таких как utPLSQL — среда для тестирования модулей. Его адрес: http://plnet.org
О Open Directory Project. На узле проекта «dmoz» (Directory МогШа) по адресу http://dmoz.org/Computers/Programming/Languages/PL-SQL/ вы найдете ссылки на узлы, посвященные PL/SQL. Здесь, в частности, имеется набор ссылок на коммерческие и некоммерческие средства разработки (категория Developer Tools). Узел поддерживается командой редакторов-волонтеров.
О Oracle FAQ. Этот узел содержит «независимую и полезную информацию о продуктах Oracle. Вы найдете здесь FAQ, сценарии, советы, новости, решения для некоторых задач, каталоги, форумы и чаты, а также многое другое, что сделает вашу работу с Oracle более успешной». Для этого вам нужно обратиться по адресу http:// www. orafaq. com/faq. htm.
Средства разработки и утилиты
Создавая программное обеспечение в программе Блокнот (в гетлоязычной версии Windows — Notepad) и затем запуская и тестируя код исключительно в SQL*Plus, вы теряете много времени. Вы теряете его еще больше, если отлаживаете программы путем вставки в них бесконечного количества вызовов продедуры DBMS_ OUTPUT.PUT_LINE, вместо того чтобы использовать удобные и эффективные средства отладки исходного кода.
На сегодня в распоряжении разработчиков PL/SQL имеется огромный выбор разнообразнейших вспомогательных средств, облегчающих работу, — от дорогостоящих коммерческих интегрированных сред разработки (Integrated Develop- nient Environment, IDE) до недорогих замен для SQL*Plus и небольших утилит для выполнения отдельных задач.
В этой книге вы не найдете обзор всех указанных средств, равно как и рекомендации по их выбору. Приведем лишь краткий список продуктов и URL, с которых вы можете начать самостоятельное исследование имеющихся возможностей.
Q TOAD (Tool for Oracle Application Developers). Самая популярная среда разработки для PL/SQL. Она полюбилась разработчикам благодаря наличию браузера со вкладками. TOAD доступна как в коммерческой, так и в бесплатной версиях. Обращайтесь по адресу: http://www.quest.com/toad.
О SQL Navigator. Производитель этой среды разработки тот же, что и предыдущей (Quest). Пользователи ценят ее в первую очередь за фундаментальную поддержку Java для PL/SQL. Вы получите эту IDE, обратившись по адресу: http://www.quest.com/sql_navigator.
О PL/SQL Developer. PL/SQL Developer - прекрасное и относительно недорогое ПО с дружественным интерфейсом, которое включает множество функций. Позволяет сторонним разработчикам создавать и подключать компоненты, расширяющие его возможности. Адрес web-узла, где представлен этот продукт: http://www.allroundautomatlons.nl/plsqldev.html.
О TOra (Toolkit for Oracle). Набор инструментальных средств, который включает в себя браузер, редактор PL/SQL и отладчик для Linux или Windows. ТОга доступен в двух версиях — коммерческой и версии с открытым кодом. Обращайтесь по адресу: http://www.globecom.se/tora
Несколько советов
С 1995 года, когда вышло в свет первое издание настоящей книги, у нас появилась возможность обучать десятки тысяч разработчиков PL/SQL, помогать им и даже сотрудничать с ними. За это время мы многому научились и у наших читателей, составили более полное представление об оптимальной форме подачи материала. Теперь мы хотим поделиться с вами рядом соображений, благодаря которым вы сможете эффективнее работать с таким мощным языком программирования.
Не стоит торопиться!
Мы почти всегда работаем в очень жестких временных рамках, ведь, как правило, за короткое время нужно написать огромное количество кода. Получая очередное задание, мы должны немедленно приступать к работе — не так ли?
Нет, не так. Если сразу же углубиться в написание программного кода, механически превращая постановку задачи в сотни, тысячи и десятки тысяч строк, получится просто «каша», которую не удастся ни отладить, ни снабдить соответствующим сопровождением. Но в жесткий график вполне можно уложиться, если тщательно все спланировать.
Мы настоятельно рекомендуем вам не поддаваться панике, расслабиться и, прежде чем приступать к написанию нового приложения или программы, провести подготовительную работу.
L Создайте сценарии тестирования. До того как будет написана первая строчка кода, вы должны определить, каким образом будет проверяться и насколько успешна реализация поставленной задачи. При таком подходе следует заранее тщательно продумать интерфейс программы и ее функциональные элементы.
Установите четкие правила написания SQL-инструкций в приложении. В общем случае мы рекомендуем разработчикам не включать в программы много
SQL-инструьсций. Большинство инструкций, в том числе однострочные запросы на обновление и вставку, должны быть《скрыты» в заранее написанных и тщательно отлаженных процедурах и функциях (это называется инкапсуля^ цией данных). Такие программы оптимизируются, тестируются и сопровождаются более эффективно, чем разбросанные по программному коду SQL-инструкции (многие из которых по многу раз повторяются в приложении).
Установите четкие правила обработки исключений в приложении. Имеет смысл создать единый пакет обработки ошибок, который содержал бы все детали ведения журнала ошибок, определяющего, как исключения инициируются и распространяются во вложенные блоки, и позволяющего избежать ввода специфических для приложения исключений прямо в программный код. Этим пакетом должны пользоваться все разработчики - в таком случае им не придется писать сложный, и забирающий много времени код обработки ошибок.
Выполняйте «пошаговую проработку#. Другими словами, придерживайтесь принципа нисходящего проектирования, что позволит уменьшить сложность кода, с которым вы имеете дело в каждый конкретный момент. Применив данный подход, вы обнаружите, что исполняемые разделы ваших модулей становятся короче и проще для понимания. В будущем такой код будет легче сопровождать и модифицировать. Важнейшую роль в реализации этого принципа играет использование локальных, или вложенных, модулей,
Это лишь несколько важных моментов, на которые следует обратить внимание, приступая к написанию программного кода. Помните, что при разработке программного обеспечения спешка приведет к массе ошибок и огромным потерям времени.
Не бойтесь обращаться за помощью
Если вы профессиональный разработчик программного обеспечения, то наверняка очень умны, много учились, повышали свою квалификацию и теперь отлично зарабатываете, создавая программный код. Вам. под силу решить практически любую задачу, и вы очень этим гордитесь. Но, к сожалению, ваши успехи делают вас слишком самоуверенным и вы терпеть не можете обращаться за помощью, даже в случае возникновения серьезных затруднений. Но такое отношение к делу опасно и деструктивно.
Программное обеспечение пишется людьми. И поэтому важно понимать, что огромную роль в его разработке играет человеческая психология. Приведем лишь один пример. Джо)ведущий разработчик в команде из шести программистов, столкнулся с затруднением при решении своей задачи. С нарастающим раздражением он несколько часов анализировал проблему, но не мог найти причину ошибки. При этом ему и в голову не приходило попросить о помощи кого-нибудь из коллег, потому что все были менее опытны. Наконец, после долгих усилий, он сдался и обратился по телефону к сотруднице: «Сандра, не могли бы Вы зайти и взгля- нутв на мою программу? У меня проблема, которую я не могу решить.» Сандра подходит к его компьютеру и с первого взгляда замечает то, что он и сам должен был бы давно увидеть.
Ура! Ошибка в программе исправлена, и можно продолжать работу. Джо выражает признательность, но в глубине души он очень смущен. В голове у Джо крутятся мысли: ^Почему же я не увидел этого раньше?» и «Если бы я потратил еще минут пять, я бы и сам обнаружил ошибку». Это понятно, но довольно глупо. Очень часто мы не можем найти источник проблемы из-за того, что слишком погружены в свой код. Иногда нужен просто свежий взгляд со стороны, и это не имеет ничего общего ни с опытом, ни с особыми знаниями.
Именно поэтому мы настоятельно рекомендуем придерживаться следующих простых правил.
О Не бойтесь признаться себе в том, что чего-то не знаете, Если скрывать, что вам не все известно о приложении или его программном коде, это не приведет ни к чему хорошему. Выработайте культуру взаимоотношений в коллективе, при которой вполне естественными считаются вопросы друг к другу и обоюдные консультации.
О Обращайтесь за помощью. Если вы не можете найти причину ошибки в течение получаса, немедленно просите о помощи. Можно даже организовать систему взаимопомощи, предусматривающую назначение каждому работнику напарника, к которому всегда можно обратиться. И не позволяйте себе или еще кому-либо в группе часами биться головой об стену, пытаясь самостоятельно выйти из затруднительного положения..
О Организуйте процесс рецензирования кода. Не допускайте использования кода без его предварительного прочтения и конструктивной критики одним или несколькими разработчиками.
Поощряйте творческий, и даже радикальныйг подход к разработке кода
Мы склонны превращать в рутину практически все составляющие нашей жизни, выбираем для чего-либо один привычный способ действия. Мы пишем код определенным образом, делаем определенные предположения о продукте, отвергаем возможные решения, не проанализировав их как следует, поскольку заранее знаем, что это не может быть выполнено. Разработчики очень необъективны в отношении собственных программ и порой умаляют их достоинства. От них часто можно услышать такие высказывания:《Зтот код не будет работать быстрее», <Эта программа не может функционировать так, как хочет пользователь; придется подождать следующей версии», «Если бы я использовал продукт X, Y или Z, все делалось бы легко и быстро. А с такими средствами приходится буквально сражаться за каждую мелочь».
Но на самом деле практически любая программа может работать немного быстрее, делая именно то, что хочет пользователь. И хотя у каждого продукта, конечно же, имеются свои ограничения, сильные и слабые стороны, не нужно ждать выхода его следующей версии. Лучше встретить проблему лицом к лицу и, не позволяя себе никаких оправдании, найти ее решение.
Как же этого добиться? Следует отказаться от некоторых из своих представлений и посмотреть на мир свежим взглядом. Пересмотрите выработанные годами профессиональные привычки. Отнеситесь к задаче творчески — постарайтесь отступить от традиционных методов, от зачастую ограниченных и механистических подходов.
Не бойтесь экспериментировать, даже если сочтете свои идеи радикальным отступлением от нормы. Вы будете удивлены тем, как многому можно научиться таким образом, вырастете как программист, способный к решению нестандартных задач. Многое становится возможным, когда вы отбрасываете мысль о неприемлемости какого-либо решения, а, наоборот, пытаетесь найти совершенно новый подход.
22
2
2
Написание и запуск кода PL/SQL
A SQL*Plus
А Выполнение базовых операций PL/SQL
А Средства разработки для Oracle PL/SQL
А Вызов PL/SQL из других языков
А Что же дальше
Превде чем вы приступите к изучению принципов программирования на PL/SQL, вам потребуются некоторые сведения о том, как компилировать и запускать свои программы. В этой главе приводится обзор основных средств, включая SQL*Plus, с помощью которых можно вызывать программы, написанные на PL/SQL. Данный материал поможет вам начать практическую работу с этим языком.
Если вы уже овладели в некоторой степени навыками программирования баз данных, то, вероятно, знаете, что SQL (Structured Query Language 一 структурированный язык запросов к базам данных) имеет широкий спектр применения, используется разными способами и в разных ситуациях. Сказанное относиться и к PL/SQL, команды которого можно не только вызывать из множества других языков, но и выполнять в двух различных средах:
О на сервере баз данных Oracle как хранимый код (так можно вызывать программы на PL/SQ.L из SQL*Plus, Java или другой среды);
Q в одной из сред разработки приложений Oracle, такой как Oracle Forms или Oracle Reports, в качестве программы, которая выполняется на клиентском компьютере.
Давайте начнем с первой возможности - запуска на сервере Oracle. Тут имеются дополнительные варианты выбора клиентской среды, из которой загружайся ваш код. Среди наиболее популярных средств программирования можно назвать следующие:
Q стандартный интерфейс командной строки SQL*Plus, связанный с сервером. Oracle, на котором и выполняются операторы PL/SQL;
О другой язык программирования, в том числе С( C++, Java, Visual Basic, COBOL, ADA и FORTRAN, для которого Oracle предоставляет библиотеку времени выполнения и/или прекомпилятор, позволяющий включать в программу код SQL и PL/SQL.
Поскольку средств выполнения кода PL/SQL очень много, вы, скоре всего, будете пользоваться одним или двумя из них. Поэтому прежде всего в этой главе рассматривается приложение SQL*Plus, хотя приводятся и примеры использования PL/SQL из разных сред.
SQL*Plus
Дедушка клиентских средств Oracle - приложение SQL*Plus - представляет собой интерпретатор для SQL и PL/SQL с интерфейсом командной строки. Это означает, что оно принимает от пользователя инструкции для доступа к базе данных и направляет их серверу Oracle, а результаты отображает на экране монитора.
Примитивность пользовательского интерфейса SQL*Plus часто служит причиной того, что о нем отзываются пренебрежительно, а между тем это одно из самых удобных средств выполнения кода для Oracle. Здесь отсутствуют всякие забавные штучки и сложные меню. Когда автор этой книги только начинал работать с Oracle (примерно в 1986 году), предшественник SQL*Plus гордо назывался UFI — User Friendly Interface (дружественный пользовательский интерфейс). И хотя сейчас, два десятилетия спустя, даже версия SQL*Plus для Oracle9i едва ли завоюет награду за дружественный интерфейс, она, по крайней мере, не слишком часто дает сбои.
Приложение SQL*Plus можно запустить разными способами.
О Как консольную программу1. Программа выполняется из оболочки или командной строки (окружения, которое иногда называют консолью).
Q В качестве программы с псевдографическим пользовательским интерфейсом. Эта разновидность SQL*Plus доступна только в Microsoft Windows. Ее называют псевдографическим интерфейсом, поскольку она очень похожа на консольную программу, отличаясь от таковой, кроме прочего, наличием растровых шрифтов. Oracle фактически сворачивает поддержку данного продукта, и даже его «расширенная» поддержка завершается в сентябре 2005 года,
О Через iSQJL*Plus (в Oracle9i или более поздней версии). Программа выполняется из web-браузера машины, на которой работает HTTP-сервер Oracle и сервер iSQL*Plus,
О Через SQL*Plus Worksheet. Графический пользовательский интерфейс Java с консольной версией SQL*Plus. Хотя он поддерживает небольшой журнал инструкций, ничего примечательного в этой версии среды разработки нет.
Oracle называет это версией SQL*Plus с интерфейсом командной строки, но мы полагаем, что данное определение не однозначно, поскольку шгтерфсйс командной строки предоставляют два из четырех способов реализации SQL*Plus.
SQL> SQL> SQL> SQL> 2SQL> SQL> SQL> SQL> 2&nd lawBrC&uthor) like J Kprihyl/1;&nd lawBrC&uthor) like J Kprihyl/1;ISBNISBNAUTHORAUTHORTITLETITLEО-596-Ш3381-1О-596-Ш3381-1Feuarstein, Stsusn, with Bill PrlbylFeuarstein, Stsusn, with Bill PrlbylOfacIe PL/SQL PrograminlnsOfacIe PL/SQL Programinlns0-59G>(9018O-00-59G>(9018O-01 with Sceusn1 with SceusnLearning Oracle PL/SQLLearning Oracle PL/SQL
SQL> SQL> SQL> SQL> 2
SQL> SQL> SQL> SQL> 2
&nd lawBrC&uthor) like J Kprihyl/1;
&nd lawBrC&uthor) like J Kprihyl/1;
ISBN
ISBN
AUTHOR
AUTHOR
TITLE
TITLE
О-596-Ш3381-1
О-596-Ш3381-1
Feuarstein, Stsusn, with Bill Prlbyl
Feuarstein, Stsusn, with Bill Prlbyl
OfacIe PL/SQL Programinlns
OfacIe PL/SQL Programinlns
0-59G>(9018O-0
0-59G>(9018O-0
1 with Sceusn
1 with Sceusn
Learning Oracle PL/SQL
Learning Oracle PL/SQL
Главное окно консольной версии SQL*Plus показано на рис. 2.1. Обычно консольную программу предпочитают остальным по следующим причинам:
О она быстрее перерисовывает экран, что важно при выполнении сложных запросов;
О у нее более полный журнал команд, используемых ранее в командной строке (по крайней мере на платформе Microsoft Windows);
О в ней проще менять такие визуальные характеристики) как шрифт, цвет и размер буфера прокрутки;
О при работе с ней используется командная строка.
;,:Uqlpkis.bul - Fer
Прмсоакмнвн к: 0racls9i Release 9.0.1.1.1 - Production JSsrusr Rslease 9.0.1.1.1 - Production
set linssize 94 pagesize 999 edLunn author Femat вЗР uard_Mi*ap colunn title fomat tt3Q WDrd_wrau select isbn. author, title fron books where lower(au.tbar) like * zfeuerstexnXJ
1-5Б592-457-61-5Б592-457-6Feuerstein, Stsuan, Bill PHbyL Chip DawesFeuerstein, Stsuan, Bill PHbyL Chip DawesOracle PL/SQL Language Packet Reference
1-5Б592-457-6
1-5Б592-457-6
Feuerstein, Stsuan, Bill PHbyL Chip Dawes
Feuerstein, Stsuan, Bill PHbyL Chip Dawes
Рис. 2.1. Окно приложения SQL* PI us в консольном сеансе
Отметим, что iSQL*Plus автоматически форматирует выходные данные запроса в виде HTML-таблиц. Это особенно удобно, когда нужно вывести множество столбцов таблицы базы данных (ниже будет приведен пример).
Запуск SQL*Plus
Для того чтобы запустить консольную версию SQL* Plus, нужно просто ввести команду sql pl us в ответ на приглашение операционной системы 0S>. Это делается как в операционных системах Unix, так и в операционных системах компании Microsoft. Далее следуют текст, а также строки для ввода имени пользователя и пароля;
SQL*P1us: Release 9.2.0.1.О - Production on Thu Jun 20 10:42:22 2002
Copyright (c) 1982, 2002. Oracle Corporation. All rights reserved.
Enter user-name: bob
Enter password: swardlsh
Connected to:
0racle91 Enterprise Edition Release 9.2.0.1,0 - Production
With the Partitioning. OLAP aqd Oracle Data mining options
JServer Release 9.2.0.1.0 - Production
SQL>
О том, что ваше приложение настроено правильно, вы узнаете, увидев приглашение SQL>. (Пароль, в данном случае swordish, на экране отображаться не будет.)
Можно также загрузить SQL*Plus, сразу задав имя пользователя и пароль в командной строке:
0S> sqlplus bob/sword1sh
Однако так поступать не рекомендуется, поскольку пользователи некоторых операционных систем имеют возможность видеть аргументы вашей командной строки, что позволит им воспользоваться вашей учетной записью.
С помощью ключа /NOLOG можно запустить SQL*Plus без подключения к базе данных, указав имя пользователя и пароль посредством команды CONNECT:
0S> sqlpl us /nolog
SQL*P1us: Release 9.2.0.1.0 - Production on Thu Jun 20 10:42:22 2002
Copyright (c) 1982, 2002, Oracle Corporation. All rights reserved.
SQL> CONNECT bob/swordlsh
SQL> Connected.
Если компьютер, на котором вы запускаете SQL*Plus, также содержит правильно сконфигурированное приложение Oracle Net1 и вы авторизованы администратором для подключения к удаленным базам данных (то есть серверам баз данных, работающим на других компьютерах), то сможете подключаться к ним из SQL*Plus. Для этого наряду с именем пользователя и паролем нужно знать идентификатор подключения Oracle Net, называемый также именем сервиса. Идентификатор подключения может выглядеть так:
testOl.ariel.datacraft.com)
Идентификатор вводится после имени пользователя и пароля, отделяясь от них символом «@»:
SQL> CONNECT bob/swordlsh&testOl.ar1el.datacraft.com
SQL> Connected.
При запуске псевдографической версии SQL*Plus идентификационные данные водятся в диалоговом окне Вход в Систему, где идентификатор подключения указывается в поле Строка Связи (рис. 2.2). Если вы хотите подключиться к серверу базы данных, работающему на локальной машине, оставьте поле Строка Связи пустым.
Для того чтобы запустить :SQL*Plus необходимо ввести в браузере соответствующий URL (http://nMA_xoCTa/isqlplus) и задать информацию о подключении, как показано на рис. 2.3.
Запустив SQ.L*Plus, можно делать следующее:
Q выполнять SQL-инструкции;
Q компилировать и сохранять программы на языке PL/SQL в базе данных;
Oracle Net — это имя продукта, ранее называвшегося Net8 или SQL*Net
О запускать программы на языке PL/SQL;
О выполнять команды SQL*Plus;
О запускать сценарии, которые могут содержать сразу несколько церечислен- ных выше команд.
Рассмотрим по очереди каждую из перечисленных возможностей.
[Внод в Систему
Имя Польз.:
Пароль:
[bob
覆 Строка Связи:
jtestO 1 .arit:1,da1acraft.com
ОК
Отмена
Рис. 2.2. Экран во время подключения к серверу базы данныхиз псевдографической версии SQL*Plus
^JiSQf*Pitts Release■ЧЛЛ - Log In - Mkrasoft juh*rriei FxiJorpf
电输"'臓礙爲艱t"‘做P静黛:蕩输廖;你再噬:事諳:号&醤;:扌鼻您二丁:::;喔营:?:奥:;二:: 炉可:熬您^袁磁;龜*.矗碱曰廁鼠硒直三圆三冒
刁 ^Переход ! Ссьлци “
?SQL*Plus
Имя пользоватвля:阮修
Идентификатор соединения: |test01.ariel.dalacraft.co:
ПОЛНОМОЧИЯ: [ЛользоватальД!
舫「5"。 ^^Й^^^®^
[&4门羅畲醐瞬然"
Рис 2.3. Страница браузера с идентификационной информацией в среде разработки ISQL*Plus
Выполнение SQL-инструкции
В консольной версии SQL*Plus запрос
SELECT * FROM books:
генерирует результат, подобный тому, который показан на рис. 2.1. (Чтобы получить результаты в таком виде, нужно воспользоваться командами форматирования столбцов. Если бы это была книга об SQL*Plus или о том, как выводить данные из баз данных, мы бы рассказали здесь обо всех способах вывода информации в данной среде и об установке различных параметров представления и форматирования выводимых данных.)
Если вам нужно представить данные в удобочитаемом виде, лучше обратиться к zSQL*Plus, Достаточно задать в этой программе инструкцию в поле Введите операторы и щелкнуть на кнопке Выполнить, как выходные данные появятся в браузере в виде таблицы (рис. 2.4).
l"W 8нД-': ■■«)fti.»«.'* fff*K 「mwa》圖威[賓"图讶%」®.f: i*"〃e»tOh扭wv” евзрзl"W 8нД-': ■■«)fti.»«.'* fff*K 「mwa》圖威[賓"图讶%」®.f: i*"〃e»tOh扭wv” евзрз卫必 0(Г ","В :!**<<0施.1mё,1 ]卫必 0(Г ","В :!**<<0施.1mё,1 ]iSQLTlusiSQLTlus懒雜^M』b磅懒雜^M』b磅;le SOL*Plu«. TT18 DMn№;le SOL*Plu«. TT18 DMn№Sonnlck, JonamsnSonnlck, JonamsnEIHPr1by(w№StranFeuersteinEIHPr1by(w№StranFeuersteinИввдмв дсейапза:Иввдмв дсейапза:WstioccnoxfHLie г.|(зиэрия |WstioccnoxfHLie г.|(зиэрия |FEu«rst«ln, Япжп “ЦП ” PftbylFEu«rst«ln, Япжп “ЦП ” Pftbyllb56S92-~jdr»cl[57B-S jOuldtlb56S92-~jdr»cl[57B-S jOuldt简沫q jLBamlng Or.de PU8QL简沫q jLBamlng Or.de PU8QLReference for PUSQL dmlagm, Including uampi- ・no dbbi pracGce recommandauoni.Reference for PUSQL dmlagm, Including uampi- ・no dbbi pracGce recommandauoni.Bagifinefe guide to Oracgt PUSQL РгсцпипттдL“W*“Bagifinefe guide to Oracgt PUSQL РгсцпипттдL“W*“СотргаКалвП trsatmwittf Oncla'sl'nlHractm dalabasetoolСотргаКалвП trsatmwittf Oncla'sl'nlHractm dalabasetooll-5B563~p«cle PJSQ1. Language Pockef |§[V Referencel-5B563~p«cle PJSQ1. Language Pockef |§[V ReferenceQulc4-refeience guide far Oracle PL/SOL 6MlOQ・e. : F ncludes Or«cie6i swge!Мвйвгг pun "Won of 口“из, ^пзИезпеагв historical :fQulc4-refeience guide far Oracle PL/SOL 6MlOQ・e. : F ncludes Or«cie6i swge!Мвйвгг pun "Won of 口“из, ^пзИезпеагв historical :f般"・。m6。般"・。m6。01.O3.SS01.O3.SSFeuefs:ei^'st^;|;;04gg BinPntyf, Chip 0тв$У"У»Feuefs:ei^'st^;|;;04gg BinPntyf, Chip 0тв$У"У»zf the Tn re 尸“ r &0加 a UQ,f ercus gqi sl-^-pit x ЫеэПНэSh;kcspe,0 M“m ]D1 D8.007哽上,1 由fGKOpk/=aY『也y,1 I 曼 FF m3-рэ^Ю広&Ч^,二心м5:^|^zf the Tn re 尸“ r &0加 a UQ,f ercus gqi sl-^-pit x ЫеэПНэSh;kcspe,0 M“m ]D1 D8.007哽上,1 由fGKOpk/=aY『也y,1 I 曼 FF m3-рэ^Ю広&Ч^,二心м5:^|^.S 心止幺3TN!.■ Il:.S 心止幺3TN!.■ Il:
l"W 8нД-': ■■«)fti.»«.'* fff*K 「mwa
》圖威[賓"图讶%」®.
f: i*"〃e»tOh扭wv” евзрз
l"W 8нД-': ■■«)fti.»«.'* fff*K 「mwa
》圖威[賓"图讶%」®.
f: i*"〃e»tOh扭wv” евзрз
卫必 0(Г ","В :!**<<0施.1mё,1 ]
卫必 0(Г ","В :!**<<0施.1mё,1 ]
iSQLTlus
iSQLTlus
懒雜^M』b磅
懒雜^M』b磅
;le SOL*Plu«. TT18 DMn№
;le SOL*Plu«. TT18 DMn№
Sonnlck, Jonamsn
Sonnlck, Jonamsn
EIHPr1by(w№Stran
Feuerstein
EIHPr1by(w№Stran
Feuerstein
Иввдмв дсейапза:
Иввдмв дсейапза:
WstioccnoxfHLie г.|(зиэрия |
WstioccnoxfHLie г.|(зиэрия |
FEu«rst«ln, Япжп “ЦП ” Pftbyl
FEu«rst«ln, Япжп “ЦП ” Pftbyl
lb56S92-~jdr»cl
[57B-S jOuldt
lb56S92-~jdr»cl
[57B-S jOuldt
简沫q jLBamlng Or.de PU8QL
简沫q jLBamlng Or.de PU8QL
Reference for PUSQL dmlagm, Including uampi- ・no dbbi pracGce recommandauoni.
Reference for PUSQL dmlagm, Including uampi- ・no dbbi pracGce recommandauoni.
Bagifinefe guide to Oracgt PUSQL Ргсцпипттд
L“W*“
Bagifinefe guide to Oracgt PUSQL Ргсцпипттд
L“W*“
СотргаКалвП trsatmwittf Oncla'sl'nlHractm dalabasetool
СотргаКалвП trsatmwittf Oncla'sl'nlHractm dalabasetool
l-5B563~p«cle PJSQ1. Language Pockef |§[V Reference
l-5B563~p«cle PJSQ1. Language Pockef |§[V Reference
Qulc4-refeience guide far Oracle PL/SOL 6MlOQ・e. : F ncludes Or«cie6i swge
!Мвйвгг pun "Won of 口“из, ^пзИезпеагв historical :f
Qulc4-refeience guide far Oracle PL/SOL 6MlOQ・e. : F ncludes Or«cie6i swge
!Мвйвгг pun "Won of 口“из, ^пзИезпеагв historical :f
般"・。m6。
般"・。m6。
01.O3.SS
01.O3.SS
Feuefs:ei^'st^;|;;04gg BinPntyf, Chip 0тв$У"У»
Feuefs:ei^'st^;|;;04gg BinPntyf, Chip 0тв$У"У»
zf the Tn re 尸“ r &0加 a UQ,f ercus gqi sl-^-pit x ЫеэПНэSh;kcspe,0 M“m ]D1 D8.00
7哽上,1 由fGKOpk/=aY『也y,1 I 曼 FF m3-рэ^Ю広&Ч^,二心м5:^|^
zf the Tn re 尸“ r &0加 a UQ,f ercus gqi sl-^-pit x ЫеэПНэSh;kcspe,0 M“m ]D1 D8.00
7哽上,1 由fGKOpk/=aY『也y,1 I 曼 FF m3-рэ^Ю広&Ч^,二心м5:^|^
.S 心止幺3TN!.■ Il:
.S 心止幺3TN!.■ Il:
Рис. 2.4. Запрос и его результаты в ISQL*Plus
SQL-инструкции в консольном или псевдографическом интерфейсе следует завершать точкой с запятой, но в zSQL*Plus этот символ не является обязательным.
Запуск программы на языке PL/SQL
Итак, начинаем. Введем в SQL*Plus небольшую программу на PL/SQL:
SQL> BEGIN
DBMS_OUTPUT.PUT_LINE('У меня получилось I');
END;"~
/
После ее выполнения вы увидите на экране следующее;
PL/SQL procedure successfully completed.
SQL>
Успешное завершение (successfully completed) этой программы должно было бы означать вывод на экран заданного текста. Однако SQL*Plus довольно странно ведет себя, по умолчанию он подавляет такой вывод. Для того чтобы увидеть выводимую программой строку, вам необходимо выполнить специальную команду SQL*Plus - SERVEROUTPUT:
SQL> SET SERVEROUTPUT ON
5QL> BEGIN
DBMS OUTPUT.PUT LINE「y меня получилось!'):
END:" r
/
И только теперь после выполнения программы на экране отобразится следующее:
У меня получилось!
PL/SQL procedure successfully completed.
SQL〉
Обычно команда SERVEROUTPUT помещается в файл запуска (о файле login.sql речь пойдет далее в этой главе), в таком случае она остается активной до тех пор, пока не произойдет одно из следующих событий:
Q разрыв соединения, выход из системы или завершение сеанса связи с базой данных иным способом;
О вы явно зададите команду SERVEROUTPUT с атрибутом OFF;
Q Oracle отменит сеанс связи либо по вашему запросу, либо из-за ошибки компиляции (см. главу 20).
При вводе в консольном или псевдографическом приложении SQL*Plus инструкции SQL или оператора PL/SQL программа назначает каждой строке, начиная со второй, порядковый номер. С какой целью в ней нумеруются строки? Тому есть две причины: во-первых, чтобы помочь вам определить, какую строку редактировать с помощью встроенного строкового редактора, а во-вторых, чтобы в случае обнаружения ошибки в вашем коде в сообщении об ошибке был указан номер строки. У вас не раз еще будет возможность это увидеть.
Ввод операторов PL/SQL в SQL*Plus завершается символом косой черты (строка 4 в приведенной выше программе). У этого символа есть несколько важных назначений и характеристик.
О Косая черта означает, что введенную инструкцию следует вьшолнить независимо от того, была это инструкция SQL или оператор PL/SQL.
О Косая черта — это команда SQL*Plus; она не является элементом языка SQL или PL/SQL.
О Она должна стоять в отдельной строке, в которой нет никаких других команд.
О В большинстве версий SQL*Plus до Oracle9i косая черта, случайно предваренная одним или несколькими пробелами, не работала! Начиная с Oracle9i среда SQL*Plus правильно интерпретирует ведущие пробелы, то есть попросту их игаорирует. Завершающие пробелы игнорируются во всех версиях.
Для удобства SQL*Plus предлагает пользователям PL/SQL применять команду EXECUTE, позволяющую не вводить команды BEGIN, END и завершающую косую черту. Таким образом, следующая строка эквивалентна приведенной выше программе;
SQL> EXECUTE DBMS_OinPUT.PUT_LINE('У меня получилось!')
Завершающая точка с запятой не обязательна, се лучше опустить. Как и большинство других команд SQL*Phis, команду EXECUTE можно сократить, причем она не зависит от регистра символов. Поэтому указанную строку проще всего ввести так:
SQL> exec dbms_output.put_line(1У меня получилось!')
Запуск сценария
Предположим, что вы знаете имя файла сценария на языке SQL или PL/SQ} Тогда для запуска этого сценария следует воспользоваться командой SQL*Plus @L
SQL> 0errpkg.pkg
Если же вы предпочитаете команды, являющиеся ключевыми словами, можете использовать эквивалентную ей команду START;
$QL> START errpkg.pkg
Результат будет точно таким же. В любом случае эта команда предписывает SQL*Plus выполнить следующие операции.
Открыть файл с именем errpkg.pkg.
Попытаться по очереди выполнить все команды SQL, PL/SQL и SQ,L*Plus, содержащиеся в указанном файле.
Закрыть файл и вернуть вас в SQL*Plus (если только в файле нет команды EXIT, выполнение которой приводит к выходу из SQL*Plus). Налример:
SQL> ^errpkg.pkg
Package created.
Package body created.
SQL>
Команды START, & и 00 доступны в не-браузерной версии SQL*Plus. В iSQL*Plus для получения аналогичных результатов используются команды Browse и Load script.
Как видите, результаты выполнения сценария выводятся на экран.
В данном примере указано расширение файла pkg. По умолчанию SQL*Plus предполагает использование расширения sql. Поэтому если задать имя файла без соответствующего расширения, SQL*Plus будет искать файл errpkg.sql и в результате выдаст сообщение об ошибке:
SQL> 髄rrpkg
SP2-O31D: unable to open file "errpkg.sql"
SP2-0310 - это номер ошибки, сгенерированной Oracle, a SP2 означает, что ошибка относится к SQ,L*Plus. (Дополнительные сведения об ошибках SQL*Plus вы найдете в документации Oracle SQjL*Plus User's Guide and Reference}
Нетрудно догадаться, что если файл сценария находится в другом каталоге, следует указать путь к файлу1:
SQL> P/files/src/release/1.0/errpkg.pkg
С запуском сценария из другого каталога связан интересный вопрос: что если файл errpkg.pkg расположен в указанном каталоге и, в свою очередь, вызывает другие сценарии? Например, он может содержать такие строки (любая строка, начинающаяся с комаяды REM, является комментарием, и SQL* Plus ее игнорирует):
REM имя файла: errpkg.pkg
Serrpkg.pks
©errpkg.pkb
Предполагается, что в ходе выполнения файла-сценария errpkg.pkg будут вызваны файлы errpkg.pksH errpkg.pkb. Однако если информация □ пути отсутствует, где же SQL*Plus будет их искать? Ответ таков: только в текущем каталоге. И вероятнее всего, нужных файлов там не окажется.
Для решения данной проблемы имеется разновидность команды @ — это команда @@. Она задает поиск файлов в каталоге текущего выполняемого файла. Таким образом, вызовы в файле-сценарии errpkg.pkg лучше записывать так:
REM имя файла: errpkg.pkg
0@errpkg.pks
0@errpkg.pkb
Другие задачи SQL*Plus
SQL*Plus поддерживает десятки команд, но мы остановимся лишь на- некоторых из них, самых важных или не всегда правильно интерпретируемых пользователями. Для более обстоятельного изучения этого продукта следует обратиться к книге Oracle SQJ.*Plus: Hie Definitive Guide (авторJonatan Gennik, издательство O^eilly), а чтобы получить краткую справочную информацию — к книге Oracle 5QI *Plus Pocket Reference того же автора.
Пользовательские установки
Как и многие другие программы с интерфейсом командной строки, SQL*Plus позволяет изменить пользователю некоторые параметры с помощью встроенных
Вы будете приятно удивлены тем фактом, что начиная с СУБД OracleSi косую черту (/) можно использовать в качестве разделителя каталогов, как в Unix и операционных системах Microsoft. Это облегчает перенос сценариев между операционными системами.
переменных и установок. Один такой пример вы уже видели, когда мы применили выражение SET SERVEROUTPUT. Посредством команды SQL*Plus SET можно установить и множество других настроек. Так, выражение SET SUFFIX позволяет изменить используемое по умолчанию расширение файла, a SET LINESIZE — задает максимальное количество символов в строке (символы( не помещающиеся в данной строке, переносятся в следующую). Для того чтобы просмотреть все SET-yc- тановки текущего сеанса, выполните команду
SQL> SHOW ALL ”
Если вы используете графическую версию SQL*Plus, эти установки можно просмотреть и изменить с помощью команды Параметры > Конфигурация.
Приложение SQL*Plus позволяет создавать собственные переменные в памяти н задавать специальные переменные, посредством которых можно управлять его настройками. Переменные SQL*Plus бывают двух видов: DEFINE и переменные привязки. Чтобы присвоить значение DEFINE-переменной, нужно выполнить команду DEFINE:
SQL> DEFINE х - "ответ 42"
Увидеть значение х можно с помощью такой команды:
SQL> DEFINE х
DEFINE х -° ответ 42" (CHAR)
Для ссылки на данную переменную предназначен символ &. Перед передачей инструкции Oracle SQL*Plus выполняет простую операцию подстановки, так что ссылку на переменную требуется заключить в кавычки:
SELECT '4х' FROM DUAL;
Переменную привязки нужно сначала объявить посредством команды VARIABLE, после чего можно будет использовать ее в PL/SQL и вывести ее значение на экран с помощью команды PRIN:
SQL> VARIABLE X VARCHAR2(10)
SQL> BEGIN
:x :" 'hullo':
END;
/
PL/SQL procedure successfully conpleted.
SQL> PRINT :x
hullo
У нас теперь две разные переменные х: одна определена с помощью команды DEFINE】а вторая - с помощью команды VARIABLE:
SQL* SELECT 'ftc1 FROM DUAL;
прежний 1: SELECT :x, 'Эх' FROM DUAL
новый 1: SELECT :x. ответ .42' FROM DUAL
:X'0TBET42'
hulloОтвет 42
Запомните, что DEFINE-переменные всегда представляют собой символьные строки, а VARIABLE-перемещгые используются в SQL и PL/SQL как настоящие переменные привязки.
ТЕКУЩИЙ КАТАЛОГ В SQL*PLUS
Каждый раз, когда вы загружаете SQL"Plus из командной строки, эта программа воспринимает текущий каталог операционной системы как собственный текущий каталог. Загрузим ее следующим образом:
C:\BILL\FILES> sqlplus
Тогда любые операции с файлами в SQL*Plus (например, открытие и запуск сценария) по умолчанию будут считаться относящимися к каталогу C:\BILL\F=ILES.
Сказанное верно и в отношении запуска графической версии SQL*Plus из командной строки операционной системы:
C:\BILL\FIIES» sqlplusw
Если вы используете для загрузки SQL*Plus ярлык или команду меню, текущим становится тот каталог, который операционная система связывает с механизмом загрузки. Ну а как же изменить текущий каталог из SQL*Plus? Все зависит от ее версии. В консольной программе этого вообще нельзя сделать. Придется выйти, сменить каталог в операххионной системе и снова запустить SQL*Plus. В графической версии можно воспользоваться командой Файл > Открыть или Файл ► Сохранить, побочным эффектом применения которых является изменение текущего каталога. Если вы используете iSQL*Plus, понятие текущего каталога относится только к диалоговым окнам сохранения и извлечения файла, а возможность его реализации зависит от браузера.
Сохранение выходной информации в файле
Очень часто выходные данные сеанса SQL*Plus необходимо сохранить в файле. Предположим) вы сгенерировали отчет и хотите записать свои действия или динамически сгенерировали команды, которые собираетесь выполюпъ позднее. В любом случае для реализации этих задач в SQL*Plus используется команда SPOOL:
5QL> SPOOL report01.txt SQl> Jrun_report
,…выходные данные выводятся на экран и записываются в файл report01.txt.,.
SQl> SPOOL OFF
Первая команда; SPOOL, позволяет сохранять в текстовом файле report01.txt все строки данных, что следуют за ней. Последняя команда, SPOOL с атрибутом OFF, указывает SQL*Plus на необходимость остановить сохранение выходной информации и закрыть файл. Команда SPOOL также работает и в графической версии SQL*Plus.
Выход из SQL* Plus
Для того чтобы выйти из SQL*Plus и вернуться в операционную систему, нужно выполнить команду EXIT:
SQL> EXIT
Если в момент выхода из приложения производилась запись выходных данных в файл, SQL*Plus прекратит ее и закроет этот файл.
А что произойдет, если выйти из SQL* Plus, не сохранив изменения в базе данных? Обычно такие изменения производятся с помощью инструкций SQL или операторов PL/SQL (манипулирующих данными в базе данных), за которыми не следует явная инструкция COMMIT или ROLLBACK. Результат такой транзакции зависит от текущей установки команды AUTOCOMMIT. По умолчанию команда AUTOCOMMIT используется с атрибутом ON и все несохраненные изменения записываются в базу данных. Если же применить эту команду с атрибутом OFF, Oracle выполнит откат всех несохраненных изменений:
5QLa 5ЕТ А1ЛОСОНМ1Т OFF
Для того чтобы разорвать соединение с базой данных, но остаться подключен* ным к SQL*Plus, нужно выполнить команду DISCONNECT, результат которой выглядит примерно так:
SQL> DISCONNECT
Disconnected from Oracle91 Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning. OLAP and Oracle Data Mining options
JServer Releese 9.2.0.1.0 - Production
SQL>
А зачем нужна команда DISCONNECT? Вы пишете сценарий, в ходе работы он изменяет параметры подключения к базе данных, поэтому перед очередным подключением лучше разорвать предыдущее. В противном случае, если используется аутентификация, сценарий может автоматически подключиться к неверной учетной записи.
Редактирование инструкции
SQL*Plus сохраняет последнюю инструкцию в буфере, и для редактирования содержимого этого буфера можно использовать встроенный строковый редактор (что довольно неудобно) либо любой внешний редактор, выбранный вами. Для начала познакомимся с настройкой и работой внешнего текстового редактора.
В ответ на команду EDIT SQL*Plus сохраняет буфер в файле, временно приостанавливает свою работу и вызывает системный текстовый редактор:
SQLa edit
После окончания редактирования все изменения следует сохранить в буфере с помощью команды Сохранить системного текстового редактора. Если же требует- ся сохранить измененный текст в файле, то по умолчанию таким файлом будет afiedt.buf, но вместо этого имени можно задать другое (с помощью выражения SET EDITFILE или команды Сохранить как системного редактора). Когда вы сохраните файл и выйдете из редактора, сеанс SQL*Plus поместит содержимое этого файла R буфер и продолжит свою работу. Если же вы хотите отредактировать существующий файл, просто укажите его имя в качестве аргумента команды EDIT:
SQL> EDIT errpkg.pkg
Для Unix и Linux Oracle по умолчанию использует системный текстовый редактор edt а для операционных систем Microsoft Windows - Notepad.
Хотя эти установки жестко запрограммированы в исполняемом файле sqlplus, Их легко изменить, присвоив переменной _EDITOR другое значение. Например;
SQL> DEFINE _EDITDR 工 /bin/vl
Здесь /bin/vi 一 полный путь к редактору (настоятельно рекомендуем его использовать).
Если же вы хотите работать со строковым редактором (а это в самом деле может быть удобно), вам нужно знать следующие команды:
q L - показать все строки буфера SQL и сделать текущей последнюю строку;
О L/7 — показать n-ю строку буфера SQL и сделать ее текущей;
О DEL — удалить текущую строку, а следующую за ней сделать текущей;
О C/old/new - заменить первое вхождение old в текущей строке на new (разделителем может быть произвольный символ, в данном случае это косая черта);
OR- запустить текущую инструкцию SQL;
QI — перейти в режим ввода строк после текущей строки (режим заканчивается вводом пустой строки).
Кстати говоря, команда EDIT прекрасно работает и в графической версии приложения SQL*Plus, а кроме того, в нем можно использовать операции вырезки и вставки. В iSQL*Plus команда EDIT не работает; но указанные операции выполняются, а также функционируют кнопки Сохранить сценарий и Загрузить сценарий.
Автоматическая загрузка пользовательского окружения при запуске
Для настройки среды разработки SQL*Plus можно изменять один или оба сценария ее запуска. SQL*Plus при запуске осуществляет две основные операции: О находит в корневом каталоге Oracle файл sqlplus/admin/glogin.sql и выполняет содержащиеся в нем команды (этот «глобальный» сценарий реализуется независимо от того, кто запустил SQL*Plus из корневого каталогаtDracle и какой каталог был при этом текущим);
О находит и выполняет файл login.sql из текущего каталога.
Начальный сценарий может содержать те же типы команд, что и любой другой сценарий на SQL*Plus: команды SET, SQL-инструкции, команды форматирования столбцов и т. п. Оба файла выполняются в указанном порядке. В случае конфликта параметров или переменных преимущество получают установки последнего файла, login.sql. Приведем несколько интересных установок в файле login.sql:
REM Количество строк выходных панных инструкции SELECT перед повторным вмв。口он заголовков SET PAGESIZE 999
REM Ширина выводимой страницы в символах
SET LINESIZE 132
REM Включение вывода сообщений DBMS OUTPUT
SET SERVEROUTPUT ON SIZE 1000000 FORMAT WRAPPED
REM Замена текстового редактора, который вызывает SQL*Plus
DEFINE EDITOR - vi
REM Форматирование стобцов. извлекаемых из словаря данных
COLUMN segment_name FORMAT АЗО WORD_WRAP
COLUMN object_name FORMAT A30 WORDWRAP
В iSQL*Plus понятие текущего каталога отсутствует, поэтому файл login.sql не применяется. Здесь используется только файл glogin.sql, расположенный на том сервере, на котором выполняется данная программа.
Обработка ошибок в SQL* Plus
Способ, каким SQL*Plus информирует вас об успешном завершении операции, зависит от класса выполняемой команды. Для большинства команд SQL*Plus — это просто отсутствие какого-либо сообщения об ошибке. А успешное выполнение инструкции SQL и операторов PL/SQL обычно сопровождается какой-либо текстовой информацией.
Если ошибка содержится в инструкции SQL или операторе PL/SQL, SQL*Plus по умолчанию сообщает о неё и продолжает работу. Это удобно, когда вы работаете в интерактивном режиме. Но если выполняется сценарий, желательно, а часто даже необходим。,чтобы при возникновении ошибки работа SQL*Plus прекращалась. Для этого применяется команда
SQL> WHENEVER SQLERROR EXIT SQL.SQLCODE
SQL*Plus прекратит работу, если после выполнения команды сервер базы данных в ответ на какую-нибудь инструкцию SQL или оператор PL/SQL вернет сообщение об ошибке. Часть приведенной выше команды, SQL.SQLCODE, означает, что, оканчивая работу, SQL*Plus установит свой код завершения в ненулевое значение, которое можно определить в вызывающей среде1. В противном случае SQL*Plus всегда завершается с кодом 0, что иногда неверно истолковывается как успешное выполнение сценария.
Более распространенной является другая форма указанной команды:
SQL> WCNEVER SQLERROR EXIT ROLLBACK
Это означает, что перед выходом SQL*Plus производится откат всех несохра- ненных изменений данных.
Достоинства и недостатки SQL*Plus
Кроме тех, что указаны выше, у SQL*Plus имеется несколько дополнительных функций, которыми вы наверняка с удовольствием будете пользоваться.
О С помощью SQL*Plus можно запускать пакетные программы, задавая в командной строке аргументы и ссылаясь на них в сценарии следующим образом: &1 (первый аргумент), &2 (второй аргумент) и т. д.
О SQL*Plus обеспечивает полную поддержку всех инструкций SQL и операторов PL/SQL. Это обычно имеет значение при использовании специфических
Это можно сделать с помощью системной переменной $? в Unix и XERRDRLEVEL3S в Microsoft Windows.
возможностей Oracle. В инструментальных средствах разработки сторонних производителей отдельные элементы указанных языков могут поддерживаться не на все 100 %. Например, некоторые из них до сих пор <не понимают» объектных типов Oracle, введенных несколько лет назад. (Об использовании объектных типов в PL/SQL рассказывается в главе 21.)
q Для SQL*Plus подходит то же аппаратное обеспечение и те же программные платформы, что и для сервера Oracle.
Как и любые другие инструментальные средства, SQL*Plus имеет свои недостатки.
О В консольных версиях SQL*Plus буфер команд содержит только последнюю из ранее использовавшихся команд. Журнала команд эта программа не ведет.
О Когда в процессе сеанса связи с базой данных или выполнения сценария вы с помощью команды CONNECT переключаетесь на другого пользователя, SQL*Plus не повторяет стартовый сценарий login.sql или glogin.sql, а выполняет команду SERVEROUTPUT с атрибутом OFF. Это не совсем удобно для тех разработчиков, которые задают в файле login.sql пользовательское приглашение командной строки. Например, приглашение TEST9i> остается на экране и после того)как с помощью команды CONNECT пользователь переключился на базу данных PR0D91. Поэтому нс забывайте вручную запускать стартовый сценарий после каждого выполнения команды CONNECT.
Q SQL*Plus не имеет таких возможностей, характерных для современных интерпретаторов команд, как автоматическое завершение ключевых слов и подсказки о доступных объектах базы данных, появляющиеся при вводе команд.
О Интерактивная справочная система содержит информацию базовой документации по набору команд SQL*Plus, (Для получения справки по конкретной команде используется команда HELP.)
О После запуска SQL*Plus изменить текущий каталог невозможно, что довольно неудобно, если вы часто открываете и сохраняете сценарии и при этом не хотите постоянно указывать полный путь к файлу. Поэтому, обнаружив, что вы не в том каталоге, выйдите из SQL*Plus, смените каталог и снова запустите программу.
Как видите, с точки зрения программистов, SQL*Plus не является средством, отличающимся удобством в работе и изысканностью интерфейса. Но данное приложение используется повсеместно, работает надежно и наверняка будет поддерживаться до тех пор, пока существует Oracle Corporation.
Выполнение базовых операций PL/SQL
Теперь поговорим о создании, выполнении, удалении и других операциях с программами PL/SQL, выполняемых с помощью SQL*Plus. В этом разделе не будет обилия деталей — он, скорее, представляет собой вводный обзор темы, которую мы подробно рассмотрим в следующих главах.
Создание хранимой программы
Для того чтобы написать собственную программу на PL/SQL, нужно воспользоваться одной из SQL-инструкций CREATE. Например, если вы хотите создать хранимую функцию с именем wordcount, которая подсчитывала бы количество слов в строке, выполните такую инструкцию:
CREATE FUNCTION wordcount (str IN VARCHAR2)
RETURN PLSJNTEGER
AS
здесь объявляются локальные переменные
BEGIN
здесь резлизуется алгоритм END;
/
Как и в случае простого блока BEGIN.. .END, приведенного выше, код этой инет* рукции в SQL*Plus завершается вводом символа косой черты, который помеща* ется в отдельную строку.
Если администратор баз данных предоставил вам привилегию CREATE FUNCTION, эта инструкция заставит Oracle огкомпилировать и сохранить в схеме заданную хранимую функцию. И если код будет откомпилирован успешно, вы увидите следующее сообщение:
Function created.
При условии, что в схеме Oracle уже имеется объект (таблица или хранимая программа) с именем wordcount, выполнение инструкции CREATE FUNCTION завершится сообщением об ошибке:
DRA-00955: name is already used by an existing object.
Это одна из причин, по которой Oracle поддерживает инструкцию CREATE OR REPLACE FUNCTION — вы будете ее использовать в 99 случаях из 100:
CREATE OR REPLACE FUNCTION wordcount (str IN VARCHAR2)
RETURN PLSJNTEGER
AS
то же, что и 8 примере кода, приведенном выше
Инструкция CREATE OR REPLACE FUNCTION позволяет избежать побочных эффектов, вызванных удалением и повторным созданием программ; она сохраняет все привилегии на объект, предоставленные другим пользователям или ролям. При этом она заменит только объекты одного типа и не станет автоматически удалять таблицу wordcount лишь потому, что вы решили создать функцию с таким же именем.
Программисты обычно сохраняют подобные инструкции, равно как и анонимные блоки, используемые более одного раза, в файлах операционной системы. Например, для хранения рассматриваемой функции можно было бы создать файл wordcount.fun, а для его запуска применить команду SQL*Plus @:
SQL> @wordcount.fun
Function created.
Как следует из данного примера, SQL*Plus по умолчанию не выводит содержимое сценария на экран. Для того чтобы исходный код сценария, включая присвоенные Oracle номера строк, прокручивался на экране, воспользуемся командой SET ECHO ON. Особенно удобно ее применять в случае возникновения каких-либо
проблем. Давайте намеренно допустим в нашей программе ошибку, закоммевти ровав объявление переменной:
5QL> /* Файл в Web: wordcount.fun */
SQL> SET ECHO ON
SQL> 0wordcount.fun
SQL> CREATE OR REPLACE FUNCTION wordcount (str IN VARCHAR2)
RETURN PLS—INTEGER
AS
/* words PLSJKTEGER :- 0; ***3акомментировано для преднамеренной ошибки*** */
len PLS_INTEGER NVL(LENGTH(str).O);
inside_a_word BOOLEAN:
BEGIN --
FOR i IN 1..len + 1
LOOP
IF ASCIKSUBSTRCstr, 1, 1)) < 33 OR 1 > len
THEN
IF 1nside_a_word
THEN 一 "
words := words + 1:
1nside_a_word :- FALSE;
END IF;--
17181920211718192021■inside_a_word :e TRUE;■inside_a_word :e TRUE;ELSE
17
18
19
20
21
17
18
19
20
21
■inside_a_word :e TRUE;
■inside_a_word :e TRUE;
END IF;
END LOOP;
22 END;23 /22 END;23 /RETURN words;
22 END;
23 /
22 END;
23 /
warning: Function created with compilation errors.
SQL>
Выведено предупреждающее сообщение, которое мало чем поможет при отладке. Поэтому нужен полный текст сообщения об ошибке. И получить его проще всего с помощью команды SHOW ERRORS, которую можно сократить до SHOW ERR:
SQL> SHOW ERR
Errors for FUNCTION WORDCOUNT:
LINE/COLERROR
14/13PLS-00201: identifier 'WORDS'mustbedeclared
14/13PL/SQL: Statement Ignored
21/4PL/SQL: Statement ignored
21/11PLS-00201: identifier 'WORDS'mustbedeclared
Почему SQL*Plus для получения информации об ошибке заставляет вас выполнить еще одно действие, не известно. Компилятор обнаружил оба вхождения переменной и сообщил точные номера строк и столбцов. Команда SHOW ERRORS запрашивает представление Oracle USER_ERRORS из словаря данных. Более подробную информацию об ошибке можно поискать по идентификатору (в данном случае -PLS-00201) в документации Oracle Database Error Messages.
Распространенной практикой является добавление команды SHOW ERRORS после каждой инструкции CREATE в хранимой программе PL/SQL. Поэтому «хороший»
шаблон для создания хранимых программ в SQL*Plus может иметь примерно такую форму:
CREATE OR REPLACE тип программы
AS"
ваш'
/
SHOW ERRORS
Обычно команда SET ECHO ON в сценарий не включается, а просто вводится в командной строке, когда это требуется.
Если ваша программа содержит ошибку, которая может быть определена компилятором, в ответ на инструкцию CREATE Oracle сохранит эту программу в базе данных, но в нерабочем состоянии. Если же вы неверно используете синтаксис инструкции CREATE, Oracle не поймет, что вы хотите сделать, и не сохранит код.
ВЫВОД ДРУГИХ ОШИБОК
Большинство программистов Oracle зияют только одну форму команды SQL*Plus:
SQL> SHOW ERRORS
Они полагают, что для получения дополнительной информации об ошибке помимо сообщения о результате последней компиляции нужно непосредственно запрашивать представление USER_ERRORS. Однако если вы зададите в команде SH佻 ERRORS категорию и имя объекта, то получите информацию о последних связанных с ним ошибках:
SQL> SHOW ERRORS категория ш_рбьекта
Например, для того чтобы просмотреть информацию о последних ошибках в процедуре wordcount, нужно выполнить такую команду;
SQL> SKJW ERRC«S PROCEDURE wcrdcount
Вы должны насторожиться, получив сообщение
No errors.
Оно выводится в трех случаях: когда код объекта откомпилирован успешно, если вы указали неверную категорию (скажем, функцию вместо процедуры) и если объекта с заданным вамх именем не существует.
Полный список категорий, распознаваемых этой командой, зависит от версии СУБД; в Oracle9i он таков;
DIMENSION
FUNCTION
JAVA SOURCE
JAVA CLASS
PACKAGE
PACKAGE BODY
PROCEDURE
TRIGGER
TYPE
TYPE BODY
VIEW
Выполнение хранимой программы
Вы познакомились с двумя способами вызова хранимой программы: первый состоит в ее заключении в простом блоке PL/SQL, второй — в использовании команды EXECUTE среды SQL*Plus. Одни хранимые программы можно использовать в других. Например, функция wordcount применяется везде, где допускается использование целочисленного выражения. Протестируем функцию wordcount с входным значением CHR⑼,которое является ASCII-кодом символа табуляции:
BEGIN
DBMS_OUTPUT.PUT LINECВведенная строка содержит ' || wordcountCCHR(9)) || 1 слов');
END;""
/
Вызов функции wordcount включен в выражение как аргумент процедуры DBMS_ OUTPUT. PUT_LINE. В таких случаях PL/SQL автоматически преобразует целое число в строку, чтобы соединить его с двумя другими литеральными выражениями. Результат получается следующим:
Введенная строка содержит 0 слов'
Многие функции PL/SQL можно вызывать и из SQL-инструкций. Приведем несколько примеров использования функции wordcount.
О Включается в список выбора при необходимости вычислить количество слов в столбце таблшхы:
SELECT 1sbn, wordcount(описание) FROM books:
О Применяется в ANSI-совместимой инструкции CALL для связи выходных данных с переменной SQL*Plus и вывода результата на печать:
VARIABLE words NUMBER
CALL wordcounK'некоторы^гексг') INTO :words: PRINT .-words
О Используется с той же целью, что и в предыдущем примере, йо выполняется из удаленной базы данных, определяемой ссылкой test. nayyork. ora. сот:
CALL wordcDunt@test.newyork.Dra.CDm('//скоторУй_ге/<С7-') INTO :words: PRINT :words-
О Принадлежит схеме bill и выполняется при подключении к любой схеме, имею- • щей соответствующую привилегию:
SELECT Ы11.wordcount(описание) FROM books WHERE Id ■ 10007:
Вывод хранимых программ
Рано или поздно вам потребуется просмотреть список имеющихся у вас хранимых программ и последние версии их исходного кода, которые Oracle содержит в словаре данных. (Словарь данных Oracle — множество таблиц и объектов базы данных, которые хранятся в специально费 ее области, — ведется исключительно ядром Oracle, содержит информацию об объектах базы данных, пользователях и событиях.) Эта задача выполняется при помощи инструментального средства с графическим пользовательским интерфейсом (такие средства упоминались в главе 1), но если его у вас нет, то можно написать несколько SQL-инструкций, извлекающих из словаря данных нужную информацию.
Так, чтобы просмотреть полный список программ (а также таблиц, индексов и других элементов), запросите информацию представления USER_08JECT:
SELECT * FROM USER_OBJECTS;
Это представление содержит сведения о каждом объекте, а именно его имя, тип, время создания, время последней компиляции, состояние (работоспособный или неработоспособный) и другую информацию. Если вам нужны данные об интерфейсе программы SQL*Plus, проще всего воспользоваться командой DESCRIBE: SQL> DESCRIBE wordcount
FUNCTION wordcount RETURNS BINARY_INTEGER
Argument NameTypeIn/Out Devault?
STRVARCHAR2IN-
Вы можете запросить полный исходный код хранимых программ, обратившись к представлению USER_SOURCE или TRIGGER^SOURCE. О том, как получить информацию из этих представлений, содержащихся в словаре данных, подробно рассказывается в главе 19.
Управление привилегиями и создание синонимов хранимых программ
Созданную вами программу на PL/SQL обычно не может выполнять никто, кроме вас или администратора базы данных. Предоставить право на ее применение другому пользователю можно с помощью инструкции GRANT :
GRANT EXECUTE ON wordcount TO scott;
Лишить пользователя этой привилегии позволит инструкция REVOKE:
REVOKE EXECUTE ON wordcount FROM scott;
Привилегия EXECUTE может быть предоставлена роли:
GRANT E)(ECUTE ON wordcount TO all_mis; .
а также всем пользователям Oracle:
GRANT EXECUTE ON wordcount TO PUBLIC;
Если вы предоставите привилегию EXECUTE отдельному пользователю (напри- мер, с идентификатором scott), затем — роли, в которую входит этот пользователь, и, наконец, — всем пользователям, Oracle запомнит все три варианта ее предоставления. Любой из них позволит пользователю scott выполнять программу. Но если вы захотите лишить данного пользователя этой возможности, то должны действовать следующим образом: сначала отменить привилегию пользователю с идентификатором scott, а затем аннулировать привилегию на выполнение функ- ции для всех пользователей (PUBLIC) и роли (или же исключить пользователя нз этой роли).
Для просмотра списка привилегий, которые вы предоставили другим пользователям и ролям, можно запросить информацию представления USER_TAB_PRIVS_mDE словаря данных. Имена программ в этом представлении почему-то выводятся в столбце tabi e_name.
SQL> SELECT table_name. grantee, privilege
2 FROM USER二TAB—PRNS一MADE
3 WHERE table_name - 1 WORDCOUNT'
4/"
TABLE_NAME
GRANTEE
PRIVILEGE
WORDCOUNT
PUBLIC
EXECUTE
WORDCOUNT
scon
EXECUTE
WORDCOUNT
MIS ALL
EXECUTE
Если пользователь scott имеет привилегию EXECUTE на выполнение программы wordcount, он, возможно, захочет создать ее синоним — в таком случае ему не придется писать перед именем программы префикс в виде имени содержащей ее схемы: SQL> COffflECT scott/tlger Соединено.
SQL> CREATE OR REPLACE SYNONYM wordcount FOR bill.wordcount;
Теперь можно выполнять данную программу, ссылаясь на ее синоним:
If wordcount(локальная_переменная) > 100 THEN...
Так удобнее, поскольку в случае изменения владельца программы достаточно будет изменить только ее синоним, а не все ге хранимые программы, из которых она вызывается.
Синоним можно определить для процедуры, функции, пакета или, как в последних версиях Oracle, пользовательского типа. Синонимы процедур, функций и пакетов могут скрывать не только схему, но и базу данных; синонимы для удаленных программ создаются так же просто, как и для локальных. Однако синоним для процедуры или функции внутри пакета или типа работать не будет.
Удаление синонима производится следующим образом:
DROP SYNONIM wordcount;
Удаление хранимой программы
Если какая-либо хранимая программа вам больше не нужна, ее можно удалить с помощью SQL-инструкции DROP. Например, приведенная ниже инструкция удаляет хранимую функцию wordcount:
DROP FUNCTION wordcount;
Точно так же удаляются пакеты и типы:
DROP PACKAGE имя_пакета-.
DROP TYPE иня_типа :
Когда вы удаляете программу, которая вызывается из других программ, последние помечаются как неработоспособные.
Как скрыть исходный код хранимой программы
При создании программы PL/SQL описанным выше способом ее исходный код помещается в словарь данных в виде текста, который администратор базы данных может просмотреть и даже изменить. С целью сохранения профессиональных секретов и предотвращения вмешательства в программный код посторонних, перед распространением его нужно зашифровать или как-то иначе скрыть.
В этом случае можно воспользоваться приложением Oracle с интерфейсом командной строки, называемым Wrap. Данное приложение преобразует несколько
инструкций CREATE в комбинацию текстовых символов и шестнадцатеричных чисел. Это действие не является шифрованием в прямом смысле слова, ко все же направлено на сокрытие кода. Приведем несколько фрагментов преобразованного таким образом кода:
CREATE OR REPLACE FUBCTION wordcount wrapped D abed abed ...разрыв...
1WORDS:
10: 1LEN: 1NVL: 1LENCTH: 1INSIDE_A WORD: 1BOOLEAN:" …разрыв… a5 b 81 ЬО аЗ aO IC 81 bO 91 51 aO 7e 51 aO b4 2e 63 37 :4 aO 51 a5 b a5 b 7e 51 M 2e :2 aO 7e b4 2e 52 10 :3 aO 7e 51 b4 2e d :2 aO d b7 19 Зс b7 :2 aO d b7 :2 19 3c b7 aO 47 :2 aO
Но если вам нужно зашифровать что-то по-настоящему, скажем, при передаче такой секретной информации, как пароль, полагаться на возможности программы Wrap1 (см. главу 19) не следует.
Средства разработки Oracle PL/SQL
Если вы собираетесь использовать PL/SQL для программирования, и в частности для создания пользовательского интерфейса, вам потребуются два специальных инструментальных средства для разработчиков, входящих в состав Oracle9i Developer Suite, а именно Oracle Forms и Oracle Reports. Этот программный продукт содержит также среду разработки для Java и менеджер программной конфигурации. Имеется еще одно инструментальное средство — Oracle Graphics, которое не продается как отдельный продукт, но его функциональные возможности включены в Oracle Fonns и Oracle Reports.
Вместе с продуктами Forms Builder и Reports Developer поставляется исполнительное ядро, позволяющее программистам запускать свои приложения. Однако окончательная передача приложения конечным пользователям предполагает его лицензирование с помощью исполнительной среды, такой как Oracle9i Forms Services или Oracle9: Reports Services. Данные приложения являются компонентами еще одного продукта — Oracle9t Application Server, который обычно выполняется на сервере промежуточного уровня и предоставляет конечным пользователям
Oracle позволяет использовать в приложениях защиту DES (Data Encryption Standard — Стандарт шифрования данных), для чего нужно обратиться к пакету DBMS_OBFUSCATION_TOOLKIT.
формы и отчеты через браузеры, поддерживающие Java. Однако до сих пор широко используются и более старые версии Oracle Forms и Oracle Reports, реализующиеся в конфигурациях под названием <толстые клиенты», в которых исполнительное программное обеспечение располагается на машине каждого конечного пользователя. В книге вам будут встречаться ссылки на клиентские средства разработки Oracle - это и есть указанные средства. Относительно базы данных любое программное обеспечение является клиентским, хотя на самом деле вы можете использовать многоуровневую систему, а не простую схему, состоящую из клиента и сервера.
На рис. 2.5 показан пользовательский интерфейс приложения Oracle9: Forms Builder. Вторым справа на этом рисунке представлено окно встроенного редактора кода PL/SQL.
:氾国:氾国■«'тК<И71> ':"■■«'тК<И71> ':"■dHWATE二ЯЛЦЗНЮ■FtHUrrrudHWATE二ЯЛЦЗНЮ■FtHUrrruРис. 2.5. Пользовательский интерфейс приложения Oracle Forms BuilderРис. 2.5. Пользовательский интерфейс приложения Oracle Forms Builderl,okmck4aliAttKtwdLbrvMl,okmck4aliAttKtwdLbrvM6)KtQrc^« -P<ram№t-Woyam Lhitt ■РгорчЛ, CUmm ■AKordOoM6)KtQrc^« -P<ram№t-Woyam Lhitt ■РгорчЛ, CUmm ■AKordOoMТМ;'г,产ТМ;'г,产LECLARBD0K*y_D«fln« CHARLECLARBD0K*y_D«fln« CHAR■ ■•小…зек,“ccwx■ ■•小…зек,“ccwx:URBOK BOQK_COBI«S:URBOK BOQK_COBI«SPELECT ; FROM BOOK• MERK В.Т8ЯЫ--" BOOKS :;PELECT ; FROM BOOK• MERK В.Т8ЯЫ--" BOOKS :;
:氾国
:氾国
■«'тК<И71> ':"■
■«'тК<И71> ':"■
dHWATE二ЯЛЦЗНЮ
■FtHUrrru
dHWATE二ЯЛЦЗНЮ
■FtHUrrru
Рис. 2.5. Пользовательский интерфейс приложения Oracle Forms Builder
Рис. 2.5. Пользовательский интерфейс приложения Oracle Forms Builder
l,okmck4ali
AttKtwdLbrvM
l,okmck4ali
AttKtwdLbrvM
6)KtQrc^« -P<ram№t
-Woyam Lhitt ■РгорчЛ, CUmm ■AKordOoM
6)KtQrc^« -P<ram№t
-Woyam Lhitt ■РгорчЛ, CUmm ■AKordOoM
ТМ;'г,产
ТМ;'г,产
LECLARB
D0K*y_D«fln« CHAR
LECLARB
D0K*y_D«fln« CHAR
■ ■•小…зек,“ccwx
■ ■•小…зек,“ccwx
:URBOK BOQK_COBI«S
:URBOK BOQK_COBI«S
PELECT ; FROM BOOK
• MERK В.Т8ЯЫ--" BOOKS :;
PELECT ; FROM BOOK
• MERK В.Т8ЯЫ--" BOOKS :;
Чтобы помочь пользователям в реализации базовых функций, Oracle предоставляет набор расширений PL/SQL, уникальных для каждого из средств разработки. Так, в Oracle Forms протраммьг на PL/SQL мохут;
О использовать значения элементов на экране в качестве переменных привязки (например, при вводе пользователем значения в поле isbn блока с именем bk, переменная :bk.isbn связывает значение поля с текущей записью);
О заполнять раскрывающиеся списки данными из таблицы базы данных;
О автоматически выполнять обработку событий конечного пользователя) таких, скажем, как щелчок мыши;
О управлять внешним видом пользовательского интерфейса, например путем вы* вода либо сокрытия элементов управления или окон.
Клиентский код PL/SQL может содержаться в трех перечисленных ниже час* тях приложения.
О Триггеры. Эти блоки PL/SQL выполняются в ответ на специфические события приложения, такие как запуск, щелчок мышью или проверка данных.
О Программные блоки внутри приложения. Их можно вызывать из любых других программ того же приложения.
О Библиотека PL/SQL многократно применяемых пользовательских клиентских программ. Программы вызываются из любого клиентского приложения.
И, конечно жб)из этих приложений можно вызвать серверные программы на PL/SQL. Однако подобные вызовы реализованы в Oracle со множеством ограничений. Например, переменные и курсоры из серверных пакетов нельзя использовать непосредственно в клиентских протраммах (для этого приходится писать специальные функции).
Перенос программ на PL/SQL между клиентским компьютером и сервером
Перенос кода PL/SQL между клиентом и сервером - дело несложное, но только в том случае, если вы не использовали никаких непереносимых элементов языка. На рис. 2.6 показан результат перетаскивания двух хранимых пакетов из базы данных в клиентскую библиотеку PL/SQL. Эти пакеты не компилируются, на что указывает звездочка справа от имени каждого из них. А связано это с тем, что в данных пакетах используется код PL/SQL, который не поддерживается клиентской стороной, например содержащий встроенный динамический SQL. Для решения данной проблемы нужно удалить подобные программы из пакета или же модифицировать их код таким образом, чтобы в них использовались клиентские вызовы встроенной функции EXEC_SQL.
С указанными пакетами связана еще одна проблема: хотя клиентское ядро PL/SQL может обрабатывать определяемые протраммистом исключения, которые инициируются с помощью встроенной процедуры RAISE__APPLICATION_ERROR, клиентские приложения не могут сами инициировать эти исключения. Альтернативным решением может быть сосредоточение всех определяемых программистом клиентских исключений в одном клиентском пакете. .
Очень часто разработанный для клиента код PL/SQL не пригоден для использования на сервере, поскольку содержит множество элементов, ограничивающих его переносимость. Например, если код включает ссылки на клиентские переменные привязки, подобные :bks.isbn, или в нем используются специфичные для Oracle Forms встроенные функции, такие как SHOW_ALERT и EXECUTE_QUERY, он не откомхш- лируется на сервере.
Если вы часто используете клиентские средства Oracle, вам, пожалуй, стоит купить книгу Oracle Developer Advanced Forms & Reports (авторы Peter Koletzke и Paul Dorsey), но учтите, что она не для начинающих программистов.
Вызов PL/SQL из других языков
Возможно, вам захочется вызвать PL/SQL из программного кода; написанного на С, Java, Perl, Visual Basic или другом языке. И если вам когда-нибудь приходилось создавать приложения с межъязыковыми вызовами, то вы, по-видимому, знаете, что для сопоставления типов данных разных языков иногда приходится прибегать к различным ухищрениям. Это касается в первую очередь составных типов данных, наподобие массивов, записей и объектов, не говоря уже о семантике параметров или специфичных для конкретного производителя расширений ♦стандартных» API, в частности ODBC.
Приведем несколько примеров. Предположим, у нас имеется функция PL/SQL, принимающая представленный в виде строки ISBN и возвращающая заголовок соответствующей ему книги:
/* Файл в web: booktitle.fun */
CREATE OR REPLACE FUNCTION booktitle (isbnjn IN VARCHAR2)
RETURN VARCHAR2-
ISI
IJsbn books.tltleXTYPE;
OjRSOR icur IS SELECT title FROM books WHERE Isbn - Isbnjn;
BEGIN
OPEN Icur;
FETCH 1cur INTO 1 Isbn:
CLOSE 化";
RETURN 1 isbn:
END;
/
В SQL*Plus существует несколько способов вызова этой функции. Простейший из них таков:
SQL> EXEC DBMS_0UTPUT.PUT_LINE(bookt1tle('0-596-00180-0'))
Learning Oracle PL/SQL
PL/SQL procedure successfully completed,
Мы можем вызвать данную функцию не из одной среды, а из нескольких. Перечислим их;
Q С, с использованием прекомпилятора Oracle (Pro* С);
Q Java, с использованием метода JDBC;
Q Perl, с использованием интерфейсного модуля Perl DBI и драйвера DBD::Oracle;
О PL/SQL Server Pages.
Это искусственно созданные примеры — имя пользователя и пароль в них жестко закодированы, и программы просто выводят результаты. Тем не менее они демонстрируют, с какими проблемами вы можете столкнуться при работе с разными языками.
G с использованием прекомпилятора
Oracle (Рго*С)
Oracle предоставляет как минимум два разных интерфейса для внешних систем: один называется OCI (Oracle Call Interface), а другой — Рго*С. В состав OCI входит множество функций, с помощью которых кодируются такие низкоуровневые операции, как открытие и выполнение программы, ее синтаксический анализ, определение и связывание переменных, выборка информации из базы данных. И все это делается для реализации одного запроса. Поскольку даже простейшая программа на основе OCI, выполняющая не самую сложную задачу, содержит около 200 строк кода, рассмотрим пример использования Pro* С.
Рго*С — это технология прекомпиляции, позволяющая совмещать в исходном коде операторы языков С, SQL и PL/SQL. Такие файлы обрабатываются программой Oracle Proc, которая преобразует их в код на языке С:
/* Файл в Web: callbooktitle.рс */
^include <stdio.h>
#include <strlng.h>•.、
EXEC SQL BEGIN DECLARE SECTION;
VARCHAR 5d[20];
VARCHAR pwdL20];
VARCHAR isbn[15];
VARCHAR bt1tle[40Q]:
EXEC SQL END DECLARE SECTION;
EXEC SQL INCLUDE SQICA.H:
Int sqlerrorO;
Int malnO
{
/* Значения типа VARCHAR становятся структурами, содержацими массив символов, и имеют длину */
strcpy((char *)u1d.arr.Hscott"):
uid.len - (short) strlen£(char *)u1d.arr);
strcpy((char *)pwd.arr."t1ger");
pwd.len - (short) str1en((char *)pwd.arr);
/* Это гибрид исключения и оператора goto */
EXEC SQL WHENEVER SQLERROR DO sqlerrorO;
/* подклсмеемся к серверу Oracle, а затем выполняем функцию booktitle */
EXEC SQL CONNECT :uid IDENTIFIED BY :pwd;
EXEC SQL EXECUTE
BEGIN
rbtltle :■ booJctltlef'O-SSe-OOlSO-O'J;
END;
END-EXEC;
/* вывод результата */
pr1ntf("3:s\n", btltle.arr);
/* Прерываем соединение c ORACLE. */
EXEC SQL COMMIT WORK RELEASE:
exlt(O);
sqTerror()
EXEC SQL WHENEVER SQLERROR CONTINUE;
pr1ntfC"\nX .70s \пи. sqlca.sqlerrm.sqlerrmc); EXEC SQL ROLLBACK WORK RELEASE:
exit(l);
Как видите, использование Pro* C — это не тот подход, от которого пуристы языка С придут в восторг. Однако во многих компаниях считают, что Pro* С (или Рго*СоЪо1, или прекомпиляторы для других поддерживаемых Oracle языков программирования) может служить разумной альтернативой Visual Basic (который слишком медлен) и OCI (который слишком сложен).
Java, с использованием JDBC
Как и С, Oracle поддерживает несколько разных подходов при подключении к базе данных. Один из них - встроенный SQL, который называется SQLJ, - подобен другим прекомпиляторным технологиям Oracle, хотя и имеет больше возможностей
для отладки. Но более популярен метод доступа к базе данных, называемый JDBC 0ava DataBase Connectivity):
/* Файл 8 Web: callbooktitle.java */
Import java.sql
public class book
{ public static void wa1n(String[] args) throws SQLException
< 〃 инициализируем драйвер и пытаемся установить подключение к базе данных
DriverManager.registerDriver Сnew oraclе.jdbc.dr1ver.OracleDr1ver()):
Cannectlon conn - Dri verManager .getConnecti an( "jdbc: aracl e: thi n :@1 acai hast ■. 1521:092", "scott". "tiger"):
// метод ргераreCall использует "call"-синтаксис ANSI92
CallableStatement cstmt 工 conn.prepareCall("{?工 call booktitle(?)}"):
〃 связывание переменных и параметров
cstmt.registerDutParameterd. Types.VARCHAR);
cstmt.setString(2. н0-59б-00180-0и);
// Теперь мы можем аыполнить запрос и получить результат,
// закрыть поцкличение и эывести результат на печать
cstmt.executeUpdateC):
String bookTitle = cstmt.getString(l);
conn.closet):'■
System.out.println(bookTltle):'-
}
}
В этом примере используется драйвер, отличающийся прекрасной совместимостью и простотой инсталляции (все необходимое для сетевых протоколов имеется в библиотеке Java), однако большое количество передаваемой через соединение информации сказывается на его производительности. В качестве альтернативы можно использовать драйвер OCL Пусть вас не пугает это имя - он совсем не так сложен, как одноименный интерфейс, о котором упоминалось в предыдущем разделе.
Для того чтобы больше узнать о программировании на Java для Oracle, вы можете обратиться к книгам издательства O'Reilly Java Programming with Oracle JDBC (автор Don Bales) и Java Programming with Oracle SQLJ (автор Jason Price).
Perl, с использованием Perl DBI и DBD::Oracle
Очень любимый системными администраторами язык Perl можно назвать прародителем всех языков с открытым исходным кодом. И сейчас, когда выпущена уже шестая его версия, он может практически все и распространен, кажется, повсеместно. Если воспользоваться каким-нибудь эффективным средством, например CPAN (Comprehensive Perl Archive Network — обширный сетевой архив ресурсов Для языка PER), ничего не стоит установить такие интерфейсные модули, как DataBase Interface (DBI) и соответствующий ему драйвер Oracle, DBD:;Oracle:
Файл 8 Web: callbooktitle.p] */.
#!/usr/bin/perl
use strict:
use D0I qw(:sql_tyPes>:
либо установить подключение, либо вывести сообщения die
щу $dbh = D01->connect(
'dbi:0racle:o92'.
'scott',
'tiger',
RalseError => 1,
AutoCormit -> 0
)К die "Соединение с базой данных не установлено: JDBI::errstr";
my Jretval:
вызов Oracle для синтаксического анализа инструкции eval {
щу $func = Jdbh->prepsreCq{
BEGIN
:retval 尸 booktltleCisbnjn » :bindl):
END;~
связывание параметров и выполнение процедуры
$func->bind_paramC":bindr, "0-596-00180-0");
$func->bind_param_1nout(":retval". \Jretval, SQL_VARCHAR);
Jfunc->execute:
};
if($@) {
warn "Хранимая процедура выполнена с ошибкой: SD0I::errstr\n"; $dbh->ro11back;
} else {
print "Хранимая процедура возвращает; $retval\n":
}
не'забыть отключиться $dbh->disconnect;
С другой стороны, Perl — один из тех языков, на которых очень легко написать совершенно нечитабельный код. К тому же он не обеспечивает желаемой скорости выполнения программ и не отличается компактностью кода.
За дополнительной информацией о взаимодействии между Perl и Oracle вы можете обратиться к книгам издательства O'Reilly Perl for Oracle DBAs (авторы Andy Duncan и Jared Stil) и Programming the Perl DBI (автор Alligator Descartes). Кроме того, имеется множество замечательных книг, посвященных языку Perl, где эта тема освещается достаточно глубоко.
PL/SQL Server Pages
Рабочая среда PL/SQL Server Pages (PSP) является замечательным средством для быстрого создания web-страниц, а также еще одной из технологий прекомпиляции, позволяющих вставлять код PL/SQL в HTML-страницы:
/* Файл э Web: favoritE_plsql_book.psp */
龜 page language-"PL/SQL" >一
<誕 plsql procedure-"favofite_plsql_book" X>
<HTML>
<HEAD>
<TITLE>My favorite book about PL/SQL</TITLE>
</HEAD>
<BOOY>
bookt1tleC0-596-00l80-0,)''!l!>
</B0DY>
<HTML>
Операторы наподобие <X- Ж> означают, что следует выполнить указанный код PL/SQL и вернуть результат на страницу. Если приведенную страницу правильно установить на web-сервере, подключенном к базе данных Oracle, она будет выглядеть так, как показано на рис. 2.7,
事м¥ fpvoiule 1)0叫 aRy?t 1>l.;SQL - 叩5纟吧《 Ii;往电》4畀
:^Файл 而авка:Вид , ЙзЕ哂нреГ;£覃亩ис 一 С威噪@ ' 1gB写⑥a.遹窗每m:^g瓜二::
j ^АрвС:J^ http://jerome.datecreft.com/pl5/opp3/faYDrltOJ7l5Ql book.p5P二]J
! Learning Oracle PL/SQL
ю^—丁一"^钾~l丁仔厂?7^終笳詬鼻忑…一‘殳
Рис. 2,7. Результат работы PL/SQL Server Pages
Технология PL/SQL Server Pages пришлась по душе многим программистам и пользователям, так как предоставляет отличный способ быстрого создания web- узлов. За дополнительной информацией об этой технологии вы можете обратиться к книге Learning Oracle PL/SQL, написанной теми же авторами, что и книга, которую вы сейчас держите в руках.
1то же дальше
Итак, вы узнали, как использовать PL/SQL в SQL*Plus и ряде других распространенных средах, а также совместно с другими языками программирования. Кроме того, код PL/SQL можно:
О встраивать в программы на языках COBOL и FORTRAN, обрабатывать посредством прекомгшляторов Oracle;
О вызывать из языка программирования Ada с помощью технологии, называемой SQL*Module;
О выполнять автоматически, как триггеры событий, происходящих в базе данных Oracle, например при обновленш! таблицы;
Q включать в расписание для выполнения в базе данных Oracle с использованием встроенного пакета DBMS_JOB.
Некоторые из этих возможностей рассматриваются в последующих главах.
33Основы языкаОсновы языка
3
3
Основы языка
Основы языка
Структура блока PL/SQL
Набор символов PL/SQL
Идентификаторы
А Литералы
Разделитель в виде точки с запятой
А Комментарии
Ключевое слово PRAGMA
Метки
Каждый язык, будь то человеческий или компьютерный, имеет синтаксис, словарь и набор символов, и для того чтобы общаться с помощью этого языка, нужно выучить правила его использования. Многие с опаской приступают к изучению новых компьютерных языков, но вообще-то они очень просты, а PL/SQL еще и один из самых простых. Трудность общения на языке, основанном на байтах, связана не с самим языком, а с компилятором или компьютером] с которым мы «говорим». Большинство компиляторов - это «недалеки» создания, их трудно на、 звать творческими и гибкими, они не способны нестандартно мыслить, а их словарь крайне ограничен. Разве что соображают они очень-очень быстро.
Если вы слышите что-нибудь вроде: «Давай выпьем наю% то понимаете, о чем вам говорят, и знаете, что ответить. Однако если вы скажете PL/SQL; <подкинь-ка еще с полдюжины записей», то едва ли получите требуемое. Говоря на языке синтаксиса, для использования PL/SQL нужно расставлять все точки над <1». Поэтому в данной главе описываются основные правила языка, помогающие общаться с компилятором PL/SQL, а также структура блока PL/SQL. Кроме того, вы уз・ наете, что такое лексические единицы и для чего предназначено ключевое слово PRAGMA.
Структура блока PL/SQL
Все языки программирования позволяют организовать логически связанные элементы в программные единицы. В PL/SQL фундаментальной организационной
программной единицей является блок. Он лежат в основе двух ключевых языковых концепций.
О Модульность. Блок PL/SQL является базовой единицей кода. Из данных единиц создаются модули (например, процедуры и функции), которые, в свою очередь, составляют приложение. Именно блоки как организационные единицы низшего уровня определяют читабельность и простоту сопровождения программного кода.
О Область действия. Блок определяет область действия или контекст логически связанных объектов. В блоке группируются связанные между собой объявления и выполняемые операторы.
Блок может быть анонимным (то есть не имеющим имени) или именованным (представляющим собой процедуру и функцию). С блоком связано понятие пакета PL/SQL, который представляет собой набор из нескольких процедур и функций.
Разделы блока PL/SQL
Блок PL/SQL может содержать до четырех разделов, однако только один из них является обязательным.
О Заголовок. Используется только в именованных блоках, определяет способ вызова именованного блока или программы. Не обязателен.
О Раздел объявлений. Идентифицирует переменные, курсоры и вложенные блоки, на которые имеются ссылки в исполняемом блоке и блоке исключений. Не обязателен.
О Исполняемый раздел. Операторы, которые выполняются ядром PL/SQL во время работы приложения. Обязателен.
Q Раздел исключений. Обрабатывает исключения (предупреждения и ошибки). Не обязателен.
Структура блока PL/SQL для процедуры показана на рис. 3.1.
Заголозок
IS
Раздел объяапений
BEGIN
Исполняемый раздел
EXCEPTION fe Раздел исключений
Рис 3.1. Структура блока PL/SQL
Порядок размещения разделов блока соответствует последовательности написания и выполнения программы.
L Определяются тип блока (процедура, функция, анонимный) и способ его вызова (заголовок).
Объявляются все переменные и другие объекты PL/SQL, используемые в этом блоке (раздел объявлений).
Локальные переменные и другие объекты PL/SQL, объявленные выше, применяются для выполнения необходимых действий.
Обрабатываются все проблемные ситуации, которые могут возникнуть во время выполнения блока (раздел исключений).
Процедура, содержащая все четыре раздела, показана на рис. 3.2. В действительности все перечисленные разделы блока создаются не сразу, а в несколько этапов. Не ожидайте, что вы достигнете совершенства с первой попытки!
I PROCEDURE get happy (enamjln IN VAKHAR2)•■[— Заголовок
IS
I hiredBte DATE;U——Раздел объявлений
BEGIN.
hiredate 尸 SYSRATE - 2;
INSERT INTO employee„_ ,
「\ •Исполняемый рвзаел
(emp_name. hi redate)\
VALUES (enameJn. hiredate):
EXCEPTIONI
WHEN DUP_VAL_IN_INDEX
Раздел исключенийРаздел исключенийTHEN
Раздел исключений
Раздел исключений
DMBS_OUTPUT.PUT_LINE
{'Вставка не произведена'):
Рис. 3.2. Процедура, которая содержит все четыре раздела
Анонимные блоки
Когда кто-то хочет остаться неизвестным, он не называет своего имени. То же можно сказать и об анонимном блоке PL/SQL, показанном на рис. 3.3: в нем вообще нет раздела заголовка, блок начинается ключевым словом DECLARE (или BEGIN).
BEGIN
Исполняемый разделИсполняемый разделDBMS OUTPUT.PUT UNECHello word'):
Исполняемый раздел
Исполняемый раздел
END:
Рис. 3.3. Анонимный блок без разделов объявлений и исключений
Анонимный блок не может быть вызван другим блоком, поскольку не имеет составляющей, с помощью которой к нему можно обратиться. Таким образом, он является чем-то вроде сценария, который предназначен для выполнения операторов PL/SQL, в большинстве случаев включающих вызовы процедур и функций. Поскольку анонимные блоки могут содержать собственные разделы объявлений
и исключений, разработчики часто используют их для определения области видимости идентификаторов и области действия обработки исключений в программе большего размера. Эти свойства блока мы подробно рассмотрим в следующих разделах.
Структура анонимного блока
Общий формат анонимного блока PL/SQL таков:
[DECLARE необязательные операторы объявлений ]
BEGIN
...исполняемые операторы ...
[EXCEPTION
необязательные операторы обработки исключений ...]
END;
Квадратные скобки указывают на необязательную часть синтаксиса. Ключевые слова BEGIN и END являются обязательными, и между ними должен быть как минимум один исполняемый оператор.
Примеры анонимных блоков
Ниже приведены различные сочетания разделов, которые допустимы для блока PL/SQL..
О Анонимный блок BEGIN.. .END без разделов объявлений и исключений:
BEGIN
-Вывод текущей даты в стандартном формате
DBMS DUTPITT.PUT LINE(SySDATE):
END;~
О Анонимный блок с разделом объявлений, но без раздела исключений:
DECLARE
LEght now DATE SYSDATE:
BEGIN
DBMS OUTPUT.PUT_LINE(l_right_now );
END: -~~"
Q Анонимный блок с разделом объявлений, исполняемым разделом и разделом исключений:
DECLARE
Вызов определенной ранее функции для получения даты
-найма сотрудника по фамилии "FEUERSTEIN".
l_hiredate DATE :- employee_pkg.date_of_hire ('FEUERSTEIN');
l_right_now DATE :- SYSDATE;
l」ld timer EXCEPTION;
BEGIN "
IF l_hiredate < ADD_M0NTH3 (1 right now, 6)
THEN"~~
RAISE 1 old timer;
ELSE ' '.
hiredate :- right now:
END IF;
EXCEPTION
WHEN 1 old timer
THEN --
DBMS_OUTPUT.PUT LINE(‘y вас нет доступа к информации.');
WHEN OTHERS "
THEN-
DBMS_OUTPUT.PUT_LINE('ОШИБКА: 1 || SQLCODE);
END:一 一
Анонимные блоки выполняют последовательности операторов и затем завершают свою работу, действуя как процедуры. Фактически все анонимные блоки являются анонимными процедурами.
Анонимные блоки в различных средах
Анонимные блоки используются в средах, где код PL/SQL выполняется либо непосредственно, либо как часть некоторой программы этой среды (табл. 3.1). Объект, включающий конкретный блок, обеспечивает его контекст и в ряде случаев 一 имя программы.
Таблица 3.1. Анонимные блоки в различных средах
Объект, содержащий блок
Среда
Описание
Клиентский триггер
Средства разработки Oracle
«Чистые» исполняемые операторы в триггерах Forms Builder или Reports Builder, оформленные соответствующим инструментальным средством как анонимные блоки и отправляемые исполнительному ядру PL/SQL (подробнее о триггерах см. в главе 18)
Триггер базы данных
Манипуляции данными на уровне таблицы или события базы данных
Тело триггера. Хотя у триггера имеется имя, код PL/SQL анонимен (не имеет имени)
Сценарий
SQL*Plus или его эквивалент
Программы и пакетно выполняемые сценарии, вызывающие процедуры и/или функции. Кроме того, команда SQL*Plus EXECUTE транслирует сбой аргумент в анонимный блок, заключая его в операторы BEGIN и END
Откомпилированная программа
Встроенные языки (Рго*С и т, п,); OCI (Oracle Call Interface)
Анонимные блоки, встроенные в код программы и выполняемые сервером базы данных
Когда вы связываете код PL/SQL с триггером или полем, пользуясь таким инструментальным средством, как Forms Builder, этот код составляет анонимный блок PL/SQL. При этом можно создать полный блок с объявлениями, исполняемыми
операторами и разделом обработки исключений или же ограничиться только исполняемыми операторами..
Именованные блоки
Хотя анонимные блоки PL/SQL применяются во многих приложениях Oracle, вероятно, большая часть написанного вами кода будет оформлена в виде именованных блоков. Ранее вы уже видели несколько примеров хранимых процедур (см. рис. 3.1) и знаете, что их главной особенностью является наличие заголовка. Заголовок процедуры выглядит так:
PROCEDURE [схема.^имя ^параметр [, пдрдметр ...])]
[AUID {DEFINER | CURRENT_USER}]
Заголовок функции в целом очень похож на него, но дополнительно содержит ключевое слово RETURN:
FUNCTION [схема.]и«я [(пдрдметр [. пдрдметр ...])]
RETURN тип возВрдЩдеМЫХ^Ддиных
[AUID {DEFINER | CURRENT_USER}]
[DETERMINISTIC]
[PARALLEL ENABLE
[PIPELINED]
Поскольку Oracle позволяет вызывать некоторые функции из SQL-инструкций, заголовок функции включает больше необязательных элементов, чем заголовок процедуры. Таким образом, вам предоставляется гораздо больше возможностей управлять поведением функции во время ее выполнения. Более подробно процедуры и функции рассмотрены в главе 16.
Вложенные блоки
Вложенным является блок, который располагается внутри другого блока. Пример процедуры, содержащей анонимный вложенный блок, приведен ниже:
PROCEDURE calc totals
IS~
year_total NUMBER;
BEGIN -
year_total := 0;
/* Начало вложенного блока */
DECLARE
month_total NUMBER:
BEGIN ~
month_total :- year_total / 12:
END set_month_total;-
/* Конец вложенного блока */
END;
Между операторами /* и */ помещается комментарий (см. раздел «Комментарии» далее в этой главе). Вложенный блок, в свою очередь, также может содержать блок. Таким образом, допустимой является структура вложенных блоков, представленная на рис. 3.4.
DECLARE
CURSOR emp^cur IS
Рис. 3.4. Вложенные анонимные блоки
Вложенный блок PL/SQL называют также дочерним или подблоком, а внешний блок — родительским.
Область действия
Главное преимущество вложения блоков — возможность ограничить область действия всех объявленных во вложенном блоке объектов и исполняемых операторов. Она, в свою очередь, позволяет не только лучше управлять поведением программы, но и уменьшить вероятность того, что по ошибке программист изменит значение не той переменной.
Переменные, исключения, модули и некоторые другие структуры являются локальными для блока, в котором они объявлены. Когда выполнение блока прекратится, ни на одну из этих структур сослаться будет нельзя. Так, в приведенной выше процедуре calc_totals к элементам внешнего блока, в частности к переменной year_tota1, можно обращаться из любого места процедуры, тогда как элементы, объявленные во внутреннем блоке, из внешнего недоступны.
Чаще всего вложенный блок создается с целью размещения в нем раздела обработки исключений. Когда исключение обрабатывается в программе, она продолжает свое выполнение, вместо того чтобы завершиться с ошибкой. Например, если вы не уверены в том, имеется ли в базе данных конкретная запись, можно поступить так:
BEGIN
BEGIN /* вложенный блок */
INSERT INTO book_cop1es (isbn. qty)
VALUES C:new1sbn, -.copies);.
EXCEPTION.
WHEN DUPJAL ON_INDEX
THEN ~
UPDATE book_copies SET qty = ;copies WHERE Isbn 口 :newisbn: '
END: /* вложенный блок */
books.distribute Inventory report;
END:"-
В данном случае вложенный блок помогает обеспечить наличие в таблице нужной записи перед вызовом процедуры books. di strlbute_1 nventory_report. Kpo・ -ме того, он предотвращает прекращение работы программы из-за исключения DUP_VAL__ON_INDEX. Однако в случае появления таких ошибок) как 0RA-00942: Table or vi ew does not exi st, может оказаться, что работу программы необходимо прервать. Важно понимать) что именно вы должны контролщэовать происходящее и решать, как будет действовать приложение в том или ином случае. Более подробно об обработке ошибок в программах PL/SQL рассказывается в главе 6.
Видимость переменной
Важным свойством переменной, связанным с областью ее действия, является видимость. Данное свойство определяет, можно ли обращаться к переменной только по ее имени или же к имени необходимо добавлять префикс,
«Видимые» идентификаторы
Рассмотрим следующий фрагмент кода:
DECLARE
flrst_day DATE:
last_day DATE:
BEGIN "
first day :- SYSDATE;.
lastjay :- ADD MONTHS [first day, 6):
END:-"
Переменные f1 rst^day и last_day объявлены в том же бтюк% где они используются, поэтому црн обращении к ним указаны только их имена. Мы говорим, что идентификаторы этих переменных видимы. В общем случае видимыми идентификаторами являются:
О индексная переменная цикла (видима и доступна только внутри цикла);
О идентификатор, объявленный в текущем блоке;
Q идентификатор, объявленный в блок& который включает текущий блок;
О отдельный объект базы данных (таблвда, представление и т. д.) или объект PL/SQL (процедура, функция)) владельцем которого вы являетесь;
Q отдельный объект базы данных или объект PL/SQL, на который у вас есть соответствующие права и который определяется соответствующим синонимом. Следовательно, ссылаться на эти объекты можно, просто указывая их имена.
PL/SQL позволяет также обращаться к существующим объектам, которые не находятся в пределах непосредственной видимости блока. Как это делается, рассказано в следующем разделе.
Уточненные идентификаторы
Характерными примерами идентификаторов, которые невидимы в области кода, где они используются, являются идентификаторы, объявленные в спецификации
пакета (имена переменных, типы данных, а также имена процедур и функций), Делая ссылку на такой объект, необходимо перед его именем указывать префикс и символ точки (аналогичным образом аы уточняете имя столбца посредством имени таблицы, в которой он содержится). Например:
О pr1ce_uti 1. compute^reans - программа с именем compute_reans из пакета pri ce_utl 1 :
О math, pi - константа с именем р1, объявленная и инициализированная в пакете math;
О :GLOBAL.company.Id - глобальная переменная в Oracle Fonns.
(В описании такой переменной указано, что ока собой представляет, ко по ее имени невозможно определить тип - это еще один аргумент в пользу соглашения об информативных именах.)._
Можно также использовать дополнительное уточнение, определяющее владельца объекта. Например, выражение
scott, рг1 ce_utl Lconipute_fneans
указывает на процедуру compute^reans пакета price_uti 1, принадлежащего пользователю Oracle с учетной записью scott.
Уточнение идентификаторов с помощью имен модулей
Обеспечить правильное применение идентификаторов в PL/SQL можно несколькими методами. В частности, при работе с пакетами удобно создавать переменные, имеющие глобальную область действия. Предположим, у нас есть пакет сотра- ny_pkg и в спецификации пакета объявлена переменная с именем 1 ast_companyjd:
PACKAGE companyjjkg/
IS
last_companyjd NUMBER:
END conipanyjJlcg:■.
Далее, ссылаясь ка переменную last.conpahyJd MaB配;品"ходим。предварять ее имя именем этого пакета:■”
IF new_company]d - companyjikg. 1 aste_conipany_'ld THEN
Поскольку переменная( объявленная в специфн^сащхи пакета, глобальна для сеанса, на нее можно ссылаться в любой программе, но доступна она только при указании имени пакета.
Идентификатор можно уточнить также с помощью имени модуля)в котором он определен:
PROCEDURE calc totals
is -
salary NUMBER;
BEGIN-
DECLARE
salary NUMBER;
BEGIN
salary :- totals, sal ary;
END;-
END;"
В первом объявлении создается переменная salary, областью действия которой является вся процедура. Однако во вложенном блоке объявляется другая переменная с тем же именем. Поэтому ссылка на переменную salary во внутреннем блоке всегда сначала разрешается относительно объявления в этом блоке, где переменная видима безо всяких уточнений. Чтобы во внутреннем блоке сделать ссылку на переменную salary, объявленную на уровне процедуры, необходимо уточнить ее имя посредством имени процедуры (cal__totals.salary).
В PL/SQL имеется много правил, с помощью которых можно найти выход из подобных ситуаций. И хотя знать их полезно, лучше использовать уникальные идентификаторы, чтобы избежать конфликтов имен вообще.
Набор символов PL/SQL
Программа PL/SQL представляет собой последовательность операторов, состоящих из одной или нескольких строк текста. Набор символов, из которых можно составлять эти строки текста, зависит от используемого в базе данных набора символов. Для примера в табл. 3.2 приведены символы, входящие в набор US7ASCIL
Таблица 3.2. Очмволы из набора US7ASCII, которые можно использовать в PL/SQL
ТипСимволы
БуквыA-Z, а-г
Цифры0-9
Знаки~!@#$%*0_-+=|[]{}:;"' <>г?/л
Пробельные Табуляция,пробел,новая строка, возврат каретки
символы
Каждое ключевое слово в PL/SQL состоит из разных комбинаций символов данного набора. По умолчанию язык SQL не чувствителен к регистру символов. Это означает, что символы верхнего регистра в нем интерпретируются так же, как символы нижнего, за исключением ситуаций, когда символы заключены в одинарные кавычки, что превращает их в литеральные строки.
Многие из указанных символов, в отдельности или в сочетании с другими символами, имеют в PL/SQL специальное назначение. Простые и составные специальные символы описаны в табл. 3.3.
Таблица 3.3. Простые и составные специальные символы в PL/SQL
СимволыОписание
;Завершаетобъявления и операторы
%Индикаторатрибута (атрибут курсора, подобный%ISOPEN, или атрибут
неявных объявлений, например % ROWTYPE); кроме этого, используется в качестве символа подстановки в условии LIKE
_Символ подстановки одного символа в условии LIKE
@Индикатор удаленного местоположения (произносится «эта»)
Символы
Описание
Индикатор хосг-переменной, например :block.item в Oracle Forms
**
Оператор возведения в степень
о или != или
Оператор уравнения «не равно»
Л= или ~=
II
Оператор конкатенации
« и >>
Ограничители метки
<=и >=
Операторы сравнения «меньше или равно» и «больше или равно»
:=
Оператор присваивания
=>
Оператор ассоциации
Оператор диапазона
-
Индикатор однострочного комментария
/*И*/
Начальный и конечный ограничктели многострочного комментария
Символы группируются в лексические единицы, которые называют атомарными, поскольку они представляют собой наименьшие самостоятельные элементы языка. В PL/SQL лексическими единицами являются вдентификатор, литерал, разделитель и комментарий. Мы рассмотрим их в следующих разделах.
Идентификаторы
Идентификатор — это имя объекта PL/SQL, которым может быть:
О константа;.
О скалярная переменная;
О составная переменная (запись или коллекция);
О исключение;
О процедура;•
О функция;
О пакет;'.
О тип;、.
О курсор;
О зарезервированное слово;
О метка.
К вдентификаторам PL/SQL предъявляются следующие требования:
О длина — до 30 символов;
О должны начинаться с буквы;
Q не должны содержать пробелов (но могут включать символы《$相 <_» и «#>).
Если единственным отличием двух идентификаторов является регистр одного или более символов, компилятор PL/SQL воспринимает их как один и тот же идентификатор1. В частности, одинаковыми считаются следующие идентификаторы:
lots_of_$MONEY$'
LOTS 二of_$MONEY$ Lots_of_$MoneyS
Примеры допустимых имен идентификаторов вы найдете далее;
conipanyjd#
prl mary_acct-responsi М1 i ty
F1rst_Name Fl rstName addressjlnel S123456"
Идентификаторы, которые приведены ниже, в PL/SQL недопустимы:
1stj*ear--Начинается с цифры
procedure-name--Содержит кедопустиммйсимвол
min1muni_X_due--Содержит недопустимыйсимвол Т
max1nmm_value_exp]oded_for_deta11 --Имя слишкон длинное
company ID-- Имя не должно содержать 叩обелов
Идентификаторы — это имена, с помощью которых можно обращаться к элементам программы, и лучше, когда они отражают назначение элементов. Не стоит пользоваться именами, подобными XI и temp, поскольку они ни о чем не говорят ни вам, ни программисту, которому придется работать с вашим кодом.
Некоторые из перечисленных правил именования объектов можно нарушать, если заключить идентификатор в двойные кавычки. Однако мы не рекомендуем пользоваться этим методом. Хотя когда-нибудь вы наверняка встретите <крутой» код вроде следующего:
SOL> DECLARE
"pl" CONSTANT NUMBER :" 3.141592654;
"PI" CONSTANT NUMBER :- 3.14159265358979323846;
”2 pl” CONSTANT NUMBER :- 2 * *p1*:
BEGIN
DBMS_OUTPUT.PUT LI NEC pl: ' || "pl");
OBMS.OUTPUT.PUTJINECPI: ' || pl);
DBMS_OUTPUT,PUT LINEC2 pl: 1 11 "2 pl"): 9* END;
/
pl: 3.141592654
PI: 3.14159265358979323846
2 pl; 6.283185308
С этой целью компилятор для своего внутреннего представления на р&нней стадии компиляции переводит всю программу я верхний регистр.
Обратите внимание, что в строке 7 идентификатор pl используется без кавычек, Поскольку компилятор обеспечивает независимость от регистра, преобразуя все идентификаторы и ключевые слова в верхний регистр, данный идентификатор указывает на переменную, объявленную в строке 3 как "PI".
Иногда двойные кавычки применяются в SQL-инструкциях для ссылки на таблицы базы данных, имена столбцов которых содержат несколько слов, разделенных пробелами..
Зарезервированные слова
Некоторые идентификаторы (такие как BEGIN, IF и THEN) имеют в языке PL/SQL специальное назначение. К ним относятся:
О ключевые слова;
О идентификаторы пакета STANDARD.
Вы не должны, и в большинстве случаев не сможете, использовать их в качестве имен объектов, объявляемых в своих программах.
Ключевые слова
Существуют слова, которые, с точки зрения компилятора PL/SQL, имеют строго определенную семантику — являются ключевыми. К таковым относится, например, слово END, завершающее программу, условный оператор IF и оператор цикла. Предположим, вы Допытаетесь объявить переменную с именем end, как в следующем примере:'
DECLARE1
end VARCHAR2(10) :- 'blip'; /* He будет работать! END нельзя использовать в качестве имени переменной.*/
BEGIN
DBMS DUTPUT.PUT LINE(end);
END;—"
/
В ответ получите сообщение об ошибке компиляции:
PLS-00103: Encountered the syntol "END" when expecting one of the following:
Слово end в разделе объявлений сообщает компилятору PL/SQL о преждевременном завершении анонимного блока.
Идентификаторы пакета STANDARD
Не следует использовать в качестве идентификаторов имена объектов)которые определены в специальном пакете STANDARD. В нем объявлено большое число идентификаторов —это имена встроенных исключений (подобные OUP_VAL_ON_INDEX), функций (например, UPPER, REPLACE и Т0_0АТЕ) и подтипов (таких как STRING). Список функций и процедур модуля STANDARD можно просмотреть с помощью команды
SQL> DESC SYS.STANDARD
Если все же вам потребуется назначить своей переменной имя встроенного объекта модуля STANDARD, то вы можете сделать это. На соответствующий модулю объект можно будет ссылаться, указав префикс STANDARD, как показано в следующем примере:
DECLARE
dup_val_on_index EXCEPTION: -- локальное переопределение
BEGIN ———
INSERT INTO ... /* может вызвать встроенное исключение */
RAISE dup_val_on_index; ♦- разрешается как ссыпка на локально объявленное исключение EXCEPTION ~ - -
WHEN dup_val_on_index
THEN ~ ~ ~
/* обработка локально объявленного исключения */
WHEN STANDARD.DUP_VAL_ON_INDEX
THEN-
/* обработка обычного исключения */
END;"
Как избежать использования зарезервированных слов в качестве идентификаторов
Придумать для идентификатора отвечающее требованиям имя совершенно несложно, поскольку существуют тысячи и тысячи сочетаний допустимых символов. Проблема в том, как при этом избежать использования одного из зарезервированных слов — ведь в PL/SQL их сотни!
Можно, конечно, проигнорировать данный момент и исправлять ошибки компиляции по мере их возникновения. Именно так большинство и поступает, но на поиск ошибок иногда тратится целый рабочий день, поскольку сообщения об ошибках компиляции часто не отражают причину ошибки. Некоторые программисты используют дорогостоящие средства разработки с мощным редактором, который имеет встроенные функции синтаксического анализа и сообщает о возможных проблемах по мере ввода программного кода. Мы же предпочитаем пользоваться дешевым редактором, который знает ключевые слова PL/SQL и автоматически их выделяет.
Ниже приведен минимальный список слов, которые нельзя использовать в качестве идентификаторов в программах PL/SQL (табл. 3.4). При составлении этой таблицы применялся следующий метод. Для каждого зарезервированного слова (их полный список хранится в представлении V$RESERVED_WORDS) мы пытались объявить сначала переменную, а затем процедуру с таким же именем. Если компилятор не позволял выполнить одну или обе эти операции, соответствующее ключевое слово помещалось в список.
Если вы работаете с Oracle 8.1.5 или более поздней версией и имеете соответствующие права, то для получения полного списка зарезервированных слов можете выполнить следующую инструкцию SQL:
SQL> SELECT * FROM V$RESERVED WORDS:
Хотя данный список очень длинный, он не включает идентификаторы модуля STANDARD. Это еще один аргумент в пользу «интеллектуальных» редакторов.
Таблица 3.4. Минимальный список слов, которые нельзя использовать в качестве идентификаторов в программах PL/SQL
ACCESS
DATE
INITIAL
OR
SUCCESSFUL
ADD
DEQMAL
INSERT
ORDER
SYNONYM
ALL
DECLARE
INTEGER
OVERLAPS
SYSDATE
ALTER
DEFAULT
INTERSECT
PACKAGE
TABLE
AND
DELETE
INTO
PCTFREE
THEN
ANY
DESC
IS
PRIOR
TO
AS
DISTINCT
LEVEL
PRIVILEGES
TRIGGER
ASC
DROP
LIKE
PROCEDURE
ТУРЕ
AT
ELSE
LOCK
PUBLIC
UID
AUDIT
END
LONG
RAW
UNION
BEGIN
EXCLUSIVE
MAXEX向 TS
RENAME
UNIQUE
BETWEEN
EXISTS
MINUS
RESOURCE
UPDATE
BY
HLE
ML5LABEL
RETURN
USE
CASE
FLOAT
MODE
REVOKE
USER
CHAR
FOR
MODIFY
ROLLBACK
VALIDATE
CHECK
FROM
NOAUDIT
ROW
VALUES
CLOSE
FUNCTION
NOCOMPRESS
ROWID
VARCHAR
CLUSTER
GRANT
NOT
ROWNUM
VARCHAR2
COLUMN
GROUP
NOWATT
ROWS
VIEW
COLUMNS
HAVING
NULL
SAVEPOINT
WHEN
COMMENT
IDENTIFIED
NUMBER
SELECT
WHENEVER
COMMIT
IF
OF
SESSION
WHERE
COMPRESS
IMMEDIATE
OFFLINE
SET
WITH
CONNECT
IN
ON
SHARE
CREATE
INCREMENT
ONLINE
SIZE
CURRENT
INDEX
OPEN
SMALLINT
CURSOR
INDEXES
OPTION
START
Отступы и ключевые слова
Идентификаторы необходимо отделять друг от друга хотя бы одним пробелом или разделителем. Кроме этого, вы можете форматировать текст программы, вставляя дополнительные пробелы, символы разрыва строки (перевод строки и/или возврат каретки) и табуляции в местах, где полагаются пробелы, и это не вызовет сообщения об ошибке. Например, два приведенных ниже оператора эквивалентны: IF too_many_orders THEN 一 一
warn_user;
ELSIF no orders entered
THEN
pronpt for^orders:
END IF: ~ -
IF too_nany_orders THEN warn_user:
EL5IF no orders_entered THEN prompt_for_orders;
END IF; ~~
Однако внутри лексических единиц, таких как оператор «не равно», представленный символами <1=», наличие пробелов, символов разрыва строки или табуляции не допускается. В частности, при компиляции строки
IF max_5a〕ary ! » min_salary THEN
вы получите сообщение об ошибке, поскольку между символами «1 ■» имеется пробел.
Питералы
Литерал — это значение, с которым не связан идентификатор, оно существует «само по себе». Литерал может быть представлен одним из следующих типов данных: NUMBER (например, 415, 21,6 или NULL), STRING (например, 'Предложение1, 'Q1-FEB-2QQ3', NULL) либо BOOLEAN (а именно TRUE, FALSE или NULL),
Заметьте, что в PL/SQL нет возможности непосредственно задать литеральное значение даты. Значение '01-FEB-2003' - это строковый литерал, то есть последовательность символов, заключенная в одинарные кавычки. Такую строку можно конвертировать в дату с помощью PL/SQL или SQL, но внутри базы данных Oracle для значений дат используется только двоичное представление.
Строковый литерал может состоять из одного или более символов, входящих в набор символов PL/SQL. Литерал длиной нуль символов представляется как 1' (две последовательные одинарные кавычки без символов между ними). В Огас- 1е9£ строковый литерал нулевой длины имеет значение MULL1 и тип данных CHAR (строка нулевой длины).
В отличие от идентификаторов, строковые литералы в PL/SQL чувствительны к регистру символов. Например, значения следующих двух литералов различны: 'Steven' 'steven1
При проверке приведенного ниже условия возвращается значение FALSE:
IF 'Steven' - 'steven'
Одинарные кавычки внутри строки
Иногда возникает необходимость включить одинарную кавычку в литерал как часть литерала. Для этого в месте, где должна стоять кавычка, ставятся два таких символа. Как это делается, показано ниже. Слева приведены различные строко- вые литералы, а справа - их《внутренние» строковые представления.
Пусть вас не смущает несоответствие данного утверждения с документацией Oracle, в которой сказано, НТО строка нулевой длины — это не то же самое, что значение NULL, как в стандарте ANSI.
Литерал Знзченне
T^ere'^ no business like show business.'There's no business like show business.
-Hound of the baskervllles"'"Hound of the baskervllles"
hello'hello'NLS—LANGUAGEn'ENGLISH"'nls_language=,engush,
hello'
hello'
'"hello"'
Перечень правил, которыми следует руководствоваться при включении одинарной кавычки в литерал, приведен ниже.
О Чтобы включить одинарную кавычку в литерал, поместите на ее место две подряд идущие одинарные кавычки.
О Если нужно включить одинарную кавычку в начало или конец литерала, ис- нользуЙте три подряд идущие одинарные кавычки.
О Для создания строкового литерала, состоящего из одной одинарной кавычки, поместите подряд четыре одинарные кавычки.
О Чтобы создать литерал, состоящий из двух одинарных кавычек, поставьте подряд шесть одинарных кавычек.
Две одинарные кавычки подряд — это не то же самое, что символ двойной кавычки, который в строковом литерале не имеет специального значения, а интерпретируется так же, как буква или цифра.
Числовые литералы
Числовые лйтералы могут быть целыми или действительными (то есть содержащими дробную часть) числами. Заметьте, что PL/SQL рассматривает число 154,00 как действительное, хотя его дробная часть равняется нулю и с точки зрения математики оно является целым. Целые и действительные числа имеют разное внутреннее представление, и преобразование числа из одной формы в другую требует определенных ресурсов.
Задавая числовые литералы, можно использовать также экспоненциальное пред- ставление числа. При этом символ «Е» (в верхнем или нижнем регистре) обозначает умножение числа на 10 в степени % например, 3.05Е19, 12е-5.
Логические (булевы) литералы
Для представления логических значений в Oracle определено два литерала: TRUE и FALSE. Это не строки, и их не нужно заключать в кавычки. Они используются Для присвоения значений логическим переменным, как в следующем примере:
DECLARE
enough_money BOOLEAN: -- Объявляем логическую переменную
BEGIN -
enough_money := FALSE: -- Присваиваем ей значение
END; ~
При проверке логического значения литерал можно не указывать — тип переменной говорит сам за себя:.
DECLARE
enough_nioney BOOLEAN;
BEGIN -
IF enough_nwney
THEN 一'
Разделитель в виде точки с запятой
Программа на PL/SQL представляет собой последовательность объявлений переменных и операторов, границы которых определяются не кодом конца строки, а символом точки с запятой (;). Один оператор часто состоит из нескольких строк. Например, следующий оператор IF занимает четыре строки:
IF salary < mln^salary (2003)
THEN-
salary :- salary + salary *0.25;
END IF;•
В нем вы видите два символа точки с запятой. Первый отмечает конец единственного оператора присваивания в конструкции IF.. .END IF, а второй - конец оператора IF. Данный оператор можно разместить и в одной строке, причем результат будет одинаковым:
IF salary < min_salary (2003) THEN salary :- salary + salary*0.25; END IF:
Каждый оператор также должен завершаться точкой с запятой, даже если они вложены друг в друга. Но если вашей целью не является создание нечитабельного кода, советуем каждый оператор или объявление размещать на отдельной строке.
Комментарии
Наличие поясняющего и сопроводительного текста в коде (комментариев) является основным признаком хорошей программы. В данной книге приводится множество советов, касающихся того, как сделать программы самодокументируемыми за счет использования продуманных соглашений об именах и модульного подхода. Однако для понимания сложного программного кода этого еще не достаточно. PL/SQL предлагает разработчикам две разновидности комментариев: однострочные и многострочные.
Однострочные комментарии
Однострочные комментарии начинаются двумя дефисами (--), между которыми не должно быть пробелов или каких-либо других символов. Весь текст после двух дефисов и до конца строки рассматривается как комментарий и игнорируется
компилятором. Если два дефиса стоят в начале строки, комментарием считается вся строка.
Запомните: два дефиса отмечают как комментарий остаток строки в редакторе кода, а не всю инструкцию PL/SQL, после которой они стоят. Например, в следующем операторе IF с помощью однострочного комментария поясняется содержимое условного выражения:
IF salary < m1n_salary (2003) -- Функция m1n_salary возвращает минимальную годовую зарплату THEN一一
salary :- salary + salary *0.25;
END IF:
Многострочные комментарии
Если однострочные комментарии удобны для создания кратких замечаний к фрагменту кода или для временного исключения строки программы из обработки, то многострочные позволяют включать в программу длинный сопроводительный текст или пояснения.
Многострочный комментарий помещается между начальным (/*) и конечным (*/) ограничителями. Весь текст между этими двумя парами символов рассматривается как часть комментария и игнорируется компилятором.
В следующем примере многострочный комментарий располагается в разделе заголовка процедуры. Слева, для того чтобы зрительно выделить комментарий в программе, создан столбик вертикальных черточек:
PROCEDURE calc_revenue (companyjd IN NUMBER) IS
/*"-
I Имя программы: calc_revenue.
I Автор: Стивен Ферстайн
I Программа/предназначена для ...
I Для рабо/ы программы требуется следующее аппаратное обеспечение: …
*/
BEGIN
ENO; ■
С помощью многострочных комментариев можно также отменить выполнение блока кода программы на время тестирования, как это сделано в следующем фрагменте:
EXIT WHEN a_delimiter Cnext_char)
OR
(was_a_delimiter AND NOT a_delimiter (next_char))
Ключевое слово PRAGMA
Ключевое слово PRAGMA указывает, что остальная часть оператора PL/SQL является директивой компилятора (псевдоинструкцией), которая передает некоторую информацию компилятору и при трансляции не включается в исполняемый код.
Директивы компилятора задаются следующим образом:
PRAGMA директива;
Они могут располагаться в любом месте раздела объявлений. PL/SQL поддерживает четыре директивы компилятора, краткое описание которых приведено в следующей таблице.
Директивы компилятора
Описание
AUTONOMOUS_TRANSACTION
Предписывает исполнительному ядру PU8L выполнить сохранение или откат любых изменений, внесенных в базу данных в текущем блоке, без воздействия на главную транзакцию. Данная директива введена в Orade8l. Дополнительную информацию см. в главе 13
EJCCEPTIONJNn
Указывает компилятору связать конкретный номер ошибки с идентификатором, который объявлен а программе как исключение. Дополнительную информацию см. в главе б
RESTRICT.REFERENCES
Задает для компилятора уровень чистоты программы пакета (отсутствия в ней действий, вызывающих побочные эффекты). Дополнительную информацию см. в главе 16
SERIALLY_REUSABLE
Указывает исполнительному ядру PIJ5QL, что данные уровня пакета не должны сохраняться между обращениями к ним. Эта директива введена в Огас1е81. Дополнительную информацию см. в главе 17
В следующем блоке демонстрируется, как использовать директиву EXCEPTION. INIT для указания имени встроенного исключения. Если этого не сделать, исключение будет иметь только номер.
DECLARE
no_such_sequence EXCEPTION:
PRAGMA EXCEPTION_INIT (no_such_sequence, -2289):
BEGIN~ 一 —
EXCEPTION
WHEN no_such_sequence
THEN ~~
END:
Метки
Метка. PL/SQL — это способ наименования некоторого фрагмента программы. Она имеет такой формат:
«идентифика тор»
Здесь идентификатор — это допустимый идентификатор PL/SQL (длиной до 30 символов и начинающийся с буквы), который не сопровождается символом
точки с запятой. Метка располагается непосредственно перед фрагментом кода, имя которого она определяет. Фрагмент кода должен включать исполняемый оператор, им может быть даже оператор NULL:
BEGIN
«the_spot»
NULL:"
Поскольку анонимный блок представляет собой труппу исполняемых операторов, с помощью метки можно задавать имя анонимного блока (на время его выполнения). Это показано в следующем фрагменте кода:
«1nsert_but ignore_dups»
BEGIN 一 *
INSERT INTO catalog
VALUES (...):
EXCEPTION
WHEN DUP_VAL_ON_INDEX
THEN " ~
NULL;
END insert^utjgnore^ups;
Блоки часто именуют для улучшения читабельности кода. Давая блоку имя, вы делаете код самодокументированным и наверняка еще раз задумаетесь над тем, что он делает. Это иногда помогает находить ошибки.
Метки используют также, чтобы иметь возможность уточнить имя объекта внешнего анонимного блока в случае обращения к нему из внутреннего блока. Пример того, как это делается, приведен ниже:
«outerbl оск»
DECLARE
counter INTEGER :- 0;
BEGIN
DECLARE
counter INTEGER :- 1;
BEGIN
IF counter - outerblock.counter THEN
END IF;
END:
END Giiterblock:
Без метки блока невозможно различить две переменные с одинаковым именем counter (лучше присвоить этим переменным разные имена).
Третья функция меток заключается в том, что они могут служить целевыми точками перехода для операторов GOTO. Однако в современных программах операторы GOTO использовать не принято, поскольку схема обработки исключений действует гораздо эффективнее. Во всем коде PL/SQL, который мне когда-либо доводилось видеть, я помню только один оператор GOTO.
Наиболее значимым является применение меток в качестве целевых точек выхода для операторов EXIT во вложенных циклах:
BEGIN
«outer_loop>>
LOOP -
LOOP
EXIT outer」。叩:
END LOOP; -
некоторый/пера тор;
END LOOP:
END;
Без метки outerjoop осуществляется выход из внутреннего цикла, после чего выполняется некоторый_рператор. Однако с точки зрения алгоритма это не нужно. Последовательность выполнения операторов, которую задает метка в данном случае, нельзя организовать никаким другим способом.
Часть II
Структура программы PL/SQL
В этой части книги рассматриваются основы программирования и структура программных операторов PL/SQJL, в частности описываются условные операторы (IF и CASE), операторы управления потоком (GOTO и NULL), циклы и методы обработки исключений. Изучив данный материал, вы будете знать, как составлять блоки кода, отвечающие сложным требованиям, которые предъявляются к приложениям.
П Глава 4. Условные операторы и переходы
Глава 5. Циклы
Глава 6. Обработка исключений
Условные операторы и переходы
А Операторы IF
А Операторы CASE
А Оператор GOTO
А Оператор NULL
В настоящей главе вы познакомитесь с двумя типами управляющих операторов PL/SQL: условными операторами и операторами перехода. Операторы первого типа, присутствующие практически в любом коде, управляют последовательностью выполнения пр0rpаммною кода на основе заданных условий. В языке PL/SQL к их числу относятся операторы IF и CASE (оператор CASE был введен только в Oracle9z). Кроме того, существуют так называемые CASE-выражения, совмещающие функции операторов IF и CASE. В отличие от условных операторов, операторы безусловного перехода - GOTO и NULL (не выполняют никаких функций) —используются очень редко.
Операторы IF
В программах очень часто приходится реализовывать выполнение тех или иных операций в зависимости от определенных условий, например:
Q если оклад находится в пределах от десяти до двадцати тысяч долларов, назначить бонус в размере 1500 долларов;
О если в пределах от двадцати до сорока тысяч — в размере 1000 долларов;
О а если он превышает сорок тысяч долларов 一 в размере 500 долларов;
или:
Q если пользователь предпочитает применить панель инструментов, вывести ее при открытии окна.'
Операторы IF позволяют реализовать в программе условную логику. Как следует из представленной ниже таблицы, они бывают трех видов.
Тип оператора IF Описание
IF THEN END IF;
Эго простейшая форма оператора IF. Условие, содержащееся между ключевыми словами IF и THEN, определяет, должна ли выполняться группа операторов, находящаяся между THEN и END IF. Если результат проверки условия равен FALSE, то данный код не обрабатывается
IF THEN ELSE END IF;
Конструкция реализует логику «или-или». В зависимости от условия, заданного между ключевыми словами IF и THEN, выполняется либо код, находящийся между THEN и ELSE, либо код между ELSE и END IF. Но в любом случае реализуется только одна из даух групп исполняемых операторов
IF THEN ELSIF ELSE END IF;
Эта последняя, и самая сложная, форма оператора IF выбирает действие на основе набора взаимоисключающих условий и выполняет соответствующую группу исполняемых операторов. Собираясь применить такой оператор в Oracle9L подумайте, не воспользоваться ли вместо него оператором выбора CASE
Конструкция IF...THEN
Общий синтаксис конструкции IF.. .THEN таков:
IF условие THEN
последовательность исполняемых операторов
END IF;
Здесь условие - это логическая переменная, константа или логическое выражение, результатом проверки которого является возвращаемое значение TRUE, FALSE или NULL. Исполняемые операторы, расположенные между ключевыми словами THEN и END IF, выполняются, если результат проверки условия равен TRUE, и не выполняются —если он равен FALSE или NULL.
Следующий условный оператор IF сравнивает два различных числовых значения. Учтите, что если одно из них равно NULL, тогда все выражение возвращает NULL (и в данном случае бонус не назначается):
IF salary > 40000
THEN
g1ve_bonus(employee id, 500);
END IF;"
Совершенно не обязательно размещать ключевые слова IF, THEN и END IF в отдельных строках. Фактически для любого оператора IF количество строк не имеет никакого значения. Поэтому приведенный выше пример можно было бы переписать такаким образом.
IF salary > 40000 THEN give_bonus(employee_1d, 500); END IF;
Для столь простого оператора IF так даже удобнее, хотя любой чуть более сложный оператор гораздо лучше читается, когда в нем каждое ключевое слово
размещается в отдельной строке. Например, довольно трудно разобраться, в назначении следующего трехстрочного фрагмента кода:
IF salary > 40000 THEN INSERT INTO employee_bonus (eb_employeejd. eb_bonus_amt)
VALUES (employeejd. 500): UPDATE emp_employee SET emp_bonus_given-1 WHERE emp_
employee_id-employee_1d; END IF:、
Никому не захочется тратить время на то, чтобы в этом разобраться. И тот же оператор становится вполне читабельным, если аккуратно разбить его на строки: IF salary > 40000 THEN
INSERT INTO emp1oyee_bonus
(eb_employeejd, eb_bonus_amt)
VALUES Cemployee_ld, 500);
UPDATE emp_employee
SET emp_bonus_given-l
WHERE efnp_empToyee_id-employee id;
END IF: "~I
Вопрос читабельности становится еще более важным при использовании ключевых слов ELSE и ELSIF, а также вложенных операторов IF. Поэтому, чтобы сделать логику операторов IF максимально наглядной, советуем применять все возможности отступов и форматирования. И те программисты, которым придется сопровождать ваши программы в будущем, будут вам очень признательны.
Конструкция IF...THEN...ELSE
Конструкция IF.. .THEN.. .ELSE применяется в случаях, когда нужно выбрать одно из двух взаимоисключающих действий. Формат этой версии оператора IF таков: IF условие THEN
...поспедовательность опердторов, выполняемых s случае, когда результат проверки условия равен TRUE … ELSE
...поспеаовательность операторов, выполняемых s случае, когда резупьтат проверки условия равен FALSE или NULL ...
END IF:
Здесь условие — это логическая переменная, константа или логическое выражение. Если его значением является TRUE, выполняются операторы, расположенные между ключевыми словами THEN и ELSE, а если таковым служит FALSE или NULL, то выполняются операторы, расположенные между ключевыми словами ELSE и END IF.
ПРИМЕЧАНИЕ
Обратите внимание, что после ELSE ключевое слово THEN не используется.
Важно помнить, что в конструкции IF.. .THEN.. .ELSE всегда выполняется одна из двух возможных последовательностей операторов. После выполнения соответствующей последовательности операторов управление передается тому из них, который расположен сразу после ключевых слов ENO IF.
Вот пример конструкции IF.. .THEN.. .ELSE, соответствующей последнему из приведенных в начале главы условий назначения бонуса:
IF salary <- 40000
THEN
give_bonus(employee_1d. 0): -- не назначать бонус
ELSE 一'
give_b°nus(employee__id. 500): -- назначить бонус 500 долларов
END IF~-
В этом примере сотрудникам с размером оклада более 40 000 долларов назначается бонус в 500 долларов, а всем остальным он не назначается вообще. Или все же назначается? Что произойдет, если у одного из сотрудников оклад по какой-то причине окажется равным NULL? В этом случае будут выполнены операторы, следующие за ключевым словом ELSE, и указанный сотрудник получит бонус, полагающийся только высокооплачиваемым сотрудникам. Но ведь это не правильно! Поскольку мы не можем быть уверенье что оклад никогда не окажется равным NULL, нужно предусмотреть такую ситуацию, воспользовавшись функцией NVL;
IF NVLCsalary.O) <- 40000
THEN
give_bonus(employeejd, 0); 一 не назначать бонус
ELSE --
give_bonus(employee_id, 500): -• назначить бонус в 500 долларов
END IF;"
Теперь, если значение переменной salary равно NULL, функция NVL возвращает О, и в этом случае бонус не назначается.
ИСПОЛЬЗОВАНИЕ ЛОГИЧЕСКИХ ФЛАГОВ
Логические переменные удобно использовать в качестве флагов. Это избавляет от необходимости вычислять одно и то же логическое выражение по нескольку раз. Помните, что результат такого выражения можно присвоить значению логической переменной. Например, вместо того чтобы писать:
IF :customer.order_total > max_al1owable_order
THEN-~~
order_exceeds_bal1ance :- TRUE:
ELSE ~~
order_exceeds_bal1ance :- FALSE:
END IF;~
можно воспользоваться гораздо более простым выражением: order_exceeds_bal1ance :- :customer.order_total > max_al1owable_order: Теперь если где-либо в программном коде потребуется проверить, не превышает ли заказ (order_total) максимально допустимое значение (max_al - 1 owabl e_order), достаточно будет такой простой и понятной конструкции: IF order_exceeds_bal1ance THEN 一 一
Если вам еще не приходилось работать с логическими переменными, возможно, на освоение этой техники уйдет некоторое время. Но затраты окупятся сполна, поскольку в результате вы получите более простой и читабельный код.
Конструкция IF...THEN...ELSIF
Данная форма оператора IF полезна в тех случаях, когда реализуется логика со многими альтернативными действиями; это уже не ситуация <или-или>. Вот как записывается ряд взаимоисключающих альтернатив:
IF условие 1
THEN ~
операторы_1
ELSIF условие^
операторы 力
[ELSE _'
операторы else]
END IF: _
ВНИМАНИЕ
Используя ключевое слово ELSIFr будьте внимательны, чтобы не написать ELSEIF или ELSE IF. Это очень распространенная синтаксическая ошибка.
ЗНАЧЕНИЯ NULL В ОПЕРАТОРЕ IF
Следует очень четко понимать, как в операторе IF обрабатываются значения NULL. Одно значение NULL в логическом выражении обычно превращает результат всего выражения в NULL, а это не TRUE и не FALSE. Например, два очень похожих оператора дают разные результаты:
IF х - 2
THEN
DBMS OUTPUT.PUT LINE('значение перененной x равно 2');
ELSE --
DBMS OUTPUT.PUT LINECзначение переменной x не равно 2');
END IF」-
IF x о 2 THEN
DBMS_OUTPUT.PUT_LINE('значение переменной x не равно 2 );
ELSE "-
DBMS_OUTPUT.PUT_LINE£'значение переменной х разно 2');
END IF】_
Различие между ними связано с тем, что происходит, когда переменная х содержит значение NULL В данном случае первый оператор IF.. .THEN... ELSE выводит строку «значение переменной х не равно 2», а второй — строку оначение переменной х равно 2». Вы спросите, почему? А потому, что когда значением переменной х является NULL, значение проверяемого оператором IF выражения не равно TRUE и управление передается ключевому слову ELSE.
Защититься от случайных ошибок, связанных с обработкой значения NULL, можно либо за счет использования таких функций, как NVL, либо путем явной проверки выражения на равенство значению NULL с помощью предиката IS NULL.
Конструкция IF.. .THEN.. .ELSIF представляет собой один из способов реализации функций оператора CASE. И если вы используете Oracle9i, имеет смысл применить оператор CASE, о котором рассказывается далее в этой главе.
В каждом предложении ELSIF за условием должно следовать ключевое слово THEN, а наличие ELSE не является обязательным. Последнее ключевое слово, если оно вообще используется, ставится в конце конструкции IF.. .THEN.. .ELSIF и означает «если неверно все вышеперечисленное#. То есть когда ни одно из условий не равно TRUE, выполняются операторы, следующие за ELSE. Если же ктпочевое слово ELSE отсугстаует, все исполняемые блоки конструкции IF.. .THEN,, .ELSIF игнорируются.
Теперь приведем полную реализацию логики назначения бонуса, описанной в начале главы, с использованием конструкции IF.. .THEN,. .ELSIF:
IF salary BETWEEN 10000 AND 20000
THEN
glve_bonus Cemployee_i d, 1500):
ELSIF salary BETWEEN 20000 AND 40000
THEN
glve_bonus(employee_1d, 1000);
ELSIF salary > 40000 ~
THEN
give_bonus(employee 1d, 500);
ELSE ""
g1ve_bonus(employee Id, 0);
END IF:-"
Условия в конструкции IF... ELSIF всегда выполняются в порядке их следования в операторе. Если при проверке обоих условий возвращается TRUE, обрабаты- .ваются операторы, соответствующие первому из этих условий. В приведенном примере при окладе 20 000 долларов назначается бонус в 1500 долларов, хотя эгог оклад удовлетворяет и второму условию, согласно которому бонус составляет 1000 долларов (поскольку в диапазон, задаваемый в операторе BETWEEN, входят и границы этого диапазона). Как только в результате проверки одного из условий возвращается значение TRUE, последующие условия не проверяются вообще.
ПРИМЕЧАНИЕ
Оператор CASE, введенный в ОгаиеЭ!, позволяет более эффективно запрограммировать пример с назначением бонуса, в чем вы сможете убедиться несколько позже.
Хотя в конструкции IF.. .THEN.. .ELSIF и разрешается задавать условия, для которых результаты проверки частично пересекаются, лучше этого не делать. В нашем случае ситуация неоднозначна, поскольку в коде недостаточно четко указано, что делать компилятору, когда оклад составляет 20 000 долларов, И если полагать, что мы хотим назначить более высокий бонус низкооплачиваемым сотрудникам (что кажется вполне обоснованным), лучше отказаться от оператора BETWEEN и использовать логику «больше-меньше#:
IF salary >- 10000 AND salary 20000
THEN
gi ve_bonus(employeej d. 1500);
ELSIF salary > 20000 AND salary <- 40000
THEN
g1ve_bonus(employeej d. 1000):
ELSIF salary > 40000 ~
THEN'
glve_bonus(employee_1d. 500):
ELSE ~~
give bonus(employee id, 0);
END IF*一
Отказавшись от использования условий, для которых результаты проверки являются частично пересекающимися, мы тем самым устранили неясность, с которой пришлось бы столкнуться тем программистам, в чьи руки в будущем попала бы эта программа. А еще мы устранили вероятность случайных ошибок, которые могли бы произойти из-за того, что кто-то поменял бы местами предложения ELSIF.
ВНИМАНИЕ Язык PUSQL не требует, чтобы условия выполнения предложений EtSIF были взаимоисключающими. Однако вам следует всегда помнит^ что в том случае, если одно проверяемое значение удовлетворяет двум или более условиям, необходимо быть особенно внимательным и записать предложения EL5IF в правильном порядке.
Вложенные операторы IF
Любые операторы IF можно вкладывать друг в друга. Например, следующая многоуровневая конструкция состоит из нескольких вложенных операторов IF: IF условие^ THEN -
IF условие^
THEN ~
опердторы_2
ELSE -
IF условие_3
THEN -
операторы_3
ELSIF условие^
THEN 一
опердторы_4■
END IF; 一
END IF;
END IF;
Сложную логику часто невозможно реализовать без вложенных операторов IF, но при их использовании следует быть очень внимательным. Как и вложенные циклы, они трудны для понимания и отладки. И если вы соберетесь применить операторы IF более чем на трех уровнях вложения, подумайте, нельзя ли пересмотреть логику программы и реализовать поставленные условия более простым способом. Если эта задача окажется очень сложной, подумайте о созд^ии одного или нескольких локальных модулей, скрывающих внутренние операторы IF.
Главным преимуществом вложенных структур IF считается то, что они позволяют отложить проверку внутренних условий. Условие внутреннего оператора IF проверяется только в том случае, если значение выражения во внешнем условии оказывается равным TRUE. Таким образом, очевидной целью вкладывания операторов IF является проверка одного условия только в том случае, если истинно Другое. Например, наш код для вычисления бонуса можно было бы дополнить таким фрагментом:
IF award_bonus(employee_1d) THEN
IF рг 1 nt__check(employeeJd) THEN
DBMS_DUTPJT.PUT_LINE('4eK аыдан для' || employeejd);
END IF「
END IF:
Предположим, мы хотим выводить сообщение для каждого напечатанного чека на бонус, но при этом чек для нулевого значения, соответствующего не назначенному бонусу, не должен печататься.
Вложенный оператор IF может понадобиться в ситуации, когда проверка условия является очень дорогостоящей (требуются дополнительные ресурсы памяти и время на его обработку процессором). Фрагмент кода, осуществляющий проверку, можно поместить во внутренний оператор IF — в таком случае данная операция действительно будет выполняться лишь при необходимости. Особенно это важно для кода, встречающегося очень часто или располагающегося в тех областях программы, для которых важна скорость получения ответа, Эту концепцию иллюстрирует следующий оператор IF:
IF условие J AND условие 2
THEN 一*
END IF:
Для того чтобы определить, истинны ли оба условия оператора IF, компилятор PL/SQL проверяет их по очереди. Предположим, что условие^ проверяется достаточно быстро: total_sales > 100000
Однако усло8ие_1 более сложное, и для его проверки процессору требуется много времени, так как в нем, возможно, вызывается хранимая функция, выполняющая запрос к базе данных. Если уьов"с_2 проверяется за десятую долю секунды и результатом проверки оказывается значение FALSE, а условие」проверяется в течение 3 с, а результатом является значение TRUE, тогда на выяснение того факта, что код в теле оператора IF выполнять не нужно, уходит более 3 с.
Предположим теперь, что тот же оператор IF переписан следующим образом:
IF условие 2
THEN ,-
IF условие^
THEN -
END IF:
END IF;
Теперь условие J. проверяется только в том случае, если результатом проверки условия_2 является значение TRUE. И в тех случаях, когда истинно условие to- tal_sales <= 100000, пользователю не приходится ждать 3 с, пока программа продолжит свою работу.
КАК ИЗБЕЖАТЬ СИНТАКСИЧЕСКИХ ОШИБОК
Приведем несколько советов, касающихся применения оператора IF.
口 Не забывайте использовать ключевые слова END IF, Ими обязатель-но должны завершаться все три разновидности данного оператора.
продолжение^
Не забывайте ставить пробел между ключевыми словами END и IF. Если вместо END IF ввести ENDIF, компилятором будет выведено сообщение об ошибке:
ORA-06550: line 14, col inn 4:
PLS-00103: Encountered the symbol ";" when expecting one of the following:
Ключевое слово ELSIF не должно содержать букву Е. Если вместо ключевого слова ELSIF указать ELSEIF, компилятор не воспримет последнее как часть оператора IF. Он интерпретирует его как имя переменной или процедуры. ,
Точку с запятой следует ставить только после ключевых слов END IF. После ключевых слов THEN, ELSE и ELSEIF, которые не являются отдельными исполняемыми операторами и, в отличие от END IF, не могут завершать оператор PL/SQL, указанный символ никогда не вводится. Но если вы все же поставите точку с запятой после одного из перечисленных ключевых слов, компилятор выдаст сообщение об ошибке, указывающее, что он ожидал увидеть перед точкой с запятой какой- нибудь оператор.
Оператор CASE
Операторы CASE, впервые появившиеся в Oracle9z, позволяют выбрать для выполнения одну из нескольких последовательностей исполняемых операторов. Сама по себе эта конструкция не нова, она давно и успешно используется в других языках программирования. Более того, в 1992 году оператор CASE был описан в стандарте языка SQL, хотя Oracle SQL не поддерживал его вплоть до версии Огас1е8г, а PL/SQL - до версии Oracle9i,
Начиная с Oracle9i поддерживаются два типа операторов CASE:
О простой оператор CASE — связывает одну или несколько последовательностей операторов PL/SQL с соответствующим значением (последовательность для выполнения выбирается с учетом результатов вычисления выражения, возвращающего указанное значение);
О поисковый оператор CASE — выбирает для выполнения одну из последовательностей операторов в зависимости от результатов вычисления списка логических условий (выполняется последовательность операторов, связанная с первым условием, результат проверки которого оказался равным TRUE).
В дополнение к операторам CASE PL/SQL поддерживает CASE-выражения, Выражение CASE очень похоже на оператор CASE, оно позволяет выбрать для вычисления одно или несколько выражений, В итоге получается одно значение, тогда как результатом работы оператора CASE является выполнение последовательности операторов PL/SQL.
Простые операторы CASE
Простой оператор CASE позволяет выбрать для выполнения одну из нескольких последовательностей операторов PL/SQL в зависимости от результата вычисления одного выражения. Он имеет следующую форму записи:
CASE выражение
WHEN результатJ THEN операторы_1 WHEN результат J THEN операторы_2
ELSE
операторы else
END CASE:"
Ветвь ELSE здесь не обязательна. При выполнении такого оператора PL/SQL сначала вычисляет выражение. Затем результат сравнивается со значением ре- зультат_к Если они совпадают, то выполняются операторы^, в противном случае проверяется значение результату и т, д.
Приведем пример простого оператора CASE, в котором в качестве основы для выбора подходящего алгоритма вычисления бонуса используется значение переменной emp!oyee_type:■
CASE employee type WHEN 'S' THEN" award__salary_bonus(effiployee_id); WHEN TH' THEN-
award hQurly_bonus(employee_id); WHEN TC THEN"
award^commlss1oned_bonus(employee Id);
ELSE"-
RAISE invalid employee_type:
END CASE; ~~
В этом операторе CASE имеется явно заданное ключевое слово ELS^. Однако в общем случае оно не является обязательным. Если его нет, компилятор PL/SQL неявно выполняет такой код:
ELSE
RAISE CASE_NOT_FOUHD;
Иными словами, если не задать ключевое слово ELSE и если никакой из результатов в предложениях WHEN не соответствует результату выражения в операторе CASE, PL/SQL инициирует исключение CASE_NOT_FOUND. В этом и заключается отличие данного оператора от предыдущего. Когда в операторе IF отсутствует клю・ ненов слово ELSE, то при невыполнении условия ничего не происходит, тогда как в операторе CASE аналогичная ситуация приводит к ошибке.
Теперь вы, надо полагать, хотите узнать, как с помощью оператора CASE реализовать описанную в начале главы логику назначения бонуса. На первый взгляд эт0 кажется невозможным. Однако подойдя к делу творчески, можно написать вот такой код:..
CASE TRUE
WHEN salary a 10000 AND salary <= 20000 THEN
Si ve_bonus(employeeJ d. 1500):
WHEN salary > 20000 AND salary е 40000 THEN
glve_bonus(employee_i d, 1000);
WHEN~salary > 40000 THEN
give_bonus(employee_id. 500):
ELSE"~
glve_bonus(employeeJd. 0):
END CASE;-
На примере этого решения покажем (помимо того, что программирование иногда требует творческого подхода), что элементы выражение и результат могут быть либо скалярными значениями, либо выражениями, результатами которых являются скалярные значения.
Вернувшись к оператору IF.. .THEN.. .ELSIF, реализующему ту же логику, вы увидите, что в оператор CASE мы ввели ключевое слово ELSE)тогда как в операторе IF.. .THEN.. .ELSIF его не было. Причина добавления ELSE проста; если ни одно из условий назначения бонуса не выполняется, оператор IF просто ничего не делает и бонус получается нулевым. Оператор же CASE в этом случае генерирует ошибку, и поэтому ситуацию, когда бонус является нулевым, приходится кодировать явно.
Возможно, использование приведенного выше оператора CASE TRUE кажется вам замечательным нестандартным решением, но на самом деле он всего лишь явно реализует поисковый оператор CASE, о котором мы поговорим в следующем разделе.
Поисковый оператор CASE
Как Bbij возможно, догадываетесь, поисковый оператор CASE (searched CASE) проверяет набор логических выражений % обнаружив выражение, равное TRUE, выполняет последовательность связанных с ним операторов. По сути дела, это эквивалент оператора CASE TRUE, пример которого приведен в предыдущем разделе. Поисковый оператор CASE имеет следующую форму записи:
CASE.'
WHEN выражение^ THEN операторы_1.
WHEN выражентсу THEN операторы^
ELSE
операropw_e7se
END CASE;"
Он прекрасно подходит для реализации логики назначения бонуса:
CASE
WHEN salary >= 10000 ANO salary <= 20000 THEN
give^onusCemployeeJd, 1500);.
WHEN salary > 20000 AND salary c 40000 THEN
give_bonus(emp1oyee_1d, 1000);
WHEN~sslary > 40D0D-THEN
gi ve^bonus(employee_1 d. 500):
ELSE"~
g1ve_bonus(employee_id, 0);
ENO CASE;-
Поисковый оператор CASE, равно как и простой оператор с этим же именем, действует в соответствии со следующими правилами.
О Работа оператора заканчивается сразу же после выполнения последовательности исполняемых операторов, связанных с истинным выражением. Если истинными оказываются несколько выражений, выполняются операторы, связанные с первым из них.
О Ключевое слово ELSE не обязательно. Если оно не задано и ни одно из выражений не оказалось равным TRUE, инициируется исключение CASE_NOT_FOUND.
О Условия WHEN проверяются по порядку, сверху вниз.
Рассмотрим еще одну реализацию логики назначения бонуса, в которой используется то обстоятельство, что условия WHEN проверяются в порядке их записи. Отдельные выражения проще, но этот оператор не так нагляден, как предыдущий:
CASE
WHEN salary > 40000 THEN
give_bonus(enployeejd. 500);
WHEN~salary > 20000~THEN
give_bonus(enployee_id, 1000):
WHEN~salary x 100而 THEN
gi ve_bonus (enpl oyeej d, 1500);
ELSE"~
give_bonus(employeejd. 0);
END CASE;~
Если оклад некоего сотрудника равен 20 000 долларов, результатом проверки первых двух условий будет FALSE, а третьего условия — TRUE, и сотрудник получит бонус в 1500 долларов. Если же его оклад равен 21 000 долларов, результат второго условия будет вычислен как TRUE и сотрудник получит бонус в 1000 долларов. Вьшолнение оператора CASE будет завершено на второй ветви WHEN, а третье условие даже не будет проверяться.
Стоит ли использовать такой подход при написании операторов CASE — вопрос спорный. Как бы то ни было, имейте в виду, что вам могут встречаться такие операторы, в которых результат зависит от порядка следования выражений, и при их отладке и модификации требуется особая внимательность.
Логика, зависящая от порядка следования однородных ветвей WHEN в операторе, является потенциальным источником ошибок, возникающих при попытке поменять их местами. В качестве примера рассмотрим следующий оператор CASE, в котором при сумме оклада 20 000 долларов результат проверки условий в обеих ветвях WHEN оказывается равным TRUE:
CASE
WHEN salary BETWEEN 10000 AND 20000 THEN gi ve_bonus (enpl oyeej d, 1500);
WHEN salary BETWEEN' 20000 AND salary 40000 THEN -
g1ve_bonus(employee_1d. 1000);■
Подумайте, что произойдет, если программист, которому поручено сопровождать эту программу, решит переставить ветви WHEN, чтобы упорядочить их по убыванию окладов. Не стоит полагать, что это маловероятно. Программисты часто
склонны к совершенствованию уже работающего кода в соответствии с собственным представлением о порядке. Вот как может быть переписан приведенный выше код:
CASE
WHEN salary BETWEEN 20000 AND salary 40000 THEN
give_bonus(etnplo,yee_1d. 1000):
WHEN~salary BETWEEN~10000 AND 20000 THEN
gl ve_bonus (empl oyeed d. 1500):
На первый взгляд все верно, не так ли? Отнюдь, поскольку из-за пересечения двух ветвей WHEN на значении 20 000 получается небольшая ошибка в программной логике. Теперь сотрудник с таким окладом вместо бонуса в 1500 долларов получит только 1000.
Вам понятно, почему следует избегать пересечения условий в ветвях WHEN и почему лучще сдерживать желание усовершенствовать уже работающий код. Если же вам придется вносить изменения в чужой про1рам&шый код, всегда помните) что порядок логических выражений может иметь значение.
ВНИМАНИЕ
Поскольку условия в ветвях WHEN проверяются па порядку; можно немного повысить эффективность кода, поместив ветви с наиболее вероятными условиями в начало списка. Кроме того, если у вас есть ветвь WHEN с «дорогостоящими» выражениями (например, требующими много времени на обработку процесхором или больших ресурсов памяти), их можно поместить в конец, минимизировав таким образом вероятность их проверки.
Поисковые операторы CASE используются в тех случая% когда для поиска подлежащего выполнению набора операторов применяется набор логических выражений. А простой оператор CASE имеет смысл использовать тогда, когда решение принимается на основании результата одного выражения.
Вложенные операторы CASE
Подобно операторам IF, операторы CASE могут быть вложенными. Например вложенный оператор CASE включает следующая не слишком наглядная реализация логики назначения бонуса:
CASE
WHEN salary >- 10000 THEN
CASE'
WHEN salary <- 20000 THEN
gi ve_bonus(employeej d. 1500):.
WHEN salary > 20000 THEN
gi ve_bonus(employee_i d. 1000):
END CASE;一
WHEN salary > 40000 THEN
g1ve_bonus(e(nployee_id. 5Q0):
WHEN salary < 10000 THEN
give_bonus(employeejd. 0):
END CASE:~
Внутри оператора CASE могут использоваться любые операторы, так что внутренний оператор CASE нетрудно заменить оператором IF. Точно так же любой оператор, включая CASE, может быть вложен в оператор IF.
Выражения CASE
Выражения CASE выполняют ту же задачу, что и операторы CASE, но только не для исполняемых операторов, а для выражений. Простое выражение CASE позволяет выбрать для вычисления одно из нескольких выражений на основе заданного скалярного значения. Поисковое выражение CASE последовательно вычисляет выражения из заданного списка, пока одно из них не окажется равным TRUE, а затем возвращает результат связанного с ним выражения.
Приведем синтаксис этих двух видов выражения CASE:
Простое^вырджение_Сд5е :-
QkSE~выражение
WHEN результат^ THEN
результирующее выраление 1
WHEN результатJ THEN -
резуль гаду碌8.8叩@"0"%6_2
"else
результирухицее выражение else
END;~
Поисковое_выраженне Case :-
CASE "-
WHEN выражение^ THEN
резупь тируюй^ее_выраженте_1
WHEN анрамен林ej THEN
резуль тирую^е_вырал(ениеJ
ELSE
результяруюи^ее выражение else
END;~'
Выражение CASE возвращает одно значение - результат выбранного для вычисления выражения. Каждой ветви WHEN должно быть поставлено в соответствие одно результирующее выражение (но не оператор). В конце выражения CASE не нужно ставить ни точку с запятой, ни ключевые слова END CASE. Выражение CASE оканчивается единственным оператором END.
Ниже дан пример простого выражения CASE, используемого совместно с процедурой PUT_LINE пакета D0MS_OUTPUT для вывода на экран значения логической переменной. В этом примере выражение CASE преобразует логическое значение в символьную строку, которая затем выводится с помощью процедуры PUT_LINE: declare
boolean_true BOOLEAN 丁 TRUE;
boolean^false BOOLEAN :- FALSE:
boolean_null BOOLEAN;
FUNCTION boolean_to_varchar2 (flag IN BOOLEAN) RETURN VARCHAR2 IS
BEGIN
RETURN
CASE flag
WHEN TRUE THEN 'True'
WHEN FALSE THEN 'False' ELSE 'NULL' END;
END:
BEGIN
DBMS_0UTPUT.PUT_LINE(boolean_to_varchar2(boolean_true)):
DBMS20UTPUT.PUT2LINECboolean_to_varchar2(boolean2false)):
DBMS20UTPUT.PUT2LINE(boolean_to_varchar2(boo1ean2null)):
ENO: 一 ~-
Для реализации логики назначения бонуса можно использовать поисковое выражение CASE, возвращающее значение бонуса для заданного оклада;
DECLARE
salary NUMBER :- 20000;
employee_id NUMBER :- 36325;
PROCEDURE give_bonus Cempjd IN NUMBER, bonus_anrt IN NUMBER) IS
BEGIN ~~一
DBMS_OUTPUT.PUT_LINE(emp_1d):
DBMS OUTPUT.PUT LINECbonus amt):
END;"
BEGIN
give_bonus(emplDyee_1d..■
' CASE ~■
WHEN salary >- 10000 AND salary <=20000 THEN 1500
WHEN salary > 20000 AND salary <- 40000 THEN 1000
WHEN salary > 40000 THEN 500
ELSE 0 END); END:
Выражение CASE можно применить везде, где допускается использование выражений любого другого типа. В следующем примере CASE-выражение используется для вычисления бонуса, умножения его на 10 и присвоения результата переменной, значение которой выводится на экран с помощью функции PUT_LINE пакета DBMS_OUTPU:-
DECLARE
salary NUMBER := 20000;
employeejd NUMBER := 36325;.
borrjs_amount NUMBER;
BEGIN —
bonus_amount := CASE■.
WHEN salary x 10000 AND salary <-20000 THEN 1500 WHEN salary > 20000 AND salary ” 40000 THEN 1000 WHEN salary > 40000 THEN 500 ELSE 0 END * 10;
OBMS_OUTPUT.PUT_LINE(bDnus_amount);
END: --
В отличие от оператора CASE, в том случае, если ни одна из ветвей WHEN не выбрана, выражение CASE не генерирует ошибку, а просто возвращает NULL.
Оператор GOTO
В языке PL/SQL имеется несколько специальных операторов, управляющих последовательностью выполнения основных операторов программы. В частности, операторы IF и CASE выбирают выполняемую часть кода на основе проверки заданных условий; оператор LOOP (описанный в главе 5) используется для многократного выполнения фрагмента кода. В дополнение к этим строго структурированным методам управления последовательностью выполнения операторов программы PL/SQL предлагает программистам оператор GOTO. Этот оператор выполняет безусловный переход к другому исполняемому оператору в том же исполняемом разделе блока PL/SQL. Как и другие конструкции языка, он требует внимания и продуманного подхода.
Общий формат оператора GOTO таков:
GOTO ш^нетки:
Здесь имя_метки — это имя метки, идентифицирующей целевой оператор перехода. В программе данная метка выглядит так:
«ш метки»
ВЫРАЖЕНИЯ CASE В SQL-ИНСТРУКЦИЯХ
Еще до того как выражения и операторы CASE появились в PL/SQL, они стали использоваться в S QL-инструкциях в виде поисковых выражений CASE, поддержка которых была введена в Oracle8z. К сожалению, и к большому недовольству программистов, в SQL-инструкциях, вызываемых из кода PL/SQL, CASE-выражения использовать не разрешалось. Например, в SQL*Plus можно было выполнить такую SQL-инструкцию:
SELECT
CASE■■
WHEN DUMMY-'X' THEN 'Dual Is OK'
ELSE
'Dual Is missed up'
'END
FROM DUAL:
Однако следующий код PL/SQL не работал: '
DECLARE
dualjnessage VARCHAR2(20);
BEGIN -
SELECT■
CASE
WHEN DUMMY-'X' THEN 'Dual is OK'
ELSE 'Dual is missed up'
END INTO dual_message
FROM DUAL: -
DBMS OUTPUT.PUT LINECdual message):
END; ~~~■
В Oracle9i такая ситуация больше не возникает. Теперь поддерживаются поисковые и простые выражения CASE и их можно использовать в SQL, в PL/SQL, а также в SQL-инструкциях, вызываемых из PL/SQL.
Имя метки должно быть заключено в двойные угловые скобки (« »). Кодда компилятор PL/SQL встречает оператор GOTO, он немедленно передает управление первому исполняемому оператору, следующему за указанной меткой. Приведем полный блок кода, содержащий и метку, и оператор GOTO:
BEGIN
GOTO second_output:
DBMS_OUTPUT.PUT^LINE('Эта строка никогда не будет выполнена');
<<second_output>>
DBMS OUTPUT.PUT LINE《'Mm здесь!');
END:『■'
Несмотря на стойкое предубеждение против использования оператора GOTO, иногда он может быть очень полезен. Например, в некоторых случаях он способен упростить логику программы. С другой стороны, поскольку PL/SQL включает так много разнообразных управляющих конструкций и средств обеспечения модульности программы, практически всегда найдется более удобный способ решения задачи, чем использование оператора GOTO.
Ограничения на использование
На использование оператора GOTO налагается несколько ограничений, которые подробно описываются в следующих разделах.
□ За меткой должен следовать хотя бы один исполняемый оператор.
□ Целевая метка должна находиться в х^еделах области действия оператора GOTO.
□ Целевая метка должна находиться в той же части блока PL/SQL, что и оператор GOTO.
За меткой должен следовать хотя бы один исполняемый оператор
Метка сама по себе не является исполняемым оператором (обратите внимание, что за угловыми скобками не ставится точка с запятой) и поэтому не может его заменять, В следующем фрагменте кода все ссылки на метку «all_done» недопустимы, поскольку за ней не следует ни один исполняемый оператор:
IF statusjnout - 'COMPLETED'
THEN "
«аП_бопе» /* Недопустимо! */
ELSE "
schedule_act1v1ty:
END IF;-
DECLARE
CURSOR company_cur IS ...;
BEGIN"
FOR company_rec IN company cur
LOOP ""
叩plyjonuses Ccompany_rec.companyjd):
«alTdone» /* Недопустимо! */ -
END LOOP?
END:
FUNCTION new_farniiila (molecule_1n IN NUMBER) RETURN VARCHAR2
IS"-
BEGIN
запрограммируйте соответствующую формулу ...
''RETURN forniula_str1ng:
«all_done>> /* Недопустимо! */
END:
Такие ключевые слова, как END, THEN, ELSE)ELSIF, END LOOP, не являются исполняемыми операторами, и непосредственно перед ними метка ставиться не может.
Целевая-метка должна находиться в пределах области действия оператора GOTO
Область действия оператора GOTO определяется той из перечисленных ниже конструкций, в которой он находится:
О функция;
О процедура;
О анонимный блок;
О оператор IF;
О оператор LOOP;
О обработчик исключеноя;
О оператор CASE.
Каждый из приведенных далее примеров программного кода содержит ошибку, которую можно отнести к числу наиболее распространенных. Как видите, в каждом случае делается попытка перехода к метке, которая лежит вне области действия оператора GOTO, в результате чего генерируется ошибка PL/SQL:
PLS-00375: Illegal GOTO statement; this GOTO cannot branch to label
О Условие IF. Единственный способ войти в оператор IF — обеспечить выполнение его условия (когда результат проверки условия равен TRUE). Поэтому после обработки следующего кода будет сгенерировано сообщение об ошибке;
GOTO label Inside If:
IF status - 'NEW'-
THEN
<<13beljns1de_lf» /* Вне области цействия! */ show new_one:
END IF「-
Точно так же нельзя вьшолнить переход в оператор CASE.
Q Операторы BEGIN. Единственный способ войти во вложенный блок - пройти через его оператор BEGIN, Синтаксис PL/SQL предусматривает соблюдение строгой последовательности входа и выхода. Поэтому после выполнения следующего кода также будет сгенерировано сообщение об ошибке:
GOTO label Inside subblock;
BEGIN_"
«1 abel_1 ns 1 de_subblock» /* Выход за границы блока! */
NULL:-"
END;
О Область действия оператора IF. В операторе IF область действия операторов ограничивается соответствующим блоком, начинающимся с ключевого слова WHEN. Оператор GOTO не может передавать управление из одного блока в другой. Поэтому следующий код некорректен:
GOTO label_inside_1f: IF status - 'NEW' THEN ' «new_status» GOTO old_status: /* Выход за границы блока! */ ELSIF status - 'OLD' THEN «old_status»
GOTO new_status: /* Выход з己 границы блока' */
END IF; -
Аналогичным образом, невозможно перейти из одной ветви оператора CASE в другую. Ниже приведены рекомендации относительно того, как можно избежать таких ошибок.
О Не производите переход в тело цикла. С помощью оператора GOTO нельзя перейти в тело цикла. Поэтому следующий код ошибочен:
FOR month_nuiB IN 1 .. 12
LOOP -
«do_a_month»
scheduTe_act1vity(month_num);
END LOOP; -~
GOTO da_a_month; /* Невозможно снова войти в цикл.*/
О Не переходите в локальный модуль. Не разрешается переход с помощью оператора GOTO из главного блока к метке в функции, процедуре или в другом модуле. Поэтому приведенный ниже код сгенерирует ошибку:
DECLARE
FUNCTION local_null IS BEGIN «decrip case statement» NULL: ~"
END;
BEGIN
GOTO decrip case statement: /* Метка здесь невидима. */
END:~ 一
Целевая метка должна находиться в той же части блока PL/SQL, что и оператор GOTO
Оператор GOTO из исполняемого раздела не может выполнить переход в раздел исключений, а оператор GOTO из раздела исключений не может выполнить переход в исполняемый раздел. Оператор GOTO в разделе исключений должен ссылаться на метку, расположенную в этом же разделе исключений. Следующий код генерирует ту же ошибку PL/SQL (PLS-00375), что и примеры кода из предыдущего раздела:
BEGIN
/*
|| Метка и оператор GDTO должны находиться в одном разделе!
*/
GOTO out of here;
EXCEPTION
WHEN OTHERS
THEN
«out_of_here>> /* Вне области видимости! */
NULL -
END;
Оператор NULL
Каждый оператор программы, как правило, выполняет определенное действие. Однако бывают случаи, когда нужно просто указать компилятору PL/SQL, чтобы он ничего не делал, и здесь на помощь приходит оператор NULL. Он имеет следующий формат записи:
NULL;
Вы ведь не ожидали, что оператор, который <ничего не делает», будет иметь сложную структуру? Имя оператора NULL является зарезервированным словом PL/SQL, за которым следует точка с запятой (;), указывающая, что это оператор, а не значение NULL. Единственное выполняемое оператором NULL действие — передача управления очередному исполняемому оператору. А о том, для чего может понадобиться такой оператор, вы узнаете из следующих разделов.
Повышение читабельности программы
Случается, что в определенной логической ситуации программа вообще не производит никаких действий. В большинстве таких случаев PL/SQL позволяет вообще не использовать операторы, тем не менее программа выполняется правильно. Однако у человека, который читает такую программу, происходящее может вызвать определенные сомнения, поэтому ему лучше объяснить, что все идет по плану.
Например, в операторе IF не обязательно должно присутствовать ключевое слово ELSE. В частности, оно отсутствует в следующем коде, который генерирует указанный пользователем отчет:
IF :report.select!on = 'DETAIL'
THEN
exec detail report:
ENO IF] ~
Но что должна делать программа, если указан другой отчет, а не DETAIL? Можно предположить, что ничего. Но поскольку в коде это явно не задано, кто-то может подумать, что вы просто забыли запрограммировать соответствующее действе. Если же внести в эту программу ключевое слово ELSE, которое не должно выполнять какие-либо функции, это будет означать: «Не волнуйтесь, я действительно хочу, чтобы в этом месте не производились никакие операции»:
IF :report.select!on = 'DETAIL'
THEN
exec detail report;
ELSE ~ 一
NULL; -- Ничего не делать
END IF;
Обработка исключений с помощью оператора NULL
В программе может присутствовать необязательный раздел исключений, содержащий один или несколько обработчиков исключений. Эти обработчики обрабатывают ошибки, генерируемые при выполнении программы. Структура раздела исключений и последовательность его обработки очень похожи на структуру и последовательность обработки условного оператора CASE:
EXCEPTION
WHEN ш исключения 1
THEN -"
исполняемыеуператоры_1'
WHEN исключения N
THEN ""
ислолнлеиые операторы^
WHEN OTHERS ~-
THEN
исполняемые операторы others
END;''
Если имя генерируемой ошибки соответствует исключению имя^исключения^Ц выполняются исполняемые_рпера торы_1; если оно соответствует исключению имя_ис- ключенияJJt выполняются исполняемые_операторы_^ и т, д. Предложение WHEN OTHERS служит для обработки исключений, не обработанных операторами предшествующих предложений WHEN (подобно ключевому слову ELSE оператора IF). Если вы решили не писать программный код для обработки определенного исключения, но при этом не хотите, чтобы данное исключение считалось необработанным, можете поступить так: включить в блок обработки исключений имя исключения (или предложение WHEN OTHERS), но вместо соответствующих исполняемых операторов поместить единственный оператор NULL. Приведем пример такой программы:
PROCEDURE сз1с avg sales
BEGIN ~ -
:sales.avg :■ :sales.monthl / :sales.total;
EXCEPTION ''
WHEN ZERO_DIVIDE
THEN ~
;sales.avg :- 0:
RAISE FORMJRIGGER FAILURE:
WHEN OTHERS"
THEN
NULL:
END;
При условии, что общее количество продаж (sales.total) равно нулю, среднее количество продаж (sales.avg) также устанавливается равным нулю и выполнение триггера в Oracle Forms прекращается. Если же происходит любое другое исключение (например, исключение VALUE_ERROR, инициируемое в том случае, когда полученное в результате вычислений значение оказалось больше, чем может вместить элемент sales.avg), то управление передается предложению WHEN OTHERS, не вьшолняющему никаких функций (то есть содержащему оператор NULL), и работа
продолжается. Поскольку исключение обработано, PL/SQL ие инициирует его снова. Более подробно данный вопрос рассматривается в главе 6.
Использование оператора NULL после метки
В некоторых случаях переход к оператору NULL позволяет избежать выполнения дополнительных операторов. Большинство программистов стараются не польз。- ваться оператором GOTO, но бывают ситуации» когда без него трудно обойтись. Од- нако помните, что за меткой, к которой осуществляется переход, должен стоять хотя бы один исполняемый оператор. В следующем примере оператор GOTO при- меняется для быстрого перехода в конец программы в том случае, если данные указывают, что дальнейшая обработка не требуется:
PROCEDURE process_data (datajn IN ordersXROWTYPE, ~ data~actlon IN VARCHAR2) IS
BEGIN"
-Первая пров叩ка
IF data In.ship date IS NOT NULL
THEN —~
status ;-valldatejhlpdate (data_1n.shlp_cl8te);
IF status I- 0 THEN GOTO end_ofj)rocedure7
END IF;"
Вторая проверка
IF data 1n.order date IS NOT NULL
THEN "-
status ;- valldate^orderdate (datajn.order^date);
IF status !■ 0 THEN GOTO end ofjsrocedure;"
END IF:"
Дополнительные проверки ...
« end ofjirocedure »
NULL;"
END:
В случае обнаружения ошибки в одном из разделов остальные операторы, осуществляющие проверку, отменяются с помощью оператора'GOTO. Поскольку в конце процедуры делать ничего не нужно, но там должен находиться хотя бы одни исполняемый оператор, помещаем после метки NULL. И несмотря на то что с последним не связано никакое действие, он все же является исполняемым оператором.
5
Циклы
А Основы циклов
А Простой цикл
А Цикл WHILE
А Использование счетчика в цикле FOR
А Использование курсора в цикле FOR
А Метки циклов
А Полезные советы
В настоящей главе рассматриваются управляющие структуры PL/SQL, которые называются циклами. Они предназначены для многократного выполнения программного кода. PL/SQL поддерживает циклы трех видов: простой (он же бесконечный), FOR и WHILE.
В ходе изложения материала вы познакомитесь с правилами использования циклов и особенностями их построения. Из представленных ниже таблиц узнаете, как завершается цикл, когда проверяется условие его завершения и в каких случаях циклы какого вида следует применять.
Основы циклов
Возможность использования циклов разных видов позволяет выбрать оптимальный способ решения каждой конкретной задачи. В большинстве случаев решить задачу можно с помощью любого цикла, но если воспользоваться самым подходящим из них, код будет более коротким и простым.
Примеры разных циклов
Чтобы вы сразу получили некоторое представление о каждом из циклов и о том, как они работают, рассмотрим три процедуры. Каждая из них содержит цикл, в теле которого выполняется одна и та же строка кода (процедура set_rank, устанавливающая категорию с заданным номером): set^rank (ranking level):
О Простой цикл. Процедура принимает в качестве аргумента максимальный номер категории и устанавливает категории, начиная с первого номера и заканчивая самым большим. Заметьте, что от бесконечного вьшолнения цикла нас предохраняет оператор EXIT WHEN, обеспечивающий его завершение:
PROCEDURE set_all_ranks Cmax_rankjn IN INTEGER)
is --
rank1ng_level NUMBER C3) :- 1:
BEGIN "
LOOP
EXIT WHEN ranklngjevel > max_rank_in;
set_rank Cranklngjevel):
rankingjevel :- ranklngjevel + 1:
END LOOP;--
END set_all_ranks;
q Цикл FOR. В этом случае устанавливается категория из фиксированного диапазона, от 1 до максимального значения:
PROCEDURE set__all_ranks (max rankjn IN INTEGER) is -~
ranklngjevel NUMBER (3) := 1
BEGIN ~
FOR ranking level 1 . max rank in
LOOP 一~
set_rank (ranklngjevel);
END LOOP:
END set_all_ranks;
Q Цикл WHILE. Процедура принимает в качестве аргумента максимальный номер категории и устанавливает категории, начиная с первого номера и заканчивая максимальным значением. Обратите внимание на то, что условие завершения цикла задано в одной строке с ключевым словом WHILE:
PROCEDURE set_all_ranks (max_rankjn IN INTEGER)
IS-一 一
rankingjevel NUMBER(3) := 1;
BEGIN ~
WHILE ranlclng level max rank in
LOOP —~
set_rank Crankingjevel):
rankingjevel :- rankingjevel + 1;
END LOOP: ~~
END set_an_ranks:
В приведенном выше примере самым маленьким получился цикл FOR. Однако его можно использовать только потому, что заранее известно, сколько раз (тах_ rank_in) должно выполняться тело цикла. Во многих других случаях количество повторений может быть различным, и поэтому для них цикл FOR не подходит.
Структура циклов PL/SQL
Цикл определяется начинающими его зарезервированными словами, условием завершения и оператором END LOOP, отмечающим конец цикла в программе. Тело цикла — это последовательность исполняемых операторов, которая выполняется на каждой итерации цикла (рис. 5.1).
Условие завершенияУсловие завершенияТало циклеТало циклеWHILE |need rnore data
Условие завершения
Условие завершения
Тало цикле
Тало цикле
LOOP
read_next_record (record_1d); need_more_data :-'
record_1d IS NOT NULL;
END LOOP;
Рис. 5.1. Цикл WHILE и его тело
В общем случае цикл можно представить себе как процедуру или функцию. Его тело — ^черный ящик*, а условие завершения — это интерфейс такого ящика. Код, находящийся вне цикла, ничего не должен знать о его внутренней работе. Именно так можно рассматривать примеры разных циклов, которые приведены в этой главе.
Помимо тех, что содержатся в настоящей главе, можно просмотреть несколько более подробных примеров использования циклов в Oracle Forms. Вы найдете их на узле O'Reilly в файлах hlghrec.doc и hJghrec.fp (демонстрируют принцип выбора элементов в Oracle Fonns), а также в файлах ofquery.doc, postqry.fp и preqry.fp (описаны методы автоматической обработки до и после запроса в Oracle Forms).
Простой цикл
Структура простого цикла лежит в основе всех остальных циклических конструкций. Такой цикл состоит из ключевого слова LOOP, исполняемого кода (тела цикла) н ключевых слов END LOOP:
LOOP
исполняемые_операторы
END LOOP: -
Цикл начинается оператором LOOP, а заканчивается оператором END LOOP. Тело Цикла должно содержать как минимум один исполняемый оператор. Свойства простого цикла описаны в следующей таблице.
Свойство
Описание
Условие завершения цикла
Когда проверяется условие завершения цикла
В каких случаях используется данный цикл
Если в теле цикла выполняется оператор EXIT. В протишом случае он работает бесконечно
При выполнении оператора ВОТ или EXIT WHEN. Таким образом, тело цикла (или его часть) всегда выполняется как минимум один раз
Во-первых, если не известно, сколько раз будет выполняться тело цикла, а во-вторых, когда необходимо, чтобы тело цикла было выполнено хотя бы один раз
Данный цикл удобно использовать в ситуации, когда нужно гарантировать хотя бы однократное выполнение тела цикла (или его части).
Простой цикл завершается при выполнении в теле цикла оператора EXIT или его близкого родственника — оператора EXIT WHEN, а также в том случае, если в теле цикла инициируется исключение (которое остается необработанным).
Завершение простого цикла: операторы EXIT и EXIT WHEN
При использовании простых циклов нужна предельная внимательность. Убедитесь, что в коде задано условие, при котором выполнение цикла останавливается. Для этого в его теле должен содержаться оператор EXIT или EXIT WHEN. Синтаксис данных операторов таков:
EXIT
EXIT WHEN условие\
Здесь условие — это логическое выражение. Приведенный ниже пример показывает, как оператор EXIT останавливает выполнение цикла и передает управление оператору, следующему за ключевыми словами END LOOP. Функция account_balance возвращает остаток денег на счету с идентификатором accounted. Если на этом счету осталось меньше 1000 долларов, выполняется оператор EXIT и цикл завершается. В противном случае программа снимает с банковского счета клиента сумму, необходимую для оплаты заказов:
LOOP
balance_rema1ning :- account_balance (accounted):
IF balance remaining < 1000 --
THEN 宀
EXIT;.
ELSE
apply_balance (accounted, balance_rema1n1ng):
END IF: --~
END LOOP..
Следует отметить, что оператор EXIT можно использовать только в цикле LOOP.
PL/SQL для выхода из цикла предлагает еще один оператор, EXIT WHEN, предназначенный для завершения цикла при наличии определенного условия. Он сочетает в себе функции операторов IF.. .THEN и EXIT. Приведенный выше цикл оператор EXIT WHEN изменяет следующим образом:
LOOP
/* Вычисление баланса */
balance_rema1n1ng :- account_balance (accountjd):
/* Встраиваем условие в оператор EXIT */
EXIT WHEN balance_remaining < 1000:
/* Если цикл все еще выполняется, баланс уменьшается */ apply_balance (accountjd, balance_refnaining);
END LOOP;--
Как видите, для проверки условия завершения данного цикла оператор IF не Нужен. Его задачу выполняет оператор EXIT WHEN.
Так в каких же случаях следует использовать оператор EXIT WHEN, а в каких - просто EXIT?
О Оператор EXIT WHEN больше подходит для ситуаций, когда условие завершения цикла определяется одним выражением, как в приведенном выше примере.
О Если существует несколько условий завершения цикла, когда условия выхода из цикла разные, а также если должно быть определено возвращаемое значение, предпочтение следует отдать использованию оператора IF или CASE с оператором EXIT.
Давайте рассмотрим пример, когда предпочтительнее использовать оператор EXIT. Он взят из функции, определяющей, являются ли два файла эквивалентными (то есть содержащими одну и ту же информацию):
IF (end of filel AND end of file2)
THEN 一 ~.
retval TRUE:
EXIT:
ELSIF Ccheckline !- agalnstllne)
THEN
retval 尸 FALSE;
EXIT;
ELSIF (end_of_filel OR end_of_file2)■
THEN ~ 一-
retval :- FALSE;
EXIT:
END IF:
END LOOP:
Эмуляция цикла REPEAT UNTIL
В PL/SQL отсутствует цикл REPEAT UNTIL, б котором условие проверяется после выполнения тела цикла, в результате чего последнее выполняется как минимум один раз. Однако этот цикл легко эмулировать с помощью следующего простого цикла:
LOOP
...тело цикла ...
EXIT WHEN логическоеусловие;
END LOOP;
Здесь логическоеусловие — это логическая переменная или выражение, результатом проверки которого является значение TRUE или FALSE.
Цикл WHILE
Цикл WHILE относится к числу условных. Его выполнение продолжается до тех пор, пока результатом проверки определенного в цикле условия является значение TRUE. А поскольку возможность выполнения этого цикла зависит от условия
и не ограничено определенным количеством повторений, он используется именно в тех случаях, когда количество повторений цикла заранее не известно.
Приведем общий синтаксис цикла WHILE:
WHILE условие
исполняемыеупера торы
END LOOP: "
Здесь условие - это логическая переменная или выражение, результатом проверки которого является логическое значение TRUE, FALSE или NULL. Условие проверяется на каждой итерации цикла перед выполнением операторов, находящихся в его теле. Если результат оказывается равным TRUE, тело цикла выполняется. В том случае, когда результат равен FALSE или NULL, цикл завершается и управление передается исполняемому оператору, следующему за оператором END LOOP. Свойства цикла WHILE приведены в таблице.
Свойство
Описание
Условие завершения цикла
Когда проверяется условие завершения цикла
В каких случаях используется данный цикл
Если значением логического выражения цикла является FALSE или NULL
Перед первым и каждым последующим выполнением тела цикла. Таким образом, не гарантируется даже однократное выполнение тела цикла WHILE
Во-первых, если не известно, сколько раз будет выполняться тело цикла; во-вторых, когда необходимо, чтобы возможность выполнения цикла определялась условием; в-третьих, когда необязательно, чтобы тело цикла было выполнено хотя бы один раз
Условие WHILE проверяется в начале цикла и в начале каждой его итерации, перед выполнением тела цикла. Такого рода проверка позволяет сделать следующие выводы.
О Всю информацию, необходимую для вычисления условия, нужно задавать перед первым выполнением цикла.
О Вполне возможно, что цикл WHILE не будет выполнен ни разу.
Приведем пример цикла WHILE из файла datemgr.pkg, который можно найти на узле O'Reilly. Здесь используется условие, представленное сложным логическим выражением. Остановка цикла WHILE вызвана одной из двух причин; либо закончился список масок даты, которые применяются для выполнения преобразования, либо преобразование успешно завершено (и теперь переменная date_conver- ted содержит значение TRUE):
WHILE maskjndex <= fmt_count AND NOT date_converted
LOOP"
BEGIN
/* Попытка преобразовать строковое значение с использованием маски в строке таблицы */ retval :- TO_DATE (valuejn. fmts Cmaskjndex)); date_converted :- TRUE:-一
EXCEPTION
WHEN OTHERS
THEN
retval :- NULL:
mask_1ndex := maskjndex + 1:
END; 一~
END LOOP:.
Использование счетчика в цикле FOR
В PL/SQL существует дна вида цикла FOR: с числовым счетчиком и с курсором. Цикл с числовым счетчиком — это традиционный и хорошо знакомый всем программистам цикл FOR, имеющийся в большинстве языков программирования. Количество итераций данного цикла известно еще до его начала — это диапазон, задаваемый между ключевыми словами FOR и LOOP. При установке диапазона значений счетчика цикла неявно объявляется сам счетчик (если он не был объявлен ранее), определяются начальное и конечное значения диапазона, а также задается направление изменения значений счетчика (от наименьшего к наибольшему или наоборот).
Приведем общий синтаксис цикла FOR:
FOR индекс цикла IN [REVERSE] начальное значение .. конечное значение
LOOP~
исполияемые_опера торы
END LOOP: ~
Между ключевыми словами LOOP и END LOOP должен стоять хотя бы один исполняемый оператор. Свойства цикла FOR с числовым счетчиком приведены в следующей таблице.
Свойство
Описание
Условие завершения цикла
Если выполнено то количество итераций, которое определено диапазоном значений счетчика. (Можно завершить цикл и оператором ЕХГТ, но это делать не рекомендуется)
Когда проверяется условие завершения цикла
После каждого выполнения тела цикла компилятор PL7SQL проверяет значение счетчика цикла. Если оно находится вне заданного диапазона, выполнение цикла прекращается. В том случае, когда начальное значение больше конечного, тело цикла не выполняется
В каких случаях используется данный цикл
Если тело цикла должно быть выполнено определенное количество раз
Правила для циклов FOR с числовым счетчиком
При использовании цикла FOR с числовым счетчиком необходимо следовать таким правилам.
О Не объявляйте счетчлк цикла. По умолчанию он будет неявно объявлен как локальная перемеиная, им&ющая тип данных INTEGER. Областью действия указанной переменной будет весь цикл; на счетчик цикла нельзя ссылаться извне.
О Выражения, используемые при охфеделеиии диапазона счетчика цикла (начального и конечного значений) вычисляются один раз. В ходе выполнения цикла они не пересчитываются. Поэтому если внутри цикла вы измените значения переменных, которые используются при определении диапазона счетчика, его граничные значения останутся прежними.
О Никогда ие меняйте звачения счетчика цикла и границ диапазона внутри .цикла. Это порочная практика. Компилятор PL/SQL либо выдаст сообщение
□б ошибке, либо проигнорирует ваши действия — в любом случае у вас возникнут проблемы.
О Если вы хотите, чтобы значения счетчика уменьшались в направлении от конечного к начальному, используйте ключевое слово REVERSE, При этом первое значение в определении диапазона (начвльноеундчение) должно быть меньше второго (конечное_значение). Но не меняйте порядок следования этих значений 一 ключевое слово REVERSE само сделает все, что нужно.
Примеры циклов FOR с числовым счетчиком
Далее будут рассмотрены примеры, демонстрирующие некоторые варианты синтаксиса цикла FOR с числовым счетчиком.
О Цикл выполняется 10 раз; значение счетчика увеличивается от 1 до 10:
FOR loop_counter IN 110
LOOP -
исполняемые операторы
END LOOP;
О Цикл выполняется 10 раз; значение счетчика уменьшается от 10 до 1:
FOR loop_counter IN REVERSE 1 .. 10 LOOP
исполняемые операторы
END LOOP:
О Цикл не выполняется ни разу. Используется ключевое слово REVERSE, так что значение счетчика цикла, loop__counter, изменяется от наибольшего до наименьшего. Однако начальное и конечное значения счетчика заданы неверно:
FOR 1сюр_С0иМег IN REVERSE 10 .. 1
LOOP
/* Это тело цикла не выполнится ни разу! */
END LOOP:
Даже если вы зададите направление с помощью ключевого слова REVERSE, начальное значение диапазона счетчика все равно должно быть указано первым. Если начальное значение больше конечного, тело цикла ни разу не будет выполнено. Если же граничные значения будут одинаковыми, тело цикла будет выполнено один раз,
Q Цикл выполняется для диапазона, определяемого значениями переменной и выражения:
FOR calcjndex IN start_pEriod_number
LEAST (end pEr1od_number, current period)
LOOP'~
исполняемые операторы
END LOOP;
В этом примере количество итераций цикла определяется во время выполнения программы. Начальное и конечное значения вычисляются один раз, перед началом цикла, и затем используются в течение всего времени его выполнения.
Обработка нетривиальных приращений
PL/SQL не предоставляет специальных синтаксических способов задания шага приращения счетчика. Во всех разновидностях цикла FOR с числовым счетчиком значение счетчика на каждой итерации увеличивается на единицу.
В том случае, когда приращение должно быть нестандартным (не равным единице), придется писать специальный код. Например, что нужно сделать для того, чтобы тело цикла выполнялось только для четных чисел из диапазона от 1 до 100? Во-первых, можно использовать числовую функцию MOD, как в следующем примере:.
FOR loop Index IN 1 .. 100
LOOP ~
MOD (loop index, 2)-0
THEN "
/* Число четное, поэтому выполним вычисления */
calc_values(loop_1ndex);
END IF;~
END LOOP;■
Во-вторых, в теле цикла значение счетчика можно умножить на два и использовать вдвое меньший диапазон:
FOR even_number IN 1 .. 50
LOOP -
ca lc_va1 ues (even_number*2):
END LOOP; ~~
В обоих случаях процедура calc_values выполняется только для четных чисел. В первом примере цикл FOR повторяется 100 раз, во втором - только 50. Но какой бы подход вы ни избрали, обязательно подробно его закомментируйте. Комментарии помогут другим программистам при сопровождении вашей программы.
Использование курсора в цикле FOR
Цикл FOR с курсором — это цикл, связанный с явно заданным курсором или заданной непосредственно в рамках цикла инструкцией SELECT. Такой цикл следует использовать только в том случае, если вам нужно извлечь и обработать все записи курсора, что приходится делать довольно часто.
Цикл FOR с курсором — одна из замечательных возможностей PL/SQL, обеспечивающая тесную и эффективную интеграцию процедурных конструкций с мощью языка доступа к базам данных SQL. В случае его применения заметно сокращается объем кода, необходимого для извлечения данных из курсора, и уменьшается вероятность возникновения ошибок при циклической обработке данных — ведь именно циклы являются основными источниками ошибок в программах.
Приведем базовый синтаксис цикла FOR с курсором:
FOR инлекс_записи IN [имя^рсора Сявная_инструкция_5ЕСЕСТ')]
LOOP ~-~~
исполняемые_опера торы
END LOOP; ~
Здесь индексуаписи — неявно объявленная запись с атрибутом ZROWTYPE для курсора 0XHJ<yp8Pd.
ВНИМАНИЕ—■~
Не объявляйте явно запись (индекс_записи), на которой основан цикл. В этом нет необходимости, поскольку запись автоматически объявляется неявна, а к тому же подобное действие может привести к логическим ошибкам. О там, как получить доступ к информации о записях, обработанных в цикле for после его выполнения, рассказывается далее в этой главе.
В цикле FOR можно также задать не курсор, а. непосредственно SQL-инструкцию SELECT, как показано в следующем примере:
FOR book_cur IN (SELECT * FROM book)
LOOP ~
show_usage:
END LOOP:
На первый взгляд такая конструкция может показаться достаточно удобной, но мы рекомендовали бы ее избегать, поскольку встраивание инструкций SELECT в «неожиданные» места кода затрудняет его сопровождение и отладку. Свойства цикла FOR, в котором используется курсор, приведены ниже в виде таблицы.
Свойство
Описание
Условие завершения цикла
Если выбраны все записи курсора. Цикл можно завершить и оператором EXIT, но так поступать не рекомендуется
Когда проверяется условие завершения цикла
После каждого выполнения тела цикла компилятор PL/SQL схуществляет очередную выборку строки. В том случае, когда значение атрибута курсора %NOTFOUND оказывается равным TRUE, цикл завершается. Если курсор не возвратит ни одной строки, тело цикла никогда не будет выполнено
В каких случаях используется данный цикл
При необходимости выбрать и обработать каждую запись курсора
Данный цикл рекомендуется использовать, когда нужно безо всяких условии по очереди выбрать все строки курсора (и в цикле нет операторов EXIT и EXIT WHEN, вызывающих его преждевременное завершение). С помощью этого цикла можно упростить программный код и уменьшить вероятность появления ошибок.
Примеры цикла FOR с курсором
Предположим, нам необходимо обновить счета владельцев всех живущих в специальном отеле домашних животных. Следующий код включает анонимный блок, в котором для выбора номера комнаты и идентификационного номера животного используется курсор occupancy_cur. Процедура update_bill назначает комнаты новым постояяьцам отеля:
DECLARE
CURSOR occupancy_cur IS
SELECT petjd. room_nuinber
FROM occupancy WHERE occupiedjt - TRUNC (SYSDATE);
occupancy_rec occupancy^curXROWlVPE;
б 8EGIN
OPEN occupancy_cur;
В LOOP-
FETCH occupancy_cur INTO occupancy^rec;
EXIT WHEN occupancy_curlNDTFOUND;
update_bi11
(occupancy_rec.petJd, occupancy_rec.room_number):
END LOOP; '~~
CLOSE occupancy_cur;
. END;"
Этот код последовательно и явно выполняет все необходимые действия: определяет курсор (строка 2) и запись для этого курсора (строка 5), открывает курсор (строка 7), начинает выполнение цикла (строка 8), выбирает из курсора строку (строка 9), проверяет условие выхода из цикла (конец данных) с помощью соответствующего атрибута курсора (строка 10) и, наконец, выполняет обновление (строка И). После этого программист должен закрыть курсор (строка 14).
А вот что получится, если переписать тот же код с использованием цикла FOR с курсором:
DECLARE
CURSOR occupancy_cur IS
SELECT piet—ld. room^number
FROM occupancy WHERE occupied dt - TRUNC (SYSDATE):
BEGIN"
FOR осхиргтз」ес IN occupancy_cur
LOP 一~
update bill (occupancy rec.petjd, occupancy_rec.room_nuniber):
END LOOP;p"
END;■
Как все просто и понятно! Исчезло объявление записи. Исчезли операторы OPEN, FETCH и CLOSE. Больше не нужно проверять атрибут ZNOTFOUND. Нет никаких сложностей с организацией выборки данных. Далее PL/SQL с учетом того, что нужна каждая строка курсора и что ее следует поместить в соответствующую запись курсора, позаботится обо всем сам.
Как и любому другому курсору, курсору в цикле FOR можно передавать параметры, Если какой-либо из столбцов списка инструкции SELECT задается в качестве выражения, обязательно определите для него псевдоним. Единственным способом доступа к конкретному значению в строке курсора является применение синтаксиса с использованием точки имяуаписи.имя_столбцд (например, occupan- cy_rec. room_nuntoer), так что без псевдонима к столбцу-выражению обратиться невозможно.
Метки цикла
Циклу можно назначить имя, воспользовавшись для этой цели меткой. (О метках рассказывалось в главе 3.) Метка цикла в PL/SQL имеет следующий формат:
«ийя кетн»>
Метка должна стоять непосредственно перед оператором FOR:
<<a11_emps»
FOR emp_rec IN enip_cur
LOOP ~
END LOOP:,
Эту же метку можно указать и после зарезервированных слов END LOOP, как в следующем примере:
«уеаг_1оор»
WHILE year number <- 1995
LOOP -
«nonth_loop>=*
FOR month_nunber IN 1..12
LOOP "
END LOOP monthJoop;
END LOOP yearJaop;
В каких же случаях обычно используется метка цикла? Назовем несколько типичных ситуаций.
О Если вы написали очень длинный цикл (начинающийся, скажем, на 50-й строке, оканчивающийся на 725-й и содержащий 16 вложенных циклов), используйте метку цикла для того, чтобы явно связать его конец с началом. Эта визуальная пометка поможет вам при отладке и сопровождении программы, иначе будет сложно отследить, какой оператор LOOP соответствует каждому из операторов END LOOP.
О Метку цикла можно использовать для уточнения имени индексной переменной цикла (записи или счетчика), что также делает текст программы более читабельным:
«уеаг_1оор»
FOR year_number IN 1800..1995
LOOP ~
«month_l oop»
FOR month_number IN 1. .12
LOOP ~
IF year_loop.year_number - 1900 THEN END IF:
END LOOP month_loop:
END LOOP yearJoop;
Q При использовании вложенных циклов метку применяют как для повышения читабельности кода, так и с целью более эффективного управления циклами. При желании выполнение именованного внешнего цикла можно остановить посредством оператора EXIT с заданной в нем меткой цикла:
EXIT метка_цикла:
EXIT меткаjjtfкла WHEN условие;
Но обычно не рекомендуется применять метку цикла подобным образом, так как логика программы становится в результате этого плохо структурированной (как в ситуации с операторами GOTO) и ее трудно отлаживать. Если вам потребуется использовать подобный код, будет целесообразнее изменить структуру цикла, а возможно, заменить его циклом FOR или простым циклом WHILE.
Полезные советы
Циклы — это очень эффективные управляющие конструкции, но при их использовании необходима особая аккуратность и внимательность. Именно с циклами часто связаны вопросы производительности программ, и любая ошибка, возникшая в цикле, повторяется ввиду многократности его выполнения. Логика, определяющая условие остановки цикла, бывает очень сложной. В этом разделе приводится несколько советов по поводу того, как сделать циклы более четкими и понятными, а также максимально упростить их сопровождение.
Использование для счетчиков циклов понятных имен
Программист, которому поручено сопровождать программу, не должен уподобляться Шерлоку Холмсу, с тем чтобы с помощью сложной дедукции определять смысл начального и конечного значений счетчика цикла FOR. Вы должны применять понятные и информативные имена переменных и циклов, и тогда другим программистам (да и вам самим некоторое время спустя) легко будет разобраться в таком коде.
Как можно понять следующий код, не говоря уже о его сопровождении?
FOR i IN start_1d .. end_1d
LOOP"~
FOR j IN 1 .. 7
LOOP
FOR k IN 1 .. 24
LOOP
build_schedule (i, j, k): END LOOP?
END LOOP:
■ END LOOP;
Неужели невозможно обойтись без подобных имен переменных? Тем не менее их можно видеть сплошь и рядом. А исправить этот код очень просто — достаточно заменить имена переменных более информативными:
FOR focus account IN start id .. end id
LOOP ~~'
FOR day_1n_week IN 1 .. 7
LOOP
FOR month_1n_biyear IN 1 .. 24
LOOP
build_schedule (focus_account, day_1 n_week. monthjn_biyear);
END LOOP;一一 —
end loop;
END LOOP;
Теперь мы видим, что внутренний цикл просто перебирает месяцы двухлетнего периода (12 2 * 24).
Как правильно попрощаться
Один из фундаментальных принципов структурного программирования звучит так: «один вход, один выход*. Первая его часть в PL/SQL реализуется автоматически. Какой бы цикл вы ни выбрали, у него всегда только одна точка входа — первый исполняемый оператор, следующий за ключевым словом LOOP. Но вполне реально написать цикл с несколькими точками выхода. Однако это не совсем удачная практика, поскольку цикл, который может завершаться несколькими способами, трудно отлаживать и сопровождать.
При задании выхода из цикла нужно придерживаться следующих правил.
О Не использовать в циклах FOR и WHILE операторы EXIT и EXIT WHEN. Цикл FOR должен завершаться только тогда, когда исчерпаны все начальные и конечные значения диапазона. Оператор EXIT внутри цикла FQR прерывает данный процесс и поэтому идет вразрез с самим назначением цикла FQR. Точно так же условие окончания цикла WHILE задается в самом операторе WHILE, и больше нигде задавать или дополнять его не следует.
О Не использовать в циклах операторы RETURN и GOTO, поскольку это вызывает преждевременное и неструктурированное завершение цикла. Применение указанных операторов, возможно, покажется вам привлекательным приемом, позволяющим уменьшить количество кода. Однако некоторое время спустя понять, модифицировать и отладить такой код будет нелегко.
Рассмотрим суть этих правил на примере цикла FOR, в котором используется курсор. Как вы уже видели, данный цикл облетает просмотр возвращаемых курсором записей, но не подходит для тех случаев, когда выход из цикла определяется некоторым условием, основанным на данных текущей записи. Предположим, что в цикле записи курсора просматриваются до тех пор, пока сумма значений определенного столбца (например, количество животных) не превысит максимальное значение, как показано в следующем примере. Хотя это можно сделать с помощью цикла FOR и курсора, выполнив внутри этого цикла оператор EXIT, поступать так не следует.
DECLARE
CURSOR occupancy_cur IS
SELECT pet_i d. room_number
FROM occupancy WHERE occupied_dt = TRUNC (SYSDATE);
pet_count INTEGER :- 0;
'BEGIN "
FOR occupancy_rec IN occupancy_cur
LOOP ~"
update_bi1】
°(occupa ncy_rec.pet_i d. occupancy_rec.roam^number):
^ pet_count :- pet_count + 1;"
EXIT WHEN pet_count >- :GLOBAL.max_pets;
END LOOP; ~~
END;■
В цикле FOR явно говорится, что его тело должно быть выполнено п раз (где п — количество шагов в цикле со счетчиком или количество записей в цикле с курсором). Оператор EXIT внутри цикла FOR (строка 12) изменяет логику его выполнения, но в результате получается код, который трудно понять и отладить. Поэтому, если нужно остановить цикл на основании информации текущей записи, лучше воспользоваться циклом WHILE или же простым циклом. Тогда структура кода будет лучше отражать ваши намерения.
Получение информации о выполнении цикла FOR
Циклы FOR — это довольно строгие структуры, использовать которые очень удобно. Они выполняют в программе большую «административную» работу, особенно циклы с курсором. Однако есть у них и существенный недостаток: позволив Oracle делать вместо нас определенную работу, мы ограничиваем собственные возможности доступа к конечным результатам цикла после его завершения.
Предположим, нам нужно узнать, сколько записей обработано в цикле FOR с курсором, и затем использовать это значение в программе. Было бы очень удобно написать примерно такой код:
BEGIN
FOR book_rec IN books cur (author in -> 1FEURSTEIN,STEVEN')
LOOP""
...обработка санных ...
END LOOP;
IF books_cur«ROWCOUNT > 10 THEN
Но, попытавшись это сделать, мы получим следующее сообщение об ошибке: 0RA-D1OO1: invalid cursor (недопустимый курсор), поскольку курсор был неявно открыт и закрыт Oracle. Так как же получить нужную нам информацию из уже завершившегося цикла? Для этого следует объявить переменную в том блоке, в который входит цикл FOR, и присвоить ей значение в его теле - в таком случае она останется доступной и после окончания цикла. Вот как это делается:
DECLARE
book count PLS INTEGER:
BEGIN "'
FOR book_rec IN books cur (author In -> 'FEURSTEIN,STEVEN'J
LOOP-~
...обработка данных ...
book count :- books curXROWCOUNT:
END LOOP:"
IF book_count > 10 THEN ...
SQL-инструкция в качестве цикла
На самом деле SQL-инструкцию SELECT можно рассматривать как цикл, поскольку она определяет действие, выполняемое компилятором SQL с набором данных. В некоторых случаях при реализации определенной задачи можно даже выбирать между использованием цикла PL/SQL и SQL-инструкции. Давайте рассмотрим пример, а затем сделаем некоторые выводы о том, какое решение лучше.
Предположим, нам необходимо написать программу, которая переносит информацию о выбывших из отеля животных из таблицы occupancy в таблицу occupancy^! story. Выбираем цикл FOR, в котором используется курсор. В теле цикла сначала добавляем каждую выбранную из курсора запись (представляющую выбывшее из отеля животное) в таблицу occupancy_hi story, а затем удаляем ее из таблицы occupancy:
DECLARE
CURSOR checked_out_cur IS
SELECT petjd. name, checkout_date-■
FROM occupancy WHERE check。疋 date IS NOT NULL;
BEGIN-
FOR checked out rec IN checked_out_cur
INSERT INTO occupancy_history (petjd. name. checkout_date)
VALUES (checked_out_rec.pet_i d, checked_out_rec.name.
checked_out_rec.checkout^date):
DELETE FROM occupancy "WHERE petjd - checked_out_rec. petjd;
EM) LOOP;~—
E№:
Этот код все делает правильно, но является ли данное решение единственным? Конечно жег нет. Поставленную задачу можно также выполнить с помощью SQL-инструкций:
BEGIN
INSERT INTO occupancy_history (petjd, NAME. checkout_date)
SELECT petjd, NAME, checkout_dSe"
FROM occupancy WHERE checkoif_date IS NOT NULL:
DELETE FROM occupancy WHERE checkout date IS NOT NULL;
ЕЮ:-
Каковы преимущества этого подхода? Код стал короче и выполняется более эффективно, поскольку уменьшилось количество переключений контекста (переходов от исполняемого ядра PL/SQL к исполняемому ядру SQL и обратно). Теперь обрабатываются только одна инструкция INSERT и одна инструкция DELETE.
Однако у «стопроцентного» SQL-подхода имеются свои недостатки. SQL-инструкция обычно действует по принципу <все или ничего». Иными словами, если при обработке одной строки происходит ошибка, отменяются все инструкции INSERT и DELETE и ни одна запись не будет вставлена или удалена. Кроме того, приходится дважды записывать предложение WHERE. Для нашего примера это не очень важно, но в случае более сложных запросов данное обстоятельство может иметь решающее значение. А первоначальный цикл FOR позволяет избежать дублирования потенциально сложной логики в нескольких местах.
Кроме того, PL/SQL более гибок, чем SQL. Допустим, нам хотелось бы переносить за одну операцию максимально возможное количество записей, а для тех записей, при перемещении которых произошли ошибки, просто записывать сообщения в журнал, В этом случае необходимо создать цикл FOR, дополненный разделом исключений:
BEGIN
FOR checked out rec IN checked out cur
LOOP~"
BEGIN
INSERT INTO occupancy_history ...
DELETE FROM occupancy ...
EXCEPTION
WHEN OTHERS THEN
log_checkout_error (checked out_rec):
END; - 一~
END LOOP;
END:
PL/SQL позволяет обрабатывать записи по одной и для каждой из них выполнять необходимые действия, которые могут быть и очень сложными. В таких слу~ чаях удобнее использовать PL/SQL и SQL совместно. Но если ваша задача такова, что для ее решения достаточно одного SQL, лучше им и воспользоваться — код получится и короче, и эффективнее.
Глава 6
66Обработка исключенийОбработка исключений
6
6
Обработка исключений
Обработка исключений
А Как в PL/SQL обрабатываются ошибки
А Определение исключений
А Как инициировать исключение
А Обработка исключений
К сожалению, многие программисты склонны выдавать желаемое за действительное и часто полагают, что проделали титаническую работу, реализовав какую-либо положительную функцию приложения, скажем, операцию формирования заказов или счетов. Им невероятно трудно и с психологической точки зрения, и с точки зрения ресурсов сконцентрироваться на неявном аспекте, например предусмотреть, что произойдет, если пользователь нажмет не ту клавишу, или же что делспъ, если база данных окажется недоступной. Как правило, они пишут приложения, предназначенные для работы в «лучшем из миров», где в программах не бывает ошибок, пользователи вводят лишь правильные данные и только должным образом, а все системы — и аппаратные, и программные — всегда в полном порядке.
Но жестокая реальность свидетельствует о том, что как бы вы ни старались, в приложении всегда останутся ошибки. И пользователь обязательно воспользуется именно той последовательностью клавиш, на которую ваша программа реагирует сбоем. Вот почему перед вами стоит очень простой выбор: либо потратить определенное время на основательную отладку и защиту программ, либо приготовиться к тяжким и непрекращающимся баталиям с рассерженными пользователями.
К счастью, PL/SQL предлагает достаточно мощный и гибкий механизм перехвата и обработки ошибок. И вполне возможно написать на языке PL/SQL такое приложение, которое полностью защитит от ошибок и всех пользователей, и базу данных.
Как в PL/SQL обрабатываются ошибки
В языке PL/SQL ошибки всех вщюв интерпретируются как исключения, то есть недопустимые ситуации, которых всячески следует избегать. К числу исключений относятся:
О ошибки, которые генерируются системой (в частности такие, как нехватка памяти и повторяющееся значение индекса);
О ошибки, вызванные действиями пользователя;
О предупреждения, выдаваемые приложением пользователю.
PL/SQL перехватывает ошибки и реагирует на них при помощи так называемых обработчиков исключений. Механизм функционирования обработчиков исключений позволяет четко отделить код обработки ошибок от исполняемых операторов, дает возможность реализовать обработку ошибок, управляемую событиями, отказавшись от устаревшей линейной модели программирования. И независимо от того, как и по какой причине было инициировано конкретное исключение, оно обрабатывается одним и тем же обработчиком в разделе исключений.
Когда происходит ошибка, будь то системная ошибка или ошибка в приложении, в PL/SQL инициируется исключение. В результате выполнение исполняемого блока прерывается и управление передается отдельному разделу исключений в текущем блоке, если таковой имеется. После обработки исключения возврат в тот блок, из которого оно было инициировано, не возможен, поэтому управление переходит во внешний блок. Куда передается управление, если в программе произошло исключение, показано на рис. 6.1.
Рис. 6.1. Архитектура обработки исключенийРис. 6.1. Архитектура обработки исключенийИсполняемый разделИсполняемый разделРадел исключенийРадел исключенийPROCEDURE Jinffrlny 'ISPROCEDURE Jinffrlny 'ISnew value VARCHARE2(25]:new value VARCHARE2(25]:
Рис. 6.1. Архитектура обработки исключений
Рис. 6.1. Архитектура обработки исключений
Исполняемый раздел
Исполняемый раздел
Радел исключений
Радел исключений
PROCEDURE Jinffrlny '
IS
PROCEDURE Jinffrlny '
IS
new value VARCHARE2(25]:
new value VARCHARE2(25]:
Стратегия обработки исключений
Механизм обработки исключений в PL/SQL достаточно мощен и гибок, но его эффективное использование требует определенных навыков. Очень важно определить
собственную стратегию и последовательность обработки ошибок еще до начала работы над программным кодом. Для этого нужно ответить на ряд вопросов.
О Когда и как следует протоколировать ошибки, чтобы их можно было просмотреть и исправить?
О Когда и каким образом пользователь будет извещаться об ошибках?
С учетом ответов на эти общие вопросы принимаются решения, связанные с более конкретными действиями.
О Следует ли включать раздел обработки исключений в каждый блок PL/SQL?
О Не лучше ли поместить раздел обработки исключений только в самый внешний блок?
О Как обрабатывать транзакции в случае возникновения ошибки?
Сложность обработки исключений отчасти заключается в том, что на каждый из этих вопросов не существует единого правильного ответа. Любое решение зависит от архитектуры приложения и от того, как оно используется (например, выполняется в пакетном режиме или в режиме управляемых пользователем транзакций). И как бы вы ни ответили на поставленные вопросы, касающиеся конкретного приложения, мы настоятельно рекомендуем «закодировать^ стратегию и правила обработки ошибок в стандартизированном пакете. Эта проблема еще будет затронута в конце главы.
Ниже изложены концепции, лежащие в основе обработки исключений в PL/SQL, и даны примеры их различных видов, ознакомившись с которыми, вы поймете, как инициировать ошибки в программах и как их обрабатывать.
Концепции и терминология обработки исключений
Существует два типа исключений.
О Системное нсключение. Определено в Oracle и обычно инициируется исполняемым ядром PL/SQL, обнаружившим ошибку. Одни системные исключения имеют имена (например, NO_DATA_FOUND), тогда как другие - только номера и описания.
О Исключение, определяемое программистом. Определяется программистом, а следовательно, специфично для данного приложения. Имя исключения можно связать с конкретной ошибкой Oracle с помощью директивы компилятора EXCEPTION_INIT. Ошибке можно присвоить номер и создать для нее описание, воспользовавшись процедурой RAISE_APPLICATION_ERROR.
Дадим определение некоторым из терминов, вводимых в этой главе.
О Раздел нсключений — это необязательный раздел блока PL/SQL (анонимного блока, процедуры, функции, триггера или инициализационного раздела пакета), содержащий один или несколько обработчиков исключений. Структура раздела исключений очень похожа на структуру оператора CASE, о котором рассказывалось в главе 4.
Q Инициировать исключение 一 значит остановить выполнение текущего блока PL/SQL путем уведомления исполняемого ядра об ошибке. Исключение может
инициировать либо Oracle, либо ваги собственный программный код при помощи оператора RAISE или процедуры RAISE_APPLICATION_ERROR.
О Обработать исключение — значит перехватить ошибку, передав управление обработчику исключения. Написанный программистом обработчик исключения может содержать код, который в ответ на исключение выполняет определенные действия (например, записывает информацию об ошибке в журнал, выводит соответствующее сообщение для пользователя или передает исключение во внешний блок).
О Область действия — часть кода (блок или раздел), где может инициироваться исключение, а также часть кода, инициируемые исключения которой могут перехватываться и обрабатываться соответствующим разделом исключений.
О Передача исключения — процесс передачи исключения во внешний блок, если в текущем блоке это исключение не обработано.
О Необработанное исключение - это исключение, которое передается без соответствующей обработки в «самом внешнем» блоке PL/SQL. После этого управление передается исполнительной среде, которая уже сама определяет, как отреагировать на исключение (выполнить откат транзакции, вывести сообщение об ошибке или, скажем, проигнорировать ее).
О Неименованное, или анонимное, исключение — это исключение, с которым связан код ошибки и описание, Такое исключение не имеет имени, которое можно было бы использовать в операторе RAISE или предложении WHEN обработчика исключений.
О Именованное исключение — это исключение, которому имя присвоено либо Oracle (в одном из встроенных пакетов), либо разработчиком. В частности, для этой цели можно использовать директиву компилятора EXCEPTION_INIT (в таком случае его можно будет применять и для инициирования, и для обработки исключения).
Определение исключений
Прежде чем исключение можно будет инициировать и обрабатывать, его нужно определить. В Oracle заранее определены тысячи исключений, большинство из которых имеют только номера и поясняющие сообщения. Имена присваиваются только самым распространенным исключениям.
Вы можете определить собственные исключения и использовать их лишь в своих приложениях. Это можно сделать двумя способами, описанными ниже.
Объявление именованных исключений
Исключения PL/SQL, объявленные во встроенных пакетах, например в пакете STANDARD, связаны с внутренними ошибками и ошибками, генерируемыми системой. Однако многие проблемы, с которыми будет сталкиваться пользователь приложения (и которые часто будут вызываться его действиями) специфичны именно Для данного приложения. Возможно, вашей программе придется перехватывать
и обрабатывать такие ошибки, как ^отрицательный баланс на снегу» или «дата звонка не может быть меньше текущей даты». Хотя у этих ошибок и иная природа, чем, скажем, у ошибки «деление на нуль», они также относятся к числу исключений, связанных с нормальной работой программы, и должны обрабатываться этой программой.
Очень полезной особенностью модели обработки исключений PL/SQL принято считать отсутствие структурного различия между внутренними ошибками и ошибками, специфическими для приложения. Любое исключение может и должно обрабатываться в разделе исключений независимо от типа ошибки.
Конечно, для того чтобы обработать исключение, необходимо знать его имя. Поскольку в PL/SQL имена пользовательским Исключениям автоматически не назначаются, вы должны делать это самостоятельно, определяя исключения в разделе объявлений блока PL/SQL. При этом задается имя исключения, за которым следует ключевое слово EXCEPTION:
ш_исключения EXCEPTION;
Приведенный ниже раздел объявлений процедуры calc_annua1_sales содержит два объявления исключений, определяемых программистом:
PROCEDURE calc_annual_sales
(company Idjn IN company.coir^any^id^TYPE)
IS-
1nvalid__companyjdEXCEPTION;
negative_balanceEXCEPTION;
duplicate^compapyBOOLEAN:
BEGIN -
…исполняемые операторы ...
EXCEPTION
WHEN NO DATA FOUND системное исключение
THEN 一 一
WHEN invalid coiroany
THEN 一 一'
WHEN negat1ve_balance
THEN ~
END;…
По своему формату имена исключений подобны именам логических переменных, но ссылаться на них можно только двумя способами.
Q Во-первых, это можно сделать с помощью оператора RAISE, помещенного в исполняемый раздел программы (для того чтобы инициировать исключение):
RAISE invalid^companyjd;
Q Во-вторых, это делается посредством предложения WHEN раздела исключений (для обработки инициированного исключения):
WHEN invalid_conipany_1d THEN
Как связать имя исключения с кодом ошибки
В Oracle, как уже было сказано, определены имена лишь немногих, наиболее широко распространенных исключений. Преобладающее большинство ошибок в СУБД имеют лишь номера и снабжены поясняющими их сообщениями. Инициировать исключение с номером ошибки (он должен быть представлен значением в диапазоне от -20 999 до -20 000) может и разработчик приложения, воспользовавшись для этой цели процедурой RAISE_APPLICATIQN-ERRQR> о которой рассказывается ниже, в разделе «Как инициировать исключение*.
Наличие в коде программы исключений без имен вполне допустимо, но такой код малопонятен и его трудно сопровождать. Предположим, вы написали программу, при выполнении которой Oracle генерирует ошибку, связанную с данными, например такую; как ORA-01843: not a valid month. Для перехвата этой ошибки в код программы помещен обработчик исключений:
EXCEPTION
WHEN OTHERS THEN
IF SQLCOOE - -1843 THEN
Но, как видите, данный код совершенно не понятен, поэтому он обязательно должен сопровождаться комментарием.
РИМЕЧАНИЕ-;
SQLCODE—это встроенная функция, которая возвращает номер последней сгенерированной ошибки. Она будет рассмотрена далее в этой главе.
Использование директивы EXCEPTION_INIT
С помощью директивы компилятора EXCEPTION_INIT предложение WHEN, использовавшееся в предыдущем примере, можно изменить следующим образом:
EXCEPTION
WHEN invalidjnonth THEN
И никакие литеральные номера ошибок, которые так трудно запомнить, не понадобятся. Теперь имя ошибки говорит само за себя. Вы спросите, как же связать его с определенной ошибкой? Ответ прост: воспользоваться EXCEPTION_INIT — директивой компилятора (то есть командой, выполняемой во время компиляции программы), которая связывает заданное имя с внутренним кодом ошибки. Имя должно быть объявлено в программе как EXCEPTION. Установив такую связь, можно инициировать исключение по имени и использовать это имя в предложении WHEN обработчика ошибок.
Директива EXCEPTION_INIT располагается в разделе объявлений блока. Указанное в ней исключение должно уже быть объявлено либо в том же блоке, либо во внешнем, либо в спецификации пакета.
Приведем синтаксис анонимного блока:
declare
имяисключения EXCEPTION:
PRAGMA EXCEPTION」NIT (шисключения. целое_число):
Здесь имя_исключения — это имя исключения, объявляемого профаммистом, а целое_число — номер ошибки Oracle, которую следует связать с данным исключением. Причем номером ошибки может служить любое целое число, кроме: числа
-1403; 0 или положительного числа, за исключением 1; отрицательного числа меньше -10 000 000.
Число -1403 является кодом ошибки NO_DATA_FOUND. Если по какой-то притаве необходимо связать с этой ошибкой собственное именованное исключение, укажите в директиве EXCEPTION_INIT значение 100. NO_DATA_FOUND - это единственная, надо полагать, опшбка, для которой в Oracle определено два разных номера. Связано это со стандартом ANSI, который требует, чтобы для определения данной опшбки использовался номер 100.
Ну а теперь рассмотрим пример возможного объявления исключения для обработки ошибки:
ORA-2292 violated Integrity constralnlng COWNER.CONSTRAINT)- child record found
Данная ошибка происходит при попытке удалить родительскую запись, для которой в таблице имеются дочерние записи. (Дочерняя запись — это запись с йнеш- ним ключом, указывающим на родительскую таблицу.)
PROCEDURE delete company (company 1d In IN NUMBER)
IS~"
/* Объявляем исключение. */
stl1l_have_emplqyees EXCEPTION:
/* Связываем имя исключения с номером ошибки. */
PRAGMA EXCEPTIONJNIT (still have_employees, -2292);
BEGIN~"
/* Пытаемся удалить информацию о компании. */
DELETE FROM company
WHERE company - company id in:
EXCEPTION ~
/* Если найдена дочерняя запись, инициируется это исключение! */
WHEN stl11_have_employees
THEN ~~
DBMSJJTPJT.PUT_LINE
('Пожалуйста, сначала удалите данные о служащих компании.'); END:
Как лучше использовать директиву EXCEPTION_INrT
Директиву EXCEPTION_INIT наиболее целесообразно использовать в двух следующих случаях:
О при необходимости присвоить имя безымянному системному исключению, на которое имеется ссылка в программе (следовательно, если в Oracle не определено имя для некоторой ошибки, это еще не означает, что вы должны работать с ней только исходя из ее номера);
Q когда нужно присвоить имя специфическому для приложения исключению, которое инициируется при помощи процедуры RAISE_APPLICATION_ERROR (о ней рассказывается далее, в разделе «Как инициировать исключение»; такой прием позволяет обрабатывать данное исключение исходя из имени, а не из номера).
/ В обоих случаях советуем собрать все директивы EXCEPTION_INIT в пакет, чтобы определения исключений не были разбросаны по всему коду приложения. Пред- (положим, вы интенсивно используете динамический SQL (см. главу 15) и при
выполнении запросов у вас часто возникает ошибка «invalid column пате» (неверное имя столбца). Конечно, вы не сможете сразу запомнить ее код, но и определять имя для исключения в 20 разных программах не рационально. Поэтому имеет смысл определить собственные «системные исключения» в отдельном пакете, предназначенном для работы с динамическим SQL:
CREATE OR REPLACE PACKAGE dynsql
IS■
invalid_table_name EXCEPTION:
PRAQ^A EXCEPTIONJNIT C1 rival 1d_table_name, -903);
lnval1d_column_name EXCEPTION;
PRAGMA EXCEPTIONJNIT (1nvalid_colunin_nanie. -904);
Теперь для перехвата этих ошибок в каждой программе можно использовать следующую строку кода:
•、 WHEN dynsql.1nval1d_colLtmn_name THEN
Коды ошибок -20ЛМЧ передаваемые процедуре RAISE_APPLICATIDN_ERROR, не следует задавать в виде литеральных значений. Создайте пакет, в котором этим кодам будут присвоены имена. 0H может быть, например, таким:
PACKAGE errnums,
IS
en_too^young CONSTANT NUMBER :- -20001;
exc_tooj^oung EXCEPTION;
PRAGMA EXCEPTIONJNIT (excjoo_young. -20001);
en_5dLtoo」ow CONSTANT NUMBER := -20002;
exc_sal_too_low EXCEPTION;
PRAGMA EXCEPTIONJNIT (exc_sal_too_low . -20002):
END errnums:
Имея подобный пакет, указывать номер ошибки в следующем коде не обязательно:
PROCEDURE validate_emp (birthdate_fn IN DATE)
IS"~
min^years CONSTANT PLS_INTEGER :- 18;
BEGIN
IF ADD MONTHS (SYSDATE, min years * 12 * - 1) < birthdatejn
THEN■'
RAISE一APPLICATION-ERROR (errnunis.en__too,oung, 'Возраст служещего должен быть не менее ' || minjears || ' лет.');■
END IF;
END;
Об именованных системных исключениях
В Oracle для некоторых исключений определены стандартные имена, которые задаются с помощью директивы компилятора EXCEPTION_INIT во встроенных пакетах. Наиболее важные и широко применяемые из них определены в пакете STANDARD. Это один из двух используемых по умолчанию пакетов PL/SQL (вторым является DBMS_STANDARD), То обстоятельство, что эти два пакета используются по умолчанию, означает, что на определенные в них исключения можно ссылаться без указания в качестве префикса имени пакета. Например, если необходимо инициировать
в программе исключение NO_DATA_FOUND, то это можно сделать посредством любого из двух операторов:
WHEN NO_DATA_FOUNO THEN
WHEN STANDARD. NOJATAJOUNO THEN
Определения стандартных именованных исключений можно найти и в других встроенных пакетах, в частности в DBMS_L08, предназначенном для проведения операций над большими объектами. Вот пример одного такого определения из указанного пакета:
1nvalid_argval EXCEPTION;
PRAGMA EXCEPTWN_INIT(invalid_argval. -21560);
Поскольку пакет DBMS_LOB не относится к числу используемых по умолчанию, ссылка на это исключение должна предваряться именем пакета:
WHEN DBMS_LOB.1nva11d_argval THEN...
Значительная часть исключений, определенных в пакете STANDARD, перечислена в табл. 6.1. Для каждого из них приведен номер ошибки Oracle, значение, возвращаемое при вызове функции SQLCODE, и краткое описание. SQLCODE — это встроенная функция PL/SQL, которая возвращает код состояния последней выполненной инструкции SQL или DML. Если данная инструкция выполнена без ошибок, функция возвращает нуль. Значение, возвращаемое функцией SQLCODE, совпадает с кодом ошибки Oracle, но существует одно исключение: определяемый стандартом ANSI номер ошибки NO_DATA_FOUND равен 100.
Таблица 6.1. Стандартные исключения в PUSQL
Имя исключения Ошибка Orade/SQLCODE
Описание
CURSOR,ALREADY_OPEN ORA-6511 SQLCODE=-6511
Попытка открыть (OPEN) курсор, который был открыт ранее. Поэтому перед повторным открытием курсор необходимо сначала закрыть (CLOSE)
DUP_VAL_ONJNDEX
ORMWOOl SQLCODE=-1
Инициируется, когда инструкция INSERT или UPDATE пытается сохранить повторяющиеся значения в столбце или столбцах, которые объявлены с ограничением UNIQUE
INVALID一CURSOR ORA-01001 SQLCODE=-1OO1
Ссылка на несуществующий курсор. Обычно это происходит при попытке извлечь данные (FETCH) из неоткрытого курсора или закрыть (CLOSE) курсор до его открытия (OPEN)
INVALID.NUMBER
ORA-01722 SQLCODE=-1722
PIVSQL выполняет SQL-инструкцию, которая не может успешно преобразовать символьную строку в число. Это исключение отличается от исключения VALUE_ERROR/ инициируемого только SQL-инструкцией
LOGIN_DENIED
ORA-01017 SQLC0DE=-1017
Попытка программы подключиться к СУБД Oracle с неверным именем пользователя и паролем
NO—DATA一FOUND
ORA-01403 SQLCODE=+100
Это исключение инициируется в трех случаях: при выполнении инструкции SELECT INTO (неявный курсор), которая не возвращает ни одной строки; при наличии ссылки на неинициализированную строку локальной таблицы PL/SQL; при попытке выполнить операцию чтения после достижения конца файла с использованием пакета UTL_FILE
продолжение^
Таблица 6.1 (продолжение)
Имя исключения Ошибка Oracle/SQLCODE
Описание
NOT_LOGGED_ON
ORA-01012 SQLCODE=-1012
PROGRAM一ERROR
ORA-06501 SQLCODE=-6501
Программа пытается обратиться к базе данных (обычно посредством инструкции DML) до подключения к СУБД Oracle
Внутренняя программная ошибка PiySQL, В сообщении об ошибке обычно предлагается обратиться в службу поддержки Oracle
STORAGJERROR
ORA-06500 SQLCODE=-6500
TIMEOUT ON RESOURCE
ORA-00051 5QLCODE—51
TOO MANY ROWS ORA-01422 SQLCODE=-1422
Программе PL/SQL недостаточно доступной памяти или память по какой-то причине повреждена
Твйм-аут с СУБД при ожидании ресурса
Инструкция SELECT INTO возвращает несколько строк, хотя может возвращать лишь одну строку; если же их больше одной, можно поместить инструкцию SELECT в явное определение курсора и выбирать (FETCH) строки из него по одной
TRANSACTION一BACKED.OUT 0RA-OOO61 SQLCODE—61
Удаленная часть транзакции отменена либо при помощи явной инструкции ROLLBACK, либо а результате какого-то другого действия (например, неудачного выполнения SQL- или DML屮нструкцми в удаленной базе данных)
VALUE_ERROR
ORA-06502 SQLCQDE—6502
Ошибка, связанная с преобразованием, усечением или проверкой ограничений числовых или символьных донных. Это общее и очень распространенное исключение. Если подобная ошибка содержится в инструкции SQL или DML, то а блоке PL/SQL инициируется исключение INVALID一NUMBER
ZERO_DIVIDE
ORA-01476 SQLCODE—1476
Попытка выполнить в программе деление числа на ноль
Вот пример того, как можно использовать эту таблицу исключений. Предположим, что ваша программа инициирует необрабатываемое исключение для ошибки ORA-6511. Обнаружив указанную ошибку в таблице, вы видите, что она связана с исключением CURSOR_ALREADY_OPEN. Найдите блок PL/SQL, в котором произошла данная ошибка, и добавьте в него обработчик исключения CUR30R_ALREADY_0PEN: EXCEPTION .
WHEN CURSOR ALREADY OPEN
THEN"
CLOSE ny_cursor: ■
END;-
Конечно, было бы хорошо проанализировать весь программный код и заранее определить, какие из стандартных исключении в нем могут инициироваться. В таком случае вы сможете решить, какие из них следует обрабатывать в отдельности, какие включить в предложение WHEN OTHERS, а какие оставить необработанными.
Область действия исключения
Область действия исключения - это та часть программного кода, к которой оно относится, то есть блок, где данное исключение может быть инициировано. В приведенной ниже таблице указаны области действия исключений четырех возможных типов.
Тип исключения
Область действия
Именованное системное
Исключение является глобальным, поскольку оно объявлено не
исключение
в каком-то конкретном блоке кода. Инициировать и обрабатывать системные исключения можно в любом блоке
Именованное
Это исключение можно инициировать и обрабатывать только в том
исключение^
исполнительном разделе и разделе исключений, которые входят
определяемое
в состав блока, где объявлено данное исключение (или в состав
программистом
любого из вложенных в него блоков). Если это исключение определено в спецификации пакета, его областью действия являются все те программы, владельцы которых имеют на этот пакет привилегию EXECUTE
Анонимное системное
Данное исключение можно обрабатывать в предложении WHEN
исключение
OTHERS любого раздела исключений. Если присвоить ему имя, его область действия будет такой же, как у именованного исключения, определяемого программистом
Анонимное исключение. Такое исключение определяется в вызове процедуры
определяемое
RAISEJ\PPLICATION„ERROR, а затем передается в вызывающую
программистом
программу
Давайте рассмотрим пример исключения overdue^balance, объявленного в процедуре checMccount (область его действия ограничена указанной процедурой):
PROCEDURE check account (company 1d_1n IN NUMBER)
IS~…
overdue balance EXCEPTION;
BEGIN "
...исполняемые операторы ...
LOOP
"iF ... THEN
RAISE overdue balance;
END IF; "
END LOOP;
EXCEPTION
WHEN overdue balance THEN
END; *-
С помощью оператора RAISE исключение overdue_balance можно инициировать в процедуре check^account, но не в программе, которая ее вызывает. Например, следующий анонимный блок генерирует ошибку компиляции:
DECLARE
company 1d NUMBER 100:
BEGIN -
check_account (100):
EXCEPTION
WHEN overdue_balance /* В PL/SQL такая ссылка недопустима */
THEN -
END:
PLS-00201: identifier "OVERDUE一BALANCE” must be declared
Для приведенного выше анонимного блока процедура check_account является <черным ящиком». Все объявленные в ней идентификаторы, в том числе идентификаторы исключения, не видимы для внешних программ.
Как инициировать исключение
Исключение может быть инициировано Oracle при обнаружении ошибки. Его также может инициировать программист посредством оператора RAISE или процедуры RAISE_APPLICATIONJRROR.
Как Oracle инициирует исключения, вы уже знаете. Теперь давайте рассмотрим механизмы, с помощью которых это может сделать программист.
Оператор RAISE
Для того чтобы программист имел возможность самостоятельно инициировать именованные исключения, Oracle поддерживает оператор RAISE. С его помощью можно инициировать как собственные, так и системные исключения. Оператор имеет три формы:
RAISE ипяусключения:
RAISE имя_пакега.имя исключения;
RAISE; 一一
Первая форма (без имени пакета) предназначена для инициирования исключений, определенных в текущем блоке (или в содержащем его блоке), а также для инициирования системных исключений, объявленных в пакете STANDARD. Вот два примера исключений, в первом из которых инициируется исключение, определенное программистом:
DECLARE
invalid_id EXCEPTION; -- Все идентификатры должны начинаться с букаы 'X'
idevalue VARCHAR2:
BEGIN
id_vd〕ue 尸 1d_for ('SMITH');
IF SU8STR (1d_value, 1. 1) 1- 'X'
THEN
RAISE inva〕id_id;
END IF;-
END;
Если нужно, в любой момент можно инициировать системное исключение:
BEGIN
IF total__sales - О
THEN ~
RAISE ZERO DIVIDE; -- Определено в пакете STANDARD
ELSF -
RETURN Csales_percentage_calculatlon (my_sales, total sales));
END IF; 一—
END:
Если исключение объявлено в пакете (но не в STANDARD) и инициируется извне, имя исключения нужно уточнить именем пакета:
IF da^s_overdue (1sbn_in. borrower in) > 365
THEN 一~-
RAISE overdue pkg.book_is_lost;
END IF; -■
Третья RAISE не требует указывать имя исключения, но используется только в предложении WHEN раздела исключений. Ее синтаксис прост:
RAISE:
Этой формой оператора рекомендуется пользоваться, когда в обработчике исключений нужно повторно инициировать (или передать) то же самое исключение:
EXCEPTION
WHEN NO_DATA_FOUND
THEN
-Используем общий пакет для записи всей 屮呻叩^叫屮讨 о
-контексте, такой как код ошибки, имя программы и т. д.
Brrlog.putline (company J djn);
-А теперь передаем исключение NO_DATA_FOUND в родительский блок, не обрабатывая его RAISE;
Эта возможность особенно полезна в тех случаях, когда информацию об опшб- ке нужно записать в ясурнал, а сам процесс обработки ошибки возложить на родительский блок. Таким образом определяется, где именно произошла ошибка, и при этом завершается работа одного или нескольких родительских блоков, без потери информации об ошибке.
Инициирование исключений во вложенных блоках
Когда исключение объявлено в блоке, оно является локальным для этого блока, но глобальным для всех вложенных в него блоков. В следующем варианте приведенной выше процедуры check_account содержится анонимный блок, в котором также инициируется исключение overdue_balance. И поскольку данный анонимный блок определен внутри блока процедуры, в нем допускается ссылка на это исключение:
PROCEDURE check_account Ccompariyjdjn IN NUMBER)
IS"一 -
overdue_balance EXCEPTION;
BEGIN, ...исполняемые операторы ....
-Начало вложенного блока
BEGIN
...операторы во вложенном блоке ...
RAISE overdue_balance; -- Исключение инициируется во вложенном блоке .
END:,
Конец вложенного блока'
LOOP
IF ... THEN
RAISE overdue balance: -- Исклочение инициируется в родительском блоке
END IF;一,
END LOOP;
EXCEPTION
WHEN overdue_balance THEN ... -- Исключение обрабатывается в родительском блоке
Когда исключение overdue_bal ance инициируется во вложенном или родительском блоке, управление немедленна передается последнему, где содержится единственный в процедуре раздел обработки исключений. А поскольку исключение overdue_balance объявляется для всей процедуры, во вложенных блоках к нему можно обращаться по имени.
Использование процедуры
RAISE »PPLICATION_ERROR
Для инициирования исктпочений, специфических для приложения! Oracle предоставляет процедуру RAISE一APPLICATION_e3rOR (определенную в используемом по умолчанию пакете DBMS^STANDARD). Ее преимущество перед оператором RAISE (который тоже может инициировать специфические для приложения явно объявленные исключения) заключается в том, что она позволяет связать с исключением сообщение об ошибке.
При вызове этой процедуры выполнение текущего блока PL/SQL прекращается и любые изменения аргументов OUT и IN OUT (если таковые имеются) отменяются. Изменения, внесенные с помощью инструкции INSERT, UPDATE или DELETE в глобальные структуры данных, такие как переменные пакетов и объекты баз данных, ме отменяются. Для отката DML-инструкций необходимо явно указать в разделе обработки исключений оператор ROLLBACK.
Вот заголовок процедуры RAISE_APPLICATION_ERROR:
PROCEDURE RAISE^PPLICATIONJRROR ( num blnaryjnteger.■
msg varchar2.
keeperrorsteck boolean default FALSE):
Здесь num — это номер ошибки из диапазона от - 20 999 до - 20 000 (вы только подумайте: все остальные отрицательные числа, которые больше -20 000, Oracle использует для собственных исключений!); msg — это сообщение об ошибке, длина которого не должна превышать 2048 символов (символы, выходящие за эту границу, игнорируются); аргумент keeperrorstdck указывает, хотите вы добавить ошибку к тем, что уже имеются в стеке (TRUE), или заменить существующую ошибку (значение по умолчанию 一 FALSE).
ПРИМЕЧАНИЕ
Предполагается, что Orade для обозначения пользовательских ошибок выделила диапазон номеров от -20 999 до -20 000, но учтите, что в нескольких встроенных пакетах, в том числе в DBMS_OUTPUT и DBMS_DESCRIBE, номера от-20 005 до -20 000 по-прежнему присваиваются системным ошибкам. Не очень удобно, конечно, но дело обстоит именно так. Более подробно об их использовании вы узнаете из документации пакетов.
А теперь давайте рассмотрим одну нз возможных областей применения этой встроенной процедуры. Предположим, нам требуется, чтобы сообщения об ошибках выдавались пользователям на разных языках. Создадим для них таблицу ег- ror_table и определим в ней язык каждого сообщения значением столбца string.
language. Затем создадим процедуру, которая генерирует заданную ошибку, считывая соответствующее сообщение с учетом значения параметра NLS_LANGUAGE:
/* файл в Web: ra1se_tiyjanguage.sp */
CREATE OR REPLACE PROCEDURE ra1se_byjanguage (codjin IN PLSJNTEGER) IS
message error_tab1e.error_stri ng:
BEGIN'"
SELECT error_str1ng
INTO l^message
FROM error_table. v$nlsj)arameters v
WHERE error^number - codeJn
AND strlngjanguage ■ v:VALUE
AND v.parameter - 'NLS_LANGLjAGE';
RAISE APPLICATION ERROR (code In, 1 message);
END:■
При использовании процедуры RAISE_APPLICATION_ERROR вы сами определяете и номер ошибки, и поясняющее ее сообщение. Правда, это может вызвать у вас некоторую растерянность, и вы наверняка будете действовать по принципу: «Сомневаюсь, что кто-нибудь уже задал номер -20 774, воспользуюсь им» Для того чтобы облегчить себе управление кодами ошибок и обеспечить своих коллег согласованным интерфейсом для обработки серверных ошибок, имеет смысл создать таблицу для хранения всех выделенных для нужд разработчиков номеров ошибок (-20 NNN) и связанных с ними имен исключений и сообщений об ошибках. Тогда любой программист сможет просмотреть уже определенные нами или членами вашей команды ошибки и найти те из них, которые подходят для текущей ситуации. Пример такой таблицы аы найдете в файле msginfb.prg на узле O'Reilly. Там же имеется код, генерирующий пакет с объявлениями всех «зареги- стрмрованных» исключений.
Обработка исключений
Как только инициируется исключение, нормальное выполнение блока PL/SQL останавливается и управление передается в раздел исключений. Затем исключение либо обрабатывается обработчиком исключений в текущем блоке PL/SQL, либо передается в родительский блок.
Для того чтобы обработать или перехватить исключение, нужно написать для него обработчик. Обработчики исключений располагаются после исполняемой части блока, но перед завершающим его ключевым словом ENO. Начало блока исключений отмечает ключевое слово EXCEPTION:
declare
объявления
BEGIN
исполняемые операторы
EXCEPTION. *
обработчики 1лсключений ...]
Синтаксис обработчика исключений может быть таким:
WHEN 修月且 исключения [OR шусключения ...]
THEN ~
исполняемые операторы
или таким:
WHEN OTHERS THEN
исполняемые операторы
В одном разделе исключений может быть несколько их обработчиков. Обработчики структурируются подобно условному оператору CASE.
EXCEPTION
WHEN NO_DATA_FOUND -- Если инициировано исключение NO_DATA_FOUND.
THEN-- то выполнить первый набор исполняемых операторов
исполияекые_олера торы!
WHEN payment—overdue-- Если 叩。срочена оплата (payment_overdue).
THEN— то выполнить второй набор исполняемых операторов
исполняемые_опера торы?
喇EN OTHERS-- Если инициировано иное исключение,
THEN— то выполнить третий набор исполняемых операюров
испопняемые_рператоруЗ
END;~
Если имя, заданное в предложении WHEN, совпадает с инициированным исключением, то это исключение обрабатывается соответствующим набором операторов. Обратите внимание, что таким образом исключения перехватываются только исходя из имен, а не из кодов ошибок. Но если инициированное исключение не имеет имени или его имя не соответствует ни одному из имен, указанных в предложениях WHEN, тогда его обработка выполняется посредством операторов, заданных в предложении WHEN OTHERS (если таковое имеется). Любая ошибка может быть перехвачена только одним обработчиком исключений. После выполнения операторов этого обработчика управление сразу же передается из текущего блока в родительский или вызывающий блок.
Предложение WHEN OTHERS не является обязательным. Когда оно отсутствует, все необработанные исключения немедленно передаются в родительский блок, если таковой имеется. Предложение WHEN OTHERS должно быть последним обработчиком исключений в блоке. Если поместить после него еще одно предложение WHEN, компилятор выдаст следующее сообщение об ошибке:
PLS-00370: OTHERS handler must be last among the exception handlers of a block
Объединение нескольких исключений в одном обработчике
В одном предложении WHEN, используя оператор OR, можно объединить несколько исключений — точно так же, как с его помощью объединяются логические выражения:
WHEN inval1d_company_ld OR negative_balance THEN ~~~
В одном обработчике можно также комбинировать имена пользовательских и системных исключений:
WHEN balance_toojow OR ZEROjrVDIDE OR D8MS_LDAP. INVALIO_SESSION
THEN ~ ~~~
А вот применить оператор AND в этой конструкции нельзя, поскольку одновременно не может быть инициировано несколько исключений.
Необработанные исключения
Исключение, инициированное в программе, но не обработанное в соответствующем разделе текущего или родительского блока PL/SQL, называется, как вы уже знаете, необработанным. PL/SQL возвращает сообщение об ошибке, вызвавшей необработанное исключение, в ту среду, где была запущена данная программа. Эта среда (ею может быть SQL*Plus, Oracle Forms или программа на языке Java) производит сообразные с ситуацией действия. В частности, SQL*Plus осуществляет откат (ROLLBACK) всех DML-инструкций, выполненных в родительском блоке.
Одним из важнейших моментов, связанных с проектированием архитектуры приложения, ядляется вопрос о том, разрешается ли в нем использовать необработанные исключения. Такие исключения различными средами обрабатываются no-разному)и не всегда это делается корректно. Если ваша программа PL/SQL вызывается не из PL/SQL-среды, в ее «самом внешнем» блоке можно запротрам- мировать следующие действия:
О перехват любых исключений, которые могут быть переданы в хост-среду;
О запись информации об ошибке в журнал, с тем чтобы впоследствии ее мог проанализировать разработчик;
О возврат кода состояния, описания и другой информации, необходимой хост- среде для выбора оптимального варианта действий.
Использование функций SQLCODE и SQLERRM в обработчиках исключений
Как вы уже знаете, предложение WHEN OTHERS используется для перехвата исключений, не указанных в предложениях WHEN. Однако в этом обработчике вам тоже нужна информация о том, какая именно ошибка произошла. Для ее получения можно воспользоваться функцией SQLCQDE, возвращающей номер текущей ошибки (значение 0 указывает, что в стеке ошибок нет ни одной ошибки). Еще одна полезная функция, SQLERRM, возвращает поясняющее сообщение для текущей или Для указанной разработчиком ошибки.
Таким образом, механизм обработки конкретных исключений без использования директивы EXCEPTION_INIT включает в себя предложение WHEN OTHERS и вызов функции SQLCODE. Рассмотрим пример, в котором перехватываются два исключения, инициированные ошибками -2292 и -2291, и выполняется их обработка: PROCEDURE delete company (company id in IN NUMBER) IS-~ ■
begin
DELETE FROM company WHERE companyj d - company_1d_i n:
EXCEPTION
WHEN OTHERS
THEN
Анонимный блок внутри оСраСотчика исключения позволяет
объявить локальные переменные для хранения информации об ошибке
*/
DECLARE
error_code NUMBER SQLCODE:
/* Максимальная длина строки, возвращаемой функцией SQLERRM */
error_msg VARCHAR2 (512) :- SQLERRM:'
BEGIN
IF error_code - -2292
THEN "
/* Имеются дочерние записи. Удалим их тоже! */
DELETE FRDM empl оуее WHERE companyjd - companyjdjn;
/* Теперь удалим родительскую запись */
DELETE FROM company WHERE company_1d - companyjdjn;
ELSIF error_code - -2291
THEN ~
/* Ключ родительской записи не найден */
DBMS_OJTPJT.PUT_LINE,Неправульнь麻 идентификатор компании:1
" I|Т0 CHAR (companyjd In)):
ELSE_~ "
/* Это что-то вроде WHEN OTHERS внутри WHEN OTHERS! */
DBMS_OUTPUT.PUT_LINE С1 Ошибка при удалении информации о компании:
--11 error msg);
END IF;-
END: -- Конец анонимного блока
END delete_cwnpany:
Продолжение работы после инициирования исключения
Когда в блоке PL/SQL инициируется исключение, нормальная работа программы прерывается и управление передается в раздел исключений. Однако и после обработки исключения управление не будет возвращено в тот блок, в котором оно было инициировано. А как же быть в тех случаях, когда необходимо обработать исключение и продолжить работу, начиная с того места, где одно произошло?
Предположим, нам нужно написать процедуру, которая выполняет последовательность DML-инструкций по отношению к разным таблицам (удаляет данные из одной таблицы, обновляет другую, добавляет строки в третью). Первая версия этой процедуры может быть примерно такой:
PROCEDURE change data IS
BEGIN"
DELETE FROM employee WHERE ...:
UPDATE company SET ...:
INSERT INTO company_h1Story SELECT * FROM company WHERE
END:"
Данная процедура содержит все необходимые DML-инструкции. И хотя они выполняются последовательно, на самом деле логически не зависят друг от друга. А нам требуется, чтобы в случае неудачной попытки удалить записи выполнялись инструкции UPDATE и INSERT.
При использовании приведенной версии процедуры change_data не гарантируется, что будет предпринята попытка выполнить каждую из трех инструкций. Если при обработке инструкции DELETE инициируется исключение, выполнение всей программы прекращается и управление передается в раздел исключений, если таковой имеется. Оставшиеся SQL-инструкции вообще не выполняются.
Как же сделать так, чтобы выполнение программы продолжалось во время инициирования и обработки исключений. Одним из вариантов решения может быть размещение инструкции DELETE в собственном блоке. Рассмотрим такую версию программы change_data:
PROCEDURE change data IS
BEGIN"
BEGIN
DELETE FROM employee WHERE
EXCEPTION
WHEN OTHERS THEN NULL;
END:
BEGIN
UPDATE company SET ...:
EXCEPTION
WHEN OTHERS THEN NULL:
END:
BEGIN
INSERT INTO company history SELECT * FROM company WHERE …;
EXCEPTION-
WHEN OTHERS THEN NULL;
END;
END;
В этой программе! если при выполнении инструкции DELETE инициируется исключение, управление немедленно должно быть передано в раздел обработки исключений. Но в какой? Поскольку инструкция DELETE находится теперь в отдельном блоке, у нее может быть собственный раздел исключений. Предложение WHEN OTHERS мгновенно обрабатывает эту ошибку. Затем управление передается из блока инструкции DELETE процедуре change^data.
Далее выполнение рассматриваемого родительского блока продолжается, обрабатывается очередная SQL-инструкция процедуры. Мы входим в следующий анонимный блок — иа этот раз в блок инструкхщи UPDATE. Если она выполняется неудачно, исключение перехватывает обработчик WHEN OTHERS раздела исключений блока инструкции UPDATE, после чего управление снова возвращается процедуре change_data и выполняется инструкция INSERT 一 тоже в собственном анонимном блоке. На рис. 6.2 описанный процесс продемонстрирован на примере двух последовательных инструкций DELETE.
Итак, подведем итог. Инициированное исключение всегда обрабатывается в текущем блоке, если, конечно, в нем имеется соответствующий обработчик. Вокруг любого оператора (или группы операторов) можно создать ^виртуальный блок»,
поместив перед ним ключевое слово BEGIN, а после него - раздел исключений и ключевое слово END. Таким образом можно указывать, выполнение каких частей кода приложения в случае инициирования исключения должно быть прекращено, а каких, наоборот, продолжено. Можно сказать, что при этом создаются своеобразные <буфера» для хранения исключений в виде анонимных блоков с критическим кодом.
Одно исключение — а выполнение После исключения управление передается программа прекращаетсяследующей инструкции
BEGINBEGIN
DELETE7—DELETE FROM tabi el:
FROM tabi el;Г EXCEPTION
DELETE7^aWHENOTHERSTHENNULL:
FROM table?END:
EXCEPTION
a …BEGIN
END:/—DELETE FROM table2
C EXCEPTION
OTHERS THEN NULL:
END:
Рис. 6.2. Выполнение двух последовательных инструкций DELETE с использованиемразных подходов к определению области действия их исключений
Данную стратегию можно развить, поместив критический код в отдельные процедуры и функции. В таких именованных блоках PL/SQL тоже могут быть собственные разделы исключений, защищающие программу от общего сбоя. Одним из важных преимуществ использования процедур и функций является то, что они скрывают от основной программы конструкцию BEGIN.. .EXCEPTION.. .END, загромождающую ее и затрудняющую восприятие основной логики. Такую, более структурированную, программу легче читать, понимать и многократно использовать в разных ситуациях.
Передача необработанного исключения
В программе инициированное исключение распространяется в соответствии с определенными правилами. Сначала PL/SQL шцет обработчик исключения в текущем блоке (анонимном блоке, процедуре или функции). Если такового нет, исключение передается в родительский блок. Затем PL/SQL пытается обработать исключение, инициировав его еще раз в родительском блоке. И так в каждом внешнем по отношению к другому блоке до тех пор, пока все они не будут исчерпаны (рис. 6.3). После этого PL/SQL возвращает необработанное исключение в среду приложения, из которого был выполнен <самый внешний» блок PL/SQL. И только теперь исключение сможет остановить выполнение хост-программы.
Рис. 6.3. Передача исключений во вложенных блоках
Потеря информации об исключении
Структура процесса обработки локальных, определяемых программистом, исключении в PL/SQL такова, что можно легко потерять информацию об исключении (то есть о том, какая именно произошла ошибка). Проиллюстрируем это на примере. Мы объявили исключение
BEGIN
«1 ocal_block»
DECLARE"
case_is_not_made EXCEPTION;
BEGIN
END local_block;
При этом мы забыли включить в данный блок раздел обработки исключений. Область действия исключения case_1s_not_made ограничена блоком local_block. Если указанное исключение не обрабатывается в данном блоке, оно передается в родительский, где нет никакой информации о нем. Мы знаем только то, что произошла ошибка, а какая именно — неизвестно. Объясняется это тем, что у всех пользовательских исключений один и тот же номер ошибки, 1, и одно и то же сообщение, «User-defined error» (ошибка, определяемая пользователем). Как видите, не слишком информативно. Уникально только имя исключения, но вне блока local_block оно не видимо. Следовательно, используя локально объявленное (и инициированное) исключение, его обработчик необходимо всегда добавлять в тот же блок, в котором оно объявлено, и обрабатывать это исключение исходя из его имени.
Примеры передачи исключения
Теперь давайте рассмотрим несколько гфимеров передачи исключений через внешние блоки. На рис. 6,4 показано, как исключение too_many_faults, инициированное во внутреннем блоке, обрабатывается в следующем блоке, внешнем. «Самый внутренний» блок (на рисунке — вложенный блок 2) содержит раздел исключений, так что PL/SQL сначала проверяет, обрабатывается ли в этом разделе инициированное исключение too_many_faults. А поскольку оно не обрабатывается, PL/SQL закрывает данный блок и инициирует исключение too_many_fau1ts во внешнем блоке, обозначенном на рисунке как вложенный блок 1. (Исполняемые операторы, расположенные после вложенного блока 2, не выполняются.) Затем просматривается раздел исключений этого блока с целью поиска обработчика исключения too_many_ faults, который обрабатывает его и передает управление процедуре Mst_rny_faults.
Обратите внимание, что если исключение NO_DATA_FOUND будет инициировано в «самом внутреннем» блоке, то оно будет обработано в разделе исключений этого же блока. Затем управление будет передано во вложенный блок 1 и будут выполнены исполняемые операторы, расположенные после вложенного блока 2.
На рис. 6.5 мы видим пример обработки в «самом внешнем» блоке исключения, инициированного во внутреннем блоке. Внепший блок — это единственный блок, имеющий раздел исключений, поэтому, когда во вложенном блоке 2 инициируется исключение too_many_faults, PL/SQL прекращает выполнение этого блока и инициирует данное исключение в его родительском блоке, вложенном блоке 1. Но поскольку и у него нет раздела исключений, управление передается
《самому внешнему» блоку, лроцедуре lis^my^faults. В указанной процедуре имеется раздел исключений, поэтому PL/SQL проверяет его, находит обработчик исключения too_many_faul ts, вьшолняет имеющийся там код и возвращает управление программе, вызвавшей процедуру Ъ51_ту_ГаиН5.
PROCEDURE 11st_my_faults IS BEGIN
DECLAREtoo many_faults BEGIN "-…Исполняемые перед новымDECLAREtoo many_faults BEGIN "-…Исполняемые перед новымEXCEPTIONEXCEPTIONоператоры, расположенные блоком …операторы, расположенные блоком …•ЙЛОЖВЙИЫиблок 1•ЙЛОЖВЙИЫиблок 1
DECLARE
too many_faults BEGIN "-
…Исполняемые перед новым
DECLARE
too many_faults BEGIN "-
…Исполняемые перед новым
EXCEPTION
EXCEPTION
операторы, расположенные блоком …
операторы, расположенные блоком …
•ЙЛОЖВЙИЫи
блок 1
•ЙЛОЖВЙИЫи
блок 1
BEGIN*SELECT SUM (faults) INTO num_ faults FROM profileIF num_faults > 100THEN ~RAISE too many faults:END IF; ■~EXEPTION WHEN NO DATA FOUND THENEND:"-BEGIN*SELECT SUM (faults) INTO num_ faults FROM profileIF num_faults > 100THEN ~RAISE too many faults:END IF; ■~EXEPTION WHEN NO DATA FOUND THENEND:"-Впажвнный блок2Впажвнный блок2... Исполняемые операторы, расположенные после вложенного блока 2 ...... Исполняемые операторы, расположенные после вложенного блока 2 ...\EXEPTION'WHEN too_many faults THEN ...:end; 一 -END list_iny_faults:Рис 6.4. Передача исключения в первый вложенный блок\EXEPTION'WHEN too_many faults THEN ...:end; 一 -END list_iny_faults:Рис 6.4. Передача исключения в первый вложенный блок
BEGIN*
SELECT SUM (faults) INTO num_ faults FROM profile
IF num_faults > 100
THEN ~
RAISE too many faults:
END IF; ■~
EXEPTION WHEN NO DATA FOUND THEN
END:"-
BEGIN*
SELECT SUM (faults) INTO num_ faults FROM profile
IF num_faults > 100
THEN ~
RAISE too many faults:
END IF; ■~
EXEPTION WHEN NO DATA FOUND THEN
END:"-
Впажвнный блок2
Впажвнный блок2
... Исполняемые операторы, расположенные после вложенного блока 2 ...
... Исполняемые операторы, расположенные после вложенного блока 2 ...
\EXEPTION
'WHEN too_many faults THEN ...:
end; 一 -
END list_iny_faults:
Рис 6.4. Передача исключения в первый вложенный блок
\EXEPTION
'WHEN too_many faults THEN ...:
end; 一 -
END list_iny_faults:
Рис 6.4. Передача исключения в первый вложенный блок
PROCEDURE 11st_my_faultsIStoo many faults EXCEPTION: BEGIN "PROCEDURE 11st_my_faultsIStoo many faults EXCEPTION: BEGIN "Вложенный блок1Вложенный блок1Вложенный блок 2Вложенный блок 2
PROCEDURE 11st_my_faults
IS
too many faults EXCEPTION: BEGIN "
PROCEDURE 11st_my_faults
IS
too many faults EXCEPTION: BEGIN "
Вложенный блок1
Вложенный блок1
Вложенный блок 2
Вложенный блок 2
EXEPTION
WHEN too_many_faults THEN ...;
END llst^^aults:
Рис 6.5. Исключение, инициированное во вложенном блоке,обрабатывается в «самом внешнем» блоке
Использование стандартизированных программ обработки ошибок
Обязательным элементом любого созданного профессионалами приложения является надежная и согласованная схема обработки ошибок. Согласованность в этом вопросе важна как для пользователя, так и для разработчика. Если в случае возникновения опшбки пользователю предоставляется понятная, хорошо структурированная информация, он сможет более подробно рассказать об ошибке службе поддержки. К тому же работая с таким обстоятельным приложением, он будет увереннее себя чувствовать. Если приложение всегда обрабатывает и протоколирует ошибки определенным образом, программистам, занимающимся его поддержкой и сопровождением, будет легче их найти и устранить.
Все сказанное кажется убедительным и совершенно очевидным, не так ли? К сожалению, на практике, особенно в больших командах разработчиков, все происходит несколько иначе. Очень часто каждый разработчик идет своим путем, следуя личным принципам и приемам, записывая информацию в журналы собственной структуры и т. д. Одним словом, без стандартизации отладка и сопровождение приложений превращаются в кошмар. Рассмотрим пример типичного кода:
EXCEPTION
WHEN NO DATA FOUND
THEN,
v_msg :- 'Нет компании с данным идентификатором1 || TO_CHAR (v_ld):
v_err :- SQLCODE;
v_prog :- 'fixdebt1;
INSERT INTO errlog VALUES
(v_err. v msg, v_prog. SYSDATE. USER):
WHEN OTHERS '
THEN
v_err :- SQLCODE:
v』sg ;- SQLERRM;
v_prog :- 'fixdebt';
INSERT INTO errlog VALUES
'(v_err. vjnsg. v_prog, SYSDATE. USER);
RAISE; ——
На первый взгляд этот код может показаться абсолютно нормальным. Вот как можно пояснить то, что он делает. Если компания с данным идентификатором не найдена, получаем значение SQLCODE, задаем имя программы, сообщение и записываем строку с информаиией об ошибке в таблицу опшбок. Работа родительского блока продолжается, поскольку ошибка в данном случае не очень серьезна. Если происходит любая другая ошибка, получаем ее код и соответствующее сообщение, задаем имя программы и записываем строку с информацией об ошибке в таблицу ошибок, а затем передаем исключение в родительский блок, чтобы остановить его выполнение (поскольку неизвестно, насколько серьезна эта ошибка).
Так что же здесь не так? Чтобы подробно объяснить суть проблемы, достаточно взглянуть на код. В нем одинаково жестко закодированы все действия по обработке ошибок. В результате код, во-первых, получается большим, а во-вторых, его придется полностью переписывать в случае изменения схемы обработки ошибок. Обратите внимание еще и на тот факт, что информация об ошибке записывается
в таблицу базы данных. Это означает, что запись в журнале становится частью логической транзакции. И если потребуется выполнить откат транзакции, строки журнала ошибок будут утеряны.
Существует несколько способов избежать потери информации. В частности, можно записывать данные в файл, использовать автономные транзакции, что даст возможность помещать данные в журнал вне основной транзакции. Но все равно код в случае его изменения придется исправлять в сотнях разных программ.
А теперь посмотрите, как этот же раздел исключений можно переписать, используя стандартизированный пакет:
EXCEPTION
WHEN NO_DATA_FOUND
THEN
errpkg.record_and_continue (
SQLCODE, 'Нет компании с данным идентификатором' || TO_CHAR Cv_id)):
WHEN OTHERS~~
THEN
errpkg.record_and_stop;
END;
Такой пакет обработки ошибок скрывает все детали реализации процесса. Но достаточно просмотреть спецификацию пакета и станет ясно, какую из процедур обработки ошибок требуется использовать в каждом конкретном случае. Если нужно записать информацию об ошибке и продолжить работу родительского блока, вызываем программу record_and_conti nue, а если нужно записать информацию об ошибке и остановить работу родительского блока, то обращаемся к record_ and_stop. Мы не знаем, как эти программы записывают информацию об ошибке, как они останавливают работу родительского блока, то есть передают исключение, но для нас это и не важно. Главное, что все происходит так, как определено стандартами приложения. Теперь можно больше времени уделить разработке более интересных элементов приложения и не заниматься административной рутиной.
На узле O'Reilly имеется файл errpkg.pkgr в котором вы найдете прототип стандартизированного пакета для обработки ошибок. Правда, прежде чем использовать его в приложениях, вам нужно будет завершить его реализацию, что поможет составить ясное представление о том, как конструировать подобные утилиты. Можете также просмотреть пакет plvexc библиотеки PL/Vision, который включает свыше 60 пакетов, входящих в состав Quest Software PL/SQL Knowledge Expert. В указанном пакете имеется полная функционируюшая реализация универсального компонента обработки ошибок, предназначенного для многократного использования.
Часть III
Работа с данными в PL/SQL
Любая программа оперирует данными, причем значительная часть этих данных локальна, то есть определена в процедурах или функциях программы. В этой части книги описаны различные типы программных данных, которые используются в PL/SQJ., - числа, строки, записи и коллекции. Вы также узнаете о новых типах данных, введенных в Orade9i (INTERVAL, TIMESTAMP, XMLType), Кроме того, в главах 7-12 рассматриваются встроенные функции Oracle, предназначенные для управления данными и их преобразования.
□ Глава □ Глава П Глава□ Глава □ Глава П ГлаваРабота с программными данными
□ Глава □ Глава П Глава
□ Глава □ Глава П Глава
Строки
Числа
Глава10. Дата и время
Глава 11. Записи и коллекции
口 Глава 12. Другие типы данных
77Работа с программнымиРабота с программными
7
7
Работа с программными
Работа с программными
данными
А Именование программных данных '
А Обзор типов данных PL/SQL
А Объявление программных данных
А Подтипы данных, определяемые программистом
А Преобразование типов данных
Вероятно, в любом написанном вами блоке программы PL/SQL вы будете определять данные и выполнять с ними те или иные операции. Программные данные представляют собой структуры, которые существуют только в рамках сеанса PL/SQL и не хранятся в базе данных. Физически они располагаются в программной глобальной области - Program Global Area (PGA). К программным данным относятся:
О переменные и константы — значения переменных могут изменяться во время выполнения программы, а значения констант статичны, то есть устанавливаются при их объявлении и больше не изменяются;
О скалярные и составные данные — скалярные данные состоят из одного значения (числового или строкового), а составные данные имеют несколько значений, как например запись, коллекция или экземпляр объектного типа;
О контейнеры - могут содержать информацию, полученную из базы данных или иного источника.
В настоящей главе мы расскажем о том, как объявляются данные в программе, и опишем правила, которым нужно следовать при выборе имен для этих данных. Затем рассмотрим все типы данных, поддерживаемые PL/SQL, и обсудим концепцию преобразования типов. Завершается глава рекомендациями о том, как лучше работать с программными данными. Конкретные типы данных подробно описаны в остальных главах этой части.
Именование программных данных
Перед использованием переменную или константу нужно объявить, присвоив ей имя и определив тип. Задавая имена структур данных, следуйте приведенным ниже правилам PL/SQL (они касаются также имен объектов базы данных, например таблиц или столбцов):
О имя может иметь длину не более 30 символов;
О оно должно начинаться с буквы и может состоять из букв, цифр, а также символов <$>, <#> и «」;
О имена нечувствительны к регистру (если только не заключены в двойные кавычки).
Согласно этим правилам допустимыми являются имена:
l_tot account
fTrst_12亠ears
totaF#_of_trees
salaryjnj
Следующие три имени допустимы, однако считаются в PL/SQL идентичными из-за нечувствительности языка к регистру имен:
dlapers_changed weekl
DIAPERSJHANGED^WEEKl
D1apers_Changed2weekl
Приведенные ниже имена недопустимы:
lst_account ― Имя начинается с цифры, а не буквы
favoriteJce_cream_f1avours_that_dont_conta1n_nuts -- Слишком длинное иия
ema11_address®bus1ness_lос " -- Имя содержит недопустимый символ @
Из этих правил есть несколько исключений. Если имя в объявлении заключить в двойные кавычки, можно не придерживаться данных правил^ кроме одного: длина имени не должна превышать 30 символов. Например】 допустимыми являются следующие объявления:
DECLARE
■ "truTyJower__c3se" INTEGER:
"” DATE: -- Да-аа, это имя состоит из пяти пробелов!
м123 go!" VARCHAR2C10);
BEGIN "
"123 до!" 'Steven':
END:-'
Когда вы указываете в исполняемом разделе эти имена, их всегда нужно заключать в двойные кавычки, иначе код не будет компилироваться без ошибок.
При создании объектов базы данных в именах иногда используют двойные кавычки, поскольку это позволяет сохранить в идентификаторах исходный регистр. Например, если в программе создать таблицу "docs"; она будет называться именно docs, а не DOCS. Однако в любых других случаях лучше избегать применения двойных кавычек в программах PL/SQL.
Еще одно исключение из правил именования данных связано с именами объектов Java (подробнее об этом рассказывается в главе 22).
Выбирая имена для переменных и констант, придерживайтесь следующих рекомендаций.
О Убедитесь, что имя соответстаует назначению объекта и по нему можно быстро определить это назначение. Попытайтесь сформулировать, что представляет собой значение переменной) это поможет выбрать для нее более точное имя. Например, если в переменной хранится общее количество звонков по поводу остывшего кофе, для нее подойдет имя total_cal 1 s_on_cold_coffe или tota\cold_cans. Имена totcoffe и t_#_calls_lwcoff неудачны, поскольку составлены из сокращений, которые будут непонятны как постороннему человеку, так и вам через некоторое время.
О Выработайте определенные соглашения об именовании объектов и следуйте нм. Соглашения обычно предполагают использование префиксов и суффиксов, указывающих тип и назначение переменных. Например, имена всех локальных переменных должны иметь префикс <1_>, а глобальных переменных, определенных в пакетах, — префикс <g ». Для записей применяется суффикс <_rt> и т. д. С полным набором соглашений об именах можно ознакомиться на узле O'Reilly (http://oracle.orellly.com), щелкнув сначала на ссылке Oracle PL/SQL Best Practices, а затем на ссылке Examples.
Обзор типов данных PL/SQL
При объявлении переменной или константы необходимо определить ее тип данных. (PL/SQL, за некоторыми исключениями, сильнотипизированный язык. Ниже поясняется, что это означает.) В PL/SQL определен широкий набор скалярных и составных типов данных, кроме того, начиная с версии Огас1е8 можно создавать пользовательские (абстрактные) типы данных.
В PL/SQL практически все предопределенные типы данных объявлены в пакете STANDARD. Например, следующие операторы объявляют тип данных BOOLEAN и два числовых типа данных:
create or repl асе package STANDARD Is
type BOOLEAN is CFALSE. TRUE);
type NUMBER is NUMBER_BASF;
subtype INTEGER Is NUMBFRC38):
PL/SQL поддерживает распространенный «джентльменский набор» типов данных, а также ряд других типов. В этом разделе приводится краткий обзор различных предопределенных типов данных, их более подробное описание можно найти в главах 8-12, 14 и 21.
ЧТО ОЗНАЧАЕТ СИЛЬНОТИПИЗИРОВАННЫЙ
<Сильнотипизироваыный язык программирования — это язык, в котором каждый тип данных (целочисленный, символьный и т. д.) предопределен как часть языка и должен назначаться всем константам и вем переменным,
используемым в программе. Каждая операция допускается только для конкретных типов данных. Компилятор требует обязательного определения типов данных и соответствующего их применения. Преимуществом строгой типизации является то, что программист должен соблюдать определенные правила, и тем самым гарантируется согласованность результатов. Ее недостаток состоит в том, что программист не может использовать типы данных, которых нет в стандартном наборе типов языка, и ограничиваются возможности творческого применения типов данных» (Это определение приводится на www.whatls.com.)
Символьные типы данных
PL/SQL поддерживает строки фиксированной и переменной длины, состоящие как из традиционных символов, так и из символов Unicode. К строкам первого вида относятся строки типов CHAR и NCHAR, а к строкам второго вида 一 типов VAR- CHAR2 и NVARCHAR2. Объявление строки переменной длины, которая может содержать до 2000 символов, имеет следующий вид:
DECLARE
Lace 1dent_descrlption VARCHAR2C2000):
Правила работы с символьными данными, примеры их применения и встроенные функции, предназначенные для операций со строками в PL/SQL, описаны в главе 8.
В базе данных до версии Огас1е8 поддерживаются также большие строки (тип данных LONG), а в версии Oracle8 и выше — большие объекты (LOB( Large Objects). Эти типы позволяют хранить и обрабатывать очень большие объемы данных (LOB может содержать до 4 Гбайт информации). Символьные типы данных LOB называются CLOB (Character Large Object — большой символьный объект) и NCLOB (National Language Support Character Large Object — большой символьный объект с поддержкой национальных языков).
СОВЕТ*
Существует множество правил, ограничивающих использование типа данных LONG. Мы не рекомендуем применять его в версиях начиная с Огвс1е8 и выше.
В главе 12 рассматриваются правила работы с большими объектами, приводится много примеров их применения, а также описываются встроенные функции и пакет DBMS LOB, предназначенные для обработки таких объектов средствами PL/SQL. ~
Числовые типы данных
В PL/SQL имеются как действительные, так и целочисленные типы данных. Все они основаны на одном базовом типе NUMBER. Тип данных INTEGER поддерживает лишь целые числа, его разновидностями являются типы NATURAL и POSITIVE. Ниже приведен пример объявления числовой переменной, которая может иметь только три значения (1, -1 и NULL).
DECLARE
l_d1recti on SIGNTYPE:
Правила работы с числовыми данными, примеры их применения и встроенные функции, щзедназначешше для операций с числами в PL/SQL, описываются в главе 9.
Типы данных DATE TIMESTAMP и INTERVAL
До появления версии Oracle9i мир дат в Oracle был ограничен типом данных DATE, который использовался для хранения даты и времени. В Oracle9t были введены два новых типа данных, INTERVAL и TIMESTAMP, значительно расширившие возможности разработчиков программ на PL/SQL в отношении операций с датами и временем, а также вычисления и хранения временных интервалов. Их использование продемонстрировано в функции, вычисляющей возраст человека:
CREATE OR REPLACE FUNCTION age (dob In IN DATE)
RETURN INTERVAL YEAR TO MONTH
IS
retval INTERVAL YEAR TO MONTH;
BEGIN
RETURN (SYSDATE - dobjn) YEAR TO MONTH;
END:-
Правилам работы co значениями даты, времени и интервалами времени посвящена глава 10)в ней также приведено много примеров и описаны соответствующие встроенные функции.
Тип данных BOOLEAN
PL/SQL поддерживает логический (булев) тип данных - BOOLEAN. Переменные этого типа могут принимать одно из трех значений - TRUE, FALSE или NULL - и позволяют писать понятный, легко читающийся код даже в случаях, когда он содержит очень сложные логические выражения. Пример объявления логической переменной с присваиванием ей значения по умолчанию приведен ниже:
DECLARE
1ме11glЫe_for_d1scount BOOLEAN :■
…custorerjn.balance > min_balance AND customerjn. pref_type - *MOST FAVDRED' AND customerjn. di *二电1 Igibillty:
Работа с логическими данными и примеры их использования описаны в главе 12.
Двоичные данные
Oracle поддерживает несколько форм двоичных данных, которые являются неструктурированными, не интерпретируются и не обрабатываются Oracle. Для этой цели служат типы RAW, LONG RAW, 0FILE и BIOS. Тип BFILE используется для двоичных данных, которые хранятся в виде файлов в операционной системе вне базы данных. Тип RAW — это тип данных переменной длины, при операциях с которыми Oracle не выполняет преобразование символов. В остальном он подобен символьному типу VARCHAR2.
В главе 12 рассматриваются правила работы с двоичными данными, приводится много примеров, описываются встроенные функции и пакет DBMS_LOB, пред- вазваченныедля операций с данными типа BFILE н другими двоичными данными в PL/SQL.
Типы данных ROWID и UROWID ■
Oracle поддерживает два типа данных, предназначенных для представления адреса строки в таблице. Тип RQVJID — это уникальный адрес строки таблицы, а тип UROUID — логическая позиция строки в индекс-таблице (Index-Organized Table, ЮТ).
Правила работы с типами данных ROWID и UROWID описываются в главе 12.
Тип данных REF CURSOR
Тип данных REF CURSOR позволяет объявлять курсорные переменные, которые могут использоваться со статическими и динамическими SQL-инструкциями, обеспечивая большую гибкость в реализации самых разнообразных требований. Переменные этого типа могут быть заданы строго либо не строго. Во втором случае REF CURSOR представляет собой один из немногих слаботипиэированных типов данных, поддерживаемых PL/SQL
Ниже приведен пример строгого объявления типа данных REF CURSOR (связывание переменной этого курсора с конкретной записью с помощью атрибута XROWTYPE):
DECLARE
TYPE baak_data_t IS REF CURSOR RETURN baokXROWTYPE;
baok^curs^ar 6gk_datU:;
Далее следуют два нестрогих объявления переменных типа REF CURSOR:
DECLARE■
TYPE boak_data_t IS REF CURSOR;
book^curs^var baak_data_t;
Типу данцых REF CURSOR и курсорным переменным посвящева глава 14.
Типы данных для поддержки Интернета
В Oracle9i появилась встроенная поддержка технологий, связанных с Интернетом, в частности XML (Extensible Markup Language) и URI (Universal Resource Identifier). В Oracle имеются типы для работы с данными XML и URI, а также специальный класс идентификаторов URI (DBUri-REF), который используется Для доступа к базе данных. Кроме того, в Oracle появился новый набор типов данных, обеспечивающий хранение внепших и внутренних URI н доступ к ним из базы данных.
Тип XMLType позволяет запрашивать из базы данных и сохранять в ней XML- данные. Это осуществляется с помощью таких функций, как SYS_XMLGEN из пакета DBMS^XMLGEN. Кроме того, используя язык XPath и встроенные операторы языка XML, вы можете выполнять поиск данных.
Типы данных для работы с URI, включая URIType и HttpURIT^pe, входят в иерархию объектных типов и могут использоваться для хранения URL внешних web- страниц и файлов, а также для создания ссылок на информацию, хранящуюся в базе данных.
В главе 12 рассматриваются правила работы с типом данных XMLType и различными URI-типами, приводится примеры их использования и описываются встроенные функции, предназначенные для операций с ними.
Типы данных «апу»
Конечно, большую часть времени программисты пишут код, который выполняет узко специализированные задачи. Однако иногда им приходится создавать более универсальные программы. Типы данных <апу» предназначены именно для таких ситуаций.
Эта новая группа типов данных Oracle, появившаяся в Oracle9t, значительно отличается от остальных. Типы данных «апу* позволяют динамически инкапсулировать описания типов, экземпляры данных и наборы экземпляров данных любого другого типа, поддерживаемого языком SQL, и получать к ним доступ. С помощью объектных типов <апу» и их методов можно, к примеру определить тип данных, которые хранятся в конкретной вложенной таблице, не обращаясь к существующему определению этой таблицы. В группу типов данных «апу* входят АпуТуре, AnyData и AnyOataSet.
Правила работы с типами данных «апу* и несколько полезных примеров описаны в главе 12.
Типы данных, определяемые пользователем
С тех пор как в Огас1е8 появились объектные типы данных, на основе всевозможных встроенных и пользовательских типов можно создавать типы данных произвольной сложности, которые с большой точностью отражают структуру и поведение данных различных систем.
Эта возможность подробно рассматривается в главе 21, там же рассказывается о том, как использовать добавленную в Огас1е9г поддержку наследования объектных типов данных.
Объявление программных данных
Прежде чем обращаться к переменной или константе, ее нужно объявить. (Единственным исключением из этого правила являются индексные переменные циклов FOR.) Все объявления должны размещаться в разделе объявлений анонимного блока, процедуры, функции, триггера, тела объектного типа или тела пакета. (Подробнее о структуре блока PL/SQL и его раздела объявлений рассказывается в главе 3.)
В PL/SQL можно объявлять разные типы и структуры данных, включая переменные, константы, типы ТУРЕ (например, коллекцию или запись) и исключения. В этой главе описано, как объявляются переменные и константы. (Об операторе TYPE вы узнаете в главе 11г а о том, как объявлять исключения - в главе 6.)
Объявление переменной
Когда вы объявляете переменную, PL/SQL выделяет память для хранения ее значения и присваивает ей имя, указывая которое вы можете это значение извлекать и изменять. В объявлении также задается тип данных переменной, он используется для проверки присваиваемых ей значений. Объявление переменной или константы выполняется следующим образом:
имя типуганных [NOT NULL] [_задаиие_зндчения_по_умолчанию'];
Здесь имя - это имя переменной или константы, тип_данных ~ это тип или подтип данных, определяющий, данные какого типа можно присваивать переменной. При желании можно включить в объявление выражение NOT NULL, означающее, что если переменной присвоить значение NULL, инициируется исключение. Задавать значение по умолчанию обязательно только при объявлении констант, так как это обеспечивает их инициализацию.
В следующем примере показано, как объявлять переменные разных типов:
DECLARE
-Простое объявление числовой переменной
1_total_count NUMBER;
--Объявление числа, округляемого до ближайших сотых (центов)
l_dollar_amaunt NUM8ER (10,2):
—Переменная, которой присваивается текущее значение даты
-и которая не может иметь значение NULL
l_right_now DATE NOT NULL DEFAULT SYSOATE;
Задание значения по умолчанию с помощью оператора присваивания l_favor1te__flavour VARCHAR2(100) :- '8м любите кофе?';
-Двухэтапный процесс объявления ассоциативного массива.
Сначала тип таблицы:
TYPE list_of_books_t IS TABLE OF books^ROWTYPE INDEX BY BINARY_INTEGER:
-А затем конкретный список, с которым мы будем работать в данной блоке:
orellly_oracle_books 11st_of_books_t:
Чтобы при объявлении переменной или константе присвоить значение по умолчанию, можно применить ключевое слово DEFAULT или оператор присваивания — в данном случае они эквивалентны и взаимозаменяемы. Мы предпочитаем для констант использовать оператор присваивания, а для начальных значений переменных -ключевое слово DEFAULT. При объявлении константы задается не значение по умолчанию, а значение, которое не может быть изменено впоследствии, поэтому DEFAULT кажется нам неуместным.
Объявление константы
Между объявлениями переменной и константы существуют два различия; объявление константы содержит ключевое слово CONSTANT, и в нем обязательно задается ее значение, которое не может быть изменено впоследствии:
и«я CONSTANT тмп』анн&х [NOT NULL] := | DEFAULT значение;
Далее следует несколько примеров объявлений констант:
DECLARE
-Текущий год; в течение сеанса он не будет изменяться
1 curr^ear CONSTANT PLS INTEGER 尸
一 TQ_NUMBER (TO_CHAR (SYSOATE, 'YYYY'));
-Использование ключевого слова DEFAULT.
^author CONSTANT VARCHAR2(100) DEFAULT 'Bill Pribyll':
-Объявление сложного типа данных как константы Константы могут быть не только скалярными!
Steven CONSTANT person_ot :-
person_ot ('HUMAN', 'Steven Feurstein'. 175. '09-23-1958');
ПРИМЕЧАНИЕ
Неименованная константа — это литеральное значение, подобное 2 или Bobby McGee. Литерал не обладает именем, но имеет тип данных, который не объявляется, а определяется непосредственно на основе значения литерала.
Сейчас мы рассмотрим особенности объявления переменных. В большинстве случаев сказанное ниже относится и к константам.
Объявления с ограничениями
Объявление с ограничениями — это объявление с указанием числа, задающего допустимые значения переменной. Тип данных без ограничений — это тип данных, для которого такие ограничения не заданы.
В качестве примера рассмотрим тип данных NUMBER. Для хранения переменной подобного типа выделяются 38 разрядов и соответствующий объем памяти. Если такая точность не требуется, переменную можно объявить с ограничением: itty_bittyj NUMBER(l);
1 arge_but_constrai ned_# NUMBER(20.5);
Переменной, объявленной с ограничениями, нужно меньше памяти, чем переменной, объявленной без ограничений, как в следующем примере:
no_lini1ts_here NUMBER:
NOT NULL
Если переменной присваивается значение по умолчанию, можно также указать, что оно всегда должно быть определено, то есть добавить в объявление выражение NOT NULL. Например, следующее объявление инициализирует переменную сот- рапу_пагпе значением PCS R US и обеспечивает ее неравенство NULL:
company_name VARCHAR2C60) NOT NULL DEFAULT 'PCS R US';
Если код включает приведенную ниже строку, при ее выполнении будет активизировано исключение VALUE_ERROR:
company_name :- NULL.;
В следующем фрагменте кода переменная объявлена неправильно, поскольку отсутствует ее начальное значение, и после компиляции вы получите сообщение об ошибке:
cornpany_name VARCHAR2(6D) NOT NULL; -- NOT NULL требует задания начального значения!
Объявления с привязкой
Очень часто вы будете явно задавать типа данных:
】coinpany_name VARCHAR2(100):
Помимо этого, в Oracle существует другой метод объявления переменных, называемый объявлением с привязкой (anchored declaration). Он обладает рядом преимуществ в тех случаях, когда переменной необходимо присвоить значение из другого источника данных, например из строки таблицы.
Привязывая переменную, вы устанавливаете ее тип данных на основе типа уже определенной структуры данных. Таковой может являться другая перемен- 'ная PL/SQ.L, предопределенный тип или подтип (TYPE или SUBTYPE), таблица базы данных либо конкретный столбец таблицы. В PL/SQL существуют два вида привязки.
О Скалярная привязка. С помощью атрибута ZTYPE переменная определяется на основе типа столбца таблицы или другой скалярной переменной PL/SQL.
О Привязка к записи. Используя атрибут ZROWTYPE, можно определить переменную на основе таблицы или предопределенного явного курсора PL/SQL.
Синтаксис объявления переменной с привязкой выглядит следующим образом:
ипя_перемениой тнпутрибута ШРЕ [необязательиое_зддзнне_зндчения_}1оумолчанию^: имя^еременной имя^таблииы | имя__доссраХК(МУРЕ [необяздтельное_зддзние_знзчения_по^молчанию^: Здесь имя_переменной — это имя объявляемой переменной, тип__атрибутд — это либо имя ранее объявленной переменной PL/SQL, либо спецификация столбца таблицы в формате -таблица.столбец.
Наличие ссылок привязки в коде допускается компилятором и не приводит к увеличению времени выполнения. Кроме того, привязка устанавливает зависимость между программным кодом и элементом, к которому выполняется привязка (таблицей, курсором или пакетом, содержащим переменную, на которую есть ссылка в объявлении). Это означает, что при изменении данного элемента привязанный к нему программный код помечается как INVALID. При повторной компиляции привязка выполняется заново, и таким образом код согласуется с данным элементом.
На рис. 7.1 показано, как тип данных определяется на основе столбца таблицы базы данных и переменной PL/SQL.
Ниже приведен пример привязки переменной к столбцу таблицы базы данных:
LcompanyJ d company. company_1 dHYPE:
Аналогичным образом выполняется привязка к переменной PL/SQL; как правило это делается для того, чтобы избежать нескольких объявлений одного и того же жестко закодированного типа данных. В подобном случае в пакете создают переменную и затем в программах ссылаются на нее с помощью атрибута ^ТУРЕ.
(Можно также создавать в пакете подтипы SUBTYPE; о них рассказывается далее в этой главе.)
emp_id[emp.empnoZTYPE: |emp_id[emp.empnoZTYPE: |new_emp|emp_idXTYPE:BEGIN 一new_emp|emp_idXTYPE:BEGIN 一Рис. 7.1. Использование атрибута %7YPEРис. 7.1. Использование атрибута %7YPEешрпоNUMBER(S)епатеVARCHAR2(30)hiredateDATEешрпоNUMBER(S)епатеVARCHAR2(30)hiredateDATEСловарь данныхТаблица ЕМРСловарь данныхТаблица ЕМРDECLARE
emp_id[emp.empnoZTYPE: |
emp_id[emp.empnoZTYPE: |
new_emp|emp_idXTYPE:
BEGIN 一
new_emp|emp_idXTYPE:
BEGIN 一
Рис. 7.1. Использование атрибута %7YPE
Рис. 7.1. Использование атрибута %7YPE
ешрпо
NUMBER(S)
епате
VARCHAR2(30)
hiredate
DATE
ешрпо
NUMBER(S)
епате
VARCHAR2(30)
hiredate
DATE
Словарь данных
Таблица ЕМР
Словарь данных
Таблица ЕМР
В следующем примере приведен фрагмент кода пакета, предназначенного для облегчения работы с Oracle Advanced Queuing (AQ):
/* Файл в web: aq.pkg */
CREATE OR REPLACE PACKAGE aq
IS
/* Стандартные типы данных, используемые в Oracle AQ. */'
v_msg1d RAW (16);.
SUBTYPE msgid_type IS v_msgid?TVPE:
v_name VARCHAR2 (49): ~
SUBTYPE nanie_type IS v_natneXTYPE;.
END aq:
В пакете aq тип данных для хранения идентификаторов сообщений определен как RAW (163. Вместо того чтобы помнить эту спецификацию и несколько раз жестко кодировать ее в приложении, можно объявить переменную для идентификатора сообщения следующим образом:
DECLARE'
rny_msg_id aq.nisg1d_type;
begin" 一 一
Если Oracle изменит тип данных для идентификаторов сообщений, достаточно изменить определение подтипа (SUBTYPE) в пакете aq, и после перекомпиляции все объявления в программах обновятся автоматически.
Наличие объявлений с привязкой свидетельствует о том, что PL/SQL является не просто процедурным языком программирования, а разработан специально как расширение языка Oracle SQL. Корпорация Oracle приложила большие усилия для того, чтобы интегрировать программные конструкции PL/SQL с базами данных, для доступа к которым используется SQL.
Значительным преимуществом объявлений с привязкой является то, что они позволяют писать очень гибкие приложения, которые автоматически настраиваются на последующие изменения структуры данных.
Привязка к курсорам и таблицам
Ранее мы уже продемонстрировали примеры того, как осуществляются объявления переменной с привязкой к столбцу базы данных и другой переменной PL/SQL. Теперь проследим, как используется атрибут привязки ^ROWTYPE.
Допустим, нам нужно запросить одну строку из таблицы books. Вместо того чтобы с помощью атрибута ШРЕ объявлять для каждого столбца таблицы отдельную переменную, можно воспользоваться атрибутом XROWTYPE:
DECLARE
l_book booksXROWTYPE;
BEGIN
SELECT * INTO l_book
FROM books-
WHERE isbn н '1-56592-335-9';
process_book (l_book);
END; ~"
Теперь предположим, что мы хотим извлечь из таблицы book только имя автора и название интересующей нас книги. В этом случае сначала явно определим курсор, а затем на его основе объявим переменную:
DECLARE
CURSOR book_cur IS
SELECT author, title FROM books
WHERE Isbn - 11-56592-335-9';
l_book book_curXROWTYPE;
BEGIN "-
OPEN book cur:
FETCH book_cur INTO l_book;
process_book (l_book);
END;
Ниже приведен пример неявного использования атрибута ZROWTYPE в объявлении записи book^rec цикла FOR:
BEGIN
FOR book_rec IN (SELECT * FROM books)
LOOP
process_book (book_rec):
END LOOP; ~~
END:
Преимущества объявлений с привязкой
Во всех объявлениях, которые приведены в предыдущих главах книги, тип переменной (символьный, числовой, логический, дата) задается явно. В каждом из них содержатся непосредственное указание типа данных и, как правило, ограничение, налагаемое на значение этого типа. Если программа содержит такие объявления, то считается, что данные в ней кодируются жестко. Это распространенный подход к объявлению переменных, но иногда он может вызвать проблемы.
В первую очередь они касаются синхронизации со столбцами таблицы базы Данных. Ведь в программе PL/SQL переменная часто «представляет» информацию из таблицы базы данных. Если явно объявить переменную, а затем изменить
структуру таблицы, данные которой содержит переменная, программа может работать неправильно.
Вторая проблема касается стандартизации локальных переменных. Представьте, что переменная PL/SQL хранит вычисляемые значения, которые используются в разных местах приложения. Какими могут быть последствия того, что в нескольких местах кода жестко объявлена переменная одного типа?
Синхронизация со столбцами таблицы базы данных
База данных содержит информацию, которая считывается и используется в приложении. Операции с этой информацией выполняются с помощью как SQL, так и PL/SQL. При этом программы PL/SQL часто считывают информацию из базы данных, присваивают результат локальным переменным и после соответствующей обработки записывают информацию из переменных обратно в базу данных.
Предположим, что таблица с данными。компаниях содержит столбец NAME с типом данных VARCHAR2(60). Локальную переменную для хранения значений этого столбца можно объявить следующим образом:
DECLARE
cname VARCHAR2C60):
Теперь обратимся к приложению, в котором используется информация о компаниях. В нем могут быть десятки различных процедур и отчетов, содержащих одно и то же объявление PL/SQL VARCHAR2(60). И все это прекрасно работает, пока не изменятся биэнес-требования или администратора базы данных не охватит жажда перемен. Тогда ему ничего не стоит изменить определение типа столбца NAME таблицы на VARCHAR2C100), чтобы в него помещались более длинные названия компаний. В результате в таблице могут оказаться такие данные, что при считывании их в переменную cname будет инициироваться исключение YALUE.ERROR.
После изменения типа данных столбца все программы, в которых объявлена и используется переменная cname, станут несовместимыми со структурой исходных данных. Чтобы программа продолжала работать без ошибок, нужно изменить все эти объявления.
Стандартизация локальных переменных
Еще один недостаток явного объявления типов данных проявляется при работе с переменными PL/SQL, которые содержат вычисляемые значения, не хранящиеся в базе данных. Предположим, что программисты разработали приложение для управления финансами компании. Во многих его программах для хранения итоговой выручки используется переменная tota^revenue, объявленная следующим образом:
total^revenue NUMBER(1Q,2);
Как видите, руководитель компании имеет возможность подсчитать выручку До последнего пенни. В 1992 году, когда впервые была написана спецификация приложения, максимальная выручка, которую компания могла получить от одного заказчика, составляла 99 млн. долларов, и поэтому для переменной использовалось объявление NUMBER(10.2). Позже, в 1995 году, было принято предложение переоборудовать бомбардировщик В-2 в транспортный самолет, использующийся Для доставки шпеницы, и фирма получила контракт на 2 млрд, долларов! Когда
все уже готовились откупорить шампанское, ведущий программист сообщил, что не может сгенерировать отчеты для этого нового проекта, поскольку переменные total_revenue не позволяют разместить результат.
Он принялся разыскивать в приложении экземпляры переменных total Revenue, чтобы изменить их объявления. Это была долгая и кропотливая работа, поскольку объявления были расположены во многих местах кода. Все локальные структуры данных были не стандартизованы. Жаль, что он не объявил все локальные переменные total_revenue со ссылкой на один тип данных, что позволило бы использовать объявления с атрибутом TTYPE.
Объявления с привязкой и ограничение NOT NULL
Объявляя переменную, вы можете задать для нее ограничение NOT NULL, и оно будет перенесено на переменные, объявляемые на ее основе с атрибутом ШРЕ. Если включить ограничение NOT NULL в объявление переменной, к которой с помощью атрибута ШРЕ привязываются другие переменные, то обязательно нужно задавать начальные значения привязываемых к ней переменных. Предположим, что мы объявили переменную max_available_date с ограничением NOT NULL:
DECLARE
max available date DATE NOT NULL :-
LAST_DAY (ADDJONTH (SYSDATE,3)):
1ast_shTp_date max_available_dateXTYPE:
Такое объявление переменной 】ast_ship_date вызовет следующую ошибку компиляции:
a variable declared NOT NULL must have an initialization assignment,.
Если вы используете переменную, объявленную с ограничением NOT NULL в объявлении с атрибутом XTYPE, обязательно задавайте в этом объявлении ее начальное значение. Однако если источником значений переменной является столбец базы данных, объявленный как NOT NULL, делать это не обязательно, поскольку в подобном случае ограничение NOT NULL на переменную не переносится.
Подтипы данных, определяемые программистом
PL/SQL поддерживает оператор SUBTYPE, который позволяет программисту определять собственные подтипы (абстрактные типы данный. В PL/SQL подтип типа данных — это тип данных с набором правил и подмножеством значений исходного типа данных. Существуют два вида подтипов данных.
Q Подтип с ограничениями. Набор значений этого подтипа ограничен по сравнению с набором значений исходного типа данных. Например, тип POSITIVE является подтипом BINARY-INTEGER с ограничениями. В пакете STANDARD, где определяются типы данных и функции, входящие в состав стандартного языка PL/SQL4, подтип POSITIVE определяется следующим образом:
SIBHPE POSITIVE IS BINARY INTEGER RANGE 12147483647;'
В переменной, объявленной как POSITIVE, могут храниться только целочисленные значения больше 0.
О Подтип без ограничений. Это подтип, имеющий тот же набор значений, что и исходный тип данных. Например, тип FLOAT является подтипом NUMBER без ограничений. Он определен в пакете STANDARD следующим образом:
SUBTYPE FLOAT IS NUMBER;
Иными словами, подтипы данных без ограничений — это псевдонимы или альтернативные имена исходных типов данных. До появления версии Oracle8z разработчики PL/SQL могли определять только подтипы данных без ограничений. Теперь же разрешаются и подтипы данных с ограничениями, как например:
CREATE OR REPLACE PACKAGE utility
AS
• SUBTYPE big.string IS VARCHAR2(32767):
SUBTYPE big2db_string IS VARCHAR2C4000);
END Utility; -
Подтип нужно объявить в разделе объявлений анонимного блока PL/SQL, процедуры, функции или пакета. Вы уже видели, как объявляется подтип, используемый PL/SQL в пакете STANDARD. Общий формат объявления подтипа следующий:
SUBTYPE ш_по/1типа IS базовый_гип:
Здесь ш_подт^па 一 это имя нового определяемого подтипа, а базовыйуип — имя типа данных, на котором основывается определяемый подтип.
Имейте в виду, что на переменные, привязанные к подтипу, не распространяется ограничение NOT NULL и игнорируются начальные значения, включенные в исходное объявление переменной или спецификацию столбца.
Преобразование типов данных
Существует множество ситуаций, когда требуется преобразовать данные из одного типа в другой. Такое преобразование можно выполнить двумя способами:
О неявно — дать возможность сделать <наилучший» выбор исполнительному ядру PL/SQL;
О явно — вызвать для преобразования функцию PL/SQL или воспользоваться соответствующим оператором.
В данном разделе речь пойдет о том, как в PL/SQL реализуются эти два снос。- ба преобразования типов.
Неявное преобразование типов
Когда типы используемых в операции данных не согласуются друг с другом, PL/SQL выполняет их преобразование. Вы будете удивлены, узнав, как часто это делается. На рис. 7.2 показано, какие виды неявного преобразования типов выполняются PL/SQL.
BINARYJNTEGER
BLOB
CHAR
CLOB
DATE
LONG
NUMBER
PLSJNTEGTR
RAW
UROWID
VARCHAR2
BINARYINTEGER
—
•
•
■
•
•
BLOB
—
•
CHAR
•
—
•
CLOS
•
—
•
DATE
•
—
•
•
LONG
•
—
•
•
NUMBER
•
•
•
—
•
•
PLS_ INTEGTR
•
•
•
•
—
•
RAW
•
•
•
—
•
UROWID
•
—
•
VARCHAR2
•
•
—
Рис. 7.2. Неявные преобразования типов, выполняемые PL/SQL
Неявное преобразование типов осуществляется в том случае, когда вы задаете в операторе или выражении литеральное значение. В приведенном ниже примере PL/SQL преобразует литеральную строку «125» в числовое значение 125 и присваивает это значение числовой переменной;
DECLARE
a^number NUMBER;
BEGIN
a_number :- '125':
END「
Неявное преобразование типов выполняется также, когда вы передаете программе значения данных не того формата, который в ней используется. В следующей процедуре таким параметром является дата. Вызывая эту процедуру, вы передаете ей строку в формате DD-MON-YY, которая автоматически преобразуется в дату:
PROCEDURE change_hiredate
(empjdjn IN INTEGER, hiredatejn IN DATE)
change^hlredate (1004. '12-DEC-94');
Ограничения неявного преобразования
Преобразование может выполняться только между определенными типами дан- ных, и нельзя преобразовать один тип данных в любой другой (см. рис. 7.2). Более
того, некоторые неявные преобразования типов генерируют исключения. Рассмотрим следующую операцию присваивания:
DECLARE
a_number NUMBER;
BEGIN a number :■ 'abc';
END ~
В PL/SQL нельзя преобразовать строку «аЬс» в число, и поэтому при выполнении приведенного кода инициируется исключение VALUE.ERROR Вы сами должны позаботиться о том, чтобы значение, для которого PL/SQL придется выполнить преобразование типов, могло быть конвертировано без ошибок.
Недостатки неявного преобразования
Неявное преобразование типов имеет ряд недостатков.
О Каждое такое преобразование означает частичную потерю контроля над программой. Вы не делаете его самостоятельно и никак им не управляете, а лишь предполагаете, что оно будет выполнено и даст желаемый эффект. В этом есть элемент неопределенности — изменение компанией Oracle способа или условия преобразования может отразиться на программе. ■
О Неявное преобразование типов в PL/SQL зависит от контекста. Оно может выполниться в одной программе и не выполниться в другой, хотя на первый взгляд код кажется одинаковым. Кроме того, результат преобразования типов в PL/SQL может оказаться не таким, как вы ожидаете.
Программу легче читать и понять, если данные в ней преобразуются явно, поскольку при этом фиксируются различия между типами данных в разных таблицах или в таблице и коде. Исключив из программы скрытые действия, вы устраните и потенциальную возможность ошибок.
Таким образом, в SQL и PL/SQL рекомендуется избегать неявного преобразования типов. Лучше пользоваться функциями, которые выполняют явное преобразование и гарантируют, что будет сделано именно такое преобразование, как вы задумали.
Явное преобразование типов
Oracle предоставляет обширный набор функций и операторов, с помощью которых можно выполнять преобразование типов данных в SQL и PL/SQL. Их полный список приведен в табл. 7.1. Большая часть функций описывается в других главах книги (для них в последнем столбце указан номер главы). О функциях, которые нигде в книге нс описаны, рассказывается далее в этой главе.
Таблица 7.1. Встроенные функции преобразования
Функция
Выполняемое преобразование
Глава
ASaiSTR
Строку из любо「о набора символов в строку ASCII из набора символов базы данных
8
CAST
Одно значение встроенного типа данных или коллекции в другой встроенный тип данных или коллекцию. Этот способ может использоваться вместо традиционных функций, таких как TQ_DATE
7, 9, 10
Функция
Выполняемое преобразование
Глава
CHARTOROWID
Строку в значение типа ROWID
7
CONVERT
Строку из одного набора символов в другой
7
FROM 工
В значение типа TIMESTAMP добавляет информацию о часовом поясе, преобразуя его тем самым в значение типа TIMESTAMP WITH TIME ZONE
10
HEXTORAW
Значение из шесгнадцатеричнсо формагтв в значение типа RAW
7
MULTISET
Таблицу базы данных в коллекцию
11
NUMTODSINTERVAL
Число (или числовое выражение) в литерал INTERVAL DAY ТО SECOND
10
WUMTOYMIWTERVAL
Число (или числовое выражение) в литерал INTERVAL YEAR ТО MONTH
10
RAWTOHEX, RAWTONHEX
Значение типа RAW в шестнадцатеричный формат
7
REFTOHEX
Значение типа REF в символьную строку, содержащую его шестнадцатеричное представление
21
ROWIDTOCHAR, ROWIDTONCHAR
Двоичное значение типа ROWID в символьную строку
7
TABLE
Коллекцию в таблицу базы данных; по своему действию обратна функции MULTISET
И
THE
Преобразует значение столбца в строку виртуальной таблицы базы данных
И
TO.CHAR, TO.NCHAR (числовая версия)
Число в строку (VARCHAR2 или NVARCHAR2 соответственно)
9
TO.CHAR, TO.NCHAR (версия для дат)
Дату в строку
10
TO„CHAR, TO^NCHAR (символьная версия)
Данные из набора символов базы данных в набор символов национального языка
а
TO_BLOB
Значение типа RAW в значение типа BLOB
12
TO_CLOB, TO.NCLOB
Значение типа VARCHAR2, NVARCHAR2 или NCLOB в значение типа CLOB (либо NCLOB)
12
ТОМАТЕ
Строку в дату
10
TO_DSINTERVAL
Значение типа CHAR, VARCHAR2, NCHAR или NVARCHAR2 в значение типа INTERVAL DAY ТО SECOND
10
TO.LOB
Значение типа LONG в LOB
12
TO_MULTLBYTE
Однобайтовые символы исходной строки в их многобайтовые эквиваленты (если это возможно)
8
TO_NUMBER
Строку в число
9
TO.RAW
Значение типа BLOB в значение типа RAW
12
TO_SINGLE_BYTE
Многобайтовые символы исходной строки в соответствующие однобайтовые символы
8
TO_7IMESTAMP
Символьную строку в значение типа TIMESTAMP
10
TO_TIMESTAMP_TZ
Символьную строку в значение типа TIMESTAMP_TZ
10
TO_TIMESTAMP_LIZ
Символьную строку в значение типа TIMESTAMP_LT2
10
TO_YMINTERVAL
Значение типа CHAR, VARCHAR2, NCHAR или NVARCHAR2 в значение типа INTERVAL YEAR ТО МОМТН
10
продолжение^
Таблица 7.1 (продолжение)
Функция
Выполняемое преобразование
Глава
TRANSLATE. ..USING
Текст в набор символов, заданный для преобразования набора символов базы данных в национальный набор
.8
символов
UNISTR
В качестве аргумента принимает строку из любого набора символов и преобразует ее в строку с кодировкой Unicode
S
Функция CHARTOROWXD
Преобразует строку типа CHAR или VARCHAR2 в значение типа ROWIO. Синтаксис функции имеет следующий вид:
FUNCTION CHARTOROWID (.исходндя^строка IN CHAR) RETURN ROWID
FUNCTION CHARTOROWID (.исходнаГстрока IN VARCHAR2) RETURN ROWID
Для успешного преобразования функцией CHARTOROWID строка должна быть задана в формате
BBBBdSBB.RRRR.FFFF
Здесь ВВВВВВВВ — номер блока в файле базы данных, RRRR — номер строки в блоке, a FFFF — номер файла базы данных. Все три числа необходимо указать в шестнадцатеричном формате. Если исходная строка не соответствует этому формату, инициируется исключение VALUE_ERROR.
Функция CAST
Эта функция появилась в Огас1е8 и является очень удобным и гибким механизмом преобразования данных. Она преобразует значение любого (или почти любого) встроенного типа данных или коллекции в другой встроенный тип данных или коллекцию. Одноименный оператор хорошо знаком тем, кто работал с объект- но-ориентированпыми языками программирования, где часто возникает необходимость преобразовать объект одного класса в объект другого.
С помощью функции CAST можно преобразовать неименованное выражение (чйсло, дату, результат вложенного запроса) или именованную коллекцию (например, вложенную таблицу) в тип данных или именованную коллекцию совместимого типа.
Допустимые преобразования между встроенными типами данных показаны на рис. 7.3. Необходимо соблюдать следующие правила:
О нельзя преобразовывать типы данных LONG, LONG RAW, LOB и типы данных, cneim- фические для Oracle;
О обозначению《DATE», которое используется на рисунке, соответствуют типы данных DATE, TIMESTAMP, TIMESTAMP WITH TIME ZONE, INTERVAL DAY TO SECOND и INTERVAL YEAR TO MONTH;
О для преобразования именованной коллекции определенного типа в именованную коллекцию другого типа нужно, чтобы элементы обеих коллекций имели одинаковый тип;
О нельзя преобразовать тип UROWID в тип ROWIO, если значение UROWID представляет собой ROWI0 индекс-таблицы.
CHAR, VARCHAR2
NUMBER
DATE
RAW
ROWID, UROWID
NCHAR, NW?CHAR2
CHAR, VARCHAR2
•
•
•
•
•
NUMBER
•
•
DATE
•
•
RAW
•
•
ROWID, UROVW
•
•
NCHAR, NVARCHAR2
•
•
•
Рис. 7.3. Преобразование встроенных типов данных
Ниже приведен пример использования функции CAST для преобразования скалярных типов данных. Ее можно вызвать с помощью SQL-инструкции:
SELECT employeejd, CAST (hirjdate AS VARCHAR2 C30))
FROM employee;
Она вызывается также в PL/SQL:
DECLARE
hd_dislay VARCHAR2 (30);
BEGIN
hd_dislay := CAST (SYSDATE AS VARCHAR2);
END;
Функцию CAST удобно использовать при работе с коллекциями PL/SQL, поскольку она позволяет преобразовывать коллекцию из одного типа в другой, а также для работы с коллекцией, объявленной как переменная PL/SQL. Эта тема рассматривается подробнее в главе 11.
Следующий пример продемонстрирует синтаксис и возможности указанных операций. Сначала создадим два типа вложенных таблиц и одну реляционную таблицу:
CREATE ТУРЕ names」 AS TABLE OF VARCHAR2 (IOC);
CREATE TYPE authors_t AS TABLE OF VARCHAR2 (100);
CREATE TABLE favorlte_authors (name VARCHAR2 C200))
Далее напишем программу, которая связывает данные из таблицы favorl- tjauthors с содержимым вложенной таблицы scifi_favorites, объявленной и заполненной в другой программе. Рассмотрим следующий блок:
/* Файл a web; cast.sql */
DECLARE
scifi_favorites authors t
:= authors_t ('Sheri S. Tepper', 'Orson Scott Card', 'Gene Wolfe1);
BEGIN
DBMS__OUTPtrr.put_l 1 ne ('I recormiend that you read books by:'):
FQR. rec IN (SELECT column_value favs
FROMTABLE (CAST (sclflfavorites AS names_t))
UNION-
SELECTNAME
FROMfavorite authors)
LOOP-
DBMS_OUTPUT.put line (rec.favs):
END LOOP; ~
END;
В строках 2 и 3 объявляется локальная вложенная таблица, и она заполняется именами нескольких популярных авторов, пишущих в жанре научной фантастики. В строках 7-11 с помощью оператора UNION объединяются строки таблиц favo- rite_authors и scifi_favor1tes. Для этого, используя функцию CAST, преобразуем вложенную таблицу scifi_favorites (локальную и не видимую для ядра SQL) в тип вложенной таблицы names_t- Заметьте, что коллекцию типа authors_t можно преобразовать в коллекцию типа names_tf поскольку их типы данных совместимы. После преобразования с помощью оператора TABLE сообщаем SQL, что вложенную таблицу следует интерпретировать как реляционную. На экране отобразятся следующие результаты:
I recommend that you read books by:
Gene Wolfe
Orson Scott Card
Robert Harris
Sheri S. Tepper■
Tom Segev
Toni Morrison
Функция CONVERT
Преобразует строки из одного набора символов в другой. Синтаксис функции имеет такой вид:
FUNCTION CONVERT
(исходная_страка IN VARCHAR2,
новый^абор_символов VARCHAR2
[,стары^_набор_сшолов VARCHAR2])
RETURN VARCHAR2 -
Третий аргумент, с тдрый_наборуимволоз, необязателен. Если он не задан, применяется набор символов, используемый в базе данных по умолчанию.
Функция CONVERT не переводит слова или фразы с одного языка на другой, а заменяет буквы или символы одного набора символов буквами или символами другого.
Двумя наиболее распространенными наборами символов являются US7ASCII (7-разрядный набор ASCII для США) и F7DEC (7-разрядный набор DEC для Франции).
Функция HEXTORAW
Преобразует шестнадцатеричную строку типа CHAR или VARCHAR2 в значение типа RAW. Синтаксис функции выглядит следующим образом:
FUNCTION HEXTORAW (исходная_строка IN CHAR5RETURN RAW
FUNCTION HEXTORAW (исходндяутрока IN VARCHAR2)RETURN RAW
функция RAWTOHEX
Преобразует значение типа RAW в шестнадцатеричную строку типа VARCHAR2. Синтаксис функции приведен ниже:
FUNCTION RAWTOHEX (исходное^воичное_значеиие IN RAW)RETURN VARCHAR2
Функция RA^OHEX всегда возвращает строку переменной длины, хотя обратная ей перегруженная функция HEXTORAW поддерживает оба типа строк.
Функция RAWIDTOCHAR
Преобразует двоичное значение типа RAWID в строку типа VARCHAR2. Синтаксис функции выглядит так:
FUNCTION RAWIDTOCHAR (исходнаяСтрока IN RAWID)RETURN VARCHAR2
Возвращаемая функцией строка имеет следующий формат:
dBBBBBBB.WR.FFFF
Здесь ВВВВВВВВ — номер блока в файле базы данных, RRRR — номер строки в блоке, a FFFF — номер файла базы данных. Все три числа должны быть заданы в шестнадцатеричном формате, как в следующем примере: 11/14/199419882018
Строки
А Наборы символов
А Строковые типы данных
А О работе со строками
А Функции для работы со строками
А Функции NLS
Текст хранится в переменных символьных типов, а для работы с ними используются символьные функции. Символьные строки имеют «свободную формул, в них можно хранить буквы, цифры, а также специальные символы. Существует несколько символьных типов данных, имеющих разное назначение.
ПРИМЕЧАНИЕ
Хотя типы данных CLOB и LONG тоже можно считать символьными, они используются не так, как символьные типы данных^ описанные в этой главе. Указанные типы данных лучше воспринимать как типы больших объектов. О больших объектах рассказывается в главе 12.
Наборы символов
Работа со строками традиционно считается очень простой темой. Однако по мере того как приложения становятся все более «интернациональными» и расширяется поддержка Oracle разных наборов символов, и в частности Unicode, выясняется, что не все так просто. Поэтому для эффективной работы со строками нужно иметь представление о том, что такое наборы символов, какими они бывают и как ими пользоваться.
Что такое набор символов
Набор символов - это совокупность символов (знаков), применяемых человеком, и соответствующий ей набор битовых последовательностей, используемых для
представления этих символов в компьютере или на диске. В США обычно применяется 7-битовый набор символов ASCII. Каждый 7-битовый символ ASCII представлен семью битами 8-битового байта (значение восьмого бита — 0). Например, буква G в нем представлена значением 0100 0111. Если интерпретировать эту последовательность битов как число, получится шестнадцатеричное значение 0x47 или десятичное 71. С помощью семи битов можно представить всего 128 символов, но этого вполне достаточно для представления всех букв английского алфавита и целого ряда дополнительных символов. Таким образом, набор символов ~ это набор представляемых знаков и соответствующие им числовые значения.
Код ASCII, в котором для представления символа используется 7 бит, был одним из первых и применялся в основном в США. Это означает, что его создатели не собирались представлять буквы языков, отличных от английского. В результате центры стандартизации и отдельные компании определили для этой цели большое количество других наборов символов. Многие из этих наборов являются надмножествами ASCII и используют восьмой бит, позволяющий представить еще 128 символов. Например, набор символов кодовой страницы Microsoft Windows Code Page 1251 совместим c ASCH, но может представлять еще и символы кириллицы.
Для представления букв большинства западных языков, основанных на латинском алфавите и кириллице, вполне достаточно 256 символов. Однако этого слишком мало для восточных языков, таких как японский, корейский и китайский. Поэтому для представления знака этих языков традиционно используется не менее двух байтов. Такие наборы символов называют многобайтовыми.
Набор символов Unicode появился относительно недавно. В нем объединены все известные символы существующих алфавитов. При использовании Unicode любому символу независимо от платформы, программы и языка присваивается уникальный код. Unicode был принят в качестве стандарта такими лидерами компьютерной индустрии, как Apple, HP, IBM, JustSystem, Microsoft, Oracle, SAP, Sun, Sybase, Unisys.
ПРИМЕЧАНИЕ
За полной и подробной информацией об Unicode вы можете обратиться по адресу; http://Uni- code.org.
Типы наборов символов
Наборы символов можно классифицировать по разным признакам, но для программистов PL/SQL важны прежде всего такие характеристики;
Q является набор многобайтовым или однобайтовым;
Q имеет он фиксированную или переменную длину.
Давайте еще раз вернемся к набору символов ASCII. Используемые для представления каждого символа 7 бит помещаются в 1 байт, и каждому символу соответствует отдельный байт. Поэтому набор символов ASCII считается аднобайтог вы^> Кроме того, он является набором символов фиксированной длины, поскольку все символы представлены в нем одинаковым количеством байтов (в данном случае —одним).
В наборах символов, состоящих более чем из 256 символов, как например в Unicode UTF-8, один байт часто используется для представления символов стан- дартного набора ASCII и ряда других распространенных символов; символы остальных наборов представляются несколькими байтами. Так, в UTF-8 буква G представляется одним байтом (71 или 0x41 в шестнадцатеричной системе), а символ евро (€) — тремя байтами (0хЕ282АС). Кроме того, отдельные символы этого набора могут быть представлены замещающими парами, то есть специальными последовательностями, состоящими из двух символов, для которых всегда используются четыре байта. Наборы символов, подобные UTF-8, являются многобайтовым^ поскольку отдельные символы представлены в них более чем одним байтом и имеют переменную длину, так как количество байтов на один символ в них не всегда одинаково.
Третий класс наборов символов - это многобайтовые наборы символов фиксированной длины. Одним из них является набор символов Unicode UTF-16, в котором каждый символ представлен двумя байтами. Например, буква А представлена байтами со значениями 0 и 65.
ПРИМЕЧАНИЕ Как вы понимаете, четвертого класса наборов символовг то есть однобайтовых наборов символов переменной длины, не существует, В однобайтоеом наборе по определению всегда используется один байт нв символ, и поэтому его символы всегда имеют фиксированную длину.
Набор символов базы данных и набор символов национального языка
С каждой базой данных Oracle связаны не один, а два набора символов.
О Набор символов базы данных. Используется для представления значений столбцов типа CHAR и VARCHAR2, а также имен таблиц, столбцов, переменных PL/SQL, SQL-инструкций, большинства строковых литералов и других подобных идентификаторов.
О Набор символов национального языка. Используется для представления значений столбцов типа NCHAR и NVARCHAR2 и строковых литералов с префиксом N. Почему существует два набора символов? Так сложилось исторически, и в основном это в свое время было связано с производительностью. Набор символов базы данных использовался для однобайтовых символов ASCII, а набор символов национального языка — для многобайтовых символов фиксированной длины. Получается, что поддерживаются и символы ASCII, и многобайтовые символы, но при этом отсутствуют издержки, связанные с обработкой последних.
Если вы не знаете, какой из двух наборов символов поддерживает ваша база данных, запросите информацию представления NLS_DATABASE_PARAMETERS словаря данных Oracle:
SOL> SELECT *'
FROM nls database parameters
WHERE parameter IN (' NLSJHARACTERSET'. ' NLS_NCHARJHARACTERSET');
PARAMETERVALUE
MS CHARACTERSETWE8MSWIN1252
NLS NCHAR CHARACTERSETAL16UTF16
Параметр NLS_CHARACTERSET возвращает имя используемого набора символов базы данных Oracle, а параметр NLS_NCHAR_CHARACTERSET - имя национального набора символов в Oracle, используемого для типов данных NCHAR и NVARCHAR2.
Применяемые в Oracle имена наборов символов - просто удобные сокращения, распознаваемые программным обеспечением компании. Эти сокращения не всегда признаются центрами стандартизации и не являются официальными именами наборов символов. Например, набор Unicode UTF-16 в Oracle называется AL16UTF16. Как правило, имя набора символов в данной СУБД несет много полезной информации. Используемое здесь соглашение об именах наборов символов проиллюстрировано на рис. 8.1.
Количество битов для представления символа — 8 WE 加 SWIN3252
Регион/язык— Имя набора символов, соответствующего Западная Европа модовой странице, — Microsoft Windows {Western Europe)Code Page T252 8-Wf Wes] Europe
Рис 8.1. Иллюстрация соглашения об именах наборов символов, принятого в Grade
Главным в этом соглашении является второй элемент, указывающий количество битов, необходимых для представления одного символа. Если это число 7 или 8, можете быть уверены, что имеете дело с однобайтовым набором символов фиксированной длины, а если это число больше 8, например 16 или 32, значит речь идет о многобайтовом наборе символов. Символы многобайтовых наборов, как правило, имеют переменную длину.
ПРИМЕЧАНИЕ Иногда в конце имени набора символов присутствует буква S или С. Такой суффикс указывает, что данный набор символов может использоваться только на сервере (S) или только на клиентском ком- пыотере (С).
Рассмотренное соглашение об именах не универсально, то есть оно применяется не ко всем наборам символов в Oracle. Например, набор UTF-8 называется UTF8, что не соответствует данному соглашению.
Вопросы, связанные с наборами символов
Один байт или два? Фиксированная длина или переменная? Какое это имеет значение? При объявлении строковой переменной от набора символов зависят максимальное количество символов, помещающихся в строку, интерпретация результатов, возвращаемых рядом строковых функций и порядок сортировки (какая из строк считается большей, а какая — меньшей).
Байты и символы
Рассмотрим объявление строковой переменной:
feature name CHAR(SO):
Какую длину имеет объявленная здесь строка — 50 символов или 50 байт? Это одно и то же в том случае, если набор символов базы данных 一 однобайтовый. Но если в базе данных используется многобайтовый набор символов, тогда действительно нужно знать, какое значение переменной вы получите в результате.
В Oracle8i и более ранних версиях системы длина строковых переменных всегда объявлялась в байтах, так что объявление CHAR(50) означает, что будет создана переменная длиной 50 байт. Количество символов, помещающихся в этих 50 байтах, зависит от используемого набора символов. Так, набор символов Oracle JA16EUC поддерживает и символы ASCII, и символы японского алфавита, которые имеют переменную длину (до 3 байт). Если все символы строки — 3-байтовые, она может иметь длину до 16 символов.
Ситуация усложняется тем, что большинство многобайтовых наборов состоят из символов переменной длины, поэтому количество символов, помещающихся в 50 байтах, зависит от содержимого конкретной строки. Например, в наборе Unicode UTF-8 символы могут иметь длину до 3 байт (замещающие пары мы в данном случае не учитываем), так что если вам нужно, чтобы в значении переменной обязательно помещалось 50 символов, следует объявлять ее как CHARC150).
Объявление строк в Oracle9i
Начиная с Oracle9z при объявлении строковой переменной ее длину можно указывать в байтах или в символах. Например, объявляя переменную типа CHAR для хранения 50 байт, нужно написать так:
feature_name CHARC50 BYTE);
Если вы хотите)чтобы в переменную обязательно помещалось 50 символов из текущего набора символов, объявите ее следующим образом:
feature__nanie CHARC5D CHAR):'
А что же тогда означает такое объявление:
feature_name CHAR(50);
Для того чтобы ответить на вопрос, необходимо знать, как сконфшурирована база данных. А именно, нужно выяснить, каково значение параметра NLS_LENGTH_SE- MANTICS. С этой целью выполним такой запрос:
SQL> SELECT *
FROM nls_sessi on_parameters
WHERE parameter - 'NLS-LENGTH^EMANTICS';
PARAMETERVALUE
NLS_LENGTH_SEMANTICSBYTE
По умолчанию параметр NLS_LENGTH_SEMANTICS имеет значение BYTE, указывающее, что используется байтовая семантика и объявление CHAR(50) эквивалентно CHAR(5D BYTE). А значение CHAR указывает на символьную семантику, когда CHARC50) интерпретируется как CHAR(50 CHAR). Так что имейте в виду, что администратор базы данных может изменить значение параметра NLS_LENGTH_SEMANTICS, воспользовавшись инструкцией ALTER SYSTEM. Вам тоже удастся изменить эту установку - с помощью инструкции ALTER SESSION на уровне сеанса.
Спецификаторы BYTE и CHAR применяются не только при объявлении переменных. Их использование отражается и на интерпретации значений переменных некоторыми строковыми функциями, а также на том, дополняются ли значения переменных типа CHAR пробелами до их максимальной длины. Рассмотрим две таблицы, содержащие символы набора UTF-8:
SQL> DESCRIBE Utest
Name Null? Type
UCHARCHARd CHAR)
SQL〉 DESCRIBE utest2
Name Null? Type
UCHAR2CHAR⑶
Один столбец объявлен с использованием символьной семантики, другой — с использованием байтовой. При этом каждый столбец занимает три байта, как в следующем запросе к словарю:
SQL> SELECT table name, colunin_name. datajength, char length. char_used
FROM user tab_columns
WHERE column_name IN CUCKAR','UCHARZ'):
TABLE』AME COLUMN_NAM DATA_LENGTH CHARJENGTH CHAR_USED
UTESTUCHAR31 C
UTEST2UCHAR233 В
Из приведенного примера видно, что оба столбца имеют длину по три байта (DATA_LENGTH) и учитывается, какая семантика использовалась при объявлении каждого из столбцов (CHAR_USED).
Следующий фрагмент кода PL/SQL показывает, как семантика объявления переменной отражается на работе функции LENGTH:
DECLARE
uchar utest.uchar^TYPE;
uchar2 utESt2.uchar22TYPE;■
BEGIN
uchar :=* 'a';
uchar2 := '3':
DBMS_OUTPUT.Pm'__LINE(LENGTH(uchar)):
DBMS_0UTPUT.PJTJINE(LENGTH(uchar2)):
DaMSJUTPUT.Pm-JINECLENGTHBCuchar)];
DBMS_0UTPUT.PJTJINE(LENGTHB(uchar2));
END;
Вот что получается в результате:
1
2
3
Функции LENGTH и LENGTHB возвращают именно такие значения потому, что первая (более подробно о ней рассказывается ниже) всегда определяет длину строки
в символах, а вторая — количество байтов, занимаемых содержащимися в строке символами, а не количество байтов, указанных в объявлении строки. Приведем несколько пояснений к данному фрагменту кода.
О Переменные uchar и uchar? имеют такой же тип, как и столбцы базы данных. Поэтому каждая переменная занимает три байта.
О Обеим переменным присвоен один символ — «W》,представляемый в Unicode UTF-8 двумя байтами.
О Поскольку переменная uchar объявлена с символьной семантикой, ее длина возвращается в символах. И так как «豆》— это один символ, длина uchar всегда будет равняться одному символу.
О Поскольку переменная uchar? объявлена с байтовой семантикой, один свободный байт после двухбайтового символа «&» заполняется значением символа пробела (как всегда бывает с лишними байтами строк типа CHAR). Однако результирующая длина при этом возвращается в символах.
О Результаты, возвращаемые функцией LENGTH, не отражают того факта, что обе переменные занимают по три байта, поскольку эта функция подсчитывает символы, а не байты.
О Функция LENGTHB возвращает длину переменной в байтах, но на ее результат влияет семантика объявления переменной. Данная функция определяет количество байтов, занимаемых не переменной, а ее значением. Значение «а» переменной uchar2 занимает два байта, но к этому значению добавляется символ пробела, и всего получается три байта.
Поскольку смешение байтовой и символьной семантики вызывает путаницу, рекомендуется не использовать спецификаторы BYTE и CHAR, а задавать нужное значение параметра базы данных — NLS_LENGTH_SEMANTICS. Но независимо от того, учтете ли вы эту рекомендацию, вам необходимо знать, как спецификаторы BYTE и CHAR влияют на строковые переменные.
Семантика символьных функций
Такие символьные функции, как SUBSTR и INSTR, работают с позицией символов в строке. Например, первая из них позволяет задать начальную и конечную позиции подстроки, которую требуется извлечь из исходной символьной строки. Эти позиции можно задать либо в байтах, либо в символах. Функции SUBSTR и INSTR всегда работают с символами, а функции SUBSTR8 и INSTRB (обратите внимание на суффикс В) — с байтами. При работе с многобайтовыми наборами символов имеет значение, какая из функций используется в каждом конкретном случае. Если же применяется набор символов Unicode, необходимо знать еще больше разновидностей символьных функций.
Кодовый индекс и кодовый блок
При работе с Unicode вам придется иметь дело не только с байтами и символами. Нужно усвоить еще два понятия — кодовый индекс и кодовый блок. В Unicode кодовым индексом называется числовое значение, соответствующее записи в кодовой таблице. Например, 0x0061 — это кодовый индекс для буквы а. Иногда кодовые индексы комбинируются, образуя один символ. Например, если объединить
кодовый индекс буквы а со значением 0x0303 (кодовый индекс, соответствующий тильде), получится символ «А». Кодовый индекс 0x0303 представляет собой комбинируемый диакритический знак, то есть он всегда используется для модификации символя, а не сямостоятельно.
ПРИМЕЧАНИЕ- В Unicode не только несколько кодовых индексоэ могут представлять один символ, но и, наоборот, один кодовый индекс может представлять несколько символов.
Если кодовый индекс — это числовое значение в кодовой таблице, соответствующее символу, то кодовый блок — это реальное представление кодового индекса. Для примера возьмем кодовый индекс Unicode 0x0061, представляющий букву а. Представление этого кодового индекса в UTF-8 занимает один байт — 0x61, который является кодовым блоком. В UTF-16 тот же кодовый индекс представлен двумя байтами — 0x0061. Эти два байта также составляют один кодовый блок. Как видите, размер кодового блока (то есть количество занимаемых им байтов) зависит от используемой формы набора Unicode. Иногда значение кодового индекса слишком велико для одного кодового блока. В таких случаях он представляется двумя или несколькими кодовыми блоками. Например, кодовый индекс OxlDHE, соответствующий символу скрипичного ключа ($),в UTF-16 представлен двумя кодовыми блоками — 0xD834 и OxDDlE. Но ни одно из этих двух значений само по себе не представляет кодовый индекс — они делают это только вместе, а тот, в свою очередь, представляет один символ.
Понимание сущности таких понятий, как байт, символ, кодовый индекс и кодовый блок, важно для работы с функциями, подобными LENGTH, INSTR и др. Что вас интересует, когда требуется узнать длину строки, - количество байтов, символов, кодовых индексов или кодовых блоков? Oracle поддерживает разновидности этих функций для работы с каждой из перечисленных выше семантик. Например, для того чтобы узнать количество содержащихся в строке символов Unicode, следует воспользоваться функцией LENGTHC. Некоторые символы Unicode могут быть представлены несколькими способами. В UTF-16 символ «а» представляется как кодовый индекс 0x00ЕЗ либо как два индекса — 0061 и 0x0303. Стандартная функция LENGTH воспримет два кодовых индекса как два символа, а функция LENGTHC распознает последовательность 0x0061,0x0303 как один символ.
ВНИМАНИЕ В Unicode существует множество тонкостей и нюансов. Мы с вами уже говорили о том, что встречаются необычные случаи, когда один символ Unicode может интерпретироваться как несколько символов определенного языка. Поэтому настоятельно рекомендуем тем, кто работает с Unicode, ознакомиться с ресурсами, содержащимися на сайге http://unicode.org.
Помимо функций LENGTH и LENGTHC существуют также функции LENGTH2 и LENGTH4. Функция LENGTH2 подсчитывает количество содержащихся в строке кодовых блоков, а функция LENGTH4 — количество кодовых индексов. Поэтому важно понимать различие между кодовыми блоками и кодовыми индексами. Такие же разновидности, как у функции LENGTH, имеются и у других строковых функций, например У SUBSTR и INSTR.
В документации Oracle вы найдете ссылки на UCS-2 (кодовый блок) и UCS-4 (кодовый индекс). Суффиксы функций LENGTH2 и LENGTH4 происходят именно от
этих аббревиатур. Первоначально они определялись стандартом ISO, дублировавшим спецификацию Unicode, но теперь не используются.
Эквивалентность строк Unicode
Проверяя две символьные строки на эквивалентность, ядро PL/SQL не учитывает, что один символ Unicode может иметь более одного представления. Строка, содержащая один кодовый индекс ОхООЕЗ, который представляет символ «а», не считается эквивалентной строке, содержащей этот же символ в виде двух кодовых индексов — 0x0061 и 0x0303. Например:
DECLARE
X NVARCHAR2(30):
у NVARCHAR2(30):
BEGIN
х UNISTR('\00E3');
у UNISTR('\0061\0303');
IF x - у THEN
DBMS_OLJTPLJT.PLJT LINE,x = y'):
ELSIF x о у THEN -
DBMS_OUTPUT.PUT_LINE('x <> у'):
END IF;
END;■
Результат выполнения этого кода таков:
х о у
Для обработки данной ситуации используется функция COMPOSE, о которой рассказывается ниже. Делается это следующим образом:
IF COMPOSE(х) = COMPOSE (у)
Однако в одном случае функция COMPOSE не поможет — когда символ представляется и замещающей парой, и значением кодового индекса.
ВНИМАНИЕ~
В Oracle91 Release 1 функция COMPOSE вызывается из SQL-инструкции. В Release 2 ее можно вызн- вать и из кода PiySQL
Порядок сортировки
Порядок сортировки — это, пожалуй, единственный аспект работы с символьными данными, где не имеет значения проблема байтов и символов. В различных наборах для представления символов используются разные числовые значения. И это отражается на операторах сравнения, которые одну строку или символ сравнивают с другой строкой или символом, Прописная буква А «большее или «меньше» строчной буквы а? Это зависит от используемого набора символов. Если таковым является набор ASCII, буквы верхнего регистра будут «меньше» букв нижнего регистра, если же используется набор EBCDIC — наоборот.
Строковые типы данных
Oracle поддерживает четыре строковых типа данных (табл. 8.1). Какой из них использовать, зависит от ответов на следующие вопросы.
Q С какими строками вы работаете — переменной или фиксированной длины?
О Какой набор символов вы хотите использовать — базы данных или национальный?
Таблица 8.1. Строковые типы данных PUSQL
Набор символовФиксированная длинаПеременнаядлина
Базы данныхCHARVARCHAR2
НациональныйNCHARNVARCHAR2
Типы данных фиксированной длины — CHAR и NCHAR — в приложениях Oracle используются очень редко. Их вообще не рекомендуется применять, если нет особых причин работать именно со строкой фиксированной длины. Далее, в разделе «О работе со строками», рассказывается о проблемах, которые могут возникнуть при совместном использовании строковых переменных фиксированной и переменной длины.
Тип данных VARCHAR2
В переменных типа VARCHAR2 хранятся символьные строки переменной длины. При объявлении такой строки необходимо определить для нее максимальную длину, которая может иметь значение от 1 до 32 767 байт. Максимальная длина может быть задана в байтах или символах, но в любом случае для строки будет выделено определенное количество байтов. Общий синтаксис объявления переменной типа VARCHAR2 таков:
ш_переменной VARCHAR2 («аксинапьная^линд [CHAR | BYTE]);
Здесь имя_переменной - это имя объявляемой переменной, мзксимальная^лина - ее максимальная длина, CHAR и BfTE - аргументы, указывающие, что максимапьная/〃w- Nd выражена в символах и в байтах соответственно.
Если максимальная длина строковой переменной VARCHAR2 задается в символах (с использованием спецификатора CHAR), ее реальная длина в байтах определяется на основе максимального количества байтов, используемых для представления одного символа в наборе символов базы данных. Например, в наборе Unicode UTF-8, как вы помните, для представления отдельных символов используется три байта. Поэтому если вы работаете с UTF-8, объявление переменной типа VAR- CHAR2, максимальная длина которой составляет 100 символов, эквивалентно объявлению этой же переменной с указанием длины, равной 300 байт.
Если в объявлении переменной VARCHAR2 опустить спецификатор CHAR или BYTE, тогда в зависимости от значения параметра NLS_LENGTH_SEMANTICS заданное значение максимальной длины будет интерпретировано как количество байтов или количество символов. По умолчанию указанный параметр имеет значение BYTE, так Уто объявление без спецификатора обычно соответствует заданию длины строки в байтах.
Приведем несколько примеров объявлений строк типа VARCHAR2: declare
sma11_string VARCHAR2(4);
line_of_text VARCHAR2C20D0);
feature^name VARCHAR2C10Q BYTE):-- Строка длиной 100 байт
efnp_nanie VARCHAR2(30 CHAR); -- Строка длиной 30 символов
Итак, максимальная длина переменной типа VARCHAR2 в PL/SQL составляет 32 767 байт - гораздо больше, чем максимальная длина переменной типа данных VARCHAR2 в РСУБД Oracle (2000 байт в версиях до Oracle8i и 4000 байт в Огас1е8г и выше). Это ограничение действует независимо от того, определяете вы длину строки в байтах или символах. Так что если вы собираетесь помещать значение переменной VARCHAR2 в столбец базы данных, учтите, что его максимальная длина меньше, чем у переменной. Ни в PL/SQL, ни в SQL не предусмотрено никаких средств для автоматического устранения этого несоответствия.
ПРИМЕЧАНИЕ Если вам нужно работать со строками длиной более 4000 байт (или 2000 байт в версиях до OracleSi), а™ строки можно хранить в столбцах типа CLOB (то есть как большие символьные объекты). О столбцах данного типа подробно рассказывается в глвве 12.
Тип данных CHAR
Тип данных CHAR определяет строку фиксированной длины. Объявляя такую строку, нужно задать ее максимальную длину в диапазоне от 1 до 32 767 байт. (Это намного больше максимальной длины строки типа CHAR в РСУБД Oracle — 2000 байт, а также в версиях до Oracle8i — 255 байт.) Длина может быть задана как в байтах, так и в символах. Например, следующие два объявления создают строки длиной 100 байт и 100 символов соответственно:
feature_name CHARCIOO BYTE);
feature^name CHARC10O CHAR);
Реальное количество байтов в 100-символьной строке зависит от текущего набора символов базы данных. Если используется набор символов переменной длины, PL/SQL выделяет для строки столько места, сколько необходимо, чтобы в нее поместилось заданное количество символов, для представления которых требуется максимальное количество байтов. Например, в наборе UTF-8, где символы имеют длину от 1 до 3 байт, PL/SQL создавая строку для хранения 100 символов, зарезервирует 300 байт (3 байта х 100 символов).
Мы уже знаем, что если не задать спецификатор CHAR или 8YTE, результат будет зависеть от значения параметра NLS_LENGTH_SEMANTICS. И так как по умолчанию оно равно BYTE, результатом следующего объявления будет строка длиной 100 байт: feature_name CHAR(IOO);
Если же опустить длину строки, PL/SQL объявит строку длиной 1 байт. Предположим, что вы объявили переменную так:
feature_name CHAR;
Как только этой переменной присваивается строка длиной более одного символа, PL/SQL инициирует универсальное исключение — VALUE_ERROR. Но при этом не указывается, где именно возникла проблема. Так что в случае обнаружения □шибки проверьте объявления переменных на предмет незаданной длины строки. Для того чтобы избежать проблем и облегчить работу программистам, которые придут вам на смену, всегда задавайте длину строки типа CHAR.
Приведем несколько примеров:
yes_or_no CHAR(l) DEFAULT 'Y':
Hne_o?_text CHARC80 CHAR); -- Всегда все 80 символоа!
^01ё paragraph CHARCIOOOO BYTE); -- Подумайте обо всех этих пробелах.-
Поскольку строка типа CHAR имеет фиксированную длину, то PL/SQL в случае необходимости дополняет присвоенное переменной CHAR значение символами пробелов, добиваясь таким образом соответствия заданной в объявлении максимальной длине.
ПРИМЕЧАНИЕ до версии Oracle? строки типа данных CHAR имели переменную длину, а символьные строки постоянной длины вообще не поддерживались. Однако для улучшения совместимости с реляционными базами данных IBM и для обеспечения соответствия стандартам ANSI в Oracle? тип CHAR был переопределен как тип данных строк фиксированной длины, в для строк переменной длины был введен тип VARCHAR2.
ФИКСИРОВАННАЯ ИЛИ ПЕРЕМЕННАЯ ДЛИНА СТРОКИ
Объявляя переменную типа CHAR с указанием длины в байтах и используя ее для хранения символов многобайтового набора, можно получить совершенно неожиданные результаты. Следующий пример был выполнен в системе, где в качестве набора символов базы данных используется UTF-8:
DECLARE
х CHAR(3);
BEGIN
Присваиваем опнобайтовый символ
х ;
DBMS_OUTPUT.PUTjrNE(LENGTH(x)):
А теперь двухбайтовый символ
X %':
DBMS_OUTPUT.PUT_LINECLENGTH(x)):
--А теперь два символа общей длиной три байта х :- 'За';
.DBMSJUTPUT.PUT LINE(LENGTHCx)):
END;~
Результаты выполнения этого кода таковы:
3 2 2
Правда, интересно, что фиксированная длина строки каким-то образом изменяется? Как и то, что длина символа <а» такая же, как длина пары символов ^аа». Объясняется это тем, что функция LENGTH подсчитывает символы. В первом случае строка содержит символ «а», за которой следуют два пробела, так что общая длина строки получается равной трем символам. Во втором случае за двухбайтовым символом «а» следует один пробел, так что общая длина строки составляет два символа (хотя и три байта). В последнем случае за двухбайтовым символом «а» следует однобайтовый символ «а», и общая длина строки снова равняется двум символам.
Выполните тот же фрагмент кода, объявив переменную х как CHAR(3 CHAR), и длина строки изменяться не будет. Таким образом, при работе с многобайтовыми символами длину строк в объявлении переменной следует задавать в символах.
Типы данных NCHAR и NVARCHAR2
Типы данных NCHAR и NVARCHAR2 представляют строки, состоящие из символов национального набора, а не символов набора базы данных. Кроме того, длина таких строк всегда объявляется в символах (спецификатор CHAR при этом не используется). Во всем остальном NCHAR и NVARCHAR2 ничем не отличаются от типов данных CHAR и VARCHAR2. Первый из них используется для хранения строк постоянной длины, а второй - переменной. Например:
11ne_of_text NVARCHAR2C2000); -- 2000 символов, переменная длина feature_name NCHAR(IOO): -- 100 симзолов, постоянная длина
В версиях до Огас1е9: в качестве национального набора символов мог использоваться любой однобайтовый или многобайтовый набор. Однако начиная с Огас- 1е9г, национальным набором символов, используемым для переменных типов NCHAR и NVARCHAR2, может быть только UTF-8 (многобайтовые символы со строками переменной длины) или UTF-16 (многобайтовые символы со строками фиксированной длины).
ПРИМЕЧАНИЕ
Понятия национального набора символов и набора символов базы данных подробно описывались в разделе «Набор символов базы данных и набор символов национального языка» ранее в этой главе.
В Orac】e9i типы данных NCHAR и NVARCHAR2 были усовершенствованы, и теперь их можно совместно использовать в выражениях с типами данных CHAR и VARCHAR2. Если в версиях до Oracle9z в ответ на попытку сравнить значение VARCHAR2 со значением NVARCHAR2 PL/SQL выдавал сообщение об ошибке PLS-00561; character mis- mdtch, то теперь подобное сравнение допускается. Конечно, за это приходится расплачиваться некоторым снижением эффективности вычислений, поскольку PL/SQL должен неявно конвертировать значения к общему набору символов.
Строковые подтипы
PL/SQL поддерживает строковые подтипы данных (табл. 8.2), которые тоже могут использоваться для объявления символьных строк. Однако многие из этих подтипов определены только для обеспечения совместимости со стандартом ANSI SQL. Маловероятно, что они вам когда-либо понадобятся, но знать о них все же нужно.
Каждый из перечисленных в таблице подтипов эквивалентен одному из базовых типов данных PL/SQL, указанных в правом столбце. Например:
feature_name VARCHAR2(100);
feature.name CHARACTER VARYING(IOO):
feature_name CHAR VARYING(IOO); feature name STRING(IOO);
Таблица 8.2. Строковые подтипы и экаивапенткые им типы данных PL/SQL
Подтип
Эквивалентный тип
CHAR VARYING
CHARACTER
CHARACTER VARYING
NATIONAL char
NATIONAL CHAR VARYING national character
NATIONAL CHARACTER VARYING
NCHAR VARYING
STRING
VARCHAR
VARCHAR2
CHAR
VARCHAR2
NCHAR
NVARCHAR2
NCHAR
NVARCHAR2
NVARCHAR2
VARCHAR2
VARCHAR2
Подтип VARCHAR заслуживает особого внимания. Уже на протяжении нескольких лет корпорация Oracle собирается изменить значение подтипа данных VARO HAR (в результате чего он перестанет быть эквивалентным типу VARCHAR2) и предупредила, что пользоваться им не следует. Мы придерживаемся этой рекомендации. Если существует вероятность такого изменения, не имеет смысла вводить в свои программы подтип данных VARCHAR. Используйте вместо него VARCHAR2.
О работе со строками
Работать со строками в основном довольно просто. Однако существует несколько тонкостей, о которых программистам обязательно следует знать. О них рассказывается в следующем разделе.
Пустая строка — это NULL
Одна из причин постоянных недоразумений - особенно для тех программистов, которые перешли к Oracle от других СУБД и языков программирования — заключается в том, что Oracle интерпретирует пустые строки как значение NULL. Это не согласуется со стандартом ANSI SQL, определяющим четкое различие между пустой строкой и строковой переменной, имеющей значение NULL:
/* Файл в web: ch08 emptyisnull.sql */
DECLARE“
empty^varchar2 VARCHAR2(10);-
empty二char CHAR(IO):-";
BEGIN -
IF empty_varchar2 IS NULL THEN
DBMS_OUTPUT.PUT_LINE('Пустая строка типа varchar? это NULL');
END IF;- ~
IF " IS NULL THEN
OBMSJUTPUT.PUT LINEC это NULL');
END IF;-~'
IF empty_char IS NULL THEN
DBMS OUTPUT.PUT_LINE('nycTa« строка типа char это NULL'):
END IF:~"
END;
Код выводит следующий результат:
Пустая строка типа varchar2 это NULL
"это NULL
Данный пример показывает, что переменная типа CHAR не интерпретируется как NULL. Ведь это переменная фиксированной длины, и она не может быть пустой: по определению. В приведенном примере она просто заполняется пробелами до длины 10 символов. А вот переменная VARCHAR2 равняется NULL, поскольку ей присвоен строковый литерал нулевой длины.
На эту особенность строковых переменных следует обращать внимание при их сравнивании в операторах IF. Возьмем программу, которая запрашивает у пользователя имя и сравнивает это имя со значением, прочитанным из базы данных:
DECLARE
user_entered_name VARCHAR2C30):
name_froni_database VARCHAR2C30):■
begin''
IF user_entered_name <> nane_froni_database THEN
Если пользователь введет вместо имени пустую строку, результат проверки заданного в этом примере условия IF никогда не будет равен TRUE, поскольку значение NULL не может быть равным или не равным никакому другому значению. Альтернативный способ записи данного оператора IF таков:
IF (user_entered__name q name_fronLdatabase)
OR [user_entered_name IS NULL) THEN
Это только один из способов решения проблемы равенства значению NULL пустой строки, но его можно применять не в каждой ситуации. В общем случае можно сказать так: вы должны продумать задачу, учесть, что пустые строки будут интерпретироваться как NULL, и написать соответствующий код.
Смешение значений CHAR и VARCHAR2
Если вы используете в программе строки как фиксированной (CHAR), так и переменной (VARCHAR2) длины, следует учитывать способ выполнения операции в Oracle, где участвуют оба типа строк. Об этом рассказывается в следующих разделах.
Перенос информации из базы данных в значение переменной
При извлечении (SELECT или FETCH) данных из столбца базы данных типа CHAR и присвоению их значению переменной типа VARCHAR2 завершающие пробелы сохраняются. Если же перенести данные из столбца типа VARCHAR2 в переменную типа CHAR, PL/SQL автоматически дополнит извлекаемое значение пробелами до максимальной длины переменной. То есть результирующее значение переменной определяется ее типом, а не типом столбца.
Перенос значения переменной в базу данных
Если вы помещаете (INSERT или UPDATE) значение переменной типа CHAR в столбец базы данных типа VARCHAR2, ядро SQL не подавляет завершающие пробелы. Например, при выполнении следующего кода PL/SQL столбцу company_name базы данных присваивается значение ACME SHOWERS(где символ «■» обозначает пробел). Иными словами, столбец дополняется пробелами до 20 символов, хотя исходным значением была строка из 12 символов:
DECLARE
comp_1d# NUMBER;
со叩二name CHAR(20) :- 'ACME SHOWERS';
BEGIN ~
SELECT company_1d_seq.NEXTVAL
INTO comp_id#
FROM dual"
INSERT INTO company Ccompanyjd, company_name)
VALUES (compjd#. comp_name):
ENO;"~
С другой стороны, когда вы помещаете значение переменной типа VARCHAR2 в столбец базы данных типа CHAR, ядро SQL автоматически дополняет строку переменной длины пробелами до максимальной длины столбца (заданной при создании таблицы).
Сравнение строк
Предположим, что некоторая программа содержит следующий оператор сравнения строк:
IF company_name - parent_company_name ...
PL/SQL должен сравнить значения company_name и parent_company_name. Эта операция выполняется одним из двух способов, в зависимости от типов переменных.
О При наличии двух переменных типа CHAR PL/SQL производит сравнение с дополнением завершающими пробелами.
Q Если хоть одна из строк имеет переменную длину, PL/SQL выполняет сравнение без дополнения завершающими пробелами.
Различие между этими двумя методами сравнения видно в следующем фрагменте кода:
DECLARE
сол1рапу_пате CHAR(30)
卜'Мама мыла раму';
cha г_ра rent_company_name CHAR(35)
:-'Мама мыла раму';
varchar2_parent_company_name VARCHAR2C35)
:='Мама мыла раму';
BEGIN
―Сраэниааем даа значения CHAR, поэтому строки дополняются пробелами
IF company_name = char_parent_company_name THEN
D8MS_0UTPUT.PUT_LI NE ('Результат первой проверки равен TRUE');
ELSE~
DBMS OUTPUT.PUT_LINE £'Результат первой проверки равен FALSE');
END IF:~'
―Сравни заем CHAR и VARCHAR. поэтому строки не дополняются 叩обелзии
IF company_name = varchar2_parent_company_name THEN
DBMS_OUTPUT.PUT_LINE ('Результат второй проверки равен TRUE'):
ELSE ——
DBMS_OUTPUT.PUT_LINE С'Результат второй проверки равен FALSE');
ENO IF ~-
END:
Результат выполнения этого кода таков:
Результат первой проверки равен TRUE
Результат второй проверки равен FALSE
Первыми сравниваются два значения типа CHAR, поэтому производится дополнение завершающими пробелами: PL/SQL дополняет более короткое из двух значений до длины более длинного значения. После этого выполняется их сравнение. В данном примере PL/SQL добавляет пять пробелов в конец значения переменной company_name и затем сравнивает значения company_name и char_parent_compa- ny_name. После этого обе строки имеют равную длину. Обратите внимание, что PL/SQL не изменяет значение переменной company_name. Он просто копирует это значение в другую структуру памяти и там модифицирует его для сравнения.
Во второй операции участвуют значения типов VARCHAR2 и CHAR, поэтому PL/SQL выполняет сравнение без дополнения завершающими пробелами. Он не меняет ни одного из значений и использует их текущую длину. В данном случае первые 22 символа обеих строк одинаковы (Feuerstein and Friends), но значение перемен- { ной фиксированной длины company_name дополнено восемью пробелами, а значение переменной varchar2_pa rent_company_name — нет. В результате строки оказываются не равными.
Участие в проверке значения типа VARCHAR2 говорит о том, что сравнение будет выполнено без дополнения завершающими пробелами. Это верно и для выражений, содержащих более двух переменных, а также для выражений с оператором IN. Например:
IF menu_selection NOT IN
(save_and_close. cancel_and_ex1t. 'OPEN_SCREEN')
THEN ...
Если хоть одна из четырех строк данного примера (menu_selection, две именованные константы и один литерал) объявлена как VARCHAR2, точное сравнение будет выполнено без модификации значений. Обратите внимание на то, что литерал, подобный OPEN_SCREEN, всегда рассматривается как строка фиксированной длины, то есть значение типа CHAR.
Символьные функции и аргументы типа CHAR
Символьная функция — это функция, принимающая в качестве параметров одно или несколько символьных значений и возвращающая символьное или числовое значение. Возвращаемое функцией символьное значение всегда имеет тип VARCHAR? (переменной длины), если речь идет не о функциях UPPER и LOWER. Указанные функции переводят заданную строку в верхний или нижний регистр соответственно и возвращают значение фиксированной длины типа CHAR, если заданные в качестве их аргументов строки имели тип CHAR.
Определение строковых констант
Задаваемые строковые константы берутся в одинарные кавычки:
■Brighten the corner where you are'
Если одинарную кавычку нужно включить в строковую константу, ее следует продублировать:
'Aren''t you glad you''re learning PL/SQL?' '
Как правило, строковые константы задают на основе набора символов базы данных. Если такая константа присваивается значению переменной типа NCHAR или NVARCHAR2, она неявно преобразуется в символы национального набора. СУБД Oracle сама выполняет такие преобразования по мере необходимости, и программисту об этом заботиться не нужно. Если же потребуется явно указать, что строковая константа задана с применением национального набора символов, поместите перед ней префикс N:
Н'Эта строка задана в национальном наборе символов.'
При выполнении кода PL/SQL в SQL*Plus проблемы часто вызывает символ амперсанда (&). Дело в том, что в SQL*Plus указанный символ используется в качестве префикса переменных, и, встретив его, SQL*Plus запрашивает у вас значение переменной. Например:
SQL〉 BEGIN
DBMS_OUTPLJT.PUT_LINE('Tom & Jerry.'}:
END: -~
/
Enter value for saving:
Существует несколько способов решения этой проблемы. Один из них состоит в выполнении команды SQL*Plus SET DEFINE OFF, отменяющей подстановку переменных. В следующих разделах подробно описывается каждая из функций, перечисленных в таб. 8.3.
Таблица 8.3. Строковые функции
НмаОписание
ASCIIВозвращает ASCII-код символа
ASQISTRПреобразует исходную строку в строку символовнабора ASCII
CHRВозвращает символ, соответствующий заданномукоду
COMPOSEПринимает строку символов набора Unicode и возвращает ее
в нормализованной форме
CONCATВыполняет конкатенацию двух строк (то есть объединяет их в одну)
DECOMPOSEПринимает строку символов набора Unicode и возвращает ее
в виде строки, где каждый составной символ «разобран» на составляющие элементы
GREATESTВозвращает строку, которая при сортировке по алфавиту будет
последней
INHCAPПереводит первую букву каждого слова в верхний регистр, а все
остальные буквы — в нижний
Таблица 8.3 (продолжение)
Имя
Описание
INSTR, INSTRB, INSTRC, INSTR2, INSTR4
Возвращает позицию заданной подстроки в строке
LEAST
Возвращает строку, которая при сортировке по алфавиту будет первой
LENGTH, LENCTHB, LENGTHC, LENGTH2, LENGTH4
Возвращает длину строки
LOWER
Переводит все буквы строки в нижний регистр
LPAD
Дополняет строку слева заданными символами
LTRIM
Удаляет все заданные символы в левой части строки
REPLACE
Заменяет последовательность символов в строке указанной последовательностью символов
RPAD
Дополняет строку справа заданными символами
RTRIM
Удаляет все заданные символы в правой части строки
SOUNDEX
Возвращает символьную строку, содержащую фонетическое представление заданного аргумента
SUBSTR, 5UBSTRB, SUBSTRC, 5UB5TR2, SUBSTR4
Возвращает заданную часть строки
TO_CHAR
Преобразует символы национального набора в символы набора базы данных
TO—MULTI一ВГГЕ
Принимает строку и преобразует в ней однобайтовые символы в их многобайтовые эквиваленты. Например, в UTF-8 многие символы могут быть представлены как одним, так и несколькими байтами
TO_SINGLE_BYTE
Выполняет действие, обратное дбйстгвию функции TO_MULTT_BYTE — преобразует многобайтовые символы в их однобайтовые эквиваленты
TRANSLATE
Преобразует от^бльные символы в строке в указанные символы
TRANSLATE …USING
Преобразует символы одного набора в символы другого
TRIM
Позволяет выполнить действия функций RTRIM и LTRIM
UNISTR
Выполняет действие, обратное действию функции ASCIISTR — преобразует строку в Unicode
U叩ER
Переводит все буквы в строке в верхний регистр
Строковые функции
PL/SQL предоставляет богатый набор строковых функций, позволяющих получать информацию о строках и модифицировать их содержимое. Перечень основных строковых функций PL/SQL представлен в табл. 8.3, а ниже приводится их подробное описание. Кроме перечисленных в таблице существуют также функции, специфичные для Oracle: Trusted Oracle и National Language Support (NLS). Последние рассматриваются далее в этой главе, в разделе «Функции NLS»,
функция ASCII
Возвращает код типа данных NUMBER, представляющий заданный символ в наборе символов базы данных. Приведем ее синтаксис:
FUNCTION ASCII (одинуимвол IN VARCHAR2) RETURN NUMBER
Здесь один_симаол — это символ, код которого необходимо определить. Хотя функция называется ASCII, она возвращает код символа в текущем наборе символов базы данных, например в EBCDIC Code Page 500 или 7-битовом ASCII. Обратите внимание, что коды букв верхнего регистра отличаются от кодов тех же букв нижнего. Например, для 7-битового набора символов ASCII вызов ASCII('а') возвращает значение 97, а вызов ASCIK 'А') — 65.
Если передать функции ASCII более одного символа, она вернет код первого из них, а остальные просто проигнорирует. Поэтому следующие три вызова возвращают оно и то же значение 一 228:
АЗСИСдклр')
ASCII('fl')
ASCIK'fl^eJ^')
Функция ASCIISTR
Принимает в качестве аргумента строку любого набора символов и преобразует ее в строку набора ASCII. Любые не АЗСП-символы выводятся в форме \XXXX, где ХХХХ — Unicode-значение символа. Синтаксис функции ASCIISTR таков:
FUNCTION ASCIISTR (CTpoKaJ IN VARCHAR2) RETURN VARCHAR2
Приведем пример ее использования:
BEGIN
DBMS_OUTPUT.PUT_LINE(
ASCIISTRC'Символ Э не является символом набора ASCII.')
END:
Результат выполнения этого кода:
Сиивал \0QE3 не является символом набора ASCII.
Функция UNISTR, описанная далее в этой главе, выполняет обратную операцию.
ПРИМЕЧАНИЕ;
Информацию об Unicode, и в частности о байтовых кодах символов Unicode, вы найдете на web-узле по адресу: http://unlcode.org.
Функция CHR
Является обратной функции ASCH. Она возвращает символ VARCHAR2 (строку длиной 1 символ), соответствующий заданному в ее аргументе коду. Приведем синтаксис функции:
FUNCTION CHR (тод IN NUMBER) RETURN VARCHAR2
Здесь коп — это значение, определяющее символ в наборе символов базы данных.
функцию CHR особенно удобно использовать в тех случаях, когда в программном коде нужна ссылка на непечатаемый символ. Например, символ новой строки в ASCII имеет код 10. Пользуясь функцией CHR, его можно найти в строке и выполнить после его обнаружения определенные действия.
Используя функцию CHR, можно также вставить в строку символ новой строки. Предположим, вам нужно создать отчет, в котором выводится адрес компании. Помимо строк с названиями города, страны и индекса адрес может содержать до четырех дополнительных строк, и значение каждой из них нужно выводить с новой строки. При этом пустых строк быть не должно. В этой ситуации SQL-инструкция SELECT бессильна:
SELECT name, addressl. address?, address3. address4,
city II '. ' И state || ' '|丨 zipcode location
FROM company;
Если каждый столбец (поле отчета) выводится с новой строки, у вас получится адрес, состоящий из шести строк, независимо от того, сколько из них равно NULL. Ыапример:
HAROLD HENDERSON
22 BUNKER COURT
SUITE 100
WYANDACH, MN 66557
Функцией CHR можно воспользоваться и для удаления пустых строк:
DECODE(addressl, NULL,NULL,CHR (10)IIDECODECaddress2, NULL.NULL.CHR (10)11DECODE(address3, NULL,NULL,CHR (10)IIDECODE(address4. NULL,NULL.CHR (10)DECODE(addressl, NULL,NULL,CHR (10)IIDECODECaddress2, NULL.NULL.CHR (10)11DECODE(address3, NULL,NULL,CHR (10)IIDECODE(address4. NULL,NULL.CHR (10)SELECT name ||SELECT name ||CHR (10) Иcity И '. ' II state 11 ' ' 11 zipcodeCHR (10) Иcity И '. ' II state 11 ' ' 11 zipcodeFROM company;FROM company;addressl) address2) address3) address4)addressl) address2) address3) address4)IIIIIIIIIIIIIIII
DECODE
(addressl, NULL,
NULL,
CHR (10)
II
DECODE
Caddress2, NULL.
NULL.
CHR (10)
11
DECODE
(address3, NULL,
NULL,
CHR (10)
II
DECODE
(address4. NULL,
NULL.
CHR (10)
DECODE
(addressl, NULL,
NULL,
CHR (10)
II
DECODE
Caddress2, NULL.
NULL.
CHR (10)
11
DECODE
(address3, NULL,
NULL,
CHR (10)
II
DECODE
(address4. NULL,
NULL.
CHR (10)
SELECT name ||
SELECT name ||
CHR (10) И
city И '. ' II state 11 ' ' 11 zipcode
CHR (10) И
city И '. ' II state 11 ' ' 11 zipcode
FROM company;
FROM company;
addressl) address2) address3) address4)
addressl) address2) address3) address4)
II
II
II
II
II
II
II
II
Теперь запрос возвращает по одному форматированному столбцу, соответст- вующегиу каждой компании. Функция DECODE позволяет включить в SQL-инструкцию логику IF.. .THEN: «если строка адреса равна NULL, то добавить в формируемую строку NULL; в противном случае добавить символ перевода строки и за ним строку адреса». Таким образом, пустые строки адреса игнорируются, и он уменьшается на две строки:
HAROLD HENDERSON
22 BUNKER COURT
SUITE 100
WYANDACH. MN 66557
По умолчанию функция CHR преобразует указанный код символа в символ набора базы данных. Если же вы предполагаете использовать символы национального набора, воспользуйтесь предложением USING NCHAR_CS:
DECLARE
х NVARCHAR2(3D);■
BEGIN
x :- CHR(65 USING NCHAR_CS);
Ту же операцию выполняет функция "NCHR, о ней будет рассказано ниже.
Функция COMPOSE
Принимает в качестве аргумента строку набора символов Unicode и возвращает ЭТу «с строку в нормализованной форме. Ее синтаксис таков:
FUNCTION COMPOSE {строка_1 IN VARCHAR2) RETURN VARCHAR2
Набор символов Unicode позволяет представлять отдельные символы несколькими способами. Например, символ «а» может быть представлен так:
а к \00E3
Его можно представить и следующим образом:
а = а\0303
В первом случае \00E3 - кодовый индекс Unicode (в шестнадцатеричной системе) символа «а». Во втором случае \0303 — несамостоятельный символ тильды набора Unicode, добавляемый к предыдущему символу «а». Таким образом, символ «А» формируется из буквы & и символа тильды. Полный символ \00E3 назы- ■ вается составным символом. Функция COMPOSE принимает в качестве значения строку, состоящую из двух определенных символов, такую как в\0303, и преобразует ее в один составной символ. Например:
DECLARE
х VARCHAR2(30);
у VARCHAR2(30);
BEGIN
SELECT COMPOSE(UNISTR('a\0303 это составной символ')).
.ASCIISTR(COMPOSE(UNISTR('a\0303 зто составной симаол')))
INTO X. у
■ FROM dual;
OBMS_OUTPUT.PUT_LINE(x);
DBMS OUTPUT,PUT LlNE(y);
Вот что выводит этот код:
а это составной символ
\00E3 это составной симзол
ВНИМАНИЕ—
В Oracle9i Release 1 функцию COMPOSE нужно было вызывать только из SQL-инструкции; в программах PL/SQL она использоваться не могла. В Oracle9i Release 2 ее можно вызывать и из кода PL/SQL.
Функция CONCAT
Выполняет конкатенацию двух строк тила VARCHAR2 и возвращает объединенную строку, в которой исходные строки следуют одна за другой в заданном порядке. Ее синтаксис таков:
FUhICUON CONCAT (строка』IN VARCHAR2, строке J IN VARCHAR2)
RETURN VARCHAR2~
Функция CONCAT всегда добавляет значение аргумента строка^ в конец значе- иия аргумента строка__1. Если одна из заданных строк равна NULL, она возвращает
тот аргумент, значение которого не равно NULL. Приведем несколько примеров использования этой функции (символ «т» означает, что функция возвращает указанное значение):
CONCAT ('абв'. 'где') т 'абвгде'
CONCAT (NULL, 'жзл') т 'жзл'
CONCAT Саб'. NULL) -> 'аб'
CONCAT (NULL, NULL) т NULL
В PL/SQL (и РСУБД Oracle) для объединения строк служит также оператор конкатенации - две вертикальные линии (| |), Например:
DECLARE
х VARCHAR2(100);
BEGIN
х 尸 'абв' |] 'где' || 'жзл';
DBMS OUTPUT.PUT LlNE(x):
END;-
Результат выполнения этого кода таков:
абагдежзл
Чтобы выполнить такую же операцию с помощью функции CONCAT, нужно один ее вызов вложить в другой:
X :» CONCATCCONCAT('a6B'. 'где'), 'жзл'):
Как видите, оператором 11 пользоваться гораздо удобнее, чем функцией CONCAT, и результирующий код получается гораздо более читабельным.
Функция DECOMPOSE
Ее действие противоположно действию функции COMPOSE. Функция DECOMPOSE принимает строку набора символов Unicode и возвращает строку, в которой составные символы заменены соответствующими им парами символов. Ее синтаксис таков:
FUNCTION DECOMPOSE {строка! IN VARCHAR2) RETURN VARCHAR2
Рассмотрим следующий пример:
DECLARE
X VARCHAR2(30):
BEGIN
х :- '3 это составной символ';
SELECT ASCIISTR(DECOMPOSE(x))
INTO х
FROM dual:
DBMS_QUTPUT.PUT_LINE(xk '
END;-
Вот что выводит этот код:
а\О303 это составной символ
В данном случае выполнена декомпозиция Символа «豆》входной строки, в результате чего получаем символ «а» и кодовый индекс, соответствующий несамостоятельному символу \0303, представляющему тильду.
ВНИМАНИЕ -'
В Oracle9i Release 1 функция DECOMPOSE, подобно функции COMPOSE, вызывалась не непосредсг- венн0 из программы PL/SQL, а только из SQL-инсгрукции. В Oracle9i Release 2 это ограничение снято.
функция GREATEST
Принимает в качестве аргумента одну или несколько строк и возвращает строку, которая при сортировке по алфавиту оказалась бы последней. Сортировка строк производится в соответствии с кодами символов набора базы данных. Синтаксис функции таков:
FUNCTION GREATEST (строкаJ IN VARCHAR2. строкаJ IN VARCHAR2.
RETURN VARCHAR2
Приведем пример:
BEGIN
DBMS_OUTPUT.PUT_LINE(GREATEST('Владимир1, 'Антон', 'Борис1)):
END: 一 一
Владимир■
Обратитесь также к описанию функпии LEAST, противоположной по действию функции GREATEST.
Функция INITCAP
Изменяет регистр символов строкового аргумента, переводя первую букву каждого слова строки в верхний регистр, а оставшиеся буквы — в нижний. Словом считается последовательность символов, отделенная от остальных символов пробелом или не буквенно-цифровым символом (как《#》или «」)• Синтаксис функции INITCAP таков:
FUNCTION INITCAP (строка」 IN VARCHAR2) RETURN VARCHAR2
Приведем несколько примеров использования этой функции.
О Перевод строки нижнего регистра в строку, каждое слово которой начинается с большой буквы:
INITCAP('эго нижний регистр') -> 'Это Н欄н疝 Регистр'
■ Q Перевод строки верхнего регистра в строку, каждое слово которой начинается с большой буквы:
IN1TCAPC'БОЛЫ1ЮЙ>И*ВЬ;СОКИЙ') т 'Большой>ИАВысокиГ
Q Перевод строки, содержащей символы верхнего и нижнего регистров в строку, каждое слово которой начинается с большой буквы:
INITCAPC'чТоЗТ0всЕ_знаЧИТ?1) т 'Чтоэтовсе_3начит?'
Q Формирование имени переменной языка Visual Basic (для удаления символов подчеркивания воспользуемся функцией REPLACE, о которой рассказывается ниже в этой главе):
REPLACE{INITCAP('ALMOS^UNREADABLE-VAR-NAME'). '_'. NULL) т 'AlmostUnreadableVarNanie,
В каких же случаях применяется функция INITCAP? Во многих базах данных Oracle все символьные строки (такие как имена, названия, адреса и т. п.) хранятся в верхнем регистре. Это облегчает поиск записей, соответствующих определенному критерию.
Хотя использовать такой «внутренний формата удобно, выводимые данные получаются нечитабельными. Например:
CUSTOMER TRACKING LIST - GENERATED ON 12-MAR-1994
LAST PAYMENT WEDNESDAY: PAUL JONSON. 123 MADISON AVE - $1200
LAST PAYMENT MONDAY: HARRY SIMMERSDN. 555 AXELROD RD - S1500
Все сливается, трудно выделить отдельные слова и разные типы информации. Представленный ниже текст читается гораздо легче:
Customer Tracking List - Generated On 12-Mar-1994
Last Payment Wednesday: Paul Jonson, 123 Madison Ave - $1200
Last Payment Monday: Harry Slmmerson. 555 Axel rod Rd - $1500
Вы убедились, что функщпо INITCAP удобно использовать при форматировании выходных данных. Однако у нее имеется несколько недостатков. Во-первых, написание всех слов с большой буквы в заголовках — и иногда в других случаях 一 для русского языка, в отличие от английского, не свойственно. А во-вторых, функция, не понимая смысла текста, не различает, например, тех слов, внутри которых отдельные буквы должны быть большими, как D в слове MacDonald's:
INITCAP('MACDONALD"S') т 'Macdonald'S'
Поэтому при печати отчетов для вывода данных пользуйтесь этой функцией осторожно.
Функции INSTR, INSTRB, INSTRC, INSTR2, INSTR4
Семейство функций INSTR позволяет найти в строке заданную подстроку. Если в исходной строке найдена подстрока, функция возвращает позицию се первого символа. В противном случае она возвращает 0.
Пять функций семейства INSTR различаются липгь способом восприятия, а следовательно, и просмотра строк.
О INSTR — строки состоят из символов. Возвращаемое значение указывает символьную позицию подстроки.
О INSTRB — строки состоят из байтов. Возвращаемое значение указывает байтовую позицию, в которой найдена подстрока.
О INSTRC — строки состоят из символов Unicode. Пары символов, представляющие составные символы, распознаются как составные символы (то есть символы «а» и его представление «а\0303» воспринимаются как одинаковые).
О INSTR2 — строки состоят из кодовых блоков Unicode.
О INSTR4 一 строки состоят из кодовых индексов Unicode.
У всех функций INSTR один и тот же синтаксис;
FUNCTION IMSTR (строкд_1 IN VARCHAR2.
строке/ IN VARCHAR2
[,начальндя^позиция IN NUMBER := 1
[.n-еухождение IN NUMBER 尸 1]])
RETURN NUMBER
Здесь строка」—это строка, просматриваемая функцией INSTR в поиске параметра п~е_вхол(дение строки, указанной в значении аргумента строка_2. Значение аргумента нд4дльная_позиция определяет символьную (а не байтовую) позицию в строке, с которой будет начат поиск. По умолчанию оно равно 1, то есть поиск начинается с начала строки, определяемой аргументом строка_1. Аргумент п-еу^ождение также является необязательным и по умолчанию его значение равно 1, то есть ищется первое вхождение подстроки в строке.
Аргументы начальндя_позиция и п-е_вхождение можно задавать и в виде литералов (например, 5 или 157): переменных или сложных выражений, как в следующем примере:
INSTRCcompany_name. 'INC. (Iast_locat1on+5)*10)
Если значение аргумента ндЧдльнзя_позиция отрицательно, функция INSTR отсчитывает соответствующее количество символов с конца строки (справа) и затем начинает просмотр в направлении начала строки. На рис. 8.2 показаны два направления поиска — в зависимости от знака значения аргумента начальная^позиция.
INSTRCstring.'A',N.M)
N>0
2…m
М...21 ABS(N)
Рис. 8.2. Просмотр строки с помощью функции INSTR
Функция INSTR — очень полезная, в особенности когда используются все ее возможности. Большинство программистов применяют только два первых аргумента, а об остальных — например, «искать с конца строки», «искать не первое, а n-е вхождение» — часто даже не знают. Не допускайте такой ошибки и освойте эту функцию в полной мере.
ПРИМЕЧАНИЕ■
8 Oracle/ при отрицательном или равном нулю значении аргумента начальная_позиция функции INSTR всегда возвращала 1. В OracleB эти же значения вызывали исключение VALUE_ERROR.
Рассмотрим несколько примеров. Вы узнаете, как использовать все четыре аргумента функции INSTR, и увидите, какую пользу они могут принести. А когда будете писать собственные программы, не забывайте о возможностях функции INSTR, позволяющих упростить программный код, выполняющий анализ символьных данных. С ее помощью можно сделать следующее.
О Найти первое вхождение строки пожалуйста в строке Как вас зовут? Скажите, пожалуйста:
INSTR ('Как вас зовут? Скажите, пожалуйста1, 1 пожалуйста') т 25
Значения аргументов начальная^озиция и первоеухождение по умолчанию равны 1.
О Найти последнее вхождение строки а в строке Как вас зовут? Скажите, пожалуйста:
INSTR ('Как вас зовут? Скажите, пожалуйста'. 'а'. -1) т 34
Помните, что символьная позиция, возвращаемая функцией INSTR, всегда вычисляется от крайнего слева символа строки (то есть символа с позицией 1). Для того чтобы найти последнее вхождение подстроки в строке, проще всего задать отрицательную начальную позицию. Номер вхождения при этом не задается, поскольку последнее вхождение при поиске с конца является первым.
О Найти предпоследнее вхождение строки а в строке Как вас зовут? Скажите, пожалуйста:•
INSTR ('Как вас зовут? Скажите, пожалуйста', 'а', -1, 2) т 28
Тут все проста. Начиная поиск с конца строки, INSTR пропускает последнюю букву а в слове «пожалуйста^, поскольку это последнее вхождение, и ищет следующее. При этом возвращаемая позиция, как всегда, считается слева, а не справа.
О Найти ближайшую к вопросительному знаку позицию буквы а (до вопросительного знака) в строке Как аас зовут? Скажите, пожалуйста:
search_string :- 'Ках эас зоэут? Скажите, пожалуйста';
tee_loc :-
INSTR (search_string , 'а',
-1 * (LENGTH (search_string) - INSTR t5earch_string, '?') +1)): т 6
В приведенном выше фрагменте кода мы динамически вычисляем позицию вопросительного знака (точнее, первого вопросительного знака; предполагается, что он один). Затем вычитаем это значение из полной длины строки и умножаем результат на -1, поскольку нам нужно определить расстояние от конца строки до вопросительного знака. С этой позиции функция и начинает поиск — от вопросительного знака к началу строки.
Данный пример напоминает, что любой из аргументов функции INSTR может быть сложным выражением, вызывающим другие функции для выполнения некоторых вычислений. Этот же факт отражает и следующий пример.
О Подтвердить допустимость введенного пользователем значения. В этом примере проверяется, имеется ли введенная пользователем команда в списке допустимых команд. Если да, она выполняется:
IF INSTR (,|ADD|DELETE|CHANGE|VIEW|CALC|,.,|, || cmd || '「) > О
THEN
execute_command (cmd);
ELSE
DBMS_OUTPUT.PUT_LINE
(тВы ввели неправильную команду. Произведите повторный ввод...1);
END IF:
В этом случае с помощью оператора конкатенации составляется строка, в которой будет производиться поиск введенной команды. В конец введенной команды добавлена вертикальная черта, поскольку в списке команд она применяется в качестве разделителя. Здесь мы яспользуем вызов функции INSTR в логическом операторе IF. Найдя соответствие, она возвращает ненулевое значение. Это означает, что результат проверки условия в операторе IF равен TRUE, и работа продолжается. В противном случае выводится сообщение об ошибке.
Следующий пример, с применением набора символов базы данных UTF-8, показывает различие между функциями INSTR и INSTRB, которыми вы будете пользоваться чаще всего:
DECLARE
-В качестве набора символов базы данных в этом примере используется Unicode JTF-8
х CHAR(30 CHAR) := 'Символ 3 занимает два байта.':
BEGIN
Определяем символьную позицию 'два'
DBMS_OUTPUT.PJT_LINE(INSTR(x, 'два')):
Определяем байтовую позицию 'два'
DBMS_OUTPUT.PUT_LINE(INSTRB(x, 'два'));
END;-
Вот что получается в результате:
19
20
Разница в позиции слова «два» объясняется тем, что символ «а» в Unicode UTF-8 занимает два байта. Поэтому слово «два» начинается с 19-го символа, но с 20-го байта.
Функция INSTRC может распознавать составные символы, представленные двумя символами. Как отмечалось в разделе, посвященном функции COMPOSE, альтернативным представлением символа <а» является <а\0303>. Следующий пример показывает, что функция INSTRC распознает это альтернативное представление, тогда как функция INSTR - нет;
declare
■ В качестве набора символов базы данных в этом примере используется Unicode UTF-8
х CHARC40 CHAR) :- UNISTR('Символ в\0303 составной.'):
BEGIN
- INSTR не видит, что а\0303 - это то ке самое, что 3
DBMS^JTPUT.PUTJINEdNSTRCx.'K1));
-А вот INSTRB понимает, что а\0303 - а
DBMS_OUTPUT. PUTJINE (INSTRB (х.'З')к'
ENO:
В результате получаем:
О
R
С точки зрения функции INSTR строка х вообще не содержит символа «а». Функция же INSTRB распознает «а\0303» как альтернативное представление этого символа. Использовавшаяся в объявлении переменной х функция UNISTR преобразует ASCII-строку, которую мы можем прочесть, в строку Unicode, требующуюся в нашем примере.
Функции INSTR2 и INSTR4 позволяют найти в строке кодовый блок или кодовый индекс, которые могут не соответствовать целому символу. В следующем примере в качестве национального набора символов используется AL16UTF16. Символ «А» в строке х представлен как два кодовых индекса: один для символа «а», а второй для тильды (\0303). Попытаемся найти кодовый индекс \0303 с помощью функции INSTRC, а затем с помощью функции INSTR4:
DECLARE
-Набор символов базы данных в этом примере Unicode UTF-16
х NCHAR(40) := UNISTRC'Символ а\0303 состазной.
8EGIN
-Ищем позицию \0303 с помощью INSTRC
DBMS_OUTPUT.PUTJI NE(INSTRC(х. UNiSTR('\0303'))):
Ищем позицию \0303 с помощью INSTR4
DBMS_OUTPUT.PUT_LINE(INSTR4(x. UNISTRC'\0303'))):
END;
Вот что получается в результате:
О
9
Функция INSTRC работает с целыми символами, и для поиска кодового индекса, являющегося частью символа, она бесполезна. А вот функция INSTR4 может найти кодовый индекс \0303. Возвращаемое ею значение 9 указывает, что \0303 находится на 9-й позиции в строке.
Функция INSTR2 действует подобным образом, но ищет кодовые блоки UCS-4. Взгляните на следующий пример:
DECLARE
х NCHAR(40]尸 UNISTRC'Это \D834\DD1E тест'): BEGIN
DBMS OUTPUT.PUT_LINE CINSTR2(x, UNISTRC'\D834\'))):
DBMS]OUTPUT.PUT二LINE (INSIH4(x, UNISTR「\0834\'))):
EMD;
5 0
Каждое значение в замещающей паре \D834\DD1E (скрипичный ключ) является кодовым блоком, а два блока вместе составляют один кодовый индекс. Данный пример показывает, что функция INSTR2 находит кодовый блок в замещающей паре, а функция INSTR4 — нет, поскольку первая воспринимает строку как последовательность кодовых блоков, а вторая — как последовательность кодовых индексов. Поиск кодового блока в замещающей паре подобен поиску первого байта во многобайтовом символе. Однако маловероятно, что вам когда-нибудь потребуется производить такой поиск.
функция LEAST
Принимает в качестве аргумента одну или несколько строк и возвращает строку, которая оказалась бы первой при сортировке заданных строк по алфавиту. Порядок сортировки основывается на наборе символов базы данных. Синтаксис функции LEAST таков:
FUNCTION LEAST {строкд_1 IN VARCHAR2. строка』IN VARCHAR2,…)
RETURN VARCHAR2 ~~
Приведем пример:
BEGIN
DBMS_OUTPUT.PUT_LINE(LEAST('БерезOBckmй', 'Лебедко'. 'Пасько')):
END: ~—
Березовский
Обратитесь также к описанию функции GREATEST, которая является обратной функции LEAST.
Функции LENGTH, LENGTHS, LENGTHC, LENGTH2, LENGTH4
Семейство функций LENGTH возвращает длину строки. Приведем перечень этих функций с указанием единиц, в которых выражается длина строки:
О LENGTH — символы;
О LENGTHB - байты;一
О LENGTHC - символы Unicode, нормализованные, где это возможно;
О LENGTH2 — кодовые блоки;
О LENGTH4 — кодовые индексы.
Синтаксис всех этих функций одинаков:
FUNCTION LENGTH (строма」 VARCHAR2) RETURN NUMBER
Если значение аргумента строка_1 равняется NULL, то функция LENGTH возвращает NULL — но не нуль! Помните, что строка нулевой длины — это «отсутствующее значение». Поэтому она не может иметь длину, даже нулевую, и всегда возвращает либо NULL, либо положительное значение.
ПРИМЕЧАНИЕ
Одним исключением является использование функции LENGTH для определения длины переменяй типа CLOB. Такая переменная может содержать нуль ба Слов и все же не бьпъ равной NULL. Это единсгвеииый случай, когда LENGTH возвращает нуль.
Приведем несколько примеров использования функции LENGTH:
LENGTH (NULL) т NULL.
LENGTH ('') т NULL -- To же самое, что строка NULL
LENGTH ('абвг') т 4
LENGTH С'абвг ')^5-
Если строка』имеет фиксированную длину (тип данных CHAR), функция LENGTH подсчитывает и завершающие пробелы. Поэтому для переменной фиксированной длины она всегда возвращает одно и то же значение, независимо от того, какое значение присвоено этой переменной. Если же необходимо подсчитать длину содержащейся в переменной строки без завершающих пробелов, просто удалите их с помощью функции RTRIM (о которой рассказывается далее в этой главе). В следующем примере переменной lengthl присваивается значение 60, а переменной length? - значение 13:
DECLARE
company name CHARC60) :- 'ACME PLUMBING';
Tengtt)rNUMBER;
lengthZ NUMBER:
BEGIN
lengthl :- LENGTH Ccompany_name):
length2 :- LENGTH (RTRIM (company name));
END:~
Рассмотрим еще один пример, в котором используется переменная типа NVAR- CHAR2 и набор символов AL16UTF16. Данный пример демонстрирует различие между функциями LENGTH, LENGTHB и LENGTHC, Запомните, что AL16UTF16 — это использующееся в Oracle имя набора символов UTF-16, в котором каждый символ представлен двумя байтами:
DECLARE
В этом примере NVARCHAR2 - UTF-16,
х NVARCHAR2(50) :- UNISTR('Символ 百 эквивалентен а\03031):
BEGIN
DBMS_OUTPUT.PUT_LINE(LENGTHCx)):
DBMS_OUTPUT.PUTJINE(LENGTHB(x)):
DBMS_OUTPUT.PUT_LINE(LENGTHC(x));
END:"
Вот результат выполнения этого кода:
25 50
24
Функция LENGTH подсчитывает символы, но《а\0303» воспринимается ею как' два разных двухбайтовых символа. Функция LENGTHB подсчитывает байты, и возвращает значение, равное удвоенному количеству двухбайтовых символов, возвращенному функцией LENGTH. И, наконец, LENGTHC подсчитывает символы Unicode и видит, что «а\0303» на самом деле представляет один символ «а».
Функция LOWER
Переводит все буквы строки в нижний регистр. Синтаксис функции таков:
FUNCTION LOWER (строка」IN CHAR) RETURN CHAR
FUNCTION LOWER (строкд_1 IN VARCHAR2) RETURN VARCHAR2
Функция LOWER, как и UPPER, о которой будет рассказано ниже, возвращает строку фиксированной длины, если входная строка имеет фиксированную длину,
Л строку переменной длины — в противном случае. При этом ни та, ни другая функция не меняет символов, которые не являются буквами, — например, цифр м специальных символов.
Приведем несколько примеров использования функции LOWER:
LOWER с'БОЛЬШИЕ БУКВЫ') т 'большие буквы'
LOWER Г123АБВ') т '123абв'
Функции LOWER и UPPER удобно применять для согласования регистров симвоЛОв при сравнении строк. PL/SQL не чувствителен к регистру символов в отношении собственного синтаксиса и идентификаторов, чего нельзя сказать о регистре символьных строк, являющихся константами, литералами или значениями переменных. Строка А5В — не то же самое, что абв, и поэтому в программах нужно внимательно обрабатывать такие значения.
функция LPAD
По умолчанию PL/SQL удаляет из символьной строки все завершающие пробелы, если только строка не объявлена как CHAR. Однако случается, что в начало или конец строки необходимо добавить несколько пробелов (или других символов). Тогда на помощь приходит функция LPAD (либо аналогичная ей функция RPAD), возвращающая строку, дополненную слева до заданной длины одним указанным символом (или несколькими символами). Вот ее синтаксис:
FUNCTION LPAD (spoxd」IN VARCHAR2.
конечндя_/злинв IN NUMBER
[,допопняюи1дя^трокд IN VARCHAR2])
RETURN VARCHAR2~
Функция LPAD возвращает значение аргумента строка J. t дополненное слева до значения параметра конечная_длина символьной строкой дополняющдяутрокд. При условии, что последний аргумент не задан, строкз_1 дополняется слева пробелами. Аргумент конечная_длина является числом и должен быть задан обязательно. Если аргументы строка J. и конечндя_длина равны, функция LPAD возвращает значение первого без изменений. Если же конечная длина меньше исходной длины строки, функция LPAD обрезает ее, то есть в исходной строке, заданной значением аргумента строка_1, возвращается количество символов, равное значению аргумента конечная_длина.
Как видите, функция LPAD может не только добавить пробелы в левой части строки. Рассмотрим несколько примеров ее использования.
Q Вывод представляю щей числовое значение строки, дополненной слева нулями до длины 10:
LPAD ('55'. 10. '0') т '0000000055'
Q Вывод представляющей числовое значение строки, дополненной слева нулями до длины 5:
LPAD ('1234567В'. 5. '0') т '12345'
Функция LPAD интерпретирует значение аргумента конечная』л挤на как максимальную длину возвращаемой строки. Поэтому она отсчитывает слева (от начала строки) 5 символов и просто возвращает подстроку заданной длины.
О Повторение слова «ура!» перед словом <голШ> до тех пор, пока длина строки не будет равна 44;
LPAD ('гол!!!1, 44. 'ура!') т 'ура!ура!ура!ура!ура!ура!ура!ура!ура!ургол!!!'
Поскольку длина строки гол!!! составляет 6 символов, а строки ура! — 4, для последнего полного вхождения дополняющей строки места не осталось. В результате десятое повторение урезано на два символа.
Функция LTRIM
Действие функции LTRIM противоположно действию функции LPAD. Если последняя добавляет символы с левой стороны строки, то первая их удаляет. И так же, как LPAD, функция LTRIM располагает большими возможностями, чем просто удаление ведущих пробелов. Ее синтаксис таков:
FUNCTION LTRIM (строка」IN VARCHAR2 [, удаляеныеуинволь1 IN VARCHAR2J)
RETURN VARCHAR2 -一
①ункция LTRIM возвращает стр。也 1, из которой удалены все ведущие символы, входящие в состав строки удаляеные^ине олы. Второй аргумент не обязателен и по умолчанию является символом пробела.
Существует одно важное различие между функциями LTRIM и LPAD. Последняя добавляет слева заданную строку и повторяет ее до тех пор, пока длина результирующей строки не достигнет заданного значения. Функция LTRIM удаляет все ведущие символы, которые представляет строка удаляемые_симеолы. Приведем несколько примеров.
О Удаление из фразы《 Книга — лучший подарок» всех ведущих пробелов:
LTRIM (' Книга - лучший подарок') т 'Книга - лучший подарок'
Поскольку второй аргумент не задан, и。умолчанию он равен символу пробела, и в результате удаляются все ведущие пробелы.
О Удаление всех цифр в начале строки:
m7_str1ng := 707457835абвгд':.
LTRIM (my_string, '1234567890') т 'абвгд'■
Задав во втором аргументе все возможные цифры) мы гарантируем, что они будут удалены из начала строки, независимо от порядка их следования в значениях аргументов строкаJ. и удаляемые^имволы.
А что если мы захотим удалить из начала строки только заданную последовательность, скажем,《аб»? Функция LTRIM для этого не подходит, поскольку она не сопоставляет последовательности символов. Для их удаления или замены используется функция REPLACE, о которой рассказывается в следующем разделе.
ПРИМЕЧАНИЕ
В ANSI-стандарте функции LTRIM соответствует функция TRIM.
функция REPLACE
Возвращает строку, в которой все вхождения заданной подстроки заменены указанной замещающей строкой. Синтаксис функции таков:
FUNCTION REPLACE (строе」IN VARCHAR2. искомаяутрока IN VARCHAR2
[,замёщающая_строка IN VARCHAR2])
RETURN VARCHAR2
Если здмещающая_строка не задана, функция REPLACE просто удаляет все вхождения искомой_строки в с троке _1. Если же не задана ни искомая_строка, ни ззмещаю- щая^трока, функция REPLACE возвращает NULL.
Приведем несколько примеров использования функции REPLACE.
Удаление всех символов «С» в строке САБВ С СС 000С:
REPLACECCABB С СС ОООС'.'С) т 'А6В ООО'
Поскольку мы не задали замещающую строку, функция REPLACE заменила все вхождения символа «С» на NULL.
Замена всех вхождений числа 99 на 100 в следующей строке:
ЯЕР1_АСЕСЧисло 99 равно числу 99'', '99'. '100') т 'Число 100 равно числу 100!1
О Обработка вхождений одинарной кавычки в строке. В PL/SQL одинарная кавычка является символом-ограничителем, указывающим начало и конец строкового литерала. Допустим, пользователь вводит строку, содержащую одинарную кавычку:
Русская неделя в ресторанах MacDonald's.
Программа объединяет эту строку с другим текстом, и результирующая инет- рукция SQL (динамически созданная Oracle Forms в режиме запроса) получается ошибочной, поскольку в ней содержатся несбалансированные кавычки. Для разрешения данной проблемы нужно удвоить в строке каждую одинарную кавычку, указав тем самым SQL, что она является частью строки. С этой целью можно выполнить следующий оператор:
criteria_str1ng :-REPLACE (cr1teria_string.""");
Четыре кавычки во втором аргументе интерпретируются как строка, содержащая одну одинарную кавычку, а шесть кавычек в третьем аргументе — как строка, содержащая две одинарные кавычки. В каждом случае внешняя пара кавычек ограничивает строку, а каждая пара кавычек внутри нее интерпретируется как одна кавычка.
Q Удаление последовательностей символов «абн» в начале строки:
аБвабвааагдеёжз - буквы алфавита: дбв
Нечто подобное делала описанная в предыдущем разделе функция LTRIM. Однако мы хотим удалить последовательность символов «абв», а не просто символы 4а», «6» и «в» в любом порядке, и не в середине или конце обрабатываемой строки, а только в начале. Например, не должны удаляться последовательности «ааа». Эта задача более сложная, чем может показаться на первый взгляд. Если просто применить к строке функцию REPLACE, она удалит все последовательности ааб叫 вместо того чтобы удалить их только в начале строки:
REPLACE Сабвабвааагдеёжз - Буквы алфавита; абв'. 'абв') -» 'дйдгдеёжз - Буквы алфавита;'
А это не то, что нам нужно. С другой стороны, если использовать функцию LTRIM, она удалит и ведущие символы «а*
ЕТКТМСабвабвааагдеёжз - букаы алфавита; абв', 'збв') -> 'гдеёжз - буквы алфавита; абв'
Это тоже не то, что нам нужно. Требуется, чтобы осталась строка ааагдеёжз - буквы алфавита; абв. Решением проблемы является савместнае использование функций LTRIM и REPLACE:
niy_string :- 'абвабвавагдеёжз - буквы алфавита: абв';
REPLACE
(LTRIM
(REPLACE (niy_string. 'абз1. '0'), '@'), ^1. 'аба');
'ааагдеёжа - буквы алфавита; абв'
Поясним этот фрагмент кода: сначала заменяем все вхождения строки абв в строковом значении локальной переменной fny_string на специальный символ @ (предполагается, что его в исходной строке нет), затем удаляем все ведущие экземпляры символа @ и снова заменяем оставшиеся его экземпляры на абв.
Функция RPAD
Добавляет символы в конец строки. Она возвращает строку, дополненную справа до заданной длины указанной последовательностью символов. Ее синтаксис таков:
FUNCTION RPAD (строка_1 IN VARCHAR2, конечная_рпш IN NUMBER [,допопняющая_строка IN VARCHAR2]) RETURN VARCHAR2"
Функция RPAD возвращает значение аргумента с трока _1, дополненное справа до значения аргумента конечная_длш символьной строкой дополняющая_строка. В том случае, когда последний аргумент не задан, строка_1 дополняется справа пробелами. Аргумент конечнаяjinim должен быть задан обязательно. Если длина исходной строки уже равна значению аргумента конечнаяjywa, функция RPAD возвращает аргумент строка_1 без изменений. Если же конечная длина меньше исходной длины строки, функция RPAD обрезает эту строку, то есть возвращает в ней количество символов, равное значению аргумента конечна я _дл^на.
Рассмотрим несколько примеров.
О Вывод представляющей числовое значение строки, дополненной справа нулями до длины 10:
RPAD ('55', 10. 'О') -> '55QQQ0000Q'
Можно также воспользоваться функцией TO_CHARf преобразующей число в строку (следует помнить, что всегда имеется несколько способов решения задачи):
TO_CHAR (55 * 100000000) -> '5500000000'■
О Вывод представляющей числовое значение строки, дополненной справа нулями до длины 5:
RPAD ('12345678'. 5. '0') т '12345'
Функция RPAD интерпретирует значение аргумента конечная_длина как максимальную длину возвращаемой строки. В результате она отсчитывает слева
(от начала строки) 5 символов и просто возвращает подстроку заданной длины. В этом случае она делает в точности то же самое, что и описанная ранее функция LPAD, а не возвращает 5 крайних символов справа (в данном случае 45678).
О Повторение слова <ура!» после <гол!!1* до тех пор, пока длина строки не станет равной 44:
RPAO СголН1.1 . 44. 'ура!')
'гол!!!ура!ура!ура!ура!ура!ура!ура!ура!ура!ур'
Поскольку длина строки гол!!! равняется 6 символам, а длина строки ура! — 4, для последнего полного повторения дополняющей строки места не осталось. В результате десятое повторение урезано на два символа.
С помощью функции RPAD (или LPA0) можно генерировать повторяющиеся последовательности символов. Например, можно создать строку из 60 дефисов)используемую в качестве разделителя информации в отчете:
RPAD С'-'. 60.'-')
Функции RPAD и LPAO вызываются также из SQL-инструкций,
Функция RTRIM
По своему действию противоположна функции RPAD и сходна с функцией LTRIM. Если RPAD добавляет символы с правой стороны строки, го RTRIM их удаляет. И, подобно функции LTRIM, функция RTRIM способна гораздо на большее, чем просто удалить завершающие пробелы. Ее синтаксис таков:
FUNCTION RTRIM (crpofisj IN VARCHAR2 [. уцапяеные_символы IN VARCHAR2])
RETURN VARCHAR2 -
Функция RTRIM возвращает spo®」из которой удалены все завершающие символы) входящие в строку удаляемыеушолы. Второй аргумент не обязателен, и по умолчанию он является символом пробела. Приведем примеры.
Q Удаление из строки всех завершающих пробелов:
LTRIM ('Бросай курить') т 'Бросай курить1
Поскольку второй аргумент не задан, по умолчанию он является символом пробела, и в результате удаляются все завершающие пробелы.
О Удаление последовательности слов ・ЕАМ【БУМ! БАМ!» из строки Звуковые эффекты: БАМ! 5УМ! АМ!:
吋.string :- 'Звуковые эффекты: БАМ! БУМ! АМ!':
RTRIM (niy_string, ' БАМ! БУМ') т 1 Звуковые эффекты:'
В данном случае функция RTRIM удаляет из конца строки все буквы, имеющиеся в строке БАМ! БУМ. И хотя, например, слова <АМ» в строковом значении ар- гумента удаляемыеуимволы нет, там есть все составляющие его буквы, и поэтому данное слово удаляется из исходной строки. Можно добавить, что двух восклицательных знаков в значении аргумента уцдляемые_столы указывать не нужно, поскольку функция RTRIM ищет не слова «БАМ!» и <БУМ1», а только составляющие их символы.
ПРИМЕЧАЛ И Е■~~—
В ANSI-стандарте функции RTRIM соответствует функция TRIM.
Функция SOUNDEX
Обрабатывает строку с учетом ее фонетики (звучания), а не семантики (написания)1. Функция возвращает символьную строку, являющуюся «фонетическим представлением» аргумента. Ее спецификация такова:
FUNCTION SOUNDEX (строка_1 IN VARCHAR2) RETURN VARCHAR2
Вот несколько значений, генерируемых функцией SOUNDEX:
SOUNDEX ('smith') т 'S530'
SOUNDEX ('SMYTHE') т 'S530'
При работе с функцией SOUNDEX следует придерживаться следующих правил.
О Возвращаемое функцией значение всегда начинается с первой буквы исходной строки.
О Для формирования выходного значения SOUNDEX использует только первые пять согласных букв строки.
О Для определения числовой части значения SOUNDEX применяет только согласные. Все гласные, за исключением начальной, игнорируются.
О Функция SOUNDEX не чувствительна к регистру. Для одних и тех же букв в верхнем и нижнем регистре возвращается одно и то же значение.
Функция SOUNDEX используется для выполнения запросов, например динамических, в которых точное значение, находящееся в базе данных, неизвестно или его трудно определить.
ПРИМЕЧАНИЕ
Функция SOUNDEX предназначена для работы с буквами английского языка^ а с другими языками может работать плохо или даже не работать вовсе.
Функции SUBSTR, SUBSTRB, SUBSTRC, SUBSTR2, SUBSTR4
Семейство функций SUBSTR — один из наиболее популярных наборов символьных функций. Эти функции позволяют извлечь из строки цэуппу последовательных символов (подстроку). Подстрока задается значением начальной позиции и длиной, а функции различаются только используемыми единицами измерения:
О SUBSTR - начальная позиция и длина задаются в символах;
О SUBSTRB — начальная позиция и длина задаются в байтах (при использовании однобайтового набора символов функции SUBSTRB и SUBSTR возвращают одинаковые результаты);
О SUBSTRC — начальная позиция и длина задаются в символах Unicode, после «сборки» всех составных символов;
1 Для получения фонетического представления строки Oracle Corporation предлагает использовать алгоритм Дональда Нута, описанный в третьем томе книги Art о/ computer Programming.
О SUBSTR2 — начальная позиция и длина задаются в кодовых блоках;
□ SUBSTR4 — начальная позиция и длина задаются в кодовых индексах.
Синтаксис этих пяти функций одинаков:
FUNCTION SUBSTR (строкд_1 IN VARCHAR2, нача^ьная_позиция IN NUMBER [,длина_подсгроки IN NUMBER])
RETURN VARCHAR2
Здесь строка J. — исходная строка; начвль нал_позиц^я — начальная позиция подстроки в строке_1\ длинз_подстроки — длина возвращаемой подстроки.
Последний аргумент не обязателен. Если вы не зададите его, функция SUBSTR вернет все символы строки」(от заданной начальной позиции). Если же аргумент длина_подстроки задан, его значение должно быть больше нуля.
Значение аргумента начальная_позиция не может быть равно нулю. Если оно меньше нуля, подстрока извлекается из конца строки; функция SUBSTR отсчитывает количество символов, заданное значением аргумента начальная^позиция, от конца строка」. Однако и в этом случае извлекаемые символы располагаются справа от начальной позиции. На рис. 8.3 показано, как функцией SUBSTR используются разные значения аргументов.
SUBSTR(string.7.4)
74
SUBSTR(string,-7.4) 4
ABSC-7)
Рис. 8.3. Использование функцией SUBSTR разных значений аргументов
Функция SUBSTR очень «снисходительна». Даже если нарушить правила задания второго и третьего аргументов, ошибка не будет сгенерирована — функция просто вернет NULL или всю строку.
Приведем несколько примеров использования функции SUBSTR.
。Возвращение последнего символа строки:
SUBSTR ('Компьютер'. -1) т 'р'
Это простейший способ вернуть последний символ. Можно, конечно, поступить и так:
SUBSTR
('Коипьптер', LENGTH ('Компьптер'), 1) т 'р'
Иными словами, мы определяем длину строки и извлекаем из нее подстроку длиной в один символ, начиная с последней позиции.
О Удаление элемента из списка строк. Кажется, что это задание противоречит назначению функции SUBSTR: требуется извлечь часть строки, оставив остальную ее часть нетронутой. Тем не менее SUBSTR с ним отлично справится. Предположим, на экран выводится список выбранных температурных режимов:
I ЖАРКО I ХОЛОДНО I ТЕПЛО I ПРОХЛАДНО I
Вертикальная черта разделяет элементы списка. Предположим, нужно удалить один элемент списка, например ТЕПЛО. Для выполнения этой задачи проще всего определить начальную и конечную позиции удаляемого элемента, затем с помощью функции SUBSTR выделить оставшиеся части списка и соединить их вместе — без заданного элемента.
DECLARE
myj1st VARCHAR2C50):
3>letB VARCHAR2(20);
start_pos NUMBER;
end_pos NUMBER;
BEGIN ~
niyjist :- 'I ЖАРКО I ХОЛОДНО I ТЕПЛО I ПРОХЛАДНО I':
to_delete ;- 'ТЕПЛО':
startup os :■ INSTR(iny_list, to_delete): -- первый удаляемый символ
end_pos :■ start_pos + LENGTH(to_delete): 一 последний удаляемый символ
吋—Tlst SUBSTR CinyJIst. 1. start_pos-l)
' II '"
SUBSTR (my_lI st, end_pos+l):
DBMS_OUTPUT.PUT_LINE Tniyjist):
END;—
Результат работы кода приведен ниже:
I ЖАРКО I ХОЛОДНО I ПРОХЛАДНО I
О Извлечение части строки, расположенной между заданными начальной и конечной позициями. Такая задача решается очень часто. Функции SUBSTR нужно знать начальную позицию и количество извлекаемых символов. Однако часто бывает так, что известны лишь начальная и конечная позиции, и тогда приходится вычислять количество символов, находящихся между ними: разность двух значений, разность плюс единица или разность минус единица. Чтобы не было путаницы, приведем пример функции betwnstr. Она извлекает подстроку, вычисляя количество символов между начальной и конечной позициями, которое равно значению выражения конечная_позиция - нзчдльная_позиция + Г.
/* Файл a web: betwnstr.sf */
FUNCTION betwnstr (
strlngjn IN VARCHAR2,■
startjn IN IMTEGER, endjn IN IMTEGER)
RETURN VARCHAR2
IS
BEGIN
RETURN SUBSTR (strlngjn. startjn. end_1 n - startjn + 1):
END:"~~~
Хотя эта функция не обеспечивает такой гибкости, как функция SUBSTR (например, в отношении отрицательной начальной позиции), она может послужить отправной точкой для инкапсуляции больших возможностей.
Подобно семействам функций INSTR и LENGTH функция SUBSTR имеет разновидности, предназначенные для работы с многобайтовыми наборами символов и набором символов Unicode. В следующем блоке PL/SQL отражено различие между символьной и байтовой семантикой:
DECLARE
8 этом примере NVARCHAR2 - UTF-16.
х NVARCHAR2(50 CHAR) :- UNISTRC'Символ a\0303 - это аналог З1);
BEGIN
DBMS_OUTPUT.PUT_LINE(SUBSTR(x. 17, 6));
DBMS_OUTPUT.PUTjlNECSUBSTRB(x. 33, 12));
END;
Вот что выводит данный код:
аналог аналог
Слово «аналог» содержит шесть символов, начиная с символьной позиции 17. Однако оно занимает 12 байт, начиная с байтовой позиции 33. Поскольку мы используем набор символов UTF-16, каждый символ занимает два байта. Первые 16 символов занимают 32 байта. Поэтому 17-й символ начинается с 33-го байта и занимает байты 33 и 34.
Функция TO_CHAR
Преобразует символы национального набора в символы базы данных. Синтаксис функции таков:
FUNCTION TD_CHAR (символьные^анные IN NVARCHAR2) RETURN VARCHAR2
Функция TO_CHAR может принимать значения следующих типов данных: NCHAR, NVARCHAR2, CLOB и NCLOB. Возвращаемое значение всегда имеет тип VARCHAR2.
Приведем пример использования функции TO_CHAR для преобразования строки национального набора символов в строку набора базы данных:
DECLARE
a VARCHAR2O0):.
b NVARCHAR2C30) 'Я хочу съесть торт':
BEGIN
а := TOJHAR(b):
END; ~
О том, как преобразовать данные в обратном направлении, рассказывается далее в этой главе, в разделе <Функции NLS». Кроме того, вы можете обратиться .к описанию функции TRANSLATE.. .USING.
ПРИМЕЧАНИЕ
Функция TO_CHAR может использоваться для преобразования в символьный тмп чисел, а также значений даты и времени. О таком ее применении рассказывается в главах 9 и 10.
Функция TO_MULTI_BYTE
Преобразует однобайтовые символьные данные в их многобайтовые эквиваленты. Некоторые многобайтовые наборы символов, и в частности UTF-8, поддерживают более одного представления некоторых символов. В UTF-8 буквы (скажем, G) могут быть представлены одним или тремя байтами. Функция TO_MULTI_BYTE позволяет преобразовывать данные в соответствии с обоими представлениями. Ее синтаксис таков:
FUNCTION TO_MULTI_BHE (строкд_1 IN VARCHAR2) RETURN VARCHAR2
Тип значения, возвращаемого функцией TD_MULTI_BYTE, определяется типом данных входного значения и всегда с ним совпадает.*
В следующем примере функция TO_MULTI_BYTE используется для преобразования буквы G в ее многобайтовое представление. Код предназначен для системы, в которой в качестве националйтого набора символов используется UTF-8:
DECLARE
g_one_byte WARCHAR2 (1 CHAR) 'G';
g_three_bytes NYARCHAR2 (1 CHAR):
g_one_again NVARCHAR2(1 CHAR):
dump_output YARCHAR2C30);
BEGIN
--Преобразовать однобайтовый символ "G" в многобайтовое представление g_three_bytes :- TO_MULTI_BYT£(g_ane_byte):
DBMS_OUTPUT.PUT_LINE(LENGTHB(g_one_byte));
DBMS_OUTPUT. PUT_LINE(LENGTHB(gjhree_bytes)):
SELECT DUMP(g_three_bytes) INTO dunp_output FROM dual:
DBMS_OUTPUT .PUT_LINE(dump_OLitput); ~
--Преобразовать это многобайтовое представление обратно в однобайтовое
g_one_aga1n := TO_SINGLE_BYTE(g_three_bytes):
D0MS_OUTPUT.PUT_lYnE(g_one_aga1n || ' это снова ' ||
"~TO_CHAR(LENGTHS(g_one_aga in))
II ' байт.'): END;
Вот результаты выполнения этого кода:
1
3
Тур-1 Len-3: 239.188,167
G это снова 1 байт.
Как видите, функция TO_MULTI_BYTE в строке 8 вернула трехбайтовое представление буквы G, соответствующее UTF-8. Далее мы вызвали функцию TO_SINGLE_BYTE, чтобы преобразовать это трехбайтовое представление обратно в однобайтовое.
Функция TO_SINGLE_BYTE
Преобразует многобайтовые символы в их однобайтовыс эквиваленты, выполняя действие, обратное действию функции TO_MULTI_BYTE. Синтаксис функции таков:
FUNCTION TO_SINGLE_BYTE (строка」 IN VARCHAR2) RETURN VARCHAR2
Тип данных возвращаемого функцией TO_MULTI_BYTE значения всегда совпадает с типом данных ее входного значения. Пример использования этой функции приведен выше, в разделе, посвященном функции TO_MULTI_BYTE.
функция TRANSLATE
Является разновидностью функции REPLACE. Напомним, что последняя заменяет в строке все вхождения заданной подстроки другой подстрокой, то есть она обрабатывает не отдельные символы, а их последовательности. В отличие от нее функция TRANSLATE выполняет указанное действие посимвольно, заменяя и-й символ искомой последовательности m-м символом замещающей последовательности. Ее синтаксис таков:
FUNCTION TRANSLATE (.CTpoKaJ IN VARCHAR2.
искомдя_последовательность IN VARCHAR2.
3dMeщающал^юснеповатепьность IN VARCHAR2)
RETURN VARCHAR2-
Здесь с трока JI — это строка, символы которой должны быть заменены; искомая^ последов а тель нос гь — символы, которые должны быть заменены (если они присутствуют в строке_1у, з амещающая_последов а гель нос ть — символы, на которые производится замена. В отличие от функции REPLACE, последний аргумент которой может быть опущен, для функции TRANSLATE необходимы все три аргумента. Однако любой из них может быть равен NULL, и тогда TRANSLATE вернет NULL.
Приведем несколько примеров использования функции TRANSLATE:
TRANSLATE ('абвг', 'аб'. '12') -> '12аг'
TRANSLATE ('12345'. '15', 'хх') т 'х234х'
TRANSLATE (1 какой чудесный день', 'аое'. '?$*') т 'кЖк$й ч*д*сный д*нь'
TRANSLATE ('какой чудесный пень', 'аоеь', 'X']
'кХкй чудсный пн':
TRANSLATE ('какой чудесный я', 'а'. NULL) т NULL
Просмотрев эти примеры, вы могли бы сделать ряд заключений о правилах работы с функцией TRANSLATE сомостоятельно, но мы, конечно, опишем их.
О Если искомая строка содержит не найденный функцией символ, никакая замена символов не производится.
Q Если строка содержит символ, не найденный в искомой последовательности, тогда этот символ не заменяется.
Q Если в искомой последовательности больше символов, чем в заметающей, тогда символы, которые имеются в искомой последовательности и отсутствуют в замещающей, просто удаляются из исходной строки. В следующем примере буквы а, 5 и в заменяются на э, ю и я соответственно. А буква z удаляется из строки, так как в замещающей последовательности для нее нет символа замены. TRANSLATE ('абвгдежзл', 'абвг'. 'зюя') т 'зюядежзл'
В таких случаях для всех остальных символов искомой последовательности замещающим символом является NULL. Заменить символ на NULL — это то же самое, что просто удалить его из строки.
О Если значение любого из трех аргументов равняется NULL, функция возвращает NULL. Это соответствует сути данного значения: в результате операции над неизвестным значением получается неизвестное значение.
Функция TRANSLATE используется и в тех случаях, когда нужно заменить в строке некоторое подмножество символов независимо от порядка их расположения.
Функция TRANSLATE...USING
Поддерживается для совместимости со стандартом ANSI. Функция преобразует данные одного набора символов в данные другого. Ее синтаксис таков:
TRANSLATE (тея^г USING {CHAR_CS | NCHAR_CS})
Здесь текст — это текст, который вы хотите преобразовать. Им может быть символьная переменная, выражение или литералъно заданная текстовая строка. Аргумент NHAR_C3 указывает, что входной текст должен быть преобразован в символы базы данных, а аргумент NCHAR_CS — что он должен быть преобразован в символы национального набора.
Выходным типом данных является либо VARCHAR2, либо NVARCHAR2, в зависимости от того, с какай целью выполняется преобразование — достичь соответствия набору символов базы данных или национальному набору.
Как пользоваться этой функцией, демонстрирует такой простой пример:
DECLARE
a VARCHAR2C30) 'Алфавит1;
b NVARCHAR2(30):'
BEGIN
Ь 尸 TRANSLATE(a USING NCHAR_CS); -
END;"
В этом примере символы строки представлены символами набора базы данных. С помощью функции TRANSLATE.. .USING они преобразуются в символы национального набора.
ПРИМЕЧАНИЕ
В Orade9i можно просто присвоить значение VARCHAR2 переменной типа NVARCHAR2 и наоборот— и система неявно выполнит нужное преобразование. Если же вы хотите сделать это явно, можно, воспользовавшись функцией TO_CHAR или TO_NCHAR/ преобразовать текст в набор символов базы данных или национальный набор символов соответственно. Oracle рекомендует пользоваться именно указанными функциями, а не TRANSLATE...USING, поскольку они поддерживают более широкий набор входных типов данных.
Функция TRIM
Была введена в версии Oracle8z для обеспечения более полной совместимости со стандартом ANSI SQL, сочетает в себе действия функций LTRIM и RTRIM. Функция TRIM немного отличается от остальных SQL-функций тем, что позволяет вместо аргументов использовать ключевые слова. Чего же еще ждать от функции, разработанной комитетом?
Функция TRIM так же не обычна, как TRANSLATE... USING. Приведем ее синтаксис:
TRIM ([LEADING | TRAILING | BOTH] [удаляемыИ_синвол'] FROM строка)
Здесь строка — это строка, которую необходимо обрезать; удаляемый_символ — символ, который вы хотите удалить из одного или обоих концов строки. По умолчанию удаляются пробелы. Аргумент LEADING 丨 TRAILING | BOTH указывает место, где вы хотите удалить заданные символы: в начале строки, в конце или с обеих сторон. По умолчанию строка обрезается с обеих сторон.
Приведем несколько примеров использования функции TRIM.
О Удаление из строки ведущих и завершающих пробелов:
TRIMC' Удаляем ведущие и завершающие пробелы. '); т 'Удалить ведущие и завершающие пробепм '
О Удаление только ведущих пробелов:
х;- ' Удаляем только ведущие пробелы. ' TRIMCLEADING FROM х);
т 'Удаляем только ведущие пробелы. 1
О Удаление завершающей точки:
X:- 'Удаляем точку.'
TRIMCTRAILING у FROM х);
т 'Удаляем точку'
Если функция TRIM совместима со ANSI-стандартом, стоит ли вообще использовать функции LTRIM и RTRIM? Стоит. С помощью функции TRIM можно удалить только один символ, а с помощью LTRIM и RTRIM — последовательность символов. Например:
RTRIMC'Удаляем последовательность символов т 'Удаляем последовательность символов'
Один вызов RTRIM позволил удалить из строки все завершающие знаки препинания. Функция TRIM такого делать не умеет. Можно, конечно, написать рекурсивную функцию PL/SQL, которая вызывала бы функцию TRIM для каждого возможного знака препинания, но зачем?
Функция UNISTR
Выполняет операцию, противоположную той, что выполняет функция ASCIISTR,— преобразует строку в символы набора Unicode. Непечатные символы строки можно задавать с помощью синтаксиса \XXXX, где ХХХХ представляет значение кодового индекса Unicode. Синтаксис функции UNISTR таков:
FUNCTION UNISTR (строкаJ IN VARCHAR2) RETURN YARCHAR2
Вот пример ее использования:
BEGIN
DBMS^OUTPUT.PUT_LINE(
UNISTR ('Символ \00E3 не является ASCII-сиквелом.'}
ЕМ):
'Символ ci не является ASCII-символон.'
ПРИМЕЧАНИЕ
Чтобы узнать значения кодовых индексов набора Unicode, можете обратиться по адресу: http://uni- code.Qrg/cherts/
Функция UNISTR предоставляет доступ к полному набору символов Unicode, включая те, которые нельзя непосредственно вводить с клавиатуры.
Функция UPPER
Переводит все буквы заданной строки в верхний регистр. Как и LOWER, эта функция перегружена, и для входной строки фиксированной длины она возвращает строку фиксированной длины, а для входной строки переменной длины - строку переменной длины. Вот ее синтаксис:
FUNCTION UPPER (строка」 IN CHAR) RETURN CHAR
FUNCTION UPPER {строк^ IN VARCHAR2) RETURN VARCHAR2
бункция UPPER не меняет символов, которые не являются буквенными, например цифр и специальных символов, таких как знак доллара.
Приведем несколько примеров использования функции UPPFR:
UPPERC'Mockbhm 412') т 'МОСКВИЧ 412'
UPPERCземля') т 'ЗЕМЛЯ'
Функции LOWER и UPPER можно применять для согласования регистров при сравнении строк.
Функции NLS
Oracle поддерживает ряд строковых функций, предназначенных для работы с набором символов национального языка (табл. 8.4). Эти функции используются в тех случаях, когда возвращаемый результат зависит от конкретного набора символов. Например, если в качестве набора символов базы данных используется ASCII, а в качестве национального набора символов — EBCDIC, с кодом 65 в этих наборах связаны разные символы. Для преобразования кода 65 в символ набора базы данных применяется функция CHR, а для преобразования этого же кода в символ национального набора - NLS-функция NCHR.
Таблица 8.4. Функции для поддержки символов национального языка
Имя
Описание
NCHR
Возвращает символ национального набора, соответствующий заданному коду
NLS」NITCAP
Переводит первую букву каждого слова в верхний регистр, а все остальные буквы — в нижний в соответствии с правилами национального языка
NLS.LOWER
Переводит все буквы строки в нижний регистр в соответствии с правилами национального языка
NLS.UPPER
Переводит все буквы строки в верхний регистр в соответствии с правилами национального языка
NLSSORT
Возвращает строку байтов, которая может использоваться для сортировки строки в соответствии с правилами национального языка
TO_NCHAR
Преобразует символы набора базы данных в символь! национального набора
Функция NCHR
Похожа на функцию CHR, но возвращает символ национального набора. Ее синтаксис таков:
FUNCTION NCHR (ад IN NUMBER) RETURN NVARCHAR2
Значение, возвращаемое функцией NCHR, присваивается переменной типа NVAR- CHAR2 или NCHAR2, Если присвоить его переменной другого типа, скорее всего, будет выполнено нежелательное преобразование. Приведем пример использования функции NCHR:
DECLARE
х NVARCHAR2(30): BEGIN
X 尸 NCHRC65):
В функции NCHR можно применить предложение USING NCHAR_CS, служащее той же цели, что и в функция CHR.
Функция NLS_INITCAP
Изменяет регистр символов строкового аргумента, переводя первую букву каждого слова в верхний регистр, а оставшиеся буквы — в нижний, другими словами, делает то же, что и функция INITCAP. Кроме того, данная функция позволяет производить сортировку в соответствии с правилами определенного языка. Ее синтаксис таков:
NLS_[NITCAP Сстрока_1 [, 1 ^5_$0^~имя_роследователъиосп«портировки'])
Приведенный ниже пример показывает различие между функциями INITCAP и NLSJNITCAP: begin"
DBMS-OUTPUT.PUTJINEdNITCAPC'ijzer'));
DBMS_OUTPUT.PUT_LINE(NLS_INITCAP('Ijzer'. 'NLS_SORT=XDUTCH'));
END; ~~~"
.Вот что выводит этот код:
Ijzer IJzer
В голландском (Dutch) языке пара символов <ij» считается одним символом. Благодаря спецификации NLS_SORT функция NLSJNITCAP правильно распознает указанные символы и преобразует голландское слово《ijzer» (означающее «железо») в это же слово, написанное с большой буквы.
Функция NLS」NITCAP возвращает значение типа VARCHAR2. Ее входное значение может содержать символы любого набора.
функция NLS.LOWER
Переводит строку в нижний регистр в соответствии с правилами конкретного языка. Синтаксис функции таков:
NL$_LOWER (строка」[.'NLS_SORT=MM»_nocnej!/osa тель нос та_сор тировки'])
Спецификация NLS_SORT была описана выше, в разделе, посвященном функции NLS INITCAP.
Функция NLS_UPPER.
Переводит строку в верхний регистр в соответствии с правилами конкретного языка. Синтаксис функции и принцип ее использования такие же, как у NLS_LOWER.
Функция NLSSORT
Возвращает строку байтов, которая может использоваться для сортировки строкового значения в соответствии с правилами конкретного языка. Возвращаемая строка имеет тип данных RAW. Синтаксис функции NLSSORT таков:
NLSSORT (строкз」[,1 ^.5_30^шуоследовательности^:ортировки,])
Приведем пример использования функции NLSSORT для сравнения двух строк в соответствии с правилами конкретного языка:
IF NLSSORKx, 'NLSJORT-XFRENCH') > (у. 1 NLS_SORT-XFRENCH') THEN...
В этом случае оператор IF применяется для проверки того, действительно ли строка х больше строки у. При проведении простого сравнения, х > у, результат зависит от того, будет ли двоичное представление х больше двоичного представления у. В некоторых языках значения двоичных представлений символов не соответствуют тем, которые используются при сортировке. Строки RAW, возвращаемые функцией NLSSORT, позволяют сравнить два значения на основе порядка сортировки символов языка.
Функция TO_NCHAR
Перегруженная функция, которая может использоваться для преобразования чисел, значений даты и символьных строк набора базы данных в символьные строки национального набора. С числами и датой функция TO_NCHAR работает так же, как функция TO_CHAR, описанная в главе 9. Единственным отличием является то, что последняя возвращает значение, имеющее тип данных NVARCHAR2, а не VARCHAR2.
Описываемая в этом разделе версия функции TO_NCHAR преобразует данные из набора символов базы данных в символы национального набора. Назначение функции TO_NCHAR противоположно назначению функции TO_CHAR, описанной выше, в разделе, посвященном символьным функциям. Ее синтаксис таков:
FUNCTION TO_NCHAR (символьиые_дднные IN VARCHAR2) RETURN NVARCHAR2
Функция TO_NCHAR может принимать на входе значения следующих типов данных: CHAR, VARCHAR2, CLOB и NCLOB. Возвращаемое значение всегда имеет тип NVARCHAR2.
Приведем пример использования функции TO_NCHAR для преобразования строки символов базы данных в строку символов национального набора:
DECLARE'
a VARCHAR2(30] .- 'Алфааит':
b NVARCHAR2(30):
BEGIN'
b TOJCHAR(a):
END; ~
Если вы планируете применять функцию TO_NCHAR, вернитесь к описанию функции TRANSLATE.. .USING в этой главе. Кроме того, имейте в виду, что TO_NCHAR может использоваться так же, как функция TO_CHAR, для представления значений даты, времени и чисел в читабельной форме. Об этом рассказывается в главах 9 и 10.
9
Числа
А Числовые типы данных
А Числовые преобразования
А Числовые функции
И что бы мы делали без чисел? Хотя люди, которые не любят математику, предпочитают рассматривать любую информацию как текст, реальность такова, что большая часть информации в базах данных представляет собой числа. Каковы запасы на складе? Сколько мы должны? Насколько вырос бизнес? Наиболее точные ответы на эти и многие другие вопросы можно получить именно в числовом выражении.
Для работы с числами в PL/SQL нужно изучить:
О числовые типы данных, имеющиеся в вашем распоряжении;
О преобразование числовых значений в текст и обратно;
О богатую библиотеку встроенных числовых функций PL/SQL.
Эти темы рассматриваются в настоящей главе. Начнем с типов данных.
Числовые типы данных
PL/SQL, как и РСУБД Oracle, предлагает множество числовых типов данных, предназначенных для решения конкретных задач. В их основе лежат три типа — NUMBER, PLS_INTEGER, BINARY_INTEGER,
В программах встречаются и другие числовые типы данных, например FLOAT м DECIMAL, но по сути они представляют собой альтернативные имена перечисленных базовых типов данных.
Числовые типы данных Oracle реализованы таким образом, что они идентично функционируют на всех поддерживаемых Oracle платформах, благодаря чему повышается переносимость баз данных и приложений. Например, при делении чисел типа NUMBER округление выполняется одинаково точно на аппаратном обеспечении Intel и Sun. Такая переносимость достигается за счет того, что вместо аппаратно-ориентированных математических функций в Oracle используются собственные математические подпрограммы. Исключением является тип данных PLS—INTEGER, скорость обработки значений которого повышена в ущерб независимости от аппаратной платформы.
Тип данных NUMBER
Это самый распространенный тип данных, с которым вам придется встречаться в Oracle и PL/SQL, и единственный числовой тип, непосредственно поддерживаемый ядром базы данных Oracle. Его значимость по сравнению с другими числовыми типами данных весьма велика, поскольку для работы с числами можно использовать только его, не прибегая к помощи остальных типов. Тип данных NUMBER поддерживает следующий диапазон абсолютных значений:
от 1 X 10~127 ДО 9,9999999999999999999999999999999999999 х 10121 (38 девяток)
ПРИМЕЧАНИЕ
Таким был диапазон допустимых значений при тестировании версии Orade9i Release 1. В Oracle9i SQL Reference он определен следующим образом: от 1,0 х 10'130 до 9,99...9 х 10125 (38 девяток).
Простейпхий способ объявить переменную типа NUMBER — задать ключевое слово NUMBER:
DECLARE
х NUMBER:■
В результате вы получите переменную, значением которой является число с плавающей запятой. Память, выделяемая Oracle для переменной, позволяет хранить
NUMBER(9,2)
❶ Общее количества значащих цифр в числе равно 9
1——9999999,99
❸ Отсчитав алмо две позиции,丿:I❷ Количество цифр после запятой
ставим десятичную запятуюiзадано положительным значением,
поэтому отсчитывать следует влево с этой позиции.
Рис. 9.1. Типичное объявление числа с фиксированной запятой
В результате объявления переменной как NUMBERC9.2) ее значением является число с фиксированной запятой, состоящее из семи цифр слева от десятичной запятой и двух справа, как показано на рис. 9.1. Такая переменная может хранить значения до десяти миллионов. Они будут округляться максимум до сотых (табл. 9.1).
Таблица 9.1. Округление значений переменной, объявленной как NUMBER(9,2)
Исходное значение
Округленное значение, хранящееся в переменной
1 234,56
1 234 567,984623
1 234 567,985623
1 234 567,995623
10 000 000,00
1 234,56
1 234 567,98-
1 234 567,99
1 234 568,00
Значение слишком большое для данной переменной. При попьпхе присвоить его переменной генерируется ошибка переполнения
-10 000 000,00
Число слишком маленькое для данной переменной. При попытке присвоить его переменной генерируется ошибка потери значимости (отрицательного переполнения)
Попытки присвоить переменной последние два из указанных в таблице значений вызывают ошибки переполнения и потери значимости, поскольку для представления этих значений требуется больше цифр, чем помещается в переменной. Для хранения значений свыше десяти миллионов нужно минимум восемь значащих цифр слева от десятичной запятой. При округлении чисел до семи цифр будут генерироваться ошибки.
Результат объявления переменной с количеством цифр после десятичной запятой, большим, чем общее количество значащих цифр, приведен на рис. 9.2. Если количество цифр после десятичной запятой задано отрицательным значением, то результат будет другим (рис. 9.3).
Переменная, показанная на рис. 9.2, содержит то же количество значапщх цифр, нт。и переменная на рис. 9.1, но используются они по-другому. Поскольку параметр В равен И, девять значащих цифр могут представлять только абсолютные значения меньше 0,01, которые округляются до стомиллиардных. Результаты присваивания нескольких значений переменной типа NUMBER(9,11) приведены в табл. 9.2.
❶ Общее количество значшцих цифр равно 9❶ Общее количество значшцих цифр равно 9NUMBER(9,11)NUMBER(9,11)I-.00999999999宀'ааааааааааааQ Десятичная зепятая !! = ::!;;;:: оказьваелтся иа дев 11 Ю 9 8 7 6 5 4 3 2 1 позиции левее девяти значе<Д£/х цифр i : 5 • ; 5 = ^ ;;I-.00999999999宀'ааааааааааааQ Десятичная зепятая !! = ::!;;;:: оказьваелтся иа дев 11 Ю 9 8 7 6 5 4 3 2 1 позиции левее девяти значе<Д£/х цифр i : 5 • ; 5 = ^ ;;❸ Отсчитываем влево И позиций и добавляем нули в те позиции, которые находятся после значащих цифр❸ Отсчитываем влево И позиций и добавляем нули в те позиции, которые находятся после значащих цифрКоличество цифр после десятичной запятой забено положительным значением ("J, поэтому отсчитьеать следует с этой позиции влевоКоличество цифр после десятичной запятой забено положительным значением ("J, поэтому отсчитьеать следует с этой позиции влевоРис. 9.2. Количество цифр после десятичной запятой больше общегоколичества значащих цифрРис. 9.2. Количество цифр после десятичной запятой больше общегоколичества значащих цифр❶ Общее Kanwecmeo значащих цифр рмно 9❶ Общее Kanwecmeo значащих цифр рмно 9NUMBER(9,-11)NUMBER(9,-11)
❶ Общее количество значшцих цифр равно 9
❶ Общее количество значшцих цифр равно 9
NUMBER(9,11)
NUMBER(9,11)
I-.00999999999
宀'аааааааааааа
Q Десятичная зепятая !! = ::!;;;:: оказьваелтся иа дев 11 Ю 9 8 7 6 5 4 3 2 1 позиции левее девяти значе<Д£/х цифр i : 5 • ; 5 = ^ ;;
I-.00999999999
宀'аааааааааааа
Q Десятичная зепятая !! = ::!;;;:: оказьваелтся иа дев 11 Ю 9 8 7 6 5 4 3 2 1 позиции левее девяти значе<Д£/х цифр i : 5 • ; 5 = ^ ;;
❸ Отсчитываем влево И позиций и добавляем нули в те позиции, которые находятся после значащих цифр
❸ Отсчитываем влево И позиций и добавляем нули в те позиции, которые находятся после значащих цифр
Количество цифр после десятичной запятой забено положительным значением ("J, поэтому отсчитьеать следует с этой позиции влево
Количество цифр после десятичной запятой забено положительным значением ("J, поэтому отсчитьеать следует с этой позиции влево
Рис. 9.2. Количество цифр после десятичной запятой больше общегоколичества значащих цифр
Рис. 9.2. Количество цифр после десятичной запятой больше общегоколичества значащих цифр
❶ Общее Kanwecmeo значащих цифр рмно 9
❶ Общее Kanwecmeo значащих цифр рмно 9
NUMBER(9,-11)
NUMBER(9,-11)
99999999900000000000,
лаалалала444
❷ Снова отсчитываем от крайней слввв цифры. В данном случае количество цифр после десятичной запятой задеио отрицательньм значением, поэтому считаем вправо❷ Снова отсчитываем от крайней слввв цифры. В данном случае количество цифр после десятичной запятой задеио отрицательньм значением, поэтому считаем вправо12 3 * S 6 7 8 S 10 :1 ❹^幾師 g
❷ Снова отсчитываем от крайней слввв цифры. В данном случае количество цифр после десятичной запятой задеио отрицательньм значением, поэтому считаем вправо
❷ Снова отсчитываем от крайней слввв цифры. В данном случае количество цифр после десятичной запятой задеио отрицательньм значением, поэтому считаем вправо
.;;;.;,((;, очень оопьшое число,:;;? J ! ; : ; : Iокругленное до сотан
миллиардов
❸ Отсчитьваем ” позицийи добавляем нули
Рис, 9.3. Количество цифр после десятичной запятой задан。отрицательным значением
Таблица 9.2. Округление значений переменной, объявленной как NUMBER(9,11)
Исходное значение
Округленное значение, хранящееся в переменной
0,001 234 567 89
0,000 000 000 005 0г000 ООО 000 004
0,01
0,001 234 567 89.
0,000 000 000 01
0,000 000 000 00
Значение слишком большое для данной переменной; для его записи в переменную в позиции сотых требуется значащая цифра, при попытке присвоить его переменной генерируется ошибка переполнения.
-0,01
Число слишком маленькое для данной переменной; для его записи в переменную в позиции сотых требуется значащая цифра. При попытке присвоить его переменной генерируется ошибка потери значимости (отрицательного переполнения)
Если количество цифр после десятичной запятой задано отрицательным значением, то десятичная запятая переносится вправо. Переменная, объявленная как NUMBER(9,-11), показана на рис. 9.3.
Мы задали девять значащих цифр, но, как видно из табл. 9.3, теперь вместо малых значений вплоть до стомиллиардных наименьшим значением, которое может содержаться в переменной, стало 100 миллиардов.
Отрицательное значение параметра, который определяет количество цифр после десятичной запятой, позволяет представлять очень большие значения, как показывают рис. 9.3 и табл. 9.3. Это осуществляется только за счет потери данных, находящихся в младших разрядах. При записи в переменную, объявленную как NUMBER(9,-11), любое абсолютное значение меньше 50 триллионов округляется до нуля.
Таблица 9.3. Округление значений переменной, объявленной как NUMBER(9,-11)
Исходное значенияОкругланное значение, хранящееся
а переменной
50 000 000 000Д23100 000 000 000
49 999 999 999,9990
150 000 975 230,001150 000 000 000 000
100 000 000 000 000 000 000 или 1x10го Число слишком большое для данной переменной;
для его записи в переменную в позиции сотен квинтиллионов требуется значащая цифра. При попытке присвоить его переменной генерируется ошибка переполнения
-100 000 000 000 000 000 000 или -1x10го Число слишком маленькое для данной переменной;
для его записи в переменную в позиции сотен квинтиллионов требуется знающая цифра. При попытке присвоить его переменной генерируется ошибка потери значимое (отрицательного
Учтите, что при объявлении переменных типа NUMBER количество цифр после десятичной запятой не является обязательным параметром и по умолчанию равняется нулю. Например, следующие два объявления эквивалентны:
х NUMBER С9.0):
х NUMBER С9):
В результате любого из них создается целочисленная переменная, в которой могут храниться числа от -999 999 999 до 999 999 999.
При таком широком диапазоне допустимых значений и разнообразных возможностях не удивительно, что тип данных NUMBER является столь распространенным. Используя в объявлениях только ключевое слово NUMBER, можно создать переменные с плавающей запятой, а задавая рассмотренные параметры — переменные с фиксированной запятой. Если количество цифр после десятичной запятой Указать равным нулю или не задать вовсе, получится целочисленная переменная. Таким образом, один тип данных NUMBER покрывает все возможные варианты числовых значений.
Тип данных PLS_INTEGER '
Поддерживается версией PL/SQL Release 2.3 и выше. Позволяет хранить целые числа в диапазоне от -2 147 483 647 до 2 147 483 647. Эти значения хранятся в «родном» целочисленном формате аппаратной платформы. Ниже приведено несколько примеров объявлений переменных типа PLS_INTEGER:
DECLARE
lQQp_cuonter PLS_INTEGER;
days_in_standardCONSTANT PLS_INTEGER :- 365;
emp_vacation_days PLS_INTEGER DEFAULT 14;
Тип данных PLS—INTEGER был разработан для увеличения скорости вычислений. При выполнении арифметических операций над значениями данного типа программное обеспечение Oracle использует встроенную машинную арифметику, тогда как с другими числовыми типами Oracle работает посредством ахшарт- но-независимой библиотеки математических функций. В результате операции со значениями типа PLS_INTEGER выполняются быстрее операций со значениями NUMBER. А поскольку значения PLS_INTEGER целочисленные, проблем совместимости при переходе с одной платформы на другую из-за них практически не бывает.
Для достижения большей эффективности вычислений Oracle рекомендует всегда использовать тип PLS__INTEGER, если только значения не выходят за границы диапазона. Учтите, что в выражениях, где приходится выполнять частые преобразования в тип данных NUMBER и наоборот, лучше использовать только тип NUMBER. Тип данных PLS_INTEGER наиболее эффективен в целочисленной арифметике и для счетчиков пиклов, где он используется безо всяких преобразований.
Тип данных BINARY_INTEGER
Позволяет хранить целые числа со знаком в двоичном формате. В отличие от типа данных PLS_INTEGER не использует встроенную машинную арифметику; для операций со значениями типа BINARYJNTEGER применяет независимые от платформы библиотечные функции Oracle. В PL/SQL BINARYJNTEGER был первым двоичным целочисленным типом данных. Однако в настоящее время Oracle рекомендует использовать вместо него PLSJNTEGER. Поэтому BINARY_INTEGER следует применять только для достижения совместимости с программным кодом, написанным до выхода версии PL/SQL Release 2.3.
Значения типа BINARY_INTEGER расположены в диапазоне от -2 147 483 647 до 2 147 483 647. Использование этого типа данных вместо NUMBER обеспечивает ускорение вычислений при большом объеме операций с целочисленными значениями. Но в большинстве случаев эффект этого ускорения слишком незначителен.
Числовые подтипы
Oracle поддерживает ряд числовых подтипов данных. Большая их часть представляет собой альтернативные имена для трех описанных нами базовых типов данных. Подтипы введены для достижения совместимости с типами данных ANSI SQL, SQL/DS и DB2 и обычно имеют те же диапазоны допустимых значений, что и их базовые типы. Однако иногда значения подтипа ограничены некоторым
подмножеством значений базового типа. Подтипы числовых данных представлены в табл. 9.4.
Таблица 9.4. Предопределенные числовые подтипы данных
Подтип
Совместимость
Соответствующий тип данных Oracle
DEC (А, В)*
ANSI
NUMBER (A, B)*
DECIMAL (А, В)'
IBM
NUMBER (A, A)*
double PRECISION
ANSI
NUMBER
FLOAT
ANSI, IBM
NUMBER
FLOAT (A一 ДВ0ИЧН0Й_50ЧН0СТИ)*
ANSI, IBM
NUMBER, no NUMBER не может быть объявлен с пвраметром А двоичной точности*
INT
ANSI, NUMBER
INTEGER
ANSI, IBM
NUMBER
NATURAL.
BINARYJWTEGER
NATURALN
То *0 что И NATURAL, но с дополнительным ограничением — не допускаются значения NULL
NUMERIC (А, В)*
ANSI
NUMBER (А, В)*
POSITIVE
Аналогичен BINARY_INTEGER, но допускаются только положительные значения (1 и больше),
POSniVEN
То же, что POSITIVE, но с дополнительным ограничением — не допускаются значения NULL
REAL
ANSI
NUMBER
SIGWTYPE
BINARY-INTEGER
SMALLINT
ANSI, IBM
NUMBER3S)
Параметр Л — общее количество значащих цифр, параметр В — количество цифр после десятичной запятой.
Типы данных NUMERIC, DECIMAL и DEC позволяют объявлять только значения, представляющие числа с фиксированной запятой. Типы DOUBLE PRECISION и REAL эквивалентны типу NUMBER. С помощью типа данных FLOAT можно объявлять числа с плавающей запятой с двоичной точностью в диапазоне от 63 до 126 бит. Возможность определять размер значений в битах, а не цифрах обычно применяют редко, и, скорее всего, вам не придется использовать типы данных ANSI/IBM.
Очень удобно использовать подтипы данных BINARYJNTEGER, к которым относятся NATURAL и POSITIVE. Они ограничивают значения, которые могут храниться в переменной, и их применение делает программу более «самодокументирован- Ной». Например, в том случае, когда значения переменной не являются отрицательными, ее можно объявить как NATURAL (числа больше 0) или POSITIVE (числа больше 1). Но заметьте, что тип данных PLS_INTEGER более эффективен, чем BI- NARYJNTEGER и его подтипы.
Числовые преобразования
Компьютеры лучше работают с числами в двоичном представлении, тогда как люди предпочитают видеть числовые данные в виде строк, состоящих из цифр, запятых и пробелов. PL/SQL позволяет преобразовывать числа в строки и наоборот. Обычно для выполнения таких преобразований применяются функции Т0_ CHAR и TO_NUMBER. Для того чтобы использовать все возможности этих функций, нужно знать, что такое модели форматирования чисел и как они используются.
Модели форматирования чисел
Числовые форматы применяются при работе с функциями TO_CHAR и TO_NUMBER. Задавая числовой формат в вызове функции TO_CHAR, вы определяете, как преобразовать числовое значение в строку VARCHAR2. Таким образом можно задать знаки препинания, местоположение знака «+» или «-» и другие полезные элементы. И наоборот, задавая числовой формат в вызове TO_NUMBER, вы определяете, как следует интерпретировать строку при преобразовании ее в числовое значение.
Модель форматирования числа (маска форматирования) — это строка, состоящая из одного или более элементов форматирования, перечисленных в табл. 9.5. Строка, полученная в результате преобразования числового значения, будет соответствовать сочетанию указанных элементов. Примеры использования разных форматов приведены в описаниях функщий TO_CHAR и TO_NUMBER.
Элементы форматирования, описание которых начинается словом «Префикс:», могут применяться только в начале маски форматирования. Если описание начинается словом «Суффикс», такой элемент может использоваться только в конце маски. Для большинства элементов указано их влияние на преобразования числа в строковое представление. Однако учтите, что большая их часть может использоваться и в обратной операции, то есть для определения способа преобразования строки в число.
Таблица 9.5. Элементы модели форматирования
Эл ❹ мент форматирования
Описание
9
Представляет возвращаемую значащую цифру. Нулевые старшие разряды числа выводятся как пробелы (ведущие и завершающие)
0
Представляет возвращаемую значащую цифру, Нулевые старшие разряды числа выводятся как нули
$
Префикс; выводит перед числом знак доллара
В
Префикс; возвращает нулевое значение как пробел, даже если используется элемент форматирования 0
MI
Суффикс: помещает после отрицательного числа знак «-». Если число положительно, после него добавляется пробел
S
Префикс: помещает перед положительным числом знак «+», а перед отрицательным знак «-»
Элеиеит форматирования
Описание
PR
Суффикс: заключает отрицательное значение в угловые скобки (< и >), Положительные значения дополняются одним пробелом в начале и конце
D
Определяет для возвращаемого значения местоположение десятичной запятой. Все элементы, которые находятся слева от D, задают формат целочисленной части значения. Символ, используемый в качестве десятичной запятой, определяется параметром базы данных NLS_NUMERIC一CHARACTERS
G
Определяет для возвращаемого значения местоположение разделителя групп разрядов, например запятой, отделяющей разряд тысяч, как в числе 6,754 (число 6754, согласно форме записи, принятой в США). Символ, используемый в качестве разделителя групп разрядов, определяется параметром базы данных NI_S_NUМERIC_CHARACTERS
С
Указывает для возвращаемого значения местоположение символа обозначения валюты ISO, который определяется параметром базы данных NLS_ISO_CURRENSY
L
Указывает для возвращаемого значения местоположение текущего ■ символа валюты, который определяется параметром базы данных NLS_CURRENSY
,(запятая)
Указывает, что в этой позиции возвращаемого значения должна стоять запятая, которая применяется а качестве разделителя групп разрядов (см. элемент форматирования G)
.(точка)
Определяет, что в данной позиции в возвращаемом значении должна стаять точка. Эта точка, согласно форме записи числа, принятой в США, используется в качестве десятичной запятой (см. элемент форматирования D)
V
Умножает число, расположенное в маске форматирования слева от символа V, на 10 в степени п, где п — число, обозначаемое в маске форматирования девятками после символа V
ЕЕЕЕ
Суффикс; указывает, что значение должно быть возвращено в научном формате (в экспоненциальном представлении)
RN или гп
Определяет, что возвращаемое значение должно быть выведено римскими цифрами верхнего или нижнего регистра. Так можно выводить числа от 1 до 3999. Значение должно быть целочисленным. RN возвращает число, записанное римскими цифрами в верхнем регистре, а гл — в нижнем
FM
Префикс; удаляет пробелы возвращаемого значения из старших или младших разрядов числа
тм
Префикс; возвращает число, используя минимальное количество символов. (ТМ означает «text minimum».) Для десятичного представления после ТМ ставится цифра 9 (по умолчанию^ для экспоненциального — буква Е
и
Помещает в заданную позицию символ евро, который определяется параметром базы данных NLS_DUAL_CURRENCY
X
Возвращает число в виде шестнадцатеричного значения. Этот элемент можно предварить нулями, чтобы вернуть нулевые старшие разряды числа, или элементом FM, чтобы удалить ведущие и завершающие пробелы, Элемент X не может использоваться в сочетании с другими элементами форматирования
Обратите внимание на то, что в некоторых случаях одинаковое форматирование можно задать с помощью двух разных элементов. Например, знак доллара, запятая и точка по своему действию аналогичны буквенным элементам L, G и 0. Однако буквенные элементы учитывают текущие установки NLS, и вы получите символы, соответствующие используемому языку. Например, в одних языках для разделения дробной части числа применяется точка, а в других — запятая. Знак доллара, запятая и точка ориентированы на стандарты США и всегда задают именно эти символы. Мы рекомендуем использовать NLS-ориентированные элементы •форматирования — L, G и D.
ОБОЗНАЧЕНИЕ ДЕНЕЖНЫХ ЕДИНИЦ
Элементы форматирования $, L, С и U, которые можно использовать для обозначения денежных единиц, показаны в табл. 9.5. Их различие состоит в следующем:
элемент $ ориентирован на национальную валюту США и всегда возвращает знак доллара;
элемент L учитывает текущую установку параметра NLS_CURRENSY, определяющего соответствующий символ валюты. Если значением параметра NLS_TERRITORYявляется United Kingdom, параметру NLS_CURRENSY будет соответствовать символ «£>, и элемент форматирования L задает использование в качестве символа валюты《£»;
элемент форматирования С подобен элементу L, но применяет ISO-обозначение валюты, указанное в параметре NLS_ISO_CURRENSY. Например, для США — это USD и т. д.;
элемент форматирования U добавлен для поддержки евро. Он использует значение параметра NLS_DUAL_CURRENCY. Для стран, валютой которых является евро, в данном параметре в качестве символа валюты задан евро (по умолчанию (€)).
Данные о текущих установках параметров NLS_CURRENSY и NLS_ISO_CURRENSY находятся в системном представлении NLS_SESSION_PARAMETERS.
Функция TO_NUMBER
Преобразует строки фиксированной и переменной длины в числа. Используйте эту функцию, когда нужно преобразовать строку, представляющую число, в соответствующее числовое значение. Синтаксис ее вызова следующий:
T0_NUM8ERCcrpOK(? [. формат^, napanerpujils^)
Здесь строка — это строка, содержащая символьное представление числа; формат — необязательная маска форматирования, которая определяет, как функция TO_NUM- BER должна интерпретировать символьное представление числа, содержащегося в первом параметре; паранетры_п15 — необязательная строка, которая содержит значения параметров NLS. Ее можно применять для подмены текущих установок параметров NLS уровня сеанса.
Использование функции TO_NUMBER без параметров форматирования
ЧасТо функция TO_NUMBER вызывается без строки форматирования. Например, следующие преобразования выполняются без использования дополнительных параметров:
declare
a NUMBER:
b NUMBER:
с NUMBER:
d NUMBER;
nl VARCHAR2C2Q) := '-123456,78':
n2 VARCHAR2C20)产'+123456,70':
BEGIN
a :- TO_NUM0ER('123,45');
b 尸 TO_NUM0ER(nl);
c :- TO2NUMBER(n2):
d :- TOJUMBERCl.ZSEP');
END; ~
Как правило, функцию TO NUMBER можно вызвать без параметров форматирования в следующих случаях:
О когда число представлено только цифрами с единственной десятичной запятой;
О при использовании научного формата — например 1.25Е2;
О когда знак числа (плюс или минус) стоит перед числом; при отсутствии знака число считается положительным.
Если символьная строка не соответствует этим критериям или нуасно округлить значения до заданного количества десятичных знаков, вызывайте функцию TO NUMBER с маской форматирования.
Использование функции TO_NUMBER с маской форматирования
Применение функции TO_NUMBER с маской форматирования позволяет получить более разнообразные представления чисел. Например, можно задать местоположение разделителей групп и символа валюты:
а ;= TO_NUM0ER('$123 456,78'. 'L999G999D991):
Не обязательно указывать в строке форматирования точное количество цифр. Функция TO—NUMBER позволяет задать в строке форматирования больше цифр, чем в преобразуемом значении. Следующая строка кода также будет выполнена без ошибки;
а :в ТОJUMBERC'$123 456,70'. 'L999G999G999D99');
Но если справа или слева от десятичной запятой значение содержит больше цифр, чем допускает маска форматирования, то преобразование не будет выполнено, и вы получите сообщение об ошибке ORA-06502: PL/SQL: numeric or value error. Первое из следующих двух преобразований завершится ошибкой, поскольку строка содержит десять цифр слева от десятичной запятой, тогда как маска разрешает только девять. Второе преобразование завершится ошибкой из-за того, что строка содержит слиткам много цифр справа ат десятичной запятой:
а :- T0_NUM8ER('$1234 567 890,78'. 'L999G999G999D99'):
а :- T0jUMBER('$234 567 890.780'. 'L999G999G999D99'):
Используя элемент форматирования 0, можно правильно обработать нулевые старшие разряды числа:
а :- TOJUMBERC'OOl 234'. 'OOOG00O'):
С помощью элемента PR можно распознать угловые скобки как обозначение отрицательного числа:
а :- T0_NUMBER('<123.45>'. '999099PR'):
Однако не все элементы форматирования предназначены для преобразования строк в числа. Например, элемент RN, который применяется для вывода числа римскими цифрами, предназначен только для форматирования выводимой информации. Поэтому следующая попытка преобразования вызовет ошибку:
а :- TO_NUMBER('cxxi1i'. 'гп');
Элемент ЕЕЕЕ тоже используется только для форматирования вывода. Для обратного преобразования он не нужен, поскольку функция TO_NUMBER распознает числа в научном формате безо всяких дополнительных указаний:
а :- T0_NUMBER('1.23456E-24');
Передача функции TO_NUMBER установок NLS
Действие многих элементов форматировалия, перечисленных в табл. 9.5, определяется текущими установками параметров NLS. Например, элемент G представляет разделитель групп разрядов, но какой именно символ используется в качестве разделителя — зависит от текущего значения параметра NLS_NUMERIC_CHARACTER5 в момент выполнения преобразования. Для того чтобы просмотреть эти установки, можно запросить представление NLS_SESSION_PARAMETERS:
SQL> SELECT * FROM nls_sess1on_parameters
PARAMETER
VALUE
NLS LANGUAGE
AMERICAN
NLS TERRITORY
AMERICA
NLS CURRENCY
$
NLS ISO CURRENCY
AMERICA
NLS NUMERIC CHARACTERS
NLS CALENDAR
GREGORIAN
NLS DATE FORMAT
DD-MON-RR
Некоторые установки по умолчанию зависят от других. Если присвоить пара* метру NLS_TERRITORY значение AMERICA, Oracle по умолчанию установит параметр NLS_NUMERIC_CHARACTERS в '..1 (первый символ — десятичный разделитель, второй —
разделитель групп разрядов). Это не мешает явно присвоить параметру NLS_CUR- RENCY с помощью команды ALTER SESSION другое значение:
ALTER SESSION SET NLS_CURRENCY-'p.'
Иногда определенные параметры NLS требуется переустановить только на время вызова функции TO_NUMBER. В данном случае нужные установки задают в этом вызове, и действуют они исключительно для него. Например, в следующем операторе в вызове функции TO_NUMBER задаются установки NLS, соответствующие NLS.TER- RITORY-FRANCE:~~
а - ТО NUMBERCF123.456,78', '19996999099').
"1NLS NUMERI^CHARACTERS-''..1 "
II "NLS CURRENCY-"F'"
II ' NLSJSO^CURRENCY-FRANCE'):
Поскольку строка параметров NLS слишком длинная, мы разбили ее на три отдельных строки, объединенных с помощью оператора конкатенации, чтобы пример лучше выглядел на странице. Обратите внимание на дублирование кавычек. Установка параметра NLS_NUMER IC_CHARACTERS выглядит следующим образом:
NLSJUMERIC-CHARACTERS-1..'
Поскольку это значение вместе с кавычками включается в строку параметров NLS, каждую кавычку нужно продублировать. В результате получается строка: 'NLS.NUMERI^CHARACTERS-'1..1 "
В функции TO_NUMBER можно задавать только три приведенных в данном примере параметра NLS. Было бы удобнее, если бы можно было написать так:
а :- T0_NUMBER('F123.456.78,. 'L999G999D99'. 'NLSJERRITDRY-FRANCE'):
Но, к сожалению, в функции TO_NUMBER нельзя задать параметр NLS_TERRITORY. Она поддерживает только NLS_NUMERIC_CHARACTERS, NLS_CURRENCY и N【S_IS匚CURRENCY.
ПРИМЕЧАНИЕ
Более подробную информацию об установке различных параметров NLS вы найдете в документации Oracle9i в части Oracle's Globalization Support Guide.
He советуем задавать третий аргумент функции TO_NUMBER — лучше полагаться на установки сеанса, определяющие, как PL/SQL интерпретирует элементы маски форматирования L, G и D. Вместо того чтобы жестко кодировать информацию в программах, нужно дать возможность пользователю задавать их на уровне сеанса.
Функция TO_CHAR
Зта функция обратна функции TO_NUMBER. Она преобразует число в его символьное представление. Используя необязательную маску форматирования, можно подробно указать, каким должно быть представление. Функция T”HAR вызывается следующим образом:
TQ_CHAR (число [. формат^. пдрдметры_п15^)
Здесь число — это число, которое требуется представить в символьной форме. Он。может относиться к любому символьному типу PL/SQL: NUMBER, PLS_INTEGER иля 8INARY_INTEGER; формзт — необязательная маска форматирования, определяю- Ц^ая, как функция TO_CHAR должна представить число в символьной форме; пара - иегры_п15 — необязательная строка, содержащая значения параметров NLS. Ее можно применять для подмены текущих установок параметров NLS уровня сеанса.
ПРИМЕЧАНИЕ
Если вы хотите, чтобы результат был представлен с помощью национального набора символов, используйте вместо TO—CHAR функцию TO_NCHAR. При этом помните, что строка форматирования числа тоже должна быть представлена символами национального набора. Иначе результатом будет строка, состоящая из символов «#».
Использование функции TO_CHAR без маски форматирования
Функцию TO_NUMBER, как и TO_CHAR, можно вызывать без маски форматирования:
DECLARE
b VARCHAR2C30):
BEGIN
Ь 尸 TO_CHAR(123456789.0D:'
DBMS OUTPUT.Р1Я LINE(b):-
END: 一一
Данный код выводит следующие значения:
123456789.01
Однако в отличие от TO_NUMBER функцию TO CHAR редко вызывают таким образом. Чтобы число лучше читалось, нужно задать, как минимум, разделитель групп разрядов.
Использование функции TO_CHAR с маской форматирования
При преобразовании числа в символьное представление функция TO_CHAR используется чаще всего с маской форматирования. Например, с ее помощью можно вывести денежную сумму:
DECLARE
b VARCHAR2(30):
BEGIN
b TO_CHAR(123456789.01. 'L999G999G999D99'):
DBMS_OUTPUT.PLR_LINE(b);
END;.
Результат будет выглядеть следующим образом:
$123 456 789.01'
Элементы маски форматирования, описанные в табл. 9.5, позволяют очень гибко задавать формат символьного представления числа. Чтобы лучше понять, как они работают, стоит немного с ними поэкспериментировать. В следующем примере указывается, что в числе должны быть сохранены нулевые старшие разряды, но при этом элемент форматирования В требует превращения нулей в пробелы. Данный элемент предшествует цифровым элементам (нулям), но следует за. индикатором вывода обозначения валюты L:
DECLARE
b VARCHAR2C30):
BEGIN■
b := TO_CHARC123.01. 'LB000G000G009D99'):
DBMS OUTPUT.Р1Я LINE(b);
ь :-TD_CHAR(0. 'LB000G000G009D99');
DBMS_DUTPUT.PUT_LlNE(b);
END;
Результат будет иметь следующий вид:
$000 000 123,01
Вы видите только одну строку, полученную после первого преобразования, В результате второго преобразования получился нуль, и элемент форматирования В инициировал возврат функцией TO_CHAR пустой строки, хотя в маске форматирования указано, что нулевые старшие разряды числа следует оставить. В качестве эксперимента можно выполнить этот пример без элемента В,
ПРИМЕЧАНИЕ:
Не все комбинации элементов форматирования являются допустимыми. Например, нельзя использовать сочетание LRN, которое выводит перед числом, записанным римскими цифрами, символ валюты. Oracle не документирует таких нюансов, поэтому о некоторых из них можно узнать только посредством эксперимента.
Элемент форматирования V
Едва ли вы часто будете пользоваться элементом V, но знать о нем нужно. Этот элемент форматирования позволяет масштабировать значение, и его действие лучше показать на примере (рис. 9.4).
123,45-——999V9999
О Применение маски форметирования с элементом V к числу
529V#999
導29gg99
❷ Накладываем число,a формат. Десятичную запятую выравниваем с позицией элемента V
❸ Убаллем элемент У и десятичную запятую
安294g00 1234500
❹ Дописываем нули, чтобы результирующее каличестео значащих цифр получилось таким же, как в маисе форматирования
❺ Возвращаем результат
Рис. 9.4. Элемент форматирования V
Для чего может потребоваться масштабировать значение при преобразовании его в символьную форму? Рассмотрим следующий пример. Стандартная единица сделки на бирже равняется 100 акциям, и сообщая о реализованных на бирже акциях обычно говорят о количестве проданных пакетов по 100 акций. Поэтому 123 продажи означает 123 пакета по 100 акций, то есть 12 300 акций. Следующий пример показывает, как использовать элемент V для масштабирования значения 123, учитывая, что на самом деле оно представляет количество сотен: declare
shares_sold NUMBER := 123:
BEGIN "
DBMS OUTPUT.PUT LINK
ТО CHARCshares sold, '999G9V99')
):"'
END:
Результат будет выглядеть следующим образом:
12 300
Заметьте, что в этом примере маска форматирования включает элемент G, определяющий местоположение разделителя групп (пробела), который может быть задан только слева от элемента V, что не всегда удобно. Рассмотрим такую маску форматирования;
T0.CHARC123.45. '9G99V9G999')
Вы ожцдаете результата 1 234 50 0, но элемент G нельзя задавать справа от V, Можно использовать маску 9G99V9999, чтобы получить результат 1 234500, или маску 999V9999 для получения 1234500. Однако все это - не то, что нам нужно.
Округление при преобразовании чисел в символьные строки
В том случае, когда при преобразовании символьной строки в число слева от десятичной запятой имеется больше цифр, чем допускает маска форматирования, возникает ошибка. Если после десятичной запятой задать меньше цифр, то число будет округлено до заданного в маске количества цифр.
Функция TO_CHAR возвратит строку, состоящую из символов «#», если преобразование не получилось из-за того, что слева от запятой было слишком много цифр. Например, следующее преобразование не будет выполнено, потому что число 123 не помещается в маску:
DECLARE
b VARCHAR2C30):
BEGIN
Ь .- TO_CHARC123.4567, '99.99');
DBMS_OUTPUr.PUr^LINE(b);
END;
Если же дробная часть числа не помещается в маску, то получится округленный результат:
BEGIN
DBMS_0UTPUT.PlJT,LI»C(T0_CHAR(123.4567, '999099'));
DBNS_0UTPUT.PJTJI»C(T0_CHAR(123.4567, '999')):
END;
123,46
123
Цифры 5 и больше округляются в большую сторону, так что 123,4567 округляется до 123,46, а цифры меньше 5 — в меньшую, поэтому 123)4ххх всегда округляется до 123.
Обработка пробелов при преобразовании чисел в символьные строки
Преобразуя число в символьную строку, функция T0_CHAR всегда оставляет место для знака <-», даже если число положительное.
DECLARE
b VARCHAR2(30):
С VARCHAR2C30):
BEGIN
Ь :- ТО CHARC-123.4, '999D99'):
С :- T0"CHAR(123.4, '999D99');
DBMS_O(JTPUT.PUT LINEC':' || b || ' ' II TO CHAR(LENGTH(b))):
DBMSZOUTPUT.PUTZlINEC:' II с II ' ' II TO_CHAR(LENGTH(c))):
END:"
Результат преобразования имеет следующий вид:
:-123.40 7
:123.40 7
Обратите внимание, что каждая строка имеет длину семь символов, хотя для положительного числа достаточно и шести. Ведущий пробел применяется при выводе чисел в виде столбца. Но если пробел не нужен, можно его убрать.
ПРИМЕЧАНИЕ
Когда для представления отрицательных чисел используются угловые скобки (в маске задан элемент PR), положительные числа дополняются одним ведущим и одним завершающим пробелом.
Если необходимо, чтобы числа не содержали ни ведущих, ни завершающих пробелов, можно применить несколько методов. Один из них заключается в использовании элемента форматирования ТМ, задающего «минимальное» представление числа:
DECLARE'■
b VARCHAR2(30):
с VARCHAR2C30):
BEGIN
b.:= TO_CHAR(-123.4. 'W3;
с 产 ТО CHAR(123.4. 'ТМ9'):
DBMS—OU汴UT.PJTJJNEr:' II b II ' ' II TO_CHAR(LENGTH(b))):
DBMS_OUTPUT.PJTJ_INE(':‘ " с [I |丨 TOZCHARCLENGTHCOJj;
END:—-
Результат выполненного преобразования выглядит следующим образом:
>123,40 6 :123,40 7
Этот метод удобен, но элемент ТМ появился только в Огас1е8: и более ранними версиями не поддерживается. Кроме того, он не позволяет использовать другие элементы форматирования. Например, нельзя задать формат ТМ999.99, чтобы поручить после десятичной запятой две цифры. Если необходимы другие элементы форматирования или вы работаете с одной из ранних версий PL/SQL)можно Усечь результат преобразования:
DECLARE
b VARCHAR2(30):
с VARCHAR2(30):.
BEGIN
Ь LTRIM(T0_CHAR(-123.4, '999D99'));
с .- LTRIM(T0_CHAR(123.4. '999099'));
DBMS DUTPUT-P^LINEC:' || b || ' ' || TO_CHAR(LENGTH(b))):
D0MS2OUTPUT.PUT_LINE(':' il с II 1 ' И TO_CHAR(LENGTH(c))):
END:-
Ниже приведен результат преобразования:
:-123.40 7
:123,40 6
В данном случае мы применили функцию LTRIM, чтобы удалить ведущие пробелы, и сохранили две фиксированные цифры справа от десятичной запятой. Если же знак выводится справа от числа, что осуществляется с помощью элемента форматирования MI, то можно воспользоваться функцией RTRIM. Когда отрицательные числа выводятся в угловых скобках и из-за этого у положительных имеются пробелы с двух сторон — тогда нужно применить функцию TRIM.
Передача функции TO_CHAR значений параметров NLS
Функция TO_CHAR может принимать в качестве третьего параметра строку установок NLS, подобно функции TO_NUMBER. Например;
BEGIN
D8MS_QUTPUT.PUT_LINE(
TO_CHAR(123456.78, '9990999099'.
~'NLS_NUMERIC_CHARACTERS=''..'")
END:
Результат имеет следующий вид:
123,456,78
Таким способом можно задавать три параметра NLS: NLS_NUMERIC_CHARACTERS, NLS_CURRENCY и NLS_ISO_CURRENCY. Ранее в этой главе приводился пример одновременного использования всех трех параметров.
Использование функции CAST
Эта функция появилась только в Oracle9i и может применяться для преобразования числа в строку и наоборот. Синтаксис функции выглядит следующим образом: CAST «ырахенне AS тип_дднных)
В приведенном ниже примере показано, как с помощью функции CAST преобразовать число типа NUMBER в строку типа VARCHAR2, а затем каждый символ этой строки в отдельности тгреобразовать в соответствующее числовое значение: DECLARE
a NUMBER := 123.45:
al VARCHAR2(30):
b VARCHAR2(30) '-123 45':
bl NUMBER:
BEGIN
al := CAST Ca AS VARCHAR2):
bl :-= CAST (b AS NUMBER);
DBMS_OUTPUT.PUT_LINE (al);
DBMS_OUIPUT.PUT_UNE (bl);
END:-"
Результат выполнения кода имеет следующий вид:
12345
-123,45
У функции CAST есть один недостаток: она не поддерживает маски форматирования чисел. Однако она является частью стандарта ANSI SQL, тогда как функции TO—CHAR и TO^NUMBER в этом стандарте не описаны. Если разработчику важно, чтобы программный код был полностью совместим со стандартом ANSI, следует использовать для преобразований чисел в строковые значения функцию CAST. В других случаях мы рекомендуем применять функции TO_CHAR и TO_NUMBER.
П РИМЕЧАН И Е—
Поскольку PL/SQL не соответствует стандарту ANSI, написать на этом языке вполне совместимый с указанным стандартом код невозможно. Таким образом, функция CAST в программах на PL/SQL вообще не нужна. Она востребована только а SQL-инструкциях (SELECT, INSERT и т. п.), если они должны быть совместимы со стандартом ANSI.
Неявные преобразования
Еще один способ преобразования чисел в строки состоит в том, чтобы поручить выполнение этой работы PL/SQL. Такое преобразование называется неявным, поскольку оно не определяется явно в программном коде. Ниже приведен пример неявных преобразований:
DECLARE■
a NUMBER:
b VARCHAR2(30);
BEGIN
а :- '-123.45';
Ь :- -123.45;
Однако неявные преобразования имеют ряд недостатков. Во-первых, при использовании этого метода разработчик не имеет полного контроля над программным кодом. Всегда лучше знать, когда и какое именно происходит преобразование, Поэтому желательно выполнять его явно. Если полагаться на неявное преобразование, то вы не сможете отследить, где и когда оно происходит, и код получится мвнее эффективным. Кроме того, наличие явных преобразований делает код более понятным другим программистам.
Во-вторых, результат неявного преобразования не всегда очевиден. Рассмотрим пример:
declare
a NUMBER;
BEGIN
а :- '123.400' И 999;
Какое значение будет содержаться в переменной а после выполнения данного Кода, зависит от того, как PL/SQL вычисляет выражение справа от оператора присваивания. Если он начинает с преобразования строки в число, вычисление будет выполнено следующим образом:
:= '123.400' И 999;
3 :- 123,4 I) 999;
:■= '123,4' И 999;
а '123,4999':
а 123,4999:
Если PL/SQL начнет с преобразования числа в строку, результат будет иметь такой вид;
а :- '123.400' И 999:
а - '123.400' И '999':
а '123.400999':
а :- 123.400999:
Даже если вы знаете, какой из двух результатов получится на самом деле, вряд ли другие программисты будут догадываться об этом, читая ваш код. В данном случае лучше написать следующее (кстати, именно таким образом в Oracle вычисляется предыдущее выражение):
а :- TO^NUMBER С'123,400' Ц TO_CHAR(999));
ПРЕОБРАЗОВАНИЕ ОДНОГО ЧИСЛОВОГО ТИПА ДАННЫХ В ДРУГОЙ
Язык PL/SQL поддерживает три числовых типа данных, NUMBER, PLS_IN- TEGER и 8INARY_INTEGER, и поскольку в нем отсутствуют функции для преобразования одного числового типа данных в другой, все подобные преобразования вьшолняются неявно в случае, если в выражении используются разные числовые типы. Это действие, как и любое другое, требует определенных затрат ресурсов, поэтому желательно свести количество подобных преобразований к минимуму.
Большая часть описанных в следующем разделе числовых функций (если не все) принимает в качестве параметров значения типа NUMBER. Поэтому вызывая их с аргументами типа PLS.INTEGER или B1NARY_1NTEGER, вы применяете неявное преобразование типов.
Постарайтесь изолировать значения типа PLS^INTEGER и BINARY_INTEGER от переменных типа NUMBER и числовых функций, которые используются в программе.
Числовые функции
В PL/SQL реализовано множество числовых функций для работы с числами. Эти функции приведены в табл. 9.6 и подробно описаны в следующих разделах.
Таблица 9.6. Встроенные числовые функции PL/SQL
ФункцияВозвращаемое значение
ABSАбсолютное значение числа
ACOSАрккосинус заданного угла
ASINАрксинус заданного угла
ATANАр»стангенс заданного угла
Функция
Возвращаемое значение
ATAN2
Арктангенс заданного угла, позволяет задать это число иначе, чем в функции ATAN
BITAND
Результат поразрядной операции AND по отношению к битам двух положительных целых чисел
CEIL cos COSH ЕХР(п) FLOOR LN(a) LCXS(a,b) MOD(a,b) POWER(afb) ROUND(a,[b]) SIGN(a)
Наименьшее целое число, которое больше или равно заданному значению
Косинус заданного угла
Гиперболический косинус заданного угла
Число е в степени п, где е=2/71828183„.
Наибольшее целое число, которое меньше или равно заданному значению
Натуральный логарифм числа а
Логарифм числа b по основанию а
Остаток деления а на b
Результат возведения числа а в степень b
Результат округления числа а до b знаков после десятичной запятой
1 — если число а имеет положительное значение, 0 — если а равно нулю, -1 — если а отрицательно
SIN SINH SQRT TAN TANH TRUNC(ar[b])
Синус заданного угла
Гиперболический синус заданного угла
Квадратный корень числа
Тангенс заданного угла
Гиперболический тангенс заданного угла
Число а, усеченное до b знаков после десятичной запятой
Примите во внимание то, что тригонометрические и логарифмические функции поддерживаются только в PL/SQL версии 2.0 и выше. Обратные тригонометрические функции появились в версии 2.3.
При использовании указанных функций учтите, что все углы задаются в радианах, а не в 1радусах. Преобразование радианов в градусы и наоборот выполняется следующим образом:
радианы * ге * градусы/180 - Градусов а радианы градусы - радианы * 180/ге - Радианов в градусы
Корпорация Oracle не реализовала функцию, возвращающую значение 冗,но его можно получить косвенным методом:
ACOS(-l)
Арккосинус числа -1 равен значению п. Поскольку это число представляет собой бесконечную десятичную дробь, вы всегда будете работать с округленным его значением. Для получения нужной точности можно воспользоваться функцией ROUND. Далее рассмотрены функции, выполняющие округление и усечение число- ВДх значений, а затем описана каждая из встроенных числовых функций PL/SQL.
Функции округления и усечения
Существуют четыре числовые функции, выполняющие округление и усечение числовых значений: CEIL, FLOOR, ROUND и TRUNC. У начинающих программистов выбор нужной функции для конкретной ситуации может вызвать определенные затруднения. Поэтому в табл. 9.7 мы приводим их сравнительное описание.
Таблица 9.7. Функции, выполняющие округление и усечение чисел
Функция
Описание
CEIL
Возвращает наименьшее целое число, которое больше или равно заданному значению (от англ, ceiling — потолок)
FLOOR
Возвращает наибольшее целое число, которое меньше или равно заданному значению (от англ, floor — пол)
ROUND
Выполняет округление числа. Положительное количество цифр после десятичной запятой задает округление цифр, находящихся справа от десятичной запятой, а отрицательное — находящихся слева
TRUNC
Усекает число до заданного количества десятичных знаков, отбрасывая все цифры, находящиеся справа.
Результаты вызовов всех четырех функций с разными значениями показаны на рис. 9.5. О том, как округлять и усекать даты, вы можете узнать из главы 10.
1,75
1,3
55,56
55,5в
Входное значение
爾加иия
0
0
L
-1
Количество десятичных
ROUND
60
60
60
60
знаков
TRUNC
50
50
50
50
FLOOR
55
55
55
55
OHL
56
56
56
56
Рис. 9.5. Функции округления и усечения
Функция ABS
Возвращает абсолютное значение числа. Ее синтаксис имеет следующий вид:
FUNCTION ABS (л NUMBER) RETURN NUMBER
Функция ABS помогает упростить логику программного кода. Предположим, что программа выводит отчет доходов, расходов и соответствующие итоги. Если отклонение в строке составляет больше 100 долларов, она помечается как ошибочная вне зависимости от того, было это значение положительным или отрицательным. Первая версия кода выглядела следующим образом:
IF variance_table (line_1tem_nu) BETWEEN 1 AND 100 OR variance_table (line_item_nu) BETWEEN -100 AND -1
THEN "~~
apply_variance (statementjd);
ELSE ~—
flag error (statement id, line item nu);
END IF;~~~~
Здесь variance_table — таблица PL/SQL, в которой хранятся отклонения для каждой строки отчета.
В этот код можно внести два усовершенствования. Во-первых, вместо того чтобы жестко кодировать величины максимально допустимых отклонений, нужно присвоить нх именованным константам. Во-вторых, чтобы проверка выполнялась только один раз, можно воспользоваться функцией ABS. После этих изменений приведенный выше код примет следующий вид:
IF ABS Cvariance_table (line_item__nu))
BETWEEN min variance AND max variance
THEN—~
apply_varlance (statement_id):
ELSE ~~
fl ag_error (statenientjd. 11ne_iteni_nu):
END IF〕~~
Функция ACOS
Возвращает арккосинус угла. Синтаксис функции таков:
FUNCTION ACOS (л NUMBER) RETURN NUMBER
Здесь " — число из диапазона [-1; 1]. Значение, возвращаемое функцией ACOS, находится в пределах от 0 до тс.
Функция ASIN
Возвращает арксинус угла. Синтаксис этой функции выгладит следующим образом;
FUNCTION ASIN (n NUMBER) RETURN NUMBER
Здесь л - число из диапазона [-1; 1]. Возвращаемое функцией ASIN значение находится в пределах от -л/2 до л/2.
Функция ATAN
Возвращает арктангенс угла. Ниже приведен синтаксис данной функции:
UNCTION ATAN (п NUMBER) RETURN NUMBER
Здесь л - число из диапазона (-叫 +℃). Функция ATAN возвращает значение, которое находится в пределах от 一兀/2 до 兀/2.
Функция ATAN2
Возвращает арктангенс п/т. Синтаксис функции имеет следующий вид:
FUNCTION ATAN2 (门 NUMBER, m NUMBER) RETURN NUMBER
Здесь п и w — числа из диапазона (-℃;十^). Значение, возвращаемое функцией ATAN2, находится в пределах от -л до 兀.
Функция BITAND
Выполняет поразрядную операцию AND над битами двух положительных целых ^сел. Синтаксис функций:.
FUNCTION BITANO 5 NUMBER, m NUMBER) RETURN NUMBER
Следующий пример показывает, как применять функцию BITAND для чтения битов целочисленной переменной, используемой для хранения флагов:
DECLARE
-Объявляем переменную для хранения флагов,
-биты которой интерпретируются следующим образом:
-0000
-|--0-женщина, 1-мужчина
- I---0-частичная занятость, 1-полный день
I0-почасовая оплата. 1-ставка
I0»офис. 1-фабрика
bltjlags PL3JNTEGER:
BEGIN -
Инициализируем переменную флагов значением,
-соответстаующим сотруднице, которая работает полный день.
-Значение 3 в двоичном представлении равно 11.
bi't_fl明5 :■ 3;
-Выполняем определенные действия,,
-если сотрудник работает полный день.'
IF BITAND(biVlags, 2) <>0 THEN
DBMS_DUTPUf.PUT_LINE('Сотрудник работает полный день.'):
Для того чтобы установить отдельные биты целочисленного значения, можно применить следующий метод. Первый из двух операторов сбрасывает бит полной занятости, а второй его устанавливает:
bit_flags :- BITAND Cbit_fl明s. 13):
biVlags :- BITAND (bit二打明5. 13) + 2:
Суть метода заключается в том, что в результате вызова функции BITAND с битовой маской возвращаются все биты, кроме того, который требуется установить (или сбросить). Значение 13 в двоичном формате представляется как 1101. Поэтому в значении, возвращаемом функцией 8ITAND с таким аргументом, второй бит всегда будет равен 0. Если нам нужно, чтобы значение данного бита было равно 1, прибавим к результату 2. В двоичном представлении число 2 выглядит как 10, и после сложения второй бит результирующего числа будет равен 1.
Максимальное положительное значение, которое можно присвоить переменной типа PLSJNTEGER, составляет 230, то есть для флагов доступно 30 бит.
Функция CEIL
Возвращает наименьшее целое число, которое больше или равно заданному значению. Синтаксис функции выглядит следующим образом:
FUNCTION CEIL (〃 NUMBER) RETURN NUMBER'
Ниже приведено несколько примеров ее вызова:
CEIL ⑹ т 6
CEIL (119.1) т 120
CEIL CD.2) т -17
Можете сравнить функцию CEIL с другими числовыми функциями, обратившись к табл. 9.7 и рис. 9.5 раздела этой главы «Функции округления и усечения».
функция COS
Возвращает косинус заданного угла. Ниже приведен ее синтаксис:
FUNCTION COS {угол NUMBER) RETURN NUMBER
Здесь угол должен быть задан в радианах. Если значение угла указано в градусах, функцию COS нужно вызвать следующим образом:
Q—Cosine :- COS(ACOSC-I)ty™_e_rpwycdx/1BO);
функция COSH
Возвращает гиперболический косинус заданного значения. Приведем синтаксис функции:
FUNCTION COSH (л NUMBER) RETURN NUt^ER
Если n — действительное число, а 7 - мнимая единица, тогда связь между функциями COS и COSH можно выразить следующим образом:
COS (1 * n) - COSH (п)
функция ЕХР
Возвращает число е в степени л, где л — аргумент функции. Ниже описан синтаксис функции:
FUNCTION EXP S NUMBER) RETURN NUMBER
Число е (приблизительно равное 2,71828) является основанием натурального логарифма.
Функция FLOOR
Возвращает наибольшее целое число, которое меньше или равно заданному значению. Синтаксис функции имеет следующий вид:
FUNCTION FLOOR (л NUMBER) RETURN NUMBER
Ниже приведено несколько примеров ее вызова:
FLOOR (6.2) т 6
FLOOR (-89.4) т -90
Для сравнения функции FLOOR с другими числовыми функциями обратитесь к табл. 9.7 и рис. 9.5 раздела «Функции округления и усечения» этой главы.
функция LN
Вычисляет натуральный логарифм числа. Ее синтаксис таков:
FUNCTION LN 5 NUMBER) RETURN NUMBER
Значение аргумента л должно быть больше 0 или равно ему. Если использовать R этой функции отрицательный аргумент, генерируется следующее сообщение об ошибке:
ORA-01428: argument '-1' is out of range
Функция LOG
Вычисляет логарифм задаииого числа по указанному основанию. Ее синтаксис следующий:
FUNCTION 出 NUMBER, л NUMBER) RETURN NUMBER
Основанием логарифма b должно быть значение, которое больше 1, а л — число, которое больше 0 или равно ему. Если какой-либо из аргументов функции не отвечает этим требованиям, PL/SQL выдает следующую ошибку:
0RA-0142B: argument '-Г is out of range
Функция MOD
Возвращает остаток, полученный при делении одного заданного числа на другое. Синтаксис функции имеет следующий вид:
FUNCTION MOD (делите NUMBER, делитель NUMBER) RETURN NUMBER
Если делитель равен 0, делимое возвращается без изменений. Ниже следует несколько примеров вызова функции MOD:
MOD (10, 5) т 0
MOD (2. 1) -> 0'
MOD (3. 2) т 1
С помощью функции MOD можно быстро определить, является число четным или нечетным:、
FUNCTION 1s_odd (numjn IN NUMBER) RETURN BOOLEAN
IS~
BEGIN
RETURN MOD (numjn. 2) - 1;
END:~
FUNCTION 1 s^even Cnumjn IN NUMBER) RETURN BOOLEAN
BEGIN
RETURN MOD (numjn. 2) - 0;
END:~
Функция POWER
Возводит число, являющееся значением первого аргумента, в степень, заданную значением второго. Синтаксис этой функции выглядит следующим образом:
FUNCTION POWER (число NUMBER, степень NUMBER) RETURN NUMBER
Если число отрицательное, степень должна быть целым числом. Приведенное ниже выражение вычисляет диапазон допустимых значений переменной типа BI - NARYJNTEGER (от -231 + 1 до 231 - 1):
POWER (-2, 31)+1 POWER (2. 31) - 1
или:
-2147483647 .. 2147483647
функция ROUND
Возвращает первый аргумент, который округлен до количества цифр после десятичной запятой, заданного во втором аргументе. Синтаксис функции таков:
FUNCTION (л NUMBER [. количество_знаков NUMBER]) RETURN NUMBER
Аргумент количество__знаков необязателен и по умолчанию равен 0. Это означает, что число л будет округляться до нуля знаков после десятичной запятой, то есть до целого. Значение количествоунаков может быть и меньше нуля. В этом случае функция ROUND округляет цифры слева от десятичной запятой. Ниже приведено несколько примеров ее вызова:
ROUND (153.46) -> 153
ROUND (153.46, 1) т 153.5
RDUND (153, -1) т 150
Сравнить функцию ROUND с другими числовыми функциями можно, обратившись к табл. 9.7 и рис. 9.5 раздела «Функции округления и усечения» этой главы.
функция SIGN
Возвращает знак числа. Ее сиитаксис имеет следующий вид:
FUNCTION SIGN (л NUMBER) RETURN NUMBER
Функция возвращает -1, если п меньше нуля, 0 — если п равно нулю, +1 — если и положительно.
Функция SIN
Возвращает синус заданного угла. Синтаксис функции выглядит следующим образом:
FUNCTION SIN (угол NUMBER) RETURN NUMBER
Здесь угол должен быть задан в радианах. Как вызвать функцию SIN, если значение угла задано в градусах, показано ниже:
my_sine :- SIN (ACOS(-1)*угол_в_градусах/ISO);
Функция SINH
Возвращает гиперболический синус заданного значения. Синтаксис этой функции имеет следующий вид:.
FUNCTION SINH 5 NUMBER) RETURN NUMBER
Если " — действительное число, а 7 — мнимая единица, то связь между функциями SIN и SINH можно выразить таким образом:
SIN (1 * n) = SINH (п)
Функция SQRT
Возвращает квадратный корень числа. Синтаксис этой функции таков:
FUNCTION 5 NUMBER) RETURN NUMBER
Здесь л должно быть больше 0 или равно ему. Если п - отрицательно, PL/SQL выдает следующее сообщение об ошибке:
0RA-0142B: argument '-1' Is out of range
Функция TAN
Возвращает тангенс заданного угла. Синтаксис функции имеет следующий вид:
FUNCTION TAN {угол NUMBER) RETURN NUMBER
Здесь угол должен быть задан в радианах. Если у вас указано значение угла в градусах, функцию TAN нужно вызвать следующим образом:
ny_tane :- TAN (ACOS(-1)^yronyjpanycax/1 ВО);
Функция TANH
Возвращает гиперболический тангенс заданного значения. Ее синтаксис таков:
FUNCTION TANH (〃 NUMBER) RETURN NUMBER
Если 刀一действительное число, а 7 — мнимая единица, то связь между функциями TAN и TANH можно выразить следующим образом:
TAN (1 * n) - TANH (п)
Функция TRUNC
Усекает значение первого аргумента до количества цифр после десятичной запятой, заданного во втором аргументе. Синтаксис функции следующий:
FUNCTION TRUNC (л NUMBERE. ноличество^знанов NUMBER]) RETURN NUMBER
Аргумент количество_знаков необязателен и по умолчанию равен 0 (число п усекается до целого). Значение количествоунаков может быть и меньше 0. В этом случае функция TRUNC преобразует в нули цифры слева от десятичной запятой. Ниже приведено несколько примеров ее вызова:
TRUNC С153.46) т 153.
TRUNC (153.46. 1) т 153.4
TRUNC (-2003.16. -1) т -2000
Для сравнения функции TRUNC с другими числовыми функциями обратитесь к табл. 9.7 и рис. 9.5 раздела <Функции округления и усечения» этой главы.
1 0 "ат3 и 叩е1^
А Типы данных даты и времени
» Преобразование типов данных DATE и TIMESTAMP
» Математические операции над значениями даты-времени
> Функции обработки значений даты и времени
Большинство приложений выполняют те или иные операции со значениями даты и времени. Работать с датами довольно сложно: кроме того что приходится иметь дело со строго форматированными данными, существует множество правил определения их допустимых значений и проведения корректных вычислений (следует учитывать високосные годы, национальные праздники и выходные, диапазоны дат и т. д.). К счастью, СУБД Oracle и PL/SQL очень хорошо обрабатывают различные виды информации о датах и времени.
И СУБД и PL/SQL предоставляют целый набор типов данных для хранения дат и времени в определенном внутреннем формате. Независимо от того, как значения даты и времени выглядят в программе при вводе и выводе, в PL/SQL и СУБД они всегда представляются одним и тем же стандартным способом.
Любое значение даты или времени может храниться в Oracle как информация, которую можно разбить на следующие составляющие: год, месяц, день, часы, минуты, секунды, доли секунды, смещение часового пояса в часах, смещение часового пояса в минутах, название и аббревиатура часового пояса.
Поддержка типов данных даты и времени — только часть дела. Еще необходим язык, который может манипулировать этими значениями естественным и разумным способом — как настоящими датами и временем. Oracle обеспечивает разработчиков исчерпывающим набором функций для выполнения всевозможных операций с датами и временем. Например, если нужно преобразовать символьную строку в дату, Oracle предоставит функцию ТО_ОАТЕ> которая способна правильно интерпретировать множество разных форматов даты. Нужно преобразовать время,
чтобы обеспечить соответствие одного часового пояса другому, — воспользуйтесь функцией NEW—TIME.
В вашем распоряжении имеется богатый набор встроенных функций для работы с датой и временем, и к тому же Oracle позволяет непосредственно выполнять над этими значениями ряд арифметических операций. Например, чтобы вычислить количество дней между двумя датами, достаточно вычесть одну дату из другой.
Для успепшой работы нужно уметь правильно пользоваться таким широким набором средств. Следующий раздел посвящен разным типам данных для представления дат и времени, которые поддерживаются PL/SQL. Далее речь пойдет о выполнении преобразований между ними и о том, как присваивать значения даты и времени переменной. В оставшейся части этой главы рассказывается о функциях обработки даты и времени, а также о том, как работать с поддерживаемыми Oracle типами данных даты и времени..
Типы данных даты и времени
Если вы не стоите перед проблемой выбора, тогда все просто. В Oracle8i, Огас1е8 и предшествующих мм версиях для работы с датой и временем имелся лишь тип дашшх DATE. Однако в Oracle9i появилось несколько новых типов данных, а следовательно, и функциональных возможностей. В основном они связаны с семействами типов данных TIMESTAMP и INTERVAL и помимо выполнения своих непосредственных «обязанностей» помогают РСУБД больше соответствовать стандартам ANSI/ISO SQL
Тип данных DATE
Исходный тип данных для представления даты и времени DATE, поддерживаемый Oracle, является единственным, который был доступен до Огас1е8г включительно. В нем в виде 7-байтового значения фиксированной длины хранится следующая информация: год; месяц; день; часы; минуты; секунды.
Подобные внутренние значения нельзя задать в операторе присваивания. Вместо этого приходится полагаться на неявное преобразование символьных и числовых значений в дату или выполнять такое преобразование явно с помощью функции TO—DATE (см. раздел «Преобразование типов данных DATE и TIME- STAMP» далее в этой главе).
Объявление переменных типа DATE
Синтаксис объявления переменной типа DATE таков:
ния^переменной [CONSTAbfT] DATE [:= ] DEFAULT ндчдльноеундчение!
Здесь имя_переменной — это имя объявляемой переменной. В такой переменной можно хранить значение даты или комбинированное значение даты-времени.
ПРИМЕЧАНИЕ ——
Значения даты-времени всегда содержат компоненты времени. Вы можете их использовать либо игнорировать.
Приведем несколько примеров объявления переменных типа DATE:
DECLARE
to由ys_date CONSTANT DATE SYSDATE:
hire_date DATE DEFAULT SYSDATE;
end_of>ear DATE :- T0_DATE('12/31/2002'. 'MM/DD/YYYY');
Когда использовать тип данных DATE
Тип данных DATE применяется в программах для баз данных, содержащих столбцы этого типа, а также в приложениях, предназначенных для работы с базами данных версий до Oracle8i, И, конечно, вы будете его использовать в том случае, если у вас установлена Oracle8i, Огас1е8 или более ранняя версия. Однако при разработке новых приложений, предназначенных для Oracle9i и последующих версий, больше подойдут новые типы данных TIMESTAMP.
Ограничения на использование типа данных DATE
Для типа данных DATE имеется два ограничения, о которых следует знать.
О Этот тип данных позволяет хранить время с точностью до секунды, так что для отслеживания в реальном времени процессов, длительность которых измеряется долями секунды, он не подходит.
О Значения типа DATE не содержат информации о часовом поясе. Если пользователи вашей системы находятся в разных часовых поясах, могут возникать проблемы. Извлекая из базы данных значение типа DATE, вы должны знать, какому часовому поясу оно соответствует.
При использовании нового типа данных — TIMESTAMP — указанные ограничения отсутствуют. Он позволяет хранить время с точностью да миллиардной доли секунды, а также информацию о часовом поясе вместе со значениями даты и времени.
ПРИМЕЧАНИЕ
Если вы работаете с OracleSf или более ранней версией РСУБД и вам нужно отслеживать время с точностью до долей секунды, информацию можно хранить в виде чисел. Для определения времени с большой точностью используется функция GET—TIME пакета DBMSJJnLTTV.
Типы данных TIMESTAMP
В Oracle9i введено три типа данных TIMESTAMP, иногда называемых временной меткой. Все они позволяют хранить время с точностью до миллиардной дали секунды; два из них включают информацию о часовом поясе, но в разных форматах.
Q TIMESTAMP. Хранит значение даты и времени без информации о часовом поясе. Эквивалентен типу данных DATE, отличаясь от него лшпь тем, что хранит время с точностью до миллиардной дали секунды.
Q TIMESTAMP WITH TIME ZONE. Хранит вместе с каждым значением даты и времени информацию а часовом поясе.
Q TIMESTAMP WITH LOCAL TIME ZONE. Хранит значения даты и времени, которые, как предполагается, соответствуют «локальному» часовому поясу. Для столбцов базы данных этого типа «локальным» считается часовой пояс базы данных,
а для переменных PL/SQL — часовой пояс сеанса. Значения столбцов базы данных, присваиваемые значениям переменных PL/SQ.L или наоборот, преобразуются в соответствии с часовым поясом каждого из них.
Как разные типы данных отражают точность значений даты-времени при их «перемещении# от пользователя одного часового пояса через базу данных к пользователю другого часового пояса( можно судить по рис. 10.1.
Восточныйчасовой пояс(UTC -5:00)Восточныйчасовой пояс(UTC -5:00)ДжонатанДжонатанБаза данных Oracleя горном часовом поясе (UTC -7:00)
Восточныйчасовой пояс(UTC -5:00)
Восточныйчасовой пояс(UTC -5:00)
Джонатан
Джонатан
DATE 0*2-20021 5:00:00,00 -5:00— ■► 032-2002 15:00:00
TIMESTAMP
06-02-200215:00:00.00 -5:00 —► 06-02-2002 15:00:00.00 --■ ■
TIMESTAMP WITH LOCAL TIME ZONETIMESTAMP WITH LOCAL TIME ZONETIMESTAMP WITH TIME ZONE 06-02-200215:00:00.00 -5:00-•••••-► 06-02-200215:00:00.00 6 00 ”
TIMESTAMP WITH LOCAL TIME ZONE
TIMESTAMP WITH LOCAL TIME ZONE
Горныйчасовой пояс(UTC-7:00)Горныйчасовой пояс(UTC-7:00)06-02-200215:00:00.00 -5:00A 06-02-200213:00:00.00
Горныйчасовой пояс(UTC-7:00)
Горныйчасовой пояс(UTC-7:00)
06-02-2002 15:00:00.00 -7:00 <”06-02-2002 15:00:00.00 -7:00 <一06-02-2002 15:00:00.00 -5:00 <“06-02-2002 13:00:00.00 -7:00 <“06-02-2002 15:00:00.00 -7:00 <”06-02-2002 15:00:00.00 -7:00 <一06-02-2002 15:00:00.00 -5:00 <“06-02-2002 13:00:00.00 -7:00 <“Sa? учета часового пояса__
06-02-2002 15:00:00.00 -7:00 <”
06-02-2002 15:00:00.00 -7:00 <一
06-02-2002 15:00:00.00 -5:00 <“
06-02-2002 13:00:00.00 -7:00 <“
06-02-2002 15:00:00.00 -7:00 <”
06-02-2002 15:00:00.00 -7:00 <一
06-02-2002 15:00:00.00 -5:00 <“
06-02-2002 13:00:00.00 -7:00 <“
SeJ ^втачааовоаопояса ""'
Сохраняется исходный часовой пояс*
Правильное время, но в часовом поясе пользователя
Рис. 10.1. Разные типы значений даты-времени
На рисунке показан пользователь Джонатан, находящийся в Восточном часовом поясе, разница во времени между которым и всеобщим скоординированным временем (Coordinated Universal Time, или UTC) составляет 5 ч. (Описание UTC см. во врезке.) Джонатан сохраняет значения даты-времени четырех разных типов в четырех столбцах базы данных. Эти значения представлены в стандартной нотации ANSI/ISO, и все они равны 3 часам дня (15:00:00.00) в Восточном часовом поясе (-5:00) 6 февраля 2002 года (06-02-2002).
База данных находится в Горном часовом поясе. Обратите внимание на то, как в ней меняется представление времени для каждого из типов данных. Типы DATE и TIMESTAMP игнорируют разницу часовых поясов пользователя и базы данных. Они не сохраняют информацию об исходном часовом поясе)так что эта информация теряется) а вместе с ней теряется и значение времени, которое было зафиксировано первым пользователем.
При записи значения времени в столбец типа TIMESTAMP WITH TIME ZONE информация о часовом поясе сохраняется, так что значение столбца представляет реальное время, зафиксированное Джонатаном. Сравните значение этого столбца
со значением столбца типа TIMESTAMP WITH LOCAL TIME ZONE, в котором время, зафиксированное в Восточном часовом поясе, приведено в соответствие с Горным часовым поясом. На этот раз сохранено правильное значение времени, но потеряна информация о том, в каком часовом поясе оно фиксировалось.
ПРИМЕЧАНИЕ:—
В отношении типа данных TIMESTAMP WITH TIME ZONE на рис. 10.1 показана концепция, а не реальный формат данных. Oracle представляет все значения этого типа как всеобщее скоординированное время (время UTC). Без сомнения, это сделано для того, чтобы облегчить сравнение значений даты-времвни и выполнение арифметических операций над этими значениями. Для значения нв рисунке внутреннее представление будет следующим: 06-02-2002 15:00:00.00 -5:00. Информация о часовом поясе сохраняется таким образом, что дату и время легко преобразовать в соответствии с иосодным часовым поясом, а внутренний формат UTC для вас будет совершенно прозрачным.
Рассмотрим данные) полученные пользователем Донной. Она, как и база данных, находится в Горном часовом поясе. Значения типа ОАТЕ и TIMESTAMP неверные. Джонатан зафиксировал время 15:00 в Восточном часовом поясе, а Донна видит то же время, 15:00, в Горном часовом поясе. С другими типами данных дело обстоит лучше. Значение типа TIMESTAMP WITH TIME ZONE Донна видит в точности так, как оно было введено Джонатаном в Восточном часовом поясе. Кроме того, Донна видит правильное время для значения типа TIMESTAMP WITH LOCAL TIME ZONE, но не знает, в каком часовом поясе оно было введено.
ВСЕОБЩЕЕ СКООРДИНИРОВАННОЕ ВРЕМЯ
Всеобщее скоординированное время (Coordinated Universal Time, или UTC) измеряется с высочайшей точностью при помощи атомных часов и составляет основу всемирной системы времени. Например, все часовые пояса определяются как разница в часах и минутах относительно мирового времени, соответствующего среднему местному времени относительно нулевого меридиана. Поскольку UTC — атомарное время, оно периодически корректируется для синхронизации со временем, определяемым в зависимости от вращения Земли.
Вы, вероятно, знакомы с понятием времени по Гринвичу (Greenwich Mean Time, GMT). Практически GMT эквивалентно UTC, но между ними есть разница.
Почему используется аббревиатура UTC, а не CUT? Центры стандартизации никак не могли договориться между собой относительно того, какую аббревиатуру, английскую CUT или французскую TUC, им использовать. Поэтому сошлись на UTC, что не соответствует ни одному из языков. См. http:〃www・bouldermistgov/Hmef「eq/geneBl/misc.Mm#Anchor-14550・
За дополнительной информацией об UTC можно обратиться к документу U.S. Naval Observatory What й Universal Time?, расположенному по адресу: http://aa.usno.navy.mll/feq/docs/UT.html, а также к документу от National Institute of Standards and Technology, который вы найдете по адресу: http://phy- slcs.nlst.gov/GenInt/Tlme/world.html.
Объявление переменных типа TIMESTAMP
Синтаксис объявления переменных типа TIMESTAMP более сложен, чем переменных типа DATE. Большинство имен типов данных семейства TIMESTAMP состоят из нескольких слов, к чему поначалу трудно привыкнуть. Кроме того, эти типы данных позволяют задать количество десятичных знаков, используемое для представления долей секунды. Вот синтаксис их объявлений:
TIMESTAMP Цточность)1
TIMESTAMP КточностьП WITH TIME ZONE
TIMESTAMP ^точность}} WITH LOCAL TIME ZONE
Значение параметра точность определяет количество десятичных цифр, выделенное для хранения значений долей секунды. По умолчанию оно равно 6, что означает отслеживание времени с точностью до 0,000001 с. Допускаются значения точности от 0 до 9.
ПРИМЕЧАНИЕ
Поведение переменной типа TIMESTAMP(O) идентично поведению переменной типа данных DATE.
В каких случаях используются переменные типа TIMESTAMP
Переменные типа TIMESTAMP используются в основном для отслеживания времени с точностью до долей секунды и для оперирования значениями времени в разных часовых поясах.
Все разновидности типа данных TIMESTAMP поддерживают доли секунды, так что если для вашей работы этого достаточно, используйте тип TIMESTAMP, а если необходима информация о часовом поясе — тип TIMESTAMP WITH LOCAL TIME ZONE или TIMESTAMP WITH TIME ZONE.
Информация а часовом поясе хранится в виде данных типа TIMESTAMP WITH TIME ZONE как часть значения даты-времени. Этот тип данных используется в тех случаях, когда важно знать исходный часовой пояс, например при работе с данными, поступающими из источников, которые находятся в разных часовых поясах.
ПРИМЕЧАНИЕ
Вследствие некоторых специфических обстоятельств, в частности из-за перехода на зимнее/летнее время, значение типа TIMESTAMP WITH TIMEZONE может интерпретироваться по-разному. Подробнее об этом рассказывается в разделе «Преобразование типов данных DATE и TIMESTAMP».
Если важно знать правильное время, но не важно, в какам часовом поясе оно было зафиксировано, можно пользоваться типом данных TIMESTAMP WITH LOCAL TIME ZONE. Значения этого типа автоматически приводятся в соответствие с текущим часовым поясом. Однако следует обязательно учесть тот факт, что в трехуровневых системах текущим часовым поясом легко может стать часовой пояс сервера web-приложения, а не пользователя, просматривающего данные в web-браузере.
Тип данных TIMESTAMP WITH LOCAL TIME ZONE применяется при переносе данных из столбцов типа DATE старой базы данных в столбцы типа TIMESTAMP новой базы. Непосредственно перенести эти значения в столбец типа TIMESTAMP WITH TIME ZONE нельзя, поскольку тип DATE не содержит информации о часовом поясе. Но если вы
хотите интерпретировгггь все значения типа D/СГЕ как относящиеся к текущему часовому поясу, их можно представить как тип данных TIMESTAMP WITH LOCAL TIME ZONE. После этого значения даты-времени в результирующем столбце будут автоматически преобразованы для достижения соответствия между часовым поясом базы данных и часовым поясом сеанса пользователя. Подобное преобразование в большей степени относится к сфере администрирования базы данных, нежели программирования, но знать о нем необходимо.
Типы данных INTERVAL
Типы данных INTERVAL введены только в Oracle9z. Чтобы получить более полное представление о них, вспомним, с какими еще значениями даты-времени мы можем сталкиваться в повседневной жизни.
О Момент — временная точка, определенная с некоторой точностью. Например, когда мы собираемся встать утром в заданное время, это время представляет момент. Он может быть определен с точностью до часа или же до минуты. Все типы данных DATE и TIMESTAMP позволяют задавать моменты времени.
О Интервал — это понятие, которое относится не к конкретной временной точке, а к определенному количеству времени. В повседневной жизни мы постоянно имеем дело с временными интервалами. Например, мы работаем по восемь часов в день (надеемся), обедаем в течение часа (в мечтах!) и т. д. Два новых типа данных INTERVAL, введенных в Oracle9z, позволяют нам представлять интервалы времени.
О Период — это интервал, который начинается и заканчивается в заданные моменты времени. Например, если вы встали в 8:00 и работали в течение восьми часов. 8-часовой интервал времени, начинающийся в 8:00, можно считать периодом. В Oracle нет специализированного типа данных для непосредственной поддержки периодов, как нет его и в стандарте ANSI/ISO.
В Oracle9z введены два новых типа данных INTERVAL, соответствующих стандарту ANSI/ISO:
О INTERVAL YEAR ТО MONTH — позволяет определить интервал времени в годах и месяцах;
О INTERVAL DAY ТО SECOND — позволяет определить интервал времени в днях, часах, минутах и секундах (включая доли секунды).
Объявление переменных типа INTERVAL
По сравнению с другими объявлениями переменных PL/SQL синтаксис объявлений переменных обоих типов INTERVAL необычен. Помимо того, что имена этих Двух типов состоят из нескольких слов, для них задается не одно, а два значения, определяющих точность соответствующих параметров:
шпеременной INTERVAL YEAR UwHoerbjer^ ТО MONTH
Или
чмя^переменной INTERVAL DAY [(точностьjHeU}} ТО SECOND [(точность_полей_секунды)]
Здесь имя_переменной — это имя объявляемой переменной типа INTERVAL; точность^ 兆т — количество цифр (от 0 до 4), выделенное для представления количества лет
(по умолчанию 2); точность_дней — количество цифр (от 0 до 9), выделенное для представления количества дней (по умолчанию 2); точность_долей_секунды ——количество цифр (от 0 до 9), выделенное для представления количества долей секунды (по умолчанию 6).
О точности значений интервалов, как правило, можно не беспокоиться. Значения типа INTERVAL YEAR ТО MONTH всегда нормализуются таким образом, что количество месяцев лежит в диапазоне от 0 до 11. Фактически Oracle не позволяет задать месяц значением больше И; интервал в 1 год и 13 месяцев должен быть выражен как 2 года и 1 месяц. Значение параметра точность_лет устанавливает максимальный размер интервала типа INTERVAL YEAR ТО MONTH. Точно так же значение параметра точность_дней устанавливает максимальный размер интервала типа INTERVAL DAY TQ SECOND. ~
Точность для часов, минут и секунд значения типа INTERVAL DAY ТО SECOND не нужно задавать по той же причине, по которой не задается точность для месяцев значения типа INTERVAL YEAR ТО MONTH. Интервалы INTERVAL DAY ТО SECOND всегда нормализуются таким образом, что значения часов, минут и секунд находятся в естественных диапазонах: 0-23 ч, 0-59 мин и 0-59 с (за исключением долей секунды).
Доли секунды указываются потому, что значения типа INTERVAL DAY ТО SECOND могут определять интервалы с указанной точностью до долей секунды. Значения типа INTERVAL YEAR ТО MONTH не могут содержать долей месяца, и последние для них не задаются.
В каких случаях используются типы данных INTERVAL
Типы данных INTERVAL относительно новы для Oracle, поэтому поговорим об их применении, воспользовавшись несколькими примерами. Надеемся разбудить ваши творческие способности и подтолкнуть к размышлениям о том, как эти типы данных могли бы использоваться в создаваемых вами системах.
Представьте себе, что вы работаете на консервном заводе. На каждой банке консервов должен быть проставлен срок реализации, и ваша задача — написать программу для вычисления этой даты для консервов, производимых в данный момент. Это простейшая задача, требующая использования интервала. Пища в банке будет съедобна в течение определенного интервала времени, и дата ее реализации соответствует концу этого интервала. Поскольку консервы хранятся долго, мы будем исчислять интервалы в годах и месяцах.
Задача усложняется тем, что каждый тип консервов имеет свой срок хранения. Например, консервированные овощи хранятся дольше, чем мясо, а пища для домашних животных — дольше, чем пища для людей. Вы создаете таблицу, содержащую информацию о сроке хранения каждого типа продуктов. Ее объявление может быть таким;
CREATE TABLE good_for (
productjd NUMBER,
gaod_for_per1od INTERVAL YEAR(2) TO MONTH NOT NULL - );~ ~
Далее вы решаете, что вам нужна функция PL/SQL, возвращающая дату реализации продукта, которая определяется на основе срока его хранения. Эта функция может быть такой:
CREATE OR REPLACE FUNCTION expiration^date C
good_for_period_1n IN shelf_life.good_for_periodITYPE
return date
IS
BEGIN
--обрезаем текущую дату, и получаем
только дату, без времени.
RETURN TRUNC (SVSOATE) + good_for_per1od_1n;
END;
Если бы мы использовали два числовых столбца для года и месяцев вместо одного столбца типа INTERVAL, вычисления были бы сложнее:
exp1ration_date :-
ADD_MONTHS £ TRUNC(SYSOATE),
一 (good_for^years_in * 12) + good_for_months_1n
Использование интервала вместо двух числовых значений имеет ряд преимуществ. Во-первых, программный код получается более простым, а значит, отладка его проще и уменьшается вероятность появления ошибок. Во-вторых, он становится более читабельным и понятным. В-третьих, использование типа данных INTERVAL YEAR ТО MONTH в таблице good_for гарантирует, что в ней будут храниться только допустимые интервалы (например, интервал в 1 год и 24 месяца не приемлем).
Кроме того, тип данных INTERVAL удобно применять в тех случаях, когда нужно определить разность между двумя датами. Рассмотрим следующий нрнмер, в котором вычисляется срок работы сотрудника:
DECLARE
start_date DATE;
end_date DATE:
servicejnterval INTERVAL YEAR TO MONTH;
years_of_service NUMBER;
months_of_service NUMBER;
BEGIN
-Обычно мы извлекаем начальную и конечную даты из базы двнных
start_date :- T0_DATE('29-DEC-1988','dd-mon-yyyy');
end_date := TO_DATE ('26-DEC-1995'.'dd-mon-yyyy'):
Определяем и выводим количество проработанных лет и месяцев:
servicejnterval := (end_date - start_date) YEAR TO MONTH:
DBMS_OUTPUT.PUT_LINE(service__interva'iy:
-Используем ноэую функцию EXTRACT, для того чтобы
-выделить отдельные компоненты - год и месяц
years_of_service :- EXTRACTCYEAR FROM service_1nterval):
months_of_service :- EXTRACTCMONTH FROM servicejnterval);
DBMS_OUTPUT.Pirr_LINE(years_of_service 111 лет и '
11 months of service 11' месяцев
END:
Приведем строку, выполняющую реальные вычисления для получения количества лет и месяцев работы:
servicejnterval := (end_date - start_date) YEAR TO MONTH;
Здесь YEAR TO MONTH - это часть синтаксиса выражения, возвращающего интервал. Подробнее о нем рассказывается далее в этой главе. Как видите, вычисление интервала сводится к простому вычитанию одной даты из другой, Если бы мы не
использовали тип данных INTERVAL, нам бы пришлось написать что-то вроде следующего:
months_of_service :- ROUND(months_between(end_date. start^date));
years_of_service := TRUNC(months_of_service/12);
months_of_service := MOD(months_of_service,12);
И снова решение, в котором не используется тип данных INTERVAL, оказывается сложнее как для программирования, так и с точки зрения понимания написанного кода.
ПРИМЕЧАНИЕ Тип данных INTERVAL YEAR ТО MONTH выполняет округление значений, и вы должны знать о возможных последствиях этой операции. Данный вопрос подробно рассматривается ниже.
ДЛЯ ЧЕГО НУЖНЫ ДВА ТИПА ДАННЫХ INTERVAL
Поначалу мы удивились тому, что Oracle ввела сразу два типа данных INTERVAL. Решение обрабатывать годы и месяцы отдельно от дней, часов, минут и секунд показалось нам довольно странным. Почему бы не создать единый тип данных INTERVAL, покрывающий все возможности? Однако оказалось, что за это нам нужно благодарить римского императора Юлия Цезаря (создателя юлианского календаря), определившего продолжительность большинства месяцев.
Два типа данных с разделительной линией на уровне месяцев пришлось определить потому, что месяц — это единственный компонент даты-времени, продолжительность которого может быть разной. Подумайте об интервале в 1 месяц и 30 дней. Какую продолжительность он имеет на самом деле? Это меньше двух месяцев? В точности два месяца? Или, может быть, больше? Если этим месяцем является январь, тогда через 30 дней уже будет март и получится интервал в 61 день. Если же этим месяцем является февраль, тогда интервал равен либо 59, либо 60 дням. Ну а если месяц — апрель, тогда интервал получится в 60 дней.
Поэтому вместо того чтобы возиться со всеми этими сложностями, обусловленными разной продолжительностью месяцев и возникающими при сравнении интервалов, выполнении математических операций с датами, а также при нормализации значений даты-времени, стандарт ANSI «разбивает» маску даты-времени на две части: в одной — год и месяц, во второй —все остальное. За дополнительной информацией по этому вопросу обратитесь к книге A Guide to SQL Standard (автор CJ. Date, издательство Addison-Wesley, 1993).
Преобразование типов данных DATE и TIMESTAMP
Теперь, когда вы имеете представление о типах данных Oracle, предназначенных для работы с датой и временем, давайте посмотрим, как присваивать даты значениям переменных этих типов и как такие значения впоследствии использовать.
Дату и время в понятной для человека форме можно представить в виде символьных строк наподобие《8 марта 2003 года» и《10:30». Так что речь пойдет о преобразовании значений даты-времени — приведении символьных строк в соответствие с внутренним представлением Oracle и наоборот.
PL/SQL проверяет и хранит даты от 1 января 4712 до н. э. до 31 декабря 9999 года н. э. Для просмотра диапазона дат в вашей версии Oracle запустите сценарий showdaterange.sql, имеющийся на web-узле издательства O'Reilly.) Если ввести дату без времени (во многих приложениях не требуется отслеживать время, так что PL/SQL позволяет его не задавать), то время в результирующем значении по умолчанию будет равно полуночи (00:00),、
Oracle способна интерпретировать практически любой из известных форматов даты и времени. Эта возможность основана на использовании маски форматирования даты, которая представляет собой строку специальных символов, определяющих формат даты для Oracle. Поэтому мы сначала рассмотрим маски форматирования, а затем проследим, как они используются при работе с переменными PL/SQL.
Маски форматирования даты и времени
В версии Огас1е6 и более ранних версиях РСУБД по умолчанию дата задавалась в формате DD-MON-YY, наводившем ужас на пользователей и разработчиков. Хотя этот формат распространен во многих странах мира, в Соединенных Штатах им практически никто не пользуется.
Начиная с Oracle? администраторы базы данных могут сами задавать используемый по умолчанию формат (который вступает в силу при инициализации или запуске РСУБД). Этот формат определяется значением параметра NLS_DATE_FOR- МАТ, например:
NLSJATEJORMAT * 'MM/DD/YYYY'
Кроме того, используемый по умолчанию формат даты неявно задается с помощью другого инициализационного параметра, а именно NLS_TERRITORY. Указывая значение параметра NLS TERRITORY, вы устанавливаете соглашения о формате даты, языке даты, числовых форматах, символах валюты и первом дне недели.
Формат даты можно задать также на уровне сеанса, что удобно делать, когда ваши потребности отличаются от потребностей большинства пользователей базы данных. Для этого применяется оператор ALTER SESSION. Следующий код работает в Огас1е8: и более поздних версиях и устанавливает по умолчанию формат даты MM/DD/YYYY:
BEGIN
EXECUTE IMMEDIATE 'ALTER SESSION SET NLS DATE FORMAT = "MM/DD/YYYY" 1 ■
END:一 -
Для того чтобы узнать, какой формат даты по умолчанию используется в вашем сеансе, нужно выполнить запрос к представлению NLS_SESSION_PARAMETERS: SELECT value
FROM nls_sess1onjjarameters
WHERE parameter- 'NLS DATE FORMAT';
Из разделов «Преобразование строки в дату» и «Преобразование даты в строку» вы узнаете, как задавать элементы маски при форматировании даты для отдельных преобразований, не ограничиваясь элементами, используемыми по умолчанию.
Как видите, при преобразовании даты и символьных данных очень важную роль играет маска их форматирования (такая как MMDDYY или Month DO, YYYY). Маска форматирования даты состоит из нескольких элементов. Например, элементами маски MM/DD/YYYY являются ММ, DD, YYYY и две косые черты.
В табл. 10.1 приведен полный список элементов форматирования даты и времени и указано, как они используются. Элементы форматирования дат можно задавать в любых сочетаниях и в любом порядке. В параметре NLS_DATE_FORMAT один и тот же элемент нельзя задать дважды; например, можно использовать только один из элементов MONTH, MON или ММ, поскольку все они обозначают месяц.
ПРИМЕЧАНИЕ В более ранних версиях Oracle разрешалось дважды задавать един и тот же элемент даты (в маске «Моп (ММ) DD( YYYY» дважды указан месяц). Однако начиная с Orade9l каждый элемент форматирования используется только один раз.
Некоторые из приведенных в табл. 10.1 элементов форматирования применяются только для преобразования значений даты-времени внутреннего формата Oracle в символьные строки, но не наоборот. Такие элементы нельзя включать в используемую по умолчанию маску форматирования (то есть задавать в параметре ■ NLS_DATE_FORMAT), поскольку эта маска применяется для преобразования в обоих направлениях. Указанные элементы помечены в таблице как <Только вывод».
Таблица 10.1. Элементы маски форматирования даты и времени
Элемент
Описание
SCC или СС
Столетие. Если используется формат SCC, даты, произошедшие в период до н, э. предваряются знаком минус (-). Только вывод
SYYYY или YYYY
Год представляется четырьмя цифрами. Если используется формат SYYYY, даты до н. э. предваряются знаком минус (-)
IYYY
Год представляется четырьмя цифрами; соответствует стандарту ISO. Только вывод
YYY, или YY, или Y
Последние три, две или одна цифра года. Если эти элементы используются при преобразовании символьной строки в дату, по умолчанию принимается, что речь 倣ет о текущем столетии
IYY, или IY, или I
Последние три, две или одна цифра года; соответствует стандарту ISO. Только вывод
Y,YYY
Год представляется четырьмя цифрами с запятой
SYEAR, YEAR SYear, Year, syear или year
Год, представленный символьной строкой (то есть «two thousand two»).
Префикс 5 помещает перед датами до н. э. знак минус (-). Может быть записан в верхнем, нижнем или смешанном регистре. Только вывод
RR
Последние две цифры года. Этот формат используется для вывода года, не относящегося к текущему столетию
RRRR
Год представляется четырьмя цифрами
ВС или AD
Индикатор ВС (до н. э.) или AD (н. э.) без точек
Элемент
Описание
В,С. или A.D. Е
Индикатор В.С. Сдо н. з.) или A.D. (н. э.) сточками
Сокращенное название эры. Используется в японском, тайско-буддистском и некоторых других календарях. Только вывод
ЕЕ
Q
Полное название эры
■ Квартал「о^а, от 1-го до 4-го. Период январь-март считается первым кварталом, апрель-июнь — вторым и т. д. Только вывод
ММ
Номер месяца в году, от 01-го до 12-го. Январь представляется значением 01, сентябрь — 09 и т. п.
RM
Представление номера месяца римскими цифрами, от I до XII. Январь является месяцем I, сентябрь — IX и т. д.
MONTH, Month или month
MON, Mon или mon
WW
IW
Название месяца в символах верхнего, смешанного или нижнего регистра
Сокращенное название месяца, такое, как JAN для January. Может быть представлено символами верхнего, смешанного или нижнего регистра
Неделя года, от 1-й до 53-й. Только вывод
Неделя года, от 1-й до 52-й или от 1-й до 53-й; соответствует стандарту ISO. Только вывод
w
Неделя месяца, от 1-й до 5-й. Неделя 1 начинается с первого дня месяца и заканчивается седьмым. Только вывод
DDD DD
D
День годв, от 1-го до 366-го
День месяца, от 1-го до 31-го
День недели, от 1-го до 7-го. Номер дня недели зависит от того, какой день неявно определен как первый значением параметра базы данных NLS_JERRTTORY
DAY, Day или Название дня недели, представленное в символах верхнего, смешанного
dayили нижнего регистра
DY, Оу или dyJАМ или РМ А,М. или Р.М, TZDТ7НTZMTZRDY, Оу или dyJАМ или РМ А,М. или Р.М, TZDТ7НTZMTZRСокращенное название дня недели, такое, как TUE для Tuesday, в верхнем, смешанном или нижнем регистре
DY, Оу или dy
J
АМ или РМ А,М. или Р.М, TZD
Т7Н
TZM
TZR
DY, Оу или dy
J
АМ или РМ А,М. или Р.М, TZD
Т7Н
TZM
TZR
Формат для отображения дня в юлианском календаре (количество дней с 1 января 4712 года до н. э. — самой ранней даты, поддерживаемой РСУБД Grade)
Индикатор времени до или после полудня без точек
Индикатор времени до или после полудня с точками
Аббревиатура названия часового пояса, например ESTf PST и т. д. Этот формат используется только при вводе, вначале он может показаться непривычным
Смещение времени часового пояса в часах. Например, -5 соответствует часовому поясу, в котором аремя на пять часов меньше UTC
Смещение времени часового пояса в минутах. Например, -5:30 соответствует часовому поясу, в котором время на пять часов и тридцать минут меньше UTC. Таких часовых поясов немного
Регион действия часового пояса. Например, «US/Eastern» — это регион, для которого действуют время EST и EDT (см. табл. 10.8)
Таблица 10.1 (продолжение)
ЭлементОписание
НН или НН12 Часы, от 1 до 12. Формат НН12 используется только для вывода
НН24Часы, от 0 до 23
MIМинуты в значении даты-времени, от 0 до 59
SSСекунды в значении даты-времени, от 0 до 59
SSSSSFFXTHSPSPTHFXFMДругой ТЕКСТSSSSSFFXTHSPSPTHFXFMДругой ТЕКСТКоличество секунд после полуночи в компоненте времени. Значения лежат в диапазоне от 0 до 86399, и каждый час состоит из 3600 с
SSSSS
FF
X
TH
SP
SPTH
FX
FM
Другой ТЕКСТ
SSSSS
FF
X
TH
SP
SPTH
FX
FM
Другой ТЕКСТ
Доли секунды. Допустим только для значений типа TIMESTAMP. Независимо от количества десятичных цифр, которые вы хотите видеть или использовать, для формата задаются две буквы F. Другое их количество недопустимо
Локальный символ десятичного разделителя. В американском английском это точка (J. Данный элемент помещается перед FF, чтобы доли секунды интерпретировались и представлялись правильно
Суффикс, преобразующий число для представления его в виде порядкового числительного (для английского языка). Например, 4 превращается в 4th, а 1 — в 1th. Этот элемент может стоять после любого элемента, представляющего число. Например, формат «DDth-Mon-YYYY» продуцирует выходное значение вида «15th-Nov~1961». Возвращаемое значение всегда дается на английском языке, независимо от языка даты
Суффикс, преобразующий число в полное текстовое представление на английском языке. Этот элемент может стоять после любого элемента, обозначающего число. Например, формат «DDth-Mon-Yyyysp» продуцирует выходное значение вида «15th-Nov-One Thousand Nine Hundred Sixty-Опе». Возвращаемое значение всегда дается на английском языке, независимо от языка даты. (Обратите внимание, что формат Yyyy ссютветсгвует смешанному регистру символов)
Суффикс, преобразующий число для его полного текстового представления на английском языке. Например, 4 возвращается как FOURTH, а 1 как FIRST. Этот элемент может стоять после любого элемента, обозначающего число.
Например, формат «Ddspth Mon, Yyyysp» продуцирует выходное значение вида «Fifteentti Nov, One Thousand Nine Hundred Sixty-Опе», Возвращаемое значение всегда дается на английском языке, независимо от языка даты. (Обратите внимание, что формат уууу допускает смешанный регистр символов)
Элемент, требующий точного соответствия между данными и маской форматирования. (FX означает Format eXact — точный формат.) См. далее в згой главе раздел «Элемент форматирования FX»
Элемент, который служит для удаления пробелов в результирующей строке. (FM означает Fill Mode — режим заполнения.) См. раздел «Элемент форматирования FM» далее в этой главе
Любые знаки препинания, в том числе запятая (,), косая черта (/) или дефис (-) будут воспроизведены в результирующей строке. В ней также будет воспроизведен текст, если включить его в маску форматирования, взяв в двойне кавычки (" "). Примеры использования этого элемента приводятся ниже
В тех случаях, когда в результате форматирования даты генерируется ее текстовое представление, а не цифровое (например, MONTH, MON, DAY, DY, AM и PM), язык, используемый для написания данных слов)определяется параметрами NLS —
NLS DATE_LANGUAGE и NLS_LANGUAGE - или необязательным артументом языка даты, который^можно передавать функциям TO_CHAR и TO-DATE.
Приведем несколько примеров масок форматирования дат, составленных из приведенных выше элементов форматирования:
■Month DO. YYYY'
'WI/DD/YY Day A.M.'
■Year Month Day HH24:MI:SS'
'J'
'SSSSS-YYYY-MM-OD'
"Д beautiful sunnier morning on the" DDth" day of "Month'
Дополнительные примеры использования различных элементов форматирования вы найдете в описаниях функций TO_CHAR и TO_DATE.
ДАТЫ ISO
Элементы IYY и IW в соответствии со стандартом ISO (International Organization for Standardization — Международная организация по стандартизации) представляют год и неделю. В календаре, разработанном данной организацией, первый день года - всегда понедельник и определяется он с учетом следующих правил:
когда 1 января приходится на понедельник, год начинается с этого же дня;
когда 1 января приходится на один из дней со вторника по четверг, год начинается с предшествующего понедельника;
когда 1 января приходится на один из дней с пятницы по воскресенье, год начинается со следующего понедельника.
Следование этим правилам часто приводит к нелепым ситуациям. Например, 31 декабря 2001 года считается первым днем 2002 года в соответствии со стандартом ISO, и если вывести эту дату в формате IYYY, получится 31-DEC-2002.
Недели ISO всегда начинаются с понедельника и нумеруются начиная с первого понедельника года ISO.
Преобразование строк в даты
Первым вопросом, с которым вы столкнетесь при работе с датами, будет принцип присвоения даты (или значения времени) значению переменной PL/SQL. Эта операция выполняется путем преобразования значения даты-времени для достижения соответствия его символьного представления внутреннему формату Oracle. Такое преобразование можно выполнить неявно, присвоив символьной строке значение переменной типа даты-времени, или явно, с помощью встроенных функций Oracle.
Неявное преобразование — вещь рискованная, и мы не рекомендуем на него полагаться. Приведем пример неявного преобразования символьной строки при записи ее в переменную типа DATE:
Declare
b1rthday DATE;
BEGIN
birthday ;- '15-Nov-1961':
END;
Возможность такого преобразования зависит от установленного значения параметра NLS_DATE_FORMAT и преобразование будет выполняться до тех пор, пока администратор базы данных не решит изменить это значение. Как только он это сделает, код перестанет функционировать. Нарушит работу кода и изменение значения параметра NLS_DATE_FDRMAT на уровне сеанса.
Вместо того чтобы полагаться на неявные преобразования и установленное значение параметра NLS_DATE_FORMAT, лучше преобразовывать даты явно, с помощью встроенных функций Oracle. Эти функции не только делают очевидным выполняемое преобразование, но и позволяют задать используемый формат даты- времени.
В следующих разделах описываются встроенные функции преобразования даты-времени. Отдельный раздел посвящен новой возможности Oracle9z - работе с литеральными значениями даты-времени. Литерал, задающий дату и время,— это стандартный текстовый формат ANSI/ISO. С его помощью можно также преобразовать текст в значение типа DATE или TIMESTAMP,■
Функция TO_DATE
Функция TO_DATE преобразует символьную строку в значение типа данных DATE. Она является перегруженной — поддерживает как строковые, так и числовые входные значения, синтаксис функции TO_DATE следующий:
FUNCTION TOJATE
(вхоцндя_строкд IN VARCHAR2
[.маска_форматирования IN VARCHAR2
[.я зык Tils IN VARCHAR2]]
RETURN DATE'
FUNCTION TJDATE
{входное_число IN NUMBER
[,маскаjpopMarnpOBаяия IN VARCHAR2
[.азнк$】5 IN VARCHAR2]]
)
RETURN DATE
Вторая версия этой функции может использоваться только с маской форматирования J для юлианского календаря. Дата юлианского календаря представляется количеством дней, прошедших с 1 января 4712 года до н. э. И только такая дата может передаваться функции TO_DATE в числовом виде.
Во всех остальных случаях аргументами функции TO DATE могут являться: входная^трока — строковая переменная, литерал, именованная константа или выражение, подлежащее преобразованию; маска^орматировдния — маска форматирования, которую функция TO—DATE будет использовать для преобразования строки (по умолчанию она равна значению параметра NLS^DATE^FORMAT); k3hkj1s - необязательный параметр, который задает язык, используемый для интерпретации имен и сокращенных названий месяцев и дней недели в строке. Формат этого параметра таков:
'NLS DATE LANGUAGE”зьМ
Здесь язык — это язык, распознаваемый вашей базой данных. Обычно перечень поддерживаемых языков приводится в руководстве по установке Oracle.
В следующем примере строка 123188 преобразуется в дату:
TO.DATEC 123188'. 'MMDDYY')
С помощью параметра язык_п1з указано, что название месяца задано на испанском языке:
то DATECAbril 12 1991', 'Month 00 YYYY'. 'NLS.DATE^ANGUAGE-Spanlsh')
Ошибки Oracle от ORA-01800 до 0RA-O1899 связаны с внутренними функциями обработки даты РСУБД и могут генерироваться при неудачном преобразовании даты. Прочитав описание этих ошибок, вы узнаете много нового о нюансах правил преобразования даты. Перечислим некоторые из них.
О Задающий дату литерал, переданный для преобразования функции T0_DATE, не может быть длиннее 220 символов.
О Нельзя в одну маску форматирования включать и элемент, обозначающий дату юлианского календаря (J), и день года (DDD).
О Включать в маску несколько элементов, представляющих один и тот же компонент значения даты-времени, не разрешается. Например, маска форматирования YYYY-YYY-DD-MM недопустима, поскольку в ней используются два представления года: YYYY и YYY.
О Нельзя в одной маске использовать 24-часовой формат времени (РР24) и элемент, обозначающий время до или после полудня (АМ/РМ).
Семейство функций TO_TIMESTAMP
Существует три типа данных семейства TIMESTAMP, и с каждым из них связана определенная функция преобразования символьной строки в значение типа TIMESTAMP:
Q TO_TIMESTAMP — преобразует символьную строку в значение типа TIMESTAMP;
Q TQ TIMESTAMP TZ — преобразует символьную строку в значение типа TIMESTAMP WITH TIME ZONE;
Q TO_TIMESTAMP_LTZ — преобразует символьную строку в значение типа TIMESTAMP WITH LOCAL TIME ZONE.
Синтаксис этих трех функций однотипен. В отличие от функции Т0_0АТЕ, ни одна из функций TO_TIMESTAMP не поддерживает входные значения типа NUMBER. Приведем синтаксис функции TO_TIMESTAMP:
FUNCTION то timestamp
(входиая^строка IN VARCHAR2
L нзскд ((юрмдтировдния IN VARCHAR2
)L ЯЗЫК_П15 IN VARCHAR2]]
RRurn TIMESTAMP
Ее аргументы очень похожи на аргументы функции TO_DATE: входная_строка - Это символьная строка, представляющая значение даты-времени; маска^ормати- РВана* ——строка элементов из табл. 10.1, определяющая формат входной строки.
По умолчанию маска форматирования равна значению одного из двух параметров NLS;
О NLS_TIMESTAMP_FОRMAT — для функций TO_TIMESTAMP и TO_TIMESTAMP_LTZ;
О NLS_TIMESTAMP_TZ_FORMAT - для функции TO_TIMESTAMP_TZ.
Необязательный параметр язык__п!з задает язык, используемый для интерпретации имен и сокращенных названий месяцев и дней недели в строке.
С семейством функций TO_TIMESTAMP также используются элементы форматирования, описанные в табл. 10.1. Например, следующие вызовы ТО TIMESTAMP преобразуют символьные строки в значения типа TIMESTAMP:
--Установки параметров NLS соответствуют принятым 8 США DECLARE
a TIMESTAMP;
b TIMESTAMP;
BEGIN
а 尸 TO_TIMESTAMP('24-Feb-2002 09.00.00.50 PM');
b 尸 TOJIMESTAMPC'02/24/2002 09.00.00.50 PM'.
'frm/dd/yyyy hh:m1 ;ssxff AM');
DBMS_OUTPUT.PUT_LINE(a):
DBMSJUTPUT.PULLINECb);
END;・,
Вот результат выполнения этого кода:
24-Feb-20D2 09.00.00.500000 PM
24-Feb-2002 09.00.00.500000 PM
Обратите внимание на то, как представлены доли секунды (.50), и на использование маски XFF. Элемент форматирования X определяет местоположение десятичного разделителя (в данном случае точки), отделяющего количество целых секунд от дробной части. Можно было бы просто поставить в этом месте точку (•FF), но мы предпочли воспользоваться символом X, поскольку он указывает что на этом месте должен находиться разделитель, соответствующий установке парамера NLSJERRITORY.
①ункцин TO_TIMESTAMP_TZ и TO_TIMESTAMP_LTZ также могут преобразовывать символьные строки, содержащие информацию。часовом поясе. О том, как с ними работать, рассказывается в следующем разделе.
Часовые пояса
Возможное наличие информации о часовом поясе в функциях TO_TIMESTAMP_TZ и TO_TIMESTAMP_LTZ несколько усложняет их использование по сравнению с базовой функцией TO_TIMESTAMP, Информация о часовом поясе задается одним из следующих способов.
Q Как положительное или отрицательное значение смещения времени в часах и минутах относительно UTC (иногда называемого GMT или временем по Гринвичу). Например, значение -5:00 эквивалентно Восточному стандартному времени США (U.S. Eastern Standard Time). Значение смещения должно находиться в диапазоне от -12:59 до +13:59.
Q В виде названия часового пояса, например: US/Eastern, US/Pacific и т. д.
О В виде сочетания названия и аббревиатуры часового пояса, например: US/Eas- tern EDT.
Рассмотрим несколько примеров. Начнем с наиболее простого из них, в котором часовой пояс вообще не указан:
TO_TIMESTAMP_TZC123198 083015.50', 'MMDOYY HHMISS.FF');
Дата и Ервмл здесь определены как 31 декабря 1998 года, 8 часов 30 минут 15 с половиной секунд до полудня. Поскольку часовой пояс не указан, Oracle считает, что время относится к текущему часовому поясу. Каждый сеанс Oracle связан с конкретным часовым поясом, который можно определить с помощью следующего запроса:
SQL> SELECT SESSIONTIMEZONE FROM DUAL:
SESSIONTIMEZONE
-05:00.
Значение -05:00 указывает, что время часового пояса сеанса на пять часов нуль минут меньше UTC. Эта разница соответствует Восточному стандартному времени в Соединенных Штатах, а разница +10:00, например, — Восточному стандартному времени в Австралии.
Далее представим часовой пояс с использованием смещения в часах и минутах относительно UTC. Обратите внимание на обозначения TZH и TZM, отмечающие, где во входной строке указано смещение времени( выраженное в часах и минутах:
TO_TIMESTAMP_TZ('123188 083015.50-05:00', 'ММООУУ HHMISS.FF TZH:TZM'):
Здесь значение даты-времени интерпретируется как Восточное стандартное время США (независимо от часового пояса сеанса).
Следующий пример показывает, как задать часовой пояс, используя имя региона. В нем задается регион America/Detroit, что эквивалентно Восточному времени в Соединенных Штатах. Обозначение TZR в маске форматирования указывает местоположение имени региона во входной строке (в параметре NLS_TERRITORY задана установка для США):
TO_TIMESTAMP_TZ('27-0ct-2002 01:30:00.00 America/Detroif.
'dd-Mon-yyyy hh:mi:ssxff TZR'):
Этот пример интересен тем, что он представляет не Восточное стандартное время, а просто Восточное время. Разница между ними заключается в том, что термин Восточное время обозначает либо Восточное стандартное время, либо Восточное летнее время - в зависимости от того, выполнен ли переход на летнее время. А он может быть выполнен1 Мы составили этот пример таким образом, Чтобы он действительно был неоднозначным. 27 октября 2002 года - это дата, когда заканчивается действие Восточного дневного времени и время 2:00 превращается в 1:00. Поэтому в указанный день 1:30 наступает дважды: первый раз по Восточному летнему времени, а второй раз по Восточному стандартному времени. Так какое же время мы имеем в виду, когда говорим, что сейчас 1:30 27 октября 2002 года?
Названия региона не достаточно для различения стандартного и летнего времени. Чтобы устранить эту неоднозначность, необходима спецификация часового
пояса, как в следующем примере. Обратите внимание, что в маску форматирования добавлен элемент TZD:
ТО Т1МБТАМР_ЛС'27-Oct-2002 01:30:00.00 Amerlca/Detrolt EOT1. 'dd-Mon-yyyy hh:ml:ssxff TZR TZD');
Нам нужно задать смещение часового пояса в часах и минутах (например, -5:00) или использовать сочетание названия и аббревиатуры часового пояса. Если указать только имя региону то при наличии неоднозначности в определении времени (летнее или стандартное) Oracle будет считать, что задано стандартное время.
ПРИМЕЧАНИЕ Если значение параметра ERROR_ON_OVERLAP„TIME сеанса равно TRUE, Oracle выдает ошибку каждый раз, когда вы задаете неоднозначное время.
Полный список поддерживаемых Oracle названий регионов и часовых поясов содержит представление V$TIMEZONE_NAMES. Доступ к нему может получить любой пользователь базы данных. Анализируя информацию этого представления, обратите внимание на то, что сокращения часовых поясов не уникальны (см. следующую врезку).
К ВОПРОСУ О СТАНДАРТЕ ЧАСОВЫХ ПОЯСОВ
Часовые пояса настолько важны, что естественным было бы сделать предположение о наличии международного стандарта, определяющего их названия и аббревиатуры. Тем не менее такого стандарта нет. Названия часовых поясов и их аббревиатуры не только не стандартизированы, но некоторые из них даже повторяются. Налрнмер, аббревиатура EST обозначает Восточное стандартное время и в США и в Австралии, однако оно соответствует двум часовым поясам с разным смещением времени. Вот почему функция TO_TIMESTAMP не позволяет задавать часовой пояс лишь с помощью аббревиатуры его названия.
Поскольку стандарта часовых поясов ие существует, у вас может возникнуть вопрос об источнике информации, содержащейся в представлении V$TIMEZONE_NAMES. Она получена по адресу: ftp://el5le.ncl.nlh.gov/pub. Если вы заинтересуетесь этой темой, обратите внимание на файл tzdata.2002c.tar.gz.
Литералы типа DATE и TIMESTAMP
Литералы типа DATE и TIMESTAMP являются частью стандарта ANSI SQL и введены только в Oracle9i, Они представляют еще одну возможность записи значений в переменные типа даты-времени. Литерал типа DATE состоит из ключевого слова DATE, за которым следует дата:
DATE 'YYYY-MM-DD'
Литерал типа TIMESTAMP состоит из ключевого слова TIMESTAMP, за которым следуют дата и время:
TIMESTAMP 'YYYY-MM-DO HH;MI:SS[.FFFFFFFFF] [{+|-}НН:М1]'
Обозначение FFFFFFFFF представляет доли секунды и не является обязательным. Такое представление содержит от одной до девяти цифр. Смещение часового
п0яса (+НН:М1) может содержать знак плюс или минус, оно не является обязательным. Часы всегда задаются в 24-часовом формате.
ПРИМЕЧАН И Е
Если в литерале типа TIMESTAMP опустить смещение часового пояса, по умолчанию будет использоваться часовой пояс текущего сеанса.
В следующем коде PL/SQL содержится несколько допустимых литералов типа
ОЛТЕ и timestamp;
declare
a TIMESTAMP WITH TIME ZONE;.
b TIMESTAMP WITH TIME ZONE;
c TIMESTAMP WITH TIME ZONE;
d TIMESTAMP WITH TIME ZONE;
e DATE:
BEGIN
Две цифры для долей секунды
а :- TIMESTAMP '2002-02-19 11:52.00.00 -05;00';
-9 цифр для долей секунды, 24-часовой формат. 14:00 - 2:00 пополудни
Ь :■ TIMESTAMP- '2002-02-19 14.00:00.000000000 -5:00';
-Без долей секунды
с :- TIMESTAMP '2DD2-D2-19 13:52:00 -5:00';
-Без часового пояса, по умолчанию используется часовой пояс текущего сеанса
d :- TIMESTAMP '2002-02-19 13:52:00':
■ Литерал типа DATE
е :- DATE '2002-02-19':
Формат литералов типа TIMESTAMP определяется стандартом ANSI/ISO, изменить его не можете ни вы, ки администратор базы данных. Поэтому их использование уместно везде, где нужно литерально задавать в программном коде значения даты-времени.
Применяя литерал типа TIMESTAMPj часовой пояс можно задать посредством имени региона его действия, Например, следующий блок показывает, что Тихоокеанское стандартное время США 10:52 (смещение -8:00) эквивалентно Восточному стандартному времени США 13:52:
DECLARE
a TIMESTAMP WITH TIME ZONE:
b TIMESTAMP WITH TIME ZONE:
BEGIN
a :- TIMESTAMP '2002-02-19 10:52:00 -8:00';
bTIMESTAMP '2002-02-19 13:52:00 EST1:
IF a - b THEN
dbms output.put lineCa - b');
END IF;』 "'
Вот результат выполнения этого кода; а ■ b
Вас могло смутить, что в данном примере используется обозначение EST, но оно иллюстрирует важный момент: запрос к представлению V$TIMEZONE_NAME$ возвращает обозначение EST и как название региона (столбец TZNAME), и как его аббревиатуру (столбец TZABBREV). В качестве названия региона обозначение EST может использоваться для задания часового пояса в литерале типа TIMESTAMP. Очевидно, что в этом примере оно интерпретируется как Восточное стандартное время США.
Элемент форматирования FX
В PL/SQL элемент FX (точный формат) используется в качестве модификатора маски форматирования. Он означает, что символьный аргумент в вызове функции TO_DATE должен точно соответствовать маске форматирования.
Если элемент FX не задан, функция TO_DATE не требует точного соответствия символьной строки формату. Она допускает следующие послабления.
О Лишние пробелы в символьной строке игнорируются. Ни в какой части значения даты-времени пробелы не являются значащими данными. Единственное их назначение - разделять части даты и времени:
TO_DATE С Jan151994'. 'MON DD YYYY')
О Такие числовые значения, как номер дня или года, не обязательно должны содержать ведущие нули, чтобы соответствовать маске. Если числа располагаются в строке в определенном месте (обычно они выделяются в строке с помощью разделительных символов), функция TO_DATE правильно преобразует числовые значения:
TO_DATE ('1-1-4', 'DD-MM-YYYY')
ТО”АТЕ ('7/16/94', 'MM/DO/YY')
О Местоположение знаков препинания в преобразуемой строке должно соответствовать местоположению знаков препинания в маске форматирования . В следующем примере в маске форматирования используются разделители в виде дефисов (-), а в исходной строке — в виде символов вставки (л). Но даже несмотря на это, у функции ТО_ОАТЕ не возникло затруднений:
ТО—МТЕ CJANUARri*94', 'Month-dd-yy'5
Такая гибкость — замечательное свойство функции, если вы, конечно, не хотите ограничить пользователя вводом данных только в стандартном формате. В противном случае используется модификатор FX, благодаря чему между маской форматирования и строкой соблюдается строгое соответствие.
Элемент FX не допускает гибкости в интерпретации строки. Строка не может содержать дополнительных пробелов, если их нет в маске форматирования. Числовые значения должны содержать ведущие нули, чтобы их длина в точности была равна длине соответствующих элементов маски. Все знаки препинания и литеральные значения должны соответствовать знакам препинания и тексту в кавычках в маске форматирования (за исключением регистра, который всегда игнорируется). Во всех следующих примерах PL/SQL генерирует одну из приведенных ниже ошибок:
TO_DATE ('1-1-4'. 'fxDD-MM-YYYY'}
TO_DATE ('7/16/94'. 'FXMM/DD/YY')
TO_DATE ('JANUARY*!* the year of 94', 'FXMonth-dd-,'WhatIsaynQtdo"yy')
ORA-01B61: literal does not match format string
DRA-01B62: the nuneric value does not match the length of the format Item
Модификатор FX может быть задан в верхнем, нижнем и смешанном регистре. Результат будет одинаковым. Так как он является переключателем, то может задаваться в маске форматирования несколько раз. Каждое его появление меняет установку на противоположную: первый модификатор FX требует точного соответствия для всех следующих за ним элементов, второй — указывает, что для следующих за ним элементов точное соответствие не требуется, и т. д. По умолчанию, то есть когда модификатор FX вообще отсутствует, точное соответствие не требуется ни для какой части строки.
В приведенном ниже примере задана три модификатора FX. В результате точное соответствие требуется для номера дня и гада, на не для номера месяца:
TO_DATE ('07-1-1994'. 'FXDD-FXMM-FXYYYY')
Следующая попытка преобразования даты вызовет ошибку ORA-01862, поскольку номер года задан не полностью:
TO_DATE С'07-1-94', 'FXDD-FXMM-FXYYYY') -- Неверный формат строки!
Для дополнения строки пробелами или нулями в вызове функции TO_DATE, согласно требованиям маски форматирования, можно использовать модификатор FM. Иными словами, модификатор FM обеспечивает требуемое модификатором FX точное соответствие формату. Далее в этой главе, в разделе, посвященном элементу FM, вы узнаете, как этот же модификатор удаляет ведущие нули и пробелы из строки, возвращаемой функцией TO_CHAR.
Теперь вызов функции TO_DATE возвращает дату, поскольку модификатор fm в начале маски форматирования включает режим заполнения для всей строки, изменяя 1 на 01 и 94 на 1994:
TO_DATE C'07-L94', 'FXfmDD-FXMM-FXYYYY')
В одну строку форматирования можно вводить па нескольку экземпляров модификаторов FM и FX, включая и отключая для разных частей строки соответствующие им режимы.
Элемент форматирования RR
После недавней смены тысячелетий все чаще используется четырехзначный формат года, поскольку стала очевидной неоднозначность привычного двухзначного формата. Например, что означает 1 января 45 года — начало 1945 года или 2045 года? Однако не все желают менять сваи привычки, так что вам, вполне вероятно, придется позволить пользователям вводить даты с двухзначным обозначением года.
Использование такого обозначения мажет вызывать проблемы при определении столетия и тысячелетия. Элемент форматирования YY всегда означает теку- Вее столетие, поэтому если пользователь в ноябре 1999 гада вводит 1/1/1 или ^■Jan-1, то считается, что речь идет а 1 января 1901 года, анеа 1 января 2001 года.
Какой же выход у менеджеров информационных систем? Одно из решений заключается в изменении логики приложений таким образом, чтобы при вводе пользователем номера года меньше 10 (или другого значения, которое вы посчитаете подходящим) данный год считался относящимся к следующему столетию. Зто удастся осуществить, но сколько труда придется затратить?
К счастью, в Oracle предусмотрен элемент форматирования, с помощью которого данная проблема легко решается. Это элемент RR, позволяющий до 2000 года вводить даты XXI столетия, а после 2000 года - XX столетия.
ПРИМЕЧАНИЕ
В приведенном ниже описании работы элемента RR столетие определяется таким образом: XX столетие составляет периед 1900-1999 годы, а XXI — период 2000-2099 годы. Мы понимаем, что это определение не правильное, но так проще объяснить поведение указанного элемента.
Когда текущий год относится к первой половине столетия (значения от 0 до 49), тогда при вводе даты первой половины столетия элемент RR возврашает текущее столетие, а если вводится дата второй половины столетия (значения от 50 до 99), элемент RR возвращает предыдущее столетие.
И наоборот, если текущий год относится ко второй половине столетия (значения от 50 до 99), при вводе даты первой половины столетия элемент RR возвращает следующее столетие, а при вводе даты второй половины столетия - текущее столетие.
Не понятно? Нам тоже пришлось призадуматься, когда мы прочитали это впервые. Правила работы элемента RR призваны помочь определить, какое столетие имел в виду пользователь; вводя две цифры, обозначающие год. Вот несколько примеров. Обратите внимание, что в 2002 году для 88-го года и 18-го года функция TD_CHAR возвращает даты, относящиеся к XX и XXI столетиям соответственно:
SELECT TO_CHAR (SYSDATE, 'WDD/YYYY ') "Current Date".
TO_CHAR (TO_DATE CU-OCT-SB'. 'DD-MON-RR'), 'YYYY') "Year 8B".
TO_CHAR (TO_DATE ('14-0CT-18'. 'DD-MON-RR'), 'YYYY') "Year IB" FROM dual;
Current Date Year 8B Year IB
02/25/20021988 2018
Когда наступит 2050 год, RR будет интерпретировать те же даты иначе:
SELECT TO_CHAR (SYSDATC, 'WI/DD/YYYY') "Current Date",
TO_CHAR (TO_DATE ('10/14/8B', 'WI/DD/RR'), 'YYYY') "Year 88',
TO_CHAR (TO_DATE ('10/14/18,, 'MM/DD/RR'), 'YYYY'} "YEAR 18* FRW dual;
Current Date Year BB Year IB
02/25/2050 20BB 211B
Конечно, если выг используя элемент RR сейчас, захотите ввести дату второй половины XXI столетия (то есть захотите, чтобы 75-й год интерпретировался как 2075), вам придется соответственно дополнить программный код. До наступления 2050 года маски с элементом RR будут интерпретировать такие двухзначные номера года как относящиеся к XX столетию. Точно так же вам потребуется специально дополнить программу, если нужно будет вводить номера годов 00-49, относящиеся к началу XX столетия, поскольку элемент RR распознает их как принадлежащие XXI столетию.
Существует несколько способов активизации логики элемента RR в уже существующих приложениях. Простейший из них заключается в том, чтобы просто
изменить используемую по умолчанию маску форматирования для даты в базе данных. Для этого нужно следующим образом изменить инипиализационный параметр NLS_DATE_FORMAT:
NLS_DATE_FORMAT - 'bW/DD/RR'
или
NLS_DATE_FORMAT - 'DD-MOhJ-RR1
в зависимости от того, каким был предыдущий формат. И если только в ваших программах и отчетах нет жестко закодированных масок форматирования, считайте, что дело сделано. Перезапустите базу данных - и приложение позволит пользователям вводить даты, относящиеся к XXI столетию. Если маски форматирования заданы где-либо в свойствах элементов Oracle Forms или Oracle Reports, вам придется изменить эти модули.
Преобразование даты в строку
Присвоить переменкой значение типа даты-времени — только полдела. Еще нужно прочитать его и представить в понятной для человека форме. Для этого Oracle предлагает воспользоваться функцией TO_CHAR.
Функция TO_CHAR
Предназначена для преобразования значения даты-времени в строку переменной длины. Эта функция применяется и для типа данных DATE, и для типов семейства TIMESTAMP. Кроме того, она выполняет преобразование чисел в символьные строки (см. главу 9). Синтаксис фушщии TO_CHAR следующий:
FUNCTION TO_CHAR
(входная_дата IN DATE
[,паска_форматмрО8анми IN VARCHAR2
[.R3HK$ls IN VARCHAR2J]
RETURN VARCHAR2
Здесь входнаяjiara — символьная строка, представляющая значение даты-времени; маска j/юрмдтирования — строка элементов из числа перечисленных в табл. 10.1, определяющая формат выходной строки; язык_п1$ — строка, задающая язык отображения даты. Параметры наска^юрма тировдния и язык^1з не являются обязательными.
ПРИМЕЧАНИЕ
Для того чтобы результирующая строка была представлена символами национального набора, аме- сго функции TO_CHAR следует воспользоваться функцией TO.NCHAR. Но имейте в виду, что в этом случае маска форматирования тоже должна быть задана в символах национального набора. В про- рвном случве б^дет сгенерирована ошибка ORA-01821: date format not recognized.
Если маска^рматирования не задана, по умолчанию используется формат даты, Установленный для базы данных. Это формат DD-MON-RR, если только администра- тор базы данных не задал другой формат, изменив значение параметра NLS_DA- TE-FORMAT, С помощью команды ALTER SESSION можно также изменить используемый по умолчанию формат для текущего сеанса :
MTER SESSION SET NLS_DATE_FORMAT - 'DD/MM/YYYY1;
Возможно, у вас не будет задан параметр язык_п1з; в таком случае по умолчанию используется язык, установленный для базы данных. Он задается либо параметром NLS—LANGUAGE, либо параметром NLS_DATE_LANGUAGE.
Приведем несколько примеров применения функции для преобразования дат ТО CHAR. Примеры предваряются краткими пояснениями, определяющими особенности использования функции.
О Обратите внимание на то, что между месяцем и днем стоят два пробела, а перед номером месяца — ведущий нуль:
TO_CHAR (SYSDATE. 'Month DD. YYYY') т 'February 05, 1994'
О Для подавления пробелов и нулей используется элемент FM:
TO_CHAR (SYSDATE. 'FMMonth DD. YYYY') т 'February 5, 1994'
О Обратите внимание на различие регистра в обозначении месяца в следующих двух примерах: (Возможности форматирования в Oracle действительна замечательны!)
TO_CHAR (SYSDATE. 'MON DDth. YYYV') т 'FEB 05TH. 1994'
TUCHW (SYSDATE. 'FMMon DDth. YYYY') т 'Feb 5砒 1994'
На элемент форматирования TH не распространяются правила задания регистра. Даже если вы наберете его в нижнем регистре (th) в выходную строку Oracle запишет TH.
О Для текущей даты, представленной порядковым номером дня года, выводится номер месяца и номер недели:
TO_CHAR (SYSDATE. 'DDD DD D1) т '036 05 7'
TO:CHAR (SYSDATE. 'fmDDD DD T) т '36 05 7'
О А вот как интересна можно отформатировать отчет:
TO_CHAR (SYSDATE. "'In month "RM" of year "YEAR')
'In month II of year NINETEEN NINETY FOUR'
Q Для переменных типа TIMESTAMP можно задавать время с точностью до миллисекунд:
TO_CHAR (AJIMESTAMP. 'YYYY-ffl-DD HH;MI:SS.FF AM TZH:TZM')
т значение типа '2002-02-19 01:52:00.123457000 PM -05:00'
Работа co значениями времени, включающими доли секунды, требует особого внимания. В маске форматирования доли секунды представляются элементом FF. Возможно, вы захотите задать такое количество букв F, которое соответствует желаемому количеству десятичных цифр в выходной строке. Но у вас ничего не получится. В следующем примере мы пытаемся с помощью спецификации FFFFF указать, что нам нужно пять цифр после десятичного разделителя:
DECLARE
A TIMESTAMP WITH TIME ZONE:
BEGIN
A :- TIMESTAMP '2002-02-19 13:52:00.123456789 -5:00';
DBMS_OUTPUT.PUT LINEtTO CHARCa.'YYYY-MM-DD HH:MI:SS.FFFFF AM TZH:TZM')):
END: "~
Результат будет таким:
declare *
ERROR at line 1:
ORA-01821: date format not recognized
ORA-06512: at Une 5
Если вам нужно получить пять цифр после десятичного разделителя, объявите переменную как TIMESTAMP(5). Например:
declare
A TIMESTAMP(5) WITH TIME ZONE;
BEGIN
A 尸 TIMESTAMP '2002-02-19 13:52:00.123456789 -5:00';
DBMS OUTPUT.PUT LI NE(TO CHAR(a,'YYYY-MM-DD HH:MI;SS.FF AM TZH:TZM')):
END; 一~
Результат выполнения этого кода следующий:
2002-02-19 01:52:00.123460000 РМ -05:00
PL/SQL procedure successfully completed.
Обратите внимание на полученное значение. Количество секунд на самом деле равняется 00,123456789. Однако эго значение округлено (не усечено, а именно округлено) до 00,12346.
ПРИМЕЧАНИЕ
К сожалению, Oracle жестко привязывает количество выводимых десятичных цифр к точности значения переменной типа TIMESTAMP. Мы же полагаем, что эти два параметра не должны зависеть друг от друга. PL/SQL таким образом обрабатывает числа, и было бы логично, если бы он так же обрабатывал значения типа TIMESTAMP,
Ничего не стоит по ошибке задать неверный формат даты, и с появлением типов данных TIMESTAMP вероятность этого даже увеличилась. Некоторые элементы форматирования, использующиеся со значениями типа TIMESTAMP, не могут применяться со значениями типа DATE. Например, если попытаться преобразовать значения типа DATE в символьную строку с помощью элементов FF, TZH и TZM, получится следующее:
DECLARE.
A DATE:
BEGIN
А := SYSOATE:
DBMS OUTPUT.PUT LINECTO CHAR(A.1YYYY-HM-DD HH:MI:SS.FF AM TZH'.TZM')):
END: -~"
Результат будет таким:
A :- SYSDATE;
ERROR at line 4:
ORA-01821: date format not recognized
ORA-06512: at line 5
9т。сообщение об ошибке (формат даты не распознается) может сбить вас с толку. На самом деле проблема состоит в том, что формат даты применяется не
к тому типу данных. Поэтому если получите такое сообщение, проверьте не только формат даты, но и тип данных, который вы собираетесь преобразовывать.
Преобразование данных о часовых поясах в символьные строки
Необходимость учитывать часовые пояса усложняет процесс преобразования значений даты-времени в символьные строки. Информация о часовом поясе состоит из следующих элементов: смещение относительно UTC в часах и минутах, название и аббревиатура часового пояса. Все они хранятся в переменной TIMESTAMP WITH TIME ZONE раздельно. При этом смещение указывается всегда, а возможность вывода названия и аббревиатуры часового пояса зависит от того, была ли эта информация задана при установке значения переменной. Рассмотрим следующий пример:
DECLARE
A TIMESTAMP WITH TIME ZONE;
В TIMESTAMP WITH TIME ZONE;
C TIMESTAMP WITH TIME ZONE;'
BEGIN
A :- TO TIMESTAMP TZC2002-06-18 13:52:00.123456789 -5:00',
'YYYY-MM-DD HH24;MI:SS.FF T2HJZM'):
В :- TO_TIMESTAMP TZ('2002-06-18 13:52:00.123456789 US/Eastern',
-~ 'YYYY-MM-DD HH24-MI;SS.FF TZR');
C := TO TIMESTAMP TZ「2002-06-18 13:52:00.123456789 US/Eastern EDT',
"-'YYYY-MM-DD HH24:MI:SS.FF TZR TZD');
DBMS_DUTPUT.PUT_LINE(TD_CHAR(A.
'YYYY-MM-DD HH:MI:Ss7fF AM TZH:TZM TZR TZD1));
D8MS_DUTPUT.PUT LINEtTD CHARCB.
'YYYY-MM-DD HH:MI:SS7fF AM TZH:TZM TZR TZD'));
DBMS_OUTPUT.PUT_LINE[TO CHARCC,,
'YYYY-MM-DD HH:MI:SS~FF AM TZH:TZM TZR TZD')):
END:
Результат выполнения этого кода таков;
2002-06-18 01:52:00.123457000 РН -05:00 -05:00
2002-06-18 01:52:00.123457000 РМ -04:00 US/EASTERN EOT
2002-06-18 01:52:00.123457000 РМ -04:00 US/EASTERN EOT
Обратите внимание на следующее.
О Для переменной А часовой пояс задан как смещение в часах относительно UTC. Поэтому при выводе значения этой переменной отображается только смещение.
О Поскольку название региона для переменной А не задано, Oracle возвращает смещение для часового пояса. Это лучше, чем вообще отсутствие какай-либо информации.
О Для переменной В задано название региона действия часового пояса. Для внутреннего представления Oracle преобрааует это наавание в смещение относительно UTC, но название региона тоже сохраняет. Поэтому выводится и название региона)и смещение,
О Для переменной В Oracle правильно распознала действующее в июне летнее время. В результате значение В было дополнено аббревиатурой EDT.
О Для переменной С заданы название и аббревиатура часового пояса, так что при выводе этой переменной отображаются оба значения.
Смещение относительно UTC связано с регионами действия часовых поясов но принципу <один-ко-многим>; для определения региона знать только лишь смещение недостаточно. Поэтому если имя региона не задано в исходном значении, Oracle не может его вывести.
Элемент форматирования FM
PL/SQL поддерживает элемент форматирования FM, используемый как модификатор маски форматирования. Этот элемент обеспечивает подавление дополняющих пробелов и ведущих нулей, возвращаемых функцией TO_CHAR.
По умолчанию функция TO_CHAR с маской форматирования генерирует и пробелы> и ведущие нули (между именем месяца и номером дня в нашем примере пять пробелов):
TO_CHAR (SYSDATE. 'Month DD. YYYY') т 'April 05, 1994'
Но если поместить в начало маски форматирования модификатор FM, то и лишние пробелы, и ведущие нули исчезнут:
TO_CHAR (SYSDATE, 'FMMonth DD, YYYY') т 'April 5, 1994'
Модификатор можно задавать в верхнем, нижнем, а также смешанном регистре; на результат это никак не влияет.
Модификатор FM работает как переключатель и может использоваться в маске форматирования неоднократно, каждый раз меняя свое действие на противоположное. По умолчанию, то есть если модификатор FM вообще отсутствует в маске форматирования, пробелы не подавляются, и ведущие нули включаются в результирующее значение. Поэтому первое появление модификатора FM в маске указывает, что для всех следующих элементов пробелы и ведущие нули подавляются. Второе его появление отключает подавление пробелов и ведущих нулей, третье 一 опять включает и т. д.
В следующем примере дополняющие пробелы в конце имени месяца подавляются, но в результате второго включения модификатора FM сохраняется ведущий ноль в номере дня:
TO_CHAR (SYSDATE, 'FMMonth fmDD. YYYY') т 'April 05. 1994'
Если модификатор FM в маске отсутствует, преобразованная дата всегда дополняется пробелами до фиксированной длины, зависящей от других элементов форматирования. При использовании FM длина возвращаемой строки связана с фактическим значением даты-времени и может быть разной.
Модификатор FM может также применяться для заполнения исходной строки пробелами и нулями в ссютветствии с маской форматирования функции TO_DATE. 。6 этом рассказывалось ранее, в разделе, посвященном модификатору FX.
Преобразование интервалов
В отличие от значений даты-времени, представляющих конкретные моменты времени, интервалы представляют промежутки времени. Их можно задавать в текстовом виде, причем разными способами.
Интервал состоит из одного или нескольких элементов даты-времени. Например, его можно выразить в годах и месяцах. В табл. 10.2 перечислены стандартные имена каждого из этих элементов. Они используются в функциях преобразования типов данных и в выражениях,о чем подробно рассказывается в следующих разделах.
Таблица 10.2. Имена элементов интервала
ИмяОписание
YEARНекоторое количество лег в диапазоне от 1 до 999 999 999
MONTHНекоторое количество месяцев в диапазоне отО до И
DAYНекоторое количество дней в диапазоне от 0 до 999 999 999
HOURНекоторое количество часов в диапазоне от 0 до 23
MINUTEНекоторое количество минут в диапазоне от 0 до 59
SECOND Некоторое количество секунд в диапазоне от 0 до 59,999 999 999
При использовании в функциях преобразования типов имена элементов интервала не чувствительны к регистру. Например, элементы YEAR, Year и year эквивалентны.
Семейство функций NUMTO
Функции NUMTOYMINTERVAL и NUMTODSINTERVAL предназначены для преобразования отдельных числовых значений в значения типа INTERVAL. В вызове функции задаются числовое значение и один из элементов интервала, перечисленных в табл. 10.2. В результате числовое значение преобразуется в этот элемент интервала.
Функция NUMTOYMINTERVAL преобразует числовое значение в интервал типа INTERVAL YEAR ТО MONTH, а функция NUMTODSINTERVAL — в интервал типа INTERVAL DAY ТО SECOND.
В следующем примере функция NUMTOYMINTERVAL используется для преобразования значения 10.5 в значение типа INTERVAL YEAR ТО MONTH. Второй аргумент этой функции — строка Year — указывает, что заданное в первом аргументе число представляет количество лет:
DECLARE
A INTERVAL YEAR ТО MONTH; BEGIN
А ;= NUMTOYMINTERVAL (10.5. 'Year');
DBMS_OUTPUT.PUTJINE(A);
END;-
Результат выполнения данного кода таков:
+10-06
Как видите, исходное значение 10.5 преобразовано в значение, которое соответствует 10 годам и 6 месяцам. Любое дробное количество лет преобразуется в эквивалентное количество месяцев, и результат округляется до целого. Например, если исходное значение равно 10,9 годам, ано будет преобразовано в 10 лет и 10 месяцев.
В следующем примере показано, как числовое значение преобразуется в интервал типа INTERVAL DAY ТО SECOND;
DECLARE■
A INTERVAL DAY TO SECOND:
BEGIN
A :- NUMTODSINTERVAL (1440. 'Minute']:
DBMS_OUTPUT.PUT_LINE(A);
END: ~
В качестве результата получаем такое значение:
+D1 DO:DO:OO.OOOODO
PL/SQL procedure successfully completed.
Как видите, Oracle автоматически преобразовала входное значение 1400 минут в интервал равный 1 дню. Это удобно, поскольку вам не придется заниматься такой работой самостоятельно. С помощью функций NUKTO ничего не стоит вывести любое количество минут (или секунд, или дней, или часов) в читабельном виде.
Функции TO^xxINTERVAL
Если функции NUMTO подходят для преобразования в интервалы числовых значений, то для преобразования символьных строк можно использовать функции TO_YMINTERVAL и TO_DSINTERVAL. Выбор функции зависит от того, значение какого типа вы хотите получить 一 INTERVAL YEAR ТО MONTH или INTERVAL DAY TO SECOND.
Функция TO_YMINTERVAL преобразует символьную строку в значение типа INTERVAL YEAR ТО MONTH. Вызывается она так:
TOJMINTERVALC'K-Af')
Здесь аргумент У представляет количество лет, а М — количество месяцев. Обязательно должны быть заданы оба значения, разделенные дефисом.
Точно так же функция TO_DSINTERVAL преобразует символьную строку в значение типа INTERVAL DAY ТО SECOND. А вот как она вызывается:
TO_DSINTERVAL CD HH-.MI-.SS')
Здесь D — это количество дней, а аргумент HH:MI:SS представляет часы, минуты и секунды, разделенные символами двоеточия.
Следующий пример демонстрирует использование обеих функций:
DECLARE
A INTERVAL YEAR ТО MONTH;
В INTERVAL DAY ТО SECOND:
С INTERVAL DAY TO SECOND;'
BEGIN
A :- TO_YMINTERVAL ('40-3'):-- моб возраст
В 尸 TOJSINTERVAL ('10 1:02:10');
С :н TO DSINTERVAL ('10 1:02:10.123');-- доли секунды
end; —
При вызове любой из них нужно задавать полный набор значений. Например» Нельзя вызвать функцию TO.YMINTERVAL, указав только год, или вызвать функцию T°_D3 INTERVAL, не задав секунды. Можно опустить только доли секунды.
Выражения с интервальными значениями
Выражения с интервальными значениями используются подобно литералам типа TIMESTAMP и предоставляют еще одну возможность для преобразования символьной строки в значение типа INTERVAL. При этом они обладают большей гибкостью, чем семейство функций ТО_ххINTERVAL.
Выражение с интервальным значением имеет такой синтаксис:
INTERVAL 'символьное_предстдвление' ндЧдльнийулемЕНт ТО конечный_эленент
Здесь стольноеуредставление - это символьная строка, представляющая интервал (описание представления двух типов интервалов в символьной форме дано выше, в разделе «Функции TO_xxINTERVAL>); начальныйулемент задает начальный элемент интервала; конечный_злемент задает конечный элемент интервала.
В отличие от функций TO_xxINTERVAL выражения с интервальными значениями позволяют задавать интервал с использованием любой последовательности элементов даты-времени (см. табл. 10.2). Указываются только два ограничения: элементы должны быть последовательными и в одном интервале не допускается переход от месяцев к дням.
Приведем несколько примеров:
DECLARE
A INTERVAL YEAR ТО 側TH;
В INTERVAL YEAR TO MONTH:
C INTERVAL DAY TO SECOND;
D INTERVAL DAY TO SECOND:
BEGIN
/* Несколько примеров интервалов YEAR TO 側TH */
A :■ INTERVAL '40-3' YEAR TO MONTH:
В :- INTERVAL '40' YEAR;
/* Несколько 叩达миров интервалов DAY ТО SECOND */
С :■ INTERVAL '10 1:02:10.123' DAY TO SECOND:
/* He работает в 0гас1в91 Release 1 из-за ошибки */
•- 0 INTERVAL '1:02' HOUR ТО MINUTE;
/* Ниже пр4в。口нтс月 два обходных способа задания интервала-
HOUR ТО MINUTE, представляощего ссобой только часть интервала DAY ТО SECOND */ SELECT INTERVAL '1:D2' HOUR TO MINUTE INTO D FROM dual:
D :- INTERVAL 'Г HOUR 十 INTERVAL '02' MINUTE: END;
ВНИМАНИЕ
В Oracle9l Release 1 и Release 2 выражение INTERVAL '1:02' HOUR TO MINUTE можно использовать в SQL-инструкции, но не в оператора PL/SQL. Более того, при выполнении программы будет выдано сообщение об использовании ключевого слова BULK в неверном контексте. Это известная ошибка, которая; по всей вероятности, будет исправлена в следующих выпусках продукта.
Поскольку не существует типа данных для представления интервалов, включающих и месяцы и дни, любая попытка задать в интервале оба значения приведет к ошибке. Например, ошибку сгенерирует следующее выражение интервала: INTERVAL '1-15' MONTH ТО DAY
Недопустим также пропуск элементов интервала. Нельзя, скажем, задать количество дней и минут, не указав количество часов.
Oracle выполняет нормализацию значений интервалов. В следующем примере 72 час 15 мин преобразуются в значение 3 дня, 0 ч и 15 мин:
DECLARE
A INTERVAL DAY ТО SECOND;
BEGIN
SELECT INTERVAL '72:15' HOUR TO MINUTE INTO A FROM DUAL;
DBMS OUTPUT.PUT LINECA);
END:-
Результат выполнения этого хода таков:
+03 00:15:00.000000
Oracle почему-то нормализует только старшие значения (например, часы). Попытка задать интервал 72:75 (72 час 75 мин) приведет к ошибке.
Форматирование интервалов для вывода
До сих пор речь шла о преобразовании интервалов и выводе данных посредством поддерживаемого Oracle механизма неявного преобразования значений. Вы, вероятно, думаете, что для контроля выводимого значения интервала можно воспользоваться функцией TO_CHAR с маской форматирования. Увы, это не так. Для форматирования интервалов функция T0JHAR применяться не может. Например, выполнение хода
DECLARE
A INTERVAL YEAR ТО MONTH;
BEGIN
А :- INTERVAL '40-3' YEAR TO MONTH;
DBMS OUTPUT.PUT LINECTO CHARCA, 'YY "лет" и ММ "месяца*')):
приведет к такому результату:
+000040-03
Честно говоря, не понятно, чем руководствовались инженеры из Oracle)принимая решение о том, что функция TO_CHAR не должна поддерживать интервалы.
Если вас не устраивает то, как выполняется неявное преобразование значения интервала в символьную строку, можете воспользоваться функцией EXTRACT (впервые появившейся в Oracle9t):
DECLARE
A INTERVAL YEAR ТО MONTH: begin
A :- INTERVAL '40-3' YEAR TO MONTH;
OBMS_OUTPUT.PUT_LINE(
EXTRACTYEAR~FR0M A) || ' лет и ' ||
EXTRACT(MONTH FROM A) || ' месяца 1
Теперь результат будет таким:
4Q лет и 3 месяца
Функция EXTRACT, конечно, не так удобна, как TO_CHAR, но обладает тем преимуществом, что обрабатывает интервалы.
Функции CAST и EXTRACT
Функции CAST и EXTRACT описаны в стандарте ANSI (SQL-92). Их поддержка только недавно включена в Oracle. Функция CAST появилась в Огас1е8 как механизм явного определения типа коллекции, но в Огас1е8г ее возможности были расширены. Теперь с помощью функции CAST можно выполнять преобразования мевду встроенными типами данных, в частности взаимопреобразования значений даты-времени и символьных строк. Функция EXTRACT, введенная в Oracle9i, позволяет выделять отдельные компоненты из значений даты-времени и интервалов.
Функция CAST
Данная функция может использоваться для выполнения следующих операций с датой и временем:
О преобразование символьной строки в значение даты-времени;
О преобразование значения даты-времени в символьную строку;
О преобразование значения одного типа даты-времени (например, DATE) в значение другого типа (например, TIMESTAMP).
При использовании функции CAST для преобразования значений даты-времени в символьные строки и обратно учитываются значения таких параметров NLS: О NLS_DATE_FORMAT - при преобразовании в тип данных DATE и обратно;
О NLS_TIMESTAMP_FORMAT — при преобразовании в типы данных TIMESTAMP и TIMESTAMP WITH LOCAL TIME ZONE и обратно;
О- NLS_TIMESTAMP_TZ_FORMAT — при преобразовании в тип данных TIMESTAMP WITH TIME ZONE и обратно.
В следующем примере показано, как выполняется каждый тип преобразования:
DECLARE
a TIMESTAMP WITH TIME ZONE:
b VARCHAR2(4Q);
c TIMESTAMP WITH LOCAL TIME ZONE:
BEGIN
a :- CAST ('24-Feb-2002 09.00.00.00 PM US/Eastern' AS TIMESTAMP WITH TIME ZONE);
b := CAST (3 AS VARCHAR2);
c CAST (a AS TIMESTAMP WETH LOCAL TIME ZONE):
D8MS_0UTPUT.PUT_LINE(a):
DBMS^OUTPUT.PUTjlNECb):
D8MS_OUTPUT.PUT~LINE(c);
END; —~
Результат выполнения этого кода таков;
24-FEB-02 09.00.00.000000 PM US/EASTERN
24-FE8-Q2 09.00.00.000000 PM US/EASTERN
24-FEB-02 09.00.00.000000 РМ
Здесь на основе символьной строки генерируется значение типа TIMESTAMP WITH TIME ZONE, потом это значение преобразуется в VARCHAR2, а затем в TIMESTAMP WITH LOCAL TIME ZONE. Учтите, что при выполнении преобразования от типа данных TIMESTAMP WITH TIME ZONE к типу данных TIMESTAMP WITH LOCAL TIME ZONE информация о часовом поясе теряется.
ПРИМЕЧАНИЕ В SQL-инструкции при вызове функции CAST можно указать размер значения типа данных, например; CAST(z AS VARCHAR2(4O)). Однако PL/SQL не позволяет задать размер значения целевого типа данных.
Функция EXTRACT
Является функцией стандарта ANSI, используемой для извлечения компонентов из значения даты-времени или интервала. Вот ее синтаксис:
EXTRACT {^ня^оппонента. FROM {пзтауреня | интервал})
Здесь имя_компонента — это имя одного из элементов даты-времени, перечисленных в табл. 10.3. Имена компонентов не чувствительны к регистру символов. В аргументе дата_время или интервал задается значение даты-времени или интервала. Тип возвращаемого функцией значения зависит от извлекаемого компонента.
Таблица 10.3. Имена компонентов даты-времени для использования в функции EXTRACT
Имя компоногга
Возвращаемый тип данных
YEAR
NUMBER
MONTH
NUMBER
DAY
NUMBER
HOUR
NUMBER
MINUTE
NUMBER
SECOND
NUMBER
■nMEZONE_HOUR
NUMBER
TIMEZONJMINUTE
NUMBER
■nMEZONE_REGION
VARCHAR2
■nMEZONE_ABBR
VARCHAR2
Следующий пример демонстрирует использование функции EXTRACT для проверки того, является ли текущий месяц ноябрем:
BEGIN
IF EXTRACT (MONTH FROM SYSDATE) - 11 THEN
DBMS_OUTPUT.PUT LINE('Сейчас ноябрь');
else ~-
DBMS OUTPUT,PUT LINE('Сейчас не ноябрь1):
END IF Г ~
END;
Функцию EXTRACT удобно использовать в тех случаях, когда управление потоком выполнения программы осуществляется на основе значения одного из элементов даты-временИ) а также в тех случаях, когда требуется получить числовое значение одного из элементов даты-времени. Однако не рекомендуется использовать ее для преобразования значения даты-времени в символьную строку, как в следующем примере:
BEGIN
DBMS_OUTPIIT. PUT_LINE('Сегодняшняя дата: 1 11
EXTRACKMONTH FROM SYSDATE) || 7* ||
EXTRACKDAY FROM SYSDATE) || 7' || EXTRACT(YEAR FROM SYSDATE));
END;
В результате выполнения кода будет выведена такая строка:
Сегодняшняя дата: 2/24/2002
Посмотрите, какая громоздкая конструкция! Функция EXTRACT не предназначена для преобразования значения даты-времени в символьную строку, поскольку она возвращает главным образом числовые, а не строковые значения. В этом примере все числа неявно преобразуются в символьные строки. Более того, EXTRACT не возвращает ни названий дней недели, ни названий месяцев и не делает многого другого, что может делать функция TO_CHAR.
Арифметические операции над значениями даты-времени
PL/SQL поддерживает арифметические операций над значениями даты-времени, числовыми значениями и значениями интервалов. Например, можно прибавить к дате заданное количество дней или вычесть одну дату из другой, чтобы получить интервал времени между ними. С появлением в Oracle9t типа данных INTERVAL выполнение арифметических операций над значениями даты-времени существенно усложнилось.
Традиционные арифметические операции
До выхода Oracle9i при работе с датой можно было выполнять всего два арифметических действия:
О прибавлять числовые значения, представляющие некоторое количество дней, к значению типа DATE и вычитать их из значения этого типа;
О вычитать одно значение типа DATE из другого для получения количества дней между ними.
В Oracle9i эти возможности по-прежнему поддерживаются, но только для типа данных DATE.
ПРИМЕЧАНИЕ;;~
Кроме того, для выполнения операций с датами могли (и по-прежнему могут) использоваться функции adD_MONTHS и MONTHS^BETWEEN, Мы расскажем о них далее в этой главе, в разделе «Функции обратки даты咱ремени».
Сложение и вычитание числовых значений
При выполнении арифметических операций с датами целочисленные значения представляют дни. Для сдвига даты на один день вперед (в будущее) нужно просто добавить к ней 1:
hi re_date + 1
Можно прибавить к дате и дробное значение, представляющее часть дня. Наиболее часто употребляемые дробные значения приведены в табл. 10.4.
Таблица 10.4. Дробные значения, используемые для представления даты в арифметических операциях
Значение
Представляет
1/24
.Один час
1/1440
Одна минута
1/86400
Одна секунда
В следующем примере к значению переменной start一time прибавляется 8/24, в результате чего время изменяется с полуночи (00:00) на 8:00 АМ:
DECLARE
start time DATE DATE '2000-09-0Г:
BEGIN -
DBMS_OUTPUT,PUT LINECTO CHARCstart time.'YYYY-MM-DO HH:MI AM')):
DBMS OUTPUT.PUT*LINE(TO CHAR(start"t-lme+S/24. 'YYYY-MM-DD HH:MI AM')):
END: ~~
Результат выполнения этого кода таков:
2000-09-01 12:00 АМ
2000'-09-01 08:00 АМ
Обратите внимание, что мы представили 8 ч как 8/24, а не как 1/3 или десятичный эквивалент этого значения — 0,33333333. Используя значение 8/24, мы тем самым показываем другим программистам, что к дате добавляется 8 ч. И вам советуем при сложении чисел, представляющих элемент значения даты-времени, с датой и вычитании таких чисел из даты всегда использовать знаменатели, указанные в табл. 10.4. Они делают операции с датами более наглядными. Приведем несколько примеров:
Q для того чтобы прибавить к значению даты-времени 12, используйте не дробное значение 1/2, а 12/24;
Q для того чтобы прибавить к значению даты-времени 1 день и 12 ч, используйте выражение 1+12/24 или 36/24, но не значение 1,5;
Q одну минуту можно задать как 60 с (60/86400) или 1 мин (1/1440), в зависимости от того, в чем, минутах или секундах исчисляется время в вашем приложении.
Избегайте использования знаменателей, отличных от тех, что представлены в табл. 10.4. Например, скажите, сколько времени представляет дробь 1/48? Вы задумались? А если бы вопрос стоял о значении 30/1440, вы бы сразу поняли, что речь идет о 30 мин. Так вот, если сократить дробь 30/1440, получится 1/48, то есть те же 30 мин. Вы, конечно же, можете оперировать значением 1/48, но мы рекомендуем в программах, которые должны быть лонятны тем, кто их читает, использовать только значение 30/1440.
ВНИМАНИЕ При сложении чисел со значениями TIMESTAMP и вычитании их из этих значений производится неявное преобразование последних в тип данных DATE.
Описанный в этом разделе способ сложения и вычитания числовых значений следует использовать только с типом данных DATE. Следующий пример показывает, что значения типа TIMESTAMP WITH TIME ZONE в таких операциях неявно преобразуются в тип данных DATE:
DECLARE
start_time TIMESTAMP WITH TIME ZONE
尸 TIMESTAMP '2000-09-01 00:00:00.00 -7:00';
BEGIN一
DBMS_OUTPUT.PUT_LINE(start_time);
DBMS_0UTPUT.PUT_LINE(start_tinie+8/24);
DBMS_0UTPUT.PUTZLINE(T0_CHAR(start_time+8/24,'DD-M0N-YY HH.MI.SS')): END;
Результат выполнения этого кода таков:
01-SEP-00 12.00.00.000000 АМ -07:00
01-SEP-00
01-SEP-00 08.00.00
Во второй и в третьей строках выходных данных к исходному значению времени прибавлено 8. Однако в результате вторая строка оказалась форматированной как значение DATE, а не TIMESTAMP — это очевидное следствие неявного преобразования типов. Третья строка показывает, что значение времени сохранилось, но часовой пояс и доли секунды исчезли.
Вычисление разности двух дат
В PL/SQL невозможно сложить два значения типа DATE. И в этом есть определенный смысл. Например, что значит сложить 15 ноября 1961 года с 18 июня 1961 года? А вот вычитать значения дат можно, поскольку разностью в этом случае будет конкретное понятие — количество дней между ними. Например:
BEGIN
DBMS_QUTPUT.PUT_LINE (
TO_DATE('15-Nov-196r. 'dd-Mon-yyyy')
):
END:
Результат таков:
150
Он показывает, что 18 июня 1961 года наступило на 150 дней раньше, чем 15 ноября 1961 года. С результатами вычитания значений типа DATE нужно обращаться
внимательно, поскольку если в них присутствует компонент времени, результат получается дробным. Приведем другой пример:
BEGIN
dbms_output.put_line (
TO_DATE('15-Nov-1961 12:01 am', 'dd-Mon-yyyy hh:mi am')
-T0_DATE('18-Jun-1961 11:59 pm'. 'dd-Mon-yyyy hh:m1 am')
ENO;
На этот раз получаем следующее:
149.001388888888888888888888888888888889
Если вас интересует количество календарных дней между двумя датами в этом примере, результат, конечно, неверен. Он верен с точки зрения определения количества 24-часовых периодов между двумя значениями типа DATE, но для большинства людей важно, что эти даты разделяют 150 дней, а не 149,00139 24-часовых периодов. Для получения правильного результата можно применить к каждому из исходных значений функцию TRUNC:
8EGIN
DBMS_OUTPUT.PUT_LINE (
TRUNC(TD_DATE('15-NOV-1961 12:01 am'. 'dd-Mon-yyyy hh:m am'))
-TRUNC(TO_DATE('18-Jun-1961 11:59 pm'. 'dd-Mon-yyyy hhiml am'))
END:
И вот результат:.
150
Функция TRUNC удаляет из переданного ей значения DATE компонент времени, так что операция вычитания здесь эквивалентна операции вычитания в первом примере этого раздела, и результат представляет количество календарных дней между двумя датами. Это очень полезная функция для работы с датами, поэтому мы изучнм ее подробнее далее в этой главе, в разделе «Функции обработки даты-времени».
ПРИМЕЧАНИЕ
Результат вычитания одного значения типа DATE из другого имеет тип данных NUMBER, а результат вычитания значений типа TIMESTAMP — тип данных INTERVAL DAY ТО SECOND.
Арифметические действия с интервалами
В OracleOi введен тип данных INTERVAL, поднявший выполнение арифметических операций со значениями даты-времени на очередной уровень сложности. Эта концепция не нова. Если вы выполняли арифметические операции с датами в старых версиях Oracle, вам уже приходилось работать с интервалами. Фигурирующие в этих операциях числа представляют собой не что иное, как интервалы, выра- Генные в днях и частях дней. Возьмем, к примеру, операцию прибавления к дате одного дня. В Oracle9z эту операцию можно выполнить одним из двух способов: Mrjddte + 1:
Как видите, интервалы уже использовались. Просто теперь эта концепция формализована, и в Oracle9z появились еще три способа измерения интервалов:
О с помощью типа данных INTERVAL YEAR ТО 阍NTH — в годах и месяцах;
О с помощью типа данных INTERVAL DAY ТО SECOND - в днях, часах, минутах и секундах; ■
О с помощью числовых значений - в днях и частях дня.
Учитывая все сказанное, арифметические действия с интервалами в Oracle9i можно свести к следующим трем типам операций:
О сложение интервала со значением даты-времени и вычитание интервала из него;
О вычитание одного значения даты-времени из другого для получения интервала времени между ними;
О сложение одного интервала с другим и вычитание одного интервала из другого.
Единственное, что здесь действительно ново, так это терминология и типы данных INTERVAL. В версиях до Oracle9i можно было складывать и вычитать интервалы даты — это означало сложение и вычитание чисеЛ| тогда как сейчас, при работе с новым семейством типов TIMESTAMP, можно складывать и вычитать значения типов данных INTERVAL.
Сложение и вычитание интервала со значением даты-времени
Базовая концепция сложения интервала времени со значением даты-времени и вычитания интервала времени из значения даты-времени в Oracle9i осталась тою же, что и в предыдущих версиях РСУБД. Например, в Oracle8i можно было выполнить такую операцию:
h1re_date + 1:'
А в Oracle9i то же самое можно сделать следующим образом:
hi rebate + INTERVAL '1' DAY:
В этой записи нет ничего нового, кроме типа данных INTERVAL DAY ТО SECOND. Выражение INTERVAL 'Г DAY генерирует значение типа INTERVAL DAY ТО SECOND. Поскольку задано только количество дней, по умолчанию часы, минуты и секунды устанавливаются в нуль. О том, как составлять подобные выражения с интервальными значениями, подробнее рассказывалось ранее в этой главе.
В следующем фрагменте кода содержится еще несколько примеров использования арифметических операций с интервалами в Огас1е9::
DECLARE
h1re_date TIMESTAMP WITH TIME ZONE;
a INTERVAL YEAR TO MONTH;
b INTERVAL DAY TO SECOND:
BEGIN
Ere date :- TIMESTAMP '2000-09-01 00:00:00 -5:00';
DSMSJUTPUT.PUTJINEChlreJate);'
a :- INTERVAL '1-2' YEAR TO MONTH;
b :- INTERVAL '3 4:5:6.7' DAY TO SECOND:
--добавляем несколько лет и месяцев
hire date :- h1re_date + a:
DBMs20UTPUT.PUT_UNECh1re_date);
._ Добавляем несколько дней, часов, минут и секунд
hl rebate :- hire date + b;
DBMS二OUTPUT PUT_LlNE(h1rB_date);
END;-
Результат выполнения этого кода таков:
01-SEP-00 12.00.00.000000 АМ -05:00
01-N0V-01 12.00.00.000000 АМ -05:00
O4-N0V-O1 04.05,06,700000 АМ -05:00
Вычисление интервала между двумя значениями даты-времени
Если вы вычисляете интервал между двумя значениями типа DATE, результатом является количество 24-часовых периодов (не то же самое, что количество дней) между ними. Если получено целочисленное значение, значит, разница представляет точное число дней. Если же значение является дробным, то разница включает также некоторое количество часов, минут и секунд,
Недостаток такого механизма вычисления интервалов заключался в том, что в результате получались дробные числа. Взгляните на следующий пример, взятый из предыдущего раздела:
BEGIN
DBMS_OUTP(JT.PUT_LINE (
T0_DATE('15-Nov-1961 12:01 am', 'dd-Моп-уууу hh:mi an')
-fo_DATE('18-Jun-1961 11:59 pm'. 'dd-Mon-yyyy hh:mi am')
END:
А вот результат вьшолнения кода:
149.0013В88888888888888888В8888888888889
Что представлено здесь значением 149, понятно — количество дней, но что такое .001388888888888888888888888888888889? Сколько это часов, например? Конечно, можно вычислить представленное этим числом количество часов, минут и секунд, если они нужны для дальнейшей работы, но вы можете не получить точных результатов, особенно если имеете дело с бесконечной десятичной дробью. Иное Дело Oracle9il Новый тип данных INTERVAL позволяет без труда вычислять интервалы в понятном для человека представлении. Например: declare
a INTERVAL DAY(3) ТО SECOND (0);
BEGIN
Вычисляем интервал и присзаиваем его
--переменной типа INTERVAL DAY ТО SECOND
а :■ T0_nMESTAMP('15-Nov-1961 12:01 am1, 'dd-Моп-уууу hh:mi am1)
-TO_TIMESTAMP('IB-Jun-1961 11:59 pm', 'dd-Mon-yyyy hh:m1 am1);
DBMS_OUfPUT.PUTJlNE (a);
Результат выполнения этого кода таков:
+149 00:02:00
В данном примере одна дата вычитается из другой, а разность присваивается переменной типа INTERVAL DAY ТО SECOND. Теперь вместо непонятного числа с повторяющейся дробной частью мы получили интервал, равный в точности 149 дням, О ч, 2 мин и 0 с.
ВНИМАНИЕ
Значения типа INTERVAL возвращаются только в результате выполнения операции вычитания значений новых типов данных TIMESTAMP.
Как ни странно, PL/SQ.L не позволяет получить в качестве разности между двумя значениями переменных типа TIMESTAMP значение типа INTERVAL YEAR ТО MONTH.
DECLARE
a TIMESTAMP:
b TIMESTAMP:
C INTERVAL YEAR TO МСЖШ:
BEGIN■
a := TO_TIMESTAMP('15-Nov-1961 12:01 am','dd-Mon-yyyy hh:mi am'):
b := TOJIMESTAMP('18-3un-1961 11:59 pm','dd-Mon-yyyy hh:mi am'); c := a - b:
c 严 CASTCa - b AS INTERVAL YEAR TO MONTH): END;
Oracle никак это не объясняет - в документации вообще не сказано □ том, что такая операция невозможна. После некоторых размышлений мы пришли к следующим выводам,
Каждое выражение генерирует результат определенного типа данных. В случае вычитания одного значения типа TIMESTAMP из другого это всегда INTERVAL DAY ТО SECOND.
Тип результата выражения не зависит от типа переменной, которой он присваивается. PL/SQ.L не может изменить семантику выражения левой либо правой части оператора присваивания для достижения соответствия между ними.
Когда результат выражения имеет тип, не соответствующий типу присваиваемой переменной, PL/SQ.L выполняет его неявное преобразование в тип переменной.
Неявное преобразование значения типа INTERVAL DAY ТО SECOND в тип INTERVAL YEAR ТО MONTH невозможно, поскольку количество дней в разных месяцах различное и значение типа INTERVAL не может содержать и месяцы, и дни (см. раздел этой главы «Типы данных INTERVAL»).
Интервалы могут быть представлены как положительными, так и отрицательными значениями. До сих пор в этой главе речь шла об интервалах, представленных положительными значениями. В контексте вычитания положительное значение интервала указывает, что вычитаемая дата, наступает раньше той, из которой производилось вычитание. Например:
15-NOV-1961 - 1B-Jun-I961 = +150
Отрицательное же значение интервала указывает, что вычитаемая дата, наступает позже той, из которой производилось вычитание:
18-Jun-1961 - 15-NOV-1961 - -150
Таким образом, знак результата указывает на направление действия с датами в пределах интервала. Мы поговорим об этом подробнее в разделе ^Сложение и вычитание интервалов». К сожалению, в Oracle нет функции, которая возвращала бы абсолютное значение интервала подобно тому, как функция ABS возвращает абсолютное значение числа.
Преобразование типа данных DATE в TIMESTAMP
В предыдущем разделе отмечалось, что каждое выражение генерирует результат определенного типа данных. Результат вычитания с участием двух значений типа TIMESTAMP имеет тип INTERVAL DAY ТО SECOND. Однако для обеспечения совместимости с предыдущими версиями Oracle результат вычитания двух дат по-прежнему является числом. Поэтому если вы хотите вычесть одно значение типа DATE из другого и получить значение типа INTERVAL DAY ТО SECOND, нужно сначала явно преобразовать с помощью функции CAST исходные значения в тип TIMESTAMP. Например:
DECLARE.
a DATE:
b DATE;
с INTERVAL DAY(3) ТО SECOND(O);
BEGIN
а :- T0_DATEC'15-Nov-1961 12:01 am'.'dd-Mon-yyyy hh:mi am1):
b :"• T0_DATE('18-Jun-1961 11:59 pm', 'dd-Mon-yyyy hh:m1 am1):
c :- CAST(a AS TIMESTAMP) - CASTCb AS TIMESTAMP);
DBMS_OUTPUT.PUT_LINE(c);
ENO;~
Результат выполнения этого кода таков:
+149 00:02:00
Если в одной операции вычитания участвуют значения типа DATE и TIMESTAMP, PL/SQL неявно преобразует тип DATE в TIMESTAMP;
DECLARE
d DATE;
b TIMESTAMP;
c INTERVAL DAY(3) TO SECOND(O);
d INTERVAL DAY(3) TO SECOND(O);
BEGIN
a := TO_DATE('15-Nov-1961 12:01 am*,'d^-Mon-yyyy hh:m1 am'):
b 尸 TO_TIMESTAMP('18-Jun-1961 11:59 pm', 'dd-Mon-yyyy hh:m1 am');
c :- a - b;
d :- b - a;
D8MS_DUTPUT.PUT LINE(c):
DBMS_OUTPUT.PUT2LINE(d):
Результат выполнения этого кода таков:
+149 00:02:00
-149 00:02:00
Однако при совмещении разных типов значений даты-времени в одном выражении лучше выполнять явное преобразование типов, чтобы сделать данный процесс абсолютно определенным как для компилятора, так и для программиста, который будет сопровождать программу, содержащую такие преобразования.
Сложение и вычитание интервалов
Единственное правило, которого вам следует придерживаться при сложении и вычитании, заключается в том, что оба интервала должны иметь один и тот же тип данных. Например:
DECLARE
al INTERVAL DAY TO SECOND *2 3;4;5.5';
Ы INTERVAL DAY TO SECOND :- '1 1:1;1,Г;
a2 INTERVAL YEAR TO MONTH :- '2-10':
b2 INTERVAL YEAR TO MONTH 尸
a3 NUMBER .- 3;'
ЬЗ NUMBER :* 1;
BEGIN
DBMS_OUTPUT,PUT_LINE(al - Ы);
DBMS_0UTPUT.PUTJINE£a2 - b2);
DBMS~0Ln-pUT.PULLINE(a3 - ЬЗ):
END:"
Результат выполнения кода таков:
+000000001 02:03:04ю500000000 +000000001-09
2
Данный пример показывает результаты трех операций вычитания. В первых двух случаях вычитались интервалы INTERVAL DAY ТО SECOND и INTERVAL YEAR ТО MONTH, а в третьем — два числа. Помните, что при работе с типом данных DATE интервал между двумя значениями типа DATE имеет тип NUMBER.
Умножение и деление интервалов
Операции умножения и деления не применимы к датам, но зато применимы к интервалам: их можно умножать и делить на числа. Приведем несколько примеров:
DECLARE
al INTERVAL DAY TO SECOND :- '2 3:4:56 ;
a2 INTERVAL YEAR TO MONTH ;- '2-10':
a3 NUMBER :- 3;
BEGIN
--Умножение интервала
DBMS OUTPUT.PUT LINECal * 2):
D8MS_OUTPUT.PUT2LlNE(a2 * 2):
D8MS_0UTPin-.PUT_LINE(a3 * 2);
Деление ингервала
DBMS OUTPUT.PUT_LINE(al / 2);
DBMS'OUTPUT.PLJT LINEOZ / Z);-
DBMS20UTPUT.PJT_LINE(a3 / Z);
END;
Результат выполнения данного кода таков:
*000000004 06:08:11.200000000 +000000005-08
б
+000000001 01:32:02.800000000
*000000001-05
1.5
Типы данных INTERVAL без ограничений
Интервалы можно объявлять с разным уровнем точности, причем значения разной точности не полностью совместимы между собой, Посмотрите на следующий пример, показывающий, как в результате невнимательного присвоения значений объявленным по-разному переменным могут потеряться цифры, определяющие доли секунды:
DECLARE
A INTERVAL DAY ТО SECOND:
С INTERVAL DAYC9) ТО SECOND С 2);
BEGIN
А :- '10 0:0:0.123456':
С А:
DBMS_OUTPUT.PUT_LINE(A):
DBMSJUTPUT.PUT_LINE(C);
END]
Результат выполнения кода таков:
+1D 00:00:00.123456'
+0D0000010 00:00:00.12
Значение, присвоенное переменной А, определяет 0,123456 с, что допустимо, поскольку по умолчанию точность переменной типа INTERVAL DAY ТО SECOND составляет шесть цифр после десятичной запятой. Однако когда значение переменной А присваивается переменной С)четыре цифры теряются, поскольку в объявлении переменной С указана меньшая точность.
На первый взгляд может показаться, что проблема заключается лишь в объяв- Мнии типов данных, но следует учесть еще один момент — использование процедур и функций, принимающих в качестве параметров значения типа INTERVAL. Обратите внимание на потерю точности в следующем примере, где значение переменной В удваивается с помощью функции doub1e_inyjnterval: declare
в INTERVAL DAY(9) ТО SECONDO):
FUNCTION double_rny_1nterval C
A IN INTERVAL DAY TO SECOND) RETURN INTERVAL DAY TO SECOND
BEGIN
RETURN А * 2:
END:■
BEGIN
В := '1 0:0:0.123456709';.
DBMS_OUTPUT.PIK_LINE(B);
DBMS OUTPUT.PUT LINE(double 吋 Interval(В));■'
END; ~~一
Результат выполнения этого кода:
+000000001 00:00:00.123456709
+02 00:00:00.246914
Мы потеряли цифры не только в дробной части секунды, но и в значении количества дней. А если бы переменной В было присвоено значение, равное 100 или более дням, вызов функции double_my_interval вообще завершился бы ошибкой ORA-01873: the leading precision of the interval is too small.
Дело здесь в том, что задаваемая по умолчанию точность типов данных INTERVAL не равна максимально возможной точности. Эта лроблема усложняется еще и тем, что PL/SQL не позволяет задавать точность при объявлении параметров процедур и функций. Например, нельзя написать такое объявление:
FUNCTION double_my_1nterval (
A IN INTERVAL DAY(9) TO 5EC0ND(9)) RETURN INTERVAL DAY TO SECOND
IS
К счастью, в PL/SQL имеются два типа, структуры данных которых объявляются без ограничений:
О YMINTERVAL_UNCONSTRAINED — принимает любое значение типа INTERVAL YEAR ТО MONTH без потери точности;
О OS I NTERVAL_UNCONSTRAINED - принимает любое значение типа INTERVAL DAY ТО SECOND без потери точности.
Воспользовавшись типом данных DSINTERVAL_UNCONSTRAINED, приведенный выше пример можно переписать следующим образом:
DECLARE
В INTERVAL DAY(9) ТО SECONDO);
FUNCTION double^Jnterval (
A IN D3INTE而AL二UNCONSTRAINED) RETURN INTERVAL一UNCONSTRAINED
IS"一
BEGIN
RETURN A *2;
end;
BEGIN
В '100 0:0:0.123456709':
D8MS_OUTPUT.Pm,_LINE(B);■
D0MS_OUTPUT.PUT_LINE(double_rnyJnterval CB)):
END; ~~
Его результат будет таким:
+000000100 00:00:00.123456789
+000000200 00:00:00.246913578
Обратите внимание, что тип данных DSINTERVAL_UNCONSTRAINED использовался дважды: один раз для задания типа формального параметра функции doubl е_ my_interval, а второй — для задания типа возвращаемого ею значения. Теперь эту функцию можно вызывать для любого значения типа INTERVAL DAY ТО SECOND без потери точности.
Функции обработки даты-времени
Описанные в предыдущем разделе арифметические действия с датой не обеспечивают реализации всех функциональных возможностей, необходимых для работы со значениями даты-времени, и поэтому PL/SQL предлагает для этой цели набор разнообразнейших встроенных функций. Вы уже знаете о встроенных функциях преобразования, таких как TO_DATE и NUMTOYMINTERVAL. Другие функции обработки даты и времени представлены в табл. 10.5.
ВНИМАНИЕ Избегайте использования традиционных функций Oracle, обрабатывающих значения типа DATE, когда имеете дело с новыми типами данных TIMESTAMP. Вместо этого для манипулирования значениями типов данных TIMESTAMP и INTERVAL следует применять новые функции. А DATE-функции пригодны в большей мере для обработки значений типа данных DATE,
Многие из приведенных в таблице функций, в том числе ADD_MONTHS, принимают значения типа DATE. Использование таких функций с новыми типами данных TIMESTAMP проблематично. Хотя любой из этих функций можно передать значение типа TIMESTAMP, Oracle неявно преобразует его в значение типа данных DATE, и только тогда функция выполнит свою задачу. В результате этого преобразования теряются доли секунды и информация о часовом поясе. Результирующее значение указанной функции имеет тип данных DATE. Например, если передать функции ADD_MDNTHS значение типа TIMESTAMP WITH TIME ZONE, будет выведено значение типа DATE без информации о часовом поясе и без указания долей секунды. Так что будьте внимательны.
Таблица 10.5. Встроенные функции обработки значений даты-времени
Имя
Описание
ADD_MONTHS
Прибавляет к дате заданное количество месяцев
CURRENT一DATE
Возвращает в часовом поясе сеанса текущую дату и время как значение типа DATE
CURRENT.IIMESTAMP
Возвращает в часовом поясе сеанса текущую дату и время как значение типа TIMESTAMP WITH TIME ZONE
DB7IMEZ0NE
Возвращает смещение часового пояса базы данных относительно UTC в форме символьной стгроки (то есть '-05:00')
FROM_TZ
Добавляет к значению типа TIMESTAMP информацию о часовом поясе, преобразуя его в значение типа TIMESTAMP WITH ПМЕ ZONE
LAST_DAY(flaTa)
Возвращает последний день месяца, содержащийся в заданной входной дате
Таблица 10.5 (продолжение)
Имя
Описание
LOCALTIMESTAMP
Возвращает текущ/ю дату и время как значение типа TIMESTAMP в локальном часовом поясе
MONTH_BETWEEN
Вычисляет количество месяцев между двумя датами
NEW_TIME
Значение типа DATE одного часового пояса преобразует в аналогичное значение другого пояса
NEXT_DAY
Возвращает дат/ первого дня недели, следующего за указанной датой
ROUND
Возвращает значение типа DATE, округленное до заданных единиц
SESSIONTIMEZONE
Возвращает смвщение часового пояса сеанса (отнсхмтельно UTC) в форме символьной строки (то есть '-05:00')
SYSDATE
Возвращает текущую дату и время сервера Oracle как значение типа DATE
SYSTIMESTAMP
Возвращает текущую дату и время сервера Oracle как значение типа TIMESTAMP WITH TIME ZONE
TRUNC
Возвращает значение типа DATE, усеченное до заданных единиц
TZ_OFFSET
Возвращает смещение относительно UTC часового пояса, заданного его названием или аббревиатурой, в форме символьной строки (то есть '-05:00')
Функции получения даты и времени
Получить текущую дату и время в PL/SQL всегда было просто — достаточно лишь вызвать функцию SYSDATE. Однако в OracleSt для этого появились и другие возможности. Ниже приведен синтаксис всех функций OracleSi; возвращающих текущую дату и время:
CURRENT_DATE RETURN DATE
CURRENT TIMESTAMP RETURN TIMESTAMP TZ_UNCONSTRAINED
10CALTIMESTAMP RETURN TIMESTAMPJNCONSTRAINED
SYSDATE RETURN DATE-
SYSTIMESTAMP RETURN TIMESTAMP_TZ_UNCONSTRAINED
ПРИМЕЧАНИЕ
В документации по SQL приведен необязательный параметр функций CURRENT一TIMESTAMP и LOCALTIMESTAMP, в котором задается точность возвращаемого значения. В OracleSI Release 1 язык SQL поддерживает этот параметр, а PL/SQL — нет. Его поддержка включена в Огас1е91 Release 2.
Какую же из перечисленных функций следует использовать в каждом конкретном случае? Ответ на этот вопрос зависит от нескольких факторов, которые лучше рассматривать в следующем порядке.
L Нужно определить, используется версия РСУБД до Oracle8i или необходимо обеспечить совместимость приложения с такой версией. Б этом случае выбор обозначен: SYSDATE,
Важна информации о часовом поясе. База данных и каждый сеанс связаны с определенным часовым поясом. При этом часовой пояс сеанса может отличаться
от часового пояса базы данных. Выберите то время, которое вам нужно, — в часовом поясе пользователя или в часовом поясе сервера.
Не менее важен тип данных. Некоторые функции возвращают значение типа DATE, одна функция — типа TIMESTAMP, а остальные — TIMESTAMP WITH TIME ZONE. Определив нужный часовой пояс (сервера или пользователя), сузьте выбор, основываясь на типе данных возвращаемого функцией значения.
Для того чтобы облегчить для вас выбор фз нкции, в дополнение к этому перечню факторов мы приводим табл. 10.6. Обратите внимание на то, что в наименовании функций Oracle не последовательна: функции LOCALTIMESTAMP и SYSTI - MESTAMP имеют однотипные имена, но возвращают разные типы данных.
Таблица 10.6. Перечень функций, возвращающих текущую дату и время
Функция
Часовой пояс
Тип данных
CURREN7_DATE
Сеанса
DATE
CURRENT_TIMESTAMP
Сеанса
TIMESTAMP WITH TIME ZONE
LOCALTIMESTAMP
Сеанса
TIMESTAMP
SYSDATE
Сервера
DATE
SYSTIMESTAMP
Сервера
TIMESTAMP WITH TIME ZONE
Если вы, например, хотите получить текущую дату и время сеанса и присвоить это значение переменной типа DATE, вам нужно воспользоваться функцией CURRENT^DATE. Если же вы работаете с датами из других часовых поясов, используя переменные типа TIMESTAMP WITH TIME ZONE, выберите функцию CURRENT_TIMESTAMP.
При выборе функции, возвращающей время в часовом поясе сеанса, убедитесь, что администратором базы данных правильно сконфигурирован сервер и что верно задан часовой пояс сеанса. Следующий пример показывает различие между результатами работы функции CURRENT_TIMESTAMPI которая предназначена для определения времени в часовом поясе сервер% и функции SYSTIMESTAMP, используемой для получения времени в часовом поясе сеанса:
BEGIN
DBMS OUTPUT.PUT LI NEC CURRENT TIMESTAMP):
DBMS OUTPUT.PUT LINE(SYSTIMESTAMP):
END:-
Результат выполнения кода таков:
24-FEB-02 04.55.54.8Q30Q9000 PM -Q8:00 24-FEB-02 07.55.54.S04221000 PM -05:00
В данном примере в часовом поясе сеанса используется Тихоокеанское стандартное время (-8:00), а в часовом поясе сервера — Восточное стандартное время (-5:00). Обратите внимание на разницу в 3 ч между этими двумя значениями, а также на соответствующее смещение часовых поясов.
А что если не существует функции, возвращающей значение нужного вам типа данных? Если вы хотит爲 например^ присвоить время часового пояса сервера значению переменной типа TIMESTAMP, Oracle выполнит неявное преобразование
типов. Но лучше в подобных случаях производить явное преобразование с помощью функции CAST:
DECLARE ■
a TIMESTAMP:
TIMESTAMP;
BEGIN
:- CASTCSYSTIMESTAMP AS TIMESTAMP);
b SYSDATE:
DBMS_OUTPUT.PUT_LINE(TO_CHAR(a,'DD-MON-YYYY HH:MI:SS AM1));
DBMS-OUTPUT.PUT~LINE(T(TCHAR(a,'DD-MON-YYYY HH:MI:SS AM'));
END; ~
Результат выполнения этого кода:
24-FEB-02 06.46.39 PM
24-FEB-02 06.46.39 PM
В данном примере результат вызова функции SYSTIMESTAMP с помощью функции CAST явно преобразуется из типа данных TIMESTAMP WITH TIME ZONE в тип данных TIMESTAMP. А результат вызова функции SYSDATE неявно преобразуется из типа данных DATE в тип данных TIMESTAMP.
Если вы пользуетесь этими функциями для определения времени с точностью до долей секунды, учитывайте ограничения аппаратного обеспечения и операционной системы. Функции CURRENT_TIMESTAMPt LOCALTIMESTAMP и SYSTIMESTAMP возвращают значения типа TIMESTAMP WITH TIME ZONE или TIMESTAMP. Указанные типы данных позволяют хранить время с точностью до миллиардной доли секунды. Все это очень хорошо, но подумайте о том, каким образам определяется время в данном случае. Oracle (по всей видимости) получает время от операционной системы, а операционная система — от аппаратного обеспечения. И если ваша операционная система или аппаратное обеспечение отслеживают время с точностью только до сотых долей секунды, Oracle не сможет вернуть более точных результатов. Например, в системе Suse Linux 7.2 на базе процессора Intel х86 нам удалось измерить время лишь с точностью до миллионных долей секунды (шесть цифр), тогда как на том же аппаратном обеспечении с Windows ХР мы получили миллиардные доли секунды.
Функции обработки информации о часовом поясе
Если требуется узнать, какай часовой пояс установлен для вашего сеанса или для базы данных либо если необходимо определить смещение некоторого часового пояса относительно UTC, имеет смысл воспользоваться одной из трех следующих функций:
DBTIMEZONE RETURN VARCHAR2;
SESSIONTIMEZONE RETURN VARCHAR2;
О TZ_0FF5ET (часовой_пояс) RETURN VARCHAR2..
Функции DBTIMEZONE и SESSIONTIMEZONE возвращают часовые пояса базы данных и сеанса, идентифицируемые смещением относительно UTC. Например:
BEGIN
DBMS__OUTPUT. PUT_LINE(DBTIMEZONE);
DBMS_OUTPUT.PUT_LINE(SESSIONTIMEZONE):
END;"
Результат выполнения этого кода:
-07:00
-08:00
Но какие часовые пояса представляют эти смещения? Напомним, что представление V$TIMEZONE_NAMES возвращает список названий часовых поясов и их аббревиатур. Если вам нужно узнать смещение часового пояса для заданного региона, воспользуйтесь функцией TZ_OFFSET. Например, следующий запрос возвращает список названий всех часовых поясов» соответствующих смещению -07:00. Этот запрос был выполнен, когда действовало зимнее время. Для летнего времени результаты были бы другими. В частности, регион America/Denver большую часть года относится к часовому поясу, которому соответствует смещение относительно UTC-07:00, а в период действия летнего времени - к часовому поясу со смещением -06:00:
SELECT TZNAME, TZABBREV FROM V$TIMEZONE NAMES WHERE SUBSTH(U2OFFSET(TZNA№).1,6) - '-07:00':
TZNAME 'TZABBREV
America/Denver LMT
America/Denver MST America/Denver MWT America/Denver MDT America/Edmonton LMT America/Edmonton MST America/Edmonton MDT
А с какой целью в этом примере использовалась функция SUBSTR? В Oracle9i Release 1 в функции TZ_OFFSET имеется ошибка, из-за которой данная функция возвращает в конце строки лишний нулевой байт:
SELECT TZ OFFSETCUS/EASTERN') offset,
LENGTH(TZ_OFFSET('US/EASTERN1)) length,
DUMP(TZ_OFFSETC1US/EASTERN')) dump
FROM dual:
OFFSETLENGTH DUMP
-05:007 Typ-1 Len-7: 45,48.53,58,46.48.0、
Обратите внимание на нулевой байт в конце столбца DUMP и посмотрите, что произойдет при выполнении следующей инструкции SELECT:
SELECT TZNAME
FROM V$T1MEZONE_NAMES
WHERE TZ_OFFSET?TZNAME) = '-07:00':
ERROR at line 3:
ORA-01875: time zone minute must be between -59 and 59
В худшем случае эта инструкция не вернет ни одной строки - такой результат был нами получен при тестировании программы в Windows ХР. Однако в Linux и Windows 2000 была сгенерирована указанная выше опшбка. Для удаления лишнего нуля воспользуйтесь функцией SUBSTR, и все будет в порядке.
Функция ADD_MONTHS
Возвращает дату, полученную путем добавления к заданной дате определенного количества месяцев. Ее синтаксис таков:
FUNCTION ADD_MONTHS (входая^ата IN DATE, количест8О_месяуеа NUMBER) RETURN DATE
FUNCTION ADD_MONTHS (каличество_несяцев NUMBER, входзяj?"d IN DATE) RETURN DATE
Как видите, функция ADD_MONTHS перегружена. При ее вызове посредством значения первого аргумента задается дата, а посредством второго 一 количество месяцев, но можно сделать и наоборот: задать количество месяцев с помощью первого аргумента, а дату — с помощью второго. Однако и в том и в другом случае оба аргумента являются обязательными.
Если значение аргумента кол^честзо^есяцев является положительным, функция ADD_MONTHS возвращает дату, наступающую через заданное количество месяцев после входной даты. Если же значение аргумента количество_месяцев отрицательно, она будет возвращать дату, наступающую на заданное количество месяцев раньше входной даты. Приведем несколько примеров использования функции ADD_MONTHS.
О Сдвиг на три месяца вперед:
ADD-_M0NTHS('12-JAN-1995,1 3) т 12-APR-1995
О Сдвиг на три месяца назад:
ADD^MONTHSClZ-MAR-iggO'. 3) т 12-MAR-19B9
Функция ADD_MONTHS всегда сдвигает дату на целое количество месяцев. В аргументе количествоу1есяцев можно задать и дробное число, но только положительное, и функция ADD_MONTHS округлит его до ближайшего наименьшего целого, как в следующем примере. Обратите внимание на результат выполнения функции, когда этот аргумент задан отрицательным значением:
BEGIN
('28-FEB-19B9', ('28-FEB-1989'. ('28-FEB-1989'. ('28-FEB-1989'.('28-FEB-19B9', ('28-FEB-1989'. ('28-FEB-1989'. ('28-FEB-1989'.1.9999)):1)): -1.9999));-D):1.9999)):1)): -1.9999));-D):DBMS OUTPUT.PUT_LINE(ADD_MONTHS DBMS OUTPUT.PUT LINEtADD^MONTHS dbms output.putjinecado^mdnths DBMS OUTPUT.PUT二LINE3DD二MONTHS
('28-FEB-19B9', ('28-FEB-1989'. ('28-FEB-1989'. ('28-FEB-1989'.
('28-FEB-19B9', ('28-FEB-1989'. ('28-FEB-1989'. ('28-FEB-1989'.
1.9999)):
1)): -1.9999));
-D):
1.9999)):
1)): -1.9999));
-D):
ENO: -
Результат выполнения данного кода:
31-MAR-19B9
31-MAR-1989
31-JAN-1989
31-JAN-1989
Если хотите сдвинуть дату на дробное количество месяцев, просто прибавьте к ней или вычтите из нее нужное количество дней. PL/SQL позволяет производить со значениями даты арифметические операции. Более подробно об этом
было рассказано в разделе этой главы < Арифметические операции над значениями даты-времени».
Если входная дата функции ADD_MONTHS не является последним днем месяца, возвращаемая ею дата приходится на тог же день месяца. Если номер дня входной даты больше номера последнего дня месяца, на который должна прийтись результирующая дата, функция ADD_MONTHS возвращает последний день заданного месяца. Приведенный ниже пример иллюстрирует эти правила. В первом вызове функции ADD_MONTHS к 15 января прибавляется один месяц( в результате чего получается 15 февраля. Поскольку в феврале нет 31-го дня, второй вызов возвращает последний день февраля:
BEGIN
DBMS OUTPUT.PUT_LINE(ADD_MONTHS ('15-JAN-1995'. 1));
DBMS"OUTPUT.PlRjlNE(ADOZMONTHS ('31-JAN-1995', 1)):
END; 一~
Приведем результат выполнения этого кода:
15-FEB-1995 2S-FEB-1995
Такое поведение функции ADD_MONTHS вполне разумно и понятно. Однако что будет, если входная дата придется на последний день месяца, а в результирующем месяце будет больше дней, чем в исходном? Что получится, если прибавить два месяца к 28 февраля 1994 года, - 30 апреля 1994 года (последний день апреля) или 28 апреля 1994 (тот же день месяца, что и в исходной дате)? Ответ таков: если входная дата представляет последний день месяца, тогда и выходная дата будет представлять последний день месяца. Например:
BEGIN
DBMS_OUTPUT・PUT LINE(ADD_MONTHS С'28-FEB49941. 2));
END; -~-
30-APR-1994
Если передать функции ADD_MONTHS дату, представляющую последний день месяца, PL/SQL вернет последний день результирующего месяца независимо от количества дней в каждом из них. В одних случаях это удобно, а в других 一 не совсем (см. врезку)..
КАК СОХРАНИТЬ ИСХОДНЫЙ ДЕНЬ МЕСЯЦА
Если входная дата функции ADD_MONTHS приходится на конец месяца, эта функция всегда возвращает дату конца результирующего месяца. Однако такое ее поведение не всегда удобно. Следующая функция PL/SQL, которая названа my_add_months, когда это возможно, сохраняет исходный день месяца:
FUNCTION 3_ddd nranths (
datejn fN DATE, nranths shift IN NUMBER)
RETURN DATE IS-
date_out DATE:
dayjn NUMBER:
day_out NUMBER;
BEGIN ~
date_out :- ADD MONTHS(date_in. months_sh1ft):
day Tn ;- TO NUMBER(TO_CHAR(datejn.'DO'});'
day^out :- TO_NUMBER(TO_CHARCdate_out.'OD'));
IF day_out > dayjn
THEN -
date out :- date out - (day out - day In);
END IF;""~
RETURN date_out;
END:
Единственный случай, при котором данная функция возвращает другой день, — это когда исходного дня просто нет в результирующем месяце. Например, если добавить к 31 января 2002 года один месяц, получится 28 февраля 2002 года. Однако если прибавить один месяц к 28 февраля 2002 года, получится 28 марта 2002 — то есть день месяца будет сохранен. Указанная функция также сохраняет все компоненты времени дня: часы, минуты и секунды.
Функция FROM_TZ
Добавляет к значению типа TIMESTAMP информацию о часовом поясе, тем самым преобразуя его в значение типа TIMESTAMP WITH TIME ZONE. Ее синтаксис таков:
FUNCTION FROM_TZ(3Hd4eH^eJ1niestamp IN TIMESTAMP.■
чдсовой_пояс IN VARCHAR2)
Часовой пояс можно задать либо в виде смещения относительно UTC, либо в виде названия региона. Функция возвращает ту же дату, которая ей была передана, но с добавлением заданного часового пояса.
Приведем программу, содержащую два вызова функции FROM_TZ:
DECLARE■
w TIMESTAMP :- CASTC'24-Jun-2002 3.41.00.00 PM' AS TIMESTAMP):
x TIMESTAMP WITH TIME ZONE;
у TIMESTAMP WITH TIME ZONE;
BEGIN.
X :- FROM_TZ(w.'-5:0D,):
у FROMJZfw.'US/Eastern');
DBMS_OUTPUT.PUT_LINE(x);
DBMS_OUTPUT.PUT_LINE(y);
END:
А вот результат выполнения кода:
24-JUN-02 03.41.00.000000 PM -05:00
24-JUN-02 03.41.00.000000 PM US/EASTERN
Как видите, дата и время остались неизменными, однако выходные значения теперь содержат информацию о часовом поясе, а их тип изменился с TIMESTAMP па TIMESTAMP WITH TIME ZONE.■
функция LAST_DAY
Возвращает дату последнего дня месяца, к которому относится заданная дата. Синтаксис функции таков:
FUNCTION LASTJAY (.входая_дата IN DATE) RETURN DATE.
Необходимость в этой функции возникла из-за того, что в месяцах разное количество дней. С ее помощью можно, например, узнать, сколько дней в феврале заданного года — 28 или 29. Приведем несколько примеров использования функции LAST_DAY:
BEGIN
--Выведем последний день месяца
DBMS_OUTPUT.PJT_LINE (LASTJAY ('12-JAN-991));
--Если это последний день месяца, на ней и остановимся
DBMS_OUTPUT.PUT_LINE (LAST_DAY С'31-JAN-99')):
--Выведем последний день месяца три месяца спустя
DBMS_OUTPUT.PUT_LINE (LAST_DAY (ADDJONTHS (SYSDATE, 3))};
—Выведем количество дней до конца текущего месяца
DBMS_OUTPUT.PUT_LINE (LASTJAY (SYSDATE) - SYSDATE);
END;--
31-JAN-0099
31-JAN-0099
31-MAY-2002
4
ОПРЕДЕЛЕНИЕ ПЕРВОГО ДНЯ МЕСЯЦА
В Oracle имеется функция LAST DAY (последний день), так почему же не быть и функции FIRST_DAY (первый день)? Для нее обязательно нашлось бы применение. Например, часто приходится писать запросы, в которых нужно определить первый и последний дни месяца введенной пользователем даты. Простейпшй способ получить первый день месяца таков:
ADD_MONTHS(LAST_DAY(x). -1)+1
Он основан на специфическом поведении функции ADD_MONTHS в отношении конца месяца. Сначала с помощью функции LAST_DAY мы определяем дату последнего дня текущего месяца, а затем с помощью функции ADD_ MONTHS прибавляем к ней 一 1 месяц, чтобы получить дату последнего дня предыдущего месяца. В завершение прибавляем к результату 1 день и получаем первый день текущего месяца.
Хотя это может показаться немного сложным, данный подход позволяет избежать манипуляций со строками и сохраняет компонент времени дня, который может входить в состав исходного значения.
Функция MONTH_BETWEEN
Вычисляет количество месяцев между двумя датами, а в качестве результата возвращает число, Ее синтаксис таков:
FUNCTION MONTH BETWEEN (ддта_1 IN DATE, дата 2 IN DATE)
RETURN NUMBER
Функция MONTH—BETWEEN действует в соответствии co следующими правилами;
О если дата_1 наступает позже 口вты& функция возвращает положительное значение;
О если 〃"厶1 наступает раньше даты^ функция возвращает отрицательное значение;
О если дата_1 и пата? относятся к одному месяцу, функция возвращает дробное значение из диапазона от -1 до +1;
О если “та』и Adrd.2 приходятся на последние дни соответствующих месяцев) функция возвращает целое число (без дробного компонента);
О если AdTd.l и да та? относятся к разным месяцам и хотя бы одна из них не приходится на последний день месяца, функция возвращает дробное значение, (Дробный компонент вычисляется на основе того, что месяц состоит из 31 дня, с учетом разницы компонентов времени двух дат.)
Приведем несколько примеров использования функции MONTH_BETWEEN:
BEGIN
--Разница между двумя последними днями месяцев; первый месяц наступает раньше второго: D8MS_0UTPUT.PUT_LINE(
M0NTHS_8ETWEEN ('31-JAN-1994', '28-FEa-1994'));
--Разница между двумя последними днями месяцев; первый месяц наступает позже второго: D0MS_OUTPUT.PUT_LINE(
MDNTHS_BETWEEN С'31-^1995', '28-FEB-1994'));
--Две даты одного месяца:.
D0MS_OUTPUT.PUT LINE(
MONTHS_BETWEEN ('28-FE0-1994'. '15-FEB-1994')):
--Вычисления с дробным компонентом:-
DBMS_OUTPUT.PUT一LINE(
MONTHS.SE^EEN ('ai-JAN-^1, 'l-MAR-1994')):
D0MS_OUTPUt.PUT LlNEt
MONTHSJETWEEN ('31-JAN-1994'. '2-MAR-19941)):.
DBMS_OUTPUT.PUT_L1NE(
'MONTHS_BETWEEN C'31-JAN-1994'. '10-MAR-1994')):
END:
Результаты выполнения этого кода таковы:
13■
.4193548387096774193548387096774193548387'
-1.03225806451612903225806451612903225806
-1.06451612903225006451612903225806451613
-1.3225S06451612903225B064516129032258065
Вы, очевидно, заметили здесь определенную закономерность. Как уже было сказано, функция MONTH_BETWEEN вычисляет дробный компонент количества месяцев исходя из предположения, что каждый месяц содержит 31 день. Поэтому на каждый день сверх полного месяца к результирующему значению прибавляется 1/31 месяца:
1/31 - 0.32258065...'
В соответствии с оговоренным правилом количество месяцев между 31 января 1994 года и 28 февраля 1994 года равняется 1, а количество месяцев между 31 января 1994 года и 1 марта 1994 года на 0,32258065 болше.
Функции ROUND и TRUNC
Функция ROUND округляет дату до ближайшей даты в соответствии с заданной маской форматирования] а функция TRUNC усекает значение даты-времени до ближайшей даты. Указанные функции обработки значений даты-времени действуют аналогично одноименным числовым функциям. (См. раздел < Функции округления и усечения# в главе 9.) Их синтаксис таков:
FUNCTION ROUND (зх“з)?_д<эпэ IN DATE [ наска』юрмат叩овбнмл, VARCHAR2]) RETURN DATE
FUNCTION TRUNC {вхопаяjjara IN DATE [ ндска_формдтировшя. VARCHAR2J) RETURN DATE
Данные функции часто применяются для установки компонента времени в значении типа DATE равным полуночи (12:00:00 АМ). Это значение по общему соглашению используется в тех случаях, когда важна только дата, а не время. Различие между двумя функциями состоит в следующем: компонент времени либо отбрасывается полностью, либо используется для округления в ту или иную строну результирующего значения типа DATE. Рассмотрим работу обеих функций:
DECLARE
datejn DATE :- T0_DATE('24-Feb-2002 05:16:00 PM'
.'DD-MON-YYYY HH;MI:SS AM'):
date_rounded DATE;
dste_truncsted DATE:
BEGIN "
dste_rounded :- ROUND(dateJn):
date_truncated :- TRUNC(dateJn);
DBMSJUTPUT.PUT LINEC
TO_CHAR(date2rounded. 'DD-MON-YYYY HH:MI:SS AM*));■
DBMS_OUTPUT.PUT_LINE(
TD CHAR(date4runcated.'DD-MON-YYYY HH:MI:SS AM'));
END:
Результат выполнения этого кода следующий:
25-FE8-2002 12:00:00 АМ
24-FEB-2002 12:00:00 АМ
Проанализируем значение переменной date_rounded. По сравнению с исходным значением дата дня месяца увеличена на единицу - теперь это 25 февраля. Так получилось потому, что время 5:16 после полудня ближе к полуночи следующего дня (25 февраля), чем к полуночи 24 февраля. А вот значение переменной
date_truncated, напротив, по-прежнему соответствует 24 февраля. Функция TRUNC просто удалила информацию о времени. Функция ROUND эту информацию учитывает и, хотя тоже удаляет ее из результирующего значения, в зависимости от значения времени может увеличить на единицу число месяца. Если бы время равнялось 5:16 утра, округление было бы произведено в сторону уменьшения значения и обе функции вернули бы один и тот же результат.
По умолчанию функции ROUND и TRUNC округляют и усекают значения даты- времени до даты, и эти операции выполняются чаще всего. Но возможно округление и усечение и до других компонентов. При необходимости это сделать в вызове функции нужно задать соответствующую маску, содержащую один из элементов, которые перечислены в табл. 10.7. Округление будет производиться до элемента, заданного в этой маске.
Таблица 10.7. Элементы маски форматирования для функций ROUND и TRUNC
Маска форматирования
Округляет или усекает до
SCC или CC
Столетия
SYYYY, YYYY, YEAR, SYEAR, YYY, YY или Y
Года (округление до следующего года начиная с 1 июля)
IYYY, IYY, IY ИЛИ I
Года (стандарт ISO)
Q
Квартала (начиная с 16 числа второго месяца квартала выполняется округление в сторону увеличения значения)
MONTH, MON, ММ или RM
Месяца (начиная с 16 числа, которое не обязательно совпадает с серединой месяца, выполняется округление в сторону увеличения значения)
WW,
Дня недели, на который приходится первый день года
IW
Дня недели, на который приходится первый день года (стандарт ISO)
W
Дня недели, на который приходится первый день месяца
DDD, DD или J
Дня
DAY, DY или D
Первого дня недели
НН, HH12 или HH24
Часа
MI
Минуты
Следующий пример демонстрирует, как с помощью функции TRUNC значение даты-времени усекается до начала года или начала месяца:
DECLARE
date_in DATE :- TO_DATE('24-Feb-2002 05:36:00 PM'
.'DD-MON-YYYY HH:MI:SS AM');
trunc_to)еа「DATE;
trunc_to__month DATE:
BEGIN
trunc_to_>ear :- TRUNC(datejn. lYYYY');
trunc to month :- TRUNC(date 1n, 'MM');
OBMS_OUTPUT.PUT_LINE(
T01CHARCtrunc_toJ^ear. 'OD-MON-YYYY HH;MI:SS AM'));
DBMSloUTPUT.PUTJINEt
TolCHAR(trunc_to_month. 'DD-MON-YYYY HH:MI:SS AM')):
END: ~
Результат выполнения этого кода:
Ol-JAN-2002 12:00:00 AM
Ol-FEB-2002 12:00:00 AM
Как видите, функция TRUNC позволяет мгновенно перейти к началу года и к началу месяца.
ПРИМЕЧАНИЕ Если вы прочитали врезку «Определение первого дня месяца», у вас наверное возник вопрос, почему для получения первого дня месяца нельзя использовать функцию TRUNC. Это возможно, если для вас не является важным сохранение компонента времени. Так что если время должно быть сохранено, используйте прием, описанный во врезке, а если нет—смело вызывайте функцию TRUNC,
Следующий пример демонстрирует два разных способа применения функции ROUND. В первом с ее помощью определяется столетие, ближайшее к 24 февраля 2002 года и к 24 февраля 1902 года. После этого мы посредством той же функции округляем значение даты-времени с точностью до часов:
DECLARE
date in」 DATE :口 TOJATEt'24-Feb-2002','DD-MON-YYYY1); datelinj DATE :・ ТО^ОАТЕС'24-Feb-1902','DD-MON-YYYY'): datejnj DATE := ^DATEC^^Feb-ZOOZ 05:36:00 PM' ,'DD-MQN-YYYY HH;MI:SS AM'):
round_l DATE;
round_2 DATE;
round_3 DATE:
BEGIN
round」;-ROUNDCdatejnJ.'CC):
round_2 :» ROUNDCdateJnj, 'CO:
rcund_3 :» ROUND(date_in_3. 'HH'):
DBMSJUTPUT.PUVINECTOJHARCroundJ.'DD-MON-YYYY HH:MI:SS AM'));
DBMS_OUTPUT.PUT_LINE(TOJHAR(round!?,'DD-MON-YYYY HH:MI:SS AM'));
DBMS_OUTPUT.PU<LINE(TO~CHAR(roundj, 'DD-MON-YYYY HH:MI:SS AM'));
END:"~~
Результат выполнения этого кода следующий:
01-JAN-2001 12:00:00 АМ ■
01-JAN-1901 12:00:00 АМ.
24-FE8-2002 D6:D0:D0 РМ.
При округлении с точностью до столетия s качестве результата получаем не столетие, а дату и время его начала. Oracle использует научное определение столетия, а не его популярное определение, так что, к примеру, XXI столетие начинается в 2001 году, а не в 2000. Объяснение третьей выходной строки очень простое: время 5:36 после полудня было округлено до ближайшего часа, то есть до 06:00.
ВНИМАНИЕ
При использовании функций ROUND и TRUNC имейте в виду то, что ненужные компоненты даты-времени никуда не «исчезают». Все переменные типа DATE содержат значения лет, месяцев, дней( часов, минут и секунд, Когда 5;3б пополудни округляется до 06:00 пополудни, компонент минут остается — он просто обнуляется.
Функцию TRUNC удобно использовать при сравнении двух дат, представленных значениями переменных типа DATE. Рассмотрим следующий пример:
IF request_date BETWEEN start ddte ANO end_date
THEN ~~"
Даты, заданные значениями переменных request_date и start_date, могут быть одинаковыми, но если не указан компонент времени, оператор IF может вернуть неверный с точки зрения логики приложения результат. Например, если пользователь ввел значение переменной request_date без компонента времени, время в этой переменой будет установлено равным полуночи указанного пользователем дня. Если при этом значение переменной start_date было сгенерировано с помощью функции SYSDATE, его компонент времени будет отражать время на момент сравнения. И поскольку полночь наступает раньше любого другого времени определенного дня, значение переменной request_date не попадет в заданный в сравнении диапазон, хотя на первый взгляд может показаться иначе.
Если вы точно не знаете, какие компоненты времени содержатся в значениях переменных даты-времени, и вам нужно, чтобы в операциях обработки дат компонент времени игнорировался, воспользуйтесь для его усечения функдаей TRUNC:
IF TRUNC Crequest_date) BETWEEN TRUNC (start_date) AND TRUNC (end_date) THEN~一一
Функция TRUNC устанавливает время в 12:00:00 AM. В этом примере все значения даты-времени усекаются до даты, так что время никак не влияет на результат сравнения..
Функция NEW_TIME
Не просто определить, сколько времени в Анкоридже, когда в Чикаго 3 часа пополудни. К счастью; PL/SQL предоставляет на этот случай функцию NEW__TIME. Она переводит даты вместе с компонентами времени из одного часового пояса в другой. Ее синтаксис таков:
FUNCTION NEW TIME С входах лета IN DATE, пояс 1 VARCHAR2. №就_2 VARCHAR2)
RETURN DATE~-
Здесь входая jjara - это исходная дата; пояс_1 一 ее часовой пояс (обычно это ваш локальный часовой пояс); поясу - тот часовой пояс, для которого нужно получить эквивалент исходной даты.
ПРИМЕЧАНИЕ Если вы создаете программное обеспечение, которое должно работать в нескольких часовых поясах, мы рекомендуем использовать новые возможности типа данных TIMESTAMP WITH TIME ZONE, в не функцию NEW.TIME со значениями типа DATE.
Названия допустимых часовых поясов перечислены в табл. 10.8,
Таблица 10.8. Аббревиатуры названий часовых поясов
Аббревиатура Название
AST
ADT
BST
BDT
CST
CDT
EST
EDT
GMT HST HOT
MST MDT
NST 、
Atlantic Standard Time (Атлантическое стандартное время)
Atlantic Daylight Time (Атлантическое летнее время)
Bering Standard Time (Берингово стандартное время)
Bering Daylight Time (Берингово летнее время)
Central Standard Time (Центральное стандартное время)
Central Daylight Time (Центральное летнее время)
Eastern Standard Time (Восточное стандартное время)
Eastern Daylight Time (Восточное летнее время)
Greenwich Mean Time (Время по Гринвичу)
Alaska-Hawaii Standard Time (Стандартное время на Аляске и Гавайях)
Alaska-Hawaii Daylight Time (Летнее время на Аляске и Гавайях)
Mountain Standard Time (Горное стандартное время)
Mountain Daylight Time (Горное летнее время)
Newfoundland Standard Time (Стандартное время в часовом поясе
Ньюфаундленд)
PST
PDT
YST
YDT
Pacific Standard Time (Тихоокеанское стандартное время)
• Pacific Daylight Time (Тихоокеанское летнее время)
Yukon Standard Time (Стандартное время в часовом поясе Юкон)
Yukon Daylight Time (Летнее время в часовом поясе Юкон)
ПРИМЕЧАНИЕ
Имейте в виду, что функция NEW_TIME принимает не все аббревиатуры названий часовых поясов, возвращаемые представлением V$TIMEZONE_NAMES. Поэтому используйте только аббревиатуры, приведенные в табл. 10.8.
Как показывает следующий пример, спецификации часового пояса в функции
NEW_TIME не чувствительны к регистру символов:
BEGIN
DBMS_OUTPUT.PUT_LINE(
TO_CHAR (NEW-TIME (TO DATE ('09151994 12:30 AM',
*" 'MMDDYYYY HH:MI AM').
'CST'. 'hdf).
'Month DD. YYYY HH:MI AM'));
END;
Этот код выводит следующую строку:
September 14, 1994, 9:30 PM
Таким образом, когда в Чикаго 12:30 утра 15 сентября 1994 года, в Анкорчдже 9:30 вечера 14 сентября 1994 года.
ПРИМЕЧАНИЕ Для того чтобы в вычислениях использовалось время, отличное от полуночи, применяется функция TO_DATE. А для вывода результирующих даты и времени в читабельной форме предназначена функция TO_CHARf имеющая другую маску форматирования, поскольку по умолчанию PL/SQL не выводит компонент времени.
Несмотря на то что функция NEWJIME выполняет преобразование с целью достижения соответствия между часовыми поясами, она не учитывает информацию о часовом поясе, имеющуюся в значениях типа TIMESTAMP WITH TIME ZONE. Об этом свидетельствует следующий пример:
DECLARE
в TIMESTAMP WITH TIME ZONE
广 TO_TIMESTAMP_TZ ('09151994 12:30 AM -5:00'.
~~ 'MMDDYYYY HH:MI AM TZH:TZM');
BEGIN
DBMS_DUTPUT.PUT_LINE(a):
DBMS OUTPUT.PUT_LINE(
TO_CHAR (NEW二TIME (a.'CST1. 'hdt').
'Month DD. YYYY HH:MI AM'));
END:
Результат выполнения кода таков:
15ТЕР-94 12.30.00.000000 AM -05:00
September 14. 1994, 9:30 PM
Обратите внимание на то, что возвращаемое время в этом примере, как и в предыдущем, равняется 9:30 РМ. Функция NEWJImE принимает значение типа DATE, и когда переданное ей значение типа TIMESTAMP WITH TIME ZONE неявно преобразуется в тип данных DATE, информация о часовом поясе теряется. Вот и получается, что время 12:30 АМ сдвигается на +9:00 часов от GST к НОТ.
Функция NEXT_DAY
Возвращает дату первого после заданной даты дня, выпадающего на указанный день недели. Вот ее синтаксис:
FUNCTION NEXT—MY {входнаяjjard IN DATE. qexbjMSd VARCHAR2) RETURN DATE
В параметре день^недели должно быть задано название дня недели на языке вашего сеанса (определяемом значением инициализационного параметра базы данных NLS_DATE_LANGUAGE). Компонент времени возвращаемой даты в точности равен компоненту времени исходной даты. Если день недели входной_даты совпадает с заданным днем_недели, функция NEXTJAY возвращает дату, которая наступает ровно на неделю (семь дней) позже входной_даты. Исходную дату она не возвращает никогда.
Приведем несколько примеров использования функции NEXT_DAY. В них определяется дата первого понедельника и первой среды 1997 года:
BEGIN
-Можно использоаать квк полные так и сокращенные названия дней недели:
DBMSJUTPUT.PUT_LINE(NEXT_DAY ('01-JAN-1997' . 'MONDAY'));
DBMSJUTPUT.PUTJINECNEXTJAV ('01-JAN-1997'. 'MON')):
Регистр при наборе названия дня недели не имеет значения: DBMS_OUTPUT.PUT_LINE(NEXT_DAY С01-JAN-1997', 'monday'));
-Следующая среда наступает ровно через неделя:
DBMS_OUTPlfT.PlfT_LINE(NEXVAY ('01-JAN-1997', 'WEDNESDAY'));
Если язык даты испанский:
EXECUTE IMMEDIATE 'ALTER SESSION SET NLS_DATE_LANGUAGE-"SPANISH' DBMS OUTPUT.PUT LINE(NEXT DAY ('01-ENE-1997'."'LUNES'));
END; 一 一
Вот что выводит этот код:
06-JAN-97
Q6-JAN-97
06-JAN-97
Q7-JAN-97
06-ENE-97
Записи и коллекцииЗаписи и коллекции
Записи и коллекции
Записи и коллекции
А Записи в PL/SQL
Коллекции в PL/SQL
Определение типов коллекций и их объявление
А Где используются ко/шекции
А Встроенные методы коллекций
А Работа с коллекциями
А Псевдофункции коллекций
Сопутствующие операции
Выбор типа коллекции
Данные могут храниться и обрабатываться в программах как в виде отдельных ■ скалярных значений, так и в виде структур, состоящих из нескольких элементов с отдельными значениями. Записи и коллекции, о которых рассказывается в этой главе, являются составными структурами данных.
С точки зрения концепции и структуры〉записи в программах PL/SQL очень похожи на строки в таблицах баз данных. Запись как целое не имеет собственного значения; однако значение имеет каждый ее компонент, или поле, а объединение их в единую запись позволяет хранить и обрабатывать все значения как одно целое. Познакомившись с этой структурой данных, вы поймете, насколько проще с ее помощью выполняются многие задачи, которые так часто приходится решать программистам.
Коллекция — это структура данных, похожая на одномерный массив. Ее можно считать близким аналогом традиционных массивов, имеющихся в языке PL/SQL. Коллекции часто используются для управления списками информации. Вы ознакомитесь с тремя разными типами коллекций (ассоциативным массивом, вложенной таблицей и массивом VARRAY), узнаете, как объявлять эти структуры, как с ними работать и как выбрать тип, подходящий для конкретного случая. Но для начала обратимся к записям. Они проще и чаще используются в коллекциях.
Записи в PL/SQL
По своей структуре запись похожа на строку таблицы базы данных. Каждая строка таблицы состоит из одного или нескольких полей, которые могут содержать
данные разных типов. Существует три способа определения записи, но после ее определения доступ к нолям и их изменение всегда выполняются в соответствии с одними и теми же правилами. Мы рассмотрим основные преимущества приме- нения записей, опишем разные способы их задания, приведем примеры использования в программах.
Эффективность использования записей
Запись — это высокоуровневое средство адресации и обработки данных, определенных внутри программ PL/SQL (в отличие от информации, хранящейся в таблицах баз данных). Представление данных в виде записей дает разработчикам оп* редеденные преимущества, о которых рассказывается в следующих разделах.
Абстракция данных
Используя метод абстракции при моделировании сущностей реального мира, вы намеренно игнорируете определенные свойства объекта, обобщаете его характеристики, отделяете его от особенностей конкретной реализации, то есть стараетесь составить об этом объекте обобщенное представление. Но все действия, вы- полняемые, скажем, с помощью функций, содержащихся в проектируемом вами программном модуле)должны отражаться (абстрагироваться) в его имени (или спецификации).
Для того чтобы создать запись, нужно выделить атрибуты или поля описываемого ею объекта. Затем нужно задать отношение между этими атрибутами и при- своить ему имя. Получившийся набор атрибутов, связанных определенным отношением, —это и есть запись.
Агрегатные операции
Организация информации в виде записей позволяет выполнять различные операции не только над их отдельными атрибутами, но и над записью как единым целым, Такие агрегатные операции лишь усиливают абстракцию данных. На практике нередки случаи, когда интерес представляют не столько значения в отдельных полях, сколько содержимое всей записи в целом.
Предположим, что по роду своей деятельности менеджер часто взаимодействует с различными компаниями. Информация о каждой из них хранится в базе данных в виде отдельной записи. Содержимое полей этой записи может быть разным, но для менеджера это не имеет особого значения. Например, ему не важно, сколько строк занимает адрес компании. Главное, чтобы информацию о компаниях можно было изменять, удалять или анализировать. Таким образом, организация данных в виде записей позволяет обрабатывать сведения о конкретной компании как единое пелое, отбрасывая отдельные реквизиты, если они не нужны, но в то же время обеспечивает доступ к каждому из этих реквизитов. Такой подход позволяет рассматривать данные как наборы объектов, к которым применимы определенные правила.
Компактность и простота кода
Использование записей помогает программисту писать более понятный и компактный код, содержащий меньше комментариев и объявлений переменных. Это
очень удобно как на этапе разработки программного кода, так и при его дальнейшем сопровождении. Для того чтобы более полно использовать все возможности, предоставляемые этими замечательными структурами, старайтесь придерживаться следующих рекомендаций.
О Создавайте записи, соответствующие курсорам. Создавая в программе курсор, тут же добавьте соответствующую запись (исключение составляет лишь курсор цикла FOR). Данные из курсора всегда извлекайте только в запись, но не в отдельные переменные. В тех немногих случаях, когда для этого потребуются дополнительные усилия, вы непременно похвалите себя за строгое следование этому принципу и оцените ^элегантность» полученного кода. А в Oracle9i Release 2 записи можно использовать даже в DML-инструкциях!
О Создавайте записи на основе таблиц. Если в программе должны храниться данные, полученные из таблицы, создайте запись на базе таблицы (или воспользуйтесь предопределенной записью). Старайтесь свести к минимуму количество переменных и динамически связывайте структуры данных программы со структурами данных РСУБД при помощи атрибута ZROWTYPE.
О Передавайте записи в качестве параметров. Вызываемым процедурам по возможности передавайте не отдельные переменные, а целые записи. Тем самым вы уменьшите вероятность изменения синтаксиса вызова процедур, благодаря чему программный код станет более стабильным. Однако у этой технологии имеется и недостаток: если запись передается как параметр типа OUT или IN OUT, программа сохраняет значения ее полей при откате транзакции, что требует дополнительного объема памяти м увеличивает нагрузку на процессор. (Иногда в подобных случаях можно воспользоваться параметром NOCOPY, о котором рассказывается в главе 16.)
Объявление записей
В PL/SQL существует три способа объявления закиси.
О Запись на основе курсора. Для объявления записи на основе явно заданного курсора или курсора, представленного переменной, где каждое поле соответствует столбцу или именованному выражению в инструкции SELECT, предназначен атрибут ZROWTYPE. Вот пример объявления записи, которая имеет ту же структуру, что и явный курсор:
DECLARE
CURSOR my_books_cur IS
SELECT"* FROM books
WHERE author LIKE «FEUERSTEIN汽;
one_5F_bcok my_books_curXROWTYPE:
О Запись на основе таблицы. Для объявления записи, каждое поле которой соответствует значению одноименного столбца таблицы, используется атрибут ZRO盯YPE с именем таблицы. Приведем пример объявления записи onB_bookf которая имеет ту же структуру, что и таблица books:
DECLARE
one book booksXROWTYPE;
□ 13апись, определяемая программистом. Для того чтобы создать запись с нуля, 卜。есть явно определить каждое поле, задав его имя и тип данных, нужно вос- Ьользоваться оператором TYPE.. .RECORD. Значением поля записи, определяемой программистом, может быть не только скалярное значение, но и другая за- Ьись・ В следующем примере определяется тип записи, содержащей некоторую информацию о писательской карьере автора книги, и объявляется экземпляр записи этого типа:
DECLARE
TYPE book_info_rt IS RECORD ( author books.author^TYPE, category VARCHAR2C100). total_page_count POSITIVE);
steven_as_author book_info_rt;
Обратите внимание на тот факт, что в объявлении записи пользовательского типа атрибут XRCMTYPE отсутствует. Запись в данном случае имеет тип book_1 п- fo_rt. Общий формат объявления атрибута %ROIYPE таков:
имя_3дписи [имя_схемы.]MMR_o6beKra^RQWTYPE;
Указывать параметр имя_схемы не обязательно (если он не задан, то для разрешения ссылки применяется схема, внутри которой компилируется данный код). В качестве указанного параметра может служить явный курсор, курсорная переменная, таблица, представление или их синоним.
Вот пример создания записи на основе курсорной переменной:
DECLARE
ПРЕ book_rc IS REF CURSOR RETURN booksXROWTYPE:
book_cv bcx)k_rc;
one_book book cvXROWTYPE:
BEGIN " 一
Запись можно объявить и неявно, например в цикле FOR с курсором или SQL-инструкцией. Ниже приведен блок кода, в котором раздел объявлений не содержит определения записи book_rec; PL/SQL автоматически объявляет запись с атрибутом %ROWTYPE в цикле с использованием SQL-инструкции SELECT:
BEGIN
FOR baok_rec IN (SELECT * FROM books)
LOOP
calculate_total_sales (book_rec);
END LOOP;'—
END;
Поскольку применение оператора TYPE считается самым интересным и сложным способом определения записи, рассмотрим его более подробно.
Записи, определяемые программистом
Записи, создаваемые на основе таблиц и курсоров, используются в тех случаях, когда в программе необходимо предусмотреть структуры для хранения определенных данных. Однако такие записи не охватывают всех потребностей программистов в составных типах данных. Предположим, что программисту понадобится
запись, структура которой не имеет ничего общего со структурой таблиц и представлений. Придется ли ему создавать курсор только для того, чтобы получить запись нужной структуры? Конечно же, нет. В подобных ситуациях PL/SQL позволяет программисту самостоятельно определить структуру записи с помощью оператора TYPE...RECORD.
Создавая запись самостоятельно, вы указываете количество ее полей, их имена и типы данных. Для объявления записи определяемого программистом типа нужно выполнить две операции:
Q с помощью оператора TYPE.. .RECORD определить тип записи, задав ее структуру;
О на основе этого типа объявить реальные записи, которые затем можно будет использовать в программе.
Определение типа записи
Тип записи определяется с помощью оператора ТУРЕ... RECORD, в котором задается имя новой структуры и входящие в нее паля. Общий синтаксис этого оператора таков:
TYPE *нх.тнпз IS RECORD
(ш_поля1 типjjaHHhixl.
ш^оля2 типданных?,
Ш_П0ЛЯ^ ТИПJdMKUXN
Здесь ш^опя^ 一 это имя Мго поля записи, а ш^аннМ - как вы, надо полагать, догадались, тип данных указанного поля.
Запись может иметь любой из перечисленных ниже типов:
О жестко запрограммированный скалярный тип данных (VARCHAR2, NUMBER и т. д.);
О какой-либо подтип, определяемый программистом;
О тип, устанавливаемый на основе типа уже определенной структуры данных, например объявление с привязкой посредством атрибута ZTYPE или ZROWTYPE (в последнем случае мы создаем вложению запись);
О тип коллекции PL/SQL; лоле записи может быть списком или даже коллекцией;
О тип REF CURSOR, то есть поле содержит курсорную переменную.
Вот как задается оператор TYPE...RECORD:
TYPE company_rectype IS RECORD
(comp# company.companyjdXTYPE. name company.nameXTYPE):
Тип записи можно определить в разделе объявлении или в спецификации пакета. Последний подход позволяет ссылаться на тип записи в любом блоке PL/SQL, откомпилированном внутри схемы, к которой принадлежит пакет, а также в блоках PL/SQL, откомпилированных в схемах с разрешением EXECUTE на этот пакет. Объявление записи
Создавая собственные пользовательские типы записей, вы можете использовать их в объявлении конкретных записей:
"内月 записи 3dwc“:・
Здесь 勿 м 方_3d〃wcw — это имя объявляемой записи, а тип_з а писи — ее тип, определенный в операторе TYPE.. .RECORD. Для того чтобы создать, скажем, запись с информацией о покупках клиента, сначала нужно определить ее тип:
TYPE customer_sales_rectype IS RECORD
(customerjd NUMBER (5).
customer_name customer.патеШРЕ.
total_saTes NUMBER (15,2)
Запись представляет собой структуру, состоящую из трех полей, которые содержат первичный ключ, имя клиента и общую сумму его покупок. Используя дйный тип, можно объявлять записи на его основе:
prev_custon)er_sales_rec customer^sales^rectype:
tap__customer_rec customer_sal es_rectype ;
Обратите внимание: для того чтобы показать, что это объявление записи, атрибут ZROWTYPE или какое-нибудь другое ключевое слово ие требуется. Атрибут XROWTYPE нужен только для объявления записей, создаваемых на основе таблиц и курсоров.
В определении типа записи для каждого поля наряду с его именем и типом можно задать значение по умолчанию. Для этой цели используется классический синтаксис DEFAULT или :-. Здесь же можно указать, что поле залиси не должно быть пустым (NOT NULL) - в таком случае для него обязательно задается значение по умолчанию. Имя каждого поля записи должно быть уникальным.
Примеры определения типа записи
Предположим, что в нашей программе объявлены подтип, курсор и ассоциативный массив1:
DECLARE SUBTYPE long_line_type IS VARCHAR2C2000):
CURSOR company_sales_cur IS
SELECT name. SUM 7order_aniount) total_sales
FROM company c, order о
WHERE c.8mpa®Jd - o.conipanyjd;
TYPE employee_1ds_tabletype IS
TABLE OF employee.employee_1d*TYPE
INDEX BY BINARYJNTEGER;-
В том же разделе объявлений определены две записи.
О Задаваемая программистом запись представляет подмножество столбцов таблицы company и таблицу PL/SQL emplоуее_1 ds_tabletype (для привязки полей записи к столбцам таблицы используется атрибут ПУРЕ, третье поле записи является ассоциативным массивом с идентификационными кодами сотрудников):
TYPE company_rectype IS RECORD
(companyj d company. company_id3!TYPE.
Как будет рассказано ниже, при описании коллекций, ассоциативным массивом в Oracle9i называется индексная таблица.
company_name company.nameXTYPE.
new_hires_tab employejldsjabletype);
О Смешанная запись демонстрирует разные типы объявлений полей, а именно с ограничением NOT NULL, с использованием подтипа и атрибута ШРЕ, со значением по умолчанию, с ассоциативным массивом и вложенной записью:
TYPE mishmash_rectype IS RECORD
(emp_number NUMBER(IO) NDT NULL, paragraph_text long_line_type. company_nm company.пагпеШРЕ, total_sales compan.y_sales.total_salesXTYPE :- 0. new_hi res_t3b empl oyeej ds_tabl etype「 prefers_nonsmoking_fl BOOLEAN :- FALSE, new_company_rec company_rectype
Разнообразие способов создания записей в PL/SQL просто впечатляет. Записи могут включать таблицы, представления и инструкции SELECT. Как правило, они имеют произвольную структуру и содержат поля, представляющие собой другие записи и ассоциативные массивы.
Обработка записей
Независимо от того как вы определяете запись (на основе таблицы или курсора либо с помощью явного оператора TYPE.. .RECORD), приемы работы с нею всегда одинаковы. Причем обрабатывается либо вся запись как единое целое, либо каждое из ее полей в отдельности.
Операции над записями
Обработка отдельной записи подразумевает выполнение над ней некоторых операций как над единым целым. В настоящее время PL/SQL поддерживает следующие операции над записями:
О копирование содержимого одной записи в другую (если они имеют совместимую структуру, то есть одинаковое количество полей одного того же или взаи- мопреобразуемых типов);
О присваивание записи значения NULL (посредством простого оператора присваивания);
О передача записи функции в качестве аргумента;
О возврат записи из функции (с применением оператора RETURN).
Но на выполнение операций над отдельными записями существует ряд ограничений.
Q Для того чтобы проверить, содержат ли все поля записи значение NULL, использовать синтаксис IS NULL нельзя. Осуществить такую проверку можно лишь путем применения оператора IS NULL по отношению к каждому полю.
О Невозможно сравнить две записи при помощи единственной операции. В частности, нельзя определить, равны или нет две записи (то есть значения всех их полей), или же узнать, какая из записей больше. К сожалению, для того чтобы ответить на эти вопросы, нужно сравнить каждую пару полей. Далее в этой главе, в разделе «Сравнение записей», рассказывается о том, как создать утилиту, автоматически генерирующую код для сравнения двух записей.
□ Только в Oracle9i Release 2 появилась возможность добавлять новые записи в таблицу базы данных целиком. В предыдущих же версиях системы значение каждого поля нужно было записывать в соответствующий столбец таблицы отдельно. Более полная информация об инструкциях языка DML, предназначенных для обработки записей, содержится в главе 13.
Выполнять операции над отдельными записями можно лишь при условии, что 0Ни имеют совместимую структуру. Иными словами, у обрабатываемых записей должно быть одно и то же количество полей и эти поля должны иметь одинаковые или взаимопреобразуемые типы данных. Предположим, что создана такая таблица:
CREATE TABLE cust_sales_roundup (
customerjd NUMBER C5],
customer_name VARCHAR2 (100),
total_saTes NUMBER (15.2)
);
Три объявленные ниже записи имеют совместимую, структуру, и их можно использовать в разных операциях:
DECLARE
cust_sales_roundup_rec cust_sales_roundupXROWTYPE:
CURSOR cust_sales_cur IS SELECT * FROM cust_sales_roundup: cust_sales_rec cust_sales_curXROWTYPE;
TYPE customer^sales_rectype IS RECORD
(customerjd NUMBERS5),
customer_name customer.nameXTYPE, total_saTes NUMBER(15,2)
);"
prefererred_cust_rec customer_sales_rectype;
BEGIN ~~
Присвоим содеожимое одной записи другой.
cust_sales_roundup_rec :- cust_sa]es_rec:
prefererred cust rec cust sales rec:
END: ~"
Рассмотрим еще несколько примеров выполнения операций над отдельными записями. Ниже записи присваивается значение по умолчанию. Запись можно инициализировать при ее объявлении, присвоив содержимое другой, совместимой с ней записи. Сейчас же локальной переменной curr_company_rec присваивается запись, переданная процедуре в качестве аргумента. Теперь значения отдельных Полей записи можно изменять:
PROCEDURE compare_conipanies
(prev_company_rec IN companyXROWTYPE)
IS~
curr conipany_rec conpanyXROWTYPE :皿 prev cornpany_rec:
BEGIN""
end;,
В следующем примере сначала создается новый тип записи и объявляется запись этого типа. Затем создается второй тип записи, для которого в качестве типа столбца устанавливается первый тип. В определении данного типа столбец-запись инициализируется переменной-записью:
DECLARE
TYPE f1rst_rectype IS RECORD (
varl VARCHARZ(IOO) ;-'WHY NOT');
flrst_rec f1rst_rectype:
TYPE second^rectype IS RECORD'
(nested_rec first rectype :-'first rec);
BEGIN 一 一"
ENO;
Разумеется, операцию присваивания можно осуществить и в разделе выполнения. Ниже объявляются две разные записи типа га i n_forest_hi story и инфор- мадия из первой из них присваивается второй записи:
DECLARE
prev_ra1n_forest_rec rain_forest_h1storyXR0WTYPE;
curr_ra1n_forest rec гаin_forest_historyXROKTYPE:
BEGIN ~~"
,..Инициализация записи prev_rain_forest_rec ...
Копирование данных первой записи во вторую
curr_rain_forest_rec :- prev_rain_forest_rec;
В результате выполнения такой операции значение каждого поля первой записи присваивается соответствующему полю второй записи. Значения полям можно было бы присваивать и по отдельности, но, согласитесь, так гораздо удобнее. Поэтому старайтесь обрабатывать записи как одно целое — это позволит сэкономить время, упростить программный код и облегчить его сопровождение.
Для перемещения данных строки таблицы в запись достаточно одного оператора FETCH с предложением INTO, в котором указывается эта запись. Вот несколько примеров выполнения такой операции:
DECLARE
/*■
Объявляем курсор, а затеи при покощи атрибута %R0奸YPE
определяем запись на его основе■
*/
CURSOR cust-SdluS—Cur IS
SELECT customerjd. name. SUM (total_sales) tot^sales
FROM cust_saTes_roundup
WHERE SOld_on <"ADD_MONTHS CSYSDATE', -3)■
GROUP BY custonerjd. name:
cust__sa 1 es_rec cust_sз 1 es^curXROWTYPE:
BEGIN
/* Перемещаем значения из курсора непосредстзенно в запись */
OPEN cust_sales_cur:
FETCH cust_sales_cur INTO cust_sales_rec;
В следующем блоке кода сначала задается тип записи, который соответствует данным, возвращаемым неявным курсором, а затем производится извлечение данных (SELECT) непосредственно в запись:
DECLARE
TYPE customer_sales_rectype IS RECORD
(customerjd NUMBER (5), customer_name customer.nameJITYPE. total_saTes NUMBER (15,2)
topjustomejrec customer_sal es_rectype ;
BEGIN 一 一~
/* Пересылаем значения непосредственна а запись */
SELECT customerjd, name. SUM (total_sales)
INTO top_customer_rec
FROM cust_sales_roundup
WHERE 501d」n <~ADD_MONTHS (SYSDATE. -3)
GROUP 8Y customerjd. name:
Как уже было сказано, можно объявить функцию, возвращающую запись. Следующий пример показывает, как это делается, а к тому же демонстрирует возможность «обнуления» записи — присваивания ей значения NULL:
CREATE OR REPLACE FUNCTION bestseller (
weekjn IN PLS_INTEGER, year J n IN PLSJNTEGER) RETURN booksXROWTYPE
IS
return_value booksXROWTYPE;
BEGIN "
SELECT *
INTO return_value
FROM books В
WHERE week_number - weekj n
AND year - yearjn
AND sales * (SELECT MAX (sales) FROM book sales BS
WHERE SS.ISbn - B.lsbn~
AND week_number - weekjn
AND year - yearjn);
RETURN return value:
EXCEPTION '
WHEN NO_DATAJOUND OR TOQ_MANY_ROW$
THEN-'
--Обеспечиваем возврат значения NULL
return_value :- NULL;
RETURN return_value:
END bestseller;
По возможности старайтесь работать с записями на агрегатном уровне, то есть обрабатывать каждую из них как единое целое, не разбивая на отдельные поля. Благодаря этому результирующий код получится более простым, понятным и стабильным. Конечно, существует множество ситуаций, когда необходимо получить доступ лишь к отдельным полям записи, и мы расскажем, как это делается.
Операции над отдельными полями
Чтобы получить доступ к отдельному полю записи (для выполнения операции чтения или изменения), используйте точечную нотацию, такую же, как при идентификации столбца таблицы базы данных. Приведем ее синтаксис:
[иня_схены.][имя_пакета.^имя^здписи.иня_попя
Имя пакета нужно указывать только в том случае, если запись определена в спецификации другого пакета (не того, с которым вы работаете в настоящее время). А имя схемы потребуется определить лишь при условии, что пакет принадлежит не той схеме, где компилируется данный код.
Идентифицировав поле записи посредством точечной нотации, хранящееся в нем значение можно обрабатывать точно так же, как любую другую переменную. Рассмотрим несколько примеров.
Оператор присваивания :- позволяет изменить значение в конкретном поле записи. В первом примере в поле total_sales записи top_customer_rec заносится значение 0. Во втором примере вызывается функция check_report_status, которая возвращает значение флага output一generated (ему присваивается либо TRUE, либо FALSE);一
BEGIN
top_custonier_rec.tQtal_sales : - О;
report_rec.output_generated ;=
check_report_status (report_rec. reported):
END: ~~~
Код следующего примера создает запись на основе таблицы rain_forest_histo- гу, записывает в ее поля значения и возвращает запись в ту же таблицу:
DECLARE •
ra1n_forest_rec raln_forest_h1story^ROWTYPE:.
BEGIN ~-
/* Установка значения полей записи */
rain_forest__rec.country_code :e 1005:
гаin_forest_rec.ana1уsis_date :-
ADD_MONTHS (TRUNC (SYSDATE), -3);
rain_forest_rec.sizejn_acres :- 32: га 1n_forest_rec.sped es_lost ;- 425;
/* Вставка в таблицу строки значений из записи */
INSERT INTO rain_forest_history
(country code. analysis_date, size in_acres. spedes_lost)
VALUES ”"
Crain_forest_rec.country_code,
ra1n_forest_rec.analys1s_date, ra1n_forest_rec.size_1n_acres, rain_forest_rec.spedesjost);'
END;
Обратите внимание, что в поле analysis_date можно записать любое значение или выражение, которое имеет тип DATE. Сказанное касается и всех других полей, а также более сложных структур.
PL/SQL позволяет создавать структуры вложенных записей, то есть такие, в которых одно из полей внешней записи представляет собой отдельную запись. В следующем примере сначала определяется тип записи, в которую заносятся фрагменты номера телефона (phone_rectype)( а затем — тип записи, в которой объединяются в единую структуру contact_set_rectype несколько телефонных номеров одного человека:
DECLARE
TYPE phone_rectype IS RECORD
(inti ^prefix
VARCHAR2(2),
area_code
VARCHAR2(3).
exchange
VARCHAR2O).
phn_number
VARCHAR2(4).
extension
):
VARCHAR2C4)
TYPE contact_set_rectype IS RECORD
Cday_phone#
phone_rectype,
/*
Вложенная
запись
*/
eve_phone#
phone__rectype.
/*
Вложенная
запись
*/
fax_phone#
phone_rectype.
/*
Вложенная
запись
*/
home_phone#
phone_rectype,
/*
Вложенная
запись
*/
cell-phone#
phone_rectype
/*
Вложенная
запись
*/
.auth_rep_info_rec contact_set_rectype:
BEGIN ~ ~~~
END;
Для ссылки на поля вложенной записи используется точечная нотация, причем таким образом можно обращаться к полям любого уровня вложенности. Вложенные структуры необходимо последовательно перечислить, разделив их точками. В приведенном ниже фрагменте кода в поле номера факса заносится содержимое поля домашнего телефона (что, конечно же, является ошибкой):
auth_repjrrfo_rec.fax_phone#.area_code := airth_rep_1 nfo_rec.home_phone#.area_code;
Следующий пример демонстрирует принцип использования ссылок на записи и типы записей, объявленные в пакете. Предположим, что пользователь формирует список книг, которые он планирует прочесть за время летнего отпуска. Для этой цели создается следующая спецификация пакета:
CREATE OR REPLACE PACKAGE summer IS
TYPE reading_Hst_rt IS RECORD ( favor1te_author VARCHAR2 (100), titleVARCHAR2 (100),
finlsh.by DATE);
must-read readlngjlst_rt;
wifes_favor1te read1ng_11st_rt;'
END summer:■
CREATE OR REPLACE PACKAGE BODY sunnier
IS
BEGIN ■■ Раздел инициализации'
must-read,favorlte_author :- 'Tepper. Sheri S.':
musCread.title :-"'Gate to Women11 s Country':
END summer;
После компиляции такого пакета формируется список литературы:
DECLARE
f 1 rst_book summer. readl ng_H st_rt ;
second book summer.readlng list rt:
BEGIN -"
sunmer.musVead.flnlsh^by :- T0_DATE ('01-AUG-2OQ2', 'DD-MON-YYYY');
f1rst_book :- summer.must-read;
second_book.favorlte_author :- 'Morris, Benny':
second book.title ^"'Rlghteous VIctlnis1:
second"book.f1n1sh_by :- TO.DATE ('01-SEP-2O021. 'DD-MON-YYYY');
END;
Далее объявляются две записи для представления информации о книгах. Сначала устанавливается значение поля f1nish_by объявленной в пакете summer записи must_read (обратите внимание на синтаксис пакет.запись .поле), затем запись присваивается переменной, представляющей первую книгу из числа запланированных для чтения. После этого значения присваиваются отдельным полям записи, относящейся ко второй книге.
Кстати говоря, при работе со встроенным пакетом UTL_FILE, предназначенным для файлового ввода-ввода, необходимо следовать тем же правилам обработки записей. Объявление типа данных ITTL_FILE.FILE_TYPE — это объявление типа записи. Вот почему, объявляя дескриптор файла, вы на самом деле объявляете запись:
DECLARE
myjilejd UTLJ4LE.FILEJYPE:
Сравнение записей
Ну а как узнать, равны ли две записи, то есть совпадают ли их соответствующие поля? Было бы неплохо, если бы PL/SQL позволял выполнять непосредственное сравнение. Например:
DECLARE
flrst—book sunner.readlngj 1 st_rt 厂 summer.must_read;
second_book summer.reading list rt :- sunmer.wlfes favorite:
BEGIN-
IF first book - second book
THEN ~一
to talk about:
END IF:' ~"
END:
Но, к сожалению, это невозможно. Для сравнения двух записей требуется написать код для сравнения всех их полей по отдельности. Если количество полей невелико, сделать это довольно проста. Например, в случае записей, рассмотренных в предыдущем пример% сравнение выполняется следующим образом: declare
first book surmer.reading_l 1 st_rt :- summer.must_read:
second book surmer.read1ng_l1st_rt :- surmer.wlfes/avarlte;
BEGIN "
IF first book.favorite author - sBCond_book.favorite_author
AND -一"
first book.title - second_book.title
AND "-
first book.finish by - second book.flnlsh^by
THEN "~I
lots to talk about:
END IF:"""
END:
Однако здесь возникает одна проблема. Если вы хотите указать, что две записи со значениями NULL будут равны между собой, оператор сравнения нужно модифицировать следующим образом:
(flrst_book.favorite_author - second_book.favorite_author
OR? flrst_book.favorite_author IS NULL AND secand_book.favorite_author IS NULL)
Как видите, код получился довольно громоздким. А правда, было бы неплохо сгенерировать его автоматически? Оказывается, это и в самом деле возможно — по крайней мере для тех записей, которые определены с атрибутом XROWTYPE на основе таблиц или представлений. В таком случае имена полей можно получить из представления ALL_TAB_COLUMNS в словаре данных.
А готовый генератор кода для сравнения записей, разработанный Дэном Спенсером (Dan Spenser), вы найдете на узле издательства O'Reilly в файле gen_re- cord_comparlson.pkg.
Коллекции в PL/SQL
Коллекция — это составной тип данных, предназначенный для хранения одномерных массивов в программах PL/SQL. Что это означает? Коллекции используются Для хранения множества однотипных элементов в коде PL/SQL или в столбцах базы данных. Ниже перечислено несколько операций, при выполнении которых наиболее целесообразно использовать коллекции.
Q Эмуляция двунаправленных курсоров и курсоров с произвольным доступом. PL/SQL позволяет извлекать записи курсора только последовательно. Но если загрузить результирующий набор строк курсора в коллекцию, то можно будет перемещаться между записями в любом направлении и обращаться к каждой строке курсора произвольное количество раз.
О Хранение списков подчиненной информации непосредственно в столбцах таблицы —в виде вложенной таблицы или массива VARRAY. (О вложенных таблицах, структурах VARRAY и ассоциативных массивах рассказывается далее в этой главе, в разделе <Типы коллекций#.) Применение коллекций позволяет заметно повысить производительность поиска.
О Отслеживание элементов данных, отобранных в программе для специальной обработки.
О Кэширование статичной информации базы данных, которая часто указывается в запросах (коллекции позволяют повысить быстродействие запросов).
В следующих разделах рассказывается о принципах создания и о применении типов коллекций в базах данных и в программах PL/SQL, приводится синтаксис используемого при этом кода. Вы познакомитесь с тремя способами инициализации коллекций, а также со встроенными методами управления их содержимым, такими как NEXT, DELETE и TRIM. И хотя охватить все аспекты использования коллекций в SQL (например, в качестве столбцов таблицы) невозможно, рассмотренные примеры покажут вам, как важны и полезны эти структуры, несмотря на свою сложность.
Пример простой коллекции
Структура и синтаксис коллекций гораздо сложнее структуры и синтаксиса скалярных значений типа даты и числа. Рассмотрим концепцию и терминологию коллекций на очень простом примере.
DECLARE
TYPE I1st_of_dates_t IS TABLE OF DATE;
一
TYPE list_of_names_t IS TABLE OF VARCHAR2 (100)
INDEX BY BINARY_INTEGER;
~'
bi rthdays list_of_dates_t
:= 11st_ofHates_t ():
happyfamily I1st_of_nanies_t;
BEGIN- 一 -
birthdays.EXTEND;
birthdays Cl) '23-SEP-1958':
birthdays.EXTEND;
birthdays (2) := '01-OCT-1986';■
16happyfamily (-15070) :- 'Steven';
17happyfamlly C88)尸'Veva':
18happyfamlly (909) :- 'Chris':
19 happyfamlly (2020202020) ;- 'Eli';-
20'
DBMSJUTPUT.put_line (birthdays.COUNT);
DBMS^OUTPUT.putjine (happyfamly.FIRST);
END:
Вот что выводит данная программа:
2
-15070
А выполняемые ею действия перечислены в приведенной ниже таблице.
Строки Описание
2-5 Определения типов коллекций. Для работы с коллекцией (списком) нужно определить ее тип, а затем объявить конкретные коллекции этого типа.
В данном примере создаются два типа коллекций: вложенная таблица (в строке 2) и всхоциативный массив (в строках 4, 5)
, 8 Объявление реальной вложенной таблицы. Обратите внимание, что вложенная таблица инициализируется вызовом конструктора — функции с тем же именем, что и у типа таблицы, автоматически генерируемой PL/SQL
Объявление ассоциативного массива. Для коллекции данного типа конструетор не нужен
ц-14 С помощью стандартного оператора присваивания задаются две строки вложенной таблицы. Обратите внимание, что место для строк необходимо явно вьщелить с помощью метода EXTEND. Если этого не сделать, в результате выполнения подобной операции присваивания будет сгенерирована ошибка. Обратите также внимание, что строки вложенной таблицы birthdays заполняются последовательно
16-19 Четырем элементам коллекции happyfamily присваиваются строковые значения. Ассоциативный массив не нужно расширять явно, при помощи метода EXTEND. Достаточно присвоить значение строке с заданным номером, и таковая появится в коллекции. Обрагтите внимание, что строки добавляются не последовательно. Фактически их номера выбираются случайным образом и могут быть как положительными, так и отрицательными числами. Эго пример разреженной коллекции
Определение с помощью метода COUNT количества строк, содержащихся во вложенной таблице birthdays
Вызов метода FIRST для определения первого или наименьшего номера строки в коллекции happyfamily
Типы коллекций
Oracle поддерживает три типа коллекций.
Q Ассоциативные массивы. Это одномерные неограниченные разреженные коллекции, состоящие из однородных элементов, которые можно обработать только в PL/SQL. Ранее, в PL/SQL Release 2, они назывались таблицами PL/SQL, а в Огас1е8 и Oracle8z - индексными таблицами, поскольку, объявляя такую коллекцию, приходилось явно указывать, что она «индексируется» номером строки. Теперь, в Oracle9z, они называются ассоциативными массивами. Этот термин выбран потому, что предложение INDEX BY может использоваться для индексирования содержимого коллекций посредством значений типа VARCHAR2 или PLS_INTEGER.
° Вложенные таблицы. Так называются одномерные, несвязанные коллекции, также состоящие из однородных элементов. Первоначально они заполняются полностью, но позднее, из-за удаления некоторых элементов, могут стать разреженными. Вложенные таблицы можно определять и в PL/SQL, и в базах данных (например, в качестве столбцов таблиц).
Q Массив типа VARRAY. Подобно двум другим типам коллекций, массивы VAR- RAY (массивы переменной длины) являются одномерными коллекциями, состоящими из однородных элементов. Однако их размер всегда ограничен, и они не
могут быть разреженными. Подобно вложенным таблицам, массивы VARRAY используются и в PL/SQL, и в базах данных. Однако порядок их элементов при сохранении и извлечении, в отличие от вложенных таблии, сохраняется.
Терминология, используемая при работе с коллекциями
С коллекциями связан целый ряд специфических терминов.
О Коллекция. Этот термин может иметь несколько значений;
• вложенная таблица, ассоциативный массив или массив типа данных VARRAY;
переменная PL/SQL типа вложенной таблицы, ассоциативного массива или VARRAY;
столбец таблицы, где хранятся значения типа вложенной таблицы или массива VARRAY.
Однако независимо от конкретного использования коллекххия всегда остается списком элементов.
О Одномерная коллекция. В каждой строке коллекции имеется всего одно поле, и с этой точки зрения она похожа на одномерный массив. Нельзя объявить коллекцию, на элементы которой можно было бы ссылаться следующим образом: 吸table СЮ. 44)
Коллекция my_table (10, 44) - это двухмерная структура, но в настоящее время такие структуры не поддерживаются.
О Однородные элементы. Коллекция может содержать только один столбец, и этот столбец должен иметь тип данных, указанный в определении типа коллекции. Поэтому все строки коллекции содержат значения одного типа, а следовательно, коллекция является однородной. Однако тип данных столбца может быть составным; например, можно объявить коллекцию, состоящую из записей, то есть таблицу. А в Oracle9i можно объявлять и многоуровневые коллекции: полем коллекции служит коллекция или запись, полем в которой, в свою очередь, также является коллекция или запись и т. д.
О Ограниченная и неограничениая коллекции. Коллекция называется ограниченной, если заранее определены границы возможных значений индексов, или номеров, ее элементов. Если же верхняя или нижняя граница номеров элемев- тов не указан% коллекция называется неограниченной. Коллекции типа VARRAY (массивы переменной длины) всегда ограниченны. При определении такой коллекции следует указать максимальное количество ее элементов (номер первого элемента всегда равен 1). Вложенные таблицы и ассоциативные массивы мы определяем как неограниченные, посколььсу максимальное количество элементов в них ограничивается только теоретически.
Границы индекса ассоциативного массива) индексируемого посредством переменной типа BINARY_INTEGER, таковы: наименьшим значением является -231 + 1( а наибольшее значение не превышает 231 - 1. Границы индекса вложенной таблицы изменяются в пределах диапазона от 1 до 231 - 1. Это означает, что в ассоциативном массиве можно определить приблизительно 4,3 млрд, элементов, а во вложенной — 2,14 млрд, элементов. Но на самом деле количество элементов во вложенных таблицах и ассоциативных массивах (в указанных структурах)
ограничивается техническими возможностями системы - объемом памяти компьютера. Вот почему ассоциативные массивы и вложенные таблицы принято считать неограниченными структурами.
□ Разреженные и плотные коллекции. Коллекция (или массив, или список) называется плотной, если все ее элементы, от первого до последнего, определены и каждому из них присвоено некоторое значение (таковым может быть и NULL). Коллекция считается разреженной, если отдельные ее элементы отсутствуют, как в рассмотренном выше примере. Не обязательно определять все элементы коллекции и заполнять ее полностью. Массивы VARRAY всегда являются плотными. Вложенные таблицы первоначально всегда плотные, но по мере удаления некоторых элементов становятся разреженными. Ассоциативные массивы могут с самого начала быть разреженными, что зависит от способа их заполнения. (Оказывается, разреженность — это очень ценное свойство, позволяющее добавлять элементы в коллекцию, используя первичный ключ или другие ключевые данные, например номер записи. Таким образом задается порядок данных в коллекции, что значительно ускоряет их поиск.)
О Коллекция, индексиронанная целочисленными значениями. К любому эле- метту коллекции можно обратиться, указав его номер, который представляет собой целочисленное значение. Создавая коллекцию типа ассоциативного массива, следует явно указать, посредством каких индексов — номеров записей или символьных значений — можно получить доступ к ее элементам. В качестве индексов коллекций двух других типов используются только номера строк.
О Коллекция, индексированная строками. В Oracle9i Release 2 в качестве индексов ассоциативных массивов можно использовать не только номера строк, но и символьные значения. Эта возможность не поддерживается ни для вложенных таблиц, ни для массивов VARRAY.
О Внешняя таблица. Так называют таблицу, содержащую столбец типа вложенной таблицы или массива VARRAY.
О Внутренняя таблица. Так принято называть коллекцию, содержащуюся в столбце таблицы.
О Вспомогательная таблица. Физическая та6лица)создаваемая Oracle для хранения внутренней таблицы (столбца, содержащего вложенную таблицу или массив VARRAY).
Использование коллекций
Опыт показывает, что лишь немногие разработчики знают о коллекциях и достаточно активно нх используют. Это удивительно, поскольку речь идет об исключительно удобных структурах. Единственным их недостатком является относительная сложность. Наличие трех типов коллекций, необходимость выполнения нескольких действий для создания каждой из ннх, использование коллекций и в программах PL/SQL, и в качестве объектов базы данных, более сложный по сравнению с обычными переменными синтаксис — все эти факторы, к сожалению. Уменьшают интерес программистов к коллекциям.
Авторы пытались построить эту главу таким образом, чтобы рассказать о коллекциях как можно подробнее и вместе с тем избежать повторений одной и той
же информации, относящейся к разным типам коллекций. Тем не менее глава получилась довольно объемной. Вот краткий перечень тем, рассматриваемых в оставшейся ее части. Вначале мы покажем, как определяются типы (или шаблоны) коллекций и как создаются конкретные экземпляры коллекций этих типов. Далее будут рассмотрены многочисленные встроенные функции и методы, предоставляемые Oracle для анализа и обработки содержимого коллекций. Полученные в результате знания позволят вам понять основные особенности работы с коллекциями, в там числе изучить процесс инициализации вложенных таблиц и массивов VARRAY, способы заполнения коллекций и доступа к их данным. Завершается глава рассказом о псевдофункциях, которые позволяют работать с коллекциями как с таблицами реляционной базы данных. Здесь же будут приведены советы по выбору наиболее подходящего типа коллекции.
Определение типов коллекций и их объявление
Приступая к работе с коллекцией, таковую нужно объявить, указав прежде всего ее тип. Существует два способа задания типа коллекции.
О Во-первых, с использованием команды CREATE TYPE. Так определяется тип вложенной таблицы и тип VARRAY в базе данных, что позволяет использовать его для объявления столбцов таблиц базы данных, переменных в программах PL/SQL, а также атрибутов объектных типов.
О Во-вторых, с применением синтаксиса TYPE.. .IS. Так определяется тип коллекции в программе PL/SQL, и в этом случае он доступен лишь в том блоке, где был декларирован.
Объявление ассоциативного массива
Как и запись, ассоциативный массив объявляется в два этапа.
О Сначала при помощи оператора TYPE,. .TABLE определяется конкретная структура ассоциативного массива (состоит из строк, дат и т. п.). В результате создается тип данных, который можно использовать в операторах объявления.
О Затем на основе этого типа объявляется собственно коллекция, то есть создается конкретный экземпляр определенного типа данных — ассоциативный массив.
Определение ассоциативного массива
Оператор для определения ассоциативного массива TYPE имеет следующий формат:
TYPE 玄нл_т和д_тд6/шиы IS TABLE OF тип_дднных [NOT NULL]
INDEX~BY [BINARY_INTEGER | подтип_типа BINARYJNTEGER | VARCHAR2 (максимальный_рдЗнер)1:
Здесь имя_гипауаблицы — это имя создаваемой табличной структуры, a THnjidH- ных 一 это тип данных единственного столбца таблицы. При желании можно добавить ограничение NOT NULL, указывающее, что каждая строка таблицы должна содержать значение.
До Oracle9i Release 2 единственным способом определения индекса ассоциативного массива был такой:
INDEX BY BINARY INTEGER
Теперь этот индекс можно определить и по-другому:‘
INDEX BY PLSJNTEGER
INDEX BY NATURAL
INDEX BY POSITIVE
INDEX BY ^С^2(.наксимальныи_размер)
INDEX BY таблиид .столбецКЛРЕ
INDEX BY панет. парененная^Р£,
INDEX BY подтип、
В этом объявлении все типы и подтипы должны быть совместимыми с типом данных VARCHAR 2.
Присвоение имен табличным типам производится в соответствии с теми же правилами именования, которые используются для любых других идентификаторов PL/SQL: имя должно начинаться с буквы, иметь длину до 30 символов, может содержать некоторые специальные символы, например символ подчеркивания и знак доллара. Более подробно об этом рассказывается ниже, в разделе «Использование ассоциативных массивов типа VARCHAR2».
Для поля табличного типа можно задать один из двух типов данных:
О скалярный тип данных — любой поддерживаемый PL/SQL скалярный тип, в том числе VARCHAR2, CLOB, POSITIVE, DATE и BOOLEAN;
О тип данных с привязкой - определяется ранее объявленной переменной или курсором, заданным при помощи атрибута ПУРЕ.
Вот несколько примеров определения типов ассоциативных массивов:
TYPE cornpany_keys_tabtype IS TABLE OF company. conipany_1 dXTYPE NOT NULL
INDEX BY BINARYJNTECER:-
TYPE booklist_tabt^pe IS TABLE OF booksXROWTYPE
INDEX BY BINARY_INTEGER:
TYPE books_by_author_tabtype IS TABLE OF booksZROWTYPE
INDEX BY VARCHAR2(100);
Объявление коллекции
Создав табличный тип, вы можете ссылаться на него в объявлениях конкретных коллекций. Общий формат объявления коллекции таков:
ш^коллекции табличный_тип;
Здесь имя__коллек11ии — это имя объявляемой коллекции, а табличный^ип — имя ранее созданного табличного типа. В следующем примере объявляется табличный тип для хранения первичных ключей таблиц с названиями компаний и именами сотрудников и на его основе создаются две коллекции первичных ключей: CREATE DR REPLACE PACKAGE cornpany_pkg IS_
/* Объявление табличного типа для первичных ключей */
TYPE primary keysjabtype IS TABLE OF NUMBER NOT NULL
INDEX BY BINARYJNTEGER:
/* Создание двух коллекций на основе табличного типа */
company_keys_tab primary_keys_tabtype;
edip_keys_tab primdry_keys_tdbEype,
HID company_pkg:
Объявление вложенной таблицы или массива VARRAY
Перед объявлением любой вложенной таблицы или массива VARRAY, равно как перед объявлением ассоциативного массива, нужно определить соответствующий тип. Его можно задать либо в базе данных, либо в блоке PL/SQL.
Тип вложенной таблицы, который можно использовать в базе данных (а не только в программе PL/SQL), создается следующим образом:
CREATE [OR REPLACE] TYPE имя_тила AS | IS
TABLE OF т^п_данных_зленента [NOT NULL];
Тип данных VARRAY, который будет доступен в базе данных (а не только в программе PL/SQL), создается так:
CREATE [OR REPLACE] ПРЕ иня_типд AS ] IS
VARRAY {максимдльный_индекс) OF т]лп^знныхулемента [NOT NULL];
Для удаления любого типа данных необходимо выполнить такую команду:
DROP ПРЕ икя_типд [FORCE];
А вот как в программе PL/SQL создается тип вложенной таблицы:
TYPE имя_типа IS TABLE OF тип_данных_элемента [NOT NULL];
и тип данных VARRAY:
TYPE имя_типа IS VARRAY (мзксимдльныи_индекс)
OF тип^анных_эленента [NOT NULL];
Опишем параметры, используемые при определении типов данных.
О Опция OR REPLACE позволяет повторно определить существующий тип данных, если от него не зависят никакие другие объекты базы данных. Это достаточно удобный прием, особенно, когда необходимо сохранять разрешения на выполнение операций замены.
О Параметр имяуипа — это допустимый идентификатор SQL или PL/SQL, который позднее будет использоваться в объявлениях переменных и столбцов.
О Параметр 『“a/dHHMX—?刀已汽已“丁己 определяет тип данных элементов коллекции. Все элементы имеют один и тот же тип. Это может быть почти любой скалярный или объектный (в том числе REF) тип данных. Если элементы являются объектами, объектный тип не должен иметь атрибутов, представляющих собой коллекции. Когда в PL/SQL элементами коллекции являются записи (RECORD), поля таких записей могут быть только скалярными элементами или объектами. Некоторые типы данных, в том числе BOOLEAN, NCHAR, NCLOB, NVAR- CHAR2, REF CURSOR, TABLE и VARRAY, в коллекциях не используются вообще.
О Опция NOT NULL указывает, что переменная данного типа не должна содержать пустых элементов. Однако вся коллекция при этом может быть равна NULL (то есть не инициализирована).
О Параметр наксималь ный_индекс — это значение, определяющее максимальное количество элементов в массиве VARRAY. Изменить его после объявления невозможно.
О Опция FORCE предписывает Oracle удалить тип данных даже в том случае, когда на нега ссылается другой тип. Например, если в определении объектного типа указывается тип коллекции, его можно удалить, добавив в соответствующую команду ключевое слово FORCE.
Обратите внимание, что единственным различием в синтаксисе определения типов вложенной таблицы и ассоциативного массива является отсутствие в первом случае предложения INDEX BY. Синтаксис определения массива VARRAY отличается от синтаксиса определения типа вложенной таблицы наличием ключевого слова VARRAY и необходимостью ограничивать количество элементов.
Примеры объявления вложенных таблиц и масхивов VARRAY
Вот несколько примеров объявления в PL/SQL вложенных таблиц. Сначала в базе данных создается тип вложенной таблицы:
CREATE OR REPLACE TYPE Color_tab_t AS TABLE OF VARCHAR2(30):
Затем объявляется несколько переменных PL/SQL Причем использовать только те типы, которые определены в базе данных, не обязательно. Можно определить только локальные типы, а можно — и локальные и глобальные:
DECLARE
--Переменная для хранения списка цветов шрифта
/* В следующей переменной позднее будет храниться временная
11 копия font_colors. Обратите внимание, что для ссылки на тип
11 данных переменной font_colors можно использовать■
спецификацию XTYPE. Ниже представлены два разных способа
обьявлений переменных типа Colojtab」.
*/一 ~
font_colors_save font_colorsXTYPE:
--Переменная дпя хранения списка цветов для рисования '
paint_mixture Color_array_t;
/* Как и для индексных таблиц Oracle?, можно определить
11 табличный тип данных прямо здесь, в разделе объявлений...
*/
ТУРЕ Number」 IS TABLE OF NUMBER:
/*... и тогда можно будет использовать новый тип в объявлении
11 локальной переменной. В следующей строке переменная
11 объявляется и инициализируется одновременно. Обратите
11 внимание на использование конструктора
11 Numbet_t(значение, значение, ...). располокенного справа от оператора присваивания
*/-
my_fаVor11e_numbers Number」尸 Number_t(42. 65536);
/* Можно также сослаться на тип данных Color_tab_t из
11 споваря данных. В следующей строке обьявляется локальная
11 переменная Tny_favorite_colors, которая инициализируется
11 двумя элементами с помощью предоставляемого Oracle
11 конструктора.
*/
my_favorite_colors Color_tab_t :« Color_tab_t(1 PURPLE'. 'GREEN');
eno;_ _~ ~
Этот код демонстрирует принцип использования конструкторов типов — специальных функций, которые Oracle автоматически предоставляет для каждого созданного программистом типа. Конструктор служит для инициализации (или заполнения) переменной соответствующего типа. Его имя совпадает с именем типа, а аргументами являются значения разделенного запятыми списка элементов. Подробнее об этом рассказывается в разделе «Инициализация переменных-коллекций э-.
Использование коллекций
В следующих разделах описываются особенности объявления и применения коллекций в различных фрагментах программ PL/SQL. Поскольку типы коллекций можно определять в базе данных (лишь вложенные таблицы и массивы VARRAY), эти структуры можно задействовать не только в программах PL/SQL, но и в таблицах и объектных типах.
Коллекции как компоненты записи
Коллекции могут использоваться в записях наряду с другими типами данных. В состав записи может входить одна или несколько таких структур, включая массивы VARRAY, вложенные таблицы и ассоциативные массивы. Например:
DECLARE
TYPE toy—recj; IS RECORD ( manufacturer INTEGER. sh1pping_weight_kg NUMBER, domestic_colors Color_array_t- international_colors Color__tab_t ):--
Тип данных RECORD нельзя использовать в базе данных — он предназначен только для программ PL/SQL. Однако в базе данных его нетрудно заменить объектным типом. В состав объектного типа также входит набор атрибутов, которые могут быть коллекциями (вложенными таблицами или массивами VARRAY). А элементы коллекций, в свою очередь, могут быть объектами. Подробнее об объектных типах рассказывается в главе 21.
Коллекции в качестве параметров программы
Коллекции могут использоваться как параметры программных модулей. Однако в этом случае невозможно получить пользовательский тип данных, определенный внутри модуля. Чтобы использовать этот тип данных, его необходимо объявить вне модуля — либо с помощью оператора CREATE TYPE, либо с помощью общедоступного объявления в пакете. Следующая функция объединяет два набора цветов, заданных входными параметрами типа color_type_t. Значение выходного параметра представляет собой результат объединения наборов цветов, заданных двумя входными параметрами. Полная реализация этой функции имеется в файле make_colors_superset.sp на узле издательства O'Reilly.
CREATE PROCEDURE make_colors_superset Cfirst_colors IN Color_tab_t, second_colors IN Color tab t, superset DUT Color tab t)
Вызвать ее можно следующим образом:
DECLARE
my_color$ Color_tab_t;
your_colors Color_tab_t;
our^colors Color_tab_t:
8EGIN -
fnake_colors_superset ( my_colors.
your_colors. our_colors
END:
За дополнительной информацией по этой теме вам следует обратиться к разделу данной главы «Передача ассоциативных массивов в качестве параметров».
Коллекции как типы данных для значений, возвращаемых функцией
Ниже приведен пример функции, возвращающей значение типа color_type_t- Этот же тип имеет и локальная переменная данной функции. В отношении возвращаемых функциями значений действует то же правило, что и в отношении их параметров: пользовательские типы данных должны быть определены вне модуля.
CREATE FUNCTION true_colors Cwhose_id IN NUMBER) RETURN Cclor_tab_t
AS-"
colors Color tab
BEGIN一 ~
SELECT favorite_colors INTO l_colors
FROM personaTi ty_inventory
WHERE personjd = whose_1d:
RETURN l_colars:
EXCEPTION ~
WHEN NO_DATA__FOUND
THEN
RETURN NULL:
END:
Этот пример демонстрирует давно назревшую и наконец-то реализованную возможность возврата сложных структур данных. Чуть позже мы еще вернемся к нему.
Вы наверняка спросите, как же эта функция используется в программе PL/SQL. Поскольку она способна заменить переменную типа color_type_t, то вы можете либо присвоить возвращаемое ею значение переменной типа коллекции, 厶ибо присвоить переменной (тип которой совместим с типом элементов коллекции) один из элементов возвращаемой коллекции.
Первый способ весьма прост. Однако обратите внимание, что это один их тех случаев, когда переменную коллекции не обязательно инициализировать явно: declare
color_array Color_tab_t:
BEGIN -- -
color_array := true_colors (8041):
END; 一一
В соответствии со вторым способом сразу после вызова функции необходимо задать индекс требуемого элемента коллекции. Общий синтаксис оператора присваивания в данном случае имеет такой вид:
переменная_типаулемеига := функция () (индекс);
Вот пример вызова функции true_colors:■
DECLARE
one_of_niy_favor1te_col ors VARCHAR2C30);
BEGIN - - --
one_of_my_favoritejcolors 尸 true_colors (whose_1d=> 8041)(1):
END; ~~-~
Правда, этот код имеет один существенный недостаток: если в таблице базы данных не окажется записи, в столбце person_1d которой содержится значение 8041, то в ответ на попытку считать первый элемент результирующей коллекции будет инициировано исключение COLLECTION_IS_NULL. Его необходимо перехватить и обработать в соответствии с требованиями приложения.
В предыдущем примере передача параметров функции осуществлялась в соответствии с их именами (whose_id=>). Этот синтаксис делает код более читабельным, но вообще-то он не является обязательным. Подробнее данный вопрос освещается в главе 16.
Коллекции как «столбцы» таблицы базы данных
Вложенные таблицы и структуры VARRAY позволяют хранить в столбце таблицы базы данных неатомарные значения и записывать или считывать их с помощью одной команды. Например, в используемой в некотором подразделении таблице employees в одном из столбцов может храниться информация о датах рождения членов семьи сотрудника (табл. 11.1),
Таблица 11.1. Хранение списка дат в виде коллекции в столбце таблицы сотрудников
Поле Xd NUMBER
Поле Name (VARCHAR2)
Поле Dependents_ages (Dependent_Urthdate_t)
10010
Zaphod Beeblebrox
12-JAN-1763
4-JUL-1977
22-MAR-2021
10020
Molly Squiggly
15-NOV-1968
15-NOV-1968
10030
Joseph Josephs .
10040
Gepheus Usrbln
27-JUN-1995
24-AUG4996
19-JUN4997
10050
Deirde Quattlebaum -
21-SEP-1997
Создать такую таблицу довольно просто. Сначала нужно определить тип коллекции:
CREATE TYPE Dependent__birthdate_t AS VARRAY(IO) OF DATE;
который затем можно использовать в определении таблицы:
CREATE TABLE employees (
Id NUMBER.
Name(VARCHAR2).
...другие столбцы ...,
Dependents_ages Dependent_birthdate_t
Заполнить таблицу можно с помощью инструкции INSERT, в которой стандартный конструктор преобразует список дат в значения данных нужного типа:
INSERT INTO employees VALUES (42, 'Zaphod Beeblebrox'
Dependent_birthdate_t('12-JAN-1763', '4-JUL-1977'22-MAR-202r)):
Теперь рассмотрим пример использования столбца типа вложенной таблицы. Создавая внешнюю таблицу personal 1 ty_1 nventory, Oracle необходимо указать имя таблицы для записи данных столбца типа вложенной таблицы:
CREATE TABLE personalityjnventory (
person_id NUMBER,
favorite_colors Color_tab_t,
date_tested DATE, test_results BLOB)
NESTED TABLE favorite_colors STORE AS favorite_colors_st;
Предложение NESTED TABLE.. .STORE AS сообщает Oracle, что таблица для хранения данных столбца favorite_colors (вспомогательная таблица) должна иметь имя favorite_colors_st. Эта таблица будет храниться отдельно от остальных данных таблицы personal ity_i nventory. Ограничений на ее размер не существует.
Данные вспомогательной таблицы нельзя обрабатывать непосредственно. Попытка прямого считывания или записи в нее информации приведет к возникновению ошибки. Данные этой таблицы доступны только посредством ссылки на внепшюю таблицу. Далее, в разделе «Псевдофункции коллекций», приводится несколько примеров того, как это делается. Физические атрибуты таблицы хранения тоже нельзя задать напрямую — они наследуются от «самой внешней»- таблицы.
Главное различие между вложенными таблицами и структурами VARRAY заключается в том, как они используются в качестве типов данных столбцов таблиц. Несмотря на то что массив VARRAY, подобно вложенной таблице, позволяет сохранять в одном столбце множество значений, для него необходимо указать максимальную длину массива, который будет храниться в таблице вместе с остальными данными. Поэтому разработчики компании Oracle утверждают, что столбцы типа VARRAY предназначены для «маленьких» массивов, а вложенные таблицы 一 для больших.
Коллекции как атрибуты объектного типа
В следующем примере моделируются данные, используемые для определения характеристик автомобиля. Каждый объект типа auto_spec_t содержит набор цветов, в которые может быть выкрашена машина.
CREATE TYPE Auto_spec_t AS OBJECT (
make VARCHAR2(30)7
model VARCHAR2C30).
available_colors Color_tab_t
Поскольку объектный тип не требует места для хранения данных, в его определении не нужно указывать имя дополнительной таблицы. Мы зададим его позже, когда будем создавать таблицу со столбцом этого типа:
CREATE TABLE auto_specs OF Auto_spec_t
NESTED TABLE available_colors STORE AS available_co1ors_st:
Приведенная выше инструкция требует пояснений. Создавая таблицу объектов, Oracle просматривает объявление объектного типа, чтобы определить, какие столбцы должна содержать такая таблица. Обнаружив, что один из столбцов, а точнее available_colors, является вложенной таблицей, Oracle создает отдельную таблицу для хранения его данных. Предложение
...NESTED TABLE ava1lable_colors STORE AS available_co1ors_st
сообщает Oracle, что вспомогательная таблица для хранения данных столбца avai- lable_colors должна называться available_colors_st. Подробнее об объектных типах Oracle рассказывается в главе 21.
Встроенные методы коллекций
PL/SQL предоставляет для создаваемых вами коллекций множество встроенных функций и процедур, называемых методами коллекций. С их помощью можно получать информацию о содержимом коллекции и изменять таковое.
Метод (функция или процедура)
Описание
Функция COUNT
Возвращает текущее количество [COUNT] элементов в коллекции
Процедура DELETE
Удаляет из вложенной таблицы один или несколько элементов.
Уменьшает значение, возвращаемое функцией COUNT, если заданные элементы еще не удалены. Может использоваться и со структурами
VARRAY, но только для удаления всего их содержимого
Функция EXISTS
Возвращает значение TRUE или FALSE, определяющее; существует ли в коллекции заданный элемент
Процедура EXTEND
Увеличивает количество элементов в коллекции, а также значения, возвращаемые функцией COUNT. Не применима по отношению к ассоциативным массивам
Функции FIRST, LAST
Возвращают индексы первого (FIRST) и последнего (LAST) элементов в коллекции
Функция LIMIT
Возвращает максимальное количество элементов в массиве VARRAY
Функции PRIOR, NEXT
Возвращают индексы элементов, предшествующих заданному (PRIOR) и следующему за ним (NEXT)
Функция TRIM
Удаляет элементы начиная с конца коллекции. Уменьшает значение, возвращаемое функцией COUNT, если элементы еще не удалены
Все эти конструкции называются методами, потому что синтаксис их вызова отличается от синтаксиса вызова обычных процедур и функций. Это типичный
синтаксис вызова методов, используемый в объектно-ориентированных языках программирования, и в частности в C++.
Рассмотрим синтаксис вызова на примере метода LAST. Функция LAST возвращает наибольший индекс элемента ассоциативного массива. Стандартный вызов этой функции мог бы быть таким:
1f LAST (carr^any_table) > 10 THEN... /* Неверный синтаксис */
Как видите, в качестве аргумента ей передается ассоциативный массив. Но поскольку функция LAST является методом, она вызывается как《член» объекта, в нашем примере — ассоциативного массива. Приведем правильный синтаксис ее вызова:
if company__table.LAST > IQ THEN... /* Правильный синтаксис */
В общем случае синтаксис вызова методов ассоциативного массива, если речь идет об операции, не требующей передачи аргументов, таков:
имя_таблицы.операция
а если об операции, аргументами которой являются индексы элементов, — таков:
имя_гдблицы.операция(индекс [. индекс])
Например, следующая инструкция возвращает значение TRUE, если в ассоциативном массиве company_tab определена запись 15:
company_tab.EXISTS(15)
Методы коллекций не доступны из SQL, их можно использовать только в программах PL/SQL.
Метод COUNT
Метод COUNT возвращает количество элементов в ассоциативном массиве, вложенной таблице или массиве VARRAY, Метод не подсчитывает элементы, удаленные из коллекции с помощью метода DELETE или TRIM.
Синтаксис вызова метода COUNT следующий:■
function count return plsjnteger
Ниже приведен пример, в котором объявляется и инициализируется коллекция volunteer_list. Перед выполнением с ней необходимых действий код проверяет, содержит ли коллекция хотя бы один элемент:
declare
volunteerjist volunteer list аг := volunteer list ar('Steven');
BEGIN -~~
IF volunteerj1st.COUNT > 0
THEN' ~
assign tasks Cvolunteer_11st):.
END IF:-"
end;
Ограничения по применению
Если метод COUNT вызван для инициализированной коллекции, не содержащей ни одного элемента, он возвращает значение 0. Это же значение он возвращает и при наличии пустого ассоциативного массива.
Возможные исключения
Если метод COUNT вызван для неинициализированной вложенной таблицы или структуры VARRAY, он генерирует исключение COLLECTION_IS_WULL. Учтите, что в случае ассоциативных массивов такое исключение не генерируется, поскольку они не требуют инициализации.
Метод DELETE
Метод DELETE предназначен для удаления одного, нескольких или всех элементов ассоциативного массива, вложенной таблицы или массива VARRAY. Будучи вызванным без аргументов, он удаляет все элементы коллекции. Вызов DELETE⑴ удаляет i-近 элемент вложенной таблицы или ассоциативного массива. А вызов DELE- ТЕС1. j) удаляет все элементы с индексами от 1 до j включительно. При вызове с параметрами метод резервирует место, занимавшееся «удаленным» элементом, и позднее этому элементу можно присвоить новое значение.
Фактически PL/SQL освобождает память лишь при условии, что программа удаляет количество элементов, достаточное для освобождения целой страницы памяти. Если же метод DELETE вызывается без параметров и очищает всю коллекцию, память освобождается немедленно. В любом случае эта операция выполняется автоматически и не требует участия программиста.
ПРИМЕЧАНИЕ
Метод DELETE применительно к массивам VARRAY может вызываться только без аргументов. Иными словами, с помощью указанного метода из этой структуры нельзя удалять отдельные элементы, поскольку в таком случае она станет разреженной, что совершенно недопустимо. Единственный 'способ удалить из VARRAY один или несколько элементов — воспользоваться методом TRIM, предназначенным для удаления группы расположенных рядом элементов начиная с конца коллекции.
Синтаксис перегруженного метода DELETE таков:
PROCEDURE DELETE
PROCEDURE DELETE (i [BINARyjNTEGER | VARCHAR2(SIZE_LIMIT)])
PROCEDURE DELETE Cl [BINARY二INTEGER | VARCHAR2(SIZE~LIMIT)]. j [BINARY_INTEGER |
VARCHAR2CSIZE_LIMIB])"
Следующая процедура удаляет из коллекпии все элементы, кроме последнего. В ней используются четыре метода: FIRST — для получения номера первого удаляемого элемента; LAST — для получения номера последнего удаляемого элемента; PRIOR — для определения номера предпоследнего элемента; DELETE — для удаления всех элементов, кроме последнего.
CREATE PROCEDURE keepjast (thejist IN OUT List t)
AS"-
first_elt pLs_INTEGER :- thejist.FIRST:
next_tojast_elt PLS_INTEGER~;= thej 1 st. PRIORCthe_l i st. LAST);
BEGIN~-
the_list.DELETEff 1 rst_elt. next_to last_eH):
END: ~-~ ~
Приведем еще несколько примеров удаления элементов из различных таблиц. Сначала мы удаляем все элементы, кроме последнего, из таблицы names:
names.DELETE:
затем элемент 77 из таблицы globals:
globals.DELETE (77):
и наконец, все элементы начиная с элемента, имеющего индекс -15 000, и заканчивая элементом с индексом 0, из таблицы temp_readings:
temp_reacl1 ngs.DELETEC-15000. 0);
Ограничения по применению
Если значения индексов i и/или j указывают на несуществующие элементы, ме- 1од DELETE пытается «сделать наилучшее» и не генерирует исключение. Например, если таблица содержит три элемента, то вызов метода 口ELETE(-5, 1) удалит первый из них, а вызов 口ELETE(-5) не выполнит никаких действий.
Возможные исключения
Вызов метода DELETE для неинициализированной вложенной таблицы или массива VARRAY инициирует исключение COLLECTION_IS_NULL.
Метод EXISTS
Данный метод используется с вложенными таблицами, ассоциативными массивами и массивами VARRAY для определения наличия в коллекции заданного элемента. Если таковой имеется, он возвращает значение TRUE, а если отсутствует — значение FALSE. Значение NULL метод не возвращает ни при каких условиях. Кроме того, метод EXISTS возвращает FALSE и в том случае, если заданный элемент был удален из коллекции с помощью метода TRIM или DELETE.
Синтаксис этого метода:
FUNCTION EXISTS (i [BINARY」NTEGER | VARCHAR2(SIZE_LIMIT)]) RETURN BOOLEAN
Ниже приведен блок кода, который проверяет, присутствует ли заданный элемент в коллекции, и при положительном ответе присваивает ему значение NULL:
DECLARE
rnyjist Color_tab_t :- SlDr_tdb_t():
element INTEGER := 1:
BEGIN
IF щу_1ist.EXISTS(element)
THEN ~
щу_11st[element) :- NULL:
END IF:
END;
Ограничения по применению
Если метод EXISTS вызывается для неинициализированной (содержащей единственное значение NULL) вложенной таблицы или структуры VARRAY либо для инициализированной коллекции, не содержащей ни одного элемента, он просто возвращает значение FALSE. Поэтому его можно вызвать без предварительного обращения к методу COUNT, не рискуя получить сообщение об ошибке.
Возможные исключения
Если i не является целым числом и не может быть в него преобразовано, метод EXISTS инициирует исключение VALUE_ERROR. Это исключение может быть вызвано любыми методами коллекции, которые имеют входные параметры.
Метод EXTEND
Для того чтобы добавить элемент во вложенную таблицу или массив VARRAY, нужно сначала выделить для него область памяти. Соответствующая операция, не зависящая от операции присваивания элементу значения, выполняется с помощью метода EXTEND. Однако помните, что указанный метод не применяется к ассоциативным массивам.
Метод EXTEND, как уже было сказано, добавляет элементы в коллекцию. Будучи вызванным без аргументов, он добавляет один аргумент, равный NULL. Вызов EXTEND (п) добавляет и элементов NULL, а вызов EXTEND(n, i)—舞 элементов и всем им присваивает значение z-ro элемента. Последняя форма метода применяется к коллекциям, для элементов которых задано ограничение NOT NULL.
Синтаксис перегруженного метода EXTEND приведен ниже:
PROCEDURE EXTEND (п PLS_INTEGER := 1)
PROCEDURE EXTEND (n PLSJNTEGER. i PL5_INTEGER)
В следующем примере процедура push добавляет в список один элемент и присваивает ему новое значение:
CREATE PROCEDURE push (thejlst IN OUT L1st_t, nsw_value IN VARCHAR2)
AS~~"
BEGIN
thej1st.EXTEND;
the_l 1 st (thej 15 t 丄 AST) := new_val ue:
END: ~~-
Далее мы рассмотрим фрагмент кода, в котором с помощью метода EXTEND формируется коллекция, состоящая из 10 элементов с одинаковыми значениями. Для этого в коллекцию сначала добавляется один элемент и ему явно присваивается нужное значение. При повторном вызове метода EXTEND в коллекцию добавляется еще 9 элементов, которым присваивается значение первого элемента коллекции.
CREATE PROCEDURE push_ten Cthejist IN OUT List_t, new_value IN VARCHAR2)
AS~~-
l_copyfroni PLS_INTECER;
BEGIN~
thejlst. EXTEND;
l_copyfrom := thejlst.LAST;
the_list(l_cooyfroni)尸 new_value:
thejlst.EXTEND (9, l_copyfrom):
END;一
Ограничения по применению
Если в результате вызова метода TRIM или DELETE из коллекции было удалено несколько последних элементов, метод EXTEND «перепрыгнете через них и присвоит новому элементу индекс следующего за ними элемента. Если параметр п метода имеет значение NULL, метод не выполняет никаких действий.
Возможные исключения
Если метод EXTEND вызван для неинициализированной вложенной таблицы или структуры VARRAY, инициируется исключение COLLECTION_IS_NULL. Попытка добавить в массив VARRAY элементы, индекс которых превышает максимальный индекс массива в его объявлении, инициирует исключение SUBSCRIPT_BEYOND_LIMIT.
Методы FIRST и LAST
Методы FIRST и LAST возвращают соответственно наименьший и наибольший индексы элементов вложенной таблицы, ассоциативного массива или массива VAR- RAY. Приведем их синтаксис:
FUNCTION first return pls_integer function last return plsjnteger
В следующем примере производится полный просмотр коллекции:
FOR indx IN holidays.FIRST .. holidays.LAST '
LOOP
send_everyone_home (indx):
END LOOP: 一
Запомните, что такой цикл будет выполнен корректно (то есть не станет причиной исключения NO_DATA_FOUND) лишь при условии, что коллекция является плотной.
А теперь рассмотрим пример, в котором метод LAST применяется для добавления элементов в ассоциативный массив companies. Данные в массив копируются из базы данных в цикле FOR с курсором. Перед началом заполнения массива коллекция companies пуста и метод LAST возвращает NULL. Поэтому в выражении для вычисления индекса массива используется функция NVL, которая возвращает значение второго аргумента, если ее первый аргумент пуст. Таким образом, индекс первого добавляемого в коллекцию элемента становится равным единице.
FOR company rec IN company_cur
LOOP 一~
companies (NVL (companies.LAST. 0) + 1).companyjd 尸
company^rec.company_1d;
ENO LOOP; ~~
Ограничения по применению
Если в вызовах методов FIRST и LAST указать неинициализированную коллекцию, то есть такую, которая не содержит ни одного элемента, оба метода возвратят значение NULL. Для массива VARRAY, в котором всегда имеется хотя бы один элемент, метод FIRST постоянно возвращает значение 1, а метод LAST — то же значение, что и метод COUNT.
Возможные исключения
При условии, что методы FIRST и LAST вызываются для неинициализированной вложенной таблицы или массива VARRAY, инициируется исключение CCLLECTI- ON」S_NULL
Метод LIMIT
Метод LIMIT возвращает максимальное количество элементов, которое можно определить в массиве VARRAY. В случае вложенной таблицы или ассоциативного массива он возвращает NULL. Синтаксис этого метода таков:
function limit return pls integer
В следующем примере добавлению нового элемента в конец массива VARRAY предшествует проверка того, есть ли в нем еще место;
IF ту_1 ist.LAST < 吸list丄IMIT
THEN —
myJist.EXTEND: END IF;
Ограничения по применению
Особых ограничений по использованию метода LIMIT не существует.
Возможные исключения
Вызов метода LIMIT для неинициализированной вложенной таблицы или массива VARRAY генерирует исключение COLLECTION」工NULL.
Методы PRIOR и NEXT
Методы PRIOR и NEXT используются для перемещения по коллекциям — вложенным таблицам, ассоциативным массивам и массивам VARRAY. Вызов методов производится следующим образом:
FUNCTION PRIOR (f〔BINARY」NTEGER | VARCHAR2(SIZE_LIMIT)]) RETURN [BINARYJNTEGER |
VARCHAR2(SIZE_LIM1T)]~
FUNCTION NEXT (? [BINARY JNTEGER | VARCHAR2(SIZE_LIMIT)J) RETURN [BINARY_INTEGER |
VARCHAR2(SIZE_LIMIT)] --
Обоим методам передается индекс элемента в коллекции. Метод PRIOR возвращает индекс предыдущего элемента коллекции, а метод NEXT — следующего. Приведенная ниже функция возвращает сумму чисел, хранящихся в коллекции Li st_t-
CREATE FUNCTION compute_sLffn (thejist IN List」)RETURN NUMBER AS
row_index PLSJNTECER thejist.FIRST:
total NUMBER 7= 0;~
BEGIN
LOOP
EXIT WHEN row_index IS NULL;
total total" + the_11st(row_ind6x);
row J ndex := thejist. NEXT(row_1ndex):
END LOOP:~~
RETURN total:
END:
Данная функция сканирует всю коллекцию, последовательно перемещая указатель от первого элемента к последнему с помощью метода NEXT. Воспользовавшись методом PRIOR, функцию можно переписать так, чтобы переход производился в направлении от конца коллекции к ее началу:
CREATE FUNCTION compute_sum (thejist IN List_t) RETURN NUMBER
AS~
rowjndex PLS—INTEGER := thejist.LAST;
total NUMBER :- 0;-
BEGIN
LOOP
EXIT WHEN rowjndex IS NULL;
total := total + the_list(rowjndex):
rowjndex :匸 the_Hst.PRIOR(rowJndex):
END LOOP:
RETURN total;
END:
Правда, сейчас направление перемещения для нас не имеет никакого значения, поскольку нужно лишь просмотреть все элементы коллекции. Но в большинстве случаев оно является очень важным условием.
Ограничения по применению
Методы PRIOR и NEXT для инициализированной коллекции, не содержащей ни одного элемента, возвращают значение NULL Это же значение метод NEXT возвращает и в том случае, если значение i больше или равно количеству элементов коллекции (COUNT), а метод PRIOR - при условии, что i меньше или равно индексу первого элемента (FIRST).
ПРИМЕЧАНИЕ В настоящее время, если коллекция не пуста, а параметр i больше или равен значению COUNT, метод PRIOR возвращает в качестве значения индекс последнего элемента коллекции, а если i меньше или равно индексу первого элемента, метод NEXT возвращает значение FIRST. Однако не известно, сохранится ли такое их поведение в будущих версиях Oracle.
Метод TRIM
Метод TRIM удаляет п последних элементов коллекции - вложенной таблицы или массива VARRAY. Если в вызове метода аргументы не указаны, он удалит только один элемент. Имейте в виду, что при совместном использовании методов TRIM и DELETE возможна накладка: если заданный в вызове метода TRIM элемент был уже удален с помощью метода DELETE, метод TRIM сделает это еще раз, поэтому количество удаленных элементов окажется меныиим, чем вы рассчитывали.
ВНИМАНИЕ
Попытка вызвать метод TRIM для обработки ассоциа-пивного массива прнведь к ошибке времени компиляции.
Синтаксис метода TRIM следующий:
PROCEDURE TRIM 5 PLSJNTEGER := 1)
Приведенная далее функция извлекает из списка последнее значение и возвращает его вызывающему блоку. Данная операция, называемая выталкиванием, реализуется как выборка значения с последующим усечением коллекции на один элемент:
CREATE FUNCTION pop (thejlst IN OUT List t) RETURN VARCHAR2
AS~"
■ Lvalue VARCHAR2(30);
BEGIN
IF theJ 1st.COUNT >= 1
THEN
/* Сохраняем значение последнего элемента коллекции,
11 которое будет возвращено функцией
*/
l_vslue theJist(theJist.LAST):,
the list.TRIM:"
END IF:
RETURN Lvalue:
END;
Ограничения по применению
Если передаваемое методу TRIM значение л равно NULL, метод не выполнит никаких действий.
Возможные исключения
При попытке удалить больше элементов, чем имеется в коллекции, инициируется исключение SUBSCRIPT_BEYOND_COUNT. Если метод TRIM вызывается для неинициализированной вложенной таблицы или массива VARRAY, инициируется исключение COLLECTION_ISJULL..
ВНИМАНИЕ
Вьвывая методы TRIM и DELETE для одной и той же коллекции, можно получить неожиданные результаты. Как вы думаете, на сколько элементов меньше станет в коллекции, если последний элемент удалить с помощью метода DELETE, а затем вызвать метод TRIM, указав то же значение параметра? Если вы считаете, что на два, то ошибаетесь — на самом деле оба метода удалят один и тот же элемент. Поэтому, чтобы избежать накладок, компания Oracle рекомендует не использовать оба эти метода при работе с одной коллекцией.
Работа с коллекциями
Итак, вы уже знаете, как определяются типы коллекций в базе данных и в программах PL/SQL и как объявляются коллекции на основе этих типов. Далее речь пойдет о программном коде, необходимом для работы с готовыми коллекциями. Но прежде чем приступить к его написанию, нужно ответить на три вопроса: О Как правильно инициализировать переменную коллекции?
О Как присвоить элементам коллекции значения, не вызвав исключений?
О Как добавлять и удалять элементы коллекции?
Кроме того, для полного освоения методики работы с коллекциями нужно понять, как сохранять и извлекать из них наборы данных. Об этом и о других интересных операциях с коллекциями рассказывается в разделе, посвященном псевдофункциям коллекций.
Инициализация переменных-коллекций
Ассоциативные массивы не требуют явной инициализации. Достаточно просто объявить такой массив, и он автоматически инициал из ируется «пустыми» значениями. После этого можно сразу присваивать значения любым его элементам. В качестве индексов коллекции могут выступать практически любые положительные и отрицательные значения. Программа может даже назначать элементам ассоциативного массива произвольные индексы, пропуская огромные диапазоны значений без каких-либо потерь памяти.
Выделение памяти для вложенных таблиц и массивов VARRAY производится иначе, чем для ассоциативных массивов. Прежде всего, если не инициализировать коллекцию одного из этих двух типов, ей автоматически будет присвоено
значение NULL, и любая попытка получить или присвоить значение содержащемуся в ней элементу приведет к ошибке времени выполнения. Например:
DECLARE
/* Переменная cool_colors не инициализирована при объявлении,
поэтому она содержит значение NULL.
★/
coo1_colors Color_tab_t;
BEGIN ~
IF cool_colors IS NULL THEN -- результат проверки равен TRUE
IF cool_colors(l) IS NULL THEN -- будет сгенерировано исключение
cool_colors(l) :- 'BLUE':-- будет сгенерировано исключение
Таким образом, использованию коллекции должна предшествовать ее инициализация. Выполнить таковую можно одним из трех способов:
О явно, с помощью конструктора;
О неявно, путем непосредственного присваивания ей содержимого другой переменной-коллекции;
Q неявно, путем выборки записей из базы данных.
При этом не важно, какое количество элементов вы запишете в коллекцию. Их может не быть вообще, а может быть один или несколько, и впоследствии всегда можно будет добавить еще. И пусть вас не смущает тот факт, что при объявлении массива VARRAY указывается количество его элементов — это максимально допустимое, но отнюдь не обязательное значение. Тот факт, что вы задали максимальное количество элементов массива, еще не значит, что при инициализапии следует поместить в него именно такое их количество.
Явная инициализация с помощью конструктора
В одном из предыдущих примеров мы объявили две переменные:
my_favorite__colors Color_tab_t := Color_tab_t('PURPLE'. 'GREEN');
my_favorite_numbers Number』:=Nunber_t^2< 65536):
Здесь Color_tab_tO — это функция-конструктор, предоставляемая Oracle для создании типа коллекции Color_tab_t. Она принимает произвольное количество аргументов соответствующего типа данных, а именно типа VARCHAR2(30), поскольку Исходное его определение было таким:
CREATE TABLE Color_tab_t AS TABLE OF VARCHAR2(30);
При инициализации переменной Oracle выделяет память для хранения значений* заданных посредством аргументов конструктора, и таким образом создаются и заполняются «ячейки» для элементов коллекции.
Для того чтобы исправить приведенный выше код, достаточно присвоить переменной корректное змачение:
DECLARE
cool^colors Color_tab_t :- Color_tab_t('VIOLET'); -- инициализация
BEGIN~~ 一
IF cool_colors(l) IS NULL THEN -- Теперь все в порядке!
А как вы думаете, что происходит при инициализации в данном случае:
working_colors Elor_tab_t Color_tab_t()
Так создается пустая коллекция. <Пустота> — это некое загадочное состояние коллекции, когда она не равна значению NULL, но и не содержит данных. Создав пустую коллекцию, ее нужно расширить с помощью метода EXTEND — лишь после этого в нее можно добавлять новые элементы. О методе EXTEND рассказывалось ранее в этой главе.
Неявная инициализация путем непосредственного присваивания
Если две переменные, объявленные как коллекции, имеют один и тот же тип данных, значение одной из них можно присвоить другой. И если переменная, которой присваивается значение, еще не была инвдиализирована, это будет сделано автоматически, в результате данной операции.
Рассмотрим пример, в котором переменной wedding_colors присваивается значение переменной earth_colors:
DECLARE
earth^colors Color_tab_t :- Colar3ab_t('BRICK', 'RUST'. 'DIRT');
wedding_colors Color_tab_t;
BEGIN -~ ~
wedding_colors :- earth_colors;
wedding_colors(3) :=• 'CANVAS':
END; -
Этот код инициализирует переменную wedding_colors и записывает в нее элементы переменной earth_colors. В результате мы получаем две независимые переменные с одинаковыми значениями, каждое из которых можно изменять, не влияя на другое; при изменении значения третьего элемента коллекции wed- ding_colors третий элемент коллекции earth_colors остается неизменным.
Учтите, что одной только совместимости типов данных переменных, объявленных в качестве коллекций, недостаточно для выполнения операции присваивания. Если вы объявите два типа коллекций с одинаковыми определениями, то получите два разных типа данных, причем переменные одного типа нельзя будет присвоить переменным другого типа.
Неявная инициализация путем выборки из базы данных
Если таблица базы данных содержит значения, объявленные как коллекции, существуют достаточно интересные способы перемещения этих данных между таблицей и PL/SQL-кодом. Как и при прямом присваивании, при выборке данных из таблицы и присваивании их переменной, объявленной как коллекция, с помощью инструкции FETCH или SELECT INTO выполняется автоматическая инициализация этой переменной. Таким образом, коллекции могут оказаться даже очень полезными!
В одном из предыдущих примеров мы уже показали, как с помощью одной команды считать из базы данных всю коллекцию. Рассмотрим эту операцию подробнее. Прежде всего создадим таблицу с коллекцией, в которую запишем несколько значений:
CREATE TABLE color_models (
model_type VARCHAR2(12).
colors Color tab t)
NESTED TABLE colors STORE AS color_model_colors_tab;
NSERT INTO color_niodels
VALUES ('RGB', Color_tab_t('RED'.'GREEN'.'BLUE'));
Теперь — самое главное. За один цикл обращения к базе данных мы считываем все значения из столбца colors заданной строки и помещаем их в локальную аеременную:
TECLARE
l_colors Colo「_tdb_t:
^EGIN…-
/* Выбираем все аложенные значения 3d один цикл.
II Это самое примечательное!
SELECT colors INTO l__colors FROM color_niodels
WHERE model_type - 'RGB';
№
Обратите внимание на несколько важных моментов.
Э При выборке значений из базы данных управление индексами коллекции l_colors осуществляется Oracle, а не программистом.
О Начальное значение индекса, присваиваемое Oracle, равно 1 (а не 0, как в некоторых других языках). Значение каждого последующего индекса на единицу больше значения предыдущего.
О Операция присваивания значений элементам коллекции требует инициализации локальной переменной, объявленной как коллекция. Мы не инициализировали переменную l_colors с помощью конструктора, но PL/SQL известно, как ее обрабатывать.
Содержимое коллекции можно легко изменить и с такой же легкостью записать информацию обратно в таблицу базы данных. Давайте, шутки ради, создадим следующую цветовую модель Fuschia-Green-Blue:
DECLARE
color_tab Color_tab t:
BEGIN -~ -
SELECT colors INTO color_tab FROM color_models
WHERE mode】_type - 'RGB';
FDR element IN 1..color_tab.COUNT'
LOOP~
IF color_tab(element) = 'RED'
THEN -
color_tab(element) := 'FUSCHIA';
END IF:
END LDOP:
/* Это самая интересная часть прим印а. Достаточно
II одной инструкции вставки - и вся вложенная таблица
11 отравляется обратно в базу данных, в таблицу color models.■
INSERT INTO color_niodels VALUES ('FGB'. color tab)-
Nd:-—
Интеграция массивов VARRAY
Возможно, кто-то спросит, реализована ли интеграция баз данных с PL/SQL и для массивов VARRAY? В общем-то, да, хотя имеется несколько особенностей такой интеграции.
Прежде всего, при записи содержимого вложенной таблицы в базу данных, а также при его считывании Oracle не всегда сохраняет порядок следования элементов таблицы. Это понятно, поскольку хорошо известно, что реляционные базы данных не заботятся о порядке строк. А вот при чтении и записи содержимого массивов VARRAY порядок элементов сохраняется.
Сохранение порядка следования элементов — очень полезная черта массива VARRAY, и очень часто порядок несет определенную информационную нагрузку. Если, предположим, нужно сохранить цвета в порядке их важности, их следует записать в столбец типа VARRAY. При выполнении каждой операции чтения вы будете получать элементы из этого столбца в той последовательности, в которой их записали. Если же придерживаться строго реляционной модели, потребуются два столбца: один для хранения целочисленных значений, которые определяют степень важности цветов, другой — для хранения их названий.
Указанное свойство массивов VARRAY открывает широкие возможности для написания новых интересных функций. Например, вы можете запрограммировать вставку дополнительного цвета в начало списка со сдвигом всех остальных элементов.
Еще одно отличие структур VARRAY от вложенных таблиц, связанное с интеграцией базы данных и PL/SQL, заключается в том, что некоторые инструкции SELECT для выборки вложенных таблиц нельзя использовать с массивами VARRAY *как есть» — они нуждаются в модификации. Подробнее об этом будет рассказано в разделе «Псевдофункции коллекций».
Присваивание значений элементам коллекции
Для присваивания значений элементам коллекции используется стандартный оператор присваивания PL/SQL:
countdown_testJ1 st (43) 尸'Internal pressure' ; company_names_table (last__name_row+10)尸 'Dohnstone Clingers':
Допускается и «агрегатное» присваивание всего содержимого коллекции другой коллекции того же типа:
DECLARE
TYPE name_table IS TABLE OF VARCHAR2C100) INDEX BY BINARYJNTEGER:
old_names namjtdble:
new_names name^table:
BEGIN --
/* Присваиваем значения строкам таблицы oljnames */
old_names(l) :- 'Smith' :
old_names(2) :- 'Harrison';■
/* Присваиваем значения строкам таблицы new_names */
new_names(lll) := 'Hanrahan';
new_nan)es(342) :- 'Blimey';
/* копируем значения из таблицы old_names в таблицу new_names */ ol d__names := new_names;
/* Следующий оператор генерирует исключение NO_DATA_FOUND */
OBMS_OUTPUT.PUT_LINE Cold_names (i));
END:
В результате выполнения оператора присваивания соддзжимое одной коллекции полностью замещается содержимым другой коллекции. В этом примере присваиванию предшествует определение первого и второго элементов коллекции ol d_names. Однако после выполнения присваивания в коллекции оказываются заполненными только элементы 111 и 342. Поэтому при попытке обратиться к строке 1 PL/SQL инициирует исключение NO_DATA_FOUND.
Ассоциативные массивы позволяют присваивать фиксированные значения произвольным элементам коллекции (с индексами от -231 + 1 до 2明-1). В результате выполнения такой операции автоматически создается соответствующий элемент коллекции с заданным значением.
Однако ни вложенные таблицы, ни массивы VARRAY, в отличие от ассоциативных массивов, не позволяют присваивать значения произвольным элементам коллекции. В этих структурах индексы назначаются не программистом, а PL/SQL, и значения их увеличиваются последовательно. Это означает, что п инициализированных элементов будут иметь индексы от 1 до п, и вы пе сможете присвоить значения никаким другим элементам. Кроме того, прежде чем присвоить значение строке вложенной таблицы или массива VARRAY, нужно расширить коллекцию с помощью метода EXTEND, о котором рассказывается далее в этой главе.
Ссылки на не определенный элемент
Конкретный элемент входит в коллекцию при соблюдении одного из двух следующих условий:
О коллекция является ассоциативным массивом, и этому элементу присвоено значение (таким образом, элемент определен);
О коллекция является вложенной таблицей или массивом VARRAY, и эта структура явно расширена путем добавления нового элемента (созданному таким способом элементу можно присваивать значения).
При попытке обратиться к несуществующему элементу (для чтения или записи в него значения) PL/SQL инициирует одно из трех возможных исключений. Проиллюстрируем сказанное следующим примером:
CREATE TYPE str1ngs_nt IS TABLE OF VARCHAR2 (100):
DECLARE
TYPE stringsjbt IS TABLE OF VARCHAR2 (100)
INDEX BY BINARY INTEGER;
-
strings strings_nt := strings_nt ():
ibt_strings strlngsjbt:
BEGIN -"
BEGIN
IF 1bt_str1ngs(2) = 'a' THEN NULL; END IF;
EXCEPTION
11
WHEN OTHERS
12
THEN DBMS_OUTPUT.pirtJine ('a.'|| SQLERRM);
13
END;
14
BEGIN
15
strings.EXTEND; strings ⑵:-'b';
16
EXCEPTION
17
WHEN OTHERS
18
THEN DBMS OOTPlH.put line ('b.'|| SQLERRM);
19
END:
20
21
BEGIN■
22
strings (0)'c';
23
EXCEPTION
24
WHEN OTHERS
25
THEN DBMS OUTPUT.put line ('c.'|| SQLERRM):
26
END;
27
END:
В результате выполнения данного сценария получаем:
ORA-01403: no data found
ORA-06533; Subscript beyond count'
ORA-06532: Subscript outside of limit■
Краткое объяснение приведенного выше кода дается в следующей таблице.
Строка
Описание
9
Попытка считать содержимое второго элемента ассоциативного массива, в котором не определен ни один элемент (он объявлен в строке 6). Поэтому PL/SQL инициирует исключение NO_DATA_FOUND
15
Расширение аложенной таблицы на один элемент. Операция присваивания значения второму элементу коллекции некорректна, поскольку расширение выполнено только один раз, индекс 2 ошибочен
22
Попытка присвоить значение элементу вложенной твблицы с нулевым индексом, что по определению невозможно. Элементов с нулевым индексом ни во аложенных таблицах, ни в массивах VARRAY нет (но они могут присутствовать в ассоциативных массивах)
Работа с коллекциями составных элементов
Возможности коллекций, впервые введенные в PL/SQL версии 2 (Огас1е7), были весьма ограниченными. Например, ассоциативные массивы заполнялись только скалярными значениями (датами, числами, строками и т. д.), что очень сужало сферу применения этих полезных структур, в то время называвшихся, как уже было сказано, таблицами PL/SQL.
Но это было давно, а теперь коллекции поддерживают гораздо более сложные структуры данных:
Q коллекции записей (Oracle 7.3.4 и выше) — используются при условии, что записи не содержат в качестве полей другие записи или коллекции;
Q коллекции объектов (Огас1е8 и выше) — создаются в виде коллекций экземпляров объектных типов, записей, LOB, типов данных XML и т. д.;
О коллекции коллекций (Oracle9i и выше) — представляют собой многоуровневые коллекции, в том числе коллекции коллекций и коллекции типов данных.
Рассмотрим примеры коллекций всех типов.
Коллекции записей
Для создания коллекции записей в предложении TABLE OF ее объявления задается тип записи (либо со спецификацией XROWTYPE, либо тип, задаваемый программистом). Этот способ применим только к типам коллекций, используемым в программах PL/SQL. Вложенные таблицы и типы VARRAY, определенные в базе данных, не могут содержать XROWTYPE-ссылок на записи.
Вот пример определения коллекции записей, основанной на пользовательском типе записей:
CREATE OR REPLACE PACKAGE compensation_pkg
IS
TYPE reward rt IS RECORD (
ПЛ1 VARCHAR2C2000).-
sal NUMBER.
comm NUMBER
TYPE reward_tt IS TABLE OF reward_rt
INDEX BY^BINARYJWTEGER "
ENO conpensat1on_pkg:
Определив перечисленные типы коллекций в спецификации пакета, в других программах можно объявлять коллекции на их основе. Например:
DECLARE
ho!1day_bonuses compensatlon_pkg.reward_tt :
Коллекции записей особенно удобно использовать для создания в динамической памяти структур, эквивалентных таблицам базы данных. В каких случаях это может понадобиться? Предположим, что каждый месяц в определенное время запускается пакетная процедура, обрабатывающая таблицы, которые модифицируются в течение всего месяца. Процесс обработки и анализа данных достаточно трудоемок, требует нескольких проходов по таблицам. Разумеется, можно просто по нескольку раз считывать данные из таблиц, но это существенно замедлит процесс и потребует интенсивного использования ресурсов системы.
Целесообразнее скопировать данные из таблицы (или таблиц) в коллекцию, где их можно обработать гораздо быстрее, имея к тому же возможность произвольного доступа к записям. Таким образом, в программе PL/SQL, по сути, эмулируется двунаправленный курсор. Рассмотрим простой пример считывания данных из таблицы в коллекцию. Как видите, для перемещения данных (путем выполнения запроса) требуется на удивление мало программного кода:
declare
CURSOR hairstyles_cur IS
SELECT * FROM hairstyles;
TYPE local_hairstyles_tab IS TABLE OF hai rstyles^urXROWTYPE
INDEX BY BINARYJWTEGER:-
local hairstyles 1ocal_hairstyles_tab:
BEGIN ~~"
FOR hairstyles_rec IN hairstyles_cur
LOOP ~~.
local hairstyles (hair3ty1es_rec.code):H hairstyles_rec:
END LOOP?~、
END:
В настоящем примере в качестве номера строки используется значение столбца code из таблицы halrstyl es. Ассоциативный массив при этом заполняется непоследовательно. Вы спрашиваете, почему? Дело в том, что при таком подходе, получив код то ли от другой программы, то ли от пользователя, можно немедленно извлечь остальные данные строки, не выполняя поиск в коллекции.
А теперь посмотрим, как можно эмулировать двунаправленный курсор. Основная идея заключается в том, чтобы считать информацию из базы данных и без повторного обращения к ней обработать набор записей в программе. Существует два подхода к реализации такой идеи:
О включить код для работы с коллекцией в главную программу;
О создать отдельный пакет, инкапсулирующий доступ к данным коллекции.
Как правило, предпочтение следует отдавать второму подходу, который предполагает создание для сложных структур и данных отдельного, строго определенного и ориентированного на многократное использование API. Приведем спецификацию пакета для эмуляции двунаправленного курсора:
/* Файл в web: bidir.pkg */
CREATE OR REPLACE PACKAGE bl dir
IS
FUNCTION rowforid (idjn IN employee.employee IdXTYPE)
RETURN employee^ROWTYPE;~
FUNCTION fl rstrow RETURN PLS」MTEGER;
FUNCTION lastrow RETURN PLSJNTEGER;
FUNCTION rowcount RETURN PLS」NTEGER;
FUNCTION end_of_data RETURN BOOLEAN: '•
PROCEDURE setrow (nth IN PLSJNTEGER);
FUNCTION curгrow RETURN enployeeXROWTYPE;
PROCEDURE nextrow:
PROCEDURE prevrow:
END;
А как пользоваться этим API? Ниже представлен пример программы, которая с его помощью просматривает данные таблицы employee — сначала передвигаясь от ее начала к концу, а затем в обратном направлении:
DECLARE
l_employee employeeXROWTYPE;
BEGIN
LOOP
EXIT WHEN bid1r.end_of_data:
l_enployee := bidir.currrow:
DBMS_OUTPUT. putjlne (l_empl oyee .last_name);
bi dir. nextrow;■
END LODP;
bidi г-setrow (bidir.lastrow);
LOOP
EXIT WHEN bidir.end_of_data;
l_employee 尸 b-id5r.currrow;
DBMS^OUTPUT.putjine Cl_empl oyee .1 ast_name);
bidir.prevrow;
END LOOP;
END;
Проницательный читатель наверняка заинтересуется, когда же в коллекцию загружаются данные и где находится сама коллекция. Ведь в представленном коде ничего похожего на нее явно не содержится.
Сначала ответим на второй вопрос. Вы не видите коллекцию потому, что она скрыта в спецификации пакета. Пользователь пакета никогда не имеет с ней дела и даже не должен знать о ее существовании. Это основная идея создания API. Вы просто вызываете ту или иную программу, и она делает все, что нужно.
Ну а теперь коротко о загрузке данных в коллекцию. Может показаться, что это делается неким волшебным образом, но когда вы прочитаете главу 17, посвященную пакетам, все станет на свои места. Заглянув в тело пакета, вы найдете в нем такой раздел инициализации:
BEGIN Инициализация пакета
FOR rec IN (SELECT *
FROM employee)
LOOP
employees (rec.employeejd) := rec:
END LOOP;~
g_currrow :- firstrow:
END Г
ВНИМАНИЕ;
Переменная g_currrow объявлена в теле пакета. Именно поэтому она отсутствует в приведенной выше спецификации.
Это означает, что при первом обращении к какому-либо элементу пакета автоматически выполняется инициализационими код, копирующий содержимое таблицы employee в коллекцию employees. Когда же это происходит в приведенной вьппе программе? В цикле, при вызове функции bidir .end_of_data, когда мы проверяем, остались ли еще непросмотренные данные.
Мы предлагаем вам самостоятельно ознакомиться с реализацией пакета Ы d1 г. Его простой и понятный код наглядно демонстрирует основные принципы технологии создания API для работы с данными посредством коллекций.
Коллекции других сложных типов данных
В создании и использовании коллекций новых типов данных Oracle нет ничего сложного, но если вы не имеете какого-либо опыта работы с этими типами данных,
некоторые моменты могут вызвать затруднение. Рассмотрим пример коллекции объектов:
/* Файл в web: object_callection.sql */
CREATE TYPE pet_t IS OBJECT (
tag_noINTEGER,
NAMEVARCHAR2 (60).
MEMBER FUNCTION set_tag_no Cnew_tag_no IN INTEGER)
RETURN pet_t) ~ ~~ ~
/-
DECLARE
TYPE pets_t IS TABLE OF pet_t:
pets pets_t
:=pets_t (
pet_t C1050. 'Sanuiy').
bird」 C1075. 'Mercury'. 14)
BEGIN
FOR 1ndx IN pets.FIRSTpets.LAST
LOOP
OBMS_OUTPUT.put_11ne (pets (indx).NAME):■
END LOOP:
ENO;
После определения объектного типа вы можете объявить основанную на кем коллекцию и заполнить ее экземплярами того же типа. Так же просто создаются и коллекции других типов. Все правила, касающиеся применения переменных этих типов данных, применимы и к строкам состоящих из них коллекций.
Многоуровневые коллекции.
В Oracle9z коллекции можно вкладывать друг в друга, создавая таким образом многоуровневые коллекщ1и. Мы рассмотрим пример подобной структуры, а затем поговорим об ее использовании в приложениях.
Предположим, что нужно разработать систему для хранения информации о нескольких домашних животных. Помимо таких сведений, как, скажем, порода, имя и возраст, в ней должна содержаться информация о периодичности посещения ветеринара, поэтому мы создаем объектный тип vet_vis1t_t-
CREATE TYPE vet_visit_t IS OBJECT (
visit_date DATE.
reason VARCHAR2 (100)
Обратите внимание, что объекты данного типа представляют не животных, а содержат описание визитов к ветеринару. Создадим вложенную таблицу с информацией о таких визитах (предполагается, что ветеринара нужно посещать хотя бы раз в год):
CREATE TYPE vet_v1sits_t IS TABLE OF vet_visit_t
Определив эти две структуры данных, можно создать объектный тип для хранения сведений о животных:
CREATE TYPE pet_t IS OBJECT (
tdgjio INTEGER.
name VARCHAR2 (60).
petcare vet_visitsj.
MEMBER FUNCTION set_tag_no (new_tag_no IN INTEGER) RETURN pet_t)
NOT FINAL;
Предлагаемый объектный тип имеет три атрибута и один метод. Каждый созданный на его основе объект будет снабжен идентификатором, именем и списком с информацией о визитах к ветеринару. Для модификации идентификатора жи- вотного можно вызвать программу set_tdg_no. Используемый нами объектный тип объявлен с опцией NOT FINAL, чтобы впоследствии его можно было дополнить или расширить, используя введенную в Oracle9i технологию поддержки наследования. Например, для типа «домашнее животное» можно определить подтип <соба- Ка». Более подробно об объектах и наследовании рассказывается в главе 21.
Итак, у нас имеется объектный тип, одним из атрибутов которого является вложенная таблица. Для отслеживания информации о визитах к ветеринару отдельная таблица базы данных не понадобилась — такого рода сведения содержатся в самом объекте.
Ну а теперь расскажем о принципе работы с многоуровневыми коллекциями в Oracle9i. В приведенном далее примере в строках 1-10 создается и заполняется многоуровневая коллекция, а в строках 11-14 мы обращаемся к этой коллекции для получения информации:
/'* Файл в web; mul111 evel_col 1 ect 1 ons.sql */
DECLARE"
TYPE bunch_of_petS_t IS TABLEOF pet_t INDEX BY BINARY_INTEGER:
ny_pets bunch_of_pets_t;
BEGIN-~
my_pets Cl):-
pet_t (100, 'Mercury',
vet_visits_t (
8vet_visit_t ('01-Jan-200r . 'Clip wings')..
9vet_visit_t ('Ql-Apr-2002', 'Check cholesterol'))
);"
DBMS_OUTPUT.putJHne (my_pets (1).NAME);
DBMS_OUTPUT.put_line (my_pet5 (1).petcare (2).reason):
DBMS_OUTPUT.put_l1ne (my_pets.COUNT):
DBMS_OUTPUT.put_l1 ne (my_pets(l).petcare.LAST);
END; 一-—
Результат выполнения этого сценария будет следующим:
Mercury
Check cholesterol
1 2
Как работает приведенный выше код, можно понять из следующей таблицы.
Строки
Описание
2,3
Объявляется локальный тмп ассоциа-швного массива, в котором каждая строка содержит один объект типа pet_t- Затем объявляется коллекция для хранения информации о «домашнем зверинце»
Строки Описание
5-10 Первому элементу ассоциативного массива в качестве значения присваивается объект типа pet_t- Как видите, синтаксис работы с подобными сложными вложенными объектами довольно громоздок. Проанализируем последовательность выполняемых действий. Для создания экземпляра объекта типа pet_t нужно задать идентификатор, имя и список визитов к ветеринару, представляющий собой вложенную таблицу, а для создания вложенной таблицы типа vet_visit5__t следует вызвать одноименный консгру1сгор. Можно задать пустой или даже неинициализированный список либо инициализировать вложенную таблицу значениями. Мы указываем эти значения в строках 8 и 9. Каждый элемент коллекции vet_visits_t является объектом тмпа vet_visit_t/ для создания которого нам опять-таки нужен конструктор. Этому конструктору, имеющему то же имя, что и объеюный тип, передаются значения каждого из атрибутов (в данном случае дата и причина визита к ветеринару)
Вывод значения атрибута пате объекта pet_t первого элемента ассоциативного
массива my^pets
Вывод значения атрибута reason объекта vet_yisit_t второго элемента вложенной
таблицы, хранящейся в первом элементе ассоциативного массива myipets
,14 Демонстрация того, как используются методы (в данном случае — COUNT и LAST) для внешней и вложенной коллекций
В этом примере коллекции обоих уровней имели имена: внешняя коллекция (ассоциативный массив) называлась my_pets, а внутренняя (вложенная таблица) 一 petcare. Однако, как свидетельствует следующий пример, так бывает не всегда. Неименованные вложенные коллекции
Предположим, нам нужно разработать приложение для хранения характерных для разных языков прозвищ и уменьшительных имен людей. Для начала ограничимся четырьмя языками и тремя областями использования прозвищ (в семье, среди друзей и на работе). Они определяются следующими именованными константами:
/* Файл в web: multilevel_collections2.sql */
CREATE OR REPLACE PACKAGE nicknames IS
french
COMSTANT
PLS INTEGER
:-1005;
american_english CONSTANT
PLS~INTEGER
:-1013:
german
CONSTANT
PLSJNTEGER
:H 2005;
arable
CONSTANT
PLSJNTEGER
к 3107;
from_fami 1y
CONSTANT
PLS INTEGER
:=88;
from_friends
CONSTANT
PLSJNTEGER
:=99;
from_colleagues
CONSTANT
PLSJNTEGER
:=Ш:
Для поддержки всех этих прозвищ создадим два типа многоуровневых коллекций, как в следующем разделе пакета:
CREATE OR REPLACE PACKAGE nicknames IS
TYPE strings^ IS TABLE OF VARCHAR2 (30) INDEX BY^BINARYJNTEGER:
TYPE nickname_set_t IS TABLE OF strings_t
INDEX BY bTnARYJNTEGER;
TYPE multi ple_sets_t IS TABLE OF nickname_set_t
INDEX BY BINARY二INTEGER;
Элементами коллекции n1ckname_set_t являются вложенные коллекции символьных строк, каждая из которых представляет собой прозвище. Один элемент будет содержать прозвища, используемые в быту, другой 一 принятые среди друзей, а третий 一 на работе. В коллекции multiple_sets_t каждый элемент является коллекцией прозвищ. Один содержит их английские варианты, другой французские и т. д. Обратите внимание, что столбцы обоих типов, nickname_set_t и multip- le_sets_t, не имеют имен — задаются только их типы данных.
В описываемом пакете используется несколько функций для преобразования прозвищ (to_french, to_german и to_arabic). Каждая из них принимает набор прозвищ на английском языке и возвращает их перевод на нужный язык в виде коллекции того же типа. Заголовок функции может быть таким:
FUNCTION to_french (nicknames_in IN rrickname_set_t)
RETURN n1ckname_set_t:
Откомпилировав пакет nicknames, вы можете использовать все его компоненты, даже не думая о том, как они реализованы. Следующая программа демонстрирует принцип применения многоуровневой коллекции с анонимными столбцами:
CREATE OR REPLACE PROCEDURE set steven nicknames
IS~~
steven_n1cknaoiesnicknames. nlckname_set_t;
universal_nicknames ni cknames.multipie_sets_t;
BEGIN ~~~
steven__nicknames(99) (1000) :° 'Steve':
steven_n1cknames
ВCnlcknames.fram^col1 eagues) (2000) := 'Troublemaker':
steven^nicknames
(nicknames.fram_col1 eagues) (3000) := 'All-around Great Guy';
H steven_nicknames
(nicknames.from family) (789)尸'Whinner';
~
14
universal_nicknamesnicknames.american_english)尸
steven_nicknames;
universal_nicknames(nicknames.french):-
nicknames.ta_french (steven_nicknames);
universal_nicknames(nicknames.german):=
nicknames.to^german (steven__nicknames):
universal_n1cknames [nicknames.arable);=
nicknames.to arable (steven nicknames):
~~
DBMS_OUTPUT.PUT_LINE (
universal^nicknames■
(nicknames.american_engl1sh)
(nicknames. f rarn_colTeagues).
28(2000));
29
30DBMS_OUTPUT.PUVINE (
31uni versal_nicknames(1005)(111)(2000)):
32 END:~
Результат выполнения сценария (но лишь при условии реализации программы перевода) будет следующим:
ТгоиЫешаквг
Provocateur
Осуществляемые в приведенном фрагменте кода действия перечислены в таблице, которую вы видите ниже.
Строки Описание
4 Определение двух коллекций: одной ——для прозвищ на английском языке, а другой — для прозвищ на других языках
&-12 Заполнение коллекции steven_nicknames четырьмя прозвищами: одно принято среди друзей, два — среди коллег и одно среди членов семьи. В строке 6 используются жестко закодированные литералы, а в строках 7-12 константы. Номера элементов, в которые записываются символьные строки прозвищ, могут быть любыми.
В строке 6 приведен оператор выбора строки в многоуровневой анонимной коллекции: steven_nicknames (99) (1ODD) := 'Steve';. Этот оператор помещает значение 'Steve' в"1000-ю строку внутренней коллекции, входящей в состав 99-й строки внешней коллекции. Поскольку внутренние коллекции являются анонимными, можно просто задать два индекса подряд
15-22 Переход на другой уровень иерархии коллекций. Прозвища на английском языке теперь переводятся на французский, немецкий и арабский. Полученные в результате перевода коллекции записываются 8 соответствующие им коллекции universal_nlcknames. Чтобы избежать ошибок и путаницы, используются именованные константы; код при этом становится более читабельным
24-31 В последних строках процедуры выводится информация из результирующей коллекции. Этот фрагмент кода демонстрирует трехуровневую индексацию: сначала с помощью именованных констант, а затем — явно заданными литеральными значениями: universal_nicknames(1005)(lll)(2000)
Код, как видите, довольно сложный, особенно при работе с анонимными столбцами коллекций. Однако его можно упростить, если использовать коллекции объектов или записей, в которых каждый столбец имеет имя (как атрибут объектного типа или поле записи).
Если у вас возникнет вопрос, насколько глубоко можно вкладывать такие многоуровневые коллекции, проведите небольшое исследование. Напишите генератор кода для прохождения по многим уровням и с его помощью создайте процедуру, которая объявляла бы ^ типов коллекций (каждый такой тип должен служить вложенной таблицей — TABLE OF — элементов предыдущего типа), а затем присваивала бы значение элементу, принадлежащему последнему уровню полученной структуры.
Например, автору удалось создать коллекцию из 250 вложенных коллекций, после чего его компьютер выдал ошибку памяти. Конечно, придумать задачу, для
решения которой потребовалось бы создавать столь сложную структуру, непросто. Можно смело сказать, что практически количеству вложений коллекций в PL/SQL нет предела. Чтобы провести предлагаемый эксперимент в своей системе, можете воспользоваться файлом gen_multcoll.sp, представленным на узле издательства O'Reilly.
Многоуровневые коллекции сложны для понимания и сопровождения, но они обладают невероятной гибкостью, благодаря чему с их помощью можно реализовывать очень оригинальные решения.
Последовательные и непоследовательные ассоциативные массивы
Ассоциативные массивы, как вы помните, могут быть разреженными. Поэтому совершенно не обязательно записывать данные сначала в элемент 1, затем - в элемент 2 и т. д. Это означает, что, создавая ассоциативный массив, вы встаете перед выбором, каким образом его заполнить. При последовательном, заполнении коллекции операция начинается с любого элемента (чаще всего — с 0 или 1) и новые значения добавляются в конец коллекции, причем индекс очередного элемента увеличивается на 1. Этот подход хорош для случаев, когда имеет значение порядок элементов в списке. Если заполнение осуществляется в произвольном порядке, номер элемента используется в качестве своего рода «целочисленного ключа». Обычно он равен значению первичного ключа таблицы базы данных, из которой данные считываются в коллекцию. Возможность случайного (то есть не последовательного) размещения значений в таблице очень полезна в тех случаях, когда первичные ключи записей таблицы генерируются не непосредственно, а определяются данными приложения. Примеры использования каждого из указанных подходов представлены ниже.
Последовательное заполнение коллекции
Попробуем заменить функцию Oracle TO_DATE собственной, более удобной для работы функцией. Как можно добиться того, чтобы она, скажем, преобразовывала любое строковое представление даты в реальную дату? Это позволил。бы пользователю задавать дату в произвольном строковом формате.
Идея создаваемой функции заключается в следующем. Сначала в коллекции сохраняется несколько шаблонов форматов. Затем вызывается функция rny_to_ date, которая последовательно считывает из нее шаблоны и, используя таковые, пытается преобразовать строку символов в значение даты. Это должно продолжаться до тех пор, пока один из шаблонов не окажется подходящим. Результат преобразования строки будет зависеть от порядка записи шаблонов форматирования в коллекции. Например, строка 04-02-01 может быть интерпретирована как MM-DD-RR (месяц, день, год) или DD-MM-RR (день, месяц, год), в зависимости от того, Какой из шаблонов проверяется первым. Код функции myjx ddte приведен ниже;
/* Файл в web: mytodate.sf */
CREATE OR REPLACE FUNCTION my_to_date (valuejn IN VARCHAR2)
RETURN DATE" ~-
IS
ПРЕ mask—t IS TABLE OF VARCHAR2 (30)
INDEX 8У BINARY INTEGER;
fmtsfmtsmask_t;
fmts
fmts
retvalniask_i ndex date_convertedretvalniask_i ndex date_convertedDATEINTEGERBOOLEANDATEINTEGERBOOLEAN-NULL:=1;=血 SE;-NULL:=1;=血 SE;
retval
niask_i ndex date_converted
retval
niask_i ndex date_converted
DATE
INTEGER
BOOLEAN
DATE
INTEGER
BOOLEAN
-NULL:
=1;
=血 SE;
-NULL:
=1;
=血 SE;
PROCEDURE init_fnits IS
BEGIN
fmts (1) := 'OD-MON-RR1:
fmts (2)尸'DD-MON-YYYY':
fmts (3) :- 'DD-MON';
fmts (4)尸'MM/DO';
END;
BEGIN
■init_fmts:
WHILE mask_index IS NOT NULL AND NOT date_converted LOOP 一一
BEGIN■
retval :- T0_DATE (valujin. fmts (maskJndex)):
date__converted TRUE;
EXCEPTION
WHEN OTHERS
THEN
mask_index :■ fmts.NEXT (masMndex):
IF mask_index IS NULL
THEN ~
RAISE:
END IF;
END:
END LOOP;
RETURN retval;
END rqy_to_date;”
Заполнение коллекции в произвольном порядке
При функционировании многих приложений баз данных выполняется большое количество однотипных запросов, извлекающих из нее информацию, которая очень редко изменяется (или вообще не изменяется). Возникает вопрос: если информация не изменяется, зачем по нескольку раз обращаться к базе данных? Более того, зачем это делать на протяжении одного пользовательского сеанса? Ведь даже если данные кэшируются в глобальной области системы (System Global Area, SGA), необходимо время для поиска информации в буферах данных, а также для ее возврата в программную область сеанса (Program Global Area, PGA).
■Чтобы избежать лишних затрат, вы можете взять за правило следующее: никогда не обращаться к одной и той же справочной таблице более одного раза в течение одного сеанса. При первом обращении эту таблицу можно сохранить в программной области сеанса, и с этого момента она станет доступной для всех последующих запросов. В качестве ключа для выборки данных можно использовать индекс коллекции.
Рассмотрим предлагаемый механизм на примере. Предположим, у нас имеется таблица hairstyles, содержащая числовые коды (первичные ключи) и названия причесок. Эти названия меняются крайне редко. Ниже приведено тело пакета, в котором создается коллекция для кэширования пары значений, включающих код и название.
/* файл в web: justonce.sql */
CREATE OR REPLACE PACKAGE BODY justonce
IS
TYPE desc_t IS TABLE OF hairstyles.descriptionXTYPE
INDEX BY BINARY_INTEGER:
descriptions desc_t;
一
FUNCTION descrlption (codejn IN hairstyles.CDdeXTYPE)
RETURN hairstyles.descriptionlTYPE
IS
return_value hai rstyles.descrlptionXTVPE;
12 FUNCTION desc from database RETURN hai rstyles.descrlptiопШРЕ
13IS""
14CURSOR desjcur IS
15SELECT description FROM hairstyles WHERE code - code_in;
16desc rec desc_cur!KROWTYPE:'
17 BEGIN --
18OPEN desc_cur;
19FETCH desc_cur INTO desc_rec:
20RETURN desc_rec.descrlptTon:
21 END;~
22BEGIN
23RETURN descriptions (codejn);
24EXCEPTION~
25WHEN NO_DATA_FOUNO THEN
26descriptions (codejn) :- desc_from_database:
27RETURN descriptions (codejn);
END:~
END justonce:
Важнейшие аспекты работы этой программы описаны в таблице.
Строки
Описание
3-5
Определяется тип коллекции, и объявляется коллекция для хранения кэшируемых данных
小
Задается заголовок функции. Функция выполняет типичный запрос к базе данных и извлекает значения столбца description с заданным значением в столбце code. Код ее реализации скрыт, как и полагается в таких случаях
12-21
Производится тривиальный запрос к базе данных. Только теперь мы имеем дело с локальной функцией внутри главной функции
23
Это весь исполняемый раздел! Он проста возвращает название прически, хранящееся в строке с заданным кодом. При первом вызове этой функции строка еще не выбрана. Поэтому P17SQL инициирует исключение NO_DATA_FOUND (см. строки 25-27). А во всех последующих обращениях к коду строка уже определена, и функция сразу возвращает значение
Строки Описание
25-27В течение сеанса данные еще не запрашивались. Поэтому нужно перехватить
сообщение об ошибке, найти информацию в базе данных и поместить ее в коллекцию, а после этого вернуть значение. Теперь все готово к последующим операциям выборки
Вы спрашиваете, какая польза от такого кэширования? Автор провел небольшое тестирование, работая с локальной базой данных на своем компьютере, и обнаружил, что на выполнение 10 000 запросов к таблице hairstyles уходит около 2 с. Это достаточно высокое быстродействие. В то же время выполнение тех же 10 000 запросов с помощью приведенной выше функции заняло всего 0,1 с. Ускорение более чем на порядок — и это только при работе с локальной базой данных! В реальных условиях оно, надо полагать, будет гораздо более значительным, так что потрудиться стоит.
Приведем еще несколько замечаний, касающихся описанной технологии кэширования. Речь пойдет о классической задаче выбора компромиссного решения между загрузкой центрального процессора и памяти. Для каждого сеанса создается собственная копия коллекции (это данные программы, хранящиеся в ее PGA). Если к базе данных подключены 10 000 пользователей, то для такого же количества кэшей потребуется достаточно большой объем памяти. Поэтому данный подход годится только для небольших статических таблиц. А для того чтобы статическую таблицу можно было кэшировать в коллекции, у таблицы должен быть целочисленный первичный ключ. Если первичный ключ формируется на основе нескольких полей или является строковым значением, для формирования хэш- значений, которые можно использовать в качестве индексов в коллекции, можно воспользоваться функцией DBMS_UTILITY.GET_HASH_VALUE. Однако пр0rp3Mма в результате этого станет более сложной и менее привлекательной. Несколько подробнее о кэшировании данных рассказывается в главах 17 и 20.
Передача ассоциативных массивов в качестве параметров
Мы уже рассмотрели пример использования коллекции в качестве параметров модуля. Однако применение в этом качестве ассоциативных массивов имеет некоторые особенности.
Коллекции в языке PL/SQL — это просто один из типов данных, и не удивительно, что их можно передавать в процедуры или функции. Такое свойство коллекций чрезвычайно важно на практике, поскольку позволяет в одном вызове передать в модуль все значения, которые хранятся в таблице. В приведенной ниже спецификации пакета определяются два модуля. Первый из них принимает в качестве параметра таблицу PL/SQL, а второй — возвращает ее вызывающему коду. Процедура send_promos отправляет всем перечисленным в таблице компаниям рекламные листки, а функция compan1es_overdue формирует таблицу с названиями компаний, которые имеют неоплаченные счета.
CREATE OR REPLACE PACKAGE compan^__pkg IS
/* Вложенная таблица, структура которой соответствует структуре таблицы company */
TY吒 companies_tabtype IS TABLE OF companyXROWTYPE;
/* Параметром является таблица первичных ключей компаний */
PROCEDURE send_promos Ccompanies_in IN companies_tabtype);
/* функция возвращает таблицу с названиями компаний */
FUNCTION compan1es_overdue (overdue_date_in IN DATE)
RETURN companies_t3btype:'
END company_pkg:
Итак, у кас есть пакет, содержащий определения типа таблицы и процедуры, где используются таблицы указанного типа. Теперь заданные процедуры можно вызывать, объявив предварительно переменную типа compdrries_tdbtype. Вот как это делается:
CREATE OR REPLACE PROCEDURE sendjnvolces
IS
indx PLS_INTECER:
/* Объявляем вложенную таблицу на осноэе определенного в пакете типа */ conpanies compan^pkg. companles tabtype;
BEGIN一一
companies :- companyjikg.companles_averdue (SYSDATE-30);
Indx :- companies.FIRST;
LOOP
EXIT WHEN indx IS NULL:
send_i nvoi ce (compani es (1 ndx). company] d):
indx := companies.NEXT (indx):
END LOOP;
END:
Интеграция с сервером
Рассмотрим еще один пример, демонстрирующий, насколько использование коллекций может облегчить пересылку данных между программой PL/SQL и сервером. Основным логическим объектом в нашем примере является список квартир, для хранения которого используется вложенная таблица объектов.
Все квартиры описываются с помощью следующих атрибутов:
CREATE TYPE Apartment_t AS OBJECT (
unitjio NUMBER,
square_feet NUMBER.
bedrooms NUMBER.
bathrooms NUMBER, rentJn_dollars NUMBER
На основе этой структуры можно определить вложенную таблицу для хранения списка квартир:
CREATE TYPE Appartnient_tab_t AS TABLE OF Appartment^t;
Теперь, задавая для столбца табличный тип, можно создать таблицу базы данных:
CREATE TABLE apartrent_complexes (
name VARCHAR2C75).-
landlord_name VARCHAR2(45),
apartments Apartment_tab_t)
NESTED TABLE apartments~STORE AS apartments_store_tab;
Приведем инструкцию INSERT, заполняющую полученную таблицу (обратите внимание на вложенные вызовы конструкторов, создающие коллекцию объектов):
Apd「tment_tdb_t(Apd「tment_tdb_t(INSERT INTO 叩artment—Complexes VALUES ('RIVER OAKS FOUR'7'MR. JOHNSON',
Apd「tment_tdb_t(
Apd「tment_tdb_t(
Apartment_td. Apartment_t^2. Apartment_tC3, Apartment_t^. Apartment_t(5-Apartment_td. Apartment_t^2. Apartment_tC3, Apartment_t^. Apartment_t(5-))780. 2. 1. 975).
Apartment_td. Apartment_t^2. Apartment_tC3, Apartment_t^. Apartment_t(5-
Apartment_td. Apartment_t^2. Apartment_tC3, Apartment_t^. Apartment_t(5-
)
)
1200, 3, 2, 1590)
690, 1. 1.5. 8DD)
690. 1. 2. 450).
870, 2. 2, 990)
):
INSERT INTO apartment_conplexes VALUES 「GALLERIA PLACE1,~'MS. DODENHOFF',
Apartment_tab_t(
Apartment一t(101,
1000, 3. 2, 1295).
Apartment_t(lD2.
8DD. 2. 1, 995).
Apartment_tClD3,
8DD, 2, 1. 995).
Apartment_t(2ai,
920, 3. 1.5. 1195).
Apartment_t(202,
920, 3. 1.5. 1195).
Apartment_t(2D5.
1000, 3. 2. 1295)
На этом подготовительная работа заканчивается, и теперь можно продемонстрировать замечательные возможности, которые мы получаем благодаря хранению коллекций в базе данных.
Предположим, что новый менеджер квартирного комплекса River Oaks Four, в отличие от своего предшественника, не намерен заниматься дешевыми квартирами. Он решил отказаться от квартир, арендная плата за которые составляет менее 500 долларов, и на 15% повысить цену на все остальное жилье. Это можно реализовать с помощью следующего кода:
DECLARE
/* Объявление курсора для извлечения коллекции объектов-
11 квартир. Перед обновлением записи
11 ее стоит заблокировать с помощью параметра FOR UPDATE
*/
CURSOR 叩tcur IS
SELECT apartments FROM apartment^ompl exes
WHERE name = 'RIVER OAKS FOUR ~
FOR UPDATE OF apartments:
/* Необходимо объявить локальную переменную иля хранения коллекции 11 считанной из базы данных «нформаинн о квартирах
*/
l_apartments apartment_tab_t; which INTEGER; ~ -
BEGIN
/* Достаточно единственной выборки! */
OPEN aptcur:
FETCH 叩tcur INTO l_apartments;
CLOSE aptcur;
/* просмотр объектов-квартир s коллекции и удаление
11 тех из них. которые не соответствуют заданному критерию
which :- l_apartments.FIRST;
LOOP ~
EXIT WHEN Which IS NULL:
IF l_apartments(wh1ch).rentjn dollars < 500
THEN"- ~
1_apartments.DELETEfwhich):
END IF;
which := 叩drtments.NEXHwMch);
END LOOP; -
/* Просмотр оставшихся квартир и увеличение арендной
II платы. Обратите внимание, что этот код не обрабатывает,
Ij удаленные элементы
*/
which :- l_apartments.FIRST;
LOOP -
EXIT WHEN which IS NULL:
l_apartmentsCwhich).rent_in_dollars :-
l_apartments(which).rent_in_dollars * 1.15:.
which 尸 l_apartments.NEXT?which);
ENO LOOP: ~
/* В завершение передача всей коллекции квартир
11 обратно на сервер - опять-таки одной командой!
*/
UPDATE apartment_complexes
SET apartments - l_apartments
WHERE name = 'RIVER OAKS FOUR';
END;
В этом примере наиболее примечательными моментами являются выборка и запись данных — операции, каждая из которых выполняется одной командой. Приведенный код эмулирует создание клиентского кэша данных, который является одним из важных компонентов многих объектно-ориентированных архитектур и архитектур типа клиент-сервер. Клиентский кэш помогает сократить сетевой трафик, а коллекции представляют собой удобное средство для создания кэша.
Использование ассоциативных массивов с индексами типаVARCHAR2
В Oracle9z Release 2 таблицы PL/SQL, называемые теперь ассоциативными массивами, приобрели невероятную гибкость. В частности, в качестве их индексов могут выступать не только целочисленные значения (то есть номера элементов), но и символьные строки. Рассмотрим несколько примеров применения такой новой возможности.
Следующий блок кода демонстрирует основы данной технологии.
/* Файл в web: assoc_array.sql */
DECLARE~
TYPE population_type IS
TABLE OF №J№ER INDEX BY VARCHAR2C64);
country j)opulation population_type;.
conti nenjpopul ati on popul ati an_type:
howmany NUMBER:
limit VARCHAR2〔64):
BEGIN
country_population('Greenland'):- 100000;
country_population('Iceland'):= 750000:
continent_population('Australia'):- 30000000;
continent_population('Antarctica'):-1000; -- Создание нового элемента
conti nentjxipul ati on(' Antarctica
1001; -- Замена предыдущего значения
limit := continentjwpulation.FIRST;
DBMS_OUTPUT.PUT_LINE (linrit);
08MS_0UTPUT.PUT二LINE (continentjjopulat'londlmiw:
limit :- continent_population.LAST;
口RMSJXJTPUT,PUTJINE (limit):
DBMS—OUTPUT.PUT LINE (continent_popu1 ation(11mit)):
END:——
Результат выполнения этого кода:
Antarctica
1001
Australia
30000000
Обратите внимание, что для ассоциативного массива данного типа методы FIRST, LAST, PRIOR и NEXT возвращают не числа, а строки. Заметьте также, что в определении типа ассоциативного массива не может использоваться спецификация XTYPE — следует задавать литеральные, жестко закодированные определения.
Ну а для чего, спросите вы, может понадобиться использовать строковую индексацию коллекций вместо числовой? Предположим, нам необходимо произвести большой объем операций по обработке информации о сотрудниках. Это потребует множества перемещений по набору записей о сотрудниках в обоих направлениях, выполнения большого количества операций, связанных с поиском нужной записи по кодам сотрудников, их фамилиям и идентификационным кодам, пришлым во многих странах.
DECLARE
ТУРЕ name_t IS TABLE OF employe述RQWTYPE INDEX BY VARCHARZdOO):
TYPE id_t IS TABLE OF employeeXROWTYPE INDEX BY BINARY」盯EGER;
by_name name_t;
by ssn name t;
by__id id_t;
ceo_name enployee. 1 ast_namerTYPE :- 'ELLISON';
PROCEDURE load_arrays IS
BEGIN
FOR rec IN (SELECT * FROM employee) LOOP
Загрузка трех массивов за один проход по таблице базы данных, by_name (rec.last_nane) :- rec;
” by_ssn (rec.ssn) :в rec; byjd (rec.employee_id) :n rec; END LOOP;~
END:
BEGIN load_arrays;
--Считывание информации, соответствующей определенной фамилии или коду:
IF by_name (ceo_name).salary > byjd (7645).salary
THEN ~~
make_adjustment (by_name):
END IF;~-
END:
Как следует из этого примера, создать несколько точек входа для кэшируемых данных, считанных из реляционной таблицы, вовсе не сложно. А для того чтобы еще более упростить для вас реализацию предлагаемой технологии, автор книги разработал утилиту, которую вы найдете в файле genaa.sp на узле издательства O'Reilly. Указанная утилита генерирует пакет для кэширования заданной таблицы реляционной базы данных. Создаваемый ею пакет заполняет коллекцию на основе целочисленного первичного ключа и еще по одной коллекции на основе каждого определенного для таблицы уникального индекса (индексируются значениями типа BINARY_INTEGER и VARCHAR2 в зависимости от типа столбца индекса).
Пример использования ассоциативных массивов, индексируемых значениями типа VARCHAR2, для работы в PL/SQL со списками информации содержится в файле summer^read/ng.pkg.
Эмуляция альтернативных индексов коллекций
До появления Oracle9i Release 2 в качестве индексов ассоциативных массивов можно было использовать только значения типа BINARY_INTEGER. Для поиска, скажем, конкретной книги в коллекции, состоящей из большого количества элементов, приходилось сканировать всю коллекцию, что занимало много времени. Существует ли другой способ решения такого рода задачи? Первое, что можно предложить в подобной ситуации, — создать собственный, альтернативный индекс коллекции. Проще всего это сделать с помощью предоставляемой Oracle встроенной функции хэширования DBMS_UT1LITV_GET_HASH_VALUE. Функция принимает строку, а возвращает целочисленное значение, которое должно быть уникальным для каждой отличной от остальных строки, но на практике гарантировать эту уникальность невозможно. В результате приходится включать в программу логику
разрешения конфликтов. Звучит пугающе? На самом деле все очень просто, причем вам даже не придется реализовывать такой алгоритм. В файле altind.pkg, содержащемся на узле издательства О*КеШу, вы найдете код обработки информации о сотрудниках, который очень просто адаптировать для работы с любой другой таблицей.
Основная идея этой программы следующая. Заполняя главную коллекцию информацией, которую вам предстоит просматривать и обрабатывать (несколько раз, с разных позиций и т. д.), вы должны будете одновременно генерировать хэш-значения для значений альтернативного индекса и использовать их в качестве номеров элементов его коллекции. Таким образом, по окончании загрузки у вас будут заполнены две коллекции: одна с обрабатываемыми данными, другая — с номерами элементов первой коллекции, соответствующими значениям альтернативного индекса. Когда вам понадобится найти в первой коллекции элемент на основе строкового значения (например, фамилии сотрудника), вы определите хэш-код этого строкового значения, найдете во второй коллекции элемент с номером, равным этому хэш-коду, и считаете из него номер искомого элемента главной коллекции. Графическое представление описанного процесса показано на рис. 11.1.
Хэшируемое символьное значение.на основе которого генерируется номерэлемента альтернативного индекса
Коллекция альтернативного индексаКоллекция альтернативного индексаНомер ; элемента j1Q55: Полный набор данных:о сотрудниках;SMITH, 12-JAN-99,...34587988 ■;j FELLON, 10-MAR-82,...j DIONA, 22^Ub90,...Номер ; элемента j1Q55: Полный набор данных:о сотрудниках;SMITH, 12-JAN-99,...34587988 ■;j FELLON, 10-MAR-82,...j DIONA, 22^Ub90,...Коллекция кэшируемых данныхКоллекция кэшируемых данныхНомер строки равен коду сотрудникаНомер строки равен коду сотрудникаYWB^ 78955 ■ 襯^> 203055 匐藍沁嗨h09878 •YWB^ 78955 ■ 襯^> 203055 匐藍沁嗨h09878 •НомерэлементаНомерэлементаКод и фамилиясотрудникаКод и фамилиясотрудника1055 SMITH1055 SMITH3458 FELLON3458 FELLON7988 DIONA7988 DIONAПолный цикл поиска строки по фамилииПолный цикл поиска строки по фамилииaltlnd*.pkgaltind.tstaltlnd*.pkgaltind.tst
Коллекция альтернативного индекса
Коллекция альтернативного индекса
Номер ; элемента j
1Q55
: Полный набор данных
:о сотрудниках
;SMITH, 12-JAN-99,...
3458
7988 ■;
j FELLON, 10-MAR-82,...
j DIONA, 22^Ub90,...
Номер ; элемента j
1Q55
: Полный набор данных
:о сотрудниках
;SMITH, 12-JAN-99,...
3458
7988 ■;
j FELLON, 10-MAR-82,...
j DIONA, 22^Ub90,...
Коллекция кэшируемых данных
Коллекция кэшируемых данных
Номер строки равен коду сотрудника
Номер строки равен коду сотрудника
YWB^ 78955 ■ 襯^> 203055 匐藍沁嗨h09878 •
YWB^ 78955 ■ 襯^> 203055 匐藍沁嗨h09878 •
Номерэлемента
Номерэлемента
Код и фамилиясотрудника
Код и фамилиясотрудника
1055 SMITH
1055 SMITH
3458 FELLON
3458 FELLON
7988 DIONA
7988 DIONA
Полный цикл поиска строки по фамилии
Полный цикл поиска строки по фамилии
altlnd*.pkgaltind.tst
altlnd*.pkgaltind.tst
Рис 11.1. Заполнение коллекции и доступ к хэш-индексу
Все эти данные лучше всего объединить в инициалиэационном разделе пакета, как показано ниже на примере пакета altind:
CREATE OR REPLACE PACKAGE BODY ALT1ND IS ...тело пакета ...
PROCEDURE loadcache
IS BEGIN loadtab.DELETE;
hashtab.DELETE:
FOR rec IN ( SELECT * FROM employee)
LOOP
loadtab [rec.employeejd)尸 rec:
add_to_altind (rec.last_name, rec.employeejd):
END LOOP ~
END;
BEGIN ■
loadcache:
END:
За дополнительной информацией по вопросам реализации этой технологии обращайтесь к файлу altlnd.pkg.'
Псевдофункции коллекций
Псевдофункции Oracle позволяют работать с таблицами базы данных как с коллекциями, а с коллекциями — как с таблицами. Они предоставляют в распоряжение программистов богатый и интересный набор средств, значительно облегчающих решение тех задач, для которых удобно использовать определенное представление данных.
ВНИМАНИЕ Псевдофункции коллекций не являются функциями PL/SQL — они существуют только в SQL Для их использования в PL/SQL приходится включать s программы соответствующие SQL-инструкции. Из последующих разделов вы узнаете, как это делается.
Существует четыре псевдофункции коллекций.
О THE (в настоящее время пользоваться ею не рекомендуется) — представляет значение одного столбца-коллекции одной строки таблицы базы данных в виде виртуальной таблицы. Эта исевдофункция позволяет манипулировать элементами коллекции, хранящейся в базе данных.
Q CAST — преобразует коллекцию одного типа в коллекцию другого типа. С ее помощью можно, например, преобразовать массив VARRAY во вложенную таблицу.
Q MULTISET — представляет таблицу базы данных в виде коллекции. С помощью функций MULTISET и CAST из таблицы базы данных можно считать набор строк и представить его в виде столбца типа коллекции.
Q TABLE — представляет коллекцию в виде таблицы базы данных. Выполняет действие, обратное производимому функцией MULTISET.
Эти псевдофункщш предназначены для работы с коллекциями, хранящимися в базе данных. Обладая рядом важных достоинств, они, в частности, обеспечивают невероятно эффективное перемещение данных между базой данных и приложением. Правда, нельзя сказать, что эти псевдофункции просты и понятны. Но если вы не пожалеете времени на их освоение, в вашем распоряжении окажется мощный и гибкий инструментарий, который не раз сослужит вам хорошую службу.
Псевдофункция THE
Если в таблице базы данных имеется столбец типа вложенной таблицы и вам нужно вставлять, обновлять, удалять его содержимое, традиционные SQL-инструкции для этой цели не подходят. Oracle предлагает в таком случае использовать ключевое слово THE, указывающее, какая строка внешней таблицы должна быть обработана.
ВНИМАНИЕ В Огас1е8[ и более поздних версиях оператор THE заменен оператором TABLE, о котором речь пойдет в следующем разделе.
Как вы помните, ранее нами была создана таблица colorjnodels:
CREATE TABLE color_mode1s (
model Jype VARCHAR2(12).'
colors Color_tab_t)
NESTED TABLE colors STORE AS color_mcxlel_col0rs_tab;
В нее была добавлена строка, в столбце model_tyPe которой содержалось значение 1 RGB', а в столбце colors - значения ('RED', 'GREEN *, * BLUE'). Теперь предположим, что таблица color_models заполнена полудюжиной записей. Как с помощью инструкции SELECT извлечь из нее информацию обо всех цветах, составляющих заданную цветовую модель? Это можно сделать так:
SELECT VALUE(c) FROM
THECSELECT colors FROM color_models
WHERE model_type - 'RGB') c;
Данная инструкция означает следующее: «извлечь отдельные элементы модели RGB» или же, если говорить более точно, «извлечь значение каждого элемента вложенной таблицы colors, входящей в состав таблицы color_rnodels». Возвращаемые ею результаты будут такими:
VALUE(C) .
RED
GREEN
BLUE
Собственно говоря, в этом нет ничего необычного - мы просто включили в предложение FROM вложенный запрос. Приведенный выше запрос можно было бы сформировать несколько иначе, воспользовавшись предопределенным псевдонимом столбца COLUMNJALUE. Он предназначен специально для ссылки на элементы вложенной таблицы, где хранятся скалярные значения. Рассмотрим еще одну инструкцию, возвращающую те же результаты, что и предыдущая:
SELECT COLUMN_VALUE FROM
THEISELECT colors FROM color_models
WHERE model_type - 'RGB');
Интересно, что вложенный запрос THE можно использовать в качестве источника данных и в других SQL-инструкций, а именно INSERT, UPDATE и DELETE. Проиллюстрируем сказанное несколькими примерами:
BEGIN
--Замена в коллекции значения BLUE значением BURGUNDI
UPDATE THECSELECT colors FROM co】orjrode〕s WHERE model_type - 'RGB')
SET C0LUMN_VALUE - 'BURGUNDY' -~
WHERE COLUMN_VALUE 工'BLUE':
--Добавление еще одного цвета
INSERT INTO THE (
SELECT colors FROM color_models WHERE model_type - 'RGB')
VALUES (,EXTRA-COLOR,);--
--Вывод информации о текущих цветах
SELECT COLUMN一VALUE
FROM THE(SELECT colors FROM color_niode]s WHERE model_type " 'RGB1):
--Удаление лишнего цвета
DELETE THECSELECT colors FROM color_models WHERE model_type - 'RGB')
WHERE COLUMN_VALUE - 'EXTRA-COLOR':"
END:
Псевдофункция TABLE
Оператор TABLE преобразует столбец-коллекцию в такую структуру, из которой данные можно извлекать с помощью инструкции SELECT. Звучит мудрено, но на практике эта операция выполняется очень легко.
Предположим, у нас имеется таблица базы данных со столбцом типа коллекции. Как узнать, какие строки таблицы содержат коллекции, соответствующие заданному критерию? Иными словами, как выполнить выборку из таблицы базы данных, задав в предложении WHERE условие, которому должно соответствовать содержимое коллекции? Хорошо было бы просто написать:
SELECT *
FROM table_name
WHERE collect1on_column
HAS CONTENTS 'whatever': — Неправильный синтаксис!
Логически это именно те функции, которые выполняет оператор TABLE. Теперь вернемся к таблице color_models. Как выбрать из нее список всех цветовых моделей, содержащих цвет RED? Если мы сделаем это следующим образом:
SELECT *
FROM color_models с .
WHERE 'RED; IN
(SELECT * FROM TABLEfc.colors));
SQL*Plus выведет такой результат:
M0DEL_nPECOLORS
RGBCOLOR_TAB_T('REO', 'GREEN', 'BLUE')
Запрос можно сформулировать и по-другому: «пройти по таблице col or_model s и вернуть все строки, в которых список цветов содержит хотя бы один элемент RED》. Если бы описываемых цветовых моделей, содержащих цвет RED, в таблице было больше, SQL*Plus вывел бы их всех.
Как следует из последнего примера, оператор TABLE в качестве параметра принимает имя столбца-коллекции, уточненное псевдонимом таблицы:
TABLE(псеводоннм. иля коллекц^}
а возвращает содержимое коллекции, представленное в виде виртуальной таблицы базы данных. Поэтому из результирующей структуры данные можно извлекать при помощи инструкции SELECT. В нашем примере она используется в подзапросе.
Не напоминает ли вам псевдофункция TABLE псевдофункцию THE? Они обе возвращают структуры данных, которые могут использоваться в оставшейся части S QL-инструкции в качестве виртуальных таблиц базы данных, а различаются только входными данными. Функция TABLE оперирует вложенной таблицей (столбцом), а функция THE — результатом, возвращаемым инструкцией SELECT, то есть строкой с одним столбцом типа вложенной таблицы.
При этом, когда вы используете псевдофункцию THE в качестве источника данных для операции INSERT, UPDATE или DELETE, «за сценой» вызывается функция TABLE, преобразующая результат вложенного запроса в виртуальную таблицу базы данных, с которой может работать DML.
Повторяем еще раз: ни одна из псевдофункций коллекций не может использоваться в PL/SQL, но программисты должны о них знать, чтобы применять их в SQL-инструкциях. Псевдофункции, и превде всего TABLE, особенно полезны при работе с новыми табличными функциями Огас1е9г. К этой категории, как вы помните, относятся функции, возвращающие коллекции. Табличные функции могут использоваться в предложении FROM SQL-запросов. О них достаточно подробно рассказывается в главе 16.
На наш взгляд, все эти новые возможности просто замечательны, а отдельные трудности, с которыми приходится сталкиваться при их освоении и использовании, следует воспринимать как увлекательнейшую гимнастику для ума. Возможно, не все из вас разделяют это мнение, но наверняка никто не станет отрицать, что программировать для Oracle, по крайней мере, не скучно.
Псевдофункция CAST
Псевдофункция THE не работает непосредственно с массивами VARRAY. Однако с помощью оператора CAST вы можете представить такой массив в виде вложенной таблицы и использовать результат в инструкциях SELECT, подобных описанным в предыдущем разделе.
Преобразование именованных коллекций
Ниже приведен пример преобразования именованной коллекции типа данных VARRAY во вложенную таблицу. Предположим, у нас имеется таблица color_mode1s со столбцом типа VARRAY:
CREATE ПРЕ Color_array_t AS VARRAY(16) OF VARCHAR2(30);
CREATE TABLE color_niDdels_a (
model_type VARCHAR2(12T.
colors Color_array_t);
Столбец colors можно преобразовать во вложенную таблицу и использовать как аргумент псевдофункции THE:
SELECT COLUMNJ/ALUE FROM
THE(SELECT CASTfcolors AS Color_tab_t)
FROM color_models_a
WHERE modeT_type 二’FGB');
В этой инструкции функция CAST преобразует коллекцию типа color_array_t в коллекцию типа color_tab_t.
Между функциями CAST и THE имеются существенные различия. Как было отмечено в предыдущем разделе, функция THE может использоваться в левой и правой части инструкций INSERT, UPDATE и DELETE. Вложенные запросы THE обычно слуЖат источниками или приемниками данных этих инструкций DML. Что касается функции CAST, то она используется только в предложениях SELECT или в правой части DML-инструкций.
Преобразование неименованных коллекций
функция CAST способна преобразовать набор записей; представляющий собой результат выполнения вложенного запроса, в коллекцию нужного типа. Правда, она может это сделать лишь с помощью функции MULTISET, о которой рассказывается в следующем разделе.
Псевдофункция MULTISET
Функция MULTISET используется только как аргумент функции CAST. Она позволяет извлечь из базы данных набор значений и тут же преобразовать его в коллекцию нужного типа. Приведем простейшую форму этой псевдофункции:
SELECT CAST [MULTISET (SELECT поле FROM таблицд) AS тип колленции) FROM DUAL;
Если у нас имеется реляционная таблица цветов:
CREATE TABLE some_colors (
color_nanie VARCHAR2C3Q),
color_classificat1on VARCHAR2(30));
и мы хотим преобразовать ее в коллекцию, чтобы посредством одной команды скопировать в программную переменную, это можно сделать так:
DECLARE
some hot colors Color tab t:
BEGIN ~ ~~ ~■
SELECT CAST(MULTISET(SELECT color^name
FROM some_colors
WHERE color_classification - 'HOT')
AS Color_tab_t)
INTO sonie_hot_coTors
FROM DUAL? ~
END:
Кроме того, функция MULTISET используется в коррелированных подзапросах, включаемых в список SELECT:■
SELECT outerfield,
CASKMULTISEKSELECT field FROM whateverTable
WHERE correlatlonCriteria)
AS collectionTypeName)
FROM outerTable:
Данная технология позволяет представлять объединения в виде таблиц с коллекциями. Рассмотрим в ^качестве примера таблицу с информацией о птицах
и связанную с ней таблицу стран, в которых водится каждый из упоминаемых в первой таблице видов птиц:
CREATE TABLE birds (
genus VARCHAR2(128).
species VARCHAR2(128),
colors Color_array_t.
PRIMARY KEY fgenus, species)
CREATE TABLE bird_hab1tats (
genus VARCHAR2(128).
species VARCHAR2C128),
country VARCHAR2(60),■
FOREIGN KEY (genus, species) REFERENCES birds (genus, spedes) ):
CREATE TYPE Country_tab_t AS TABLE OF VARCHAR2(60):
Нам нужно объединить главную и подчиненную таблицы с помощью инструкции SELECT, преобразующей подчиненные записи в коллекции. Такая возможность имеет огромное значение для приложений с архитектурой клиент-сервер, поскольку позволяет значительно сократить количество обращений к серверу и при этом не дублировать записи главной таблицы для каждой записи подчиненной таблицы. Вот как выполняется такой запрос:
DECLARE
CURSOR bird curs IS
SELECT b.genus, b.species,
CAST(MULTISET(SELECT t)h.country FROM blrd_habitats bh
WHERE bh.genus - b.genus
AND bh.species - b.species)
AS country_tab__t)
FROM birds b;~ 一
bird_row bird_curs^ROWTYPE:,
BEGIN —~
OPEN t)1rd_curs:
FETCH bird_curs into bird_row;
CLOSE bird_curs;
ENO; •"
Как и псевдофункция CAST, псевдофункция MULTISET не может использоваться в инструкциях INSERT, UPDATE и DELETE.
Сортировка содержимого коллекций
Одной из замечательных особенностей псевдофункций является то, что они позволяют выполнять SQL-операции над структурами данных PL/SQL (в частности, над вложенными таблицами и массивами VARRAY). Например, воспользовавшись предложением ORDER BY, данные можно выбрать из вложенной таблицы в определенном порядке. Предположим, нам необходимо сформировать таблицу базы данных со сведениями о нескольких авторах:
CREATE TYPE names_t AS TABLE OF VARCHAR2 (100);
CREATE TYPE authors t AS TABLE OF VARCHAR2 (100);
CREATE TABLE favQrite_authors (папе varchar2(200))
BEGIN
INSERT INTO favorite_authors VALUES ('Robert Harris');
INSERT I肝口 favorite_authors VALUES ('Tom Segev');
INSERT INTO favorite_authors VALUES ('Toni Morrison');
END;
Объединим эту информацию с данными программы PL/SQL:
declare
sc1f1_favor1tes authors_t
:-""authors_t ('Sheri S. Tepper', 'Orson Scott Card1. 'Gene Wolfe'):
BEGIN~
DBMS_OUTPUT.putJine ('I recommend that you read books by:');
FOR rec IN (SELECT column_valije favs
FROM TABLE (cast (scifl favorites AS names_t)) UNION宀-
SELECT NAME
FROM favorite_authors)
LOOP~
DBMS_OUTPUT.putJ1ne (rec.favs);
END LOOP:-
END;
Обратите внимание, что для объединения данных из таблицы базы данных и коллекции можно использовать оператор UNION:
DECLARE
scif1_favorites authors_t
尸 authors_t ('Sheri S. Tepper'. 'Orson Scott Card', 'Gene Wolfe'):
BEGIN~
DBMS_OUTPUT.putJ1 ne CI recommend that you read books by:1);
FOR rec IN (SELECT column_value favs
FROM TABLE (cast (sclf1_favorites AS names_t))
ORDER BY column_value)
LOOP-
D8MS_OUTPUT.put_line (rec.favs);■
END LOOP;-
END;
Сопутствующие операции
В этом разделе приводится ряд сведений, необходимых в первую очередь для работы с вложенными таблицами и массивами VARRAY. Речь пойдет о вспомогательных операциях.
Привилегии
Если тип данных коллекции определен в базе данных, с ним могут работать несколько пользователей Oracle, каждый из которых является владельцем одной или нескольких схем. Естественно, что в этом случае возможность доступа к данным определяется привилегиями, управлять которыми достаточно просто. Единственной привилегией пользователей Oracle, связанной с применением типов коллекций, является привилегия на вьшолнение EXECUTE.
Если, предположим, ваше имя Scott и вы хотите позволить пользователю Joe применять в своих программах созданный вами тип Color_tab_t, то должны предоставить ему привилегию EXECUTE:
GRANT EXECUTE Color_tab_t ON TO Joe;
Пользователь Joe сможет ссылаться на данный тип с помощью синтаксиса схема. тип. Например:
CREATE TABLE rny_stuff_to_paint (
wh1ch_stuff VARCHAR2(512).
paint_mixture SCOTT.Color_tab_t
)"~ ~
NESTED TABLE paint_m1xture STORE AS pa1nt_mixture_st;
Привилегия EXECUTE необходима и пользователям, выполняющим анонимные блоки PL/SQL, в которых задействован определенный вами тип коллекции. Это одна из причин, по которым лучше создавать именованные модули PL/SQL — пакеты, процедуры и функции. Предоставляя кому-либо привилегию EXECUTE на некоторый модуль, вы предоставляете ему на время выполнения этого модуля собственные разрешения.
Для таблиц, включающих столбцы-коллекции, действуют традиционные привилегии SELECT, INSERT, UPDATE и DELETE, если только в этих инструкциях не формируются коллекции для записи в столбцы. Однако если пользователь хочет обновлять содержимое столбца-коллекции, у него должна быть привилегия EXECUTE на соответствующий тип, поскольку иначе он не сможет пользоваться конструктором данного типа.
Коллекции и словарь данных
При работе с вложенными таблицами и массивами VARRAY вам наверняка пригодятся новые элементы словаря данных, описанные в табл. 11.2. Пользовательские типы в словаре данных называются просто TYPE. Определения типов коллекций возвращает представление USER_SOURCE (или OBA_SQURCE, или ALL_SDURCE).
Таблица 11.2. Элементы словаря данных для типов-коллекций
Для получения ответа на вопрос
...используйте данное представление.
...как в следующем примере
Какие типы коллекций были созданы?
USEK_TYPES
SELECT type_name
FROM user_types
WHERE typecode = 'COLLECnON';
Каким было исходное определение типа коллекции Foo_t?
USEFVSOURCE
SELECT text
FROM user_source
WHERE name = 'FOO_T
AND type = TYPE1 ORDER BY line;
Какие столбцы основаны на коллекции типа Foo_t?
USER_TAB_COLUMNS
SELECT table_namef column_name FROM user_tab_columns WHERE data_type = 'FOO_T;
Какие объекты базы данных зависят от коллекции типа Foo_t?
USER_DEPENDENQES
SELECT name, type
FROM user_dependecies
WHERE referenced_name = 'FOO_T;
Выбор типа коллекции
Можно ли определить, какая коллекция больше всего подходит для вашей программы? Очень часто выбор очевиден, но бывает и так, что приемлемых вариантов несколько. Ключевые различия между ассоциативными массивами, вложенными таблицами и массивами VARRAY перечислены в табл. 11.3.
Как правило, программисты предпочитают использовать ассоциативные мае- сивы. Почему? Причин несколько. Для них приходится писать меньше всего программного кода. Их не нужно ни инициализировать, ни расширять. Долгое время эт0 был самый эффективный тип коллекции (хотя данный фактор, конечно же, скор。потеряет значение). Однако если вам нужно хранить коллекцию в таблице базы данных, ассоциативный массив для этого не подойдет — нужно сделать выбор между вложенной таблицей и массивом VARRAY.
Помимо этих, самых очевидных, соображений примите во внимание следующие советы.
О Если вы намерены хранить в коллекции-столбце большое количество данных, то единственным выбором должна стать вложенная таблица. Для хранения структур указанного типа Oracle создает отдельные таблицы, размер которых практически не ограничен.
О Если вам нужно сохранять порядок элементов в столбце коллекции, если объем данных невелик, используйте массив VARRAY. Что значит «невелик»? Лучше всего, если размер массива не будет превышать размер блока базы данных. Когда массив занимает более одного блока, его обработка замедляется. Размер блока базы данных задается при создании таковой и обычно составляет 2, 4 или 8 К.
О Тип коллекции VARRAY удобен еще в нескольких случаях: когда нужно иметь гарантию, что элементы не будут удаляться из середины коллекции; когда количество данных ограниченно; когда коллекции должны извлекаться из базы данных целиком.
О Если вам нужна разреженная структура, единственным выбором является ассоциативный массив. Разумеется, можно добавлять и удалять элементы вложенной таблицы (как в примерах из раздела, посвященного методам NEXT и PRIOR), но это крайне неэффективный метод, и приемлемый разве что при наличии очень маленьких коллекций.
Q Если программа должна работать и в Oracle?, и в Огас1е8, наиболее целесообразным выбором являются ассоциативные массивы. Как и в том случае, когда в коллекции используются отрицательные индексы.
Таблица 11.3. Сравнение типов коллекций Oracle
Характеристика
Ассоциативный массив
Вложенная таблица
Массив типа
VARRAY
Размерность
Одномерный
Одномерная
Одномерный
Можно ли использовать в SQL
Нет
Да
Да
продолжение^
Таблица 11.3 (продолжение)
Характеристика
Ассоциативный массив
Вложенная таблица
Массив типа VARRAY
Можно ли использовать в качестве типа данных столбца таблицы
Нет
Да; данные хранятся вне таблицы (в отдельной таблице)
Да; данные хранятся прямо в таблице
Содержимое в неинициализированном состоянии
Пуст (не может быть равен NULL); элементы не определены
Содержит атомарное значение NULL; нельзя ссылаться на элементы
Содержит атомарное значение NULL;
нельзя ссылаться на элементы
Тип данных, используемый для ссылки на элементы в PL/SQL
BINARYJNTEGER (-2 147 483 647 ... 2 147 483 547)
VARCHAR2 (OradeQI Release 2 и выше)
INTEGER (положительные целые числа от 1 до 2 147 483 547)
INTEGER (положительные целые числа от 1 до 2 147 483 647)
Допустима ли разреженность данных
Да
Первоначально — нет; после удалений — да
Нет
Накладывается ли ограничение на длину
Нет
Возможно расширение
Да
Можно ли присваивать значения любому элементу в любой момент
Да
Нет; сначала может потребоваться вызвать метод EXTEND
Нет; сначала может потребоваться вызвать метод EXTEND, и расширение возможно только др верхней границы
Способ расширения
Присваивание значения элементу с новым индексом
Использование встроенной процедуры EXTEND без предопределенного максимума (для сжатия используется метод TRIM)
Использование встроенной процедуры EXTEND, но только до предопределенного максимума (для сжатия используется метод TRIM)
Допустима ли операция сравнения
Нет
Нет
Нет
Сохранение порядка строк и индексации прк записи в таблицу и извлечении из таблицы базы данных
Нет
Да
1212Другие типы данныхДругие типы данных
12
12
Другие типы данных
Другие типы данных
А Тип данных BOOLEAN
А Тип данных RAW
» Типы данных UROWID и ROWID
» Типы данных LOB
А Работа с большими объектами
А Предопределенные объектные типы
В настоящей главе рассматриваются встроенные типы данных PL/SQL, о которых ничего не было сказано в предыдущих главах. Речь пойдет о типах BOOLEAN, UROWID (ROWID), а также о семействе типов LOB (Large OBjects) — больших объектах. Кроме того, будет описано несколько важных предопределенных объектных типов, в том числе тип XMLType, который предназначен для хранения ХМЬ-даняых в столбце таблицы, и тип ANYDATA, используемый для хранения любых данных.
Тип данных BOOLEAN
В версии языка SQL для Oracle имеется ряд элементов, которые отсутствуют в PL/SQL (например, конструкция DECODE). PL/SQL, со своей стороны, включает компоненты, которых нет в стандартном языке SQL. Одним из них, особенно важным для программистов, является тип данных BOOLEAN1. Переменные этого типа могут принимать только три значения: TRUE, FALSE и NULL. Поэтому его часто называют логическим типом данных.
РСУБД Oracle не поддерживает тип данных BOOLEAN. Вот почему для хранения логических значений в таблицу обычно вставляют столбец типа CHAR (1) и записывают в него значения Y и N, обозначаюгцие TRUE и FALSE. Однако значения данного столбца, конечно же, нельзя присваивать логическим переменным PL/SQL, как
Булев тип данных назван по имени Джорджа Буля, жившего в первой половине XIX столетия
нельзя выполнять и обратную операцию - сохранять в столбце значения TRUE и FALSE. Иными славами, речь идет просто об эмуляции логического типа данных, а вовсе не об эквиваленте настоящего логического типа PL/SQL.
Логические переменные и выражения очень широко используются программистами. То обстоятельство, что логическая переменная может принимать только три значения — TRUE, FALSE и NULL, — позволяет сделать программу более читабельной. Код, содержащий логические переменные и выражения, является более простым для понимания, поскольку похож на естественный английский язык. Сложное логическое выражение со множеством переменных и операций проверки можно заменить одной логической переменой, имя которой выражает суть вычисляемого выражения.
Приведем пример оператора IF с одной логической переменной, или функцией; глядя на этот код, невозможно огфеделить, что именно - функция или переменная —в ней используется:
IF report_requested
THEN ~
pr1nt_repart С reported):
END IF; ~-
Программная логика данного фрагмента кода делает его не только самодоку- ментированным, но и защищенным от будущих изменении. Рассмотрим, к примеру, код реализации пользовательского интерфейса, который может предшествовать данному оператору. Как программа определяет, что пользователю нужно печатать отчет? Она либо выводит окно запроса с вариантами ответов «Да» и «Нет», либо предлагает установить флажок или выбрать из списка нужную опцию. Для приведенного кода это не имеет значения. Код реализации пользовательского интерфейса может быть любым, и при желании его можно в любой момент изменить —главное, чтобы он правильно присваивал значение логической переменной report_requested.
В представленном выше примере проверяется значение обычной логической переменной. Если оно равно TRUE, отчет выводится на печать. В противном случае отчет не печатается, а выполняются указанные далее в программе действия. Аналогичную проверку можно бьыо бы организовать и с помощью оператора IF вида: IF report_requested = TRUE THEN ~
pr1nt_report (reported);
END IF; ~一
Код не содержит ни логических, ни синтаксических ошибок, но этот оператор не соответствует сути логической переменной. Поскольку логическая переменная сама может принимать значения TRUE, FALSE и NULL, то не имеет смысла сравнивать ее с этими значениями, чтобы опять-таки получить TRUE, FALSE или NULL. К тому же назначив переменной подходящее имя, вы сделаете конструкцию IF.. .THEN более лаконичной и понятной.
Тот факт, что логическая переменная может принимать и значение NULL, отражается на структуре оператора IF.. .THEN.. .ELSE. Для примера сравните два следующих оператора:
IF report_requested
THEN ~
если report_requested = TRUEесли report_requested =» FALSE или IS NULLесли report_requested = TRUEесли report_requested =» FALSE или IS NULLесли report_requested = FALSEесли report_requested = TRUE или IS NULLесли report_requested = FALSEесли report_requested = TRUE или IS NULL
если report_requested = TRUE
если report_requested =» FALSE или IS NULL
если report_requested = TRUE
если report_requested =» FALSE или IS NULL
если report_requested = FALSE
если report_requested = TRUE или IS NULL
если report_requested = FALSE
если report_requested = TRUE или IS NULL
.-Код, который выполняется, ELSE
Код. который эыполняется. END IF;
IF NOT report_requested
THEN
Код, который выполняется.
ELSE
--Код. который выполняется, END IF:
Если каждому из трех возможных значений переменной должны соответствовать определенные действия, следует использовать такой оператор IF:
IF report_requested'
THEN 一
-Код, который выполняется, если report_requested = TRUE ELSIF NOT report^requested
-Код. который выполняется, если report_requested = FALSE
ELSE~
-Код. который выполняется, если report_requested IS NULL END IF:~
Подробнее об обработке значений NULL в операторах IF рассказывается в главе 4.
Тип данных RAW
Тип данных RAW предназначен для хранения и обработки двоичных данных сравнительно небольших объемов. При пересылке между базой данных и программой данные типа RAW, в отличие от VARCHAR2 и других символьных типов, никогда не преобразовываются из одного набора символов в другой. Переменные типа RAW объявляются так:
^мя^переменной ЯМ^максимапьныйразмер)
Значение максимдльныйразмер должно находиться в диапазоне от 1 до 32767. Имейте в виду, что переменная типа RAW может содержать порядка 32 767 байт данных, а столбец базы данных этого же типа — не более 2000 байт.
Тип данных RAW используется редко. Как уже было сказано, он предназначен Для хранения малых объемов двоичных данных. Если же вам придется работать с большими объемами информации, например с изображениями или звуком, обратитесь к типу данных BLOB (Binary Large OBject - большой двоичный объект). О нем рассказывается далее в этой главе.
Типы данных UROWID и ROWID
Типы данных UROWID и ROWID предназначены для работы с идентификаторами строк базы данных. ROWID — это идентификатор строки (row identifier), а точнее, двоичное значение, идентифицирующее строку данных в таблице Oracle. В некоторых слу- хаях его применение в инструкциях UPDATE и DELETE позволяет повысить скорость обработки, поскольку доступ к строке по ее идентификатору выполняется быстрее,
чем по первичному ключу. На рис. 12.1 продемонстрированы два способа обновления записей в таблице, а именно с использованием идентификатора и первичного ключа.
UPDATE employeeSET first nanje-'Heather'WHERE last_name='Grubbs':UPDATE employeeSET first nanje-'Heather'WHERE last_name='Grubbs':UPDATE employeeSET first name"'Heather' WHERE ROWTD=employee_rowi d;UPDATE employeeSET first name"'Heather' WHERE ROWTD=employee_rowi d;
UPDATE employee
SET first nanje-'Heather'
WHERE last_name='Grubbs':
UPDATE employee
SET first nanje-'Heather'
WHERE last_name='Grubbs':
UPDATE employee
SET first name"'Heather' WHERE ROWTD=employee_rowi d;
UPDATE employee
SET first name"'Heather' WHERE ROWTD=employee_rowi d;
❶ Поиск в индексе записи last_name с целью обнаружения идентификатора rowid❷ Используем идентификатор rowfd для извлечения блока базы данных, содержащего обновляемую строку❸ Изменяем данные, записываем блок на жесткий диск❶ Поиск в индексе записи last_name с целью обнаружения идентификатора rowid❷ Используем идентификатор rowfd для извлечения блока базы данных, содержащего обновляемую строку❸ Изменяем данные, записываем блок на жесткий дискlast_name 隱^^ Индексlast_name 隱^^ ИндексМин尸 шаг 工 переходим к вь^юпнвнию шага 2Мин尸 шаг 工 переходим к вь^юпнвнию шага 2Таблица^.iТаблица^.iблок данныхблок данныхТаблицаТаблица
❶ Поиск в индексе записи last_name с целью обнаружения идентификатора rowid
❷ Используем идентификатор rowfd для извлечения блока базы данных, содержащего обновляемую строку
❸ Изменяем данные, записываем блок на жесткий диск
❶ Поиск в индексе записи last_name с целью обнаружения идентификатора rowid
❷ Используем идентификатор rowfd для извлечения блока базы данных, содержащего обновляемую строку
❸ Изменяем данные, записываем блок на жесткий диск
last_name 隱^^ Индекс
last_name 隱^^ Индекс
Мин尸 шаг 工 переходим к вь^юпнвнию шага 2
Мин尸 шаг 工 переходим к вь^юпнвнию шага 2
Таблица^.i
Таблица^.i
блок данных
блок данных
Таблица
Таблица
Рис* 12.1. Идентификатор типа ROWID указываетнепосредственно на строку таблицы
В истории Oracle ROWID был одним из первых типов данных, предназначенных для идентификации строк таблиц. По мере добавления новых функциональных возможностей, таких как использование индекс-таблиц и типов данных для межсетевых шлюзов, компания Oracle, естественно, разрабатывала и новые типы идентификаторов строк, и новые типы данных для их хранения, Так появился тип данных UROWID, используемый для хранения идентификаторов строк таблиц любого типа. Буква U в его названии означает «universal» (универсальный).
ПРИМЕЧАНИЕ Мы рекомендуем использовать тип данных UROWID во всех новых программах, оперирующих с идентификаторами строк. Тип данных ROWID в Oracle сохраняется для обеспечения обратной совместимости, но он, в отличие от UROWID, не поддерживает все типы идентификаторов строк, использующихся в современных базах данных Oracle.
Получение идентификаторов строк
Идентификатор строки — это создаваемое Oracle двоичное значение, однозначно определяющее физический адрес каждой строки в таблице. Оно генерируется, когда строка добавляется в таблицу. Изменить это значение невозможно, его можно только считать с помощью инструкции SELECT, если указать в ней псевдостолбец ROWID. Мы применили здесь термин «псевдостолбец» потому, что на самом деле столбца с именем ROWID в таблице не существует. Это просто ключевое слово, которое указывается в списке столбцов инструкции SELECT и означает, что мы хотим получить значение идентификатора строки.
Для того чтобы сохранить идентификатор строки, полученный с помощью инструкции FETCH или SELECT, и иметь возможность использовать его в программе PL/SQL, нужно объявить для него переменную типа UROWID. Например:
DECLARE
employee_rowid UROWID;
employee_5alary NUMBER:
BEGIN -
—Считываем информацио о сотруднике, которую мы собираемся модифицировать
SELECT rowid. salary INTO employee_rowid. ел1р1оуее_5а1агу
FROM employee
WHERE last_name-'Grubbs' AND 行田_他修']。";
END;
Возможно, кому-то интересно узнать, что представляет собой ROWID на самом деле и какие данные хранятся в переменной типа UROWID. Если вы не являетесь администратором базы данных, это не имеет для вас никакого значения. Но если вы все же решите разобраться в этом, выполните в SQL*Plus запрос на выборку идентификаторов строк из какой-нибудь таблицы. Ниже в качестве примерз приведено несколько идентификаторов строк, взятых из индекс-таблицы:
SQL> SELECT rowid FROM enployee:
ROWID
*BAIAC1ICwQL+'
*BAIAC1ICwQP+
Следующие идентификаторы получены с помощью инструкции SELECT из обычной таблицы (в Oracle? результаты будут иными):
SQL> SELECT rowid FROM rtest;
ШЮ
AAAHZpAAlAAAAoSAAA
AAAHZpAAlAAAAoSAAB
Если вам нужно оперировать с компонентами идентификаторов строк, обратитесь к встроенному пакету DBMS_ROWID, достаточно подробная информация о котором содержится в книге Oracle Built-in Packages (издательство O'Reilly) и в руководстве Oracle, в разделе Supplied PL/SQL Packages and Types Reference. Но в большинстве случаев разбираться в содержимом идентификаторов строк нет никакой необходимости. Для решения большинства задач, в том числе для анализа всех примеров из этой книги, достаточно знать, что это некоторое значение, идентифицирующее строку таблицы.
Использование идентификаторов строк
Итак, вы уже знаете, как присвоить значение идентификатора строки переменной РL/SQL-программы, но зачем это делать? На практике идентификаторы строк час- то используются при повторном доступе к строке. Вспомните пример из предыдущего раздела, когда из базы данных извлекается информация об окладе сотруд- ника. Предположим, нам необходимо изменить величину оклада и ввести в базу Данных новое значение. Для этой цели можно, конечно, написать инструкцию UPDATE с тем же предложением WHERE, которое использовалось в инструкции SELECT: DECLARE
employee_rowid UROWID;
employee salary NUMBER;
BEGIN _
--Считываем информацию о сотруднике, которуо мы собираемся модифицировать
SELECT rowid, salary INTO employee_raw1d. emplayee_salary
FROM employee
WHERE last_name-'Grubbs' AND f1rst_nane='John';
/* Вычисляем новый оклад */
UPDATE employee
SET salary = employee_salary
WHERE last nameD,Grubbs' AND first nafne='John';■
END: —一
Этот код, безусловно, выполнит поставленную задачу, но он имеет один существенный недостаток: в инструкциях UPDATE и SELECT повторяются одни и те же параметры доступа. Следовательно, при выполнении каждой из них производится обращение к одному или нескольким индексам. А какой смысл по нескольку раз повторять одни и те же действия для поиска одного идентификатора строки? Гораздо целесообразнее присвоить идентификатор строки переменной, а затем использовать его в инструкции UPDATE (в случае надобности):
DECLARE.
employee_rawid UROWID:
employee_salary NUMBER;
BEGIN -
—Считывание информации о сотруднике
SELECT rowid. salary INTO employee_rowid. eiriplayee_salary
FROM employee
WHERE 135^3016*'Grubbs' AND first_name=,Dohn,;
/* Вычисление величины нового оклада */
UPDATE employee
SET salary = employee_salary
WHERE rowid < employee rowid;
END;~
Если employee — это обычная таблица, организованная по принципу кучи, идентификатор строки в предложении WHERE инструкции UPDATE указывает непосредственно на местоположение строки на диске. Но если employee является индекс-табли- цей, то время доступа зависит от скорости обновления ее данных; однако и в этом- случае идентификатор строки обеспечивает максимально быстрый доступ к ее содержимому.
ПРИМЕЧАНИЕ
Иногда лучший способ достижения определенного результата — это использование для извлечения данных явного курсора и последующая модификация или удаление данных с помощью предложения WHERE CURRENT OF CURSOR. Подробнее об этой технологии рассказывается в главе 14.
Изменяются ли идентификаторы строк
А можно ли изменить значения идентификаторов строк? Хороший вопрос. Если вы считываете идентификатор строки и собираетесь использовать его в программе, то нужно знать, долго ли он останется неизменным. Физические идентификаторы, относящиеся к стандартным таблицам, которые организованы по принципу кучи, не меняются никогда. И лишь при условии, что вы удалите строку и вставите ее снова, ей будет назначен новый идентификатор. Но ведь только что было
сказано, что физические идентификаторы никогда не меняются! Нет ли здесь противоречия? Абсолютно никакого. Когда вы удаляете строку и вставляете ее снова, вы вставляете новую строку с теми же значениями, и она естественным образом получает новый идентификатор.
Логические идентификаторы строк, используемые в индекс-таблицах, не так долговечны, как физические. Более того, они могут меняться при изменении первичного ключа таблицы. Но если у вас нет привычки изменять первичные ключи, последнее обстоятельство на вас никак не повлияет.
Использование идентификаторов строк в Oracle Forms
В Oracle Forms идентификаторы строк можно использовать для доступа к строке базы данных, которая соответствует записи, отображаемой на экране. Когда вы добавляете новый блок базовой таблицы, в него в качестве «невидимого псевдоэлемента» автоматически включается идентификатор строки. Его нет в списке элементов, но на него можно ссылаться в триггерах и программах PL/SQL. Например, для того чтобы обновить на экране имя сотрудника, нужно вьшолнить следующую инструкцию:
UPDATE employee
SET last_name = :employee.last_nanie
WHERE rowid = : employ ее. rowid;
Использование идентификаторов строк в цикле FOR с курсором
Идентификатор строки можно использовать в цикле FOR с курсором (или в любом другом цикле, где производится выборка записей из курсора) для внесения изменений в только что выбранную строку:
PROCEDURE remove_i nterna1_competi tors IS
BEGIN~■
FOR emp_rec IN
(SELECT connections, rowid
FROM employee
WHERE sal > 50D000)
LOOP
IF emp_rec.connections IN ('President'. 'CEO')
THEN -
send_holiday_greeti ngs;
ELSE 一 一
DELETE FROM employee
WHERE rowid * emp_rec.rowid;
END IF:"
END LOOP;
ENO;
В этой процедуре в инструкции DELETE используется идентификатор строки, соответствующий записи emp_rec. Процедура удаляет записи о сотрудниках, чей оклад превышает 500 000 долларов, если они не являются ни президентом (President), ни главным администратором (СЕО — Chief Executive Officer). Обратите внимание, что разрешением на выполнение этой процедуры управляет администратор базы данных Вы можете также проверить, не связан ли он каким-то образом
с президентом или главным администратором? Как бы там ни было, использование идентификатора строк гарантирует наискорейшее удаление строки с информацией о выбранном сотруднике.
Конечно, в приведенной выше процедуре можно просто считать содержимое столбца с идентификатором сотрудника (первичный ключ таблицы сотрудников) и удалить строку с этим идентификатором:
DELETE FROM enployee WHERE employeejd = enpjd:
Нужны ли идентификаторы строк
Нельзя с полной уверенностью сказать, что теоретическое уменьшение времени поиска нужной записи при использовании идентификаторов строк доказывает целесообразность их применения. Ведь результирующий код в этом случае получается менее понятным, чем при использовании первичных ключей. Кроме того, работая над таким кодом, необходимо точно знать, в какой момент и в каких случаях идентификаторы строк будут изменяться. Мы слышали о программистах, хранивших идентификаторы строк в таблицах, все программы которых вдруг прекратили выполняться из-за того, что администратор базы данных в один прекрасный день импортировал или экспортировал данные с целью их реорганизации. Так что вне зависимости от того, будете вы пользоваться идентификаторами строк или нет, ни в коем случае не сохраняйте их в таблицах базы данных.
Еще одна проблема, которая возникает при использовании идентификаторов строк, связана с переносимостью приложений. Идентификаторы строк не поддерживаются стандартом ANSI SQL - они отражают лишь внутреннюю структуру данных в РСУБД Oracle. Применение идентификаторов строк в будущем может вызвать проблемы, связанные с переносимостью, поскольку другими, не-Oracle, базами данных они не распознаются и не поддерживаются. Поэтому, если создаваемое приложение должно подключаться к каким-либо другим источникам данных, избегайте ссылок на псевдостолбец ROW ID и использования типов данных R0- WID и UROWID.
Большие объекты данных
Oracle и PL/SQL поддерживают несколько разновидностей типов данных, предназначенных специально для работы с большими объектами (LOB, Large OBjects). Объекты типа LOB, или LOB-объекты, позволяют хранить огромные, порядка 4 Гбайт, объемы двоичных (например, изображений) или текстовых данных.-
В PL/SQL можно объявлять LOB-объекты четырех типов.
Q BFILE 一 двоичный файл. Переменная этого типа содержит локатор файла, указывающий на файл операционной системы, хранящийся вне базы данных. Oracle интерпретирует содержимое данного файла как двоичные данные.
Q BLOB — большой двоичный объект. Переменная этого типа содержит локатор LOB, указывающий на большой двоичный объект, хранящийся в базе данных.
О CLOB — большой символьный объект. Переменная этого типа содержит локатор LOB, указывающий на хранящийся внутри базы данных большой блок текстовых данных, которые состоят из однобайтовых символов.
Q NCLOB 一 большой символьный объект, который поддерживает средства работы с символами различных языков. Переменная этого типа содержит локатор L03, указывающий на хранящийся внутри базы данных большой блок текстовых данных, состоящих из однобаитовых символов или многобайтовых символов переменой длины.
Объекты типа L08 можно разделить на две категории: внутренние и внешние. Внутренние большие объекты (типы 8LOB, CLOB и NCLOB) хранятся в базе данных и могут участвовать в транзакции на сервере базы данных. Внешние большие объекты (тип BFILE) представляют двоичные данные, хранящиеся в файлах операционной системы вне таблиц базы данных. Они не мотут участвовать в транзакциях, т0 есть вносимые в них изменения нельзя сохранить или отменить в зависимости от результата транзакции. Поэтому целостность данных в случае использования типа 8FILE зависит от целостности данных файловой системы.
Тип данных В FILE
Тип данных BF1LE используется для доступа только к большим двоичным объектам (размером до 4 Гбайт), хранящимся в файлах вне базы данных. Переменные этого типа обеспечивают доступ в режиме байтового потока (и только для чтения) к файлам, находящимся на жестком диске, CD-ROM или другом устройстве.
При объявлении переменной типа 8FILE память выделяется для хранения локатора файла, но не его содержимого. Локатор файла включает псевдоним каталога и имя файла. Подробнее о локаторах файлов рассказывается далее в этой главе, в разделе «Особенности типа данных BFILE*. Вот как объявляется переменная типа 8FILE:
DECLARE
webj)age BFILE;
Тип данных BLOB
Тип данных BLOB предназначен для хранения больших двоичных объектов внутри базы данных. Данные, связанные со значениями типа 8LOB (кроме тех, для записи которых требуется минимальный объем памяти), хранятся《вне строк». Это озна- чает, что если в таблице имеется столбец типа BLOB, строка данных этой таблицы содержит указатель, или локатор, реального местоположения данных столбца.
Переменная типа BLOB представляет собой указатель на большой двоичный объект. Подобные объекты могут иметь размер до 4 Гбайт и принимать участие в транзакциях. Иными словами, любые изменения, вносимые в BLOB-объект (на- пример, с помощью программ из встроенного пакета DBMS_LOB), можно сохранить или отменить одновременно с другими изменениями, производимыми в ходе тран- 3aКции. Однако локатор BLOB должен быть локальным в пределах транзакции или сеанса. Объявляется переменной типа BLOB следующим образом:
DECLARE
photo BLOB:
Тип данных CLOB
Тип данных CLOB предназначен для хранения в базе данных больших блоков однобайтовых символьных данных; символьные данные переменной длины он не поддерживает. Как и BLOB-объекты, объекты CLOB хранятся «вне строк».
Переменная типа CLOB содержит указатель на большой блок символьных данных. Размер CLOB-объектов достигает 4 Гбайт, и они могут принимать участие в транзакциях. Как и BLOB-локаторы, локаторы CL08 не должны быть видимыми за пределами транзакции или сеанса. Ниже приведем пример объявления переменной типа CLOB:
DECLARE
directions CLOB:
ТИПЫ ДАННЫХ LONG И LONG RAW
Возможно, те из вас, кто уже знаком с Oracle, заметили, что мы до сих пор не упоминали о типах данных LONG и LONG RAW. И это не случайно. Конечно, в столбцах типа LONG базы данных можно хранить большие объемы (до 2 Гбайт) символьных данных, а в столбцах LONG RAW 一 двоичных. Однако максимальная длина соответствующих им переменных PL/SQL значительно меньше: всего лишь 32 760 байт, что даже немного меньше длины переменных типа VARCHAR2 и RAW (32 767 байт). Учитывая столь странное ограничение, в программах PL/SQL следует использовать переменные типа VARCHAR2 и RAW, а не типа LONG и LONG RAW.
Значения типа LONG и LONG RAW, извлекаемые из базы данных и содержащие свыше 32 767 байт данных, нельзя присваивать переменным типа VARCHAR2 и RAW. По этой причине типы данных LONG и LONG RAW лучше вообще не применять. Тем более что они относятся к числу устаревших и поддерживаются только для обеспечения обратной совместимости кода. Компания Oracle не рекомендует ими пользоваться, и мы с ней полностью согласны. В новых приложениях вместо них лучше использовать типы данных CLOB и BLOB. А для существующих приложений в документации Oracle9i Application Developer Guide — Large Objects (LOBs) приводятся инструкции по преобразованию данных типа LONG в данные типа LOB.
Тип данных NCLOB
Тип данных NCLOB предназначен для хранения больших блоков символьных данных -как однобаитовых, так и многобайтовых фиксированной и переменной длины. Между типами данных NCLOB и CLOB существует такое же соответствие, как и между символьными типами NVARCHAR2 и VARCHAR2. О наборах символов подробно рассказывается в главе 8.
ПРИМЕЧАНИЕ
В Огас1е8 тип NCLOB не поддерживает символьные данный переменно访 длины — такие данные используются начиная с версии Огас1е81.
Переменная типа NCL0B содержит указатель на большой блок символьных дан- ных. Объект типа NCLOB может иметь размер до 4 Гбайт и участвовать в транзакци- ях. Иными словами, любые изменения, вносимые в NCLOB-объект, можно сохранить или отменить вместе с другими изменениями, выполняемыми в ходе транзакции. Видимость локаторов BLOB и CLOB, как и NCLO8-локаторов, должна ограничиваться транзакцией или сеансом. А вот как объявляется переменная типа NCLO8: declare
di rections NCLOB:
Обработка больших объектов
Тема работы с большими объектами очень объемна, поэтому мы не сможем осветить все ее аспекты. Данный раздел следует рассматривать как введение в программирование больших объектов. Мы расскажем об их некоторых важных особенностях и приведем ряд примеров. Излагаемый материал может лишь послужить основой для ваших дальнейших исследований в этой области.
Сначала речь пойдет о типах больших объектов, имеющихся в распоряжении разработчиков программ PL/SQ.L. Затем мы поговорим о локаторах больших объектов и о том, как создавать объекты этих типов. Вы узнаете, как с помощью программ из пакета DBMS LOB манипулировать содержимым больших объектов, и познакомитесь с новым синтаксисом Oracle9i, позволяющим обрабатывать большие объекты с применением таких встроенных функций, как SUBSTR (хотя это и приводит с снижению быстродействия приложения, о чем и рассказывается в этом разделе).
Все представленные в данном разделе примеры основаны на таблице, созданной следующей SО,Ь-инструкцией;
CREATE TABLE waterfalls С
falls^name VARCHAR2C80).
fd1l5_photo BLOB.
falls^directions CLOB, falls_description NCLOB. falls_webj)age BFILE):
Таблица waterfalls содержит информацию о водопадах, расположенных в северной части штата Мичиган. Каждый столбец таблицы предназначен для хранения больших объектов одного из четырех типов. Любую фотографию можно представить в виде двоичных данных большого объема, поэтому для столбца fal 1 s_photo определен тип данных BLOB. Столбцы с информацией о местоположении и с описанием водопадов (faHs_directions и falls_descript1ons) содержат текстовые данные, поэтому определены соответственно как CLOB и NCLOB. В реальной таблице они, конечно, имели бы один и тот же тип. Коды web-страниц водопадов хранятся в HTML-файлах вне базы данных. Поэтому мы включили в таблицу столбец типа BHLE, содержащий указатели на зти файлы.
ВНИМАНИЕ При описании больших объектов мы будем часто использовать аббревиатуру LOB, обозначающую все описываемые типы данных — BLOB, CLOB, NCLOB и BFILE. А при обсуждении конкретного типа будет даваться его полное название.
Понятие локатора LOB
Важнейшая концепция типов данных LOB связана с понятием локатора LOB. Локатором называется хранящийся в базе данных указатель на данные большого объекта, которым может быть файл или даже значение. Чтобы понять принцип его действия, рассмотрим, что же происходит во время выборки данных из столбца типа BLOB и присвоения их переменной PL/SQL того же типа:
DECLARE
photo BLOB;
BEGIN
SELECT falls_photo•
INTO photo
FROM waterfalls
WHERE falls_name='Dryer Hose';.
Какое значение будет иметь переменная photo после выполнения этой инструкции SELECT? Будет ли в нее помещена сама фотография? Нет. В переменной будет храниться лишь указатель на двоичные данные. Результат выполнения инструкции SELECT представлен на рис. 12.2.
БазаданмькБазаданмьк;Указатель:на данные ;BLOB в базе ■ данных ';Указатель:на данные ;BLOB в базе ■ данных 'Программа PL/SQL
Базаданмьк
Базаданмьк
;Указатель
:на данные ;BLOB в базе ■ данных '
;Указатель
:на данные ;BLOB в базе ■ данных '
Переменная photo
Локатор LOB :
ASB4237D1CFF26 99Q10A3D0QQ099 0123456789ABCD
Рис 12.2. Локатор LOB указывает на объект в базе данных
Описанный выше принцип использования столбцов базы данных довольно уникален. Переменные и значения в столбцах типа LOB содержат локаторы больших объектов, которые идентифицируют реальные данные, хранящиеся в другом месте базы данных или вне ее. Для работы с данными типа LOB нужно сначала извлечь локатор, а затем с помощью программ встроенного пакета DBMS_LOB получить и/или модифицировать реальные данные. Вот что нужно сделать для того, чтобы получить двоичные данные фотографии, локатор которой хранится в столбце типа BLOB нашего примера.
Выполнить инструкцию SELECT и выбрать из базы данных LOS-локатор фотографии.
С помощью функции DBMS_LOB.OPEN открыть объект типа LOB.
Вызвать функцию DBM5_LOB, GETCHUNK51 ZE для получения оптимального (с точки зрения затрат на выполнение операции чтения или записи) размера фрагмента данных типа LOB.
Вызвать функцию DBMS_LOB.GETLENGTH, с тем чтобы определить количество байтов или символов объекта типа LOB.
Несколько раз вызвать функцию DBMS_LOB. READ для считывания данных типа LOB. 6. Закрыть ранее открытый объект типа LOB.
Правда, не все эти действия являются обязательными, и не волнуйтесь, если что-то вам пока непонятно. Далее мы расскажем о них подробнее.
Необходимость использования пакета DBMS_LOB для работы с LOB-локаторами делает применение больших объектов значительно более сложным, чем примене- ние обычных типов данных. Однако предлагаемый Oracle подход имеет существенные преимущества. Напомним, что в столбцах типа LOB можно хранить до 4 Гбайт данных. Представьте себе, что могло бы произойти, если бы данные хранились в самих таблицах и вы с помощью инструкции SELECT извлекали бы не локаторы, а 4-гигабайтовые значения. Во-первых, эти значения программе пришлось бы извлекать непосредственно при выполнении инструкции SELECT. Во-вторых, для их хранения потребовался бы огромный объем памяти (который не всегда имеется в распоряжении программы). А самое главное, все эти данные вам вряд ли потребовались бы одновременно. Например, изображения выводятся на экран обычно по одному, а не все сразу. И текст из большого столбца типа LOB также может считываться не целиком, а по частям. Но благодаря тому что в столбцах типа LOB хранятся не данные, а локаторы, запросы к таким столбцам производятся гораздо быстрее. Получив локатор, вы можете в любой момент считать нужные данные — несколько значений, одно или лишь часть значения. Если вам из 4-гигабайтового столбца типа LOB потребуются, скажем, 100 байт данных, вы сможете прочитать только эти 100 байт, а не весь столбец.
Итак, подытожим сказанное. В столбцах типа LOB содержатся указатели, а не сами данные, и инструкция SELECT считывает в переменную типа LOB, объявленную в PL/SQL, исключительно значение указателя.
ДОКУМЕНТАЦИЯ ORACLE, ПОСВЯЩЕННАЯ LOB-ДАННЫМ
Если вам часто приходится работать с большими объектами, настоятельно рекомендуем ознакомиться со следующими документами Oracle:
Oracle% Application Developer Guide — Large Objects (LOBs) — содержит полную информацию о программировании для LOB;
Oracle^i Supplied PL/SQL Packages and Types Reference — включает, в частности, главу с описанием пакета DBMS_LOB;
Oracle9i SQJL Reference — содержит важную информацию о LOB-объектах (см. раздел Datatypes в главе 2).
Это далеко не полный список документации об объектах LOB, но вы найдете в нем все наиболее важные сведения.
Большие объекты — пустые и равные NULL
Теперь, когда вы понимаете различие между локатором LOB и значением, на кото- Рое он указывает, мы введем еще одно важное понятие, а именно пустой объект типа LOB. Так называется локатор, не указывающий ни на какие данные. Это не то
же самое, что значение NULL, то есть LOB-столбец (или переменная), не содержащий локатора LOB. Рассмотрим пример:
SQL> DECLARE
directions CLOB;
BEGIN
IF directions IS NULL THEN
DBMS_OUTPUT.PUT_LINE(1 directions is NULL'):
ELSE -
DBHS_OUTPUT.PUT_LINE(1 di recti ons is not NULL');
END IF:
END;
/
directions is NULL.
Значение переменной directions типа CLOB, объявленной в данном блоке, равно NULL, поскольку ей пока не присвоено никакое значение. Выглядит все привычно, не так ли? Да и происходит все то же самое, что и при работе с данными любого другого типа, поэтому результатом объявления переменной без присваивания ей значения является NULL. Создадим для этой переменной локатор LOB. В следующем блоке кода выполняется инструкция INSERT, в которой вызывается функция ЕМР- TY_CLOB, а затем - инструкция SELECT, извлекающая из базы данных локатор, сгенерированный функцией EMPTY_CLOB. Мы сохраняем его в переменной directions. О том, почему мы действуем так неэффективно, рассказывается в следующем разделе. А пока сконцентрируем наше внимание на результирующих данных этого фрагмента кода.
DECLARE
directions CLOB;
BEGIN
--Удаление всех существующих строк со значением 'Munising Falls',
—что позэолит выполнить этот пример несколько раз
DELETE
FROM waterfalls
WHERE fans_name='Mun1s1ng Falls';
Вставка новой строки и вызов функции EMPTY_CLOB для создания локатора LOB
INSERT INTO waterfalls~
(falls_name.falls_d1rections)
VALUES ('HunTsing Fd〕ls;,EMPTY_CLOB());
--Считывание локатора LOB, созданного предыдущей инструкцией INSERT
SELECT falls_directions
INTO directions FROM waterfalls WHERE falls^name-'Munlsing Falls';
IF directions IS NULL THEN
DBMS_OUTPUT.PUT_LINE('directions is NULL'):
ELSE.
DBMS_OUTPUT.PUT_LINE('d1 rections is not NULL');’
END IF;
DBMS_OUTPUT.PUT_LI NE('Length ='
11 DBMS_LOB.GETLENGTH(directions)):
END;
Вот что выводит этот код:,
directions is not NULL
Length - 0
В данном примере встроенная функция EMPTY_CLOB возвращает локатор CLOB, который сначала сохраняется в базе данных, а затем считывается из нее. Переменная типа CLOB больше не равна NULL, поскольку теперь она содержит локатор. Однако результат вызова функции DBMS_LO8. GETLENGTH свидетельствует о том, что этот локатор не указывает ни на какие данные. Поэтому, хотя значение объекта directions и не равно NULL; он пока пуст. Как видите, это важное обстоятельство, из-за которого проверка на наличие данных в объекте LOB является более сложной задачей, чем при работе с другими типами данных.
В случае использования традиционных скалярных типов данных достаточно простой проверки условия IS NULL:
IF some_number IS NULL THEN.
--Вы знаете, что данных нет
Если результатом такой проверки значений типа NUMBER либо VARCHAR2 (или любого другого скалярного типа данных) оказывается значение TRUE — переменная пуста. Однако при работе с объектами типа LOB кроме этой проверки, позволяющей определить, содержит ли переменная локатор, необходимо еще проверить и длину строки данных, на которые этот локатор указывает:
IF sonie_clob IS NULL THEN
Нет ни данных, ни локатора.
ELSIF DBMS_LOB.GETLENGTH(some_clob) = О THEN
--Нет данных
ELSE
Наконец выяснили, что данные есть
ENO IF;
Как следует из этого примера, не зная значения локатора, нельзя проверить длину строки данных типа LOB. Для того чтобы определить, содержит ли объект данные, нужно сначала убедиться в наличии локатора, выполнив проверку IS NULL, а затем проверить длину объекта.
Создание больших объектов
В предыдущем разделе для создания локатора LOB использовался код
Вставка новой строки с помощью вызова функции EMPTY_CLOB для создания локатора LDB
INSERT INTO waterfalls-
(falls_name,falls_directions)
VALUES C'MurdsIng FalTs',EMPTY_CLOBO):
За ним следовала инструкция SELECT, предназначенная для выборки значений локатора из таблицы. Выглядит не слишком изящно, не так ли? У вас наверняка возникнет вопрос, почему нельзя было сразу сделать так:
directions := EMPTY CLOBC):
На то имеются основания. Еще раз напомним, что в переменной типа CLOB на самом деле содержится локатор, указывающий на реальные данные е базе данных, и что объекты LOB в оперативной памяти не хранятся — они содержатся на диске, в одном или нескольких файлах базы данных. Более того, параметры хранения конкретного объекта на диске задаются как часть определения содержащей его таблицы. Вызвав функцию EMPTY_CLOB(), получаем пустой LOB-объект (пожалуй, правильнее было бы сказать <пустой локатор»), то есть локатор, не указывающий на конкретное место на диске. После вставки этого пустого объекта в таблицу waterfalls Oracle определяет место его хранения (основываясь на информации, заданной администратором базы данных при определении таблицы)1 обновляет локатор, с тем чтобы он указывал на данное место, и сохраняет его в строке таблицы. И только теперь, когда процесс создания локатора LOB можно считать завершенным, его следует сохранить в переменной, то есть извлечь из таблицы waterfalls.
Сказанное отнюдь не означает, что в инструкцию INSERT нужно обязательно включить вызов функции EMPTY_CLOB(). Вы можете сначала присвоить результат вызова переменной типа CLOB, а затем вставить значение этой переменной в таблицу:.
--Вставка новой строки с помощью вызова фикции EMPTY_CLO8 для создания локатора LOB directions :- EMPTY_CL08(): INSERT INTO waterfaTls
(fall s_nanie .fall s_di recti ons)
VALUES ('Munising Falls'.directions):
После выполнения этого кода нужно обязательно обновить переменную di - rections, записав в нее значение столбца falls_d1 rections из таблицы waterfalls.
ПРИМЕЧАНИЕ Для создания пустого локатора BLOB используется функция EMPTY_BLOB(), а для создания пустых локаторов CLOB и NCLOB — функция EMPTY_CLOB(),
Начиная с версии Огас1е8г с объектами типа LOB можно работать, не добавляя строк в базу данных. В таких случаях создаются временные объекты LOB, о которых рассказывается ниже, в разделе《Временные объекты LOB».
Запись данных в объекты типа LOB
При наличии локатора данные записываются в LOB-объект с помощью одной из двух процедур пакета DBMS_LOB:
О DBMS_LOB.WRITE — запись производится с учетом заданного смещения;
О DBMS_LOB. WRITEAPPEND — данные добавляются в конец объекта.
В приведенном ниже коде (это расширенная версия примера, рассматриваемого в предыдущих разделах главы) сначала создается локатор LOB для столбца directions таблицы waterfalls, а затем функция DBMS_LOB.WRITE записывает данные в CLOB-поле строки <Munising Falls». Два следующих фрагмента данных дописываются в этот же столбец посредством функции DBMS_LOB. WRITEAPPEND:
DECLARE
directions CLOB:
amount BINARY」NTEGER;
offset INTEGER;
first_d1rection VARCHAR2(1DD);
inore_direct1ons VARCHAR2(5D0);
BEGIN -
удаление всех сущестзующих строк со значением 'Munising Falls', благодаря чему этот пример можно будет выполнить несколько раз DELETE FROM waterfalls'
WHERE falls^name-'Munising Falls':
.-Вставка новой строки и вызов функции EMPTY^CLOB для создания локатора LOB
INSERT INTO waterfalls
(fall5_narne,falls_directions)
VALUES (TMunis1ng Falls'.EMPTY_CLOBO):
-Считывание локатора LOB. созданного предыдущей инструкцией INSERT
SELECT falls_d-l recti ons INTO directions FROM waterfalls'
WHERE falls_name-'Munising Falls';
-Открытие объекта LOB: это делать необязательно, но лучше аккуратно открывать --и закрыаать LOB-объекты
DBMS_LOB.OPENCdirections, 0BMSJ0B.L0B_REA0WRITE);
-С помощью вызова OBMS_LOB.WRITE начинаем запись.
first_direction :- 'Follow 1-75 across the Mackinac Bridge.':
amount :- LENCTHCf 1 rst__d1 rection);— количество считываемых символов
offset := 1; -- считывание первого символа CLOB
DBMS_LOB.WRITE(di rect1ons, amount. offset. first_direction):
■ С помощью вызова DBMS_LOB.WRITEAPPEND добавляем дополнительные указания. more_directions 尸'Take US-2 west from St. Ignace to Blaney Park.'
11 ' Turn north on M-77 and drive to Seney.'
1' From Seney, take M-28 west to Munising.'; DBMSJDB.WRITEAPPENOCdi recti ons,
LENGTH(more_d1rect1ons). more_d1rectIons):
- И еще раз добавляем указания.
n»re_directions := ' In front of the paper mill. turn right on H-58.1 J J 1 Follow H-58 to Washington Street. Veer left onto' 11 ' Washington Street. You''ll find the Munising' 11 ' Falls visitor center across from the hospital at' 11 ' the point where Washington Street becomes' 11 1 Sand Point Road.1:
DBMS_LOB.WRITEAPPEND(d1rections, LENGTH(more_directions). more_directions); ■- Закрываем объект LOB и завершаем работу
DBMS LOB.CLOSE(directions):
В этом примере используются обе процедуры, WRITE и WRITEAPPEND. Однако на практике можно ограничиться лишь процедурой WRITEAPPEND, поскольку сначала объект не содержит никаких данных. Обращаем ваше внимание на то, как происходит открытие и закрытие объекта типа CLOB. Процедуры OPEN и CLOSE лучше вызывать явно, особенно при использовании утилиты Oracle Text. В противном случае создаваемые Oracle Text индексы будут обновляться при каждом вызове процедуры WRITE или WRITEAPPEND, а не один раз при вызове процедуры CLOSE.
ПРИМЕЧАНИЕ
В разделе, посвященном типу данных BFILE, рассказывается, как прочитать данные LOB прямо из внешнего файла операционной системы.
При записи данных в объекты типа LOB обновлять LOB-столбец в таблице нет необходимости, поскольку в нем, как мы уже не раз говорили, хранится локатор (который не изменяется), а не данные. Обновление выполняется в контексте транзакции. В данном примере мы не сохраняли результаты транзакции с помощью инструкции COMMIT. Однако если вы хотите, чтобы записанная в объект типа LOB информация была сохранена в базе данных, после выполнения блока команд PL/SQL нужно задать инструкцию COMMIT. Если же после блока команд PL/SQL выполнить инструкцию ROLLBACK, все произведенные в этом блоке изменения данных будут отменены.
В нашем примере производится запись данных в столбец типа CLOB. Данные типа BLOB обрабатываются точно так же, с той лишь разницей, что аргументы процедур WRITE и WRITEAPPEND имеют тип данных RAW, а не VARCHAR2. Еще один пример, реализованный в SQL*Plus, выводит на экран данные, помещенные ранее в таблицу waterfalls. Из следующего раздела вы поймете, как извлекать данные с помощью разных процедур пакета D8MS_L0B:
SQL> SET LONG 2000
SQL> COLUMN falls_directions WORD_WRAPPED FORMAT A70
SQL> SELECT falls_directions
FROM waterfaTls
WHERE fal 1 snam^' Muni sing Falls':
FALLS一DIRECTIONS
Follow 1-75 across the Mackinac Bridge. Take US-2 west from St. Ignace to Blaney Park.
Turn north on M-77 and drive to Seney. From Seney, take M-28 west to Munising.
In front of the paper mill, turn right on H-58. Fol low H-58 to Washington Street.
Veer left onto Washington Street. You'll find the Munising Falls visitor center across from the hospital at the point where Washington Street becomes Sand Point Road.
Считывание данных из объектов типа LOB
Чтобы данные из LOB-объекта стали доступными для программы, нужно вызвать процедуру DBMS_LOB.READ. Но сначала, конечно же, следует извлечь локатор LOB. При считывании данных типа. CLOB указывается смещение, задаваемое в символах. Операция считывания начинается с позиции, заданной смещением, а первый считываемый символ CLOB всегда имеет номер 1. В случае применения типа данных
BLOB смещение задается в байтах. Кроме того, в вызове процедуры DBMS_LOB. RfAO оПределяется количество считываемых символов или байтов. Поскольку объекты типа LOB могут быть очень большими, имеет смысл извлекать их содержимое по частям.
Рассмотрим фрагмент кода, в котором сохраняются и выводятся данные о местоположении водопада Munising Falls. Мы четко определили количество считываемых символов, с тем чтобы оно соответствовало ограничению на длину строки в пакете DBMS_OUTPUT и выводимый текст выглядел аккуратно.
DECLARE
di rections CLOB:
di rections_l VARCHAR2C300);
di rections_2 VARCHAR2C300):
• chars_readj ВI NARY_I NTEGER:
charsZreadZ? BINARYJNTECER;
offset INTEGER;
8EG】N
—Извлекаем ранее добавленный локатор LQB
SELECT falls_directions
INTO directions
FROM waterfalls
WHERE falls_name-'Mun1s1ng Falls':
—Начало чтения с позиции первого символа
offset := 1:
Попытка считать 229 символов. В переменную chars_read_l
~- будет записано реальное количество считанных символов
chars_read_l :- 229;
DBMS_LOB. READ(di rections. chars__read_l, offset, di recti ons_13:
Если считано 229 символов, информация о смещении обновляется
~- и производится попытка считать еще 255 символов
IF chars_read_l - 229 THEN
offset :- offset + chars_read_l;
chars_read_2 :- 255;
D8MS_L0B.READCdirections, chars_read_2, offset, directions 2);
ELSE ~~~
chars_read_2 ;- 0;
di rections_2 :=";
END IF; ~
--Вывод общего количества считанных символов.
DBMS_OUTPIJT.PUT_LINE('Characters read 工’||
TO_CHAR(chars_read_l + chars_read_2)):
Вывод значений переменных
口GMS ОШ'РиТ.рЩ' LINECdirections 1); DBMS_QUTPUT.PUT_LlNE(directions_2):
end; ~__
В результате выполнения этого кода будет выведен следующий текст:
Characters read = 414
Follow 1-75 across the Mackinac Bridge. Take US-2 west from St. Ignace to Blaney Park.
Turn north on М-77 and drive to Seney. From Seney, take M-20 west to Munising. In front of the paper mill. turn right on H-58. Follow H-58 to Washington Street. Veer left onto Wsshington Street. You'll find the Munising Falls visitor center across from the hospital at the point where Washington Street becomes Sand Point Road.
Процедуре DBMS_LOB.READ в качестве второго параметра, объявленного здесь как IN OUT, передается переменная chars_read_l (количество считанных символов). По окончании операции процедура обновляет его таким образом, чтобы отразить реальное количество прочитанных символов. Если количество прочитанных символов (или байтов) окажется меньше указанного вами, то это будет означать, что считаны все данные, хранящиеся в объекте. К сожалению, обновить таким же способом информацию о смещении невозможно. Вы должны это делать самостоятельно, по мере считывания последовательных фрагментов объектов типа LOB, основываясь на количестве прочитанных символов.
ПРИМЕЧАНИЕ Для определения размеров больших объектов можно воспользоваться функцией DBMS_LOB.GET- LENGTH(noKaTop_LOB). Возвращаемое ею значение равно количеству байтов объекта BLO0 или BFILE либо количеству символов объекта CLOB.
Особенности данных типа В FILE
Как уже упоминалось ранее, типы данных BLOB, CLOB и NCLOB представляют собой внутренние большие объекты, хранящиеся в базе данных, в то время как BFILE является внешним типом данных. Попытаемся определить, чем объекты BFILE отличаются от внутренних объектов типа LOB в первую очередь.।
О Значение BFILE хранится не в базе данных, а в файле операционной системы.
О Объекты BFILE не участвуют в транзакциях (то есть внесенные в них изменения нельзя ни отменить, ни сохранить в транзакции). Однако изменения, внесенные в локатор BFILE, в транзакциях можно и отменять, и сохранять.
О И в PL/SQL, и в Oracle объекты BFILE можно только считывать. Записывать их Oracle не позволяет. Внешние файлы, на которые указывают локаторы BFILE, нужно создавать вне СУБД Oracle.
Работая с данными типа BFILE в PL/SQL, вы фактически работаете с локатором LOB, который просто указывает на хранящийся на сервере файл. Поэтому в разных строках таблицы базы данных в столбце типа BFILE могут быть записаны указатели на один и тот же файл.
Значение локатора BFILE состоит из псевдонима каталога и имени файла. Формирует локатор функция BFILENAME, которая принимает два указанных параметра в качестве аргументов. Псевдоним каталога - это просто определенное в Oracle имя, соответствующее имени каталога операционной системы. Псевдонимы позволяют программам PL/SQL работать с каталогами независимо от файловой системы. Имея привилегию CRRTE ANY DIRECTORY, вы можете создать псевдоним каталога и предоставить доступ к нему другим пользователям:
CREATE DIRECTORY bfile_data AS 'c:\PLSOL Book\Chl2_Misc_Datatypes1;
GRANT READ ON DIRECTORY bf11e_data TO gennick;
Создание псевдонимов каталогов и управление доступом к ним — это функции администратора базы данных, не имеющие прямого отношения к программированию на PL/SQL. Поэтому мы не затрагиваем данную тему. Если же вам нужны более подробные сведения, поговорите с администратором базы данных или же ознакомьтесь с разделом документации Oracle SQL Reference Manual, посвященным команде CREATE DIRECTORY.
Создание локатора BFILE
Локатор BFILE создается очень просто: нужно вызвать функцию BFILENAME и передать ей псевдоним каталога и имя файла. В отличие от локаторов других типов L0& его не нужно сохранять в базе данных и считывать перед использованием. Вот как создается локатор 8FILE для HTML-файла с web-страницей водопада Tannery Falls, который затем сохраняется в таблице waterfalls.
DECLARE
web_page BFILE;
BEGIN -
-Удаление строки Tannery Falls.
-благодаря чему этот 叩ммер мокно будет аыполнить несколько раз
DELETE FROM waterfalls WHERE falls_name-'Tannery Falls';
Вызов функции BFILENAME для создания локатора BFILE-
web_page := BFILENAMEC'BFILE_DATA'.'Tannery Falls.htm');■
-Сохранение нового локатора в таблице waterfalls
INSERT INTO waterfalls (falls_name, falls_web_page)
VALUES ('Tannery Falls',web_page);
END;
Объединив псевдоним каталога и имя файла, получаем значение локатора BFILE. Реальный файл и каталог не обязательно должны существовать. Иными словами, Oracle позволяет создавать псевдонимы для несуществующих каталогов, a BFILENAME — локаторы для несуществующих файлов. Иногда это очень удобно.
ВНИМАНИЕ
Функция BFILENAME чувствительна к регистру символов, поэтому имя псевдонима каталога должно в точности соответствовать имени псевдонима, возвращаемому представлением ALLDIRECTORIES из словаря данных. Выполняя этот пример, мы сначала указали псевдоним каталога как bfile_data, но при попытке получить доступ к данным внешнего файла увидели сообщение об ошибке. В большинстве случаев при вызове функции BFILENAME псевдоним каталога задается в верхнем регистре.
Доступ к данным BFILE
Получив локатор BFILE, вы можете считывать данные из внешнего файла практически таким же образом, как из объекта BLOB. Единственным различием является то, что перед считыванием файл обязательно нужно открыть с помощью функции DBMS_LOB.FILEOPEN, а по окончании работы закрыть посредством функции DBMS_ LOB. FILECLOSE. В следующем примере извлекаются первые 60 байт HTML-кода web-страницы водопада Tannery Falls. Результирующие данные, имеющие тип RAW, преобразуются в символьную строку с помощью функции UTL_RAW. CAST_TO
VARCHAR2:" 一 -
DECLARE
web_page BFILE:
html RAW160);
amount BINARYJNTEGER 产 60;
offset INTEGER :и 1:
BEGIN
--Считываем LOB-локатор web-страницы
SELECT faHs_web_page
INTO web_page
FROM waterfalls
WHERE falls_name='Tannery Falls';
--Открываем файл, считываем 60 байт и закрываем файл '
DBMS_LDB.OPEN(web_page):
DBMS_LOB.READ(web_page, amount, offset, html);
OBMS_LOB.CLOSE(web_page);
―Для выаопа результатов в шестнадцатеричном коде удалите
--символы комментария из следующей строки:.
--DBMS_OUTPUT.PUT_LINE(RAHTOHEX(html)):
--Преобразуем данные типа RAW в читабельную символьную строку
DBMS OUTPUT.PUT_LINE(UTL RAW.CAST_TO_VARCHAR2(html));
END: ~~~ ~
Вот что выводит этот код:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN
Максимальное число одновременно открытых в сеансе объектов BFILE устанавливается параметром базы данных SESSION_MAX_OPEN_FILES. Этот параметр определяет верхнюю границу количества файлов, которые могут быть одновременно открыты в сеансе (причем файлов не только типа BFILE, но и любых других типов, включая и те, что открываются с применением программ из пакета UTL_FILE),
Напомним еще раз, что из базы данных Oracle данные BFILE можно только считывать, Тип данных BFILE идеально подходит для доступа к двоичным данным, таким как коллекции изображений, сгенерированные вне СУБД. В частности, вы можете загрузить на сервер изображения, отснятые с помощью цифровой камеры, и создать локаторы BFILE для доступа к этим файлам из программ PL/SQL.
Использование BFILE для загрузки столбцов LOB
Тип данных BFILE не только обеспечивает доступ к двоичным файлам, созданным вне базы данных Oracle, но и является удобным средством для загрузки внешних файлов в столбцы данных типа LOB. В Oracle9i Release 1 для чтения двоичных данных из объекта BFILE и записи их в столбец BLOB использовалась функция OBMS_LOB. LOADFROMFILE, в Oracle9i Release 2 для этих целей имеются более удобные функции.
О Функция DBMS_LOB. LOADCLOBFROMFILE загружает в столбец типа CLOB данные из объекта BFILE; обеспечивает необходимое преобразование символов.
О Функция DBMS_LOB.LOADBLOBFROMFILE загружает в столбец типа BLOB данные из объекта BFILE; выполняет те же действия, что и DBMS_LOB. LOADFROMFILE, но ее интерфейс согласован с интерфейсом функции DSMS_LOB. LOADCLOBFROMFILE.
Представьте себе, что сведения а местоположении водопада Tannery Falls за- писаны во внешний текстовый файл TanneryFalls.directions, хранящийся в каталоге, на который указывает псевдоним BFILE_DATA. Следующий пример показывает, как с помощью функции DBMS__LOB.LOADCLOBFROMFILE данные из этого файла можно записать в столбец falls_directions типа CLOB таблицы waterfalls.
declare
Tannery^Fal1 s_Di rections BFILE
:-BFILENAMEC'BFILE_DATA','TanneryFalIs.directions');
directions CLOB:
destination_offset INTEGER ;= 1;
source^offset INTEGER := 1;
language_context INTEGER :- DBMS_LDB.defau11J ang_ctx;
warning_message INTEGER;
BEGIN ~
Удаляем строку Tannery Falls, что позволит выполнить этот пример несколько раз DELETE FROM waterfalls WHERE falls_name='Tannery Falls';
--Вставляем новую строку, используя функцию EMPTY_CLOB для создания локатора LO0
INSERT INTO waterfalls-
(falls_name,falls_direct1ons)
VALUES ('Tannery Nlfs',EMPTY_CLOB。):
--Изалекаеи локатор LOB. созданный предыдущей инструкцией INSERT
SELECT falls_di recti ons
INTO directions
FROM waterfalls
WHERE falls^a^'Tannery Falls';
--Открываем целевой объект CLOB и исходный BFILE.
DBMS_L0B.0PEN(direct1ons. DBMS_LOB.LOB_READWRITE);
DBMS_LOB. OPEN(Tannery_Fall s_Di recti ons"):
--Загружаем содержимое BFILE в столбец CLOB
DBM5_LUB.LDADCLOBFROMFILECdirections. Tannery_Falls_Directions, DBMS_LOB.LOBMAXSIZE, destination_offset. source_offset. NLS_CHAR5ET_ID('US7ASCII'),-~
language_context. warning_message);
--Проверяем наличие единственно возможного лреаупремаения
IF warning_message = DBMS_LOB.WARN_INCONVERTI8LE_CHAR THEN dbms_output.put」ine(
'Warning! Some characters couldn''t be converted.'):
END IF:
--Закрываем оба объекта типа LOB.
DBMS_LOB.CLOSEC di recti ons);
E DBMS_LOB,CLDSE(Tannery_Fal]s_Directions);
Работа приведенного выше фрагмента кода начинается фактически с вызова Функции DBMS_LOB. LOADCLOBFROMFILE. Эта процедура считывает данные из внешнего
файла, выполняет необходимые преобразования символов и записывает данные в столбец типа CLOB. Для определения количества загружаемых данных мы воспользовались новой константой — DBMS_LOB. LOBMAXSIZE. Поскольку требуется загрузить все данные из внешнего файла, значение константы DBMS_LOB.L0BMAX5IZE должно быть равным максимально допустимому размеру данных типа CLOB.
Для источника и приемника данных задается одинаковое смещение — 1. Благодаря этому данные теперь можно считывать, начиная с первого символа объекта BFILE, а записывать — начиная с позиции первого символа объекта типа CLOB. Для обеспечения возможности повторных вызовов функции LOADCLOBFROMFILE процедура обновляет оба эти значения смещения таким образом, чтобы они указывали на первый еще не считанный символ. Поскольку смещение в обоих случаях задается как IN OUT, в вызове процедуры его нужно указывать как переменную, а не константу.
Функция NLS_CHARSET_ID возвращает идентификатор набора символов, используемого во внешнем файле. Он необходим процедуре LOADCLOBFROMFILE для преобразования прочитанных данных в кодировку символов базы данных. Единственное предупреждение, которое может вернуть процедура LOADCLOBFROMFILE, — это сообщение о том, что некоторые символы не поддаются преобразованию. Наличие такого предупреждения мы проверяем посредством оператора IF, выполняемого после загрузки данных.
ПРИМЕЧАНИЕ Предупреждение не относится к категории сообщений об ошибках PL/SQL; даже несмотря на его наличие, загрузка все равно завершается.
Рассмотрим реализованный в SQL*Plus пример, демонстрирующий, какие данные загружаются из внешнего файла с помощью процедуры LOADCLOBFROMFILE.
SQL> SET LONG 2000
SQL> COLWN falls_directions WORD_WRAPPED FORMAT A70
SQL> SELECT falls_directions
FROM waterfaTls
WHERE fal1s_name-'Tannery Falls':
FALLS DIRECTIONS
From downtown Munising, take Munising Avenue east. It will shortly turn Into H-58.
Watch for Washington Street veering off to your left. At that Intersection you'll see a wooden stairway going into the woods on your right. Go up that stairway and follow the trail to the falls. Do not park on H-58! You'll get a ticket. You can park on Nestor Street. which is just uphill from the stairway.
Временные объекты типа LOB
До сих пор речь шла о долговременном хранении больших объемов неструктурированной информации, ставшем возможным благодаря использованию разных типов данных LOB. Применяемые при этом объекты LOB называются постоянными. Однако многим приложениям требуются временные LOB, используемые в качестве локальных переменных и не хранящиеся в базе данных. В этом разделе рассказывается о временных объектах LOB и о том, как ими оперировать с помощью программ встроенного пакета DBMS LOB.
В Oracle8i и последующих версиях системы посредством интерфейса Oracle Call Interface (OCI) и вызова функций DBMS_LOB реализована возможность создания, освобождения, считывания и обновления временных объектов типа LOB. По умолчанию временный объект LOB существует в течение всего сеанса, в котором он создан, хотя его можно явно освободить и до окончания сеанса. Временные объекты типа LOB являются удобной рабочей средой для быстрого выполнения различных операций над данными. Более высокая их производительность по сравнению с постоянными объектами LOB достигается за счет того, что эти операции не протоколируются и специальные записи для их отмены не генерируются. При перезаписи или обновлении объектов типа LOB Oracle копирует весь объект в новый сегмент данных. Избавившись от дополнительных операций, связанных с протоколированием и подготовкой данных при отмене определенных ранее действий, приложение, выполняющее множество мелких операций над объектами типа LOB, может работать значительно быстрее.
Только что созданный временный объект LOB всегда пуст — для установки его локатора не нужно (да и невозможно) вызывать функции EMPTY_CLOB и EMPTY一 BLOB. По умолчанию все временные объекты типа LOB удаляются в конце сеанса, в котором они созданы. Если процесс неожиданно аварийно завершится или произойдет сбой базы данных, временные объекты типа LOB будут удалены и занимаемое ими простраяство памяти освободится.
Временные объекты типа LOB похожи на постоянные тем, что хранятся на диске в базе данных. Понятие «временные» не должно вводить вас в заблуждение. Временные объекты типа LOB записываются на диск, но не связываются с определенными столбцами в таблицах, располагаясь во временном табличном пространстве сеанса. Поэтому для их использования нужно обеспечить наличие достаточного объема временного табличного пространства.
Процесс создания и освобождения временных объектов типа L08 будет рассмотрен ниже. Затем вы узнаете, как выяснить, на временный или постоянный объект указывает локатор L08. А напоследок мы расскажем о некоторых приемах администрирования, связанных с обработкой временных объектов.
Создание временного объекта типа LOB
Для того чтобы получить экземпляр временного объекта, нужно вызвать процедуру D0MS__LOB. CREATETEMPORARY, создающую в используемом по умолчанию временном табличном пространстве временный объект типа BLOB или CLOB и соответствующий ему индекс. Синтаксис этой процедуры приведен далее, а ее параметры описаны в табл. 12.1.
O0MS_LOB.CREATETEMPORARY (
lob loc IN OUT NOCOPY [SLOS | CLOB CHARACTER SET ANY_CS ], cache IN BOOLEAN."
dur IN PLS_rNTEGER :- DBMSJOB.SESSION)
Существует еще один способ создания временного объекта типа LOB, а именно объявление в программе PL/SQL LOB-переменной и присвоение ей значения. Например, следующий код создает два временных объекта - типа BLDB и типа CLOB:
DECLARE
templelob CLOB:
temp 二blob BLOB;
BEGIN
-Присвоение значения неинициализироаанной переменной
-CLOB или BLOB позволяет указать PL/SQL на необходимость неявно создать
-временный объект LOB,существующий только в течение текущего сеанса
temp clob :-' http://www.nps.gov/p1ro/':
temp二blob ;= HEXT0RAW('7A');
END;-
Оба способа создания временных больших объектов равноправны, если не считать того факта, что вызов функции DBMS__LOB. CREATETEMPORARY более определенно говорит о намерениях программиста.
Таблица 12.1. Параметры процедуры CREATETEMPORARY
Параметр
Описаниа
lobjoc Cache
Dur
Локатор LOB
Указывает, следует ли поместить объект LOB в буферный кэш
Определяет время жизни LOB. Его значением может быть одна из двух именованных констант: DBMS_LOB.SESSION, указывающая, иго временный LOB должен быть удален в конце сеанса (это значение используется по умолчанию^ или DBMS_LOB.CALL, указывающая, что таковой должен быть удален в конце текущего вызова программы, в которой он был создан
Освобождение временного объекта типа LOB
Процедура DBMS_LOB. FREETEMPORARY освобождает временный объект типа BLOB или CLOB в текущем временном табличном пространстве. Ее синтаксис таков:
PROCEDURE OBMSJOB.FREETEMPORARY (
lobjoc IN OUT NOCOPY
~ [BLOB I CLOS СНШСТЕК SET ANY_CS ])
Приведем пример, в котором сначала создаются, а затем явно освобождаются два временных объекта типа LOB.
DECLARE
teiip/lob CL08:
teiip_blob BLOB;
BEGIN -
-Присвоение значения неинициапизированной переменной
-CLOB или BLOB указывает PL/SQL на необходимость неязного создания
-временного обьекта ЮВ(существующего только в течение текущего сеанса
tempedob:-'http://www.exploringthenorth.сот/alger/alger.html';
temp二blob 产 HEXT0RAW(7A');
DBMS_LQB.FREETEMPORARY(temp_clob):
DBMS LD8.FREETEMPORARY(temp"blob):
END:-
После вызова процедуры FREETEMPORARY освобожденный локатор LOB (lob_l ос) помечается как недействительный. Если вы присвоите его другому локатору LOB с помощью обычного оператора присваивания PL/SQL, то и этот локатор будет освобожден и помечен как недействительный.
ПРИМЕЧАНИЕ p〃5QL неявно освобождает временные объекты типа LOB, когда они выходят за пределы области видимости 6/юка,
Как определить, является ли объект типа LOB временным
функция ISTEMPORARY позволяет определить, объект какого типа — временный или постоянный - идентифицируется заданным локатором LOB. Она возвращает цеЛОе значение 1, если объект временный, и 0, если он постоянный. Синтаксис указанной функции таков:
PROCEDURE DBMSJOB. ISTEMPORARY《
lobjoc IN [BLOB I CLOB CHARACTER SET ANY_CS ])
RETURN INTEGER"
Эта функция вызывается из SQL; вероятно, поэтому в Oracle она не определена как логическая.
Управление временными объектами типа LOB
Временные большие объекты обрабатываются не так, как обычные постоянные. Для них не поддерживаются транзакции, операции согласованного чтения, отката и т. д. Такое сокращение количества функциональных возможностей имеет ряд следствий.
О Если при обработке временного объекта типа LOB возникает ошибка, этот объект следует освобождать и начинать все сначала.
О Одному временному объекту LOB нельзя присвоить несколько локаторов. Отсутствие поддержки операций согласованного чтения и отката при использовании нескольких локаторов может вызвать значительное снижение производительности.
О Если пользователь модифицирует временный объект типа LOB, на который указывает еще один локатор, создается копия данного объекта, называемая в Oracle глубокой копией. После этого локаторы будут идентифицировать разные объекты. Для минимизации количества глубоких копии при передаче LOB- локаторов в качестве аргументов используйте директиву компилятора NOCOPY.
Q Для того чтобы преобразовать временный объект в постоянный, нужно вызвать программу DBMS_LOB. COPY и скопировать временный объект типа ЮВ в постоянный.
Q Временные LOB-локаторы постоянны на протяжении всего сеанса. Их нельзя передать из одного сеанса в другой (например, через канал базы данных), чтобы сделать соответствующий объект видимым в этом, втором, сеансе.
В Oracle появилось новое представление VSTEMPORARY^LOBS, определяющее, какое количество кэшируемых и некэтпируемых больших объектов активно в текущем сеансе. Администратор базы данных может, объединив информацию представле- внй V$TEMPORARY_LOBS и DBA_SEGMENTS, узнать, сколько дискового пространства используется для хранения временных объектов типа LOB.
Встроенные операции над объектами типа LOB в Oracle9l
Со времени появления больших объектов программисты мечтали оперировать с ними как с обычными скалярными переменными. В частности, они хотели бы обрабатывать объекты типа CLOB как большие символьные строки, передавая их SQL-функциям или, скажем, используя в предложениях WHERE. Однако)к большому сожалению, объекты типа CL05 невозможно было использовать в качестве переменных типа VARCHAR2. Например, в Огас1е8 и Oracle8z значения в столбцах типа CLOB нельзя было обрабатывать с помощью символьных функций:
SELECT SUBSTRCfalls_d1rections.1.60)
FROM waterfalls;.
Однако начиная c Oracle9z Release 1 появилась возможность во многих случаях работать с данными типа CLOB так же, как с данными типа VARCHAR2.
О Объекты типа CL08 можно передавать большинству функций SQL и PL/SQL, предназначенных для работы с данными типа VARCHAR2. '
О В PL/SQL (но не в языке SQL) с LOB-переменными можно использовать разнообразные операторы сравнения, например операторы < (меньше), > (больше) и - (равно).
О Значения типа CLOB можно присваивать переменным типа VARCHAR2 (или наоборот) с помощью оператора присваивания или инструкции SELECT.. .INTO. Это стало возможным благодаря тому, что PL /SQL выполняет неявное преобразование типов данных VARCHAR2 и CLOS.
ПРИМЕЧАНИЕ
Такие новые свои возможности Oracle называет поддержкой SQL-семантики для LOB-объектов. Это означает, что теперь программисты, использующие PL/SQL, могут управлять объектами типа LOB с помощью встроенных операторов, а не программ отдельного пакета.
Следующий пример демонстрирует некоторые новые возможности Oracle9i при работе с большими объектами:
DECLARE.■
name CLOB;
name_upper CLOB:
directions CLOB:
blank space VARCHAR2(1) 1 ';'
BEGIN.
-Считываем данные типа VARCHAR2 8 объект CLOB. вызываем функцио для обработки объекта CLOB
SELECT fal1s_nairie, SUBSTRCfalls_d1rect1ons.l.500)_
INTO name, directions'
FROM waterfalls
WHERE falls_name - 'Munising Falls1;
■ Преобразуем текст объекта типа CLOB в символы верхнего регистра
-Сравниваем два объекта CLOB
IF name - name_upper THEN
DBMS OUTPUT.PUT_L1NEC'We did not need to uppercase the name.');
END IF;" ■-
-Объединяем оОьект типа CLOB со строками VARCHAR2.
IF INSTRtdlrectlons.'Mackinac Srldgel) о 0 THEN
D0MS_aUTPUT.PUTMLINE('To get to || name_upper || blank^space " I j '>au"niust cross the Mackinac Bridge.'):"
END IF:
END;
В результате реализации этого примера выводится следующее сообщение:
То get to MUNISING FALLS you must crass the Mackinac Bridge.
Приведенный фрагмент кода выполняет несколько интересных операций:
О считывает значения типа VARCHAR2 из столбца falls^name в переменную типа CLOB (это пример неявного преобразования типов данных VARCHAR2 и CLOB);
О с помощью функции SU8STR ограничивает длину извлекаемого из столбца falls_di recti ons текста 500 символами, а затем & помощью функции UPPER преобразует название водопада в символы верхнего регистра (это примеры применения к большим объектам функций SQL и PL/SQL);
О сравнивает значения переменных патле и name_upper в операторе IF, демонстрируя таким образом возможность применения операторов сравнения к объектам типа LOB;
О представленное в символах верхнего регистра название водопада, имеющее тип CLOB, объединяет с двумя строковыми константами и одной переменной типа VARCHAR2 (это пример конкатенации строк объекта типа CLOB).
Однако применение описанных возможностей связано с рядом трудностей и ограничений. В частности, не каждой функции, выполняющей обработку строк типа VARCHAR2, можно передавать значения типа CL09. Сказанное касается и операторов сравнения: не все они применимы к большим объектам. Эти и ряд других □граничений подробно описаны в разделе SQL Semantics Support for LOBs главы 7 документации no Oracle Modeling and Design Application Developer's Guide. Тем, кто использует новые возможности системы, мы настоятельно рекомендуем следовать приведенным там советам.
ПРИМЕЧАНИЕ Описываемая выше семантика SQL применима только к внутренним объектам типов CLOB, BLOB и NCLOB. На объекты типа В FILE она не распространяется.
Результатам использования SQL-семантики может стать создание временных объектов типа LOB
Используя при работе с большими объектами SQL-семантику, важно помнить, что в результате этого часто создается временный объект LOB. В качестве примера рассмотрим код, выполняющий передачу значения типа CLDB функции UPPER: declare
directions CLO0:
BEGIN
SELECT UPPER(falls__d1 rectians)
INTO directions
FROM waterfalls
WHERE falls_name * 'Munising Falls':
В связи с тем, что размер значений типа CLOB может быть очень большим, они всегда хранятся на диске. Сразу преобразовать извлекаемое значение в символы верхнего регистра Oracle не может, поскольку это означало бы изменение значения в базе данных. Невозможно изменить его и в оперативной памяти, поскольку из таблицы базы данных извлекается только локатор, а не само значение. Так что программному обеспечению базы данных ничего не остается, как только создать временный объект типа CLOB во временном табличном пространстве текущего сеанса. Функция копирует исходное значение типа CLOB во временный объект и там преобразует его в символы верхнего регистра. Затем инструкция SELECT возвращает локатор LOB, идентифицирующий временный объект типа CLOB, а не исходный объект в базе данных. Этот факт имеет два важных следствия.
О Возвращенный функцией или выражением локатор нельзя использовать для обновления исходного большого объекта. Переменная directions в нашем примере не используется для обновления постоянного объекта LOB, хранящегося в базе данных, поскольку она указывает на временный объект, возвращенный функцией UPPER.
О На создание временных объектов типа LOB, размер которых может достигать десятков и сотен мегабайтов, затрачиваются дисковое пространство и ресурсы процессора. Мы подробнее обсудим этот вопрос ниже, в разделе «Влияние SQL-семантики на производительность».
Если мы хотим получить возможность извлекать содержимое столбца falls_d1 - rections, представленное в символах верхнего регистра, и при этом сохранить возможность обновлять данный столбец, нам потребуются два локатора LOB:
DECLARE
di rections^upper CLOB;
di rectionsj)ersistent CLOB:
BEGIN
SELECT UPPERCfalls_d1rections), falls_directions
IНТО di rections_upper. di rections_persistent
FROM waterfalls
WHERE faHs_name - 'Munising Falls';
END;-
Теперь вы можете получить доступ к тексту, представленному символами верхнего регистра, с помощью локатора directions_upper и модифицировать исходный текст, воспользовавшись локатором di rections_pers1 stent. При этом операция считывания дополнительного локатора никак не снижает быстродействие приложения. Небольшая задержка возникает только во время преобразования текста в символы верхнего регистра и записи его во временный объект типа CLOB. Локатор в переменной di recti ons_persi stent — это просто указатель на значение в таблице базы данных.
В общем случае строковые функции, выполняющие обработку значений типа VARCHAR2, при получении значения типа CLQB возвращают временные объекты CLOB. Так происходит и при вычислении выражений, результатом которых является значение типа CLOB.Временные объекты типа CLOB и BLOB нельзя использовать для обновления исходных значений, заданных в вызове функции или выражения.
Влияние SQL-семантики на производительность
Прежде зем использовать новую SQL-семантику для работы с данными больших объектов, стоит подуматься, как это отразится на производительности приложен ния. Напомним, что буква L в аббревиатуре LOB означает «large», то есть «большой», а точнее, объемом до 4 Гбайт. Следовательно, работая с большими объекта- ми как с данными обычных типов, можно столкнуться с серьезными проблемами. Рассмотрим запрос, возвращающий названия водопадов) для посещения которых может потребоваться переезд через мост Mackinac Bridge:
SELECT falls^name
FROM waterfalls
WHERE INSTR(UPPER(falls_d1rections).'MACKINAC BRIDGE') <> 0;
Какие действия должна произвести СУБД, чтобы выполнить этот запрос. Сначала необходимо извлечь содержимое всех строк столбца fal Is_directions таблицы waterfalls, преобразовать его в символы верхнего регистра и поместить результат во временный объект типа CLOB (во временном табличном пространстве). Затем, вызвав функцию INSTR и передав ей содержимое временного объекта, нужно найти строку MACKINAC BRIDGE. В наших примерах ширина столбца falls_directions невелика, но представьте, что было бы, если бы его размер составлял, скажем, 1 Гбайт. Сколько места понадобилось бы для хранения во временном табличном пространстве всех временных объектов типа L08I И сколько времени потребовалось бы на их копирование, преобразование их содержимого в символы верхнего регистра, на выделение для этих объектов места во временном табличном пространстве и для его освобождения, а также на посимвольный просмотр данных функцией INSTR! Не удивляйтесь, если выполнение ряда подобных запросов вызовет недовольство администратора базы данных.
ORACLE TEXT И СЕМАНТИКА SQL
Повысить эффективность поиска нужной строки, преобразованной в символы верхнего регистра, в объекте типа CLOB можно с помощью утилиты Oracle Text. Предположим, вам требуется выполнить такой запрос:
SELECT falls_name
FROM waterfalls
WHERE INSTR(UPPER(falls_directions).
'MACKIMAC BRIDGED 6 0;
Если столбец fa Hs_di recti ons имеет тип CLOB, этот запрос будет не очень эффективным. Однако определив с помощью Oracle Text для указанного столбца нечувствительный к регистру индекс и воспользовавшись предикатом CONTAINS, вы сможете организовать более результативный поиск данных: SELECT falls_name
FROM waterfalls
WHERE
CONTAINS(fal1s_directions,1mackinac bridgel) > 0:
Более полная информация о предикате CONTAINS и нечувствительных к регистру индексах Oracle Text содержится в документации Oracle Text Application Developer's Guide.
Поскольку использование SQL-семантики при работе с большими объектами может привести к значительному снижению производительности приложения, согласно документации Oracle не следует применять ату семантику к объектам размером свыше 100 Кбайт. Мы же не даем никаких конкретных рекомендаций относительно размера объектов) а предлагаем оценивать преимущества и недостатки данного подхода в каждом конкретном случае. Причем принять то или иное решение необходимо еще до начала разработки программы, и, возможно)вам для этого придется провести ряд тестов.
Функции преобразования объектов типа LOB
СУБД Oracle предоставляет в ваше распоряжение несколько функций, предназначенных для преобразования больших объектоэ. Перечень этих функций и их краткие характеристмки ххриэедены в табл. 12.2.
Таблица 12.2. Функции преобразования больших объектов'
Функция
Описание
ТО_СШВ(симвальныеланные)
Преобразует символьные данные в данные типа CLOB. Входным параметром функции TO_CLOB должно быть значение типа VARCHAR2, NVARCHAR2, CHAR, NCHAR, CLOB или NCLOB, При необходимости (например, когда значение имеет тип NVARCHAR2) входные данные, представленные символами национального набора, преобразуются в набор символов базы данных
ТО_ВЮВ(данные_.типв_гв«)
Подобна функции TO_CLOB; преобразует данные типа RAW или LONG RAW в данные типа BLOB
ТО^С1ОВ(символьные.данные)
Подобна функции TO_CLOB, отличаясь лишь тем, что ее результат имеет тип NCLOB и представлен символами национального набора
ТО_ЮВ(данные_типа_1опд)
Принимает данные типа LONG или LONG RAW и преобразует их соотнетсгвенно в данные типа CLOB или BLOB, Функцию TO_LOB можно вызвать из списка SELECT вложенного запроса инструкции INSERT...SELECT...FROM
TO_RAW
Принимает значение типа BLOB и возвращает его как значение типа RAW
Функция TJL0B предназначена для одноразового перемещения данных из столбцов типа LONG и LONG RAW в столбцы типа CLOB и BL09, поскольку типы данных LONG и LONG RAW более не используются. Функции TD_CLOB и TO_NCLOB являются удобными средствами взаимопреобразования символьных данных больших объектов и наборов символов базы данных и национального языка.
Предопределенные объектные типы
В Огас1е9: реализовано несколько ххредопределенных объектных типов, имеющих различное назначение:
О XMLType — хранение и обработка данных в XML-формате;
О типы URI — хранение унифицированных идентификаторов ресурсов (в частности, HTML-адресов);
□ типы <апу» — определение переменных PL/SQL, в которых могут храниться данные любых типов.
В следующих разделах приведено краткое описание этих объектных типов и указаны источники более полной информации о них.
Тип данных XMLType
Популярность технологии XML (Extensible Markup Language) растет очень быстро, и без ее освоения не обойтись ни одному программисту. Хранить XML-данные в базе данных Oracle и управлять ими из среды SQL и PL/SQL позволяет предопределенный тип данных XMLType.
Особенности языка XML - обширная тема, обсуждение которой выходит за рамки настоящей книги. Поэтому, излагая данный материал, мы исходили из предположения) что вы имеете хотя бы поверхностное представлеиие о технологии XML. Ниже речь пойдет лишь о значении объектного типа XMLType и о предоставляемых им возможностях, а его подробное описание вы найдете в книге Building Oracle XML Applications (автор Steve Muench, издательство O'Reilly). Если же вам нужен хороший источник информации о технологии XML, обратитесь к книге Learning XML (автор Erik Т. Ray, издательство O'Reilly).
ПРИМЕЧАНИЕ В Oracle9l Release 1 для ссылки на объекты типа XMLType использовался префикс «SYS.*. В Release 2 появилась возможность применять синонимы имен объектных типов и введен сценарий построения базы данных ($ORACLE_HOME/rdbms/admln/dbmsxmlt.sql), создающий объектный тип XMLType, который теперь определяет и общедоступный (public) синоним XMLTYPE, указывающий на предопределенный объектный тип SYS.XMLType.
Если вы используете тип данных XMLType, для хранения информации в XML-формате достаточно создать таблицу:
CREATE TABLE falls (
falljd NUMBER;
fall SYS.XMLType
В этой таблице для хранения XML-данных определен столбец fall с типом XMLType. Чтобы записать в него информацию, нужно вызвать статический метод CreateXML, который принимает XML-данные и создает для их хранения новый объект XMLType. Полученный объект можно поместить в столбец базы данных. Метод CreateXML является перегруженным: входными данными для него могут служить и данные типа VARCHAR2, и данные типа CLO0.
Рассмотрим в качестве примера три инструкции INSERT, которые создают XML-документы и помещают их в таблицу falls:
INSERT INTO falls VALUES (1, XMLType.CreateXMLC
'<?ot1 version-"!,0h?>
<name>Munising Falls</name>
<county>Alger</county>
<state>MI</state>
<url>
http;//ml chiganwaterfal1s.com/munls1ng_fal1s/mun1sing_f&11s.html
<url>
</Га11>'));
INSERT INTO falls VALUES (2, XMLType. CreateXML (
'<?xnil version-"!. D"?>
<all>
<name>Au Train Fal1s</name>
<county>Alger</county>
<state>Ml</state>
<url>
http :Z/m1ch1 ganwaterfal 1 s .cofn/autra1n_fal 1 s/autrai n_fal 1 s. html
</fa11>')):
INSERT INTO falls VALUES (3. XMLType.CreateXHLC
'<?xnil version-"1.0"?>
<fall>
<name>Laughing Whitefish Falls</name>
vcounty>Alger</county>
<state>MI</state>
Для выборки XML-данных из таблицы можно применить несколько методов объекта XMLType. Метод exlstsNode, вызываемый в следующем примере, проверяет существование в XML-документе заданного узла. Аналогичную проверку выполняет встроенная функция SQL EXISTSNODE. Она, как и указанный метод, идентифицирует узел с помощью выражения XPath. Приведенные далее инструкции возвращают одинаковые результаты:
SQL> SELECT fall id
2 FROM falls f
3 WHERE f.fal 1.exlstsNode('/fa!1/url') > 0:
SQL> SELECT falljd
FROM falls -
WHERE EXISTSNODE(fall,7fall/urT) > 0;
FALL ID
1 2
Конечно, c XML-данными можно работать и в PL/SQL В следующем примере переменной PL/SQL типа XMLType присваивается значение из столбца fall первой добавленной нами строки таблицы. Таким образом, в программу PL/SQL
считывается весь XML-документ, и мы можем работать с ним, как с любой другой информацией. В данном случае после выборки документа мы извлекаем текст из узла /fall/url и выводим его..
«demo_block»
DECLARE
fall XMLType;
url VARCHAR2C80);
BEGIN
Извлекаем XML-данные из пераой добавленной строки таблицы
SELECT fall I WTO demo__block.fall
FROM falls f
WHERE f.falljd - 1:
--Извлекаем и выводим значение URL
url :» fal 1.extracts' /fal 1 /url /textO') .getStringVal;.
DBMS OUTPUT.PUT -LINE(url):
END Г
Обратите внимание на следующие, строки:■
SELECT fall INTO den»_block.fall
url := fall .extract(7fall/url/text()') .getStringVal;
В первой из них имя переменной, fall, совпадает с именем столбца таблицы в базе данных. Поэтому в запросе мы уточняем имя переменной, указывая дополнительно имя блока PL/SQL. Для получения текста URL во второй строке мы вызываем два метода объекта XMLType:
□ extract — возвращает объект XMLType, содержащий только заданный фрагмент исходного XML-документа (для определения требуемого фрагмента используется выражение Xpath);
О getStringVal — возвращает текст XML-документа.
В рассмотренном примере метод getStringVal вызывается для XML-документа, который возвращается методом extract, в результате чего мы получаем текст URL из первой добавленной нами строки таблицы. Метод extract возвращает содержимое узла <url> в виде объекта XMLType, а метод getStringVal извлекает из него содержимое в виде текстовой строки, которую можно вывести на экран.
Столбец XMLType можно даже проиндексировать, обеспечив тем самым более эффективный поиск XML-документов. Для создания индекса вы должны иметь привилегии QUERY REWRITE. В следующем примере индекс создается на основе первых 80 символов имен каждого водопада, перечисленных в таблице falls:
CREATE INDEX falls_by_name
ON falls f ( ~ ~
SUBSTRC
XMLType.getStringVal(
XMLType.extract(f.fall, 7faH/name/text()')
Обратите внимание на то, как используется функция SUBSTR. Метод getString- Vdl возвращает строку, которая является слишком длинной для индекса, в результате чего возникает ошибка ORA-01450: MAXIMUM KEY LENGTH (3166) EXCEEDED, указывающая на превышение максимальной длины ключа. Функция же SUBSTR уменьшает длину возвращаемой строки до некоторой приемлемой длины.
Если вы решите задействовать объект XMLType в своем приложении, за более полной и актуальной информацией обращайтесь к документации Oracle. Тип XMLType был введен в Oracle9i Release 1. Но в Oracle Release 2 появилось множество новых возможностей. Важнейшим ресурсом для программистов, работающих с XML, является руководство Application Developer's Guide — XML. Некоторая полезная информация об XMLType, а также о встроенных SQL-функциях для поддержки формата XML имеется в документе SQL Reference.
Типы данных URI
Тип данных URI представлен одним основным типом данных и несколькими подтипами, используемыми для хранения URI (Uniform Resource Identifier) в переменных PL/SQL и столбцах баз данных. Основной тип данных URI называется Uri Туре. В переменной этого типа может храниться экземпляр любого из следующих подтипов:
О HttpUrlType — специфический для URL подтип UriType, обычно идентифицирующий web-страницы;
О DbUnType — подтип Uri Туре, поддерживающий URL, которые представлены в виде выражения Xpath;
О XDBUriType — подтип UriType, поддерживающий URL и идентифицирующий объекты Oracle XML DB (XML DB — это набор XML-технологий, встроенных в базу данных Oracle).
В Oracle9i поддержка работы с URI реализована в пакете Uri Factory. Входящие в него программы позволяют автоматически строить объекты соответствующего типа для заданного URI.
Объектные типы URI создаются посредством сценария dbmsurl.sql, хранящегося в каталоге $ORACLE_HOME/rdbms/admin. Их владельцем является пользователь SYS, поэтому в Oracle9z Release 1 для ссылки на них (как и при работе с типом данных XMLType) необходимо задавать префикс «SYS.» (в Release 2 он более не нужен).
Как используется тип данных HttpUrlType, можно судить по такому примеру:
DECLARE
WebPageURL HttpUrlType;
WebPage CLOB;
BEGIN
--Создаем зхзеипляр объектного типа, идентифицирующий
--сообщение Джонатана Генника'
WebPageURL :-HttpUriType.createUr1(
'http://gennick.com/me55age.pl5ql');
~- Извлекаем сообщение посредством HTTP
WebPage WebPageURL.getclobO:■
Выводим сообщение
DBMS OUTPUT.PUT LlNEC(SUBSTR(WebPage.l.6O))): END:-
В результате выполнения приведенного кода отображается следующий текст: Brighten the corner where you are.
За дополнительной информацией об использовании семейства типов Urilype обращайтесь к документации 0mcle9i XML API Reference - XDK and Oracle XML DB.
Типы данных «any»
Новое семейство типов «апу» предназначено для проведения операций над данными, тип которых до вьтолиения программы не известен. Функции-члены этого типа способны анализировать данные и самостоятельно определять их тип. Это позволяет мм корректно обрабатывать информацию. К семейству «апу» относятся следующие типы данных:
О AnyData — может содержать одиночное значение любого типа: скалярную величину, объект, созданный пользователем, вложенную таблицу, массив типа VARRAY или какое-либо другое значение не упоминаемого здесь типа;
О AnyDataSet — может содержать набор значений любого типа, но все значения в этом наборе должны быть одного типа;
О АпуТуре — обычно содержит описание типа, это <тип без данных».
Типы данных «алу» создаются сценарием dbmsany.sql^ хранящимся в каталоге $ORACLE_HOME/rdbms/admin. Их владельцем является пользователь SYS, поэтому в Oracle9i Release 1 для ссылки на них (как и для типа данных XMLType) нужно указывать префикс «SYS.» (в Release 2 он более не нужен).
В дополнение к типам «апу» сценарий dbmsany.sql создает пакет DBMS_TYPES, в котором определены приведенные ниже константы. Их можно использовать совместно с анализирующими функциями, и в частности с GETTYPE, для определения типа данных, хранящихся в конкретной переменной AnyData или AnyDataSet. Числовые значения этих констант для нас не представляют интереса 一 ведь константы для того и определены, чтобы пользоваться именами, а не значениями. typecodejate■
TYPECODE_NUMBER TYPECODE二RAW TYPECODE二CHAR TVPEC0DE_VARCHAR2 TYPECODE_VARCHAR TYPECODEJLSLABEL TYPECODE BLOB1
TYPECODE二BFILE typecddeZclob TYPECODE_CFILE typecode"timestamp
TYPECODE_TIMESTAMPJZ TYPECODE_TIMESTAMP_LTZ TYPECODE_INTERVAL_YM TYPECODE二INTERVAL—DS TYPEBDE二REF TYPECODE^OBJECT TYPECODE_VARRAY TYPECODE二 TABLE TYPECODE_NAMEDCOLLECTION TYPECDDEJPAQUE
В приведенном ниже примере создаются два пользовательских типа, представляющих два географических объекта: водопады и реки. Далее блок кода PL/SQL с помощью функции SYS.AnyType определяет массив неоднородных географических объектов (элементами которого могут быть и реки, и водопады). Итак, сначала создаются два объектных типа:
CREATE OR REPLACE TYPE waterfall AS OBJECT C
name VARCHAR2(30)..
height NUMBER
CREATE OR REPLACE TYPE river AS OBJECT (
name VARCHAR2C30).
length NUMBER
);
Далее выполняется блок PL/SQL:
DECLARE
TYPE feature_array IS VARRAY(2) OF SYS.AnyData;
features feature_array;
wf waterfall:
rv ri ver;
ret_val NUMBER;
BEGIN ~
--Создаем массив с злекентаки разных объектных типов features 『feature_array(
AnyData.ConvertObjectt
waterfall('Grand Sable Falls'.30)),
AnyDatd.ConvertObjectC
river('Manistique River'. B5.40))
--Выводим данные из этого массива
FOR х IN 1.. features.COUNT LOOP
--Выполняем код, ссютветствующий текущему
--объектному типу. ВНИМАНИЕ! Замените строку GENNICK именем своей схемы
CASE features(х).GetTypeName
WHEN 'GENNICK.WATERFALL' THEN
ret_val 尸 features(x).CetObject(wf>;
DBMS__OUTPUT.PUT_LINE( 'Waterfall :'
|[ wf.name |j ', Height = ' |( wf.height || ' feet.'):
WHEN 'GENNICK.RIVER' THEN
ret_val :- features(x).GetObject(rv);
DBMS_OUTPUT.PJT_LINE('River:'
|[ rv.name \\ '. Length = ' || rv.length || 1 miles.');
END CASE;
END LOOP;
ENO:”
Результат выполнения этого кода будет таким:
Waterfall: Grand Sable Falls. Height = 3Q feet.
River: Manistique River, Length - 85.4 miles.
Проанализируем приведенный выше код. Необходимые для его работы объекты хранятся в массиве VARRAY, инициализированном следующим образом: features := feature_array(
AnyData .ConverWbject (
waterfall('Grand Sable Falls'.30)),
AnyData.ConvertObjectC
river('Manistique River', 85.40)) ):
Рассмотрим структуру кода, описывающего объект Grand Sable Falls.
О В строке waterfall ('Grand Sable Falls', 30) вызывается конструктор типа waterfall для создания соответствующего ему объекта.
О В строке AnyData.ConvertObject объект типа waterfall преобразуется в экземпляр объекта SYS.AnyData, который после этого можно будет записать в массив объектов SYS. AnyData.-
О Функция feature_array вызывает конструктор массива. Каждый аргумент этой функции имеет тип AnyData. В данном случае массив состоит из двух переданных нами объектов.
Массивы VARRAY рассматривались, как вы помните, в главе 11, а об объектных типах будет рассказано в главе 21,
Следующий важный фрагмент кода — цикл FOR, в котором последовательно анализируются объекты массива features. При вызове пользовательских объектов имя типа предваряется именем схемы пользователя, создавшего данный объект. Это имя схемы мы включаем в предложение WHEN:
WHEN 'GENNICK.WATERFALL' THEN
Если вы решите проверить работу данного кода, не забудьте заменить имя GENNICK именем своей схемы..
ПРИМЕЧАНИЕ
При использовании встроенных типов данных, таких как NUMBER, DATE и VARCHAR2/ функция GetTypeName возвращает просто имя типа. Имя схемы указывается только для типов, определенных пользователем (то есть созданных с помощью оператора CREATE ТУРЕ).
Определив тип данных, мы извлекаем объект из массива посредством такого вызова:.
ret_vdl :- features(x).GetObject(wf):
В данном примере возвращаемое функцией значение игнорируется. В общем случае результатом работы функции может быть одно из двух значений:
О DBMS_TYPES,SUCCESS — значение, свидетельствующее о том, что переменная типа Any содержит данные определенного типа (в нашем случае — объект);
О DBMS_T У Р ES. NO_D АТ А — значение, указывающее, что в переменной типа AnyData не оказалось никаких данных.
Присвоив переменной в качестве значения экземпляр объекта, можно написать оператор, соответствующий его структуре. Например, информация о водопаде выводится так;
D8MS_DUTPUT.PUT_LINE(' Waterfall:'
11 wf.name 11'. Height - 1 11 wf.height 11 ' feet.');
Информацию о типах данных «any» вы найдете также в главе 21, где они рассматриваются с позиций объектно-ориентированного программирования. Кроме тога, советуем вам ознакомиться с документами Oracle9i Supplied PL/SQL Packages and Type Reference и Oracle9i SQ/. Reference, а также воспользоваться сценариями anynums.pkg и anynums.tst, представленными на узле издательства O'Reilly.
ПРИМЕЧАНИЕ
С позиций объектно-ориентированного программирования, существуют более эффективные способы работы с объектными типами данных. Рассмотренный пример приведен лишь с целью демонстрации возможностей предопределенного объектного типа SYS.AnyData,
Часть IV
SQL и PL/SQL
Эта часть книги посвящена одной из важнейших задач, выполняемых программами PL/SQL, — взаимодействию с базами данных, которое осуществляется с помощью 5Q£. В главах 73-15рассказывается, как объявлять в таблицах базы данных транзакции для обновления, добавления и удаления информаг^и; как запрашивать из базы данных информацию для обработки ее в программах PL/SQL; как динамически выполнять 50^-инсгпрук1щи, использух встроенный динамический SQL (NDS).
Глава 13. DML и управление транзакциями
Глава 14. Выборка данных
Глава 15. Динамический SQL и динамический PL/SQL
1 О DML и управление I О транзакциями
A DML и 叫SQL
А Пакетные DML-операции и оператор FORALL
А Управление транзакциями
А Автономные транзакции
При работе с базой данных Oracle используются языки SQL и интегрированный PL/SQL. Из программы PL/SQL можно выполнять инструкции языка манипулирования данными DML (Data Manipulation Language), в частности INSERT, UPDATE и DELETE, и, конечно же, запросы на выборку информации.
ПРИМЕЧАНИЕ
DML-инструкции в программах PL/SQL выполняются только в режиме динамического SQL. Эта тема освещается в главе 15.
Вы можете объединить несколько S QL-инструкций в одну логическую транзакцию -в таком случае их результаты либо будут сохраняться (в терминологии SQL «фиксироваться») все вместе, либо будут все вместе отменяться («откатываться»). В этой главе рассматриваются SQL-инструкции, используемые в PL/SQL для управления транзакциями.
Для того чтобы оценить важность транзакций в Oracle, нужно рассмотреть их основополагающий принцип, называемый «АСЮ» (Atomicity, Consistency, Isolation, Durability): транзакция атомарна, согласованна, изолированна и устойчива. Эти четыре характеристики транзакции определяются следующим образом.
О Атомарность. Изменения состояния, вносимые в ходе одной транзакции, атомарны; либо выполняются все эти изменения, либо не выполняется ни одно из них.
О Согласованность. Транзакция изменяет состояние базы данных. Выполненные в течение транзакции действия не нарушают ограничений целостности, связанных с данным состоянием.
q Изолированность. Возможно параллельное выполнение множества транзакций, но с точки зрения каждой конкретной транзакции остальные кажутся выполняемыми до или после нее.
О Устойчивость. После успешного завершения транзакции измененные данные становятся устойчивыми к последующим сбоям.
Можно либо зафиксировать начатую транзакцию с помощью инструкции COMMIT, либо выполнить ее откат посредством инструкции ROLLBACK. В любом из этих случаев будут освобождены заблокированные транзакцией ресурсы (инструкция ROLLBACK ТО снимет только некоторые блокировки). Затем сеанс, как правило, начинает новую транзакцию. По умолчанию в PL/SQL неявно определяется одна транзакция на весь сеанс, и все выполняемые в ходе этого сеанса изменения данных являются частью транзакции. Однако начиная с Oracle8i применение технологии автономных транзакций позволяет определять вложенные транзакции, выполняемые внутри главной транзакции уровня сеанса. Об этой возможности подробно рассказывается в конце главы, в разделе «Автономные транзакции».
DML в PL/SQL
Из блока кода PL/SQL можно выполнять DML-инструкции (INSERT, UPDATE и DELETE), оперирующие любыми доступными структурами данных.
ПРИМЕЧАНИЕ Если используется модель разрешений создателя, права доступа к этим структурам данных определяются во время компиляции. Если же используется модель разрешений вызывающего, с предложением AUTHID CURREN匸USER, права доступа определяются во время выполнения программы. Подробнее об этом рассказывается в главе 20.
Краткое введение в DML
Поскольку полное описание DML-инструкций языка Oracle SQL не входит в задачи этой книги, мы приведем лишь краткий обзор их базового синтаксиса, а затем рассмотрим несколько вопросов, связанных с использованием DML в PL/SQL:
。примеры всех DML-инструкций;
。атрибуты курсора для DML-инструкций;'
。особые элементы DML-инструкций в PL/SQL, такие, например, как предложение RETURNING.
За дополнительной информацией вы можете обратиться к документации Oracle и другим источникам, содержащим сведения о SQ.L.
В языке SQL определены три инструкции DML:
О INSERT — вставляет в таблицу одну или несколько новых строк;
Q UPDATE — обновляет в одной или нескольких существующих строках таблицы значения одного или нескольких столбцов;
。DELETE - удаляет из таблицы одну или несколько строк.
Инструкция INSERT
Существуют две базовые разновидности инструкции INSERT, синтаксис которых, конечно же, несколько различается.
О Вставка одной строки с явно заданным списком значений:
INSERT INTO таблица ^столбец_1, столбец^столбец^}
VALUES (з斤dvewfej, значение^зндчение^);
О Вставка в таблицу одной или нескольких строк, определяемых инструкцией SELECT, которая извлекает данные из одной или нескольких других таблиц:
INSERT INTO таблица ^.столбец 1, столбец^столбец_И)]
AS""~
SELECT
Рассмотрим несколько примеров инструкций INSERT, выполняемых в блоке PL/SQL. Для начала вставим новую строку в таблицу books. Заметьте, в том случае, если в предложении VALUES заданы значения для всех столбцов, список столбцов можно опустить:
BEGIN
INSERT INTO books '
VALUES C'l-56592-335-9'.
'Oracle PL/SQL Programming1.
'Reference for PL/SQL developers.' ||
'including examples and best practice ' || 'recormendations.1,
'Feuerstein,Steven, with Bin Prlbyl'..
TO DATE C'Ol-SEP-1997'. 'DD-MON-YYYY').
987):
END;
Можно также задать список имен столбцов, а их значения указать в виде значении переменных, а не литералов:
DECLARE
1_”Ьп books.isbnHYPE :" '1-56592-335-9';
l_titlR books.tltleXTYPE :- 'Oracle PL/SQL Progranring1;
l_summary books.sumnaryXTYPE :- 'Reference for PL/SQL developers.' || 'including examples and best practice ' ||.
'recommendations.' :■
l_author books.authorXTYPE := 'Feuerstein.Steven. with Bill Pribyl1;
fdatejMiblished books.date_publ1shed:nPE 尸 TQJATE ('01-SEP-1997'. 'DD-MON-YYYY'): l_page_count books.page_countXTYPE := 987:
BEGIN ~~
INSERT INTO books ( isbn, title, summary, author. date_published. psge_count)'
VALUES (~
IJsbn, l_t1tle. l_summary, l_author, l_date_published, l_page_count);
END;
Ниже приведен пример инструкции INSERT SELECT FROM, создающей «продолже- ни” списка книг, написанных Стивеном Ферстайном. Обратите внимание на то, что при извлечении данных из существующих строк значения столбцов изменяются;
BEGIN
INSERT INTO books (
Isbn, title, sirmary, author.
date_publ1shed. page_count)
SELECT SUBSTR C1sbn, 1. LENGTH(lsbn)-l) || 'X'.
title 11 '- Part Deux1.
sunnary || 'plus newer stuff, author,
ADD_MONTHS (date_published. 12).
page count
FROM books
WHERE UPPER (author) LIKE '«FEUERSTEIN, STEVEN»';
END;
Инструкция UPDATE
С помощью этой инструкции можно обновить один или несколько столбцов или одну или несколько строк таблицы. Приведем ее синтаксис:
UPDATE таблица
SET столбец^! - значение_1
[,стопбец_2 - значение^столбец^ - значение^)]
[WHERE условие]:
Предложение WHERE не обязательно; если оно не задано, обновляются все строки таблицы. Вот несколько примеров инструкции UPDATE.
О Представление названий книг таблицы books в символах верхнего регистра:
UPDATE books SET title - UPPER (title);
О Выполнение процедуры, удаляющей компонент времени из даты издания книг, которые были написаны указанным в аргументе автором, и переводящей названия этих книг в символы верхнего регистра. Как видите, инструкцию UPDATE можно выполнять как саму по себе, так и в блоке PL/SQL:
CREATE OR REPLACE PROCEDURE remove_time (
author In IN VARCHAR2) -
IS
BEGIN
UPDATE books
SET title = UPPER (title).
date_pubHshed - TRUNC (date_publ1shed)
WHERE author LIKE authorjn:
END;-
Инструкция DELETE
В заключение рассмотрим инструкцию DELETE. Она предназначена для удаления одной, нескольких или всех строк таблицы. Вот ее синтаксис:
delete FROM таблица
[WHERE условие];
Предложение WHERE не обязательно. Если оно не задано, удаляются все строки таблицы. Приведем несколько примеров использования этой инструкции.
О Удаление всей информации о книгах из таблицы books:
DELETE FROM books:
О Удаление из таблицы books всей информации о книгах, изданных до определенной даты, с возвратом их общего количества:
CREATE OR REPLACE PROCEDURE remove_books ( datejn IN DATE.
removal count out OUT PLS INTEGER)
BEGIN
DELETE FROM books
WHERE date_published < date_1n:
removal count out := SQUROWCGUNT:
END:一 ~
Конечно, все эти инструкции DML в реальных приложениях обычно бывают гораздо сложнее. Например, большое количество столбцов может обновляться данными, сгенерированными вложенным запросом. В Oracle9i имя таблицы несложно заменить табличной функцией, возвращающей результирующий набор строк, с которым работает инструкция DML.
Как уже было сказано, эта глава посвящена прежде всего особенностям использования DML в PL/SQ.L. Из нее вы узнаете, как лучше применять возможности DML в программах PL/SQL и как управлять транзакциями, неявно определяемыми при выполнении DМL-ипструесций.
Для начала рассмотрим атрибуты, предоставляемые Oracle для работы с неявными курсорами, следующими после DML-инструкциями.
Атрибуты курсора для операций DML
Для доступа к информации о последней операции, выполненной SQL-инструкцией, Oracle предоставляет несколько атрибутов курсоров, неявно открываемых для этой операции:
SQLXFOUND
SQLZNOTFOUND
SQLZROWCOUNT
SQLXISOPEN■
Атрибуты неявных курсоров возвращают информацию о выполнении инструкций INSERT, UPDATE, DELETE и SELECT INTO. Атрибуты курсора для инструкций, модифицирующих данные, рассматриваются в настоящей главе, а для инструкции SELECT INTO — в главе 14.
Прежде всего следует помнить, что значения атрибутов неявного курсора всегда относятся к последней выполненной SQL-инструкции, независимо от того, в каком блоке выполнялся неявный курсор. До открытия первого SQL-курсора сеанса значения всех неявных атрибутов курсора равны NULL. (Исключение составляет атрибут SQLXISOPEN, который возвращает FALSE.) Значения, возвращаемые атрибутами неявных курсоров, описаны в табл. 13,1.
Таблица 13.1. Атрибуты неявных курсоров для DML-инструкций
Имя
Описание
5QL%FOUND
Возвращает TRUE, если хотя бы одна или несколько строк успешно модифицированы (созданы, изменены или удалены)
SQL^bNOTfOUND
Возвращает TRUE, если DML-инструкция не модифицировала ни одной строки
SQL%ROWCOUNT
SQL%ISOPEN
Возвращает количество строк, модифицированных DML-инструкцией
Для неявных курсоров всегда возвращает FALSE, поскольку Oracle звкрывает и открывает их автоматически
Теперь посмотрим, как эти атрибуты используются.
О С помощью атрибута SQL^FOUND можно определить, обработала ли DML-инструкция хотя бы одну строку. Предположим, например, что автор издает свои произведения под разными именами, а нам время от времени необходимо обновлять записи с информацией обо всех книгах данного автора. Эту задачу выполняет процедура, обновляющая данные столбца author и возвращающая значение переменной типа BOOLEAN, указывающее, было ли произведено хоть одно обновление:
CREATE OR REPLACE PROCEDURE change_author_nanie ( old_name_1n IN books.authorXTYPE.
new_name_1n IN books.authorXTYPE. changes_made_aut OUT BOOLEAN)
IS~
BEGIN
UPDATE books
SET author = new_name_in'
WHERE author = ald_name_in;
changes_made_out :- SQLXFOUND:
END: -
Q Атрибут SQL^NOTFQUND позволяет убедиться, что ПМЬ'Инструкция не обработала ни одной строки. Его значение противоположно значению атрибута SQL^FOUND.
Q Атрибут SQLZROWCOUNT позволит выяснить, сколько строк обработала DML-инструкция. Вот новая версия приведенной выше процедуры, возвращающая более полную информацию:
CREATE OR REPLACE PROCEDURE change_author_name (
old_name_in IN books.authorXTYPE.'
new^namejn IN books.author^TYPE.
rename_count_out OUT PLS_INT£GER)
IS ~~~
BEGIN
UPDATE books
SET author = new_namejn
WHERE author - old_name_1n;
rename_count_out := SQL^ROWCOUNT;
END;~
Предложение RETURNING в DML-инструкции
Предположим, что вы выполнили инструкцию INSERT, UPDATE или DELETE и хотите получить ее результаты для дальнейшей обработки. Вместо того чтобы вьшол- нять вслед за ней явный SQL-запрос, можно включить в инструкцию предложение RETURNING, позволяющее задать переменные программы для записи в них результирующих данных. Это позволит сократить сетевой трафик и затраты ресурсов сервера, а также минимизировать количество курсоров, открываемых и управляемых приложением.
Вот несколько примеров, демонстрирующих эту возможность.
О В следующем блоке с помощью предложения RETURNING в значение переменной программы считывается новый оклад сотрудника, вычисляемый и обновляемый инструкцией UPDATE, и его фамилия:
DECLARE
щупаше empl оуее. 1 ast_namenYPE;
mysal employee.salaryJTVPE;
BEGIN'
FOR rec IN (SELECT * FROM Employee)
LOOP
UPDATE employee
SET salary - new_compensation Tec)
WHERE employee_1d ■ rec.employeejd
RETURNING salary. Idstjiame
IMTO mysal. myname:
DBMS_OUTPUT.PUT_LINE ('Новый оклад' ||
myname 11 ' - ' 11 rnysal):
END LOOP;
ENO;
О В следующем блоке с помощью предложения RETURNING программа считывает из базы данных локатор LOB. После этого она открывает файл, указанный в значении переменной типа BFILE и считывает хранящееся в нем изображение во BLOB-столбец таблицы базы данных:
DECLARE
pic_file BFILE :- BFILENAMEC'WEB_PIX'. 'memories.jpg1);
p1<blob_loc BLOB :- EMPT7_BLDB():
BEGIN-
INSERT INTO web_graphic_blobs
VALUES (1. p1c_blobjoc)
RETURNING image INTO plc_blob_loc:
DBMS_LDB.FILEQPENCpiVi 1 e, DBMS_LD0.FILE_READONLY);
DBMS_LOB. LOADFROMFILE("dest_1 ob -> pic_blob_loc, src_lob => pic_f11e,
ramount -> D0MS__LO0.GETLENGTH(pic filu))「
DBMS LOB.FILECLOSECpic file); -
END;
Q Предположим, что мы выполняем инструкцию UPDATE, модифицирующую несколько строк. В этом случае можно не просто сохранить возвращаемые значения в переменных, а записать их как элементы коллекции, воспользовавшись специальным предложением BULK COLLECT. В данном случае инструкция UPDATE
интегрируется в специальный оператор FORALL, о котором рассказывается в этой главе:
DECLARE
. names name_tab;
new_salaries nuinber_tab:
BEGIN —一
populate_nanies_array Cnames);
FORALL indx IN names.FIRST .. names.LAST
UPDATE compensation
SET salary - new_compensation [namesdndx)).
WHERE name - names (indx)
RETURNING salary BULK COLLECT INTO new_salar1es;
END;".
DML и обработка исключений
Когда в блоке PL/SQL инициируется исключение, Oracle не выполняет откат изменении, внесенных входящими в его состав DML-инструкциями. Право управления логическими транзакциями приложения предоставлено программисту.
Рассмотрим следующую процедуру:
CREATE OR REPLACE PROCEDURE emptyjlbrary (.
pre_empty_count OUT PLS INTEGER?
IS ~~-
BEGIN
-Функция tabCount возвращает количество строк
-в заданной таблице, используя встроенный
■ динамический SQL. Подробности см. в главе 15.
pre_empty_count :- tabcount ('books');'
DELETE FROM books;'.
RAISE NQ_DATA_FOUND:
END: ""
Обратите внимание, что перед инициированием исключения установлено значение параметра OUT. Теперь давайте запустим анонимный блок, вызывающий эту процедуру, и проанализируем результаты:
DECLARE
table_count NUMBER -1:
BEGIN -
INSERT INTO books
VALUES
emptyj-lbrary (table_count):
EXCEPTION~
WHEN OTHERS .
THEN
DBMS__OUTPUT.PUT_LINE (tabcount ('books'));
DBMS OUTPUT.PUT LINE (table count):
END; ~~~
Код выводит следующие значения:
О
Как видите, исключение было инициировано, но строки из таблицы книг при этом остались удаленными; дело в том, что Oracle не выполняет автоматического отката изменений. Однако переменная tabi e_count сохранила исходное значение. Получается, что Oracle производит откат для значений переменных программы, измененных после инициирования исключения. '
Таким образом, за откат транзакции отвечаете вы сами: хотите — выполняете его, а хотите - нет. Обдумывая решение, примите во внимание следующие соображения.
О Если для блока выполняется автономная транзакция (о чем рассказывается далее в этой главе), в обработчике исключения нужно произвести ее откат либо фиксацию.
О Для определения области отката следует воспользоваться точками сохранения. Можно произвести откат транзакции до конкретной точки сохранения, тем самым оставив часть изменений, выполненных в течение сеанса. О точках сохранения рассказывается далее в этой главе.
О Если исключение передается за пределы «самого внешнего» блока (то есть остается необработанным), то в среде выполнения PL/SQL, и в частности в SQL*Plus, автоматически осуществляется откат транзакции и все изменения отменяются.
DML и записи
В Oracle9i Release 2 в инструкциях INSERT и DELETE можно использовать записи PL/SQL. Приведем пример:
CREATE OR REPLACE PROCEDURE set_book_info (
book in IN booksZROWTYPE)
IS
BEGIN
INSERT INTO books VALUES book_in:
EXCEPTION-
WHEN DUP VAL_ON_INDEX'■
THEN ~~
UPDATE books SET ROW - bookJn.
WHERE isbn = bookjn.lsbn;
END;-
Это очень важное нововведение, облегчающее работу программиста и позволяющее сделать программный код более компактным (теперь можно, игнорируя некоторые значения, работать на уровне записей, то есть не объявлять отдельные переменные и не выделять отдельные поля записи перед передачей данных DML- инструкции), а также более надежным (когда в коде используются записи типа ZROWTYPE и не вьшолняются явные операции с полями указанных записей при изменении структуры основанных на этих записях таблиц и представлений, не приходится вносить значительных изменений в программный код).
Однако на использование записей в DML-инструкциях налагаются определенные ограничения, о которых рассказывается далее, в разделе < Ограничения, касающиеся операций вставки и обновления».
Вставка на основе записей
В инструкциях INSERT записи можно использовать как для добавления единственной строки, так и для пакетной вставки (посредством инструкции FORALL). Записи могут создаваться с помощью объявления ^ROWTYPE ва основе строки таблицы, в которую производится вставка, или же объявляться явно с помощью инструкции ТУРЕ на основе типа данных, совместимого со структурой таблицы.
Приведем несколько примеров.
О Вставка в таблицу книг данных из записи, объявленной как XROWTYPE:
DECLARE'
my_book booksXROWTYPE;
BEGIN".
my_book.isbn :匸'1-56592-335-9';
my^book.title :- 'ORACLE PL/SQL PROGRAMMING';.
щу book.sunwary ;= 'General user guide and reference';
myjook.author :- 'FEUERSTEIN, STEVEN AND BILL PRIBYL';
n)y_book.page_count : = 1000;
INSERT INTO books VALUES my_book;'
END:-
Обратите внимание на тот факт, что имя записи не заключается в скобки. Если мы напишем следующее:
INSERT INTO books VALUES <гцу_Ьоок):
Oracle выдаст сообщение об ошибке ORA-00947: not enough values.
Можно выполнить вставку данных, взятых из записи, тип которой определен программистом, но этот тип должен быть полностью совместимым с определением строки таблицы. В частности, нельзя вставить в таблицу запись, содержащую подмножество столбцов таблицы.
О Выполнение вставки с помощью оператора FORALL — таким образом, в таблицу вставляются целые коллекции записей:
DECLARE
TYPE book_11st_t IS TABLE OF booksXROWTYPE;
niy_books book fistj bookJ1st_t():
BEGIN-_ 一 一 _
my_books.EXTEND (2);
my books(l).isbn := '1-56592-335-9';
my^bookstn.title 尸'ORACLE PL/SQL PROGRAMMING':
W_books(2).i5bn:a '0-596-00121-5';
my_books(2).title 'ORACLE PL/SQL BEST PRACTICES';.
FORALL indx IN my_books.FIRST .. my_books.lAST
INSERT INTO books VALUES гцу books(indx);
END;-
Обновление на основе записей
В Огас1е9г Release 2 можно обновить всю строку таблицы, добавив в нее данные из записи PL/SQL. В следующем примере для обновления строки таблицы books используется запись, созданная с помощью спецификации ^ROWTYPE. Обратите внимание на новое ключевое слово ROW, указывающее, что вся строка обновляется данными из записи:
DECLARE
гцу_Ьоок booksXROWTYPE:
BEGIN"
3 book.lsbn :■ '1-56592-335-9';
3二book.title :- 'ORACLE PL/SQL PROGRAMMING';
my book.sunwary :- 'General user guide and reference';
1ДуЪок.author :- 'FEUERSTEIN. STEVEN AND BILL PRIBYL';
rqy_book.page_count :- 980:-- предполагаемое количество страниц в третьем издании
UPDATE books
SET ROW - щу.Ьоок WHERE Isbn - |ду book.lsbn;
END;"-
Существует несколько ограничений, касающихся обновления строк на основе записей.
О При использовании ключевого слова ROW должна обновляться вся строка. Пока еще нет возможности, позволяющей выполнять обновление подмножества столбцов, но не исключено, что она появится в следующих версиях Oracle.
О Нельзя обновлять данные с использованием вложенного запроса.
Использование записей с предложением RETURNING
В DML-инструкции можно включать предложение RETURNING, возвращающее значения столбцов (и основанных на них выражений) из обработанных ими строк. Возвращаемые данные могут быть помещены в запись или даже в коллекцию записей:
DECLARE
ny_bgk_new_info tx)oks3;ROWTYPE;
book return info booksXROWTYPE;
begin"--
iny_book.1sbn :- '1-56592-335-9';
щу_Ьоок,title ;- 'ORACLE PL/SQL PROGRAMMING';
(ny_book.suimiary :■ 'General user guide and reference': rny^book.author :- 'FEUERSTEIN, STEVEN AND BILL PRIBYL';
щу^Ьоок.page_count :- 980; -- предполагаеное количество страниц в третьем издании
UPDATE books
SET ROW - niy_bOQk_newJnfo
WHERE Isbn - fny_book.isbn
RETURNING isbn, title, summary, author, page_count INTO my_tx)ok__return_i nfo;
END;
Заметьте, что в предложении RETURNING перечисляются все столбцы таблицы. К сожалению, Oracle не поддерживает использования символа «*》.
Если инструкция обновляет более одной строки, в ней можно использовать предложение BULK COLLECT INTO (см. главу 14), позволяющее записать результирующие данные для каждой строки в коллекцию записей:
declare
niy^book^newjnfo booksXROWTYPE ;
ТУРЕ bDokJistJ IS TABLE OF books INDEX BY 0INARY_imEGER;
n^-books book」1s_t:
begin"
-Издатель сменил формат всех книг об Oracle,
-и поэтому для каждой из них нужно увеличить количество страниц на 10 X.
UPDATE books
SET page_count - page_count * 0.9
WHERE UPPER (title) LIKE '«ORACLE»'
RETURNING Isbn, title, sumnary. author, page_count
SULK COLLECT INTO iRy_books;
END;-
Ограничения, касающиеся операций вставки и обновления
Если вы захотите освоить операции вставки и обновления с использованием записей, имейте в виду, что на их применение существуют определенные о1раничения.
О Переменную типа записи можно использовать лишь в перечисленных ниже конструкциях;
в правой части предложения SET инструкции UPDATE;
в предложении VALUES инструкции INSERT;
во вложенном предложении INTO предложения RETURNING.
О Ключевое слово ROW используется только в левой части предложения SET. В этом случае других предложений SET быть не может (то есть нельзя задать предложение SET со строкой и затем предложение SET с отдельным столбцом).
О При вставке записи не следует задавать значения отдельных столбцов.
Q Нельзя вставлять либо обновлять строку, указывая в инструкции INSERT или UPDATE запись (либо возвращающую ее функцию), содержащую, в свою очередь, вложенную запись.
Q Записи не рекомендуется использовать в динамически выполняемых инструкциях DML (то есть в инструкциях, которые выполняются с помощью оператора EXECUTE IMMEDIATE). Это потребовало бы от Oracle поддержки динамической привязки типа записи PL/SQL с SQL-инструкцией. Oracle же поддерживает динамическую привязку только для типов данных SQL.
Пакетные DML-операции и оператор FORALL
В Oracle8i введен новый оператор, значительно расширивший возможности использования DML-инструкций в программах PL/SQL. Это оператор FORALL, связывающий элементы одной вши нескольких коллекций с S QL-инструкцией перед ее отправкой ядру SQL. Вы уже знаете, что PL/SQL тесно интегрирован с ядром SQL базы данных Oracle. Он является языком программирования, предназначенным для баз данных Oracle, и, несмотря на возможность использования в этом качестве языка Java (пока, скорее, теоретическую), ему все еще отдается безусловное предпочтение.
Но даже такая тесная интеграция не означает, что выполнение SQL-инструк- 1щй из программ PL/SQL не связано ни с какими издержками. Когда исполняющее ядро PL/SQL обрабатывает блок кода, оно самостоятельно выполняет процедурные операторы, a SQL-инструкции пересылает ядру SQL, которое их обрабатывает и, если нужно, возвращает результирующую информацию ядру PL/SQL.
Передача управления (рис. 13.1) между ядрами PL/SQL и SQL называется переключением контекста. На каждое такое переключение затрачиваются дополнительное время и ресурсы. Эти затраты могут быть разными, часто — довольно ощутимыми. Начиная с версии Oracle8z в PL/SQL появилась возможность объединить несколько переключений контекста в одно, и тем самым повысить производительность приложения. Эта возможность реализована в виде оператора FORALL, о котором рассказывается в данной главе, и предложения BULK COLLECT, обсуждаемого в главе 14. '
При пакетном связывании для SQL-инструкции задается набор входных данных. Ядро SQL выполняет инструкцию для каждого элемента набора, в результате чего за одно переключение контекста выполняется группа однотипных S(^.-инструкций. Этот процесс показан на рис. 13.2.
Ядро PUSQL
Ядро SQ.
CREATE OR REPLASE PROCEDURE order_books 1sbn_1n IN name_varray new count_in IN number_v3rray)
BEGIN
FORALL Indx IN isbnjn.FIRST IsbnJn.LAST
UPDATE books ~
SET page_count = new_count_in (indx) WHERE isbn - Isbnjn (index):
End:~
-UPDATE UPDATE UPDATE UPDATE UPDATE UPDATE
卜、
Подсистема Г выполнения J инструкций [
SQL Г
Группа инструкций UPDATEпередается ядру SQL за один раз
Рис. 13.2. Одно переключение контекста для оператора FORALL
Оператор FORALL
Несмотря на то что оператор FORALL содержит итерационную схему (в соответствии с которой он по очереди перебирает все элементы коллекции), это' не цикл FOR. Следовательно, в нем нет ни ключевого слова LOOP, ни ключевых слов END LOOP. Синтаксис этого оператора таков:
FORALL индекс_записи IN начдльное_значение...конечное_значение
sql инструкция;
Здесь индекс_записии — это индексная переменная, в которую будет помещаться номер очередного элемента коллекции, перебираемой оператором FORALL; началь- ное__знзчение — индекс элемента коллекции, с которого будет начат перебор; конеч- ное^значение — индекс элемента коллекции, на котором перебор будет окончен; sqlинструкция — SQL-инструкция, которая должна быть выполнена для каждого элемента коллекции.
При работе с оператором FORALL следует придерживаться таких правил.
О В теле оператора должна содержаться одна DML-инструкция — INSERT, UPDATE или DELETE.
Q DML-инструкция должна ссылаться на элементы коллекции с помощью переменной индекс_залиси, заданной в операторе FORALL. Областью действия переменной является только оператор FORALL; вне такового она недоступна. Однако обратите внимание, что начальное и конечное значения не обязательно должны охватывать всю коллекцию.
Q Объявлять переменную индекс_записи запрещено. Она объявляется неявно ядром PL/SQL и имеет тип PLSJNTEGER.
□ Начальное и конечное значения определяют допустимый диапазон упорядоченных номеров индексов коллекции. Если коллекция индексов содержит пропущенные номера, будет выдано следующее сообщение об ошибке:
ORA-22160: е!ement at index[3] does not exist
О Индекс элемента коллекции, задаваемый в DML-инструкции, не может быть выражением. Например, код
DECLARE
names name_varray :■ name varrayO :
BEGIN ""
FORALL 1ndx IN names.FIRST ., names.LAST
DELETE FROM emp WHERE enanre - namesflndx+10);
END:
генерирует ошибку:
PLS-00430: FORALL Iteration variable INDX is not allowed in this context .
Проблемы, вызываемые переключением контекста
Прежде чем перейти к более подробному описанию оператора FORALL, давайте проанализируем ситуации, в которых переключение контекста может вызвать определенные проблемы. Такие ситуации возможны при обработке нескольких строк данных, хранящихся в коллекции (VARRAY, вложенной таблице или ассоциативном массиве).
Предположим, нам нужна процедура для обновления количества страниц заданных книг в таблице books. Входные данные программы содержатся в двух коллекциях: в одной — ISBN-номера обновляемых книг, во второй 一 новое количество их страниц. Для версий Oracle до 8.1 подошло бы следующее решение (в нем используются два объявленных ранее массива типа VARRAY — name_varray и num- ber_varray):
CREATE OR REPLACE PROCEDURE order_books (
isbnjn IN name^array,
n^__countjn IN number уаггад)
BEGIN
FOR indx IN isbnjn.FIRST IsbnJn.LAST '
LOOP~~'
UPDATE books
SET page_count - na<_8unt_Jn (indx)
WHERE 1sbn - isbn_ln ?indx):
END LOOP:~
END;
Если нужно вставить 100 строк, придется выполнить по меньшей мере 100 переключений контекста, поскольку для каждого обновления нужно очередной раз обращаться к ядру SQL. Как производятся такие многократные, но неизбежные ранее, переключения контекста можно судить по рис. 13.3.
■ PL^SQL-мвшина;.CREATE OR REPLACE PROCEDURE order_books (J'isbnjn IN name_varray,jne*ccount_ln IN number_varray)■ IS "~*Г BEGIN■ FOR indx IN Isbnjn.FIRST!1sbn In.LASTLOOP"UPDATE booksSET page_count - new^countjn (Indx) WHERE 1sbn ■ Isbnjn (1ndx);END LOOP:■ PL^SQL-мвшина;.CREATE OR REPLACE PROCEDURE order_books (J'isbnjn IN name_varray,jne*ccount_ln IN number_varray)■ IS "~*Г BEGIN■ FOR indx IN Isbnjn.FIRST!1sbn In.LASTLOOP"UPDATE booksSET page_count - new^countjn (Indx) WHERE 1sbn ■ Isbnjn (1ndx);END LOOP:UPDATE UPDATE UPDATE UPWE BPWE UPDATEUPDATE UPDATE UPDATE UPWE BPWE UPDATESQL-машинаSQL-машинаПодсистема выполнения инструкций SQLПодсистема выполнения инструкций SQLEND:END:
■ PL^SQL-мвшина
;.CREATE OR REPLACE PROCEDURE order_books (
J'isbnjn IN name_varray,
jne*ccount_ln IN number_varray)
■ IS "~*
Г BEGIN
■ FOR indx IN Isbnjn.FIRST
!1sbn In.LAST
LOOP"
UPDATE books
SET page_count - new^countjn (Indx) WHERE 1sbn ■ Isbnjn (1ndx);
END LOOP:
■ PL^SQL-мвшина
;.CREATE OR REPLACE PROCEDURE order_books (
J'isbnjn IN name_varray,
jne*ccount_ln IN number_varray)
■ IS "~*
Г BEGIN
■ FOR indx IN Isbnjn.FIRST
!1sbn In.LAST
LOOP"
UPDATE books
SET page_count - new^countjn (Indx) WHERE 1sbn ■ Isbnjn (1ndx);
END LOOP:
UPDATE UPDATE UPDATE UPWE BPWE UPDATE
UPDATE UPDATE UPDATE UPWE BPWE UPDATE
SQL-машина
SQL-машина
Подсистема выполнения инструкций SQL
Подсистема выполнения инструкций SQL
END:
END:
Каждая инструкция UPDATEпоражается ядру SQL отдельна
Рис. 13.3. Многократное переключение контекста для выполненияпоследовательности обновлений
Точно так же происходит выборка нескольких строк информации из курсора и помещение их в коллекцию, в этом случае тоже выполняется многократное переключение контекста:
DECLARE
CURSOR major_polluters IS
SELECT name, mileage
FROM cars_and_trucks■
WHERE vehicTe_type IN 「5UV‘. 'PICKUP'):,
names name_varray ;■ name_varrayC):
mileages number varray :- питЬег_увггау();
BEGIN"
FOR bad_car IN major polluters
LOOP ~"
names.EXTEND:
names (major_pollutersXROWCOUNT) :- bad_car.name:
mlle明es.EXTEND;
mileages Cmajor_pollutersXROWCOUNT) :- bad_car.mileage;
END LOOP:-
...работа с данными в массивах ...
END:
Если вам придется программировать подобные операции, воспользуйтесь одним из описанных ниже пакетных операторов. При этом обращайте внимание на следующие элементы программного кода;
Q SQL-инструкцию, многократно выполняющуюся в цикле PL/SQL (это не обязательно должен быть цикл FOR, хотя он является первым кандидатом на использование при пакетной обработке);
Q параметры, которые можно было бы использовать для привязки переменных, при обработке оператором FORALL должны бьпъ помещены в коллекцию.
Примеры использования оператора FORALL
Приведем несколько примеров использования оператора FORALL.
О Переписывание процедуры order_books:
CREATE OR REPLACE PROCEDURE order_books C
isbnjn IN name_varray,
new_count_1n IN number_varray)
IS ~~~
BEGIN
FORALL indx IN isbnjn.FIRST .. isbnjn.LAST
UPDATE books ~-
SET page_count - new_count_in (indx)
WHERE isbn - isbnjn (indx);
END;~
Обратите внимание, что в этом примере произведены лишь незначительные изменения; оператор FOR заменен оператором FORALL и удалены ключевые слова LOOP и END LOOP. SQL-инструкции передается каждая из записей двух коллекций. На рис. 13.2 показано, как выполняется подобный код.-
О Вставка SQL-инструкцией INSERT в таблицу строк, состоящих из данных нескольких коллекций. Здесь используются три коллекции: denial, patient_name и Illnesses, но индексы в предложении VALUES инструкции INSERT заданы только для двух из них. В результате пакетное связывание будет выполнено только для первых двух коллекций, а третья (заданная без индекса), будет полностью помещаться в столбец таблицы для каждой добавляемой строки:
FORALL indx IN denial.FIRST .. denial.LAST
INSERT INTO health_coverage
VALUES (denial(indx). patient_name(indx). illnesses);
Q Получение информации для каждой отдельной инструкции DELETE посредством включения в оператор FORALL предложения RETURNING со вложенным предложением BULK COLLECT INTO:
CREATE OR REPLACE FUNCTION remove_emps_by_dept (deptlist dlist_t)
RETURN enolist t
IS~
enolist enolist_t;
BEGIN~
FORALL aDept IN deptlist.FIRST..deptlist.LAST
DELETE FROM emp WHERE deptno IN deptlist(aDept)
RETURNING empno BULK COLLECT INTO enolist;'
RETURN enolist;
END:
Атрибуты курсора для оператора FORALL
После выполнения в операторе FORALL ряда однотипных SQL-инструкций можно получить информацию о результатах, обратившись к специальным атрибутам курсора. Часть этих атрибутов вам уже знакома. Кроме того, Oracle предлагает дополнительный атрибут ZBULK_ROWCOUNT, возвращающий информацию о пакетной операции. Все атрибуты курсора описаны в табл. 13.2.
Таблица 13.2. Атрибуты неявного курсора для DML-инсгрукций
Имя
Описание
SQL%FOUNO
Возвращает TRUE, если последняя SQL-инструкция успешно модифицировала хотя бы одну строку
SQL%NOTFOUND
Возвращает TRUE, если последняя DML-инструкция не модифицировала ни одной строки
SQL%ROWCOUNT
Возвращает количество строк, модифицированных всеми
ПМЬинструкциями, а не только последней из них
SQL%ISOPEN
Всегда возвращает FALSE. Этим атрибутом пользоваться не следует
SQL%BULOOWCOUNT
Возвращает коллекцию с информацией о количестве строк, обработанных каждой SQL-инструкцией, выполненной в операторе FORALL Когда значение атрибута %BULK_ROWCOUhTT равняется нулю, значения атрибутов % FOUND и %NOTFOUND равны FALSE и TRUE соответственно'
Рассмотрим составной атрибут XBULK_ROWCOUNT. Он предназначен специально для использования с оператором FORALL и имеет семантику ассоциативного массива или коллекции (то есть работает как указанные структуры). В У-й элемент этой коллекции Oracle помещает количество строк, обработанных "近 инструкцией INSERT, DELETE или UPDATE. Если не обработана ни одна строка, №立 элемент коллекции содержит значение 0.
Приведем прймер использования атрибута XBULK__ROWCOUNT (а также атрибута XROWCOUNT):f
DECLARE
TYPE Isbnjlst IS TABLE OF VARCHAR2(13);
my_books isbnjlst :- isbnjlst ('1-56592-375-8'.
~'0-596-00121-5',
'1-56592-049-0','
'1-56592-335-9',
■1-56592-674-9', '1-56592-675-7'. '0-596-00180-0'.■
'1-56592-457-6'):
BEGIN
FORALL book] ndex IN
my_books.FIRST..my_books.LAST
UPDATE books~
SET page_count
WHERE 1sbn - books (book index):
--Обновлено ли столько книг, сколько ожидалось?
IF SQLIROWCOUNT !- 8
THEN
DBMS_OUTPUT.PUT_LINE С'Обновлены не все книги!1);
END IF;""
--Проверяем выполнение инструкции UPDATE для книги
-с isbn 1-56592-335-9
IF SQLS£BUL<_R0WCQUNT(4) - О
THEN ~
D8MS_OUTPUT.PUT_LINE С
'Что происходит с книгой Oracle PL/SQL Programlngl');'
END IF:
END;
Существует несколько особенностей применения данного атрибута.
О В операторе FORALL и в атрибуте XBULK_ROWCOUNT используются одни и те же номера записей коллекции. Например, если в коллекции, переданной оператору FORALL, данные имеются в элементах 10-200, значит( псевдоколлекция XBULK_ROW- COUNT тоже будет содержать значащие элементы 10-200.
О Если посредством инструкции INSERT вставляется только одна строка (например, когда в предложении VALUES для нее задан список значений), значением элемента в атрибуте X0ULK_ROWCOUNT является 1. А для инструкции INSERT... SELECT значение элемента в атрибуте XBULK_ROWCOUNT может быть больше 1.
О Значениями элементов псевдоколлекции, возвращаемой атрибутом XBULK_ROW- COUNT, для операций удаления и вставки могут быть любые натуральные числа (0 или положительные числа); в зависимости от содержимого предложения WHERE эти инструкции MOiyr обрабатывать одну или несколько строк.
Откат изменений, выполняемых с помощью оператора FORALL
Итак, оператор FORALL позволяет передать ядру SQL группу однотипных инструкций. С точки зрения переключения контекста это означает, что выполняется единый блок SQL-кода! состоящий из ряда отдельных инструкций.
Что же происходит, если не удается выполнить одну из инструкций этого блока? По умолчанию Oracle действует следующим образом.
О Выполнение оператора FORALL прекращается — Oracle никогда не пропустит «провинившуюся» инструкцию и не продолжит обработку со следующей записи коллекции.
О Выполняется откат текущей инструкции DML до неявной точки сохранения, установленной ядром PL/SQL перед вьшолнением инструкции.
О Предшествующие операции DML, выполненные в составе текущего оператора FORALL, не откатываются.
Продолжение работы программы после исключения, инициированного при выполнении оператора FORALL
В Oracle9i введено новое предложение SAVE EXCEPTIONS, предназначенное для использования в операторе FORALL. Оно предписывает Oracle продолжить выполнение блока даже в случае возникновения ошибки. Если при выполнении очередной инструкции блока инициируется исключение, Oracle сохраняет информацию об этом исключении (или о нескольких исключениях, если произойдет несколько ошибок подряд), а затем, когда будет выполнен весь блок инструкций, инициирует исключение ORA-24381. После этого управление, как обычно, передается в раздел исключений, где программа может прочитать информацию об отдельных ошибках из псевдоколлекции SQLXBULK_EXCEPTIONS.
Приведем пример программного кода, в котором производится пакетное связывание и обрабатываются исключения, инициированные для отдельных SQL-инструкций этой операции:
/* Файл в web; bulkexc.sql */
CREATE OR REPLACE PROCEDURE bulk_exceptl.ons (
whr In IN VARCHAR2 .- NULL)"
IS ~
TYPE namelist』 IS TABLE OF VARCHAR2 (100):
enames with_errors namel1st_t :-
nairel1st~t CLinLE'. 'BISBIGGERSIGGEST'. 'SMITHIE'.");
bulk errors EXCEPTION:
В PRAGMA EXCEPTIONJNIT (bulk_errors, -24381);
9 BEGIN
10111213141516171819202122232425262728293031321011121314151617181920212223242526272829303132FORALL indx IN
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
enames_w1th_errors.FIRST ..
enames_with_errors.LAST
SAVE EXCEPTIONS ~-
EXECUTE IMMEDIATE
'UPDATE emp SET ename - :newname'
USING enames_w1th_errQrstindx);
EXCEPTION~~.
WHEN bulk_errors
THEN -
FOR indx IN 1 ., SQU3ULK_EXCEPTIONS.COUNT
L№~
DBMS_OUTPUT.PUT_LINE (
'Error ' 11 indx 11 ' occurred during ' 11
'Iteration ' || SQLXBULKJXCEPTIONS(indx) .ERRORJNDEX 1 updating name to ' ||
enames_with errors(
SQLXBULOxCEPTI0NS(1ndX).ERR0R_INDEX)):
DBMS_OUTPUT.PUTJINE (~
'Oracle error 1s ' ||
SQLERRMC-l * SQL^BULK_EXCEPTIONS(indx).ERROR_CODE));
END LOOP:~
IIIIEND:
II
II
Результаты выполнения данного кода таковы:
5QL> exec bulk_exceptions
Error 1 occurred during iteration 2 updating name to BIGBIGGERBIGGEST
Oracle error Is 0RA-O14O1: inserted value too large for column
Error 2 occurred during iteration 4 updating name to
Oracle error is 0RA-O14O7: cannot update () to NULL
Эти сообщения означают, что при выполнении DML-инструкции для элементов коллекции enames_with_errors Oracle инициировал два исключения. Однако после первого из них выполнение операции пакетного связывания не было прервано и Oracle успешно записал в столбец name значение третьего элемента коллекции. Проанализируем приведенный выше код, воспользовавшись следующей таблицей.
Строки Описание
4-6 Объявление и заполнение коллекции, которая будет управлять выполнением оператора FORALL Намеренное помещение в эту коллекцию данных, которые вызовут две ошибки
, 9 Объявление именованного исключения с целью сделать раздел исключений более читабельным
11-17 Выполнение динамической инструкции UPDATE и оператора FORALL, обрабатывающего коллекцию enames_wlth_errors
Перехват исключения исходя из имени. Однако можно было бы перехватить его и по номеру:
WHEN OTHERS THEN
IF SQLCODE =-24381'
Просмотр в цикле FOR с числовым счетчиком содержимого псевдоколлекции, возвращаемой атрибутом SQL%BULK_EXCEPrTIONS. Обратите внимание, что для этой коллекции можно вызвать метод COUNT, возвращающий количество ее записей (сгенерированных ошибок), но нельзя вызвать другие методы, как например FIRST или LAST
22-30 Извлечение информации из коллекции и вывод ее на экран (или запись в журнал)
24 Возвращение в поле ERROR_INDEX каждой записи псевдоколлекции номера записи заданной в операторе FORALL коллекции, для которой быпо инициировано исключение
30 Возвращение в пале ERROR_CODE каждой записи псевдоколлекции номера инициированного исключения. Обратите внимание, что это значение хранится как положительное целое число, которое, прежде чем передать функции SQLERRM или вывести на экран, следует умножить на -1
Управление транзакциями
Как и положено реляционной СУБД, Oracle поддерживает очень мощную и надежную модель транзакций. Код приложения определяет логическую последовательность выполняемых транзакцией операции, результаты которых должны быть
либо сохранены с помощью инструкции COMMIT, либо отменены посредством инструкции ROLLBACK. Транзакция начинается неявно с первой SQL-инструкции, выполняемой после инструкции COMMIT или ROLLBACK (или с начала сеанса) и продолжается после обработки инструкции ROLLBACK ТО' SAVEPOINT.
Для управления транзакциями PL/SQL предоставляет несколько различных ипструкций:
О COMMIT 一 сохраняет (фиксирует) все изменения, внесенные со времени выполнения последней инструкции COMMIT или ROLLBACK, и снимает все блокировки;
О ROLLBACK — отменяет (откатывает) все изменения, внесенные со времени выполнения последней инструкции COMMIT или ROLLBACK, и снимает все блокировки;
О ROLLBACK ТО SAVEPOINT — отменяет все изменения, выполненные со времени установки последней точки сохранения, и снимает все блокировки, установленные в этой части кода;
О SAVEPOINT — устанавливает точку сохранения, после чего становится возможным частичный откат транзакции;
О SET TRANSACTION — позволяет начать сеанс чтения или чтения-записи, установить уровень изоляции или связать текущую транзакцию с заданным сегментом отката;
О LOCK TABLE — позволяет заблокировать всю таблицу базы данных в указанном режиме. (По умолчанию к таблице обычно применяется блокировка на уровне строк.)
Инструкция COMMIT
Фиксирует все изменения, внесенные в базу данных в ходе сеанса текущей транзакцией, После выполнения этой инструкции изменения становятся видимыми для других сеансов или для пользователей. Синтаксис этой инструкции таков:
COMMIT [WORK] [COMMENT тек”];
Ключевое слово WORK не обязательно — оно служит только для улучшения читабельности кода.
Ключевое слово COMMENT также не является обязательным и используется для задания комментария, который будет связан с текущей транзакцией. Текстом комментария должен быть заключенный в одинарные кавычки литерал длиной до 50 символов. Обычно комментарии задаются для распределенных транзакций с целью облегчения их анализа и разрешения сомнительных транзакций (транзакции, не завершенные по причине недоступности координирующих их серве- Р°в) в среде с двухфазовой фиксацией. Они хранятся в словаре данных вместе с идентификаторами транзакций.
Обратите внимание на то, что инструкция COMMIT снимает все блокировки таб- лиц, установленные во время текущего сеанса, например для инструкции SELECT FOR UPDATE. Кроме того, она удаляет все точки сохранения, установленные после выполнения последней инструкции COMMIT или ROLLBACK. Откат зафиксированных Изменений с помощью инструкции ROLLBACK становится невозможным.
Приведем несколько примеров использования инструкции COMMIT:
COMMIT;
COMMIT WORK:
COMMIT COMMENT 'maintaining account balance';
Инструкция ROLLBACK
Инструкция ROLLBACK выполняет откат всех или части изменений, внесенных в базу данных в ходе сеанса текущей транзакции. Для чего это может потребоваться? При выполнении SQL-инструкций, задаваемых пользователем, инструкция ROLLBACK позволяет исправить некоторые ошибки, например:
DELETE FROM orders;
<Нет, нетI Я хотел удалить только те заказы, которые были сделаны до мая 1995 года!>. При возникновении подобной ситуации следует выполнить инструкцию ROLLBACK и повторить удаление с корректным условием. Что касается SQL-инструкций, выполняемых приложением, то в случае появления проблем их откат позволяет вернуться в исходное состояние.
Синтаксис инструкции ROLLBACK таков:
ROLLBACK [WORK] [ТО [SAVEPOINT] имя^очки_сохранения];
Существуют две базовые модификации инструкции ROLLBACK: одна 一 без параметров, а другая - с предложением ТО, указывающим, до какой точки сохранения следует произвести откат. Первая отменяет все изменения» выполненные в ходе текущей транзакции, а вторая отменяет все изменения и снимает все блокировки, внесенные и установленные до точки сохранения, идентифицируемой отметкой имя_гочки_сохранения. (О том, как установить в приложении точку сохранения, рассказывается в следующем разделе.)
Параметр имя_гочкиуохранения — это необъявляемый идентификатор Oracle. Им не может быть ни литерал (заключенный в кавычки), ни имя переменной.
Приведем несколько примеров инструкции ROLLBACK:
ROLLBACK:
ROLLBACK WORK;■,
ROLLBACK TO beg1n_cleanup;
При откате до заданной точки сохранения все установленные после нее точки стираются, но данная точка остается. Это означает, что можно возобновить с нее транзакцию и при необходимости снова вернуться к этой же точке сохранения.
Перед выполнением инструкции INSERT, UPDATE или DELETE PL/SQL автоматически устанавливает неявную точку сохранения, и если инструкция завершается ошибкой, выполняется автоматический откат до этой тонки. Иными словами, любая неудачная DML-инструкция просто отменяется.
Инструкция SAVEPOINT
Устанавливает в транзакции именованный маркер, позволяющий в случае необходимости выполнить опсат транзакции до отмеченной им точки сохранения. При таком откате отменяются все изменения и удаляются все блокировки после этой
точки, но сохраняются изменения и блокировки, выполненные и установленные до нее.
Синтаксис инструкции SAVEPOINT таков:
SAVEPOINT ш_гочки_сохранения;
Здесь нмн_точкн」охранемнн - необъявляемый идентификатор. Он должен соответствовать общим правилам формирования идентификаторов Oracle, то есть иметь длину до 30 символов, начинаться с буквы, и состоять из букв, цифр и символов «#», «$> и «」.Однако объявлять его не нужно (да и невозможно).
Область действия точки сохранения не ограничивается блоком PL/SQL, в котором она установлена. Если в ходе транзакции имя точки сохранения использовано повторно, эта точка просто ^перемещается» в новую позицию, причем независимо от процедуры, функции или анонимного блока, в котором выполняется инструкция SAVEPOINT. Поэтому если точка сохранения устанавливается в рекурсивной программе; на самом деле на каждом уровне рекурсии она задается заново; и не рассчитывайте, что, вернувшись из очередного вызова, можно будет выполнить откат к предыдущей точке сохранения: она только одна — та, что установлена последней.
Инструкция SET TRANSACTION
Позволяет начать сеанс чтения или чтения-записи, установить уровень изоляции или связать текущую транзакцию с заданным сегментом отката. Эта инструкция должна быть первой S QL-инструкцией транзакции и дважды использоваться в ходе одной транзакции не может. У нее имеются четыре модификации.
О SET TRANSACTION READ ONLY - определяет текущую транзакцию как «только для чтения». В транзакциях указанного типа всем запросам доступны лишь те изменения, которые были зафиксированы до начала транзакции (благодаря чему запросам всегда доступны согласованные данные таблиц). Они применяются, в частности, в медленно формируемых отчетах со множеством запросов, благодаря чему в них используются строго согласованные данные.
Q SET TRANSACTION READ WRITE — определяет текущую транзакцию как операцию чтения и записи данных в таблицу.
О SET TRANSACTION ISOLATION LEVEL SERIALIZABLE | READ COMMITTED - определяет способ выполнения транзакции, модифицирующей базу данных. С ее помощью можно задать один из двух уровней изоляции транзакции: SERIALIZABLE или READ COMMITED. Если выбран уровень SERIALIZABLE, то инструкции манипулирования данными (UPDATE, INSERT или DELETE), пытающейся модифицировать таблицу, которая уже изменена незафиксированной транзакцией, будет отказано в этой операции. Для выполнения данной инструкции в инициал изационном параметре базы данных COMPATIBLE должна быть задана версия 7.3.0 или выше.
Если задан уровень READ COMMITED, DML-инструкция, которой требуется доступ к строке, заблокированной другой транзакцией, будет ждать снятия этой блокировки.
Q SET TRANSACTION USE ROLLBACK SEGMENT имя^сегментд — назначает текущей транзакции заданный сегмент отката и определяет транзакцию как «только для чтения». Она не может использоваться совместно с инструкцией SET TRANSACTION READ ONLY.
Инструкция LOCK TABLE
Позволяет заблокировать всю таблицу базы данных в указанном режиме. Она запрещает или разрешает модифицировать данные в таблице другим транзакциям на то время, пока вы с ней работаете. Синтаксис инструкции LOCK TABLE:
LOCK TABLE список^аблиц IN режим_блокировки MODE [NOWAIT];
Здесь список_таблиц — список, в котором заданы одна или несколько таблиц либо представлений (то ли удаленных элементов, доступных через канал связи с базой данных); режим_блокировки - один из шести режимов: RQU SHARE, ROW EXCLUSIVE, SHARE UPDATE, SHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE.
Если задано ключевое слово NOWAIT, Oracle не ждет снятия блокировки в том случае, когда нужная таблица заблокирована другим пользователем. Если же это ключевое слово не задано, Oracle ждет освобождения таблицы сколь угодно долго. Блокировка таблицы не мешает другим пользователям считывать из нее данные.
Вот примеры допустимых инструкций LOCK TABLE:
LOCK TABLE emp IN ROW EXCLUSIVE MODE:
LOCK TABLE emp, dept IN SHARE MODE NOWAIT:
LOCK TABLE scott.emp^new^ork IN SHARE UPDATE MODE;
Автономные транзакции
До версии PL/SQL 8.1 в каждый конкретный момент времени в одном сеансе могла выполняться только одна активная транзакция. Иными словами, производимые в сеансе изменения должны были фиксироваться или отменяться только одновременно. Это ограничение рассматривалось разработчиками как большое неудобство, поскольку им нужна была возможность сохранять и отменять отдельные DML-инструкции (INSERT, UPDATE и DELETE), не затрагивая всю транзакцию сеанса.
Указанная возможность была реализована в PL/SQL 8.1 в виде так называемых автономно транзакций. Определяя блок PL/SQL (анонимный блок, процедуру, функцию, пакетную процедуру, пакетную функцию или триггер базы данных) как автономную транзакцию, вы изолируете выполняемые в нем DML-инструкции от контекста транзакции вызывающего кода. Этот блок определяется как независимая транзакция, начатая другой транзакцией, которая является главной.
В блоке автономной транзакции главная транзакция приостанавливается. Вы выполняете SQL-операции, затем производите их фиксацию или откат и возобновляете главную транзакцию (рис. 13.4).
PROCEDURE add_emp(...) IS BEGINUPDATE UPDATESAVEPOINT start^add:INSERTCOMMIT:EXCEPTIONPROCEDURE add_emp(...) IS BEGINUPDATE UPDATESAVEPOINT start^add:INSERTCOMMIT:EXCEPTIONГлавная транзакция (ГТ)Главная транзакция (ГТ)Начало ГТНачало ГТАвтономная транзакция (АТ)Автономная транзакция (АТ)WHEN OTHERSTHENROLLBACK TO start_add:writeJogC ~ SQLCODE, SQLERRM;)WHEN OTHERSTHENROLLBACK TO start_add:writeJogC ~ SQLCODE, SQLERRM;)Приостановка ГТПриостановка ГТPROCEDURE write_log(...)PROCEDURE write_log(...)PRAGMAAUTONOMOUS TANSACTIONPRAGMAAUTONOMOUS TANSACTIONEND:END:)Завершение ГГ)Завершение ГГвозобновление ГТвозобновление ГТBEGININSERT INTO logVALUES( code.text. USER.SYSDATE ):COMMIT;END:BEGININSERT INTO logVALUES( code.text. USER.SYSDATE ):COMMIT;END:Начало АТНачало АТЗавершение ATЗавершение AT
PROCEDURE add_emp(...) IS BEGIN
UPDATE UPDATE
SAVEPOINT start^add:
INSERT
COMMIT:
EXCEPTION
PROCEDURE add_emp(...) IS BEGIN
UPDATE UPDATE
SAVEPOINT start^add:
INSERT
COMMIT:
EXCEPTION
Главная транзакция (ГТ)
Главная транзакция (ГТ)
Начало ГТ
Начало ГТ
Автономная транзакция (АТ)
Автономная транзакция (АТ)
WHEN OTHERS
THEN
ROLLBACK TO start_add:
writeJogC
~ SQLCODE, SQLERRM;)
WHEN OTHERS
THEN
ROLLBACK TO start_add:
writeJogC
~ SQLCODE, SQLERRM;)
Приостановка ГТ
Приостановка ГТ
PROCEDURE write_log(...)
PROCEDURE write_log(...)
PRAGMA
AUTONOMOUS TANSACTION
PRAGMA
AUTONOMOUS TANSACTION
END:
END:
)Завершение ГГ
)Завершение ГГ
возобновление ГТ
возобновление ГТ
BEGIN
INSERT INTO log
VALUES( code.text. USER.SYSDATE ):
COMMIT;
END:
BEGIN
INSERT INTO log
VALUES( code.text. USER.SYSDATE ):
COMMIT;
END:
Начало АТ
Начало АТ
Завершение AT
Завершение AT
Рис. 13.4. Последовательность выполнения главнойи вложенной (автономной) транзакций
Определение автономной транзакции
Определить блок PL/SQL как автономную транзакцию очень просто. Для этого достаточно включить в его раздел объявлений следующую директиву:
PRAGMA AUTONOMOUSJRANSACTION;
Данная директива предписывает компилятору PL/SQL определить блок кода как автономный, или независимый. Автономным может быть один из следующих блоков:
О <самый внешний» анонимный блок PL/SQL (не вложенный);
О функция или процедура, определенная в пакете или как отдельная программа;
О метод (функция или процедура) объектного типа;
Q триггер базы данных.
Директива AUTONOMOUS_TRANSACTION может располагаться в любом месте раздела объявлений блока PL/SQL. Однако лучше всего поместить ее перед определениями структур данных, чтобы каждый протраммист, которому придется читать ваш код, сразу идентифицировал программу как автономную транзакцию. Введение описанной директивы — единственное изменение, произведенное в PL/SQL Для поддержки автономных транзакций. Остальные операторы, инструкции COMMIT и ROLLBACK, а также инструкции DML остались прежними. Теперь автономная транзакция просто меняет область их действия.
В каких случаях следует применять автономные транзакции
При использовании автономных транзакций необходимо придерживаться простого принципа: программный модуль определяется как автономный, если выполняемые в нем изменения должны быть изолированы от контекста транзакции вызывающего кода. Ниже описаны типичные случаи применения автономных транзакций.
О Механизм протоколирования. Вам требуется записать информацию об ошибке в таблицу базы данных, а также выполнить откат внутренне执 транзакции, обусловленной возникновением ошибки. Вы не хотите, чтобы из таблицы-журнала были удалены эта и другие записи об ошибках. Какой же выход? Сделать внутреннюю транзакцию автономной!
О Фиксация и откат в триггерах базы данных. Определив триггер как автономную транзакцию, все выполненные им изменения можно фиксировать или отменять. Разработчики давно мечтали о такой возможности.
О Счетчик попыток. Предположим, что вы хотите предоставить пользователю N попыток доступа к ресурсу. Более того, вам требуется, чтобы количество попыток запоминалось между подключениями к базе данных. Для этого необходима инструкция COMMIT, независимая от транзакции. Пример утилиты, выполняющей подобную задачу, вы найдете в файлах retry.pkg и retry.tst на узле издательства O'Reilly.
О Счетчик использования программы. Вам нужно знать, как часто в ходе сеанса приложения вызывается некоторая программа. Такого рода информация не зависит от выполняемой приложением транзакции.
О Многократно используемые компоненты приложения. Для программ этого типа возможность определять автономные транзакции просто жизненно необходима. В современных системах, особенно в распределенном и многоуровневом мире Интернета, необходимы независимые программные единицы (иногда называемые картриджами), выполняющие свою работу без каких-либо побочных эффектов для вызывающего их окружения. Автономные транзакции играют важную роль в этой области.
Ниже речь пойдет о применении автономных транзакций в каждом из указанных случаев, но сначала поговорим о том, что можно и чего нельзя делать в рамках автономной транзакции.
Правила и ограничения на использование автономных транзакций
Включить в программный код директиву AUTONOMOUS_TRANSACTION не составляет особого труда. Однако при ее использовании существует целый ряд правил и ограничений.
q Автономкой транзакцией может быть только анонимный блок «самого внешнего» уровня. Поэтому следующий код допустим:
DECLARE
PRAGMA AUTONMUSJRANSACTION;
щуетрло NUMBER:
BEGIN
INSERT INTO emp VALUES Сщуешрпо.
COMMIT;
END:
В то же время программа
declare
щуетрп。 NUMBER:
BEGIN
DECLARE.
PRAGMA AUTONOMOUS TRANSACTION;
BEGIN■
INSERT INTO emp VALUES (щуетрпо, COMMIT;
END:
END;
вызовет ошибку:
PLS-00710: PRAGMA AUT0m0US_TRANSACTI0N cannot be declared here
О Если для выполнения автономной транзакции потребуется доступ к ресурсу, заблокированному главной транзакцией (приостановленной до завершения автономной программы), произойдет взаимоблокировка. Вот простой пример: программа обновляет таблицу, а затем, не зафиксировав изменения, вызывает автономную процедуру, которая должна внести изменения в ту же таблицу:
/* Файл 8 web: autondlock.sql */
CREATE OR REPLACE PROCEDURE update_salary(dept_in IN NUMBER)
IS-~
PRAGMA AUTONOMOUS_TRANSACTION;■
CURSOR rnyemps IS
SELECT empno FROM emp
WHERE deptno - dept in
FOR UPDATE MOWAIT?
BEGIN
FOR rec IN rnyemps
LOOP
UPDATE emp SET sal - sal * 2 WHERE empno - rec.empno;
END LOOP:
COMMIT:
END;
BEGIN
UPDATE emp SET sal - sal * 2;
update_salary(10):
END;"
Результаты выполнения этого кода не радуют:.
ERROR at Иne 1:
DRA-00054: resource busy and acquire with NOWAIT specified
О С помощью одной директивы невозможно определить как автономные все подпрограммы пакета или все методы объектного типа. Эту директиву нужно явно включать в каждую программу. Поэтому на основании только спецификации пакета нельзя выяснить, какие из программ работают в режиме автономной транзакции.
О Для выхода из программы с автономной транзакцией без ошибок нужно выполнить явную фиксацию или откат транзакции. Если в программе содержится незавершенная транзакция, исполняющее ядро инициирует исключение и производит откат незафиксированной транзакции:
ORA-06519: active autonmous transaction deleted and rolled back
О Инструкции COMMIT и ROLLBACK завершают активную автономную транзакцию, но не программу, в которой она содержится. Поэтому в блоке с автономной транзакцией может быть несколько инструкций COMMIT и ROLLBACK.
О При выполнении отката до точки сохранения эта точка должна быть определена в текущей транзакции. В частности, нельзя выполнить откат из автономной транзакции до точки сохранения, определенной в главной транзакции. Если же вы это сделаете, исполняющее ядро инициирует следующее исключение:
ORA-01086: savepoint 1ваша_точкауохранения' never established
О Параметр TRANSACTIONS в инициализационном файле Oracle определяет допустимое количество выполняемых одновременно автономных транзакций для одного сеанса. Если в приложении используется множество программ, выполняющихся в режиме автономных транзакций, этот предел может быть превышен, что вызовет инициирование следующего исключения:
DRA-01574: maximum number of concurrent transactions exceeded
В подобном случае следует увеличить значение параметра TRANSACTIONS. По умолчанию оно равно 75.
Автономные транзакции в SQL
Начиная с Oracle 7.3 появилась возможность вызывать из SQL пользовательские функции, соблюдая определенные правила. Главное из них такое: эти функции не должны обновлять базу данных. И конечно, в них нельзя сохранять или отменять изменения.
Однако с появлением автономных транзакций картина изменилась. Автономная транзакция никогда не нарушает два уровня чистоты программы - RNDS (Reads No Database State — не читать из базы данных) и WNDS (Writes No Database State — не записывать в базу данных), даже если программа выполняет чтение из базы данных или запись в таковую. Это возможно благодаря тому, что уровни чистоты относятся к S QL-инструкции (которая в этом случае является главной транзакцией). Тем не менее DML-операции автономной транзакции никогда не отражаются на главной транзакции.
Поэтому если программа определена как выполняющая автономную транзакцию, ее можно непосредственно или косвенно вызывать из S QL-инструкции. Конечно, в том случае, когда программа не может обеспечить другого уровня чисто- ты, необходимого для вызова из SQL-инструкций, например WNPS (Writes No Package State — не писать в переменные пакета), ее нельзя вызывать из определенных их частей, и в частности из предложения WHERE.
Предположим, что нам нужно знать, какие строки обрабатываются запросом. Для этого мы создаем таблицу query_trace:
/* Файл в web: trcfunc.sql */
CREATE TABLE query_trace C
table_name VARCHAR2C30),
rowidjnfo ROWID,
queried_by VARCHAR2C30).
queried_a.t DATE
);-—-
Для вставки данных в таблицу напишем простую функцию:
CREATE OR REPLACE FUNCTION traceit (
tab IN VARCHAR2,
rowidjn IN RDWID)
RETURN INTEGER
IS
BEGIN
INSERT INTO query_trace VALUES (tab. rowidjn, USER, SYSDATE);
RETURN 0:
END;
Попытавшись использовать эту функцию в запросе, мы получим сообщение об ошибке;
SQL> select ename, traceit C'emp', rowld) from emp:
ERROR at line 1:
ORA-14551: cannot perform a DML operation inside a query
Однако если определить эту функцию как автономную транзакцию, добавив Директиву AJTONOMOUS_TRANSACTION( и зафиксировать результаты перед выполнением оператора RETURN, результаты будут совсем иными. Запрос сработает, и таблица query_trace будет заполнена данными о выбранных строках:
SQL> SELECT ename, traceit ('emp', rowid) from enp:
ENAMETRACEITCEMP'.ROWID)
KING0
MILLER0
M rows selected.
$QL> SELECT table name, rowid_info, quer1ed_by,
TO_CHAR (querled^at. 'HH:MI:S5') queried_at
FROM query trace:
TABLE—NAMEROWID_INFOQUERIED_8YQUERIED_AT
empAAADEPAACMAAgOAAASCOTT05:32:54
empAAADEPAACAAAAgOAANSCOTT05:36:50
Существуют и другие способы трассировки запросов: например, можно выводить данные на экран с помощью программ встроенного пакета DBMS_OUTPUT или конвейерным методом пересылать информацию, используя программы пакета DBMS_PIPE. Но теперь, когда Oracle поддерживает автономные транзакции, удобнее записывать информацию в таблицы базы данных, разумеется, тщательно проанализировав издержки, к которым приводит эта операция.
Область видимости транзакции
После выполнения в автономной транзакции инструкции COMMIT или ROLLBACK изменения по умолчанию немедленно становятся видимыми в главной транзакции. Но что если потребуется скрыть их от последней?
Для этой цели в Oracle имеется специальная модификация инструкции SET TRANSACTION:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE:
По умолчанию для транзакции устанавливается уровень изоляции READ СОМ- MITED, при котором зафиксированные изменения тут же становятся видимыми главной транзакции.
Инструкцию SET TRANSACTION, как обычно, следует выполнить до начала транзакции (то есть перед первой SQL-инструкцией). Кроме того, данная установка влияет на весь сеанс, а не только на текущую программу. Следующий сценарий демонстрирует уровень изоляции SERIALIZABLE в действии. (Вы найдете этот сценарий в файле autonserial.sql на узле O'Reilly и сможете самостоятельно выполнить описанные ниже операции.)
Итак, сначала создается автономная процедура:
/* Файл в web: autonserial.sql */
CREATE OR REPLACE PROCEDURE fire_etn_aH
IS
PRAGMA AUTONOMOUS_TRANSACTION:
BEGIN-
DELETE FROM empZ;''
COMMIT:
END:
Затем выполняется сценарий, который устанавливает уровень изоляции SERIALIZABLE и выводит количество строк таблицы етр2 в следующие моменты:
О до вызова процедуры fi re_em_a 11;
О после вызова процедуры fire_eni_al 1, но до фиксации или отката главной транзакции;
О после фиксации главной транзакции.
Вот этот сценарий;
declare■
PROCEDURE showcount Cstr VARCHAR2) IS
num INTEGER:
BEGIN'
SELECT COUNT(*) INTO num FROM tasks:
DBMS_OUTPUT.PUT_LINE (str || ' ' || num || 1 строк');
END:-
BEGIN
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
showcount ('Перед изолированной автономной транзакцией удалено'):
firB_em_all:'
showcount ('После изолированной автономной транзакции удалено');
COMMIT;
showcount С'После главной транзакции зафиксированно'};
END;
Он выводит следующие строки:
Перед изолированной автономной транзакцией удалено 14 строк
После изолированной автономной транзакциЕЙ удалено 14 строк'
После главной транзакции з帥мксированн。 0 строк
Примеры автономных транзакций
В этом разделе приведено несколько примеров использования автономных транзакций в приложениях.
Создание механизма автономного протоколирования
Одним из распространенных требований к приложениям является ведение журнала ошибок, происходящих в ходе выполнения транзакций, Самым удобным местом для хранения журнала является таблица базы данных, поскольку она всегда доступна и из нее удобно с помощью SQL извлекать информацию для анализа.
Однако у такого журнала имеется один недостаток: операции записи в журнал становятся частью транзакции. И если в программе будет выполнена инструкция ROLLBACK, из журнала может быть удалена часть записей об ошибках. Это, конечно же, недопустимо. Можно попытаться с помощью точек сохранения выполнить откат таким образом, чтобы записи журнала оставались нетронутыми, но это крайне неудобно. А вот если воспользоваться механизмом автономных транзакции, протоколирование значительно упростится и станет более надежным.
Предположим, что у нас имеется следующая таблица-журнал:
停 Файл в web: log.pkg */
CREATE TABLE logtab (
code INTEGER.
text VARCHAR2(4000).■
crBated_on DATE,,
created二by VARCHAR2C100).
chmnged_on DATE,'
changed^by VARCHAR2C100).
machine VARCHAR2C100).
program VARCHAR2C100)
Ее можно использовать для хранения информации об ошибках (с помощью функций SQLCODE и SQLERRM), а также для протоколирования других событий, не связаяных с ошибками. В столбцы machine и program помещается информация из виртуальной таблицы VSSESSION.
Вы спрашиваете, как пользоваться этим журналом? Прежде всего не следует делать следующего:
EXCEPTION
,WHEN OTHERS
THEN.
v_code SQLCOOE;'
v_msg :- SQLERRM:.
INSERT INTO logtab VALUES (
v_code, v_msg, SYSDATE. USER, SYSDATE. USER. NULL. NULL):
END:-
Иными словами, никогда не реализуйте механизм протоколирования в разделе исключений и других разделах основного кода приложения. Выполняйте все операции с таблицей-журналом на отдельном программном уровне (технология называется инкапсуляцией). Для этого есть два существенных основания.
О Если структура таблицы однажды изменится, протраммы, в которых производится протоколирование ошибок, не пострадают.
О Протраммистам гораздо проще работать с журналом, их операции с таблицей всегда одинаковы.
В нашем примере протоколирование осуществляется с помощью простого пакета, состоящего из двух процедур:
CREATE OR REPLACE PACKAGE log
IS.
PROCEDURE putline (
codejn IN INTEGER, textjn IN VARCHAR2);
PROCEDURE savellne (
codejn IN INTEGER, textjn IN VARCHAR2):
END;
В чем разница между процедурами putline и saveline? Процедура log.save!Ше (как вы увидите далее в теле пакета) является автономной транзакцией, тогда как 1 og. putl i ne просто вставляет строку в таблицу. Вот тело пакета:
/* Файл в web; log.pkg */
CREATE OR REPLACE PACKAGE BODY log
IS
CURSOR sess IS■
SELECT MACHINE. PROGRAM
FROM VSSESSION
WHERE AUDSID = USERENVf'SESSIONID'):
rec sbss^ROWTyPE;
PROCEDURE putline С code J n IN INTEGER, textjn IN VARCHAR2)
IS
BEGIN-
INSERT INTO logtab VALUES ( codejn,'
textjn. SYSDATE.-.
USER, SYSDATE.-
USER, rec.machine, rec.program); END;'
PROCEDURE saveline( code J n IN INTEGER, textjn IN VARCHAR2)'
IS.
PRAGMA AUTONCWUSJRANSACTION: BEGIN-
putl1ne(CQde_1n. text^in); (MIT:~“
EXCEPTION WHEN OTHERS THEN ROLLBACK:'
END: END:
Следующие комментарии помогут вам понять происходящее в программах. О Во время инициализации пакета мы считываем из представления V$SESSION информацию, которую потом будем записывать в журнал (значения в течение сеанса не изменяются, и поэтому достаточно запросить их только один раз).
О Процедура putline выполняет обыкновенную вставку. Возможно, вы захотите добавить в нее раздел обработки исключений, если возьмете пакет 1 од за основу подсистемы протоколирования ошибок для реального приложения.
О Процедура saveline вызывает процедуру putline, но делает это в контексте автономной транзакции.
При наличии описываемого пакета обработка ошибок значительно упрощается: EXCEPTION
WHEN OTHERS
THEN
log.saveline (SOLCODE. SQLERRM); END;
Отлично. Разработчикам не нужно задумываться о структуре таблицы журна- 必 и, более того, они могут вообще не знать, что запись производится в таблицу •базы данных. А поскольку транзакция автономна, что бы ни происходило в приложении —журнал останется в целости и сохранности.
Использование автономных транзакций в триггерах базы данных
Благодаря введению автономных транзакций появились новые возможности при работе с триггерами (о которых рассказывается в главе 18): теперь в них можно использовать инструкции COMMIT и ROLLBACK, чего ранее не разрешалось. При этом фиксация или откат транзакции в триггере не отражается на главной транзакции, из которой он был запущен; эти операции относятся только к DML-инструкциям, выполняемым внутри триггера (и хранимых программ, вызываемых из триггера).
В триггерах базы данных часто осуществляются действия, которые не должны отражаться на исходной транзакции. Предположим, что нам нужно отслеживать все выполняемые по отношению к таблице операции независимо от того, завершены они или нет; следует также знать, какие из операций завершились ошибками. Давайте посмотрим, как это можно сделать с помощью автономных транзакций.
Создание триггера базы данных
Для начала напишем простой триггер с автономной транзакцией, записывающий сообщение в таблицу ceo_comp_h1 story. Вот определения двух таблиц:
/* Файл a web: autontrlgger.sql */
CREATE TABLE ceo_compensation (
company VARCHAR2(100).
name VARCHAR2E100).
compensation NUMBER.
layoffs NUMBER);
CREATE TABLE ceo_comp_history (
name VARCHAR2(100)7
description VARCHAR2(255),
occurred_on DATE):.
А вот сам триггер:
CREATE OR REPLACE TRIGGER bef^ins_ceo_conip
BEFORE INSERT ON ceo_compensat1 on^FOR*"EACH ROW
DECLARE~
PRAGMA AUraNOMOUS_TRANSACTION:
BEGIN
INSERT INTO ceo_comp_history VALUES (
:new.name. 'BEFORE INSERT', SYSDATE);
COMMIT;
END;
С помощью этого триггера можно протоколировать все попытки вставки данных в таблицу ceo^ompensation, как показано в следующем примере:
BEGIN
INSERT INTO ceo_compensation VALUES ( 'Mattel1, 'Jill Barad', 9100000, 2700);
INSERT INTO ceo_compensation VALUES (
'American Express Company', 'Harvey Golub'. 33200000. 3300);
INSERT INTO ceo_compensat1on VALUES (
'Eastman Kodak'. 'George Fisher1. 10700000. 20100):
ROLLBACK:
END;
SQL> SELECT name,
description,
T0_CHAR (occurred_on. 'fW/DD/YYYY HH:MI:SS') occurred_on
FROM ceo_conp_history;
MMEDESCRIPTIONOCCURRED_ON
MattelBEFOREINSERT03/17/199904:00:56
American Express CompanyBEFOREINSERT03/17/199904:00:56
Eastman KodakBEFOREINSERT03/17/199904:00:56
В файле autontrlgger.sql на узле издательства O'Reilly вы найдете весь код( необходимый для создания объектов и тестирования триггера.
Доработка триггера базы данных
К сожалению, у нашего триггера имеется один существенный недостаток. Он определяется как выполняющий транзакцию, поскольку в теле триггера модифици* руется таблица базы данных и фиксируются изменения. Но что если в нем же потребуется произвести некоторые операции, относящиеся к главной транзакции? Эти изменения в случае отката главной транзакции сохранятся, что, конечно же, недопустимо с точки зрения целостности данных.
Поэтому в общем случае не стоит определять триггеры базы данных как автономные транзакции. Все независимые DML-операции (такие как запись информации в журнал) лучше осуществлять в отдельной процедуре, вызываемой из триггера и определяемой в качестве выполняющей автономную транзакцию.
Это решение реализовано в сценарии autontrlggerZ.sql, который вы также найдете на узле O'Reilly. Сначала создается процедура
/* Файл в web: autontrigger2.sql */
CREATE OR REPLACE PROCEDURE audit ceo corap (
name IN VARCHAR2," "
description IN VARCHAR2.
occurred on IN DATE
IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN~
INSERT INTO ceo_comp_history VALUES (
aud1t_ceo_comp.name,
audit_ceo_comp.description, audit_ceo_comp.occurred on
COMMIT;
END:
Новый триггер, вызывающий данную процедуру, будет таким:
CREATE or REPLACE TRIGGER aft_ins_ceo^comp
AFTER INSERT ON ceo compensation FOR EACH ROW
declare-
ok BOOLEAN ;- is valid conp info (:new.name):
BEGIN~"
IF ok
THEN
audit_ceo_comp (
:new.nime. 'AFTER INSERT', SYSDATE);
ELSE
RAISE VALUE_ERROR:
END IF; -
END:
Обратите внимание на следующие усовершенствования.
Мы изменили спецификацию триггера - теперь это AFTER INSERT, а не BEFORE INSERT. Пусть он лучше вызывается после операции INSERT и протоколирует уже выполненные изменения.
Если функция is_valid_comp_'info возвращает FALSE, протоколирование не выполняется, а транзакция прекращается и инициируется исключение. Это еще одна из причин, по которым триггер не должен быть автономным: в одних случаях вызываемая им процедура должна выполнятся, а в других — главная транзакция должна быть остановлена с помощью исключения.
Новая технология автономных транзакций очень удачна, и вы, наверняка, не раз будете иметь возможность это оценить. Но учтите, что транзакции требуют самого тщательного планирования. И поэтому независимые транзакции всегда следует скрывать за процедурным интерфейсом.
1414Выборка данныхВыборка данных
14
14
Выборка данных
Выборка данных
А Основы курсоров
А Работа с неявными курсорами
А Работа с явными курсорами
А Предложение BULK COLLECT
А Инструкция SELECT с предложением FOR UPDATE
А Курсорные переменные
А Курсорные выражения (Oracle9i)
Одной из важнейших характеристик PI^SQL является тесная интеграция с базой данных Oracle как в отношении изменения данных в таблицах, так и в отношении выборки информации из таблиц. В этой главе рассматриваются элементы PL/SQL, связанные с выборкой информации из базы данных и ее обработкой в программах PL/SQL.
Когда SQL-инструкция выполняется из PL/SQL, РСУБД Oracle назначает ей приватную рабочую область, а некоторые данные записывает в системную глобальную область (System Global Area, SGA). В приватной рабочей области содержится информация об S QL-инструкции и набор данных, возвращаемых или обрабатываемых этой инструкцией.
PL/SQL предоставляет программистам несколько механизмов доступа к этой рабочей области и содержащейся в ней информации. Перечислим их.
Q Неявные курсоры. Инструкция SELECT.. .INTO считывает одну строку данных и присваивает ее в качестве значения локальной переменной программы. Это простейший (и зачастую наиболее эффективный) способ доступа к данным, но он часто ведет к написанию сходных и даже одинаковых SQL-инструкций во многих местах программы.
Q Явные курсоры. Запрос можно явно объявить как курсор в разделе объявлений локального блока или пакета. После этого такой курсор можно будет открывать и выбирать из него данные в одной или нескольких программах, причем возможности управления явным курсором шире, чем неявным.
О Курсорные переменные. Курсорные переменные (в объявлении которых задан тип REF CURSOR) позволяют передавать из программы в программу указатель на результирующий набор строк запроса. В любой программе, имея доступ к такой переменной, можно открыть курсор, извлечь из него необходимые данные и закрыть его.
О Курсорные вырвжения. Введенные в Oracle9i курсорные выражения, задаваемые с помощью оператора CURSOR, преобразуют извлекаемые инструкцией SELECT данные в результирующий набор строк типа REF CURSOR и могут использоваться совместно с табличными функциями для повышения производительности приложения.
О Динамические SQL-запрось!. Oracle позволяет динамически составлять и выполнять запросы, используя либо встроенный динамический SQL (NDS), либо программы пакета DBMS_SQL. Об NDS, введенном в Oracle8z, рассказывается в главе 15, а встроенный пакет DBMS_SQL подробно описан в книге Oracle Built-In Packages издательства O'Reilly.
В этой главе рассказывается о неявных и явных курсорах, курсорных переменных и выражениях.
Основы курсоров
Простейшую форму курсора можно представить себе как указатель на таблицу в базе данных. Например, следующее объявление курсора связывает всю таблицу employee с курсором employee_cur:.
CURSOR employee_cur IS SELECT * FROM employee:
После объявления курсора его можно открыть:
OPEN employee_cur:
Далее можно выбирать из него строки:
FETCH employee_cur into employee_rec;■
Окончив работу с курсором, его следует закрыть:
CLOSE employee_cur:
В рассматриваемом примере каждая выбранная из курсора запись представляет собой строку таблицы employee. С курсором можно связать любую допустимую инструкцию SELECT. В следующем примере в объявлении курсора объединяются три таблицы:
DECLARE
CURSOR joke_feedback_cur
SELECT J.name. R.laugh_volume, C.name
FROM joke J. response R. comedian C
WHERE J.jokejd - R.jokejd
AND J. joker J d = C. jokerjd;
BEGIN~~
END:
В данном случае курсор действует не как указатель на конкретную таблицу базы данных - он указывает на виртуальную таблицу или неявное представление, определяемое инструкцией SELECT. (Мы называем такую таблицу виртуальной по- тому, что инструкция SELECT генерирует данные, имеющие табличную структуру, но эта таблица существует только временно, пока программа работает с возвращенными инструкцией данными.) Если тройное объединение возвращает табли- Цу1 содержащую 20 строк и три столбца, курсор функционирует как указатель на эти 20 строк.
Некоторые термины, связанные с выборкой данных
В PL/SQL имеется множество возможностей выполнения S(^Ь-инстр5^кций, и все они реализованы в программах как курсоры того или иного типа. Прежде чем приступить к их освоению, вам нужно познакомиться с методами выборки данных и используемой при этом терминологией.
О Статический SQL. SQL-инструкция является статической, если она опредв' ляется во время компиляции программы.
О Динамический SQJL SQL-инструкция является динамической, если она определяется во время выполнения программы и затем выполняется, так что в программном коде нет ее полного, жестко закодированного объявления. Для динамического выполнения SQL-инструкций могут использоваться программы встроенного пакета DBMS_SQL (имеющегося во всех версиях Oracle) или встро- енный динамический SQL, введенный в Oracle8i и описанный в главе 15.
О Результируюпщй набор строк. Это набор строк, содержащих результирую- щие данные, определяемые SQL-инструкцией. Результирующий набор строк кэшируется в системной глобальной области с целью ускорения чтения и модификации его данных. Для доступа к нему Oracle поддерживает указатель, который мы будем называть текущей строкой.
О Неявный курсор. При каждом выполнении инструкции DML (INSERT, UPDATE или DELETE) либо инструкции SELECT INTO, возвращающей строку из базы данных прямо в структуру данных программы, PL/SQL создает неявный курсор. Курсор этого типа называется неявным, поскольку Oracle автоматически выполняет многие связанные с ним операции, такие как открытие, выборка строк и даже закрытие.
Q Явный курсор. Это инструкция SELECT, явно определенная в программе как курсор. Все операции с явным курсором (его открытие, выборка данных, закрытие и т. д.) в программе должны выполняться явно. Как правило, явные курсоры используются для выборки из базы данных набора строк с помощью статического SQL.
Q Курсорная переменная. Это объявленная программистом переменная, указывающая на объект курсора в базе данных. Ее значение (то есть указатель на курсор или результирующий набор строк) во время выполнения программы может меняться, как у самой настоящей переменной. В разные моменты времени курсорная переменная может указывать на разные объекты курсора.
Курсорную переменную можно передать в качестве параметра процедуре или функции. Такие переменные очень полезны для передачи результирующих наборов строк из программы PL/SQL в другие среды, такие как Java или Visual Basic.
О Атрибут курсора. Атрибут курсора имеет форму %иня_атрибута и добавляется к имени курсора или курсорной переменной. Это что-то вроде внутренней переменной Oracle, возвращающей информацию о состоянии курсора, например о том, открыт ли курсор или сколько строк из курсора вернул запрос. Для явных и неявных курсоров и для динамического SQL атрибуты курсора работают немного по-разному.
О Инструкция SELECT...FOR UPDATE. Это особая разновидность обычной инструкции SELECT, устанавливающая блокировку на каждую возвращаемую запросом строку данных. Пользоваться ею следует только в тех случаях, когда нужно «зарезервировать* запрошенные данные, с тем чтобы никто другой не смог их изменить, пока с ними работаете вы.
О Пакетная обработка. В Oracle8i и выше PL/SQL поддерживает запросы с предложением BULK COLLECT, позволяющим за один раз выбрать из базы данных более одной строки.
Типичные операции с запросами и курсорами
Независимо от типа курсора процесс выполнения SQL-инструкций всегда состоит из одних и тех же действий. В одних случаях PL/SQL производит их автоматически, а в других, как, например, при использовании явного курсора, они явно организуются программистом.
О Синтаксический анализ. Первым делом при обработке SQL-инструкции должен быть выполнен ее синтаксический анализ, то есть проверка ее корректности и формирование плана ее выполнения (с использованием оптимизации по синтаксису или по стоимости, в зависимости от того, какое значение параметра OPTIMIZER_MODE задал администратор базы данных).
О Привязка. Привязкой, или связыванием, называется установка соответствия между значениями хост-переменных прмраммы и параметрами SQL-инструкции. Для статического SQL привязка производится ядром PL/SQL. Привязка параметров в динамическом SQL выполняется явно с помощью переменных привязки..
О Открытие. При открытии курсора определяется результирующий набор строк SQL-инструкции, для чего используются переменные привязки. Указатель активной или текущей строки указывает на первую строку результирующего набора. Иногда явное открытие курсора не требуется; ядро PL/SQL выполняет эту операцию автоматически (так происходит в случае применения неявных курсоров и встроенного динамического SQL).
О Выполнение. На этой стадии инструкция выполняется ядром SQL.
О Выборка. Выборка очередной строки из результирующего набора строк курсора осуществляется с помощью инструкции FETCH. После каждой выборки
PL/SQL перемещает указатель на одну строку вперед. Работая с явными курсорами, помните, что и после того как все строки будут исчерпаны, можно снова и снова выполнять инструкцию FETCH, но PL/SQL ничего не будет делать (и не станет инициировать исключение).
О Закрытие. Эта операция закрывает курсор и освобождает используемую им память. После закрытия курсор больше не содержит результирующего набора строк. Иногда явное закрытие курсора не требуется, поскольку PL/SQL делает это сам (для неявных курсоров и встроенного динамического SQL).
Операции, выполняемые для выборки информации из базы данных в программу PL/SQL, показаны на рис. 14.1.
База данных
Рис. 14.1. Упрощенное представление процесса выборкиданных с помощью курсора
Знакомство с атрибутами курсора
В данном разделе перечисляются и вкратце описываются атрибуты курсоров, о которых подробно рассказывается в этой главе, в разделах, посвященных разным типам курсоров, а также в главах 13 и 15.
PL/SQL поддерживает шесть атрибутов курсоров, перечисленных в табл. 14.1.
Таблица 14.1. Атрибуты курсоров
рмл
Что возвращает
%PUND
Значение TRUE, если успешно выбрана хотя бы одна строка; в противном случае возвращает значение FALSE
%NOTFOUND
Значение TRUE, если инструкция не выбрала ни одной строки; в противном случае возвращает значение FALSE
продолжением
Таблица 14.1 (продолжение)
Имя
Что возвращает
%ROWCOUNT
%ISOPEN
Количество строк, выбранных из курсора на данный момент времени
Значение TRUE, если курсор открыт; в противном случае возвращает значение FALSE
%BULK_ROWCOUhn'
Коллекцию, в которой для каждого элемента исходной коллекции, заданной в операторе FORALL, указано количество строк, модифицированных SQL-инструкцией
%BULK_EXCEPTIONS
Коллекцию, в которой для каждого элемента исходной коллекции, заданной в операторе FORALL, указано инициированное Oracle исключение
Для ссылки на атрибут курсора укажите в виде его префикса имя курсора или курсорной переменной. Вот несколько примеров.
О Открыт ли еще явный курсор?
DECLARE
CURSOR h叩pinesS-Cur IS SELECT simple_delights FROM ...;
BEGIN~~
OPEN happiness_cur;
IF happi ness_curXISOPEN THEN ...
Q Сколько строк мы выбрали из неявного курсора? (Обратите вним出ие, что именем курсора в данном случае является SQL.)
DECLARE
ТУРЕ numjab IS TABLE OF NUMBER;
deptnums nuni_tab:
BEGIN~
SELECT deptno
BULK COLLECT 1班0 deptnums
FROM dept;
DBMS OUTPUT.PUT LINE CSQLXROWCOUNT):
EMD; "~.
Атрибут курсора %FOUND
Атрибут XFOUND возвращает информацию о результате самой последней выборки из курсора. Он возвращает TRUE, если последняя инструкция FETCH для явного курсора вернула строку, и FALSE — в противном случае.
Если курсор еще не отрыт, обращение к атрибуту ZFOUND вызывает исключение INVALID_CURSOR. Этот атрибут можно использовать для любого открытого курсора, указав, как обычно, перед именем атрибута имя курсора.
В следующем примере в цикле перебираются все строки курсора caller_cur (содержащие сведения о звонящих), и для каждой строки обновляются все строки таблицы call, содержащие информацию о звонках данного человека до текущей даты- Если после очередной выборки атрибут ZFOUND возвращает FALSE, выполняется
выход из цикла. Этот атрибут проверяется также после каждого обновления, перед выводом информации об обновленной строке.
OPEN caller_cur:
LOOP
FETCH caller_cur INTO caller^rec;
EXIT WHEN NOT caller_curXFOUND:
UPDATE call
SET callerjd - caller_rec.callerjd
WHERE can_t1mestamp < SYSDATE;
IF SQLXFOUND THEN
OBMS_OUTPUT.PUT_LINE (
'Информация о звонках обновлена для' || caller_rec.caller_id);
ENO IF;"
ENO LOOP;
CLOSE caller_cur;
Атрибут курсора %NOTFOUND
Атрибут ^NOTFDUND противоположен атрибуту ^FOUND. Он возвращает TRUE, если последняя инструкция FETCH для явного курсора не вернула ни оной строки, поскольку последняя строка курсора уже выбрана предыдущей операцией. Если же курсор не может вернуть строку из-за ошибки, инициируется соответствующее исключение.
Если курсор еще не отрыт, обращение к атрибуту 關OTFOUND вызывает исключение INVALID_CURSOR. Для любого открытого курсора можно использовать этот атрибут, указав, как обычно, перед именем атрибута имя курсора.
В каких случаях лучше использовать атрибут XFDUND, а в каких 片NOTFOUND? Принципиальной разницы здесь нет. Просто выбирайте тот атрибут, который является для проверяемого условия более естественным. Например, в приведенном выше примере для выхода из цикла используется следующий оператор:
EXIT WHEN NOT caller^cur«FOUND:
Однако более читабельным (и эффективным) был бы следующий, эквивалентный ему оператор:
EXIT WHEN caller_curXNOTFOUND;
Атрибут курсора %ROWCOUNT
Атрибут ^ROWCOUNT возвращает количество строк, извлеченных из курсора на момент обращения к этому атрибуту. Сразу после открытия курсора атрибут возвращает нуль. Если же обратиться к атрибуту XRDWCOUNT еще не открытого курсора, будет инициировано исключение INVALID_CURSOR. После выборки очередной строки значение атрибута XROWCOUNT увеличивается на 1.
С помощью атрибута %ROWCOUNT можно узнать, выбрано ли заданное количество строк (или обновлено - в случае использования курсора для DML-операций), "нбо остановить программу после выполнения указанного количества итераций.
Атрибут курсора %ISOPEN
Атрибут 灯SOPEN возвращает значение TRUE, если курсор открыт, и значение FALSE в противном случае..
Атрибут курсора %BULK„ROWCOUNT
Атрибут %BULK_ROWCOUNT, предназначенный для использования с оператором FORALL, возвращает количество строк, обработанных каждой DML-инструкцией. Этот атрибут имеет семантику ассоциативного массива. Подробнее о нем рассказывается в главе 13.
Атрибут курсора %BULK_EXCEPTIONS
Атрибут XBULK_EXCEPTIONS, предназначенный для использования с оператором FORALL, возвращает информацию об исключениях, которые были инициированы при выполнении каждой DML-инструкции. Этот атрибут имеет семантику ассоциативного массива. Подробнее о нем рассказывается в главе 13.
ВНИМАНИЕ
Как показывают приведенные выше примеры, атрибуты курсоров можно использовать в программах PL/SQL, но их нельзя применять в SQL-инструкциях. Например, попытавшись использовать атрибут %ROWCOUNT в предложении WHERE инструкции SELECT:
SELECT callerjd, company_1d
FROM caller-■
WHERE companyjd - caller_curXROWCOUHT
вы получите сообщение об ошибке:
PLS-00229: Attribute expression within SQL expression
Ссылки на переменные PL/SQL в курсоре
Поскольку курсор должен быть связан с инструкцией SELECT, в нем всегда присуг- ствовует хотя бы одна ссылка на таблицу базы данных, из которой будет выполняться выборка строк, а также условие выборки (предложение WHERE). Однако это не означает, что инструкция SELECT может возвращать информацию только из базы данных.
Список выражений, задаваемых между ключевыми словами SELECT и FROM, называется списком выборки. Во встроенном SQL список выборки может содержать столбцы и выражения (SQL-функции этих столбцов, константы, и т. п.). В PL/SQL список выборки обычно содержит переменные PL/SQL и сложные выражения.
В следующем примере курсора инструкция SELECT извлекает строки на основе таблицы employee, но в списке выборки заданы не только столбцы этой таблицы, но и переменная PL/SQL, а также переменная привязки из хост-среды (элемент Oracle Forms):
DECLARE
/* Локальная переменная PL/SQL */
projected_bonus NUMBER := 1000:
/*~
Курсор 叩Убавляет $1000 к окладу каждого сотрудника.
принятого на работу более трех лет назад.
*/
CURSOR employee_cur
IS
SELECT employee_1d, salary + projected_bonus new_salary, /* Псевдоним столбца */ :review.evaluation /* Переменная 叩нвлзки */
FROM employee
■ WHERE hiredate < ADD_MONTHS (SYSDATE. -36);
BEGIN
END;
На локальные данные программы PL/SQL (переменные и константы), а также на переменные привязки хост-среды можно ссылаться в предложениях WHERE, GROUP BY и HAVING инструкции SELECT курсора.
Приоритет идентификаторов в инструкции курсора
Совместное использование переменных и столбцов базы данных требует внимательного отношения к их именам. Например, переменным часто присваивают имена столбцов данных, которые они представляют. Это очень удобно, но только до тех пор, пока вы не захотите задать в S QL-инструкции и локальные переменные, я столбцы таблицы.
В следующем примере из таблицы выбираются записи обо всех сотрудниках, принятых на работу более трех лет назад, для того чтобы затем прибавить к их окладам по $1000. В таблице сотрудников имеется столбец с именем salary, я такая же переменная имеется в программе. Хотя программный код будет откомпилирован без ошибок, результаты мы получим неправильные.
PROCEDURE imprave_QOL
IS~
/* Локальная переменная с тем же именем, что и стопбец */ salary NUMBER 尸 iflflo;
CURSOR double_sal cur
IS一 ~
SELECT salary + salary.
FROM employee
WHERE hiredate < ADD_M(WTWS (SYSMTE, -36):
BEGIN-
END”
Вместо того чтобы добавить к окладу $1000, этот код удвоит каждый оклад. В SQL-инструкции неуточненный идентификатор salary будет воспринят как обращение к столбцу таблицы enipl оуее.
Для получения нужных результатов нужно уточнить имя переменной PL/SQL именем процедуры;
CURSOR double sal cur
is --
SELECT salary + imprave_QOL.salary
FROM employee
WHERE hiredate < ADD_MONTHS (SYSDATE, -36);
В данном случае мы указываем компилятору, что второй идентификатор salary соответствует переменной процедуры improve_QOL. Поэтому SQL-инструкция сложит значение столбца со значением переменной.
Стандартные соглашения об именах
Еще одним распространенным способом минимизации конфликтов имен между SQL и PL/SQL является использование стандартизированного соглашения об именах. Предположим, что в некоторой компании принято прибавлять к именам всех локальных переменных префикс «1_»・ Тогда объявление переменной будет таким:
PROCEDURE improve_QOL
IS~
/* Локальная переменная с гем же именем, что и столбец */ l_salary NUMBER 尸 1000;
А курсор будет определен так:
CURSOR double_sal_cur
IS~ ~
SELECT salary + 1_salary
FROM employee
WHERE hiredate < ADD_MONTHS CSYSDATE. -36):
Код будет прекрасно работать, если только однажды администратору базы данных не придет в голову добавить в таблицу employee столбец с именем l_salary. Конечно, это маловероятно, но все же возможно.
Поэтому во избежание ошибок надежнее всего имена переменных PL/SQL в SQL-инструкциях всегда уточнять, не надеясь ни на какие соглашения.
Выбор между явным и неявным курсорами
Все последние годы знатоки Oracle (включая и авторов данной книги) убежденно доказывали, что для однострочной выборки данных никогда не следует использовать неявные курсоры, мотивируя это тем, что неявные курсоры, соответствуя стандарту ANSI, всегда выполняют две выборки, из-за чего они менее эффективны, чем явные курсоры.
Так утверждалось и в первых двух изданиях этой книги, но пришло время нарушить сложившуюся традицию. Начиная с Огас1е8 вьшолнение запросов в РСУБД было оптимизировано, и теперь неявный курсор выполняется даже эффективнее эквивалентного явного курсора.
Означает ли это, что теперь лучше всегда пользоваться неявными курсорами? Вовсе нет. По-прежнему имеются убедительные доводы в пользу применения явных курсоров.
О В некоторых случаях явные курсоры эффективнее неявных. Часто выполняемые критические запросы лучше всего протестировать в двух видах (с явным и неявным курсором), чтобы точно выяснить, как лучше выполнять каждый из них в каждом конкретном случае.
О Явными курсорами проще управлять из программы. Например, если строка не найдена, Oracle не инициирует исключение, а просто вызывает завершение исполняемого блока.
Поэтому мы предлагаем вместо формулировки 4явный или неявный??» поставить вопрос так: «инкапсулированный или открытый?» И ответ на него будет таким: всегда инкапсулируйте однострочные запросы, скрывая их за интерфейсом функции и возвращая результирующие данные с помощью предложения RETURN.
Так что не стоит больше тратить время на обдумывание выбора между явными и неявными курсорами. Задумайтесь лучше о том, как усовершенствовать программный код, в котором многократно и в разных местах выполняются одни и те же однострочные запросы. Инкапсулируйте эти запросы в функции, причем лучше всего в пакетные.
Работа с неявными курсорами
При каждом вьшолнении инструкции DML (INSERT, UPDATE или DELETE) или инструкции SELECT IbTTO, возвращающей одну строку из таблицы базы данных в структуру данных программы, PL/SQL автоматически создает для нее курсор. Курсор этого типа называется неявным, поскольку Oracle автоматически выполняет многие связанные с ним операции, такие как выделение курсора, его открытие, выборка строк и т. д.
ПРИМЕЧАНИЕ О DML-инсгрукциях, выполняемых с помощью неявных курсоров, рассказывается в главе 13. Данная глава посвящена только запросам.
Неявный курсор — это инструкция SELECT, обладающая следующими характеристиками.
О Эта инструкция определяется в исполняемом разделе блока, а не в разделе объявлений, как явные курсоры.
Q В инструкции содержится предложение INTO (или BULK COLLECT INTO), которое является частью языка PL/SQL, а не SQL, и представляет собой механизм пересылки данных из базы данных в локальные структуры данных PL/SQL.
Q Инструкцию SELECT не нужно открывать, выбирать из нее данные и закрывать; все эти операции осуществляются автоматически.
Общая структура запроса, который выполняется с помощью неявного курсора, имеет следующий вид:
SELECT список_столбцов
[BULK COLLECT] INTO список_переменных_РЬ/SQL'
■..оставшаяся часть инструкции SELECT...
Итак, если в программе используется неявный курсор, Oracle автоматически открывает его, выбирает из него строки и закрывает; над этими операциями программист не властен. Однако он может получить информацию о последней выполненной SQL-MHcipyKUHH, проанализировав значения атрибутов неявного кур- 8ра SQL, как рассказывается далее в этой главе.
ПРИМЕЧАНИЕ一
В следующих разделах, говоря о неявном курсоре, мы будем иметь в виду инструкцию SELECT INTO, извлекающую (или пытающуюся извлечь) одну строку данных. Ниже мы обсудим ее разновидность SELECT BULK COLLECT INTO, позволяющую с помощью одного неявного запроса получить несколько строк данных.
Примеры неявных курсоров
Неявные курсоры часто используются для поиска данных на основе значений первичного ключа. В следующем примере выполняется поиск названия книги по ее коду ISBN:
DECLARE
title books.tltleXTYPE:
BEGIN
SELECT title
INTO l_title'
FROM books
WHERE isbn - '0-596-00121-5';
END;
После того как название книги выбрано и присвоено значению локальной переменной l_t1tle, с последней можно работать, как с любой другой переменной: изменять ее значение, выводить на экран или передавать для обработки другой программе PL/SQL.
Вот пример неявного курсора, извлекающего из таблицы строку данных и помещающего ее в запись программы:
DECLARE
T_book booksXROWTYPE:
BEGIN
SELECT *'
INTO IJwok
FROM books
WHERE isbn к '0-596-00121-5';
END:
Из запроса можно получить информацию уровня группы. Например, следующий запрос вычисляет и возвращает сумму окладов по отделу. И снова PL/SQL создает для него неявный курсор;
SELECT SUM (salary)
INTO department_total
FROM employee'
WHERE department_number = 10:
Благодаря тесной интеграции PL/SQL с базой данных Oracle с помощью запросов из базы данных можно извлекать и сложные типы данных, такие как объекты и коллекции.
Однако для выборки из базы данных нескольких строк нужно либо использовать явный курсор, либо включить в запрос предложение BULK COLLECT INTO (поддерживаемое только Огас1е8: и более поздними версиями). Оба подхода обсуждаются далее в этой главе.
ПРИМЕЧАНИЕ--
Как уже упоминалось, однострочные запросы лучше всегда инкапсулировать в функции. Этот вопрос подробно обсуждался в разделе «Выбор между явным и неявным курсорами».
Обработка ошибок, связанных с неявными курсорами
Инструкция SELECT, выполняемая как неявный курсор, представляет собой счер- ный ящик». Она передается в базу данных и возвращает одну строку информации. Что происходит с курсором, как он открывается, выполняет выборку данных и закрывается, вы не знаете. Зато оказывается, что в двух случаях при выполнении инструкции SELECT Oracle инициирует исключения.
О По сформированному программистом запросу не найдено ни одной строки данных, соответствующей заданному критерию. В этом случае Oracle инициирует исключение NO_DATA_FOUND.
О Инструкция SELECT вернула несколько строк. Следовательно, Oracle инициирует исключение TOO_MANY_ROWS.
В каждом из этих случаев (так же как и в случае любого другого исключения, инициированного при выполнении SQL-инструкции), выполнение текущего блока прерывается и управление передается в раздел исключений. Этим процессом вы не управляете; у вас даже нет возможности сообщить Oracle, что данный запрос может не вернуть ни одной строки, и это не является ошибкой. Так что, если вы используете неявный курсор, в раздел исключений нужно обязательно включить код, перехватывающий и обрабатывающий эти два исключения (и, возможно, другие исключения, как того требует логика про^аммы).
В следующем блоке кода производится поиск названия книги по ее коду ISBN и обрабатываются возможные исключения:
DECLARE
IJsbn books. IsbnXTYPE :工'0-596-00121-5':
l_t1tle books.titleXTYPE:
BEGIN
SELECT title
INTO
FROM books■
WHERE isbn - l_isbn;
EXCEPTION -
WHEN NO DATA FOUND
THEN 一 一
DBMS_OUTPUT.PUT_LINE ('Неизвестная книга: ' || l_isbn):
WHEN TOO MANY ROWS"~
THEN ~ 一
errpkg.recNstop ('Нарушение целостности данных для: 1 || l_isbn);''
RAISE:~
ENO:
Работая с неявным курсором, программист может сделать некоторые небезопасные предположения об извлекаемых данных. Приведем пару примеров.
Q В таблице не может быть более одной книги с указанным ISBN — ведь это гарантируется заданным для таблицы ограничением.
О В таблице обязательно имеется строка с данными о содержащейся в ней книге.
Поэтому не стоит задумываться об исключении NO_DATA_FOUND.
Последствия таких предположений часто плачевны, поскольку, опираясь на них, программисты не включают в программы соответствующие обработчики исключений.
Конечно, в тот момент, когда вы работаете с таблицей и точно знаете, какие данные она содержит, ваш код может быть благополучно выполнен и запрос вернет в точности одну строку. Однако если данные изменятся, запрос может вернуть, к примеру, две строки вместо одной, и тогда программа сгенерирует исключение. В этом случае отсутствие обработчика исключения может поставить под удар все приложение.
Как правило, при использовании неявных курсоров лучше всегда включать в программу обработчики исключений NO_DATA_FOUND и TOO_MANY_ROWS. В обобщенной формулировке это правило звучит так: в программе должны присутствовать обработчики всех исключений, которые в ней могут произойти. А вот действия, выполняемые этими обработчиками, могут быть самыми разными. Возьмем все тот же код, извлекающий название книги по заданному коду ISBN. Ниже он реализован в виде функции с двумя обработчиками ошибок. Обработчик NO_DA- TA—FOUND возвращает значение, а обработчик TOO_MANY_ROWS записывает ошибку в журнал и повторно инициирует исключение, вызывая завершение работы функции. (О пакете errpkg подробно рассказывается в главе 6.)
CREATE OR REPLACE FUNCTION book_title ( IJsbn IN books.-isbnXHPE) RETURN books.titleXTYPE
IS
return_value books.titleXTYPE;'
BEGIN ~
SELECT title
INTO return_value
FROM books
WHERE Isbn = IJsbn;
RETURN return_value;
EXCEPTION ~
WHEN NQ OATA FOUND
THEN ~~'
RETURN NULL;
WHEN TOO MANY ROWS
THEN -
errpkg.recNstop ('Нарушение целостности данных для:',
11 IJsbn);
RAISE:
END;
Почему же эти два обработчика ведут себя так по-разному? Дело в том, что функция должна вернуть название книги, которое не может быть представлено значением NULL. Она использует для проверки ISBN (^существует ли книга с данным ISBN?»), и поэтому возможна ситуация когда, не найдя заданной книги, она не станет инициировать исключение. В этом факте нет ничего «плохого». Например,
логика программы может быть такой: «если книги с данным ISBN не существует, используем его для новой книги». Вот как она программируется:
IF book_titleC'0-596-D0121-7,) IS NULL
THEN
Иными словами; то, что запрос не выбрал ни одной строки, не всегда является свидетельством ошибки, и, в частности, функция не должна интерпретировать это как ошибку.
С другой стороны, если запрос сгенерировал исключение TOO_MANY_ROWS, для нашей программы это действительно оптибка, поскольку не может существовать двух книг с одинаковыми ISBN. Поэтому в данном случае информация об ошибке записывается в журнал и программа прекращает свою работу.
Атрибуты неявных курсоров
Oracle позволяет получить информацию о последнем запросе, выполненном с помощью неявного курсора, для чего программисту предоставляются четыре атрибута, описанных в табл. 14.2. Поскольку неявный курсор не имеет имени, для его обозначения используется ключевое слово SQL.
Таблица 14.2. Атрибуты неявных курсоров
ИмяЧто возвращает
SQL%FOUND Значение TRUE, если инструкция успешно выбрала одну строку
(или несколько строк, при условии, что выполнялся запрос с предложением BULK COLLECT INTO)
SQL%NOTFOUND Значение TRUE, если инструкция не выбрала ни одной строки (в этом случае Oracle также инициирует исключение NO_DATA_FOUND)
SQL%ROWCOUNT Количество выбранных строк
SQL%ISOPEN Значение FALSE, поскольку Oracle закрывает и открывает неявные курсоры автоматически
Эти атрибуты курсора возвращают информацию о выполнении инструкций INSERT, UPDATE, DELETE и SELECT IНТО. Если в текущем сеансе не выполнялся ни один запрос на основе неявного курсора, все атрибуты возвращают NULL. В противном случае значения атрибутов всегда относятся к последней SQL-инструкции независимо от того, из какого блока или программы она выполнялась.
Давайте обсудим следствия последнего утверждения. Рассмотрим следующие две программы:
CREATE OR REPLACE PROCEDURE remove_fro(n_ci rcul ati on
(isbnjn In books.isbnXTYPE)
IS 一
BEGIN
DELETE FROM books
WHERE isbn = isbnjn;
END;."
SEATE OR REPLACE PROCEDURE show book count
IS
l_count INTEGER;
BEGIN
SELECT COUNT (*)
INTO l_count
FROM books:
--Такой книги нет!'
remove_from_circulation t'O-OOD-DOOOO-O*):
DBMS_OUTPUT.PUT_LINE (SQLXROWCQUNT);
END:-
Независимо от количества строк данных в таблице books результат всегда будет равным 0. Поскольку после инструкции SELECT INTO вызывается процедура remove_from_cal cul ati on, атрибут SQLXROWCOUNT возвращает количество строк, удаленных выполняемой в этой процедуре инструкцией DELETE, а вовсе не количество строк, возвращенных запросом.
Если вам нужна гарантия того, что значения атрибутов относятся к заданной SQL-инструкции, сохраняйте эти атрибуты в локальных переменных сразу после ее выполнения — как в следующем примере:
CREATE OR REPLACE PROCEDURE show_bQok_count
IS一 一
l_count INTEGER:
l_foundsome INTEGER; ■
BEGIN
SELECT COUNT (*)
INTO l_count
FROM books:
--Делаем копию значения атрибута:
l_foundsome :- SQLXROWCOUNT:
Такой книги нет!
re(nove_froni_c1 rculation ('0-000-00000-0'):
--—Теперь можно вернуться к предыдущему значению атрибута
DBMS OUTPUT.put line (1 foundsome):
END:-一 一
Теперь рассмотрим особенности атрибутов неявных курсоров.
О SQLZFOUND. Этот атрибут позволяет узнать, вернул ли запрос строку. Если найдена хотя бы одна строка, атрибут SQLXFOUND возвращает TRUE; в противном случае он возвращает FALSE.
О SQLXNOTFOUND. Значение атрибута SQL^NOTFOUND прямо противоположно предыдущему: он возвращает TRUE, если не найдена ни одна строка (напомним, что в этом случае PL/SQL к тому же инициирует исключение NO_DATA_FOUND), и FALSE в противном случае.
О SQLXISOPEN. Для неявных курсоров данный атрибут не возвращает никаких полезных сведений. Его значением всегда является FALSE, поскольку Oracle автоматически закрывает неявный курсор еще до того, как вы обратитесь к атрибуту SQLZISOPEN.
О SQL^ROWCOUNT. С помощью SQLZROWCOUNT можно определить количество строк, возвращенных инструкцией SELECT INTO. Данный атрибут возвращает 1, если в инструкции нет предложения BULK COLLECT INTO, либо 0, если не найдена ни одна строка. В последнем случае PL/SQL инициирует исключение NO_DATA_FOUND.
ПРИМЕЧАНИЕ
Атрибут SQL%ROWCOUNT возвращает 1 даже в том случае, если в переменную помещается значе- ние( вычисленное на основе данных нескольких строк. Например, после выполнения инструкции:
SELECT COUNT(*) INTO l_count FROM dual WHERE 1=2;
атрибут SQL%ROWCOUNT будет установлен в 1, поскольку переменной Lcount присвоено значение 0.
Работа с явными курсорами
Явный курсор — это инструкция SELECT, явно определенная в разделе объявлений программы. При объявлении явного курсора ему присваивается имя. Для инструкций UPDATE, DELETE и INSERT явные курсоры создавать нельзя.
Определив инструкцию SELECT как явный курсор, программист получает контроль над основными стадиями выборки информации из базы данных. Он определяет, когда открыть курсор с помощью оператора OPEN, когда выбрать из него строки с помощью инструкции FETCH (то есть извлечь данные из таблицы или таблиц, заданных в инструкции SELECT), сколько выбрать строк и когда закрыть курсор с помощью оператора CLOSE. Информация о текущем состоянии курсора доступна через его атрибуты.
' Рассмотрим пример. Следующая функция определяет (и возвращает) степень зависти, испытываемой автором книги к своим друзьям в зависимости от их местожительства:
CREATE OR REPLACE FUNCTION jealousyjevel (
NAMEJN IN fnends.NAMEmPE) RETURN NUMBER
AS
CURSOR jealousy_cur
IS~
SELECT location FROM friends
WHERE NAME = UPPER (HAMEJN);
-
jealousy_rec jealousy_curXROWTYPE:
retval NUMBER; -
BEGIN
OPEN jealousy cur:
~
FETCH jealousy_cur INTO jealousy rec:
~~
IF Jealousy_curXFOUND
THEN ~
IF jеа1ousy_rес.1ocation » 'PUERTO RICO'
THEN retval := 10;
ELSIF jea1ousy_rec.1ocation = 'CHICAGO'
THEN retval .:- 1:
END IF:
END IF;
24
CLOSE emptyp_cur;
-
RETURN level_out:
END:~
Этот блок PL/SQL выполняет следующие операции с курсором:
Строки Действие
-7Объявление курсора
Объявление записи наосновекурсора
Открытие курсора
Выборка одной строкиизкурсора'
Проверка атрибута курсора с целью узнать, найдена ли строка
18-22 Анализ содержимого выбранной строки для вычисления степени зависти
Закрытие курсора
В следующих разделах подробно рассматривается каждая из перечисленных операций. Слово «курсор》в них относится к явному курсору, если только не указано обратное.
Объявление явного курсора
Для того чтобы получить возможность использовать явный курсор, его нужно объявить в разделе объявлений блока PL/SQL или пакета:
CURSOR имя^курсорд Цпдрдметр [.параметр ...])]
[RETURN спецификацииj'eturn']
IS HHCTpyKU^_SELECT
[FOR UPDATE [OF список_столбцов]];
Здесь wMujiypcopa — это имя объявляемого курсора; спецификация_return — необязательное предложение RETURN; инструкция_SELECT — любая допустимая SQL-инструкция SELECT. Курсору могут передаваться параметры, о чем рассказывается далее, в разделе «Параметры курсора». После объявления курсора его можно открыть с помощью оператора OPEN и выбрать из него строки с помощью инструкции FETCH.
Вот несколько примеров объявлений явных курсоров.
О Курсор без параметров. Результирующим набором строк этого курсора является набор идентификаторов компаний, выбранных из всех строк таблицы:
CURSOR company_cur
IS~
SELECT companyjd FROM company :
О Курсор с параметрами. Результирующий набор строк этого курсора содержит единственную строку с именем компании, соответствующим идентификатору компании, переданному курсору в качестве параметра:
CURSOR name__cur (company一idjn IN NUMBER)
IS 一
SELECT name FROM company
WHERE companyjd - companyjdjn;
О Курсор с предложением RETURN. Результирующий набор строк этого курсора содержит все данные таблицы employee для подразделения с идентификатором 10:
CURSOR emp cur RETURN efnplayeeXROWTYPE
is -
SELECT * FROM employee
WHERE department_1d = 10:
Имя курсора
Имя явного курсора должно иметь длину до 30 символов и соответствовать тем же правилам, которым соответствуют все остальные идентификаторы PL/SQL. Однако курсор не является переменной. Это идентификатор указателя на запрос. Имени курсора не присваивается значение и его нельзя применять в выражениях. Курсор используется только в операторах OPEN, CLOSE и инструкции FETCH, а также в качестве уточнителя для атрибутов курсора.
Объявление курсора в пакете
Явные курсоры объявляются в разделе объявлений блока PL/SQL. Можно объявить курсор на уровне пакета, но не в конкретной процедуре или функции пакета. О пакетах подробно рассказывается в главе 17. Перед чтением этого раздела просмотрите главу 17, чтобы составить общее представление о том, что такое пакеты и какова их структура.
Вот пример объявления двух курсоров в пакете:
CREATE OR REPLACE PACKAGE book info
IS~
CURSOR titles cur
IS-
SELECT title
FROM books:
CURSOR books_cur (t1tle_f11ter_1n IN books.tlflefTYPE)
RETURN booksXROWTYPE "
IS
SELECT *
FROM books
WHERE title LIKE title_filter_in;
END;~
Первый курсор, titles_cur, возвращает только названия книг. Второй, books_ cur, возвращает все строки таблицы books, в которых названия книг соответствуют шаблону, заданному в качестве параметра курсора (например,《Все книги, содержащие 'PL/SQL'>). Обратите внимание, что во втором курсоре используется
предложение RETURN, объявляющее структуру данных, возвращаемую инструкцией FETCH.
В предложении RETURN могут быть указаны:
О запись, объявленная на основе строки таблицы базы данных с помощью атрибута XROWTYPE;
О запись, объявленная на основе другого, ранее объявленного курсора, также с помощью атрибута XROWTYPE;
О запись, определенная программистом.
Количество выражений в списке выборки курсора должно соответствовать количеству столбцов записи, указанной в предложении RETURN в виде имя^абли - z/b/XROWTYPE, wccpERCMTYPE или тн〃_3d/7”с乩 Например, если второй элемент списка выборки имеет тип NUMBER, второй столбец записи в предложении RETURN не может иметь тип VARCHAR2 или BOOLEAN.
Прежде чем перейти к подробному рассмотрению предложения RETURN и его преимуществ, давайте сначала ответим на такой вопрос: для чего вообще может понадобиться объявлять курсоры в пакете? Почему не объявить явный курсор в той программе, в которой он используется — в процедуре, функции или анонимном блоке?
Ответ прост. Определив курсор в пакете, можно многократно использовать заданный в нем запрос, не повторяя один и тот же код снова и снова в разных местах приложения. Реализовав запрос в одном месте, его легко модифицировать в случае необходимости, что значительно облегчает разработку и сопровождение приложения. Кроме того, экономится время на синтаксическом анализе запросов.
1РИМЕЧАНИЕ-
Объявляя курсор в пакете для многократного использования, нужно иметь в виду след/ющий важный фактор. Структуры данных, определяемые на уровне пакета (а не внутри конкретной функции или процедуры), включая и курсоры, сохраняют свои значения в течение всего сеанса. Эго означает, что объявленный в пакете курсор остается открытым до тех пор, пока вы его явно не закроете, или до окончания сеанса. Курсоры, объявленные в локальных блоках кода, закрываются автоматически по завершении работы таковых.
Ну а теперь поговорим о предложении RETURN. Одной из интересных особенностей объявления курсора в пакете является возможность отделить заголовок курсора от его тела. Такой заголовок, больше напоминающий заголовок функции, содержит информацию, которая необходима программисту для работы: имя курсора, его параметры и тип возвращаемых данных. Телом курсора служит инструкция SELECT.
Вот новая версия объявления курсора books_cur в пакете book_info, иллюстрирующая эту технологию:
CREATE OR REPLACE PACKAGE bookjnfo
IS~
CURSOR books_cur (title filter_1n IN books.titleXTYPE)
RETURN booksXROWTYPE? —
END;
CREATE OR REPLACE PACKAGE BODY bookjnfo
IS
CURSOR books_cur Ctitle_filter_1n IN books.titleXTYPE)
RETURN books?RbwnPE~ "
IS
SELECT *
FROM books
WHERE title LIKE title_tnter_1n:
END:
Такое разделение объявления курсора может служить двум целям.
О Со>фытие информации. Курсор в пакете является «черным ящиком». Это удобно для программистов, поскольку им не нужно ни писать, ни даже видеть инструкцию SELECT. Достаточно только знать, какие записи возвращает этот курсор, в каком порядке и какие столбцы они содержат. Программист, работающий с пакетом, использует курсор в приложении как любой другой предопределенный элемент.
О Минимизация перекомпиляций. Если скрыть определение запроса в теле пакета, изменения в инструкцию SELECT можно вносить, не меняя заголовок курсора в спецификации пакета. Это позволяет совершенствовать, исправлять и повторно компилировать код без перекомпиляции спецификации пакета, благодаря чему зависящие от этого пакета программы не будут помечены как недействительные и их также не нужно будет перекомпилировать.
Открытие явного курсора
Объявив курсор в разделе объявлений, его нужно открыть. Синтаксис оператора OPEN очень прост:
OPEN имя_курсард [Аргумент [. аргумент ...])];
Здесь имя_курсора - это имя объявленного ранее курсора, а аргумент - это значение, передаваемое курсору, если он объявлен со списком параметров.
ПРИМЕЧАНИЕ
Oracle поддерживает еще одну разновидность синтаксиса открытия курсора:
OPEN имя_нурсора FOR
Он используется для курсорных переменных и встроенного динамического SQL (см. главу 15).
Открывая курсор, PL/SQL выполняет содержащийся в нем запрос. Кроме того, он идентифицирует активный набор данных — строки всех участвующих в запросе таблиц, соответствующих критерию предложения WHERE и условию объединения. Оператор OPEN не извлекает данные — это задача инструкции FETCH.
Независимо от того, когда будет выполнена первая выборка данных, реализованная в Oracle модель согласованности чтения гарантирует, что все операции выборки будут возвращать данные в том состоянии, в каком они находились иа момент открытия курсора. Иными словами, от открытия и до закрытия курсора при выборке из него данных полностью игнорируются выполняемые за это время операции вставки, обновления и удаления.
Более того, если в инструкции SELECT используется предложение FOR UPDATE, все идентифицируемые курсором строки блокируются при его открытии. (Об этом подробнее рассказывается далее в этой главе, в разделе «Инструкция SELECT с предложением FOR UPDATE».)
Если вы попытаетесь открыть уже открытый курсор, PL/SQL выдаст следующее сообщение об ошибке:
ORA-06511: PL/SQL: cursor al ready open
Поэтому перед открытием курсора следует проверить его состояние, обратившись к атрибуту ZISOPEN:
IF NOT company_cur?ISOPEN
THEN-
OPEN company_cur;
END IF; -
Атрибуты явных курсоров описываются ниже, в посвященном им разделе. Там рассказывается, как лучше всего использовать их в программах.
ПРИМЕЧАНИЕ
Если в программе выполняется цикл FOR, использующий курсор, этот курсор не нуждается в явном открытии.
Выборка данных из явного курсора
Инструкция SELECT создает виртуальную таблицу 一 набор строк, определяемых предложением WHERE со столбцами, заданными в предложении SELECT. Курсор представляет эту таблицу в программе PL/SQL. Основным назначением курсоров в программах PL/SQL является выборка строк для их обработки. Выборка строк курсора выполняется с помощью инструкции FETCH:
FETCH имя^урсорз【NTD запись_или^писок_переменных:
Здесь имя_курсора - это имя курсора, из которого выбирается строка, а ззпись_или_ список_перененных — это структура или структуры данных PL/SQL, в которые копируется следующая строка активного набора записей. Данные могут помещаться в запись PL/SQL (объявленную с помощью атрибута NROWTYPE или оператора TYPE) или в переменные (переменные PL/SQL или переменные привязки, как например, в элементы Oracle Forms).
Примеры явных курсоров
Следующие примеры демонстрируют разные способы выборки данных.
О Выборка данных из курсора в запись PL/SQL
DECLARE
CURSOR company_cur is SELECT ...:
company rec company curXROWTYPE;
BEGIN "-
OPEN compdny_cur;
FETCH company_cur INTO company_rec;
Q Выборка данных из курсора в переменную:
FETCH new balance сиг INTO new balance dollars;
О Выборка данных из курсора в строку таблицы PL/SQL, переменную и переменную привязки Oracle Forms:
FETCH етр_пап)е,сиг INTO emp_name [1], hiredate. :dept.min_salary:
ПРИМЕЧАНИЕ-
Лучше всего помещать выбираемые из курсора данные в запись, объявленную на основе того же курсора с атрибутом %ROWTYPE; избегайте извлечения значений в переменные. Первый способ делает программный код более лаконичным и гибким, позволяет изменить список выборки, не меняя инструкцию FETCH.
Выборка после обработки последней строки
Открыв курсор, вы по очереди выбираете из него строки, пока все они не будут исчерпаны. Однако и после этого можно выполнять инструкцию FETCH.
Как ни странно, в этом случае PL/SQL не инициирует исключение. Он просто ничего не делает. Поскольку выбрать больше нечего, значения переменных в предложении INTO инструкции FETCH не меняются. Иными словами, инструкция FETCH не устанавливает значения этих переменных равными NULL.
Поэтому, если вам понадобится узнать, успешно ли прошла выборка очередной строки, проверка переменных из списка INTO ничего не даст. Следует проверить атрибут XFOUND или ZNOTFOUND (о них рассказывается ниже, в разделе, посвященном атрибутам явных курсоров).■
Псевдонимы столбцов явного курсора
Инструкция SELECT в объявлении курсора определяет список возвращаемых им столбцов. Наряду с именами столбцов таблиц этот список может содержать выражения, называемые вычисляемыми, или виртуальными, столбцами.
Псевдоним столбца — это альтернативное имя, указанное в инструкции SELECT для столбца или выражения. Задав подходящие псевдонимы в SQL*Plus, можно вывести результаты произвольного запроса в виде читабельного отчета. В этом случае псевдонимы не являются обязательными. Для явного курсора псевдонимы вычисляемых столбцов необходимы в следующих случаях:
Q когда данные выбираются из курсора в зались, объявленную с атрибутом ZROW- TYPE на основе того же курсора;
Q когда в программе имеется ссылка на вычисляемый столбец.
Рассмотрим следующий запрос. Инструкция SELECT выбирает имена всех компаний, заказывавших товары в течение 2001 года, а также общую сумму заказов (предполагается, что для текущего экземпляра базы данных по умолчанию используется маска форматирования DD-MON-YYYY):
SELECT company_name, SUM (1nv_amt)
FROM company C. invoice I
WHERE C.conpany_ld = I.companyjd
AND [.invoice^date BETWEEN '01-JAN-2001' AND '31-DEC-2001';
Если выполнить эту инструкцию в SQL*Plus, результаты будут примерно такими:
COMPANY_NAMESUM (INV_AMT)
ACME TURBO INC.1000
WASHINGTON HAIR CO.25.20
Как видите, заголовок столбца SUM (INV_AMT) не слишком подходит для отчета, но для того чтобы просто просмотреть данные, он вполне годится. Теперь выполним тот же запрос в программе PL/SQL с помощью явного курсора и добавим псевдоним столбца:
DECLARE
CURSOR conp_cur IS
SELECT conpany_name, SUM (inv_amt) total_sales
FROM company C, invoice I
WHERE C.companyjd = I.companyjd
AND I.invoice二date BETWEEN '01-JAN-2001' AND '31-DEC-2001';
comp_rec comp_curXROWTYPE:
BEGIN -~
OPEN comp_cur;
FETCH conp_cur INTO comp_rec;
END:
Теперь с вычисляемым столбцом можно работать точно так же, как с любым другим столбцом запроса:
IF сошр_гес.total_sales > 5000
THEN - 一•
DBMS__OUTPUT.PUT_LINE
С Вы превысили лимит кредита $5000 на' 11
TO_CHAR (5000-comp_rec.total_sales, '$9999')):
ENO IF?~~
Если поместить crpoity в запись, объявленную с атрибутом XROWTYPE, доступ к вычисляемому столбцу можно будет получить только по имени - ведь структура записи формируется на основе структуры курсора.
Закрытие явного курсора
Когда-то в детстве нас учили прибирать за собой, и эта привычка осталась у нас (надеемся) на всю жизнь. Оказалось, что исключительно важную роль это правило играет в программировании, и особенно когда дело доходит до управления курсорами. Никогда не забывайте закрыть курсор, если он вам больше не нужен!
Вот синтаксис оператора CLOSE:
CLOSE Ш_курсорд:
Ниже приводится несколько важных советов и соображений, связанных с закрытием явных курсоров.
О Если курсор объявлен и открыт в процедуре, не забудьте его закрыть. Иначе считайте, что вы запрограммировали утечку памяти. Строго говоря, курсор (как и любая другая структура данных) должен автоматически закрываться
и уничтожаться, как только выполнение приложение выходит за пределы его области действия. И, как правило, при выходе из процедуры, функции или анонимного блока PL/SQL действительно закрывает все открытые в нем курсоры. Однако этот процесс связан с определенными издержками, и поэтому иногда PL/SQL выявляет и закрывает открытые курсоры не сразу. Кроме того, курсоры типа REF CURSOR по определению не могут быть закрыты неявно. Единственное, в чем вы можете быть уверены, так это в том, что по завершении работы «самого внешнего* блока PL/SQL, когда управление будет возвращено SQL или другой вызывающей программе, PL/SQL неявно закроет все открытые этим блоком или вложенными блоками курсоры, кроме REF CURSOR.
ПРИМЕЧАНИЕ
Вложенные анонимные блоки могут служить примером того, как PL/SQL (по крайней мере, в Oracle9l Release 1) не выполняет явного закрытия курсора. Интересное обсуждение этой темы вы найдете в статье Джонатана Генника «Does PUSQL Implicitly Close Cursors» по адресу http://gennjck.com/ open_cursors.html.
О Если курсор объявлен в пакете на уровне пакета и открыт в некотором блоке или программе, он останется открытым до тех пор, пока вы его явно не закроете, или до завершения сеанса. Поэтому, окончив работу с таким курсором, его следует сразу же закрыть с помощью оператора CLOSE, как в следующем примере:
BEGIN
OPEN my_package.my_cursor;
...Работаем с курсором ...
CLOSE myjjackage.niy_cursor;
EXCEPTION"
WHEN OTHERS
THEN
CLOSE my_package.my_cursor;
END;~
О Очень важно как можно раньше закрывать курсоры с запросами SELECT FOR UPDATE, поскольку для этих запросов выполняется блокировка на уровне строк.
Q Курсор следует закрывать только в том случае, если он открыт. Состояние курсора проверяется с помощью атрибута ZISOPEN:
IF company_curXI50PEN
THEN ~
CLOSE company_сиг:
END IF;~
Q Если оставить открытыми слишком много курсоров, можно превысить значение параметра базы данных OPEN_CURSORS. В этом случае появится сообщение об ошибке:
0RA-01000: maximum open cursors exceeded
Получив такое сообщение, прежде всего проверьте, как используются курсоры, объявленные в пакетах.
Атрибуты явных курсоров
Oracle поддерживает четыре атрибута (XFOUND, XNDTFOUND,纟ROWCOUNT, ZISOPEN), возвращающих информацию о состоянии явного курсора. Ссылка на атрибут имеет следующий синтаксис:
КурСОрХдТрИбуГ
Здесь курсор — это имя объявленного курсора.
Значения, возвращаемые атрибутами явных курсоров, приведены в табл. 14.3.
Таблица 14.3. Атрибуты явных курсоров
Имя
Что возвращают
Kypcop%FOUND
Значение TRUE, если строка выбрана успешно
Kypcop%NOTFOUND
Значение TRUE, если не была выбрана ни одна строка
Kypcop%ROWCOUNT
Количество строк, выбранных из указанного курсора на данный момент времени
Kypcop%ISOPEN
Значение TRUE, если заданный курсор открыт
Значения атрибутов курсоров до и после выполнения различных операций с ними указаны в табл. 14.4.
Таблица 144 Возможные значения атрибутов курсоров
%FOUND
%NOTFOUND
% ISOPEN
%R0WC0UNT
До OPEN
Исключение
Исключение
FALSE
Исключение
0RA-O1OO1
0RA-O1OO1
0RA-O1OO1
После OPEN
NULL
NULL
TRUE
0
До первой FETCH
NULL
NULL
TRUE
0
После первой FETCH
TRUE
FALSE
TRUE
1
Перед последующей FETCH
TRUE
FALSE
TRUE
I
После последующих FETCH
TRUE
FALSE
TRUE
Зависит от данных
Перед последней FETCH
TRUE
FALSE
TRUE
Зависит от данных
После последней FETCH
FALSE
TRUE
TRUE
Зависит от данных
Перед CLOSE
FALSE
TRUE
TRUE
Зависит от данных
После CLOSE
Исключение
Исключение
FALSE
Исключение
Работая с атрибутами явных курсоров, имейте в виду следующее.
О При попытке обратиться к атрибуту ZFOUND, ZNOTFOUND или ZROWCOUNT до открытия курсора или после его закрытия Oracle инициирует исключение INVA- LID_CURSOR (0RA-01001).
О Если после первого выполнения инструкции FETCH результирующий набор строк оказывается пустым, атрибуты курсора возвращают следующие значения: 奸OUND - FALSE, XNOTFOUND = TRUE и XROWCOUNT = D.
О Если используется предложение BULK COLLECT, атрибут ^ROWCOUNT может вернуть значение, отличное от 0 или 1 — количество строк, извлеченных в заданные коллекции. Подробнее об этом рассказывается в разделе «Предложение BULK COLLECT».
Использование всех этих атрибутов демонстрирует следующий пример:
CREATE OR REPLACE PACKAGE bookinfo_pkg
IS
CURSOR bard_cur
IS SELECT title. date_published
FROM books
WHERE UPPER(author) LIKE 'SHAKESPEAREX';
rec bard_curXrowtype;
END bookinfo_pkg:
DECLARE
bard rec bookinfo pkg.bard curXROWTYPE;
BEGIN -'
Проверяем, не открыт ли уже курсор.
--Это возможно, поскольку курсор определен в пакете.
--Поэтому закрываем его и открываем повторно, чтобы
-получить "свежий" результирующий набор
IF booki nfo_pkg.bard_curXISOPEN
THEN 一 ~
CLOSE booki nfo_pkg.bard_cur:
END IF;~-
OPEN booki nfo_pkg.bard_cur:
-По очереди выбираем строки, но останавливаемся
-после вывода первых пяти произведений Шекспира
-или когда будут исчерпаны все строки
LOOP
FETCH bookinfo_pkg.bard_cur INTO book!nfo_pkg.rec:
EXIT WHEN bookinfo_pkg.bard_cur*NOTFOUND ~
OR book1nfo_pkg.bard_cur*ROWCOUNT 工 6;
DBMS__0UTPUT.putJ1ne (~
bookinfo_pkg.bard_cur*ROWCOUNT
ID'""
11 bookinfo_pkg.rec.title
II'. издана в '
11 TO_CHAR Cbook1nfo_pkg.rec,date_published. 'YYYY')
ENO LOOP:
CLOSE bookinfo_pkg.bard_cur;
END:一 一
Параметры курсора
Вы, конечно, уже знакомы с понятием параметров процедур и функций. Параметры -это средство передачи информации в программный модуль и из него. При правильном использовании они делают модули более полезными и гибкими.
PL/SQL позволяет передавать параметры курсорам. Они выполняют те же функции, что и параметры программных модулей, а также несколько дополнительных.
О Расширение возможности многократного использования курсоров. Вместо того чтобы жестко кодировать в предложении WHERE значения, определяющие условие отбора данных, можно использовать параметры для передачи в это предложение нового набора значений при каждом открытии курсора.
О Решение проблем, связанных с областью действия курсоров. Если вместо жестко закодированных значений в запросе используются параметры, результирующий набор строк курсора не привязан к конкретной переменной программы или блока. Если в программе имеются вложенные блоки, курсор можно определить на верхнем уровне к использовать его во вложенных блоках с объявленными в них переменными.
Количество параметров курсора не ограничено. В операторе OPEN для каждого параметра должно быть задано значение, за исключением параметров, для которых определены значения по умолчанию.
В каких случаях курсору требуются параметры? Общее правило здесь то же, что и для процедур и функций: если предполагается, что курсор будет использоваться в разных местах и с разными значениями в предложении WHERE, значит, ему нужны параметры.
Давайте обсудим различия между курсорами с параметрами и курсорами без параметров. Вот пример курсора без параметров:
CURSOR joke_cur IS
SELECT name, category, last_used_date
FROM joke:~~
Он возвращает все строки таблицы joke. Если же нам нужны только строки, соответствующие категории HUSBAND, в запрос следует добавить предложение WHERE: CURSOR joke_cur IS
SELECT name, category. last_used_date
FROM joke一 -'
WHERE category = 'HUSBAND';
Для выполнения этой задачи мы не стали использовать параметры, да они и не нужны. В данном случае курсор возвращает все строки, относящиеся к конкретной категории. Ну а как быть, если при каждом обращении к этому курсору категория изменяется?
Курсоры с параметрами
Конечно же, мы не станем определять отдельный курсор для каждой из категорий —это совершенно не согласуется с принципом разработки приложений, управляемых данными. Нам нужен один-единственный курсор, но такой, для которого можно было бы менять категорию, но он все равно возвращал бы требуемую информацию. И лучшим (хотя и не единственным) способом решения подобной задачи является создание курсора с параметрами:
DECLARE
/*
11 Курсор со спискам параметров, состоящим из единственного строкового параметра
*/
CURSOR joke_cur (category in VARCHAR2)
IS ~~
SELECT name, category. last_used_date
FROM joke
WHERE category - UPPER (categoryjn):
joke_rec joke__curXROWTYPE;
BEGIN
/* Теперь, открывая курсор, мы передаем ему аргунент */
OPEN joke_cur (:joke.category):
FETCH joke_cur INTO Joke_rec:
Между именем курсора и ключевым словом IS теперь содержится список параметров. Жестко закодированное значение HUSBAND удалено из предложения WHERE, а вместо него появилась ссылка на параметр - UPPER(categoryJn). При открытии курсора можно будет задавать значения HUSBAND, husband или HuSbAnD, но он все равно будет работать. Название категории, для которой наш курсор должен вернуть строки таблицы joke, задается в операторе OPEN (в скобках) в виде литерала, константы или выражения. При открытии курсора выполняется его синтаксический анализ и связывание со значением параметра categoryjn. Затем идентифицируется результирующий набор строк — и курсор готов к выборке.
Открытие курсора с параметрами
Итак, новый курсор joke_cur можно открывать с указанием любой категории:
OPEN joke_cur С:jokes_pkg.category):
OPEN joke_cur ('husband'):
OPEN joke_cur ('politician');
OPEN joke_cur (:jokes_pkg.relation || 'IN-LAW');
Параметры курсора чаще всего используются в предложении WHERE, но ссылаться на них можно и в других местах инструкции SELECT, как показано в следующем примере:
DECLARE
CURSOR joke_cur (categoryjn VARCHAR2)
IS-
SELECT name, categoryjn, last_used_date
FROM joke
WHERE category - UPPER (categoryjn);
Вместо того чтобы считывать категорию из таблицы, мы поместили параметр categoryjn в список выборки, благодаря чему будет возвращено значение этого параметра.
Область действия параметров курсора
Область действия параметров курсора охраничивается самим курсором. Вне инструкции SELECT, ассоциированной с курсором, на ннх ссылаться нельзя. Например, следующий фрагмент кода PL/SQL не будет откомпилирован, поскольку идентификатор program_nanie не является локальной переменной прохраммы. Это формальный параметр, определенный только внутри курсора:
DECLARE
CURSOR scariness^cur (program_name VARCHAR2)
IS
SELECT SUM (scaryjevel) total_scaryjevel
FROM taies_from_the_crypt
WHERE prog_name - program_name:
BEGIN~一
program_name:-'THEBREATHING MUW:/* недопустимо */
OPEN scariness_cur (program_naine);
END;~
Режимы параметров курсора
Синтаксис параметров курсора очень похож на синтаксис процедур и функций с той разницей, что параметры курсоров могут быть только входными — спецификации OUT и IN OUT для них не допускаются. Информацию, извлекаемую из базы данных с помощью курсора, можно передать программе PL/SQL только путем выборки значений из курсора в переменные, записи или коллекции, заданные в предложении INTO инструкции FETCH. (О режимах использования параметров подробно рассказывается в главе 16.)
Значения параметров по умолчанию
Параметрам курсора могут присваиваться значения по умолчанию. Вот пример курсора со значением параметра по умолчанию:
CURSOR emp_cur (emp_1d_1n NUMBER :- 0)
IS ~~ ~
SELECT employeejd, e(np_name
FROM employee
WHERE employeejd - emp_id_1n:
Если сотрудника с идентификатором 1001 зовут Joe Smith, следующий код присвоит переменной fny_emp_1 d значение 1001, а переменной ту_етр_пате — значение JOE SMITH:- 一~ ~
OPEN enip_cur (МП):
FETCH emp_cur INTO my_emp_id. щу_етр_пате:
Поскольку для параметра emp_id_in определено значение по умолчанию, в инструкции FETCH его значение можно не задавать. Курсор же вернет информацию □ сотруднике с кодом 0, так как именно 0 в данном случае является значением по умолчанию.
Предложение BULK COLLECT
В Oracle8i был введен новый структурный компонент, значительно повысивший производительность запросов в PL/SQL, — предложение BULK COLLECT. С его помощью можно за одно обращение к базе данных извлечь из явного или неявного курсора несколько строк данных. Выборка данных с помощью запроса с предложением BULK COLLECT сокращает количество переключений контекста между PL/SQL и SQL, благодаря чему работа выполняется быстрее и с меньшими издержками.
Рассмотрим следующий фрагмент кода. Нам нужно прочитать сотни строк данных об автомобилях, загрязняющих окружающую среду. Эти данные следует
поместить в набор коллекций, благодаря чему проводить их дальнейший анализ н обработку станет намного удобнее.
DECLARE
CURSOR major_polluters_cur
IS
SELECT name, mileage
FROM transportation
WHERE TYPE 匸'AUTOMOBILE'
AND mileage < 20:
names name_varray:
mileages number_varray;
BEGIN-
FOR bad_car IN major_polluters
LOOP 一~
names.EXTEND;
names (major_ponutersXROWCOUNT) := bad_C3r.NAME;
mil eages.EXTEND:
mileages (rnajor_pol 1 utersXROWCOUNT) :- bad_car.mileage;
END LOOP;~
Далее можно работать с данными в коллекциях END;
Этот код выполняет поставленную задачу, но далеко не самым эффективным способом. Если, к примеру, в таблице transportation содержится 2000 строк, PL/SQL придется произвести 2000 операций выборки данных из глобальной системной области.
Значительному ускорению работы может благоприятствовать предложение BULK COLLECT. Включив его в курсор (явный или неявный), вы указываете ядру на необходимость SQL перед передачей управления PL/SQL выполнить пакетное связывание выходных данных из множества строк с заданными в запросе коллекциями. Синтаксис этого предложения таков:
... BULK COLLECT INTO имя_коллекции[. ийя_коллеяции]...
Здесь параметр имя_колленции идентифицирует коллекцию, в которую должны быть помещены выходные данные курсора.
Правда, существует несколько правил и ограничений, связанных с использованием предложения BULK COLLECT.
О Ранее предложение BULK COLLECT можно было использовать только в статических SQL-инструкциях. Начиная с Oracle9i оно применяется и в динамическом SQL. Более подробные сведения по этому вопросу и примеры приведены в главе 15.
Q Ключевые слова BULK COLLECT можно использовать только в предложениях SELECT INTO, FETCH INTO и RETURN INTO.
Q В коллекциях, заданных в предложении BULK COLLECT, могут храниться только скалярные значения (строки, числа, даты). Иными словами, строки данных нельзя поместить в записи, являющиеся элементами коллекции. (В Oracle9i Release 2 такая возможность появилась.)
Q Ядро SQL автоматически инициализирует и расширяет коллекции, заданные в предложении BULK COLLECT. Их заполнение начинается с индекса 1, и далее
элементы добавляются последовательно (без пропусков), заменяя определенные ранее элементы.
О Инструкцию SELECT...BULK COLLECT нельзя использовать в операторе FORALL.
Рассмотрим эти правила на примерах. Вот новая версия предыдущего примера, усовершенствованная с помощью предложения BULK COLLECT:
DECLARE
names name_varray:
mileages number_varray:
BEGIN~
SELECT name, mileage
FROM transportation
BULK COLLECT INTO names, mileages
WHERE TYPE - 'AUTOMOBILE'
AND mileage < 20:
--Далее можно работать с данными в коллекциях
END:
Мы не только добавили предложение BULK COLLECT, но и удалили из программы код, выполняющий инициализацию и расширение коллекций. А теперь перепишем этот пример еще раз с использованием явного курсора:
DECLARE■
CURSOR major_polluters IS
SELECT name, mileage.
FROM transportation
WHERE TYPE - 'AUTOMOBILE'
AND mileage <20;
names name_varray;
mileages number_varray;
BEGIN一
OPEN major_polluters;
FETCH major_polluters BULK COLLECT INTO names, mileages; .
--Далее можно работать с данными в коллекциях■
END;
Ограничение на количество строк, возвращаемых инструкцией с предложением BULK COLLECT
Для предложения BULK COLLECT Oracle поддерживает сопутствующее предложение LIMIT, ограничивающее количество строк, выбираемых из базы данных. Его синтаксис таков;
FETCH ... BULK COLLECT INTO [LIMIT строки^-.
Здесь строки — это литерал, переменная или выражение, возвращающее значение типа NUMBER (иначе Oracle инициирует исключение VALUE_ERROR).
Теперь рассмотрим такой пример. Предположим, что таблица transportation очень велика, а мы хотим получать за одну выборку не более 5000 строк. В этом случае в инструкцию FETCH можно добавить предложение LIMIT:
DECLARE■
CURSOR niajor_po11uters IS
SELECT name
FROM transportation■
WHERE TYPE = 'AIROMOBILE' AND mileage v 20;
names name^varray:
BEGIN ~
OPEN major_polluters:
FETCH major^polluters
BULK COLLECT INTO names
LIMIT 5000:
END:
Выборка более одного столбца
Как показывают приведенные выше примеры, предложение SULK COLLECT позволяет выбрать из курсора данные нескольких столбцов и поместить их в несколько отдельных коллекций. Без сомнения, более целесообразным решением было бы извлечение набора строк в одну коллекцию записей. Такая возможность появилась в Oracle9z Release 2.
Предположим, что нам нужно считать из таблицы transportation информацию обо всех автомобилях с расходом топлива менее галлона на 20 миль. В Oracle9t Release 2 и последующих версиях РСУБД эту задачу можно выполнить самым оптимальным образом:
DECLARE
--Объявляем коллекции на основе заданного типа.
TYPE Vehiab IS TA8LE OF trans portat i onXROKTYPE;
gasjuzzlers VehTab;
BEGIN -
SELECT *
8ULK COLLECT INTO gas_guzzlers
FROM transportation
WHERE mileage < 20;
До Oracle9i Release 2 при выполнении этого кода было бы инициировано следующее исключение:
PLS-00597: expression 'GAS_GUZZLERS' in the INTO list is of wrong type
В Oracle8i и Oracle9z Release 1 нам пришлось бы объявить несколько коллекций и поместить в них отдельные столбцы:
declare
guzzler_type name_varray;
guzzler_nanie name_varray;
guzzler_mileage number_varray;
CURSOR 1 owjnl 1 eage_cur IS
SELECT vehlcle_type. name, ml 1 eage
FROM transportation WHERE mileage < 10:
BEGIN
OPEN lowjn1leage_cur:
FETCH low_m-ileage_cur BULK COLLECT
INTO guzzler_type. guzzler_name, guzzler mileage;
Не забывайте, что при извлечении данных из курсора в коллекцию записей также можно использовать предложение LIMIT, ограничивающее количество выбираемых строк.
Совместное использование предложений RETURNING и BULK COLLECT
Вы уже знаете, как использовать предложение BULK COLLECT с явными и неявными курсорами. Но его можно использовать и в операторе FORALL совместно с предложением RETURNING.
Предложение RETURNING, введенное в Огас1е8, позволяет получить информацию из DML-инструкции (например, обновленное значение оклада), помогая обойтись без дополнительных запросов к базе данных.
Предположим, что в Конгрессе принят закон)требующий, чтобы оклад наиболее высокооплачиваемых сотрудников компании не превышал оклад ее низкооплачиваемых сотрудников более чем в 50 раз. Мы работаем в ИТ -подразделении только что созданной компании Northrop-Ford-Mattel-Yahoo_ATT, общее число сотрудников которой составляет 250 000 человек. Поскольку руководство компа- .нии не намерено уменьшать доходы генерального директора, было решено повысить оклады сотрудников, зарабатывающих менее одной пятидесятой части его общего дохода за 2004 год, составляющего 145 миллионов долларов, и урезать оклады всего высшего руководства — должен же кто-то компенсировать такие огромные расходы.
Похоже^ придется потрудиться, чтобы пересчитать и обновить такое количество данных, а для ускорения процесса можно воспользоваться оператором FORALL, После выполнения всех расчетов и внесения изменений нам нужно будет вывести отчет со старыми и новыми окладами сотрудников. Сформировать его нам поможет предложение RETURNING.
Для начала напишем функцию, которая должна возвращать информацию о заработной плате генерального директора:
Файл в web: onlyfalr.sql
CREATE OR REPLACE FUNCTION salforexec (titlejn IN VARCHAR2)
RETURN NUMBER~
IS
CURSOR ceo_compensation
IS ~
SELECT
salary + bonus + stock_options +
me rcedes_benz_a11owa n ce + yacht_allowance.
FROM compensation
WHERE title - titlejn:
big_bucks NUMBER;
BEGIN
OPEN ceo_compensation;
FETCH ceo^compensation INTO big_bucks;
RETURN b1g_bucks:~
END; -
В главном блоке программы обновления мы объявим ряд локальных переменных и запрос для определения низкооплачиваемых и высокооплачиваемых сотрудников, чью зарплату придется пересмотреть:
DECLARE
big_bucks NUMBER :- salforexec (КЕТ);
min_sdl NUMBER :- big_bucks / 50;
names narne_tab:
old_salaries number_tab;
new_salaries number_tab;
CURSOR a ffected_ernpl oyees (ceosal IN NUMBER)
IS"
SELECT name, salary + bonus old_salary
FROM coirpensation
WHERE title !- 'CEO'
AND ((salary + bonus < ceosal / 50)
OR (salary + bonus > ceosal / 10));
В начале исполняемого раздела определяемые этим запросом данные помещаются в две коллекции с помощью инструкции FETCH, заданной с предложением BULK COLLECT:
BEGIN'
OPEN affected^employees (big_bucks):
FETCH affected^employees■
BULK COLLECT INTO names. old_salaries;
CLOSE affected_employees:
Затем выполняется оператор FORALL для последовательного перебора элементов коллекции names:
FORALL indx IN names.FIRST .. names.LAST
UPDATE compensation.
SET salary -
DECODE (
GREATEST (nin_sal. salary), min_sal, min_sal,
sdlary / 5)
WHERE name 口 names (indx)
RETURNING salary BULK COLLECT INTO new_salaries;
Инструкция UPDATE должна либо увеличивать оклад сотрудника на 80 %, либо уменьшать его. В конце используется предложение RETURNING, с помощью которого мы помещаем новые оклады в коллекцию new_salaries.
Вот и все. Благодаря предложению RETURNING нам не придется выполнять еще один запрос, выбирающий из базы данных новые оклады сотрудников, и можно сразу приступать к формированию отчета:
FOR indx IN names.FIRSTnames.LAST■
LOOP
DBMS_OUTPUT.PUT_LINE (
RPAD (namesdndx), 20) 11
RPAD ('Old: ' 11 old_salaries(1ndx), 15) ||'
'New: 1 11 new_salar1esC1ndx)
END LOOP:
END:
А вот каким будет отчет, сгенерированный сценарием, который содержится
в файле onlyfair.sql:
John DayAndNight Holly Cubicle Sandra WatchthebucksJohn DayAndNight Holly Cubicle Sandra WatchthebucksOld: 10500Old: 520GQOld: 22000000Old: 10500Old: 520GQOld: 22000000New: 2900000New: 2900000New: 4000000New: 2900000New: 2900000New: 4000000
John DayAndNight Holly Cubicle Sandra Watchthebucks
John DayAndNight Holly Cubicle Sandra Watchthebucks
Old: 10500
Old: 520GQ
Old: 22000000
Old: 10500
Old: 520GQ
Old: 22000000
New: 2900000
New: 2900000
New: 4000000
New: 2900000
New: 2900000
New: 4000000
Теперь каждый сотрудник нашей компании сможет позволить себе хорошее жилье и качественное медицинское обслуживание. И при этом все будут платить большие суммы налогов, которые пойдут, в частности, на содержание государственных школ и больниц.
ПРИМЕЧАНИЕ
Значения столбцов и выражения, возвращаемые предложением RETURNING оператора FORALL, добавляются в коллекции после ранее записанных туда значений. Если же предложение RETURNING используется в обычном цикле FOR, предыдущие значения заменяются данными, возвращенными последней DML-инсгрукцией.
Инструкция SELECT с предложением
FOR UPDATE
При выполнении инструкции SELECT для выборки строк из базы данных эти строки не блокируются. Заблокированными являются только те строки, которые уже модифицированы, но еще не зафиксированы приложениями. Но даже эти строки доступны для чтения в том состоянии, в котором они находились до внесения изменений. Так что Oracle обеспечивает максимальную свободу доступа к данным таблиц, блокируя минимальное количество строк на самое короткое время и в самом гибком режиме.
Однако иногда возникает необходимость заблокировать набор строк еще до того, как вы приступите к их изменению. Это можно сделать в инструкции SELECT с помощью предложения FOR UPDATE.
При выполнении инструкции SELECT...FOR UPDATE Oracle автоматически блокирует заданные в ней строки для монопольного доступа. С этого момента никто другой не может изменять эти строки до тех пор, пока вы не выполните инструкцию ROLLBACK или СО懵П, хотя другие сеансы по-прежнему могут читать из них данные.
Ниже приведены два примера, демонстрирующие принцип использования предложения FOR UPDATE в курсорах:
CURSOR toys_cur IS
SELECT name, manufacturer, preferencejevel, seil_at_yardsale_flag
FROM my_sons_collection
WHERE hours used = 0
FOR UPDATE:
CURSOR fall_job5_cur IS■
SELECT task, expected_hours, tools_required. dojt_/ourself_flag
FROM winterize
WHERE year - TO_CHAR (SYSDATE. 'YYYY')
FOR UPDATE 0F~task;
В первом курсоре используется предложение без параметров, а во втором — предложение с заданным в нем именем столбца.
Предложение FOR UPDATE может применяться в инструкциях SELECT, выбирающих данные из нескольких таблиц. Если при этом в указанном предложении имеется список OF, Oracle блокирует строки только тех таблиц, столбцы которых указаны в этом списке. В следующем примере не блокируется ни одна строка таблицы winterize. Вы можете ответить, почему?
CURSOR fall Jobs^cur
IS
SELECT w.task. w,expected_hours.
w.tools_required.
w.dojtqoursel JfUg
FROM winterize w. husband_conf1g he■
WHERE YEAR - T0_CHAR C5YS0ATE, 'YYYY')
AND w.taskjd = hc.taskjd
■ FOR UPDATE OF husband_config.max_procrastinat1on_allowed:
Дело в том, что в списке OF предложения FOR UPDATE задан только столбец тах_ procrastination_al 1 owed; ни один столбец таблицы winterize в нем не указан.
Список OF предложения FOR UPDATE не ограничивает вас изменением только указанных в нем столбцов. Он прсюто помогает более четко документировать свои намерения. Если в инструкции SELECT задано предложение FOR UPDATE без списка OF, Oracle блокирует все отбираемые запросом строки всех таблиц, перечисленных в предложении FROM.
Более того, инструкция SELECT...FOR UPDATE вообще не требует последующего выполнения инструкций DELETE или UPDATE - она просто сообщает Oracle, что вам, возможно) потребуется это сделать.
И наконец, предложение FOR UPDATE можно дополнить -ключевым словом NOWAIT, означающим, что если таблица заблокирована другим пользователем, Oracle не должна ждать ее освобождения. В этом случае управление будет сразу возвращено программе, чтобы она могла пока заняться чем-нибудь другим и спустя некоторое повторить попытку доступа. При отсутствии ключевого слова NOWAIT процесс ожидания будет заблокирован до тех пор, пока таблица не освободится. Причем тайм-аут в данном случае предусмотрен только для удаленных таблиц. Он определяется значением инициализационного параметра DISTRIBUTED_LOCK_TIMEOin\ Что касается локальных таблиц, ждать их освобождения можно бесконечно долго.
Снятие блокировок инструкцией COMMIT
Как только для курсора с предложением FOR UPDATE выполняется оператор OPEN, все строки его результирующего набора блокируются и остаются заблокированными до тех пор, пока в сеансе не выполнится инструкция COMMIT для фиксации внесенных изменений или инструкция ROLLBACK для их отката. В любом из этих слу~ чаев заблокированные строки будут освобождены. Поэтому после выполнения
инструкции COMMIT или ROLLBACK текущая позиция в курсоре будет утеряна, и вы не сможете выполнить следующую выборку с помощью инструкции FETCH.
Рассмотрим программу, определяющую мероприятия (задания), которые необходимо выполнить в процессе подготовки к зиме:
DECLARE
/* Что нужно сделать осенью, чтобы подготовиться к зиме */
CURSOR fall jobs cur
IS ""
SELECT task, expected_hours. tools_requ1 red. dojt^ourself^flag
FROM winterize
WHERE year - TOJUMBER (QCHAR (SYSDATE. 'YYYY')3
AND completed^flag - 'WTYET'
FOR UPDATE OF task;
BEGIN
/* Для каждого выбранного из курсора задания.,. */
FOR job__rec IN fall_jobs_cur
LOOP "
IF job_rec.dojtcounself_flag - 'YOUCANDOIT'
THEN ~~~
/*
11 Найдено очередное задание, фиксируем изменения.
UPDATE winterize SET responsible - 'STEVEN'
WHERE task = jab_rec.task'
AND year - T0_CHAR (SYSDATE, 'YYYY'):
commit; -
END IF;
END LOOP:
EW;
Предположим, что этот цикл нашел первое задание, помеченное как YOUCAN- DQIT. Ответственным за его реализацию назначается STEVEN, и программа пытается выбрать следующую строку. Однако в ответ она получает исключение:
ORA-01002: fetch out of sequence
Если в процессе извлечения данных из курсора, задаваемого как SELECT...FOR UPDATE, нужно производить фиксацию или откат изменений, придется включить в программу код, прекращающий дальнейшую выборку (например, оператор EXIT, выполняющий выход из цикла).
Предложение WHERE CURRENT OF
PL/SQL позволяет использовать в инструкциях UPDATE и DELETE специальное предложение WHERE CURRENT OF, облегчающее процесс изменения последней выбранной из курсора строки данных.
Обновления столбцов последней выбранной строки можно выполнить с помощью такой инструкции:
UPDATE имя^табпицы
SET предлокение^е1
WHERE CURRENT OF u^j<ypcQpa:
Аналогичным образом производится удаление последней выбранной строки:
DELETE
FROM иня_таблииы
WHERE CURRENT OF имя_курсорд;'
Обратите внимание, что в предложении WHERE CURRENT OF указывается курсор, а не запись) в которую была помещена очередная строка.
Важнейшим достоинством использования предложения WHERE CURRENT OF при модификации или удалении последней извлеченной строки из курсора, является то, что один и тот же критерий поиска строки не нужно задавать в двух местах программы. Не будь его, пришлось бы ввести предложение WHERE в определение курсора, а затем повторить его в соответствующих инструкциях UPDATE и DELETE. Если бы впоследствии потребовалось изменить условие поиска, изменения нужно было бы вносить во все S QL-инструкции, в которых оно используется.
Вопрос избыточности кода достаточно серьезен, и не стоит недооценивать его значение. В PL/SQL есть множество средств минимизации избыточности, и о них нужно знать и активно ими пользоваться. Такие элементы, как предложение WHERE CURRENT OF, атрибуты объявлений XTYPE и XROWTYPE, цикл FOR с курсором, локальные модули и другие языковые конструкции PL/SQL, могут значительно облегчить разработку, отладку и сопровождение приложений Oracle.
Давайте посмотрим, как с помощью предложения WHERE CURRENT OF усовершенствовать пример из предыдущего раздела. В цикле FOR нужно обновить строку, только что выбранную из курсора. Попытаемся сделать это с помощью инструкции UPDATE, в которой задано то же условие, что и в инструкции SELECT курсора (первичный ключ таблицы составляют значения столбцов task и year).
WHERE task - job rec.task
ANO year - TUCHAR (SYSDATE, 'YYYY');
Как рассказывалось выше, это неверный подход: одна и та же логика запро- фаммирована в двух местах, и в случае внесения каких-либо изменений нужно следить за синхронизацией этого кода. Как было бы хорошо, если бы PL/SQL предоставлял программные эквиваленты таких действий, как удаление только что выбранной строки и обновление столбцов только что выбранной строки.
Это именно то, что делает предложение WHERE CURRENT OF! В новой версии программы подготовки к зиме воспользуемся им и заодно заменим цикл FOR простым циклом с условным ВЫХОДОМ!
DECLARE '
CURSOR fall_Jobs_cur IS SELECT .,, то же, что и в предыдущем примере ...;
job rec falT joobs curlROWTYPE;
BEGIN """
OPEN falljobs_cur;
LOOP."
FETCH fan_jobs_cur INTO job_rec;
IF fall jobs curZNOTFOUg
THEN *-
EXIT:
ELSIF Job_rec,do_1t_yourself_flag - 'YOUCANDOIT*
THEN --—
UPDATE winterize SET responsible ■ 'STEVEN' WHERE CURRENT OF fallJobs_cur;
COMMIT:
EXIT:
END IF;
END LOOP;
CLOSE fallJobs_cur;■
END; 一一
Курсорные переменные
Курсорная переменная — это переменная, указывающая на курсор. В отличие от явного курсора, имя которого в PL/SQL используется как идентификатор рабочей области результирующего набора строк, курсорная переменная содержит ссылку на эту рабочую область. Явные и неявные курсоры, с учетом того, что они привязаны к конкретному запросу, можно назвать статическими. С помощью же курсорной переменной можно выполнить любой запрос и даже несколько разных запросов в одной программе.
Важнейшим преимуществом курсорных переменных является то, что они предоставляют механизм передачи результатов запроса (выбранных из строк курсора) между разными программами PL/SQL, в том числе между клиентскими и серверными программами. До выхода PL/SQL Release 2.3 приходилось выбирать из курсора все данные, сохранять их в переменных PL/SQL (обычно для этой цели использовалась таблица) и передавать эти переменные в качестве аргументов. А курсорная переменная позволяет передать другой программе ссылку на объект курсора, чтобы та могла работать с его данными. Это упрощает программный код и повышает его эффективность.
Кроме того, благодаря этой новой технологии курсоры могут совместно использоваться несколькими программами. Налример, в архитектуре клиент-сервер клиентская программа может открыть курсор и начать выборку из него данных, а затем передать указывающую на него переменную в качестве аргумента хранимой процедуре, выполняющейся на сервере. Эта процедура может продолжить выборку и некоторое время спустя снова передать управление клиентской программе, которая закроет курсор. Так же могут взаимодействовать и две разные хранимые программы из одного или разных экземпляров СУБД Oracle.
Описанный процесс, схематически показанный на рис. 14,2, который демонстрирует новые возможности программ PL/SQL — совместное использование данных и управление курсорами.
Программный код с курсорными переменными очень похож на код с явными курсорами. В следующем примере объявляется тип курсора (называемый REF CURSOR) для таблицы company, затем объявляется курсорная переменная этого типа, после чего курсорная переменная открывается, из нее выбираются строки и она закрывается.
DECLARE
/* Создаем тип курсора */
TYPE company_curtype IS REF CURSOR RETURN cowpanyXROWTYPE;
/* Объявляем курсорную перекенную этого типа */ company_curvar company_curtype:
/* Объявляем запись той же структуры, что и курсорная переменная */ company_rec companyXRObfTYPE;
BEGIN ~
/* Открываем курсорную лерекенную и связываем ее с SQL-инструкцией */ OPEN company_curvar FDR SELECT * FROM company:
/* Извлекаем значение курсорной переменной */
FETCH company_curvar INTO company_rec;
/* Закрываем курсорную переменную */
CLOSE company_curvar:
END;一
Программа 1
[OPEN курсор )
■ f FETCH строка 1) Вызов второй програмыы П[ЮП»ММа2 〈инииииии^ииии/ с передачей курсорной[尸
唧‘"吃竺> [^ЕТСН строга 2)
CLOSE курсор H .; Возврат в первую програиму иикмшамовшпк^аяинмвиимымистяшж
Рис. 14,2. Передача ссылки на курсор между двумя программами
Все это очень похоже на работу с обычным явным курсором, если, конечно, не считать определения типа REF CURSOR и синтаксиса оператора OPEN FOR, в котором запрос задается при открытии курсорной переменной, — здесь он несколько иной.
Несмотря на схожесть синтаксиса, сам факт, что курсорная переменная является все же переменной, открывает множество новых возможностей. О них рассказывается в следующих разделах.
Для чего нужны курсорные переменные
Курсорная переменная предоставляет программисту ряд новых возможностей.
Q По ходу выполнения программы курсорную переменную можно связывать с разными запросами. Иными славами, ее можно использовать для выборки данных из разных результирующих наборов строк.
Q Курсорную переменную можно передать в качестве аргумента процедуре или функции. Это означает, что результирующий набор строк курсора будет совместно использоваться вызываемой и вызывающей программами.
О Курсорные переменные обладают всеми возможностями статических курсоров PL/SQL. К ним можно применять операторы OPEN, CLOSE и инструкцию FETCH, а также стандартные атрибуты курсоров: ZFOUND, XNOTFOUND, ZROWCOUNT и XISOPEN.
О Значение одной курсорной переменной (вместе с результирующим набором строк) можно присвоить в качестве значения другой курсорной переменной. Однако у этой операции присваивания имеются определенные ограничения, о которых будет рассказано ниже.
Сходство со статическими курсорами
Семантика использования курсорных переменных почти совпадает с семантикой статических курсоров. Объявление курсорной переменной и открывающий ее оператор изменены, но все остальные операции остались прежними.
О Оператор CLOSE. В следующем примере объявляются тип REF CURSOR и курсорная переменная на его основе. Затем эта переменная закрывается, причем синтаксис оператора CLOSE тот Ж6)что и у статического курсора:
DECLARE
TYPE var_cur_type IS REF CURSOR;
var_cur var cur_type;
BEGIN - 一
CLOSE var cur:
END; "
О Атрибуты курсорной переменной. Для получения информации о курсорной переменной используются атрибуты статического курсора. В частности, для курсорной переменной из предыдущего примера обращение к атрибутам выполняется так:
var_curXISOPEN'
var curVOUND■
var"curXNOTFOUND
var^curXROWCOUNT
О Выборка из курсорной переменной. Для выборки данных из курсорной переменной в локальные структуры данных PL/SQL используется инструкция FETCH с тем же синтаксисом, что и у статических курсоров. Однако здесь действуют дополнительные правила, требующие соответствия структуры данных строки курсорной переменной и структур данных, указанных справа от ключевого слова INTO, Эти правила приводятся далее( в разделе «Правила для курсорных переменных».
Поскольку синтаксис перечисленных операторов и атрибутов, предназначенных для работы с курсорными переменными, во многом совпадает с синтаксисом их версий для статических курсоров, ниже рассматриваются только элементы, уникальные для курсорных переменных.
Объявление типов REF CURSOR
Подобно таблице PL/SQL или записи определяемого программистом типа курсорная переменная объявляется в два этапа.
О Сначала создается тип курсора (в Oracle9z можно использовать предопределенный слаботипизированный тип SYS_REFCURSOR).
О Затем на его основе объявляется фактическая курсорная переменная.
Синтаксис объявления типа курсора таков:
ТУРЕ ш^ш_курсора IS REF CURSOR [ RETURN возвращаемый^ип']:
Здесь MMRjHnaj^ypcopa — это имя создаваемого типа курсора, а возвращдемый_тип - спецификация возвращаемых данных курсора. Это может быть любая структура данных, использование которой допускается в предложении RETURN, определенная с помощью атрибута WROWTYPE или путем ссылки на ранее объявленный тип записи.
Обратите внимание, что предложение RETURN в объявлении типа REF CURSOR не обязательно. Поэтому допустимы оба следующих объявления:
TYPE cornpany_curtype IS REF CURSOR RETURN companyXROWTYPE:
TYPE generi c_^curtype IS REF CURSOR
В первом случае мы объявили сильнотпипизированный тип, поскольку тип структуры, возвращаемой курсорной переменной, задается в момент объявления (непосредственно или путем привязки к типу строки таблицы). Поэтому, выполняя синтаксический анализ инструкции FETCH, компилятор может сразу определить, соответствует ли тип заданной в нем структуры данных спецификации RETURN в определении REF CURSOR. Но курсорная переменная, объявленная на основе данного типа, может использоваться только с SQL-инструкциями, возвращаю* щими строки соответствующего типа, и с такими же инструкциями FETCH,
Во втором случае, без предложения RETURN, мы объявили слаботипизированный тип. Тип возвращаемой структуры данных для него не задается. Курсорная переменная, объявленная на основе такого типа, может использоваться более гибко, поскольку для нее можно задавать любые запросы с любой структурой возвращаемых данных, причем с помощью одной и той же переменной можно поочередно выполнить несколько разных запросов, возвращающих результаты разных типов.
Объявление курсорной переменной
Синтаксис объявления курсорной переменной таков:
WfjjjOTXOps。麻_〃ере用е斤。府 имя_тмпа_курсорд;
Здесь имя_курсорной_переменной — это имя объявляемой переменной, а имя_гипа_ курсора — это имя типа курсора, объявленного ранее с помощью оператора TY- РЕ,. .REF CURSOR. Как создается курсорная переменная, вы поймете из кода: deciare
/* Создание типа курсора для хранения информации о спортивных автомобилях */
TYPE sports_car_cur_type IS REF CURSOR RETURN carXROWTYPE:
/* Создание курсорной переменной для хранения информации о спортивных автомобилях */ sports_car_cur sports_car_cur_type;
BEGIN ~~
END:
Важно понимать различие между операцией объявления курсорной переменной и операцией создания реального объекта курсора, то есть результирующего набора строк, идентифицируемого SQL-инструкцией курсора. Подобно тому как константа является значением, а переменная — указателем на значение, так и статический курсор является набором данных, а курсорная переменная — указателем на этот набор. Указанное различие иллюстрирует рис. 14.3. Обратите внимание, что две разные переменные в различных программах ссылаются на один и тот же объект курсора.
Программа PL/SQL
犠志*期蝸終齡滁:.齡穗練緻:諭腓派滚备毅淑&磁』*蚓ЙЕ価鹼艙咖踰瞅擬辍ё%:
Рис. 14.3. Курсоры и курсорные переменные
В результате объявления курсорной переменной объект курсора не создается. Для создания такового, а также для помещения ссылки на него в переменную нужно выполнить оператор OPEN FOR.
Открытие курсорной переменной
Инициализацию курсорной переменной (то есть создание объекта курсора и помещение ссылки на него в переменную) выполняет оператор OPEN с предложением FOR, в котором задается SQL-инструкция:
OPEN “^^курсора FOR инструкцияjelect-.
Здесь имя_курсора — это имя курсора или курсорной переменной, а инструкция_5е- lect — это SQL-инструкция SELECT, используемая для формирования набора строк курсора.
Для курсорной переменной сильнотипизированного типа количество и типы данных столбцов, указанных в инструкции SELECT, должны соответствовать (быть
совместимыми) структуре, заданной в предложении RETURN оператора TYPE.. .REF CURSOR. Пример совместимых типов показан на рис. 14.4. Подробнее о совместимости возвращаемых курсорами структур рассказывается далее, в разделе «Ира- вила для курсорных переменных».
DECLARE
TYPE emp_curtype IS
REF~CURSOR RETURN|empffiOWrfPE;
emp_curvar emp_curtype; J
BEGIN ~"I
OPEN emp_curvar FOR 】一 +
~ SELECTI* FROM enp; |•
END;
Рис. 14.4. Совместимость типа данных REF CURSOR и типа данных значений,возвращаемых инструкцией SEI_ECT
Если курсорная переменная объявлена на основе слаботипизированного типа, ее можно открывать с любым запросом, независимо от структуры возвращаемых им данных. В следующем примере курсорная переменная трижды открывается с тремя разными запросами (а последний оператор OPEN вообще никак не связан с таблицей employed):
DECLARE
TYPE emp_curtype IS REF CURSOR;
emp__curvar emp_curtype :
BEGIN ~-
OPEN emp_curvar FOR SELECT * FROM enp:
OPEN emp_curvar FOR SELECT employee_id FROM emp;
OPEN emp_curvar FOR SELECT company_id, name FROM company;
END; ~-
Если курсорной переменной еще не присвоен никакой объект курсора, оператор OPEN FOR неявно создает его для нее. Если же переменная уже указывает на объект курсора, оператор OPEN FOR не создает новый курсор, а ассоциирует с существующим новый запрос. Таким образом, объект курсора является структурой, отдельной и от курсорной переменной, и от запроса.
Выборка данных из курсорной переменной
Как уже упоминалось, синтаксис инструкции FETCH для курсорной переменной точно такой же, как для статического курсора:
FETCH имя_курсорной_переменной INTO ш^записи;
FETCH имя_курсорной_переменной INTO имя_переменной, имя^переменной .,.:
Когда курсорная переменная объявляется на основе сильнотипизированного типа, компилятор PL/SQL проверяет совместимость типов структур, которые задаются в предложении INTO, с типом, заданным в предложении RETURN оператора TYPE...REF CURSOR.
Если же курсорная переменная объявлена на основе слаботипизированного типа, компилятор не может выполнить подобную проверку. Данные из такой курсорной переменной могут извлекаться в любые структуры данных, поскольку компилятор не знает, какой объект курсора (и какая инструкция) будет ей присвоен на момент выборки.
Поэтому проверка совместимости производится во время выполнения программы, причем непоч)едственно в момент выборки данных. И если окажется, что возвращенные запросом данные не соответствуют структурам, указанным в предложении INTO, исполняющее ядро PL/SQL сгенерирует исключение ROWTYPE_MIS- МАТСН. Учтите, что при необходимости и наличии соответствующих возможностей PL/SQ.L выполняет неявное преобразование данных.
Обработка исключения ROWTYPE_MISMATCH
Перехватив исключение ROWTYPE_MISMATCH, можно попытаться выбрать данные из курсорной переменной с другим предложением INTO. В случае успеха из объекта курсора будет считана первая строка результирующего набора.
Рассмотрим пример централизованной базы данных, состоящей из множества таблиц с информацией о частной собственности, о коммерческой собственности и т. д. Кроме того, существует единая таблица адресов, где каждому адресу поставлен в соответствие тип здания (жилой дом, ведомственное здание и т. д.). Процедура open^statejist получает в качестве параметра адрес здания, открывает курсорную переменную слаботипизированного типа и связывает с ней запрос на выборку информации об этом здании из соответствующей таблицы. С помощью этой процедуры сведения о нужном здании может получить сотрудник любого подразделения, занимающегося конкретным типом недвижимости. Вот как она создается.
О Сначала определяется слаботилизированный тип REF CURSOR:
TYPE bullding^curtype IS REF CURSOR;
О Затем создается процедура. Обратите внимание, что курсорная переменная передается как IN OUT, то есть является входной и выходной:
PROCEDURE open site list
CaddressJn"lN VARCHAR2.
site cur Incut IN DUT build1ng_curtype)
IS ""-
home type CONSTANT INTEGER :- 1;
conrerclaljype CONSTANT INTEGER :- 2;
/* Статический курсор для。叩еделенмл типа здания */
CURSOR site_type_cur IS
SELECT s"te_type FROM property_master
WHERE address ■ address_1n;"slte^type^ec site_type^curUTROWTYPE:
BEGIN
/* Считываем информацию о типе знания 口ля данного адреса */
OPEN site_type_cur;
FETCH site_type_cur INTO site_type_rec:
CLDSE site二type二cur;--
/* Теперь с учетом типа здания 叩омзволмм выборку из таблицы */
IF site type rec.s1te_type - home_type
THEN 一 "
/* Таблица жилых домов */
OPEN slte_curjnout FOR
SELECT * FROM home_propert1es
WHERE address LIKE 'X' || addressjn || 'Г:
ELSIF site_type_rec.s1te__type - conwerclal_type
THEN ~~"
/* Таблица коммерческой собственности */
OPEN s1te_cur__1nout FOR
SELECT * FROM conmerclal_properties
WHERE address LIKE 'f || addressjn || 7':
END IF;-
END 0pen_s1teJ1st;
Процедура готова, и теперь ее можно использовать.
В следующем примере процедуре open_state_list передается адрес здания, а затем выполняется попытка выборки информации из полученного курсора. При этом предполагается, что курсор содержит сведения о жилом доме. Если это не так, PL/SQL инициирует исключение ROWTYPE_MISMATCH, указывающее на несовместимость типов записей. После этого управление передается в раздел обработки исклюнени立,где повторяется попытка выборки строки, но на этот раз в запись, соответствующую коммерческому зданшо,
DECLARE
/* Объявляем курсорную переменную */
bulld1ng_curvar bul1 ding_curtype;
/* Определяем структуры записей для двух разных таблиц */
home_rec home_properti es3!ROWTYPE ;
comerclal rec commercial properties?!ROWTYPE;
BEGIN ~一
/* Запрашиваем у пользователя адрес */
prompt_for_address (address_str1ng);
/* Присваиваем курсорной переменной запрос с учетом адреса */ open^sitejlst (address^strlng. bullding_curvar):
/* Пытаемся выбрать строку из курсорной переменной в запись, соответствующую жилому дому */
FETCH build1ng_curvar INTO home_rec;
/* Если все получилось. выводим информацию */
show_home_s1te (home rec);
EXCEPTION _"
/* Если перзал строка не относится к жилому дому... */
WHEN ROWTYPE_MISMATCH
THEN ~
/* Выбираем ту же строку а запись, соответствующую коммерческому зданию */
FETCH bulldlng^curvar INTO commercial_rec;
/* Выводим информацию о коммерческом здании */
show_cownercial_site (cownercial rec);
Правила использования курсорных переменных
В этом разделе рассматриваются правила, вопросы и рекомендации, связанные с использованием курсорных переменных в программах. Речь пойдет о правилах соответствия типов данных строк, псевдонимах курсорных переменных и области их действия.
Прежде всего помните, что курсорная переменная — это ссылка на объект курсора, представляющий данные, которые выбраны из базы данных с помощью содержащегося в нем запроса. Это не сам объект курсора. Курсорная переменная может быть связана с определенным запросом при одном из следующих условий: О если выполнен оператор OPEN FOR, назначающий курсорной переменной этот запрос;
О если курсорной переменной присвоено значение другой курсорной переменной, указывающей на этот запрос.
С курсорными переменными может использоваться оператор присваивания, и эти переменные могут передаваться в качестве аргументов процедурам и функциям. Чтобы иметь возможность выполнять такие операции, переменные должны удовлетворять правилам соответствия типов строк, проверяемым во время компиляции или во время выполнения, в зависимости от типа курсорных переменных.
Правила соответствия типов строк, проверяемые во время компиляции
В процессе компиляции программы PL/SQL проверяет, соблюдаются ли следующие правила.
О Для двух курсорных переменных допустимо применять оператор присваивания или использовать их в качестве передаваемых параметров в любом из следующих случаев:
обе переменные (или параметра) определены на основе сильнотипизирован- ных типов REF CURSOR с одним и тем же именем типа возвращаемой записи;
обе переменные (или параметра) определены на основе слаботипизирован- ных типов REF CURSOR, независимо от типа возвращаемой записи;
одна переменная (или параметр) определена на основе сильнотипизиро- ванного типа REF CURSOR, а другая - на основе слаботипизированного типа.
О Для курсорной переменной (или параметра), определенной на основе сильно- типизированного типа REF CURSOR, можно открыть запрос, возвращающий строку, тип которой структурно эквивалентен возвращаемому типу, заданному в определении типа курсорной переменной.
О Для курсорной переменной (или параметра), определенной на основе слаботипизированного типа REF CURSOR, можно открыть любой запрос. Из этой переменной можно выбирать данные в любой список переменных или в запись любого типа.
Если одна из курсорных переменных определена на основе слаботипизированного типа REF CURSOR, компилятор PL/SQL не может проверить соответствие
типов возвращаемых строк. Такая проверка будет произведена во время выполнения программы с учетом правил, описанных в следующем разделе.
Правила соответствия типов строк, проверяемые во время выполнения
Во время выполнения программы PL/SQL проверяет, соблюдаются ли следующие правила:
Курсорной переменной (или параметру), определенной на основе слабо типизированного типа REF CURSOR, можно назначить любой запрос независимо от типа возвращаемых им строк.
Курсорной переменной (или параметру), определенной на основе сильноти- пизированного типа REF CURSOR, можно назначить только запрос, структурно соответствующий типу записи, заданному в предложении RETURN определения типа курсорной переменной.
О Две записи (или два списка переменных) считаются структурно соответствующими друг другу, если соблюдены два условия:
количество полей в обеих записях (или количество переменных в обоих списках) одинаково;
для каждого поля одной записи (или переменой из одного списка) соответствующее поле (или переменная из другого списка) имеет тот же тип данных или может быть неявно преобразовано к тому же типу данных.
Список переменных и запись в предложении INTO инструкции FETCH должны структурно соответствовать связанному с курсорной переменной запросу. Это правило касается и статических курсоров.
Псевдоним объекта курсора
Если одна курсорная переменная присваивается другой курсорной переменной, то обе они становятся псевдонимами одного и того же объекта курсора. В данном случае в результате присваивания в принимающую переменную копируется только ссылка на объект курсора, так что сам курсор (включая запрос и результирующий набор строк) остается на своем месте, но теперь иа него указывают две переменные. Результаты любой операции с этим курсором, выполняемой с помощью одной из переменных, сразу же становятся доступными для другой переменной.
Принцип действия псевдонимов курсоров демонстрирует приведенный ниже анонимный блок:
DECLARE
TYPE curvarjype IS REF CURSOR;
curvarl curvar_type;
curvar2 curvar_type;
story fa1ry_taTesXROWTYPE:
BEGIN -
OPEN curvarl FOR SELECT * FROM fairy_tales;
curvar2 :-= curvarl:
FETCH curvarl INTO story:
FETCH curvar2 INTO story:
CLOSE curvar2:
FETCH curvarl INTO story;
END:
Выполняемые этим кодом действия описаны в следующей таблице.
Строки Описание
2-5 Объявление слаботипизированного типа REF CURSOR и курсорных переменных на его основе
Создание объекта курсора и назначение его курсорной переменной curvarl
Назначение того же объекта курсора второй курсорной переменной, curvarZ. (Теперь
у нас две переменные, которые можно использовать для работы с одним и тем же результирующим набором строк)
Выборка первой строки с помощью переменной curvarl
Выборка второй строки с помощью переменной curvar2. (При этом совершенно
не важно, какую из переменных использовать для выборки. Указатель текущей строки находится в объекте курсора, а не в переменной)
И Закрытие объекта курсора с помощью переменной curvar2
При попытке выборки строки с помощью переменной curvarl инициируется исключение INVALID_CURSOR. (Поскольку объект курсора закрыт, ни одна из ссылающихся на него переменных больше не может использоваться для доступа к строкам результирующего набора)
Любое изменение состояния объекта курсора является видимым для любой ссылающейся на него переменной.
Область видимости объекта курсора
Область видимости курсорной переменной та жег что и у статического курсора, а именно блок PL/SQL)в котором эта переменная объявлена (если только она не объявлена в пакете, что делает ее глобально доступной). А вот область видимости объекта курсора совершенно иная.
С момента выполнения оператора OPEN FOR, создающего объект курсора, последний остается доступным до тех пор, пока на него ссылается хоть одна активная переменная. Как вы уже знаете, можно создать объект курсора в одной области (блоке PL/SQL), а затем присвоить ссылку на него переменой из другой области. Когда выполнение программы выходит за пределы первой области, первая ссылающаяся на него переменная становится недоступной, но курсор останется доступным до тех пор, пока доступна вторая ссылающаяся на него переменная.
Следующий пример с вложенными блоками демонстрирует доступ к объекту курсора извне области, в которой он был создан.
DECLARE
/* Определяем тип REF CURSOR, курсорную и скалярнуб переменные */
TYPE curvarjype IS REF CURSOR;
curvarl curvar_type;
do^Qu_get_it VARCHAR2C100):
BEGIN ~ "
/*
11 Во вложенном блоке создается объект курсора, который
11 назначается курсорной переменной curvarl.
*/
DECLARE
curvarZ curvar_type:
BEGIN
OPEN curvar2 FOR SELECT punchjlne FROM jokes;
curvarl :- curvar2:
END;
11 Курсорная переменная curvar2 больше не активна, но ссылка
11 на t^pcop была скопирована в переменную curvarl.
11 объявленную во внешнем блоке. Поэтому данные из объекта арсора
11 можно выбирать с помощью ^рсорной переменной curvarl.
*/
FETCH curvarl INTO do_7ou_get_it;
CLOSE curvarl;
END:
Передача курсорных переменных в качестве аргументов
Курсорную переменную можно передать в качестве аргумента процедуре или функции. В списке параметров такой процедуры (или функции) должен быть задан режим использования и тип REF CURSOR,
Идентификация типа REF CURSOR
В заголовке программы должен быть вдентифицирован предварительно объявленный тип параметра курсорной переменной. Если вы создаете локальный модуль внутри другой протраммы (о локальных модулях подробно рассказывается в главе 16), тип курсора можно определить в той же протрамме. Вот пример:
DECLARE
/* Определяем тип REF CURSOR */
TYPE curvar_type IS REF CURSOR RETURN companyXROWTYPE;
/* Указываем его в списке параметров */
PROCEDURE open_query (curvar_out OUT curvar_type)
IS'","
locaLcur curvar type:
BEGIN ""
OPEN local_cur FOR SELECT * FROM company:
curvar out local cur:
ENO: '-
BEGIN
END;"
Если вы создаете отдельную процедуру или функцию, объявление типа REF CURSOR придется поместить в пакет. Для ссылки на этот тип должен использоваться точечный синтаксис. Последовательность выполняемых действий в этом случае должна быть такой.
Создаем пакет с объявлением типа REF CURSOR:■
PACKAGE company
IS
/* Определяем тип REF CURSOR */
TYPE curvar_type IS REF CURSOR RETURN companyXROWTYPE;
END package;
В отдельной процедуре ссылаемся на тип REF CURSOR, предварив имя типа именем пакета:
PROCEDURE open_company (curvar_out OUT company.curvar_type) IS
BEGIN --
END;
Режимы использования параметров
Значение переменной типа REF CURSOR может являться входным параметром (IN), выходным параметром (OUT), а также и тем и другим (IN OUT).
Напомним, что значением курсорной переменной служит ссылка на объект курсора, а не сам объект. Поэтому значение переменной не меняется после выборки строк из курсора или после его закрытия.
Значение курсорной переменной можно изменить двумя способами:
О путем присваивания значения другой курсорной переменной;
О с использованием оператора OPEN FOR.
Если курсорная переменная уже указывает на объект курсора, оператор OPEN FOR не меняет эту ссылку. Он просто модифицирует связанный с курсором запрос и результирующий набор строк.
Приведем пример процедуры, параметрами которой являются курсорные переменные:
PROCEDURE ass1gn_curvar
(old_curvar_in IN company.curvar_type.
new_curvar_out OUT company.curvar_type)
BEGIN
new_curvar_out := old_curvar_1n:
END; "~~-
Эта процедура копирует значение одной курсорной переменной в другую. Ее первый параметр является входным, поскольку он используется в правой части оператора присваивания, а второй — выходным, поскольку его значение изменяется процедурой. (Режимом второго параметра мог быть и IN OUT.) Обратите внимание, что тип curvar_type определен в пакете company.
Ограничения, связанные с курсорными переменными
Курсорные переменные подчиняются следующим ограничениям. Некоторые из них, возможно, будут отменены в следующих версиях Oracle.
О Курсорная переменная не может быть объявлена в пакете, поскольку она не имеет постоянного значения.
О Курсорную переменную нельзя передавать между серверами с помощью удаленных вызовов процедуры.
О Если курсорная переменная передается PL/SQL как переменная привязки или хост-переменная, на сервере нельзя выбирать из нее строки, не открыв ее в том же вызове.
О В запросе, заданном для курсорной переменной в операторе OPEN FROM, раньше нельзя было использовать предложение FOR UPDATE. (Такая возможность появилась только в Огас1е9г.)
О Курсорную переменную нельзя с помощью операторов сравнения проверять на равенство или неравенство какому либо значению (в том числе значению NULL).
О Курсорной переменной нельзя присвоить значение NULL Попытка сделать это приведет к исключению PLS-00382: Expression Is of wrong type.
О Значение курсорной переменной не может храниться в столбце базы данных. Типы REF CURSOR не могут использоваться для определения столбцов таблиц.
О Значение курсорной переменной не может содержаться во вложенной таблице, ассоциативном массиве или VARRAY. Типы REF CURSOR не могут использоваться для определения элементов коллекции.
Курсорные выражения
В Oracle9i введен еще один новый мощный элемент языка SQL: курсорные выражения. Курсорное выражение — это выражение со специальным оператором CURSOR, используемое в SQL-запросе и определяющее вложенный курсор. Каждая строка результирующего набора вложенного курсора может содержать диапазон значений, допустимых для SQL-запросов; кроме того, она может включать другие курсоры, определяемые вложенными запросами.
ПРИМЕЧАНИЕ
Оператор CURSOR, введенный в Orade8i для SQL, не мог использоваться в программах PL/SQL. В Огас1е91 указанный недостаток был устранен, и теперь курсорные выражения могут применяться в SQL-инструкциях программ PL/SQL
С помощью курсорного выражения можно вернуть большой и сложный набор связанных значений из одной или нескольких таблиц. Такой результирующий набор данных обычно обрабатывается с помощью ряда вложенных циклов: главный цикл выбирает строки основного курсора, а вложенные циклы выбирают строки вложенных курсоров.
Курсорное выражение может быть довольно сложным, что зависит от сложности извлекаемых с его помощью данных. Однако это столь мощный и гибкий механизм выборки, что его обязательно нужно освоить.
Перечислим структурные компоненты языка, в которых могут использоваться курсорные выражения:
О объявления явных курсоров;
О динамические SQL-запросы;
О объявления и переменные типа REF CURSOR.'
В неявных курсорах курсорные выражения никогда не используются.
Синтаксис курсорного выражения очень прост;
CURSOR (.вложенный^запрос)
Oracle неявно открывает вложенный курсор при выборке строки, содержащей его выражение, из родительского или внешнего курсора. Он закрывается, когда:
О вы его явно закрываете;
О родительский курсор повторно выполняется, закрывается или отменяется;
О при выборке из родительского курсора инициируется исключение. В данном случае вложенный курсор закрывается вместе с родительским.
Использование курсорных выражений
Следующая процедура демонстрирует принцип использования вложенного курсорного выражения. Запрос верхнего уровня выбирает два элемента данных: название города и отделение компании в этом городе, информация о котором содержится во вложенном курсоре. Этот вложенный курсор, в свою очередь, извлекает с помощью курсорного выражения еще один вложенный курсор — на этот раз с фамилиями всех сотрудников каждого отделения.
Те же данные можно было бы извлечь из базы данных с помощью нескольких явных курсоров) открываемых и обрабатываемых во вложенных циклах. Однако курсорное выражение позволяет применить другой подход, более лаконичный и эффективный, переложив большую часть работы на подсистему выполнения S QL-инструкций и тем самым сократив количество переключений контекста.
CREATE OR REPLACE PROCEDURE emp_report (p locld NUMBER)
IS"'
TYPE refcursor IS REF CURSOR:
-Запрос возвращает 2 столбца, второй из которых является '
• wcopon. а значит, позволяет перебрать набор связанных строк
CURSOR al1_1n_one cur IS
SELECT T.cTty,"
CURSOR (SELECT d.department_name, CURSORCSELECT e.last_name.
FROM employees e
WHERE e.departmentjd -d. departmentjd)AS ename
FROM departments d
WHERE 1 .locatlonjd - d.locationjd) AS dname
FROM locations 1--
WHERE 1. locatlonjd - p_lodd;
departments^cur refcursor:
employees_cur refcursor:
v.clty locatlons.dtyXTYPE;
v_dname departments.department_nameXTYPE;
v_en ame епр 1 oyees .1 a st__nameXTYPE :
BEGIN'-
OPEN all_1n_one_cur;
LOOP
FETCH all_in_one_cur INTO v_dty. departments_cur;
EXIT WHEN%1Tjn2one_curXN0fF0UND;"
Перебираем список отделений, причем для этого НЕ НУЖНО
-явно открывать курсор. Oracle делает это автоматически LOOP
FETCH departments_cur INTO v_dname, employees_cur:
EXIT WHEN departments^curXNOTFOUNO;~
--Теперь можно перебрать список сотрудников текущего
--отделения. И снова цля этого не нужно явно открывать курсор
LOOP'
FETCH employees_cur INTO v_ename:
EXIT WHEN employees^curiNOTFOUND;
DBMS_OUTPUT.putJ1ne (
v二city
IT ''
11 v dname
II
11 v ename);
END LOOP;"
END LOOP;■
END LOOP:
CLOSE all In one cur:
END:--
Ограничения, связанные с курсорными выражениями
На применение курсорных выражений налагается ряд ограничений.
О Курсорное выражение не может использоваться в неявном курсоре по причине отсутствия механизма выборки из вложенного курсора в структуру данных PL/SQL.
О Курсорные выражения можно задавать только в «самом внешнем* списке выборки запроса.
О Курсорные выражения нельзя использовать в инструкции SELECT, вложенной в другой запрос (исключение составляет вложенный запрос курсорного выражения).
О Курсорное выражение может быть применено в качестве аргумента табличной функции, заданной в предложении FROM инструкции SELECT.
О Курсорные выражения нельзя использовать в определении представления.
Q Если курсорное выражение задано в динамической SQL-инструкции» по отношению к нему нельзя применять операции BIND и EXECUTE,
口 Динамический SQL
I О и динамический PL/SQL
А Динамические запросы SQL
А Многострочные запросы с курсорными переменными
А Привязка переменных
А Работа с объектами и коллекциями
А Разработка приложений с использованием динамического SQL
А Пакет Nds Utility
А Сравнение динамического SQL и пакета DBMS一SQL
Слова «динамический» и «статический» являются антонимами. Статическими называются жестко закодированные инструкции и операторы, которые не изменяются с момента компиляции программы. Инструкции динамического SQL формируются, компилируются и вызываются непосредственно во время выполнения программы. Следует отметить, что такая гибкость языка открывает перед программистами огромные возможности и позволяет писать универсальный код многократного использования.
Давайте рассмотрим, какие именно возможности предоставляют разработчикам динамический SQL и динамический PL/SQL.
Q Выполнение инструкций DDL. С помощью статического SQL в программах PL/SQL можно выполнять только DМL-инструкции. Если же вам понадобится создать таблицу или удалить индекс, воспользуйтесь динамическим SQL.
О Проектирование серверных компонентов web-приложениЙ. Можно разрешить пользователю выбирать столбцы и изменять порядок просмотра данных, то есть выводить или модифицировать данные произвольным образом по запросу.
О Создание универсальной программы синтаксического анализа строк. Такая программа может принимать список значений в виде строки с разделителями и помещать его элементы в коллекцию.
Начиная с Oracle?. 1 поддержка динамического SQL осуществляется с помощью встроенного пакета DBMS_SQL. В Oracle 8i для этого появилась еще одна возможность —встроенный динамический SQL (Native Dynamic SQL, NDS). О пакете DBMS_SQL подробно рассказывается в ряде других книг, а настоящая глава целиком посвящена NDS, который является более удобным и эффективным средством написания и выполнения динамического кода. Кроме того, в отличие от пакета DBMS—5QL, NDS интегрирован в язык PL/SQL и является его составной частью. Но поскольку в некоторых случаях у вас может возникнуть необходимость воспользоваться пакетом DBMS SQL, в конце главы мы сравним оба средства и приведем некоторые рекомендации по их использованию.
Инструкции NDS
Главным достоинством NDS является его простота. В отличие от пакета DBMS_SQL, для работы с которым требуется знание десятка программ и множества правил их использования, NDS представлен в языке PL/SQL единственной новой инструкцией, EXECUTE IMMEDIATE, немедленно выполняющей заданную SQL-инструкцию, а также расширением существующей инструкции OPEN FOR, позволяющей выполнять сложные динамические запросы.
ПРИМЕЧАНИЕ:
Инструкции EXECUTE IMMEDIATE и OPEN FOR доступны в Orade Forms Builder и Oracle Reports Builder только в версиях OracleSI и Oracle91. При работе с другими версиями РСУБД вам придется писать хранимые программы, которые скрывают вызовы ттмх инструкций и могут быть вызваны из клиентского кода PU&L
Инструкция EXECUTE IMMEDIATE
Инструкция EXECUTE IMMEDIATE, используемая для немедленного выполнения заданной S QL-инструкции, имеет следующий синтаксис:
EXECUTE IMMEDIATE CTpOKdJQL
[INTO {переменна^. переменная^... | запись}]
[USING [IN I OUT I IN OUT] аргумент
[.[IN I OUT I IN OUT] аргумент!..^-.
Здесь строка^SQL — символьное выражение, содержащее SQL-инструкцию или блок PL/SQL; переменная — переменная, которой присваивается содержимое поля, возвращаемого запросом; запись — запись, основанная на типе данных, который определяется пользователем или объявляется с помощью атрибута ZROWTYPE, и принимающая всю возвращаемую запросом строку; аргумент — выражение, значение Которого передается SQL-инструкции или блоку PL/SQL, либо идентификатор, являющийся входной и/или выходной переменной для функции или процедуры, вызываемой из блока PL/SQL; INTO — предложение, используемое для однострочных запросов (для каждого возвращаемого запросом столбца в этом предложении Должна быть задана отдельная переменная или же ему должно соответствовать поле записи совместимого типа); USING — предложение, определяющее параметры SQL-инструкции и используемое как в динамическом SQL, так и в динамическом
PL/SQL (способ передачи параметра задается только в PL/SQL, причем по умолчанию для него установлен режим передачи IN).
Инструкция EXECUTE IMMEDIATE может использоваться для выполнения любой SQL-инструкции или блока PL/SQL, за исключением многострочных запросов. Если SQL-строка заканчивается точкой с запятой, она интерпретируется как блок PL/SQL, в ином случае воспринимается как инструкция языка манипулирования данными DML (например, SELECT, INSERT, UPDATE или DELETE) либо языка определения данных DDL (например, CREATE TABLE). Строка может содержать формальные параметры, но с их помощью не могут быть заданы имена объектов схемы, скажем такие, как имена столбцов таблицы.
При выполнении инструкции исполняющее ядро заменяет в SQL-строке формальные параметры (идентификаторы, начинающиеся с двоеточия, например, :salary_value) фактическими значениями параметров подстановки в соответствии с их позицией. В качестве параметров подстановки разрешается задавать выражения, возвращающие числа, даты и строки, но логические выражения задавать нельзя, поскольку тип данных BOOLEAN поддерживается только в PL/SQL. Не разрешается и передача символьного значения NULL - вместо него следует указывать переменную соответствующего типа, содержащую это значение.
Встроенный динамический SQL поддерживает все типы данных SQL. Переменные и параметры инструкции могут быть коллекциями, большими объектами (LOB), экземплярами объектных типов и типа REF. Однако NDS не поддерживает типы данных, специфические для PL/SQL, такие как BOOLEAN, ассоциативные массивы и пользовательские типы записей, В предложении INTO может быть задана запись PL/SQL.
Рассмотрим несколько примеров.
О Вот как создается индекс:
EXECUTE IMMEDIATE 'CREATE INDEX emp.u_l ON employee (last_name)';
Согласитесь, что это достаточно просто.
О Хранимую процедуру, выполняющую любую инструкцию DDL, можно создать так:
CREATE OR REPLACE PROCEDURE execDDL (ddl_string IN VARCHAR2)
IS
BEGIN
EXECUTE IMMEDIATE ddl_str1ng;
END:
После формирования данной процедуры индекс создается следующим образом;
execDDL ('CREATE INDEX emp_u_l ON employee (last_name)'):
О Теперь мы попытаемся получить количество строк в таблице указанной схемы для заданного предложения WHERE:
/* Файл в web: tabcount.sf */
CREATE OR REPLACE FUNCTION tabCount C
tab IN VARCHAR2.
whr IN VARCHAR2 :- NULL.
sch IN VARCHAR2 :- NULL)
RETURN INTEGER
is
retval INTEGER:
BEGIN
EXECUTE IMMEDIATE
'SELECT COUNTS)
FROM ' И NVL (sch. USER) || '.' || tab ||
'WHERE ' И NVL (whr. '1-1')
INTO retval:
RETURN retval:
END;
В дальнейшем нам больше не понадобится писать инструкцию SELECT COUNT(*) ни в SQL* Plus, ни в программах PL/SQL. Для подсчета строк можно ввести следующий блок кода;
BEGIN
IF tabCount C'emp', 'deptno - ' 11 v_dept) >100■.
THEN~
DBMS_OUTPUT.PUT_LINE ('Growing fast!
END IF; 一~
О А вот как производится изменение числового значения в любом столбце любой таблицы (созданная функция возвращает также количество обновленных строк):
/* Файл в web: updnval.sf */
CREATE OR REPLACE FUNCTION updNVal (
tab IN VARCHAR2,
COl IN VARCHAR2, val IN NUMBER, whr IN VARCHAR2 :- NULL. SCh IN VARCHAR2 :- NULL) RETURN INTEGER.
IS
BEGIN
EXECUTE IMMEDIATE'
'UPDATE 1 И NVL (sch. USER) || '.' || tab ||.
'SET ' II col И 1 - :the value
WHERE ' |( NVL (whr, '1-1'j
USING val;
RETURN SQLXROWCDUNT:
ENO:
He вызывает сомнений, что такой маленький объем кода - совсем небольшая плата за гибкость, которую предоставляет эта функция. В приведенном примере показано, как используется параметр подстановки: после синтаксического анализа инструкции UPDATE ядро PL/SQL заменяет в ней формальный параметр :the_value значением переменной val. Обратите внимание) что в этом случае атрибут курсора SQL^ROWCOUNT можно использовать точно так же, как при выполнении статической SQL-инструкции,
Рассмотрим еще один пример применения динамического SQL.
Предположим, что ежедневно в 9 утра должна выполняться определенная хранимая процедура, формирующая храфик деловых встреч сотрудника определенной компании. Структура имени каждой процедуры может быть такой: DAYNAME_set__ schedule. Здесь строка символов DAYNAME идентифицирует день недели. Каждая
процедура имеет четыре одинаковых параметра: входные — идентификационный код сотрудника и время первой встречи, и выходные — фамилия сотрудника и количество встреч за день. Покажем, как составляется такой график:
/* Файл в web: run9am.sp */
CREATE OR REPLACE PROCEDURE run_9am_procedure (
1d_inIN employee.employee_idiTYPE.
hour_in IN INTEGER
) -
IS
v_apptcount INTEGER;
匸nameVARCHAR2 (100);
BEGIN■
.EXECUTE IMMEDIATE 'BEGIN 1
П TO—CHAR (SYSDATE. 'DAY')
'_set_schedule (:id. :hour, :name, :appts); END:1 USING IN idjn. IN hourjn. OUT v_name. OUT v_apptcount;
DBMS.OUTPUT.put_line {■
'Employee '
v_name
II "has '
11 v_apptcount
11 ' appointments on '
II TO_CHAR (SYSDATE)
); '
END;
Как видите, у оператора EXECUTE IMMEDIATE исключительно простой и удобный синтаксис.
Инструкция OPEN FOR
Эта инструкция впервые появилась в Oracle? и предназначалась для обработки переменных-курсоров. Затем ее синтаксис был расширен для поддержки многострочных динамических запросов. Выполнение такого запроса с помощью пакета DBM5—SQL является очень сложной процедурой: приходится производить синтаксический анализ и подстановку, отдельно определять каждый столбец, выполнять инструкцию, выбирать сначала строки, а затем — последовательно значения каждого столбца. Объем программы заметно увеличится.
Для динамического SQL разработчики Oracle сохранили существующий синтаксис инструкции OPEN, но расширили его очень естественным образом:
OPEN {переменндя_^рсор | :хост_переменндя_^урсор} FOR crpoKd_SQL
[USING аргумент Г, аргумент^...];
Здесь переменндя_курсор — слаботипизированная переменная-курсор; :хост_пере- менная_курсор — переменная-курсор, объявленная в хост-среде PL/SQL, например в программе Oracle Call Interface (OCI); строка_SQL — инструкция SELECT, подлежащая динамическому выполнению; USING - такое же предположение, как в операторе EXECUTE IMMEDIATE.
О том, что такое переменная-курсор, говорилось в главе 14. Далее в этой главе мы подробно расскажем об его использовании с NDS.
В следующем примере объявляются тип REF CURSOR и основанная на нем переменная-курсор, а затем с помощью оператора OPEN FOR открывается динамический запрос:
CREATE OR REPLACE PROCEDURE show_parts_inventory (
parts_table IN VARCHAR2.
where in IN VARCHAR2 := NULL)
IS ~
TYPE query_curtype IS REF CURSOR:
dyncur query_curtype;
BEGIN-
OPEN dyncur FOR
'SELECT * FROM ' || parts_table
'WHERE ' ]] NVL (wherejn. Ч -Г);
Затем точно так же, как при использовании явного жестко закодированного курсора, осуществляются выборка строк, закрытие переменной-курсора и считывание значений его атрибутов. В следующих разделах все это будет показано на примерах.
Запросы с переменными-курсорами, возвращающие набор строк
Теперь, когда вы знакомы с синтаксисом оператора OPEN FOR и с переменными- курсорами, рассмотрим более детально, как выполняются динамические запросы, возвращающие несколько строк.
Ядро PL/SQL выполняет оператор OPEN FOR следующим образом:
О связывает переменную-курсор с SQL-инструкцией, заданной в строке запроса;
О вычисляет значения параметров подстановки и заменяет ими формальные параметры в строке запроса;
О выполняет запрос;
О идентифицирует результирующий набор;
О устанавливает курсор на первую строку результирующего набора;
О обнуляет счетчик выбранных строк, возвращаемый атрибутом XROWCOUNT.
Обратите внимание, что параметры подстановки, заданные в предложении USING, вычисляются только при открытии курсора. Это означает, что для передачи тому же динамическому запросу другого набора параметров нужно выполнить новый оператор OPEN FOR.
Для выполнения запроса, возвращающего набор строк, необходимо сделать следующее:
Q объявить тип REF CURSOR (если он еще не объявлен, например, в спецификации пакета) и на основе этого типа объявить переменную-курсор;
Q задав строку запроса и аргументы, открыть переменную-курсор;
Q с помощью оператора FETCH по одной извлечь записи результирующего набора;
О при необходимости проверить значения атрибутов (^FOUND, MNOTFOUND, ZR0W- COUNT, IISOPEN);
О закрыть переменную-курсор с помощью обычного оператора CLOSE.
Далее приведена простая программа, которая отображает значения указанного поля заданной таблицы в строках, отбираемых с помощью предложения WHERE (столбец может содержать числа, даты или строки):
/* Файл в web: showed.sp */
CREATE OR REPLACE PROCEDURE showcol (
tab IN VARCHAR2,
col IN VARCHAR2.
whr IN VARCHAR2 :- NULL)
IS
TYPE cv_type IS REF CURSOR;
cv cv_type:
val VARCHAR2(32767);
BEGIN
/* Формируем динамический за叩ос и открываем курсор */
OPEN cv FOR■
'SELECT ' 11 col 11■
1 FROM '丨I tab 11
'WHERE ' II NVL (whr, '1 - 1'):
LOOP
/* Выбираем следующую строку и прекр訓аех работу, если строк больше нет */
FETCH cv INTO val:
EXIT WHEN CvXNOTFOUND:
/* Выводим данные с заголовком перед первой строкой */
IF cvXROWCOUNT - 1
THEN
DBMS_OUTPUT.PUT_LINE (RPAD ('-', 60.'-'));
DBMS_OUTPUT.PUT_LINE (
'Contents of ' 11.
UPPER (tdb) M UPPER (col));
DBMS_OUTPUT.PUT_LINE CRPAD 60.'-'));
END IF;
DBMS OUTPUT.PUT_LINE (val):
END LOOP; -
CLOSE cv; -- Работа программы завершена, закрываем курсор!
END;
Вот каким будет результат выполнения этой процедуры:
SQL> exec showed Cemp1, 'ename1, 'deptnc^lO']
Contents of EMP.ENAME
CLARK KING MILLER
Столбцы можно даже комбинировать:
BEGIN showed ( 'апр , 'ename 11 ' '-$" 11 sal1, 'conrn IS NOT NULL'):
ЕЮ:
Contents of EMP.ENAME |]'-$'|| SAL
ALLEN-$1600'
WARD-$1250'
MARTIN-S1250
TURNER-$1500
Считывание данных в переменные или записи
Инструкция FETCH в процедуре showcol, приведенной в предыдущем разделе, считывает данные и присваивает их в качестве значений скалярной переменной. Если возвращаемые строки состоят из нескольких значений, можно присваивать их нескольким отдельным переменным:
DECLARE
TYPE cv_type IS REF CURSOR:'
cv cv_type;
mega_bucks company.ceo_compensat1onXTYPE;
achieved_by company.cost_cuttingXTYPE;
BEGIN --
OPEN cv FOR.
'SELECT ceo_compensation, cost_cutting
FROM company
WHERE ' || NVL (whr. '1 - Г);
LOOP
FETCH cv INTO mega_bucks. ach1eved_by;
Однако с длинным списком переменных работать неудобно, не говоря уже о том, что переменные нужно объявить, затем следить за их синхронизацией с SQL-инструкцией и т. д. Встроенный динамический SQL позволяет считывать данные в запись, как показано в следующем примере:
DECLARE
TYPE cv_type IS REF CURSOR:
cv cvjype:
ceojnfo companyKROWTYPE;
BEGIN ~
OPEN cv FOR
'SELECT *
FROM company
WHERE ' 11 NVL (whr. '1 - 1'):
LOOP
FETCH cv INTO ceo Info:
Конечно, инструкция SELECT * приемлема далеко не для всех случаев выборки данных — ведь таблица может содержать огромное количество столбцов, тогда как в коде в определенный момент может понадобиться лишь несколько значений. Поэтому лучше создавать отдельные типы записей, соответствующих разным требованиям. Их удобно поместить в пакет и использовать в разных частях кода приложения. Покажем, как создается такой пакет:
CREATE OR REPLACE PACKAGE company_struc
IS
TYPE dynsql_curtype IS REF CURSOR;
TYPE ceo_1nfo_rt IS RECORD (
mega_bucks company.ceo_compensationXTYPE,.
achieved_by conpany.cost_cutti ngXTYPE);■
END company_struc;
Теперь наш код можно переписать по-другому:
DECLARE
cur company_strue.dynsql_curtype:
rec company_strue.ceo_1nfo_rt:
BEGIN ~_
OPEN cv FOR
'SELECT ceo_conipensation, cost_cutting
FROM company WHERE ' || NVL~(whr. '1=1'):
LOOP
FETCH cv INTO rec;
Предложение USING в инструкции OPEN FOR
В инструкции OPEN FOR, как и в EXECUTE IMMEDIATE, можно задавать параметры SQL- инструкции. Однако учтите, что при формировании запросов допускается применение только параметров типа IN. Использование запросов с параметрами позволяет значительно сократить количество готовых инструкций, кэшируемых в SGA, и повысить вероятность того, что в момент обращения к инструкции таковая все еще будет находиться в глобальной системной области. Кроме того, применение таких запросов упрощает решение многих задач, благодаря чему оптимизируется программный код и облегчается его сопровождение.
Вернемся к процедуре showcol, в которой можно задать произвольное предложение WHERE, Конкретизируем наши требования к запросу: необходимо вывести (или обработать) информацию тех строк таблицы, которые содержат значения даты из определенного диапазона. Иными словами, нам нужно иметь возможность выполнить такой запрос:
SELECT ename'
FROM emp
WHERE hiredate BETWEEN x AND y:
или же такой:
SELECT flavour
FROM favarltles
WHERE preference_period BETWEEN x AND y;
При этом очень важно, чтобы в условии WHERE не учитывался компонент времени. Вот объявление процедуры, выполняющей эту задачу:
/* Файл в web: showdtcol.sp */
PROCEDURE showed (
tab IN VARCHAR2.
col IN VARCHAR2.
dtcol IN VARCHAR2.
dtl IN CATE.
dt2 IN MTE :- NULL)
Ниже в инструкции OPEN FOR задается запрос с двумя формальными параметрами и соответствующее ему предложение USING:
OPEN cv FOR
'SELECT ' И col И
,FROM ' 11 tab 11
'WHERE ' 11 dtcol (I'
'BETWEEN TRUNC (:startdt)
AND TRUNC (:encldtr
USING dtl. WL (dt2. dtl+1;;
Параметры в предложении USING определены таким образом, чтобы в том случае, если пользователь не задаст конечную дату, предложение WHERE вернуло строки с датой, указанной в параметре dtl. Остальная часть процедуры showcol не изменилась, если не считать небольших модификаций, связанных с выводом заголовка столбца.
Теперь при вызове новой версии этой процедуры потребуем вывода фамилий сотрудников, принятых на работу в 1982 году:
BEGIN
showcol ('emp', 'ename', 'hiredate'. 'Ol-jan-82', '31-dec-82');.
END:
Contents of EMP.ENAME for HIREDATE between 01-JAN-82 and 31-DEC-82
MILLER
Универсальная процедура для выполнения Опросов с предложением GROUP BY
Вам наверняка много раз приходилось писать запросы такого вида:
二ELECT столбцы, COUNT(*)
FROM таблица 訳WP BY столбцы-.
Существует разновидность данной инструкции с предложением HAVING, позво' хяющая отобрать итоговую информацию по некоторому критерию. Это очень рас- пюстраненный вид запросов с группировкой. С помощью NDS достаточно про-то написать код формирования и выполнения такого запроса для любой таблицы, * также для любого столбца (или нескольких столбцов).
Приведем заголовок этой процедуры:
/* Файл в web: countby.sp */
PROCEDURE countBy (
tab IN VARCHAR2,
col IN VARCHAR2, at!east IN INTEGER := fRJLL. sch IN VARCHAR2 := NULL, maxlen IN INTEGER :- 30);
Здесь tab — имя таблицы; col — имя столбца; atleast — имя схемы (по умолчанию используется схема USER); maxlen — параметр, используемый при форматировании выводимых данных. Ненулевое значение параметра sch указывает, что в инструкцию SELECT включается предложение HAVING COUNT(*) с условием «больше данного значения».
Полный текст этой процедуры находится в файле countby.sp на узле издательства О'КеШу. Ниже приведена основная часть ее кода, за исключением строк форматирования (строки заголовка и т. п.):
IS
ТУРЕ cv_type IS REF CURSOR;
cvcv_type;
sql_string VARCHAR2 (32767)
■ 'SELECT ' И col И COUNTC*)
FROM ' 11 NVL (sch, USER} |||| tab ||
'GROUP BY ' 11'
v_val VARCHAR2 (32767);
v_count INTEGER;
BEGIN
IF atleast IS NOT NULL
THEN
sql_string := sql_string || ' HAVING COUNTC*) >- ' || atleast;
END IF?~
OPEN cv FOR sq】_stMng;
LOOP•
FETCH cv INTO v_val. v_count; EXIT WHEN cvXNOTFOUNO;" DBMS_OJTPUT.put_line (RPAD (v val, MAXLEN) || ' ' || v_count):
END LOOP;~-
CLOSE cv;
END:
Подготовить несколько таких универсальных утилит достаточно просто, и их код получается довольно компактным. Для этой работы нужно только тщательно проанализировать процесс составления SQL-инструкции.
Универсальный пакет для выполнения запросов с предложением GROUP BY
Для проведения тестирования достаточно получить минимальный объем выводимой информации) но ее, как правило, бывает недостаточно для дальнейшей
обработки считываемых данных. Попробуем модифицировать процедуру countBy из предыдущего раздела таким образом, чтобы она сохраняла результаты выполнения динамического запроса в ассоциативном массиве для последующей обработки.
Далее показана спецификация используемого для этой цели пакета:
/* файл в web: countby.pkg */
CREATE OR REPLACE PACKAGE grp'
IS
TYPE results_rt IS RECORD C
val VARCH欣2(4000).
countby INTEGER);
TYPE results_tt IS TABLE OF resu1ts_rt'
INDEX BY BINARYJNTEGER: "
FUNCTION countBy (
tab IN VARCHAR2.
col IN VARCHAR2.
atleast IN INTEGER :- NULL.
sch IN VARCHAR2 严 NULL.
maxien IN INTEGER :- 30)
RETURN results_tt:
END grp;
Код функции countBy практически такой же, как код одноименной процедуры. Единственное отличие заключается в том, что теперь результаты запроса сохраняются в записи, которая затем добавляется в ассоциативный массив. Оба эти изменения внесены в цикл выборки строк:
LOOP
FETCH cv INTO rec:
EXIT WHEN cv^NOTFOUND:、
retval(cv^ROWCOUNT) := rec;.
END LOOP;■
Подготовив такой пакет, можно использовать его в собственном коде:
/* Файл в web: countby.tst */.
DECLARE
results grp.results_tt;
indx PLS_INTEGER:"
minrow PLS_INTEGER;
maxrow PLSJNTEGER;
BEGIN
results :^ grp.countby C'enployee', 'departmentjd');
/* Находим минимальное и максимальное значения */'
indx :- results.FIRST;.
LOOP
EXIT WHEN indx IS NULL;
IF mlnrow IS NULL OR
minrow > results(1ndx).countby
THEN minrow := indx;
END IF:
IF maxrow IS NULL OR maxrow < results(indx).countby
THEN maxrow :- indx:
END IF;
DBMS_OUTPUT.PUT_LINE (
resultsdndxT.val || ' - ' || results(lndx).count by):
/* Выполняем другие операции обработки... */
/* Переходим к следующему значение */
indx := results.NEXT(indx):
END LOOP:
END:
Передача значений параметров
Вы уже видели несколько примеров использования динамических SQL-инструкций с параметрами. Теперь давайте рассмотрим общие правила и особые случаи передачи значений этих параметров динамическому SQL-запросу.
Подстановка и конкатенация
Существуют два способа передачи значений в SQL-инструкцию: путем подстановки значений параметров и с помощью конкатенации. Примеры использования обоих способов показаны ниже.
Подстановка параметров:
EXECUTE IMMEDIATE
'UPDATE ' И tab 'SET sal = :new sal' USING v_sal:■
Конкатенация:
EXECUTE IMMEDIATE
'UPDATE ' I I tab
'SET sal - ' I I v sal:
Подстановка требует использования формальных параметров и предложения USING, а при конкатенации значения вставляются прямо в SQL-строку. Попробуем разобраться, в каких случаях какой подход лучше. Хотя подстановка значений и имеет некоторые ограничения, описанные в следующем разделе, мы рекомендуем по возможности использовать именно этот способ.
Во-первых, подстановка параметров ускоряет работу приложения. SQL-инструкция с параметрами содержит только формальные параметры (маркеры), отмечающие местоположение значений, а не сами значения. Поэтому в одну инструкцию можно подставлять разные значения параметров, не меняя ее саму. И при этом приложение сможет пользоваться подготовленными курсорами, кэшируемыми в системной глобальной области базы данных (SGA).
Во-вторых, метод подстановки заметно упрощает процесс создания и изменения дииамических SQL-инструкций. Задавая в такой инструкции формальные параметры, не нужно преобразовывать их в строковый тип данных — ядро NDS само сделает все, что нужно. А вот в случае конкатенации приходится писать очень сложные строковые выражения, потенциально являющиеся источниками ошибок, с множеством одинарных кавычек, вызовами функций TO_DATE и TO_CHAR и т. д. Сравните, например, следующие инструкции EXECUTE IMMEDIATE, выполняющие одну и ту же S QL-инструкцию:
/* Использование параметров подстановки*/
EXECUTE IMMEDIATE
'UPDATE employee SET salary = :val
WHERE hirjddte BETWEEN :Iodate ANO :h1date'
USING v_start. v_end;
/* Использование конкатенации */
EXECUTE IMMEDIATE.■
'UPDATE employee SET salary =■ ' 11 val_in 11
'WHERE hire_date BETWEEN 1 |j ~■
* TO_OATE■(',, И TC_CHAR (v_start) || "T ||
'AND ' И
'TO_DATE「’’II 丁口—CHAR (v_end) ||
Теперь понятно, почему лучше использовать подстановку параметров, но это возможно не во всех случаях. Ограничения иа использование данного способа передачи значений рассмотрены в следующем разделе.
Ограничения на использование метода подстановки
В качестве параметров подстановки SQL-инструкций можно использовать только значения данных (литералов, переменных, выражений), но не имена объектов схемы базы данных (таблиц, столбцов и т. п.) Для задания этих элементов в динамической SQL-инструкции должна использоваться конкатенация.
Предположим, что нам нужна процедура, очищающая с помощью инструкции TRUNCATE заданную таблицу или представление. Попытка ее реализации может быть, например, такой:
CREATE OR REPLACE PROCEDURE truncobj (
nm IN VARCHAR2.
tp IN VARCHAR2 := 'TABLE'.
sch IN VARCHAR2 NULL)
IS
BEGIN
EXECUTE IMMEDIATE'
'TRUNCATE :trunc_type :obj_name'
USING tp, NVL (Wch, USER)~|| || nm;
END;
На первый взгляд этот код правильный, однако, попытавшись его выполнить, мы получим сообщение об ошибке:
ORA-03290: invalid truncate corrmand - missing CLUSTER ar TA8LE keyword
Упростим инструкцию, осуществляющую очистку таблицы:
EXECUTE IWEDIATE 'TRUNCATE TABLE :obj_name' USING nm;
и все равно получим сообщение об ошибке:■
0RA-O09O3: invalid table name
Почему же в NDS (впрочем, как и в пакете DBMS_SQL) имеется такое ограничение? При передаче строки оператору EXECUTE IMMEDIATE исполняющее ядро должно прежде всего выполнить синтаксический анализ инструкции, чтобы убедиться в ее правильном определении. PL/SQL может понять, что следующая инструкция задана правильно, даже не зная значения параметра :xyz:
'UPDATE emp SET sal - :xyz'
Но корректность инструкции, приведенной ниже, он определить не сможет:
'UPDATE emp SET :col_name = :xyz'
Ведь если вместо формального параметра :81」ше будет подставлено значение, не являющееся именем столбца, получится синтаксически неверная инструкция. Поэтому в данном случае нужно использовать конкатенацию:
CREATE OR REPLACE PROCEDURE truncobj (
nm IN VARCHAR2,
tp IN VARCHAR2 :- 'TABLE',
sch IN VARCHAR2 :- NULL)
IS
BEGIN.
EXECUTE IWEDIATE
'TRUNCATE ' II tp И ' ' И NVL (sch, USER) |||| nm:
END;
Режимы использования параметров
При передаче значений параметров S QL-инструкции можно использовать один из трех режимов: IN (только чтение, задан по умолчанию), DUT (только запись) и IN OUT (чтение и запись). Когда выполняется динамический запрос, все параметры SQL-инструкции, за исключением параметра в предложении RETURNING, должны передаваться в режиме IN:
CREATE OR REPLACE PROCEDURE wrong_incentive (
compmny_in IN INTEGER.
newj ayoffs IN NUMBER
IS
sql_string VARCHARZ(ZOOO);
sal_after_layoffs NUMBER: ■
BEGIN ~~
sql_string :=
'UPDATE ceo_compensation
SET salary - salary + ID * :1ayoffs
WHERE companyjd » company
RETURNING salary INTO :newsal';
EXECUTE IWEDIATE sql_str1ng
USING new_layoffs, company] n, OUT sal_after_layoffs: dbms_ojtput.put_line (
'CEO compensation after latest round of layoffs $' || sal_after_layoffs): END;
Параметры подстановки SQL-инструкции, передаваемые в режимах OUT и IN OUT, используются в первую очередь при выполнении динамического PL/SQL. В этом случае режимы передачи параметров могут быть такими же, как у обыкновенной программы PL/SQL.
Ниже приведено несколько общих рекомендаций, касающихся использования предложения USING при вьшолнении динамического PL/SQL
О В качестве параметра подстановки, передаваемого в режиме IN, может быть задан любой элемент соответствующего типа: литеральное значение, именованная константа, переменная или сложное выражение. Такой элемент сначала вычисляется, а затем передается в динамический блок PL/SQL.
О Для значения параметра динамической инструкции, задаваемого в режиме OUT или IN OUT, следует объявить переменную.
О Значения можно подставлять только вместо тех параметров динамического блока PL/SQL, типы данных которых подлаживаются SQL. Например, если параметр процедуры имеет тип BOOLEAN, его значение нельзя задать или считать с помощью предложения USING.
Рассмотрим несколько примеров. Вот заголовок процедуры с параметрами IN, OUT и IN OUT:
PROCEDURE analyze_new_technology C
tech_nane [N VARCHAR2.
analysis /ear IN INTEGER.
number_of_adherents IN OUT NUMBER.
projected_revenue OUT NUMBER,
Поскольку процедура имеет четыре параметра, в предложении USING также должно быть указано четыре элемента. Для первых двух параметров, передаваемых в режиме IN, следует задать литеральные значения или выражения, а следующие Два элемента должны быть именами переменных, так как для них заданы режимы 口UT к IN OUT. Ниже, как вы видите, дан пример динамического вызова этой процедуры:
DECLARE
devoted_followers NUMBER;
est_revenue NUMBER:
BEGIN ~
EXECUTE IMMEDIATE
'BEGIN
analyze_new_technology C:pl, :p2, :p3, :p4); END:'
USING 'Java'. 2002. devoted_followers. est_revenue:
End;一一
Дублирование формальных параметров
При выполнении динамической S QL-инструкции связь между формальными и фактическими параметрами устанавливается в соответствии с их позициями. Однако интерпретация одноименных параметров зависит от того, какой код, SQL или PL/SQL, выполняется с помощью оператора EXECUTE IMMEDIATE.
□ При выполнении динамической SQL-инструкции (DML- или DDL-строки, не оканчивающейся точкой с запятой) параметр подстановки нужно задать для каждого формального параметра, даже если их имена повторяются.
О Когда выполняется динамический блок PL/SQL (строки, оканчивающейся точкой с запятой), нужно указать параметр подстановки для каждого уникального формального параметра.
Далее приведен пример динамической SQL-инструкции с повторяющимися формальными параметрами. Особое внимание обратите на повторяющийся параметр подстановки val_in в предложении USING:
CREATE OR REPLACE PROCEDURE updnumval (
coljnINVARCHAR2,
start_1nINDATE.
end_1nINDATE,
val~1nINNUMBER)
IS
dml^str VARCHAR2(32767).-
'UPDATE emp SET '|| coljn ||' - :val
WHERE hi redate BETWEEN :Iodate AND :hi date
AND ;val IS NOT NULL';
BEGIN
EXECUTE IMMEDIATE dml_str
USING valJn, start_1n, end_1n, val 1n:
END: ~~-
А вот динамический блок PL/SQL с повторяющимися формальными параметрами —для него в предложении USING параметр val _1 п задан только один раз:
CREATE OR REPLACE PROCEDURE updnurnval (
coljnINVARCHAR2.
startjnINDATE,
end_1n INDATE,
val inIN置MBER)
IS -
dml_str VARCHAR2(32767):=
'BEGIN
UPDATE emp SET '11 col_in 11 ' - :val
WHERE hiredate BETWEEN ;Iodate AND :hl date
AND :val IS NOT NULL;
END;';
BEGIN
EXECUTE IMMEDIATE dml_str'
USING valjn. startjn, endjn;
END;
Передача значения NULL
При передаче значения NULL в качестве параметра подстановки, например, как я операторе
EXECUTE immediate
'UPDATE employee SET salary - :newsal
WHERE hire_date IS NULL'
USING NULL?
bh получите сообщение об ошибке
ORA-00457: in USING clause, expressions have to be of SQL types
которое означает, что NULL не имеет типа данных и поэтому не является значением одного из типов данных SQL.
Так каким же способом можно передать в динамически выполняемый код значение NULL? Для этого существует две возможности.
Во-первых, можно скрыть данное значение в переменной, для чего проще всего использовать неинициализированную перемекную подходящего типа:
DECLARE
/* Исходным значением переменной по умолчанию является NULL */
no_sa1ary_when_fired NUMBER;
BEGIN" ~~
EXECUTE IMMEDIATE
'UPDATE employee SET salary - :newsal
WHERE hire_date IS NULL'
USING no_salary_when fired;
END:-~
Во-вторых, с помощью функции преобразования типов явно можно преобразовать значение NULL в типизированное значение:
BEGIN
EXECUTE I刪EDIATE
'UPDATE employee SET salary " :newsal
WHERE hire_date IS NULL'
USING TO_NUMBER (NULL);
END;
Работа с объектами и коллекциями
Одним из важнейших преимуществ динамических SQL-запросов по сравнению с пакетом DBMS_SQL является поддержка типов данных, которые были введены в Oracle? и последующих версиях РСУБД, в частности объектов и коллекций.
Предположим, что требуется спроектировать систему администрирования корпорации HealthS.Com, работающей в сфере здравоохранения. Для минимизации затрат база данных должна быть распределенной, поэтому на каждом региональ- Ном сервере должны храниться таблицы с информацией о клиентах специализированного госпиталя Healths. Сот.
Начнем мы с определения объектного типа (person) и типа VARRAY (preexl - sting_conditions):
CREATE TYPE person AS OBJECK
name VARCHAR2 (50), dob DATE, income NUMBER);
CREATE TYPE preexist1ng_conditions IS TABLE OF VARCHAR2 (25):
Далее можно создать пакет для управления важнейшей информацией — данными, необходимыми для контроля доходов корпорации HealthS.Com, Приведем его спецификацию:
/* Файл в web: healths.pkg */
CREATE OR REPLACE PACKAGE hedlth$
AS
PROCEDURE setupjnewjospitdl (ho5p_nare IN VARCHAR2);
PROCEDURE add_prof1t_source (
hosp_name Tn VARCHAR2.
pers IN person.
cond IN preexisting__conditions
PROCEDURE weed_out_poor_and_sick (
hosp_name VARCHAR2,
mi nJncome IN NUMBER := 100000,
max_preexist_cond IN INTEGER :- 1
);"
PROCEDURE show_profit_centers (hosp_name VARCHAR2):
END healths;--
Пакет позволяет создать новую таблицу, которая будет хранить информацию о госпитале. Вот как данная задача может быть реализована в теле пакета;
FUNCTION tabname (hosp_nare IN VARCHAR2) RETURN VARCHAR2 IS
BEGIN~
RETURN hosp_name || '_profit_center';
END;~~~
PROCEDURE setup_new_hosp1ta1 (hosp_name IN VARCHAR2)
IS- 一~
BEGIN
ЕХЕС1ЯЕ IMMEDIATE 'CREATE TABLE ' || tabname (hosp_name) ||
pers Person,•
cond preexisting_condit1ons).
NESTED TABLE cond STORE AS cond_st':
END;"
Кроме того, данный пакет предоставляет возможность добавлять в таблицу записи о потенциальных клиентах, включая сведения о состоянии их здоровья. Ниже показано, как в теле пакета реализована эта задача:
PROCEDURE add_profit_source (
hosp_name IN VARCHAR2,
pers IN person,
cond IN preexisting__cond1fions
IS
BEGIN
EXECUTE IMMEDIATE 'INSERT Ш0 ' || tabname Chosp_name) || 'VALUES (:revenue_generator. :revenuej nhi bi tors)' USING pens, cond:
END;
Выполняемые объектами и коллекциями функции совершенно очевидны. В данном случае можно использовать и скалярные значения, но ни синтаксис, ни код от этого не изменятся.
Пакет позволяет удалить из списка всех пациентов, страдающих многими заболеваниями или имеющих низкие доходы, что сведет к минимуму финансовый риск корпорации. Эта программа самая сложная:
PROCEDURE weed_outj)oor_and_s1ck С
hosp_name VARCHAR2,
mln_inconie IN NUMBER :- 100000, max_preexist_cond IN INTEGER :- 1 ) 一
IS
cv refcurtyp: human person;
known_bugs preexi sti ng_conditlons:
v_table VARCHAR2 (30) :- tabname (hosp_naffle):
v_rowid ROWID;
BEGIN
/* Находим все записи о больных, у которых количество заболеваний npPBbiuidPT допустимое значение или поход ниме определенного уровня, и удаляем их из таблицы. */
OPEN cv FOR
'SELECT ROWID, pens, cond
FROM ' I I.
v_table II 'alias
WHERE (SELECT COUNTf*) FROM TABLE Call as.cond)) > :preexist OR al 1 as,pers.income < iincome' USING max_preexist_cond. min_1ncome;
LOOP
FETCH cv INTO v_rowid, human, known_bugs:
EXIT WHEN cvXNOTFOUND;~
EXECUTE IMMEDIATE
'DELETE FROM ' || v_tab1e ||
WHERE ROWID = irld'
USING v rowid;
END LOOP: -
CLOSE cv:
END;'
ПРИМЕЧАНИЕ
Мы извлекаем значение ROWID каждой строки, чтобы потом ее легче было идентифицировать при удалении. Гораздо удобнее было бы добавить в запрос предложение FOR UPDATE, а затем использовать в инструкции DELETE предложение «WHERE CURRENT OF cv», однако это невозможно, поскольку для ссылки на переменную-курсор в динамической SQL-инструкции необходимо, чтобы эта переменная была глобальной. А объявить ее в пакете нельзя, потому что у нее нет постоянного значения. Подробнее об этом рассказано далее, в разделе «Динамический PI-/SQL».
Разработка приложений
с использованием NDS
Вы уже хорошо понимаете, как работает динамический SQL в PL/SQL. Теперь мы рассмотрим некоторые вопросы, касающиеся разработки приложений с использованием NDS.
Совместное применение программ
с правами вызывающего
Автор на собственном опыте убедился в удобстве использования NDS и разработал на его основе множество универсальных функций и процедур, осуществляющих следующие задачи:
О выполнение произвольных DDL-инструкций;
О подсчет количества строк в любой таблице;
О подсчет количества строк, группируемых в соответствии с содержимым заданного столбца.
Посчитав эти утилиты достаточно полезными, он откомпилировал их для схемы COMMON и предоставил права на их выполнение EXECUTE как PUBLIC.
Однако осталась нерешенной одна важная проблема. Если не используется модель прав вызывающего, описанная в главе 20, пользователь Sandra регистрирующийся в схеме SANDRA и выполняющий команду
5QL> exec COMMON.execDDL ('create table Up (x date)1);
создает таблицу в схеме COMMON. Применение названной модели означает, что создаваемые пользователем хранимые программы будут выполняться от имени и с правами вызывающей схемы, а не той, для которой они определены (как происходит по умолчанию в Огас1е8.1 и что было единственно возможным до появления данной версии РСУБД).
К счастью, воспользоваться этой новой возможностью очень просто. Ниже приведена версия процедуры execDDL, выполняющей любую DDL-инструкцию, но всегда в вызывающей схеме:
CREATE OR REPLACE PROCEDURE execDDL (ddl_string IN VARCHAR2)
AUTHID CURRENT USER IS~
BEGIN~
EXECUTE IMMEDIATE dd1_5tring;
END;
Мы советуем включать предложение AUTHID CURRENT_USER во все программы, выполняющие динамические SQL-инструкции, и в частности те, которые вы пла- нируете использовать совместно с другими разработчиками.
Обработка ошибок
Любое надежное приложение должно осуществлять перехват и обработку ошибок. Особенно важно их обнаружить и исправить при выполнении инструкций динамического SQL. Возможно, вам придется объединять в запросе список полей со списком таблиц и с предложением WHERE, изменяющимся при каждом выполнении инструкции. Все эти элементы необходимо разделять запятыми, операторами AND, OR и т. п. А что произойдет, если вы где-то допустите ошибку? Oracle, конечно же, выдаст сообщение о ней. Обычно такая информация помогает выявить проблему, но часто оказывается недостаточной. Представим, что вы проектируете очень сложное приложение, в коде которого используются динамические SQL-запросы. При этом вы настолько хорошо освоили динамический SQL, что вам ничего не стоит сразу написать любой оператор EXECUTE IMMEDIATE или OPEN FOR. И вот после программирования логики программы и вставки стандартного кода обработки исключений, выводящего сообщение об ошибках, вы хотите проверить результат, для чего пишете сценарий тестирования, выполняющий весь код, и помещаете его в файл testall.sql (который можно найти на web-узле издательства O'Reilly). Затем, испытывая понятное волнение, запускаете тест:
SQL> 酰estall
и видите следующее:
ORA-00942: table or view does not exist
DRA-00904; invalid column name
DRA-00921: unexpected end of SQL command
DRA-00936: missing expression
Возникает вопрос: что же делать с этим перечнем ошибок и как понять, к какой инструкции относится каждая из них?
Рекомендуем заранее принять следующие меры, чтобы не тратить массу времени на отладку написанных динамических инструкций:
О обязательно включать в программы, выполняющие операторы EXECUTE IMMEDIATE и OPEN FOR, секцию обработки исключений;
О в каждом обработчике записывать в журнал или выводить на экран сообщение об ошибке и S QL-инструкцию;
Q для наблюдения за формированием и выполнением динамического кода поместить перед каждой инструкцией операторы трассировки.
Чтобы показать, как эти рекомендации можно выполнить в конкретной программе, модифицируем процедуру execDDL. Вот ее первоначальный текст:
CREATE OR REPLACE PROCEDURE execDDL (ddl_str1ng IN VARCHAR2)
AUTHID CURRENT_USER IS-
BEGIN"
EXECUTE IMMEDIATE ddl_str-ing:
END:~
Теперь добавим в нее секцию обработки исключений:
/* Файл в web: execddl.sp */
CREATE OR REPLACE PROCEDURE execDDL Cddl_str-ing IN VARCHAR2)
AUTHID CURRENT一USER IS
BEGIN
EXECUTE IMMEDIATE ddl_str1ng;
EXCEPTION
WHEN OTHERS
THEN
DBMS OUTPUT.PUT_LINE ('Dynamic SQL Failure; ' || SQLERRM);
DBMS 二OUTPUT.PUT_LINE (
' on statement: "' ] | ddl_str1 ng ))"");
RAISE;-
END;
Если, задав инструкцию с неверным синтаксисом, вы попытаетесь создать с ее помощью таблицу, то получите следующее сообщение:
SQL> exec execddl ('create table x')
Dynamic SOL Failure: 0RA-00906: missing left parenthesis on statement: "create table x"
Это простой пример, в реальном приложении вам, конечно, потребуется нечто более сложное, чем пакет DBMS_OUTPUT.
ПРИМЕЧАНИЕ:
Если динамический запрос, выполняемый средствамм DBM5_5QL, не пройдет синтаксический анализ и вы явно не закроете курсор в разделе исключений, данный курсор останется открытым. После ряда таких операций может быть сгенерирована ошибка «ORA-01000: maximum open cursors exceeded». При использовании NDS этого не произойдет, так как переменные-курсоры, объявленные в локальном блоке, автоматк!чески закрываются при выходе из этого блока, а занимаемая ими память освобождается.
Процедура execDDL может выполнять не только DDL-инструкции. Она подходит для выполнения любой строки кода SQL, не требующей применения предложения USING или INTO. Для этих целей предлагается применять и программу, которая может и должна использоваться вместо оператора EXECUTE IMMEDIATE, поскольку в нее встроен необходимый код обработки ошибок. Такая процедура находится в пакете ndsutil (см. ниже раздел <Пакет NDS Utility»).
Подобную программу можно создать и для оператора OPEN FOR — опять же, только для ситуаций, в которых нетрудно обойтись без предложения USING. Поскольку оператор OPEN FDR устанавливает значение курсора, его, конечно, следует реализовать как функцию, возвращающую слабый тип REF CURSOR:
PACKAGE ndsutil IS
TYPE cvjype IS REF CURSOR:
FUNCTION openFor (sql_strwg IN VARCHAR2) RETURN cv_type: END;
Пакет NDS Utility, который можно найти на узле O'Reilly в файле ndsutil.pkg, содержит полную реализацию функции openFor, очень похожей на приведенную выше процедуру execDDL.
Динамический PL/SQL
Динамический PL/SQL открывает перед программистом ряд интереснейших возможностей. Во время выполнения приложения с помощью NDS можно;
О создавать программу, в том числе пакет, содержащий глобальные структуры данных;
О получать и модифицировать значения глобальных переменных по их именам;
О вызывать функции и процедуры, имена которых не известны во время компиляции.
Автор использовал эту технологию при разработке очень мощных генераторов кода, которые динамически формируют вычислительные блоки и множество других компонентов программ. Динамический PL/SQL обеспечивает высокий уровень гибкости, но реализовать предоставляемые им возможности одновременно и сложно, и невероятно интересно.
Ниже приведено несколько правил и советов, которые пригодятся вам при работе с динамическими блоками PL/SQL.
О Динамическая строка должна быть допустимым блоком PL/SQL, начинаться с ключевого слова DECLARE или BEGIN, а завершаться оператором END и точкой с запятой. (Строка, заканчивающаяся по-другому, не считается кодом PL/SQL.)
О В динамическом блоке доступны только глобальные элементы PL/SQL (функции, процедуры и другие элементы, объявленные в спецификации пакета). Динамические блоки PL/SQL выполняются вне локального внешнего блока.
О Ошибки, возникающие в динамическом блоке PL/SQL, могут быть перехвачены и обработаны локальным блоком, в котором с помощью оператора EXECUTE IMMEDIATE выполнялись динамические инструкции SQL.
Рассмотрим данные правила подробнее. Сначала напишем маленькую утилиту для выполнения PL/SQL-кода:
/* ®айл в web: dynplsql.sp */'
CREATE OR REPLACE PROCEDURE C(ynPL5QL Cblk IN VARCHAR2) IS
BEGIN
EXECUTE IMMEDIATE
'BEGIN ' И RTRIM (blk. ':') || END:':
END;
При реализации приведенной программы учитывался ряд правил, относящихся к выполнению кода PL/SQL. Заключение полученной процедурой строки между операторами BEGIN... END гарантирует, что она будет выполнена как допустимый блок PL/SQL. Например, следующая команда динамически выполняет процедуру calc_totals;
SQL> exec dynPLSQL('calc_totals'):
Теперь с помощью этой программы определим, на какие структуры данных можно ссылаться из динамического блока PL/SQL. В следующем анонимном
блоке динамический PL/SQL используется для присвоения значения 5 локальной переменной num:
«dynamic»
DECLARE
num NUMBER:
BEGIN
dynPLSQL ('num :- 5'):
END;
Приведенная строка выполняется в собственном блоке BEGIN.. .END, который кажется вложенным в блок dynamic. Однако при выполнении этого сценария мы получим следующие сообщения об ошибках:
PLS-00201: Identifier 'NUM' must be declared
ORA-06512: at "SCOTT.DYNPLSQL", line 4
Ядро PL/SQL не может разрешить ссылку на переменную с именем num, причем такое сообщение об ошибке появится даже в том случае, если мы уточним имя переменной именем блока:
«dynaml с»
DECLARE
num NUMBER:
BEGIN
/* Вызывает ошибку PLS-00302! */
dynPLSOL ('(dynamic.num :- 5');
ENO:
Предположим, что переменная num определена в пакете dynamic:
CREATE OR REPLACE PACKAGE dynamic•'
IS
num NUMBER: ENO;
Теперь динамическое присваивание значения этой новой переменной будет выполнено успешно:
BEGIN
(lynPLSQL('dynamlс.num5');
END:
Эти два фрагмента кода различаются между собой тем, что в первом случае переменная объявлялась локально в анонимном блоке PL/SQL, а во втором она была объявлена в пакете как общая глобальная переменная..
Получается, что динамически составленный и выполняемый блок PL/SQL не считается вложенным, он выполняется как процедура или функция, вызываемая из текущего блока. Поэтому любые переменные, локальные для текущего или внешнего блока, в динамическом блоке PL/SQL не доступны, из него можно ссылаться только на глобально определенные программы и структуры данных. Это отдельные функции и процедуры, а также структуры данных, объявляемые в спецификации пакета.
К счастью, несмотря на это, динамический блок выполняется в контексте вызывающего блока. Это означает, что если в динамическом блоке инициируется
исключение, оно перехватывается обработчиком исключений вызывающего блока. Поэтому, если выполнить в SQL*Plus анонимный блок
BEGIN
dynPLSOL ('undefined.packagevar l ''abc'''):
EXCEPTION
WHEN OTHERS THEN D0MS_OUTPUT.PUT_LINE (SOLCOOE);
END;
исключение будет обработано.
ПРИМЕЧАНИЕ Присваивание в этом анонимном блоке представляет собой пример использования косвенной ссылки — мы не ссылаемся на переменную явно, а задаем ее имя. В среде разработки Oracle Forms Builder (ранее называвшейся SQL*Forms или Oracle Forms) механизм косвенных ссылок реализован в программах NAME_IN и COPY. Он позволяет разработчикам про「раммкровать лотку, совместно используемую всеми формами приложения. PLJSQL не поддерживает косвенных ссылок, но они могут быть реализованы с помощью динамического PL/SQL. Пример такой реализации вы найдете в файле dynvar.pkg на узле ОНеШу.
В следующих разделах приведено несколько примеров использования динамического PL/SQL.
Пример сокращения кода
Приведем пример из реальной жизни. Разработчики одной из страховых компаний Чикаго, где автор работает консультантом, столкнулись с серьезной проблемой: объем кода программы, которая требовала постоянной модернизации, увеличился настолько, что возникли трудности с ее компиляцией. Программа выглядела следующим образом:
CREATE OR REPLACE PROCEDURE processjine (line IN INTEGER)
IS".
BEGIN'
IF line - 1 THEN processjlnel;
ELSIF line - 2 THEN processjine?:'
ELSIF line 工 514 THEN process line5】4;
ELSIF line = 2057 THEN processJ1ne2057:
END IF;-
END:
Здесь значение в поле 1 ine каждой строки таблицы задает одно из условий страхования, разработанных компанией для дгктижения своей главной цели - минимизации выплат по страховкам. Каждому значению соответствовала отдельная программа process_11ne, в которой выполнялись необходимые расчеты. Поскольку количество условий постоянно увеличивалось, программа становилась все больше и больше. Это типичный пример немасштабируемого приложения.
В подобных случаях программист должен найти повторяющийся программный код и, если таковой будет выявлен, на его основе создать программу для многократного использования или конструкцию динамического PL/SQ.L.
Решая данную задачу, автор воспользовался пакетом DBMS_SQL, но гораздо больше подходит для этих целей динамический PL/SQL
CREATE OR REPLACE PROCEDURE process_11ne (line IN INTEGER)
IS-
BEGIN
EXECUTE IWEDIATE
'BEGIN processjine' 11 line ||': END;';
END;-
Теперь тысячи строк кода превратились в один исполняемый оператор! Конечно, в большинстве случаев идентифицировать шаблон и реализовать его в виде динамического PL/SQL очень непросто, но получаемый результат всегда стоит затраченных усилий.
Универсальная функция для вычислений
Представим, что мы создали приложение с графическим интерфейсом, предназначенное для выполнения математических расчетов. Пользователю достаточно выбрать формулу, задать аргументы — и результат готов. Приложение поддерживает несколько десятков разных формул, принимающих от одного до пяти аргументов, и каждая из них возвращает единственное значение.
Для всех формул можно было бы создать по отдельному окну. Однако это решение не только трудоемкое, но и неэффективное с точки зрения расширения приложения — ведь если вам понадобится добавить новую формулу, придется проектировать новое окно. Видимо, лучше разработать такой код, чтобы пользователь сам мог добавлять окно, когда ему потребуется новая формула.
Чтобы решить эту задачу, можно создать набор таблиц базы данных с необходимой для расчетов информацией (описание формул, имена функций, количест* во их аргументов, описание каждого аргумента и т. д.). Предположим, что такие таблицы уже созданы и осталось написать утилиту, выполняющую заданные вычисления. Воспользуемся для этого динамическим PL/SQL.
Ниже приведен заголовок функции, принимающей до пяти аргументов и выполняющей заданное действие:
/* Файл в web: dyncalc.sf
CREATE OR REPLACE FUNCTION dyncalc (
operjn IN VARCHAR2.
nargsjn IN INTEGER := 0, argljn IN VARCHAR2 尸 NULL. arg2_1n IN VARCHAR2 :- NULL, arg3_in IN VARCHAR2 严 NULL, "MJn IN VARCHAR2 :- NULL, arg5Jn IN VARCHAR2 :- NULL )
RETURN VARCHAR2
В этом случае используется оператор EXECUTE IMMEDIATE, находящийся в каскадном операторе IF. Вот часть тела функции:
ELSIF nargs_in = 2
THEN ~
EXECUTE IWEDIATE v_code || '(:1, :2); END;'
USING OUT retval, argljn, arg2_in;
ELSIF nargsjn 匸 3
THEN "
EXECUTE IMMEDIATE v_code || '(:1. .2. :3); END;'
USING OUT retval, argl in. arg2 in. arg3 in;
Как видите, ничего сложного, и этот код прекрасно работает в SQL*Plus:
SQL> BEGIN
DBMS_OUTPUT.PUT_LINE(dyncalc('sysdatej);
DBMS 二 OUTPUT.PUT一LINE(dyncalcC'power', 2, 2,44));
dbms2output.put_line(
dyncalc ('greatest', 5, 66, 5, 88. 1020, -4)):
END:
/
05-MAY-99
17592186044416
1020
У динамических функций имеются определенные ограничения, которые можно преодолеть, если вместо набора жестко закодированных параметров (от argl_in до a rg5_i п) использовать единственный массив. Эта технология описана в файле dyncalc.pkg, который вы найдете на узле издательства O'Reilly.
Еще более интересный пример применения динамического PL/SQL содержится в файле str2ilst.pkg. В нем реализована универсальная программа синтаксического анализа строк, выбирающая из строки с разделителями отдельные элементы и помещающая их в заданную коллекцию.
Пакет NDS Utility
Чтобы облегчить использование универсальных утилит, описанных в данной главе, автор создал единый пакет NDS Utility и также разместил его, в виде файла с именем ndsutil.pkg, на узле издательства O'Reilly. Содержащиеся в данном пакете программы перечислены в табл. 15.1.
Таблица 15.1. Содержимое пакета NDS Utility
ИмяОписаиие
execlmmed Заменяет оператор EXECUTE IMMEDIATE без предложений USING и INTO; включает обрабогёу ошибок
openFor Заменяет оператор OPEN FOR без предложения USING; включает обработку ошибок
tabCount Возвращает количество строк в указанной таблице, заданных с использованием необязательного предложения WHERE
countBy Возвращает количество строк в таблице для заданного выражения GROUP BY с необязательным предложением HAVING
showCol Выводит содержимое одного столбца заданной таблицы
dynPLSQL Выполняет динамическую строку PL/SQL, заключая ее внутрь блока с точкой с запятой в конце. Предложение USING не допускается
Пакет определен с использованием модели разрешений вызывающего (AUTHID CURRENT_USER), Это означает, что независимо от того, кто является владельцем пакета, разрешение внешних ссылок в динамическом SQL производится в соответствии с правами вызывающей схемы, а не владельца пакета.
Все программы содержат разделы исключений, в которых выводится информация об ошибке и «провинившейся» SQL-инструкции. Процедуры повторно инициируют исключение, а функции обычно возвращают значение NULL или пустую структуру.
Сравнение возможностей динамического SQL и пакета DBMS_SQL
Динамический SQL является частью языка PL/SQL, поэтому он и рассматривается в нашей книге. Что касается DBMS_SQL, то это встроенный пакет, не имеющий непосредственного отношения к PL/SQL. Давайте попытаемся определить, есть ли вообще смысл использовать пакет 08MS_SQL, имея в распоряжении NDS.
Визуальное сравнение эквивалентных программ
Попробуем сравнить две функционально эквивалентные программы, выводящие список имен сотрудников, выбранных из таблицы на основании заданного произвольного предложения WHERE. Первая процедура реализована с помощью пакета DBMS_SQL, а вторая — с помощью динамического SQL.
Вот реализация первой процедуры:
CREATE OR REPLACE PROCEDURE showemps (wherejn IN VARCHAR2 :- NULL)
IS-
cur INTEGER 尸 DBMS_SQL.OPEN_CURSOR:'
rec efnployeeXROWTYPE;
fdbk INTEGER;
BEGIN
DBMSJQL. PARSE.
(cur.
'SELECT employeejd, last_name
FROM employee .
WHERE ' И NVL (wherejn, '1=1'),
DBMS_SQL.NATIVE);.
DBMS_SQL.DEFINE_CDLUMN (Cur. 1. 1):
DBMS二SQL.DEFINE二COLUMN (cur, 2, user, 30);
fdbk :- DBMSJQL.EXECUTE (cur);..
LOOP
/* Выбираем следующую строку. После просмотра всех строк выходим из цикла*/
EXIT WHEN DBMS_SQL.FETCH_ROWS Ccur) - 0:.
DBMSJQL.COLUMNJALUE Ccur. 1. rec.eniployeejd);
DBHS_SQL.COLUMN_VALUE (cur, 2. rec.last_name);■
DBMS二OUTPUT, Р1Л■二 LI NE (-
TO_CHAR (rec.enployeejd) |||| rec.last_name);
END LOOP?-一
DBMS_SQL.CLOSE_CURSOR (cur):
END:~
А это реализация процедуры с использованием динамически формируемых SQL-инструкций:.
CREATE OR REPLACE PROCEDURE showenps (wherejn IN VARCHAR2 := NULL)
IS
TYPE cv_typ IS REF CURSOR:
cv cv_typ;
vjd emp 1 оу ее. emp 1 оу ее J dHYP E;
v^nm emp 1 oyee. 1 ast_nameXTyPE :
BEGIN~'
UPEN cv FOR
'SELECT employeejd. last_name
FROM employee■
WHERE ' II NVL (where in, 'IT');
LOOP
FETCH cv INTO vjd. v_nm;
EXIT WHEN cvXNOTFOUND?
DBMS OUTPUT.PUT_LINE ( TO_CHAR (vjd) || '-' || v_nm):
END LOOP;
CLOSE cv:■
END;
Как видите, во втором случае программа значительно упрощается, а объем кода уменьшается. И поскольку здесь используются «родные» элементы PL/SQL, а не программы встроенного пакета, этот код гораздо проще писать, читать и сопровождать.
Какую технологию в каких случаях предпочтительнее использовать
Если перед вами станет вопрос, какой из описанных технологий отдать предпочтение, советуем остановить свой выбор на встроенном динамическом SQL, поскольку он:
О всегда эффективнее, чем DBMS.SQL (иногда его преимущества бывают очень значительными);
О гораздо проще в применении, а программный код на его основе короче и потенциально содержит меньшее количество ошибок (в этом вы имели возможность убедиться, рассмотрев примеры предыдущего раздела);
О работает со всеми типами данных SQL, включая пользовательские объекты и коллекции (массивы VARRAY, вложенные таблицы и ассоциативные массивы), в отличие от пакета DBMS_SQL, работающего только с типами данных, совместимыми с Oracle?;
О позволяет выбирать несколько столбцов информации прямо в запись PL/SQL (при использовании DBMS_SQL выборка производится в отдельные переменные).
Если все это так, зачем же теперь нужен пакет DBMS_SQL? Оказывается, с его помощью можно выполнить задачи, которые NDS не по силам.
О Пакет DBM3_3QL поддерживает динамический SQL версии «Method 4», применимый и для тех случаев, когда во время компиляции вы не знаете количество запрошенных столбцов и переменных привязки. Это самая сложная форма
динамического SQL, которую NDS не поддерживает. При использовании NDS элементы в предложениях USING и INTO кодируются жестко, поэтому если вы не знаете, сколько переменных подстановки будет использовано, написать предложение USING для оператора EXECUTE IMMEDIATE будет непросто.
ПРИМЕЧАНИЕ Последнее ограничение можно обойти, если составить не просто динамический запрос, а динамический блок PL/SQL, содержащий динамическую строку запроса с переменным количеством элементов в предложениях USING и INTO. Однако это сложное решение требует выполнения оператора EXECUTE IMMEDIATE из динамической строки.
О DBMS_SQL позволяет описать столбцы динамического курсора, задав информацию о каждом из них в виде ассоциативного массива записей. Благодаря этому на основе данного пакета можно создавать предельно универсальный код. Примером использования такой возможности служат программы, приведенные в файле desccols.pkg.
О С помощью DBMS_SQL можно выполнять код SQL и PL/SQL произвольной длины, поскольку для его определения используются коллекции. Длина строки в случае применения NDS не должна превышать 32 Кбайт.
О Программы пакета DBMS SQL можно вызывать из клиентского кода PL/SQL, например из библиотек Oracle Forms. При использовании NDS это делать невозможно, поскольку он не поддерживается версиями PL/SQL, применяемыми в средствах разработки.
В заключение следует сказать, что NDS удовлетворяет от 80 до 90 % требований, с которыми вам приходится сталкиваться при написании и выполнении динамического кода. Однако полезно знать и о существовании пакета DBMS_SQL, который может обеспечить ряд дополнительных возможностей.
Часть V
Создание приложений PL/SQL
Итак, вы уже умеете объявлять переменные и работать с ними, знаете, как осуществлять обработку ошибок и создавать циклы. Словом, вы готовы приступить х разработке приложений. Именно этой теме и посвящена данная часть книги. Сначала мы научимся создавать процедуры, футащи, пакеты и триггеры, а затем рассмотрим., как управлять приложением в целом.
Глава 16. Процедуры, функции и параметры
Глава 17. Пакеты
Глава 18. Триггеры
Глава 19, Управление приложениями PL/SQL
1 久 Процедуры, функции I О и параметры
А Модульный код
► Процедура
А Функция
А Параметры
А Локальные модули
А Перегрузка модулей
А Предобъявления
А Дополнительные вопросы
А Модульный подход — в жизнь!
В предыдущих частях книга подробно рассматривались основные компоненты и конструкции языка PL/SQL — курсоры, исключения, циклы, переменные и т. д. Однако для создания хорошо структурированного, легкого для понимания и сопровождения приложения уметь работать со всеми этими компонентами недостаточно —нужно правильно скомпоновать все части кода.
Далеко не все задачи, с которыми мы сталкиваемся в жизни, имеют линейный алгоритм решения. Только некоторые решения можно сразу понять и описать на бумаге или, скажем, средствами компьютера. Большинство проектируемых нами систем объемны и сложны, они включают много пересекающихся и даже конфликтующих компонентов. Кроме того, пользователь требует и в итоге должен получить приложения, которые легки в применении и значительно мощнее своих предшественников. Достижение этой цели сопровождается еще большим усложнением внутренней реализации приложений.
Сейчас одной из самых важных задач в профессии программиста является упрощение рабочей среды. Человек не способен работать подобно большому параллельному компьютеру. Даже для самых ярких умов весьма сложно удерживать в голове пять-семь задач одновременно. Большие проекты мы должны разбивать на меныиие, лучше управляемые компоненты, а те, в свою очередь, декомпоно- вать на отдельные программы. И лишь создав и протестировав нужные программы, мы сможем сконструировать из них полнофункциональные приложения.
Если вы воспользуетесь нисходящим методом разработки или другой подобной методикой, то разделение кода на процедуры, функции и объектные типы позволит вам создать надежное и легкое в сопровождении приложение.
Модульный код
Мадуляриза1щя — это разбиение больших блоков кода на меньшие блоки (модули), которые можно вызывать из других модулей. Этот процесс аналогичен нормализации данных и, обеспечивая те же преимущества, обладает рядом достоинств. В частности, модуляризация позволяет повысить ряд характеристик кода.
О Доступность для повторного использования. Разбивая большие программы на отдельные компоненты, которые работают совместно, вы увидите, что многие модули используются более чем одной программой приложения. При правильной организации эти программы-утилиты могут применяться не только вашим, но и другими приложениями.
О Управляемость. Что удобней отлаживать; одну программу в 10 000 строк или пять отдельных программ по 2000 строк, которые вызывают друг друга при необходимости? Ответ однозначен: конечно, ум человека лучше приспособлен к решению небольших задач. К тому же при использовании второго подхода код можно протестировать и отладить на более низком уровне, то есть до того, как отдельные модули будут скомбинированы для проведения более сложных тестов.
О Читабельность. Имена модулей в той или иной степени отражают их назначение. Чем больше кода вы переместите в программный интерфейс или скроете за ним, тем легче будет понять, что делает программа. Модуляризация дает возможность сосредоточиться на задаче в целом, а не на отдельных фрагментах кода.
О Надежность. Если код разбит на модули, в нем меньше ошибок, а обнаруженные ошибки легче исправлять, так как они локализованы на уровне модуля. Кроме того, такой код легче сопровождать другим программистам, он имеет меньший объем и лучше читается.
Поскольку вы уже изучили все имеющиеся в языке PL/SQL конструкции и умеете создавать циклы, ветвления и курсоры, то в принципе можете приступать к написанию программ. Однако вы не получите качественное приложение, если не умеете создавать и комбинировать модули.
В PL/SQL выполнить разбиение кода на модули можно с помощью перечисленных ниже структур.
О Процедура. Программа, которая осуществляет одно или несколько действий и вызывается как исполняемый оператор PL/SQL. Передавать данные процедуре и получать их из нее можно с помощью списка параметров.
О Функция. Программа, которая возвращает одно значение и используется как выражение PL/SQL. Передавать данные функции можно с помощью ее списка параметров.
О Триггер базы данных. Набор команд, который вызывается при наступлении в базе данных некоторого события (такого, как подключение к базе данных, модификация строки таблицы или DDL-операция).
О Пакет. Именованный набор процедур, функций, типов и переменных. Пакет не является модулем, скорее, это мета-модуль, но он тесно связан с реализацией модульного подхода.
О Объектный тип или экземпляр объектного типа. Эмуляция объектно-ориентированного класса в Oracle. Объектный тип инкапсулирует состояние и поведение данных, комбинируя их (как реляционная таблица) с правилами (процедурами и функциями, которые манипулируют этими данными).
Пакеты будут рассмотрены ниже, в главе 17, триггеры баз данных 一 в главе 18, а объектные типы — в главе 21. Сейчас мы сконцентрируем свое внимание на принципах создания процедур и функций, опишем списки их параметров.
Термин «модуль» в данном контексте означает функцию или процедуру. Как и во многих других языках программирования, в PL/SQL модули могут вызывать другие именованные модули. Передавать информацию в модули и получать ее из них можно путем использования параметров. Кроме того, модульная структура PL/SQL хорошо сочетается с реализацией обработчиков исключений и предоставляет программисту все возможности по части обработки ошибок (см. главу 6).
В этой главе описано, как объявляются процедуры и функции и как устанавливаются списки параметров для них. Кроме того, мы исследуем такие «экзотические* аспекты разработки программ, как локальные модули, перегрузка, пре- добъявления, а также детерминированные и табличные функции.
Процедура
Процедура — это модуль, который осуществляет одно или несколько действий. Поскольку вызов процедуры в PL/SQL является отдельным исполняемым оператором, блок кода PL/SQL может состоять только из вызова процедуры. Процедуры относятся к числу ключевых компонентов модульного кода, позволяющих разработчикам оптимизировать код и повторно использовать его. Процедура PL/SQL имеет следующую структуру:
PROCEDURE [схе«а.]и«я ((параметр.[параметр ...])]
[AUTHID I CURRENTJJSER]
IS-
[объявлении]
BEGIN'
исполняемые операторы
[EXCEPTION
обработчики исключений^
END [им];
Назначение каждого элемента структуры описано ниже.
О схема — имя схемы, которой будет принадлежать процедура (необязательный аргумент). По умолчанию применяется имя схемы текущего пользователя. Если значение схема отлично от имени схемы текущего пользователя, этот пользователь должен обладать привилегиями на создание процедуры в другой схеме.
О имя — имя процедуры. Следует сразу же за ключевым словом PROCEDURE.
О параметр — необязательный список параметров, которые применяются для передачи данных в процедуру и возврата информации из процедуры в вызывающую программу.
О AUTHID I CURRENT_USER — определяет, с какими полномочиями будет вызываться процедура: создателя (владельца) или текущего пользователя. В первом случае выполнение процедуры осуществляется с правами создателя1 во втором —с правами вызывающего.
О объявления - объявления локальных идентификаторов для этой процедуры. При отсутствии таковых между ключевыми словами IS и BEGIN не будет никаких выражений.
О исполняемые операторы — операторы, которые выполняются процедурой при вызове. Между ключевыми словами BEGIN и END или EXCEPTION должен находиться, по крайней мере, один исполняемый оператор.
О обработчики исключений — обработчики исключении для процедуры. Не являются обязательными. Если в процедуре не обрабатываются никакие исключения, слово EXCEPTION можно опустить и завершить исполняемый раздел ключевым словом END.
На рис. 16.1 показан код процедуры apply_discount, который содержит все четыре раздела, характерных для именованного блока PL/SQL.
PROCEDURE appl7_d1scount»
(companyjd_in IN company.company IdXTYPE. dnscount_in IN NUMBER)
Заголовок
IS
m1n_discount CONSTANT NUMBER :- 0,05:
max~discount CONSTANT NUMBER :- 0,25;*
invalid discount EXCEPTION;
Раздел объявлений
BEGIN
IF discountjn BETWEEN min_discount AND max_discount
THEN'~-
UPDATE item
SET item_amount-item_amount*C1-discount]n)
WHERE EXISTS (SELEcf'x' FROM order ~
WHERE order.ordered = item.order_id*
AND order.company_1d 匸 company_id_in):.
IF SQLXROWCQUNT = 0 THEN RAISE NO~DATAJOUND;
END IF;~"
ELSE
RAISE 1nval1d_d1scount:
END IF;-
Исполняемый раздел
EXCEPTION
WHEN invalid-discount
,THEN~
DBMS_OUTPUT.PUT_LINE('Указана неверная скидка');
WHEN NQ_DATA_FQUND.>
THEN 一 ~
D8MS_OUTPUT.PLJT_LINE( 'Для компании нет заказов:' 11
TO CHAR( companyj d i n)):
Раздел исключений
END apply_discount;
Рис. 16.1. Код процедуры apply_discount
Вызов процедуры
Процедура вызывается как исполняемый оператор PL/SQL. Другими словами, ее вызов должен заканчиваться символом точки с запятой (;) и может предшествовать другим инструкциям SQL либо операторам PL/SQL (если таковые имеются) в исполняемом разделе блока PL/SQL или следовать за ними. Например, в приведенном ниже коде содержится вызов процедуры apply_discount:
BEGIN
apply_discount( new_company_1d, 0.15);—15% скидки
END:~~~
Если процедура не имеет параметров, она вызывается без круглых скобок:
di spl ay_store_summary ;
В Oracle8i и более поздних версиях вызов процедуры может включать пустые скобки, как показано ниже:
di spl ay_store_sunmary():
Заголовок процедуры
Часть определения процедуры, которая расположена перед ключевым словом IS, называется ее заголовком. Здесь содержится вся информация, необходимая для вызова процедуры:
О имя процедуры;
О ключевое слово AUTHID (если это требуется);
О список параметров (если он существует).
Например, заголовок процедуры apply^discount, описанной в предыдущем разделе, включает тип модуля, имя процедуры и список из двух параметров:
PROCEDURE apply_discount
(companyjdjn IN company. companyj ЙШРЕ.
discountJn IN NUMBER)~'
Как видите, для вызова процедуры не нужно знать, какова ее внутренняя реализация.
Тело процедуры
Тело процедуры содержит код, требуемый для ее реализации, и состоит из разделов объявлений, исполняемого и исключений. Весь код после ключевого слова IS является телом процедуры. Разделы исключений и объявлений не обязательны. Когда обработчики исключений не используются, ключевое слово EXCEPTION не указывается и код процедуры завершается ключевым словом END. Если в коде процедуры нет никаких объявлений, то непосредственно за ключевым словом IS следует ключевое слово BEGIN.
Процедура должна содержать, по крайней мере, один исполняемый оператор. Соблюсти это условие обычно не составляет проблемы, наоборот, трудно обеспечить эффективность кода процедуры в сочетании с его компактностью. Ведь с увеличением объема кода исполняемого раздела ухудшается его управляемость (в следующих разделах этой главы данный вопрос рассматривается более подробно).
Дескриптор END
После ключевого слова END можно добавить имя процедуры. Оно служит меткой, которая связывает конец процедуры с ее началом. Использовать дескриптор вместе с ключевым словом END удобно в тех случаях, если код процедуры занимает более чем одну страницу или если она находится в теле пакета.
PROCEDURE display_stores (region_in IN VARCHAR2) IS
BEGIN一一
END display_stores;
Оператор RETURN
Ключевое слово RETURN обычно ассоциируется с функциями, поскольку они должны возвращать значения (или инициировать исключения). Однако PL/SQL позволяет использовать оператор RETURN в процедурах. Версия этого оператора для процедур не принимает выражений и не может возвращать значений в вызывающий программный модуль — она просто прекращает выполнение процедуры и передает управление вызывающему коду.
Такой способ использования оператора RETURN встречается нечасто, поскольку в этом случае создаются два или несколько выходов из процедуры, а это делает поток выполнения сложным для понимания и плохо управляемым. Если вы хотите сохранить структурированность программных модулей, избегайте применения операторов RETURN и GOTO.
Функции
Функция — это модуль, который возвращает значение. В отличие от вызова процедуры, представляющего собой отдельный оператор, вызов функции всегда является частью оператора, то есть он включается в выражение или служит в качестве значения по умолчанию, присваиваемого переменной при объявлении.
Возвращаемое функцией значение принадлежит к определенному типу данных. Функция может использоваться вместо выражения, которое имеет тот же тип данных, что и возвращаемое ею значение.
Функции играют особенно важную роль в обеспечении модульного подхода.. К примеру, реализация отдельного бизнес-правила или формулы в приложении должна помещаться в функцию. Любой запрос, возвращающий одну строку, также должен объявляться внутри функции, для того чтобы его можно было использовать повторно.
ПРИМЕЧАНИЕ
Некоторые программисты функциям предпочитают процедуры, возвращающие информацию через список параметров. Если вы принадлежите к их числу, убедитесь, что поместили в процедуры бизнес-правила, формулы и запросы, возвращающие одну строку.
Если в приложении не определяются или не используются функции, то со временем его будет тяжело поддерживать и модифицировать.
Структура функции
функция имеет почти такую же структуру, как и процедура, за исключением того, что предназначение ключевого слова RETURN в ней совсем иное. Структура функции такова:
FUNCTION [схемд].имя [(.параметр [. параметр ...])]
RETURN тип_зозвращземых_ддннь1)(
[AUTHIO DEFINER | CURRENT_USER] ■
[DETERMINISTIC]
[PARALLEL ENABLE...]
[PIPELINED]
IS
[обьяялет]
BEGIN
исполняете операторы
[EXCEPTION
обработчини исключений^
END [и«я];
Назначение каждого элемента структуры описано ниже.
О схема — имя схемы, которой будет принадлежать процедура (необязательный аргумент). По умолчанию применяется имя схемы текущего пользователя. Если значение схема отлично от имени схемы текущего пользователя, этот пользователь должен обладать привилегиями на создание процедуры в другой схеме.
О имя — имя процедуры. Следует сразу же за ключевым словом FUNCTION.
О парамегр — необязательный список параметров, которые применяются для передачи данных функции и возврата информации из нее в вызывающую программу.
О тип_возврдИ1аемых_даиных — тип возвращаемого функцией значения. Это обязательная часть заголовка функции (описывается более детально в следующих разделах).
О AUTHIO DEFINER | CURRENT_USER — определяет, с какими полномочиями будет вызываться процедура: создателя (владельца) или текущего пользователя. В первом случае выполнение процедуры происходит с правами создателя, во втором —с правами вызывающего.
О DETERMINISTIC — используется для оптимизации и позволяет системе применить сохраненную копию возвращаемого функцией значения, если это возможно. Оптимизатор запросов может выбрать сохраненную копию или снова вызвать функцию.
Q PARALLEL^ENABLE — используется для оптимизации и позволяет функции выполняться параллельно в случае, когда она вызывается из инструкции SELECT.
Q PIPELINE — указывает, что результат табличной функции должен возвращаться построчно, с помощью оператора PIPE ROW.
объявления - объявления локальных идентификаторов для указанной функции. Если таких идентификаторов нет, между ключевыми словами IS и BEGIN не будет никаких выражений.
исполняемые операторы — операторы, которые выполняются функцией при вызове. Между ключевыми словами BEGIN и END или EXCEPTION должен находиться, по крайней мере, один исполняемый оператор.
обработчики исключений — обработчики исключений для процедуры (не являются обязательными). Если в процедуре не обрабатываются никакие исключения, слово EXCEPTION можно опустить и завершить исполняемый раздел ключевым словом END.
На рис. 16.2. показаны код функции tot_sales и ее структурные элементы. Заметьте, что данная функция не имеет раздела исключений.
FUNCTION tot_sales
(company_id_in IN company.company_idXTYPE,.
statusjn IN order.status_codeXTYPE :- NULL)
RETURN NUMBER-
Заголовок
IS
/* Представление кода состояния заказа в верхнем регистре*/ statusjnt order.status_cadeXTYPE := UPPER(statusJn);
/*Параметризированный курсор, возвращающий общий объем
продаж со скидкой*/
CURSOR sales_cur Cstatusjn IN 5tatus_codeWfPE) IS
SELECT SUM (amount*discQunt)
FROM item^■
WHERE EXISTS (SELECT 'X' FROM order
WHERE order.order_id - itefn.ardeMd
AND companyJd = company_1d_in AND status_code LIKE statusjn):
/♦Возвращаемое функцией значение*/
returr_value NUMBER;
Раздел объявлении
BEGIN
OPEN sales cur [status int):
FETCH sales_cur INTO return_value:
IF sales_curXNOTFOUND"
THEN ~
CLOSE sales cur;-
Исполняемый раздел
RETURN NULL:*
ELSE
CLOSE sales_cur;
RETURN return_value:
END IF:~
END tot_sales;
Типы возвращаемых данных
Функция может возвращать данные любого типа, поддерживаемого PL/SQL,— от скаляров (единичных значений типа даты или строки) до сложных структур, таких как коллекции, объектные типы, переменные курсоры и LOB (Large Objects —большие объекты). С помощью функции нельзя возвращать исключения, поскольку в PL/SQL они не типизированы. Несколько примеров использования предложения RETURN вы найдете ниже.
О Функция возвращает строку:
CREATE OR REPLACE FUNCTION favorite_nickname ( nameJn IN VARCHAR2) RETURN VARCHAR2 IS ~
END;
О Функция-член объектного типа возвращает значение типа DATE:
CREATE TYPE pet_t IS OBJECT ( tag_no INTEGER. NAME VARCHAR2 C60). breed VARCHAR2(100).■
dob DATE. MEMBER FUNCTION age Cnew_tag_no IN INTEGER)'
RETURN DATE--
)
О Возврат записи, имеющей ту же структуру, что и у таблицы books:
CREATE OR REPLACE PACKAGE book info
IS~
FUNCTION onerow(isbn_1n IN books.IsbnXTYPE) RETURN booksXROWTYPE;
О Возврат курсорной переменной типа overdue_rct:
CREATE OR REPLACE PACKAGE book info
IS~■
TYPE overdue_rt IS RECORD (isbn books.isbr.HYPE, days_overdue PLS_INTEGER):
TYPE overdue_rct IS REF CURSOR RETURN overdue_rt:
FUNCTION overduejnfo (username_1n IN 1ib_users.usernameiTYPE)
RETURN overdue_rct:■
Дескриптор END
После ключевого слова END можно добавить имя функции. Оно служит меткой, которая связывает конец функции с ее началом. Использование вместе с END дескриптора удобно в тех случаях, если код функции занимает более одной страницы или если она находится в теле пакета.
FUNCTION tot_sales (companyjn IN INTEGER) RETURN NUMBER
IS~~
BEGIN
END tot sales:
Вызов функции
Вызов функции может производиться из любого места исполняемого оператора PL/SQL, где допускается использование выражения. Из приведенных ниже примеров вы увидите, каким образом могут вызываться функции, определенные ранее в разделе «Типы возвращаемых данных».
О Установка значения переменной по умолчанию с помощью вызова функции:
DECLARE
v_nickname VARCHAR2(100):= favorite_nickname('Steven');
О Использование функции-члена для объектного типа pet_t в условии:
DECLARE
my_parrot pet_t
pet_t (10°^' 'Mercury'. 'African Grey'.
~ TO_DATE ('09/23/1996'. "MM/OO/YYYY'));
BEGIN
IF fny_parrot.age < INTERVAL '50' YEAR тип INTERVAL
THEN
OBMS_OUTPUT.PUT_LINE ('Это еще молодая птица!');
ЕЮ IF; 一-
О Вставка в запись строки с информацией о книге:
DECLARE'
niy_fir5t_book booksSSROWTYPE:
BEGIN- ~
my_first_book :- book_1nfo.onerow C'l-56592-335-9'):
О Получение информации о книгах с просроченной датой для конкретного пользователя с помощью значения курсорной переменной:
DECLARE
overdue info overdue ret:
BEGIN" ~~
my_overdue_1nfo :=
book_info.overduejnfo ('STEVENJEUERSTEIN');
О Вызов функции в инструкции CREATE VIEW и передача ей в качестве аргумента результирующего набора значений, возвращаемого курсорным выражением:
CREATE OR REPLACE VIEW young_managers AS
SELECT managers.employeej d manager_employee_1d
FROM employees managers
WHERE Most_Reports 8efore_Manager
(~~.
CURSOR (SELECT reports.hire_date FROM employees reports
WHERE reports.manager_id = managers.employee 诃
managers.hire_date
) = 1;"
Функции без параметров
Если функция не имеет параметров, то в ее вызове не требуется использовать круглые скобки. Это показано ниже на примере метода аде объектного типа pet__t: [F my_parrot.age < INTERVAL '50' YEAR -- тип INTERVAL
Начиная с Oracle8: в вызов функции можно включать пустые скобки:
IF my_parrot.age() < ...
Заголовок функции
Часть определения функции, которая расположена перед ключевым словом IS, называется ее заголовком. В заголовке содержится вся информация, необходимая для вызова функции;
О имя функции;
О модификаторы определения и поведения функции;
О список параметров;
О тип возвращаемого значения.
Как видите, для того чтобы вызвать функцию из внешней программы, программисту не требуется знать ее внутреннюю реализацию.
Заголовок приведенной ранее функции tot_sales имеет следующий вид:
FUNCTION tot_sales
(companyjdjn IN company.companyjdXTYPE,
status in TN order.status codeXTYPE :- NULL)
RETURN NUMBER"
Здесь указаны тип модуля, имя функции, список из двух параметров и тнп возвращаемого значения — NUMBER. Таким образом, вызов функции tot_sal es можно включать в любые выражения, использующие числовые значения, например:
DECLARE
v_sales NUMBER;
BEGIN
v_sales :- tot_sales (1505, 'ACTIVE');
Тело функции
Тело функции содержит код, требуемый для ее реализации, и состоит из разделов объявлений — исполняемого и исключений. Весь код после ключевого слова IS является телом функции. Разделы исключений и объявлений не обязательны. Когда обработчики исключений не используются, ключевое слово EXCEPTION не указывается и код функции завершается ключевым словом END. Если в коде функции нет никаких объявлений, то непосредственно за ключевым словом IS следует ключевое слово BEGIN.
Исполняемый раздел функции должен содержать оператор RETURN, хотя его отсутствие никак не проявляется на этапе компиляции. Если функция завершает свое выполнение без вызова оператора RETURN, Oracle инициирует следующую ошибку (верный признак плохо сконструированной функции):
ORA-06503: PL/SQL : Function returned without value
Оператор RETURN
В исполняемом разделе функции должен находиться, по крайней мере, один оператор RETURN, который задает возвращаемое ею значение. Операторов может быть и несколько, но в одном вызове функции должен выполняться только один из них. После обработки оператора RETURN выполнение функции прекращается, и управление передается вызывающему блоку PL/SQL.
Если ключевое слово RETURN в заголовке определяет тип данных возвращаемого значения, то оператор RETURN в исполняемом разделе функции задает само это значение. Вы должны включить ключевое слово RETURN в заголовок и, кроме того, использовать хотя бы один оператор RETURN в теле функции. При этом тип данных, указанный в заголовке^ должен совпадать с типом данных выражения, возвращаемого оператором RETURN.
Возвращаемые значения
Оператор RETURN может возвращать любое выражение, совместимое с типом данных, который указан в заголовке. Это выражение может включать сложные вычисления, вызовы других функций, в частности функций преобразования данных: RETURN 'buy me lunch':
RETURN POWER (max_salary, 5);
RETURN (100 - pct_of_total_salary tempioyee_ld));
RETURN TO DATE С0Г || earliest month 11 Initial year,
'DDMMYY')?-
Оператор RETURN может возвращать и сложные структуры данных, такие как экземпляры объектных типов, коллекции и записи.
Выражения, указанные в операторе RETURN, вычисляются при его вызове, а результаты передаются вызывающему блоку в процессе передачи управления.
Использование нескольких операторов RETURN
В функции tot_sales, показанной на рис. 16.2, используются два оператора RETURN. Они позволяют задать следующее поведение функции. Если из курсора нельзя получить информацию о продажах, то возвращается NULL (не путайте с нулевым значением). Но если значение из курсора получено, оно передается вызывающей программе. Таким образом, оператор RETURN передает либо значение NULL, либо значение переменной return_value.
Несмотря на то что исполняемый раздел может содержать несколько операторов RETURN, в большинстве случаев рекомендуется использовать только один такой оператор, в последней строке раздела.
Оператор RETURN как последний исполняемый оператор
Если вы хотите всегда быть уверены в том, что созданная вами функция возвращает значение, используйте оператор RETURN в качестве последнего исполняемого ее оператора. Объявите переменную return_val ue (имя переменной указывает, что в ней содержится возвращаемое функцией значение), напишите весь код, в котором вычисляется возвращаемое значение, а затем, в самом конце кода функции, верните его с помощью оператора RETURN:
FUNCTION dojt_all (parameterjist) RETURN NUMBER IS
return_value NUMBER; "
BEGIN.
...исполняемые операторы ... ■ RETURN return_value: END:
Как избежать использования нескольких операторов RETURN в функции tot_sa~ les, показано ниже:
IF sales_curXNOTFOUND
THEN return_value:-NULL;
END IF;
CLOSE sales_cur;
RETURN return_value;
Параметры
Для передачи информации между модулем (процедурой или функцией) и вызывающим блоком PL/SQL используются параметры. Они относятся к числу не менее важных компонентов модуля, чем находящиеся в нем исполняемые операторы. Ведь если список параметров не понятен или плохо составлен, то другим программистам сложно применять такой модуль, и они наверняка откажутся от этой идеи. В таком случае уже будет неважно, насколько хороша внутренняя реализация модуля.
Ниже приведены рекомендации, которые, как мы надеемся, помогут вам правильно определить список параметров модуля.
О Количество параметров. Если процедура или функция имеет слишком мало параметров, это может послужить ограничением для ее использования. К аналогичному результату приводит и ситуация, когда параметров слишком много. Конечно, число параметров сильно зависит от характера задачи, однако для оптимального их представления можно использовать разные способы (например, связывание параметров в одну запись).
О Типы параметров. При выборе типа необходимо учитывать, для каких целей будут использоваться параметры: только для чтения, только для записи либо для чтения и записи.
О Имена параметров. Параметры следует называть так, чтобы имена отражали их назначение в модуле и были легки для восприятия.
О Значения параметров по умолчанию. Обратите внимание на то, каким образом устанавливаются значения по умолчанию. Решите, когда использовать значения по умолчанию, а когда нужно заставить программиста ввести определенное значение.
PL/SQL предлагает большой арсенал возможностей для создания и эффективного использования параметров. Мы ознакомимся с ними в следующих разделах главы.
Объявление параметров
Все формальные параметры определяются в списке параметров программы. При этом применяется почти такой же синтаксис, как при объявлении переменных в разделе объявлений блока PL/SQL. Однако параметры, в отличие от переменных, должны иметь режим использования. Кроме того, при объявлении параметров нельзя указывать ограничения на принимаемые ими значения. Поэтому, если в приведенном ниже объявлении размер переменной company_nanie ограничивается 60 символами:
DECLARE
conipany_name VARCHAR2(60);
то при объявлении параметра ограничивающую часть определения необходимо опускать:
PROCEDURE display_company (company_nanie IN VARCHAR2) IS ...
Формальные и фактические параметры
Очень важно понимать различие между формальными и фактическими параметрами. Формальные параметры представляют собой имена, объявленные в' списке параметров заголовка модуля, тогда как фактические — это значения или выражения, которые помещаются в список параметров при вызове модуля.
Рассмотрим функцию tot_sales, заголовок которой выглядит следующим образом:
FUNCTION tot_sales
(company_id_in IN company.companyjdXTYPE.
statusJn Tn order.status_codeXTYpE :- NULL)■
RETURN std_types. dol 1 a r_aniount ;
Формальными здесь являются: company_id_1n — первичный ключ таблицы company, а также status_1n — параметр, определяющий состояние заказов, которые должны быть включены в расчет объема продаж.
Данные параметры вне функции не существуют, их можно считать «заполнителями» для фактических значений, передаваемых функции при использовании ее в программе.
Когда функция tot_sal es вызывается в коде, на место формальных параметров помещаются фактические, то есть переменные, значения которых будут переданы данной функции.
В этом примере значение переменной company_id_in является первичным ключом, указывающим на запись таблицы company. В приведенных ниже первых трех строках функции tot_sales передаются разные жестко закодированные значения состояния заказа. В последнем вызове состояние заказа не указано, поэтому функция присваивает переменной status_1n значение по умолчанию (заданное в заголовке функции):
new_sales := tot^sales (company_1d_1n, 'N');
pa1d_sales tot_sales (company_id_1n, 'P');
sh1pped_sales :- tot_sales (companyjdjn, 'S');
all_sales :- tot^sales Ccompany_id_in);
При вызове функции tot_sales используются значения фактических параметров. Результаты вычислений присваиваются соответствующим формальным параметрам внутри функции (учтите, что это справедливо только в отношении параметров с режимом использования IN или IN OUT; выходные параметры, имеющие режим использования OUT, в функцию не копируются).
Формальные и соответствующие им фактические параметры должны иметь один и тот же или совместимый тип данных. Во многих ситуациях PL/SQL может выполнить автоматическое преобразование типов, но лучше этого избегать. Используйте функции преобразования, подобные TO_CHAR и TO_DATE (см. главу 10), и вы всегда будете точно знать, данные какого типа передаются в модули.
Связывание формальных и фактических параметров
Каким образом при выполнении программы определяется, какому формальному параметру должен соответствовать фактический? PL/SQL предлагает два метода установки такого соответствия:
О по позиции, когда фактический параметр ассоциируется с формальным неявно;
О по имени, когда фактический параметр ассоциируется с формальным явно.
Связывание по позиции
Во всех приведенных ранее примерах применялось связывание параметров в соответствии с позицией. При его использовании PL/SQL принимает во внимание относительные позиции параметров, то есть он ассоциирует Мй фактический параметр в вызове щхлраммы с ЛЛм формальным параметром в заголовке программы.
В примере с функцией tot_sale первый фактический параметр, :order.сотра- nyjd, будет отвечать первому формальному, company_1 d_1n, а второй фактический параметр, 'N', - второму формальному параметру, statusjп:
new_sales tot_sales (:order.company_id. 'N');
FUNCTION tot_sales
(company_id_1n IN company.company_idXTYPE, statusjn Tn order.status_codeXTYPE :- NULL)
RETURN std_types.dol1ar_amount;
Итак, теперь вы знаете, каким способом компилятор передает значения параметров в модули. Метод связывания параметров по позиции, вне всякого сомнения, является наиболее наглядным и понятным (рис. 16.3).
Заголовок процедуры
Рис. 16.3. Связывание параметров по позиции
Связывание по имени
Чтобы установить соответствие параметров по имени, требуется при вызове подпрограммы явно связать формальный параметр с фактическим. Делается это с помощью комбинации символов -> следующим образом:
шJ>opMdnhHoro_ndpaneTpd ■〉значение^пардметра
Поскольку имя формального параметра указано явно, у PL/SQL отпадает необходимость учитывать порядок параметров. Поэтому в данном случае при вызове модуля не обязательно задавать параметры в том же порядке, в каком они указаны в заголовке. Таким образом, функцию tot_sales для обработки новых заказов можно вызвать любым из приведенных ниже способов:
new_sales :-
tot_sales (company_1d_1n -> :order.companyjd. statusjn -> 'N'):
new_sales :-
tot_salesCstatus_1n -> 'N'. conpany_id_1n ■> :order.company^id);
В одном вызове можно комбинировать оба метода связывания фактических и формальных параметров:
:order.new_sales :- tot_sales (:order.companyjd. statusjn -> 'N');
При этом все «позиционные^ параметры должны быть указаны перед «имен。- ванными». Для установления соответствия по позиции необходима какая-то точка отсчета, и в качестве таковой используется первый параметр. Если же разместить «именованный»- параметр перед «позиционным», PL/SQL не сможет найти позицию последнего. Оба приведенных ниже вызова функции tot_sales выполнены не будут. Первый вызов не выполнится потому, что соответствие по имени задано раньше соответствия по позиции, а второй — по той причине, что параметры указаны в неправильном порядке (PL/SQL попытается преобразовать 'N1 в значение переменной company_id_in):
:order.new_sales 尸 tot_sales
(conipany_1d_1n => :order.companyjd. 'N'):
:order.new_sales :- tot_sales('N'. companyjd_in -> :order.company_id);
Преимущества связывания по имени
Теперь, когда вы знаете о разных методах связывания фактических и формальных параметров, у вас может возникнуть вопрос: зачем использовать связывание по имени? В качестве ответа предлагаем описание преимуществ, которые обеспечивает данный метод.
Q Повышение информативности. При использовании связывания по имени вызов программы содержит сведения о формальных параметрах, с которыми ассоциируются фактические значения. Если вы не знаете, что делают модули, вызываемые приложением, наличие имен формальных параметров помогает понять назначение конкретного программного вызова. Именно по этой причине в некоторых средах разработки связывание параметров по имени является стандартом. Оно особенно эффективно в случае, когда имена формальных параметров отвечают определенному соглашению (например, в качестве последнего элемента имени указан режим передачи).
О Обеспечение гибкости при описании параметров. Вы имеете возможность размещать параметры в удобном для работы порядке и включать в список только те из них, которые действительно необходимы. В сложных приложениях иногда требуется создавать процедуры с десятком параметров. Однако, как вы знаете, любой параметр, значение которого определяется по умолчанию, при вызове модуля может быть опущен. При использовании связывания по имени более удобно передавать модулю только те значения, которые необходимы для решения текущей задачи.
Помните, что метод связывания не влияет на внутреннюю реализацию модуля, а лишь определяет способ его вызова.
Режимы использования параметра
При определении параметра указывается также допустимый способ его применения. Он задается с помощью одного из трех указанных в таблице режимов.
Режим
Предназначение
Использование пераметра
IN
Только для чтения
Значение параметра может применяться, но не может быть изменено в модуле
OUT
Только для записи
В модуле можно присваивать значение параметру, но нельзя использовать его. В некоторых случаях Oracle позволяет применять выходные параметры не так, как указано в определении, но такнх ситуаций рекомендуется избегать
IN OUT
Для чтения и записи
В модуле можно использовать и изменять значение параметра
Каждый режим определяет, каким образом программа может применять значения, присвоенные формальному параметру. Режим параметра указывается непосредственно после его имени, но перед типом данных и необязательным значением по умолчанию. В приведенном ниже заголовке процедуры задействованы все три режима передачи параметров:
PROCEDURE predict_act1v1ty
(last_date_in IN DATE,
taskZdescJnout IN OUT VARCHAR2,
next_date_out OUT DATE)
Процедура predict_activity принимает информацию о дате последнего действия (last_date) и описание этого действия (task_desc_1nout). Возвращает ока два значения: описание действия (возможно модифицированное) и дату следующего действия (next_date_out). Поскольку параметр task_desk_inout имеет режим IN OUT, программа может читать и изменять его значение.
Рассмотрим каждый из режимов использования параметра более детально.
Режим IN
Этот режим параметра позволяет только передавать значение из вызывающего блока в модуль; обратная передача, из модуля в вызывающий блок PL/SQL, запрещается. Другими словами, параметры, объявленные в режиме IN, то есть входные
параметры, функционируют внутри модуля как константы. Это означает, что данным параметрам нельзя присваивать значения или каким-либо другим методом модифицировать их.
Режим IN применяется по умолчанию. Таким образом, если при объявлении параметра не указать режим его использования, параметр автоматически рассматривается как входной. Рекомендуется всегда указывать режим параметра, чтобы такого рода информация хранилась непосредственно в коде. В заголовке программы входным параметрам можно присваивать значение по умолчанию (см. далее раздел «Значения по умолчанию»).
Значением входного параметра может быть переменная, именованная константа, литерал или сложное выражение. Таким образом, все приведенные ниже вызовы функции display_title корректны:
DECLARE
happen tie CONSTANT VARCHAR2C30) := 'С днем рождения':
changing_title VARCHAR2C30) :и 'С юбилеем':
spc VARCHARZd)尸 CHR(32)-- ASCII-код пробела;
BEGIN
display_t1tle С'С днем рождения'): -- литерал
d1splay_t1tle 出叩pyJJtle); — константа
changing_title :- h叩pyjitle:
display_title (changing_tifle);-- переменная'
display_title ('C1 || spc || 'днем рождении');-- аыражение
display_title (INITCAP (h叩ру」К1в)): еще одно выражение
END; ~一
Если вы хотите передать данные из своей программы, то вам понадобятся режимы OUT и IN OUT.
Режим OUT
По своему назначению выходные параметры, то есть объявленные с использованием ключевого слова OUT, противоположны параметрам, объявленным как IN. Выходные параметры используются для возврата значений из модуля в вызывающий блок PL/SQL. Они ведут себя как возвращаемые функциями значения и отличаются от них лишь тем, что описываются в списке параметров и их общее количество не охраничено.
В программе выходные параметры действуют как неинициализированные переменные и до успешного ее завершения не содержат никакого значения (кроме случаев, когда используется предложение NOCOPY, описанное далее). Во время выполнения программы, при присвоении значения выходному параметру, создается его внутренняя копия. Когда программа успешно завершается и возвращает управление вызывающему блоку, значение этой локальной копии передается фактическому выходному параметру и становится доступным в вызывающем блоке.
Применяя выходные параметры, вы должны обязательно учитывать следующие их особенности.
Q Значение выходного параметра невозможно присвоить другой переменной или использовать в выражениях, присваивающих значения этому же параметру. В некоторых случаях Oracle позволяет применять выходные параметры иначе, чем предписывает их определение, однако таких ситуаций лучше избегать.
О Для выходного параметра нельзя установить значение по умолчанию. Ему можно присвоить значение только в теле модуля.
О Если в программе инициируется исключение, значения выходных параметров отменяются. Поскольку значения этим параметрам до корректного завершения программы не присваиваются, любые промежуточные изменения игнорируются. Даже если обработчик перехватывает исключение и присваивает выходному параметру значение, эта операция не выполняется, и данная переменная будет иметь то же значение, что и до вызова программы.
О Фактический параметр, который соответствует формальному выходному параметру, должен быть переменной. Он не может представлять собой константу, литерал или выражение, поскольку эти объекты не позволяют PL/SQL поместить выходное значение.
Режим IN OUT
Используя режим IN OUT, значения параметра можно передавать в модуль и возвращать их обратно вызывающей программе. Возвращать можно как начальное, не измененное значение, так и новое, установленное внутри модуля. Параметры с режимом использования IN OUT, как и выходные параметры, имеют ряд особенностей.
О Во-первых, для них нельзя задать значение по умолчанию.
О Во-вторых, фактический параметр, который соответствует формальному, должен быть переменной. Он не может представлять собой константу, литерал или выражение, поскольку эти объекты не позволяют PL/SQL поместить выходное значение.
Какие-либо другие ограничения по применению параметров в режиме IN OUT отсутствуют. Эти параметры можно указывать с обеих сторон оператора присваивания, поскольку они ведут себя как инициализированные переменные. Начав выполнение программы, PL/SQL сохраняет значение рассматриваемого параметра и при необходимости использует его в программе.
Процедура combi ne_and_format_names, код которой приведен ниже, объединяет имя и фамилию в полное имя, применяя указанный формат («ФАМИЛИЯ, ИМЯ» либо <ИМЯ ФАМИЛИЯ^). Для выполнения этой операции процедуре требуется передать имя и фамилию. Затем мы переводим символы имени и фамилии в верхний регистр, чтобы привести их к стандарту. Заметьте, что в этой процедуре используются все три режима передачи параметров: IN, IN OUT и OUT.
PROCEDURE combi njand_format_name5
(f1rst_name_lnout Tn DUT VARCHAR2.
1ast_name_Tnout IN OUT VARCHAR2..
full_name out OUT VARCHAR2,
name"formatjn IN VARCHAR2 :- 'LAST. FIRST')
IS -'
BEGIN'
/* Преобразование символов имени и фамилии 8 символы верхнего регистра */
flrst_narnejnout :■ UPPER (first_narnejnout);
ast_narne_1nout :- UPPER (last^namejnout):
/* Объединение имени и фамилии в соответствии с форматом */
IF name format in - 'LAST, FIRST'
THEN 一 ~
full_name_out :- last_name_inout 11 1, 1 11 f1rst_namejnout:
ELSIF name_forniatjn - 'FIRST LAST'
THEN 一 "
full_name out :■ first_name_inout 11 1 ' 11 last^namejnout:
END IF ~~~~- 一
END:
Имя и фамилия должны быть параметрами с режимом использования IN OUT. Параметр full_narne_out является только выходным и служит для возврата полного имени. Если фактический параметр, который применяется для получения полного имени из процедуры, до передачи в процедуру имел некоторое значение, то в ней не стоит явно его использовать. Параметр narne^formatjn является входным, поскольку он лишь задает формат полного имени и не изменяется.
Каждый режим параметра имеет свои особенности и назначение. Для того чтобы режимы применялись в модуле правильно, к их выбору следует подходить очень внимательно.
Методы передачи параметров
Начиная с версии PL/SQL 8.1 существует возможность определять метод передачи параметров в подпрограмму. Реализуется она с помощью предложения NOCOPY, которое указывает компилятору, как нужно работать со структурами данных, имеющими режим использования OUT или IN OUT. Значения параметров в подпрограмму могут передаваться следующим образом.
О По ссылке. Если фактический параметр передается по ссылке, то с соответствующим формальным параметром связывается указатель на данный параметр. После этого оба параметра ссылаются (указывают) на одну и ту же ячейку памяти, в которой содержится требуемое значение.
О По значению. Когда фактический параметр передается по значению, его значение копируется в соответствующий формальный параметр. Если впоследствии программа завершается без инициирования исключений, значение формального параметра присваивается фактическому. В случае же возникновения ошибки измененное значение обратно не передается.
Каким образом выполняется передача параметров в PL/SQL, когда предложение NOCOPY не используется, можно судить по приведенной ниже таблице.
РаиимПереда” (по умолчанию}
INПо ссылке
OUTПо значению
]N OUTП。знахенмю
Если в качестве параметров передаются большие структуры данных (коллекции, записи или экземпляры объектных типов), объявленные с ключевыми слонами OUT или IN OUT, то применяется передача по значению, которая может существенно замедлить работу программы и увеличить объем потребляемой ею памяти.
Для предотвращения таких ситуаций и служит предложение NOCOPY. Применяется оно следующим образом:
имя_пдраметрд [IN | IN OUT 丨 OUT 丨 IN OUT NOCOPY | OUT NOCOPY] тип^анных_параметра
Обратите внимание, что предложение NOCOPY используется только совместно с режимами OUT и IN OUT. Список параметров, в котором содержится предложение NOCOPY, приведен ниже:
PROCEDURE analyze_results (
datejn IN DATE.
values IN OUT NOCOPY numbers_array.
vail dity_flags IN OUT NOCOPY validity_rectype
); -"
Применяя предложение NOCOPY, следует учитывать две важные особенности.
О Каждый раз при вызове подпрограммы, которая содержит выходной параметр, объявленный как NOCOPY, значение фактического параметра, соответствующего выходному, устанавливается в NULL.
О Предложение NOCOPY - это указание, а не команда, поэтому не исключено, что компилятор самостоятельно примет решение о невозможности выполнить ваш запрос. Случаи, когда именно это происходит, подробно описаны в следующем разделе.
Ограничения при использовании предложения NOCOPY
Иногда компилятор PL/SQL игнорирует предложение NOCOPY и использует метод передачи по значению, который задан для параметров с режимами OUT и IN OUT по умолчанию. Это может иметь место в следующих ситуациях.
О Фактический параметр является элеме1ггом ассоциативного массива. Предложение NOCOPY применяется к ассоциативному массиву (который может иметь структуру записи), но не к отдельному элементу таблицы. Данное ограничение несложно обойти таким образом: скопировать структуру в переменную, скаляр или запись, а затем передать ее как параметр, объявление которого содержит предложение NOCOPY.
О Наличие определенных ограничений на фактические параметры. Установка некоторых ограничений приводит к тому, что предложение NOCOPY игнорируется. Речь идет об указании количества знаков после запятой для числовой переменной и ограничении NOT NULL. На строковую переменную, объявленную с ограничением длины, это не распространяется.
О Фактические и формальные параметры являются записями. При этом одна или обе записи объявлены с использованием атрибута KROWTYPE или ШРЕ, а ограничения на соответствующие поля этих двух записей разные.
О При передаче фактического параметра PL/SQJ-. должен выполнить неявное преобразование типов данных. Выход из этой ситуации может быть следую- ,щмм. Поскольку всегда лучше использовать явное преобразование типов данных, выполните его, а затем передавайте преобразованные значения как параметры, в объявлении которых имеется предложение NOCOPY.
О Обращение к подпрограмме осуществляется путем виепшего или уделенного вызова процедуры. В таких ситуациях PL/SQL всегда будет передавать фактические параметры по значению.
Использование предложения NOCOPY
В ряде случаев применение NOCOPY позволяет повысить производительность программ, имеющих параметры с режимами IN OUT или OUT. Естественно, приобретение этой выгоды сопряжено с некоторыми проблемами. В частности, если программа завершается с необработанным исключением, значениям фактических N0- COPY-параметров доверять нельзя.
Для того чтобы понять, что же подразумевается в данном случае под словом «доверять», рассмотрим, как PL/SQL поступает с параметрами, когда программа завершается необработанным исключением. Предположим, процедуре calculate totals передана запись в режиме использования IN OUT. Исполняющее ядро PL/SQL создает копию этой записи и во время выполнения программы вносит в нее изменения. Значение фактического параметра не модифицируется до тех пор, пока процедура calculate_totals не будет успешно завершена. Когда это происходит, значение, хранящееся в локальной копии, записывается в фактический параметр, и программа, вызвавшая процедуру calculate_totals, может получить доступ к измененным данным. Если процедура calculdte_total5 заканчивается с необработанным исключением, значение фактического параметра не изменяется.
При использовании предложения NOCOPY мы будем иметь другую ситуацию. Когда параметр передается по ссылке (эффект наличия NOCOPY), любые изменения значения формального параметра сразу же отражаются на значении фактического. Предположим, процедура calculate_t°tals считывает коллекцию из 10 000 строк и вносит изменения в каждую из них. Если на строке с номером 5 000 будет инициировано исключение, которое не обрабатывается процедурой calculdte totdls, значение фактического параметра окажется измененным только ц^половину.
Как вы наверняка убедились, пользоваться предложением NOCOPY следует осторожно и только в тех случаях, когда точно известно, что проблема с производительностью вызвана методом передачи параметров. При этом нужно учитывать возможные последствия, связанные с инициированием исключений.
Значения по умолчанию
В приведенных ранее примерах показано, что входным параметрам можно присваивать значения по умолчанию и тогда их не обязательно включать в список параметров при вызове программы. Значение параметра по умолчанию используется программой только в том случае, если при ее вызове список параметров не содержит этого параметра. Конечно же, вы должны обязательно указывать фактические значения параметров с режимом передачи IN OUT.
Значение параметра по умолчанию подобно значению по умолчанию, которое задается при объявлении переменной. Указать его можно двумя способами: при помощи ключевого слова DEFAULT или посредством оператора присваивания (:="), как показано в этом примере:
PROCEDURE astrology_reading'
(signjn IN VARCHAR2 :- 'LIBRA',
born二dt_in IN DATE DEFAULT SYSDATE) ES
Если значения по умолчанию заданы, в вызов программы не обязательно включать все фактические параметры - для пропущенных параметров программа использует значения по умолчанию. Если параметр задан, его значение заменяет значение по умолчанию. Ниже приведены различные способы вызова процедуры astrology_reading с использованием связывания по позиции:
BEGIN
astrolоду reading('SCORPIO',
ТО_ОАТЁ ('12-24-2001 17:56:10'. 'MM-DD-YYYY HH24:MI:SS'));
astroTogy_readlng('SCORPIO'):
astrology_read1ng; END;
В первом вызове значения обоих параметров заданы явно, во втором указано только значение первого фактического параметра, поэтому переменная born_at_in будет равна текущей дате и времени. В третьем вызове параметры вообще не указаны, поэтому в нем нет круглых скобок. При этом в теле процедуры используются оба значения по умолчанию.
Как поступить» если в процедуре нужно задать время рождения, но не указывать знак зодиака? Для того чтобы пропустить первый параметр и присвоить ему значение по умолчанию, необходимо применить связывание по имени. Указывая имена формальных параметров, вы можете задавать только те( которым хотите назначить значения явно. В приведенном ниже вызове для первого параметра применяется значение по умолчанию — LIBRA, а текущее время заменено новым значением:
BEGIN
astrology_read1ngt
born_at_1n ->
ТО DATE ('12-24-2001 17:56:10', 'MM-OO-YYYY HH24:M1:SS')):
END;・
Локальные модули
Локальный модуль 一 это процедура или функция, которая определена в разделе объявлений блока PL/SQL (анонимного либо именованного). Локальным такой модуль считается по той причине, что он определен только внутри родительского блока PL/SQL и не может быть вызван из какого-либо другого блока, определенного вне родительского.
Рассмотрим в качестве примера рис. 16.4. Представленные на нем блоки, внешние для процедуры calc_overdue_fines, не могут непосредственно вызывать ее локальные процедуры или функции.
Синтаксис объявления локальной процедуры или функции совпадает с используемым для отдельного модуля. Например, в анонимном блоке, который приведен ниже, объявлена локальная процедура show_date:
DECLARE
PROCEDURE show_date (datejn IN DATE) IS BEGIN
DBMS OUTPUT.PUT LINE (TO CHAR (date 1n. 'Month DD, YYYY'));
END; BEGIN
END;
Рис. 16.4 Локальные модули вне процедуры недоступны
В разделе объявлений локальные модули должны быть расположены после остальных объявлений, Таким образом, объявить переменные, курсоры» исключения, типы, записи, таблицы и т, д. необходимо до первого ключевого слова PROCEDURE или FUNCTION.
О том, что дает нам использование модулей, расскажем в следующем разделе.
Преимущества локальной модуляризации
Использование локальных модулей обеспечивает следующие преимущества.
О Уменьшение объема кода за' счет исключения повторяющихся фрагментов. Это послужило наиболее веской причиной создания локальных модулей. Уменьшение объема кода сопровождается повышением его качества, так как число строк и количество потенциальных ошибок в них уменьшаются, а поддержка кода значительно упрощается. Внесение изменений производится только в локальном модуле, а результаты распространяются на все приложение,
О Улучшение читабельности кода. Даже если модуль не содержит повторяющихся блоков кода, взаимосвязанные операторы лучше поместить в локальный модуль. В этом случае вам будет легче отслеживать логику, заложенную н родительском модуле.
Уменьшение объема кода
Рассмотрим на примере, как можно сократить обзем кода. Процедура cal ^percentage принимает числовые значения из пакета sales_pkg, рассчитывает долю каждой продажи н объеме продаж (это значение передается как параметр) и затем форматирует число! которое будет отображено в отчете или форме. Приведенный
пример включает только три однотипных расчета, однако мы взяли его из реального приложения, в котором таких расчетов было 231.
PROCEDURE calc_percentages (tot_salesjn IN NUMBER)
IS一
BEGIN
:profi1e.food_sales_stg :-
T0_CHAR ((sales pkg.food_sales / tot_sales_1n) * 100.
~'$999999'): 一一 -
:profi1e.servlce_sales^stg :=
TO_CHAR ((sales_pkg.servlce_sales / tot_sales in) * 100, ~'$999999');~一 一
;profi1e.toy^sales_stg 尸
TO_CHAR ((sales_pkg.toy_sales / tot_sales_1n) * 100,
一'$999999'); ~~"
END;
Этот код требует довольно много времени для ввода и не удобен в сопровождении. Если придется изменять формат представления или алгоритм вычисления процентов, то правки, нужно будет вносить в каждое выражение.
Использование же локальных модулей позволяет весь повторяющийся код сконцентрировать в одной функции, которая затем будет многократно вызываться в процедуре calc_percentages. Версия этой процедуры с локальным модулем показана ниже:
PROCEDURE calc_percentages (tot_sales in IN NUMBER)
IS""
/*Обьявление функции непосредстзенно в процедуре*/
FUNCTION pct_stg (valjn IN NUMBER) RETURN VARCHAR2
is f 一
BEGIN
RETURN TD_CHAR C(val_in/tot salesJn)*100. '$999999'):
END;- 一
BEGIN
:prof11e.food_sa1es_stg :- pct_stg Csales_pkg.food_sales);
:profi1e.service_saTes_stg := pct_stg (saTes_pkg.service_sales):
profile.toy_sales_stg :- pct_stg (sales pkg.toy^sales);
END;~~~~~
Все расчеты — деление на значение переменной tot_sales_1 п, умножение на 100 и форматирование с помощью функции TO_CHAR — перенесены в функцию pct_stg, которая определяется в разделе объявлений процедуры. Благодаря наличию вызовов этой функции в теле процедуры calc^percentages исполняемые операторы последней значительно упростились. Кроме того, теперь при изменении расчетной формулы достаточно будет внести правку в локальную функцию, и это отразится на всех операторах присваивания процедуры.
Улучшение читабельности кода
Использование локальных модулей позволяет улучшить читабельность кода, упростить его сопровождение и реализовать такие методы, как нисходящее проектирование или пошаговую детализацию. Кроме того, локальные модули можно применять для «декомпозиции» существующей программы с целью улучшения ее читабельности.
Конечным результатом такого использования локальных модулей является
уменьшение объема исполняемого раздела программы, поскольку код, реализующий логику приложения, переносится из исполняемого раздела в локальный модуль, вызываемый из этого раздела. Если программа имеет небольшой исполняе
мый раздел, ее намного проще читать и воспринимать.
ПРИМЕЧАНИЕ—:
Считается, что объем кода исполняемого раздела не должен превышать 60 строк (объем текста, который помещается на страницу или экран). Вначале этот совет может показаться странным, но, последовав ему, вы обнаружите, что он весьма полезен.
Предположим, что имеется набор циклов WHILE (отдельные из них являются вложенными), которые содержат сложные вычисления и вложенные условные операторы. Даже при наличии подробных комментариев довольно сложно проследить порядок выполнения операторов программы, если ее код состоит из нескольких страниц. Еще труднее это сделать, когда оператор IF или LOOP конструкции находится на одной странице, а оператор END IF или END LOOP — на другой.
Если же перенести связанные между собой операторы в один или несколько локальных модулей, а затем вызывать эти модули в программе, программа будет документировать сама себя. Приведенная ниже процедура assign_workload является лишь упрощенной реализацией данного подхода, однако она хорошо демонстрирует преимущества, предлагаемые локальными модулями:
PROCEDURE assign_workload (departmentjn IN e叩.deptnoZTYPE)
IS"~
CURSOR enipsjn dept_cur (departmebtjn IN emp.deptnorTYPE)
IS 一 ~~~
SELECT * FROM emp WHERE deptno = departmentJn:
PROCEDURE ass1gn_next_open_case
(emp_id_in IN NUMBER, case_out OUT NUMBER)
IS"
BEGIN полная реализация ... END;
FUNCTION next_appointment (case_id in IN NUMBER)
RETURN DATE- ~
IS■
BEGIN полная реализация ... END:
PROCEDURE schedule_case
(casejn IN NUMBER, datejn IN DATE)
IS"
BEGIN ... полная редпазация ... END;
BEGIN /*осноаной*/
FOR emp rec IN emps_1n_dept_cur (department_1n) ''
LOOP ~--
IF analysis.caseload (emp_rec.enp_id) <
analysis.avg_cases (departmentJn);
THEN一~
assign_next_open_case(emp_rec.e!np_ld, case#);
scheduTe_case ~-~
(case#, next_appointment (case#)):
ENO IF;
END LOOP;
END ass1gn_workload:
В процедуре assign_workload объявлены три локальных модуля:
ass1gn_next_open_ca s е next_appointment schedule_case
Кроме них, процедура вызывает analysis.caseload и analysis.avg_cases 一 две программы из пакета, которые уже созданы и легко могут быть включены в нее. Для того чтобы понять, как работает процедура assign_workload, нет надобности досконально изучать весь ее код. При чтении кода, находящегося в теле процедуры, по именам локальных модулей, даже в отсутствие комментариев, легко догадаться, что делает каждый из них. Конечно, данный подход даст желаемый результат лишь при условии) что вы будете назначать процедурам и функциям информативные имена.
Область видимости локальных модулей
Раздел объявлений локального модуля очень похож на тело пакета, также содержащее объявления модулей (см. главу 17). Различие между локальными и пакетными модулями заключается в их областях видимости: локальные модули могут быть вызваны только из блока, в котором объявлены, а пакетные — как минимум из самого пакета. Если же пакетные модули указаны в спецификации пакета, то они могут быть вызваны из любой программы приложения.
Таким образом, локальные модули следует использовать для инкапсуляции кода)KOTopbdt вызывается только в текущей программе. В других ситуациях необходимо создавать пакет.
Локальные модули как средство повышения качества кода
Если перед вами поставят задачу, реализация которой потребует кода длиной более 20 строк, то вы, скорее всего, создадите в программе несколько локальных модулей. Так значительно легче найти решение задачи, поскольку представление на уровне модулей обеспечивает более высокий уровень абстракции. Кроме того, модульный подход позволяет применить методы нисходящего проектирования и пошаговой детализации. Даже если модуляризация проведена лишь на уровне одной программы, локальный модуль впоследствии легче извлечь из нее и превратить в независимую, доступную для повторного использования процедуру или функцию.
Перегрузка модулей
Программы, которые существуют в одной и той же области видимости и имеют одинаковые имена, называются пере理)уженными. PL/SQL поддерживает перегрузку процедур и функций в разделе объявлений блока (именованного или неименованного), в теле и спецификации пакета, а также в объявлении объектного типа.
Перегрузка — это очень мощный прием, и вы должны научиться использовать все его возможности.
В приведенном ниже простом примере содержатся три перегруженных модуля, которые определены в разделе объявлений анонимного блока, а следовательно, являются локальными:
DECLARE
/* Первая версия принимает параметр типа DATE */
FUNCTION value_ok Cdstejn IN DATE) RETURN BOOLEAN IS
BEGIN-
RETURN datejn <- SYSDATE;
END;-
/* вторая версия принимает параметр типа NUMBER */
FUNCTION value ok (numberjn IN NUMBER) RETURN BOOLEAN IS
BEGIN ~"
RETURN numberjn > 0:
END;-
/* Третья версия - это процедура! */
PROCEDURE value_ok (number In IN NUMBER) RETURN BOOLEAN IS BEGIN
IF number In > 0 THEN
DBMS OUTPUT.PUT LINE (number 1n || 'Ok!');■
ELSE
DBMS_OUTPUT.PUT_LINE (number_1n || '1s not Ok!');'
END IF;'
ENO:
BEGIN
Когда исполняемое ядро PL/SQL встречает в протрамме строку вида
IF value^ok (SYSDATE) THEN
список фактических параметров сравнивается со списками формальных параметров различных перегруженных модулей. В результате PL/SQL выполняет код того модуля, для которого указанные списки совпадают.
ПРИМЕЧАНИЕ
Перегрузку также называют статическим полиморфизмом. Термин «полиморфизм» означает возможность языка определять и использовать несколько программ с одинаковыми именами, Если решение о том, какой вариант программы вызывать, принимается на этапе компиляции, — это статический полиморфизм. Когда такое решение принимается во время выполнения, то говорят о динамическом полиморфизме,
Перегрузка может существенно упростить работу программиста, так как она позволяет сгруппировать под одинаковыми именами интерфейсы вызова нескольких похожих программ. В результате отпадает необходимость помнить, к примеру, имена шести подпрограмм, добавляющих значения в таблицы PL/SQL (дату, строку, булево значение, число и т. д.). Вы просто указываете, что вам необходимо добавить значение, и передаете его компилятору, а PL/SQL и перегруженные модули определят ваши намерения и выполнят всю работу.
На создание перегруженных модулей может потребоваться дополнительное время, однако эти затраты окупятся, поскольку ваши программы станут более эффективными и удобными в применении.
Использование перегрузки
Перегрузка модулей является наиболее подходящим решением в следующих ситуациях.
О Необходимость поддержки разных типов и наборов данных. Когда одно и то же действие применяется к разным видам или наборам данных, перегрузка обеспечивает несколько вариантов активизации этого действия, а не просто дает возможность назвать различные действия одним и тем же именем.
О Настройка программы под различные требования. Для расширения сферы применения программы можно создать несколько ее версий, снабдив их различными вариантами вызова. При реализации этой задачи часто требуется производить перегрузку процедур и функций. В пользу необходимости выполнения таковой может свидетельствовать создание невостребованного, «лишнего» кода. К примеру, работая с пакетом OBMS_SQL, вы вызываете функцию OBMS_ SQL.EXECUTE, однако при выполнении ею DDL-инструкций возвращаемое значение игнорируется. Если для этой функции создать перегруженную процедуру, DDL-инструкцию можно выполнить следующим образом:
BEGIN
DBMS_SQL.EXECUTEC'CREATE TABLE xyz,
Без применения перегрузки вы должны вызвать функцию:
DECLARE
feedback PLSJNTEGER:
BEGIN~
feedback := DBMSJQL.EXECUTE ('CREATE TABLE x”
а затем игнорировать (не использовать) переменную feedback.
О Необходимость выполнить перегрузку по типу, а не по значению. В этом случае при определении того, какая из перегруженных подпрограмм должна вызываться, учитывается тип данных, а не их значение. Этот способ перегрузки применяют очень редко. Продемонстрировать его можно на примере функции DBMS_SQL. DEFINE_COLUMN. Работая с ней, пакету DBMS_SQL требуется указать тип каждого столбца, полученного при помощи динамического запроса. Для задания числового столбца можно использовать вызов.
DBMS_SQL.DEFINE_COLUMN (cur. 1. 1):
или вызов
DBMS_SQL.DEFINE_COLUMN (cur, 1. DBMS_UirLITY.GET_TIME);
Как видите, в данном случае само значение этого числа не важно.
Далее мы рассмотрим пример использования перегрузки, а затем поговорим о связанных с ней ограничениях.
Поддержка разных типов данных
Перегрузку рекомендуется использовать для применения одного и того же действия по отношению к разным видам или наборам данных. Как была отмечено рз- нее, в данном случае перегрузка обеспечивает несколько способов активизации этого действия. Перегрузка с целью поддержки нескольких типов данных реализована в процедуре DBMS_OUTPUT.PUT_LINE, однако на тип данных BOOLEAN такая возможность не распространяется.
Рассмотрим фрагмент спецификации пакета DBMS.OUTPUT:
CREATE OR REPLACE PACKAGE DBMS_OUTPUT
AS
PROCEDURE putjlne (a VARCHAR2):
PROCEDURE putjine (a NUMBER);
END DBMS_OUTPUT:
Как видите, вы не можете использовать пакет DBM工OUTPUT для вывода значения переменной типа BOOLEAN, поэтому для отображения ее значения придется писать оператор IF, подобный приведенному ниже:
IF I_student_is_registred
THEN~ 一 一
DBMS OUTPUT.PUT LINE ('TRUE');
ELSE -
D0MS_OUTPUT.PUT_LINE ('FALSE'):
END IF;"
К счастью, эту проблему довольно легко решить — достаточно создать свой пакет с перетруженными процедурами DBMS_OUTPUT. PUT_LI NE. Пример такого пакета приведен ниже. Вы также можете расширить свои возможности, как это сделали мы с помощью процедуры do.pl:
/* Файл в web: do.pkg */
CREATE QR REPLACE PACKAGE DQ
IS
PROCEDURE pl (booleanjn IN BOOLEAN);
/* Вывод строки */
PROCEDURE pl (charjn IN VARCHAR2);
/* Вывод строки и значения переменной типа Boolean */
PROCEDURE pl (
charjn IN VARCHAR2, booleanjn IN BOOLEAN):
PROCEDURE pl Cxmljn IN SYS.XHLType);
END DO;
Данный пакет устанавливается поверх пакета DBMS_OUTPUT и дополняет возможности имеющейся в нем процедуры. В частности, с помощью процедуры do.pl отображать значения типа BOOLEAN можно без использования оператора IF: declare
v_is_valid BOOLEAN :-
book_info.is_va]id_isbn ('5-88888-66');
BEGIN
do.pl (v_1 s_valid):
Процедуру do. pl можно применять и при работе с относительно ложными типами данных, такими как XMLType:
/* Файл в web: xml type.sql */
DECLARE
one report XMLTYPE;
BEGIN ~
SELECT ea.report INTO one_report
FROM env_analysis ea
WHERE company - 'SMOKESTAX INC;
do.pl (one_report);
END: -
Ограничения на использование перегрузки
Выполняя перегрузку, необходимо учитывать несколько обстоятельств. Во-первых, исполнительное ядро PL/SQL, как на этапе компиляции, так и на этапе запуска, должно быть в состоянии отличить друг от друга перегруженные версии подпрограммы. Во-вторых, оно не может выполнять две подпрограммы одновременно и при компиляции кода PL/SQL все «неподходящие» перегруженные модули будут проигнорированы. Так как все перегруженные версии программы имеют одинаковые имена, исполнительное ядро PL/SQL не может различить их по именам. Для определения того, какую из них следует выполнить, PL/SQL использует списки параметров и/или сведения о типе программы (процедура или функция). Все это приводит к тому, что на перегруженные программы накладывается ряд ограничений.
О У перегруженных программ типы хотя бы одного параметра должны принадлежать к разным семействам. Типы данных INTEGER, REAL, DECIMAL, FLOAT и другие являются числовыми подтипами, а типы данных CHAR, VARCHAR2 и LONG - символьными. Если соответствующие параметры отличаются только подтипом, то есть принадлежат к одному супертипу, у PL/SQL не будет достаточно информации для определения того, какую из программ выполнять.
О Если у перегруженных программ различны только имена параметров, то такие программы должны вызываться с использованием метода связывания по имени. Когда имя аргумента не указано, то как в этом случае компилятор сможет различить вызовы двух перегруженным процзаммам? И вообще, не стоит использовать связывание по имени как единственно возможный подход.
О Список параметров перегруженных программ должен различаться ие только режимом использования параметров. Даже если в одной версии для параметра задан режим IN, а в другой — IN OUT, PL/SQL не видит различия в вызовах программы.
О Перегруженные функции не могут различаться только типом возвращаемого значения (указан в предложении RETURN функции). В момент вызова перегруженной функции компилятор не знает, значение какого типа она будет
возвращать. Поэтому он не может определить, какую из версий функции использовать, если все остальные параметры идентичны.
О Все перебуженные программы должны быть объявлены в одном и том же блоке PL/SQL или иметь одну область видимости (анонимный блок, пакет, отдельная процедура или функция). Нельзя определить одну версию программы в одном блоке (с его областью видимости), а другую — в другом. Вы не можете перегружать две отдельные программы, так как в этом случае одна из них просто заменит другую.
Предобъявления
Язык PL/SQL требует, чтобы элементы были объявлены до того, как они будут использоваться в коде программы. Но как PL/SQL может определить, насколько корректно вы их применяете? Если одни модули вызывают другие, может возникнуть ситуация, когда невозможно объявить все модули до того, как встретятся ссылки на них. Например, что делать, если программа А вызывает программу Б, а программа Б, в свою очередь, — программу А? К счастью, PL/SQL поддерживает рекурсию, в том числе и взаимную, при которой две или несколько программ явно либо неявно вызывают друг друга.
Тем, кто использует взаимную рекурсию, будет приятно узнать, что PL/SQL позволяет выполнять предобъявление локального модуля, то есть объявлять модуль до фактической его реализации. Это дает возможность вызывать модуль из других программ до его определения.
Предобъявление состоит из заголовка программы, за которым следует точка с запятой. Эта конструкция называется заголовком модуля. Такой заголовок включает список параметров (и предложение RETURN — для функций) и предоставляет всю информацию, которая необходима PL/SQL для объявления модуля и правильной обработки любых обращений к нему.
В приведенном ниже примере внутри процедуры определяются две взаимно рекурсивные функции. Следовательно, заголовок второй функции, tota1_cost, необходимо определить до объявления функции net ^profit:
PROCEDURE perform_calcs (year_in IN INTEGER)
IS""
/* Только заголовок плл функции total cost */
FUNCTION total_cost (...) RETURN NUMBER;
/* Функция net profit, использующая функцию total cost */
FUNCTION net_prof1t (...) RETURN NUMBER IS "
BEGIN
RETURN tot_sales (...) - total cost C...);
END;一一
/* Функция total_cost. использующая функцию net_profit */
FUNCTION total_COSt (...) RETURN NUMBER IS -
BEGIN
IF «условие, основанное параметра—
THEN
RETURN net_profit (...) * 0.10;
ELSE
RETURN «значение параметра>\
END IF:
END;
BEGIN
END:
Существует ряд правил, о которых необходимо помнить при использовании пред объявлений.
О Предобъявления нельзя применять по отношению к переменным или курсорам. Эта техника «работает» только с модулями (процедурами и функциями).
О Определение предобъявленной программы всегда должно находиться в разделе объявлении того блока PL/SQ.L, в котором используется пр ед объявление (анонимного блока, процедуры, функции или тела пакета).
Вам нередко придется сталкиваться с ситуациями, когда без предобъявлений не обойтись, во многих случаях они помогают сделать код легким для чтения. Однако рекомендуем использовать их только в тех случаях, когда это действительно необходимо.
Дополнительные вопросы
Этот раздел будет полезен в первую очередь опытным программистам, поскольку в нем рассматривается ряд сложных вопросов, связанных с модуляризацией, в частности вызов функций в SQL, использование табличных и детерминированных функций.
Вызов пользовательских функций в SQL
Oracle позволяет вызывать пользовательские функции, что дает возможность расширить язык SQ.L и адаптировать его под приложение.
Требования к вызываемым функциям
Для того чтобы созданную программистом функцию PL/SQL можно было вызывать из инструкций SQL, она должна отвечать следующим требованиям.
О Храниться в базе данных. Функция, определенная на стороне клиента среды PL/SQL, не может быть вызвана из 5О.Ь-инструкции, так как SQ.L не сможет обработать ее вызов.
О Все параметры функции должны иметь режим использования IN. Режимы IN OUT и OUT применять в этом случае нельзя. Отметим, что для параметров функций вообще не рекомендуется назначать режим IN OUT или OUT, поскольку это противоречит идее, что функция должна возвращать одно значение.
О Типы данных параметров функции и тип данных в предложении RETURN должны распознаваться сервером Oracle. Все типы базы данных Oracle используются в PL/SQL, но в PL/SQL добавлены новые типы данных, которые пока не
поддерживаются базой данных. Речь идет о типах BOOLEAN, 8INARY_INTEGER, ассоциативных массивах, записях PL/SQL и определяемых программистом подтипах.
О При работе с версиями РСУБД, предшествующими Oracle8z, для функции, объявленной в пакете, следует использовать директиву RESTRICT_REFERENCES. Если вы хотите вызвать определенную в пакете функцию из SQL, необходимо добавить эту директиву в спецификацию пакета. (Более подробно данная тема освещена в разделе «Директива RESTRICT_REFERENCES (Oracle8 и более ранние версии)» далее в этой главе.)
ПРИМЕЧАНИЕ
По умолчанию пользовательские функции, вызываемые в SQL, оперируют данными одной строки, а не столбца (как агрегатные функции SUM, MIN и AVG). Чтобы создать агрегатные функции, вызываемые в SQL, необходимо использовать интерфейс ODCIAggregate, который является частью среды Oracle's Extensibility Framework. За более детальной информацией по этой теме обращайтесь к документации Orade.
Ограничения для пользовательских функций, вызываемых в SQL
С целью защиты от побочных эффектов и непредсказуемого поведения хранимых функций РСУБД Oracle не позволяет им выполнять в SQL следующие действия.
О Хранимые функции не могут модифицировать таблицы базы данных и выполнять инструкции INSERT, UPDATE и DELETE. Эти ограничения ослабляются, если функция определена как автономная транзакция (описывается в главе 13). В таком случае любые вносимые ею изменения осуществляются независимо от внешней транзакции, в которой выполняется запрос.
Q Хранимые функции, которые вызываются удаленно или в параллельном режиме, не могут читать или изменять значения переменных пакета. Сервер Oracle не поддерживает побочные эффекты, действие которых выходит за рамки сеанса пользователя.
О Хранимая функция может изменять значения переменных пакета, только если она вызывается в списке выборки либо в предложении VALUES или SET. Если хранимая функция вызывается в предложении WHERE или GROUP BY, она не может изменять значения переменных пакета.
Q В версиях, предшествовавших Огас1е8, невозможно вызвать из хранимой функции процедуру RAISE_APPLICATION_ERROR.
Q Хранимая функция не может вызывать другой модуль (хранимую процедуру или функцию), который не соответствует приведенным выше требованиям. «Уровень чистоты» функции определяется уровнем самого «нечистого» из вызываемых ею модулей.
Q Хранимая функция не может ссылаться на представление, которое нарушает любое из предшествующих правил. Представление — это хранимая инструкция SELECT, которая способна вызывать хранимые функции.
Функция DECODE как замена оператора IF
Функция DECODE реализует возможности оператора IF в непроцедурной среде SQL, предоставляемой сервером Oracle. С ее помощью можно создавать матричные отчеты, имеющие фиксированное число столбцов, и задавать в запросе сложные ус、 ловия типа IF.. .THEN...ELSE. Недостатком данной функции является то, что в слу、 чае ее применения код получается сложным. Рассмотрим пример использования функции DECODE, в котором проверяется, принадлежит ли дата заказа определен' ному временному диапазону, и, если это так, то увеличивается значение соответ' ствуюшего счетчика заказов:
SELECT FC.yearenumber,
SUM (DECODE (GREATEST Cship_date. FC.ql_sdate).
sh1p_date,
DECODE (LEAST (sh1p_date. FC.ql_edate). ship_date. 1.
0)."
0)) Ql_results.
SUM (DECODE (GREATEST ($Mp_date, FC.q2_sdate).
sh1p_date,
DECODE (LEAST (shipmate, FC.q2_edate).
ship date, 1.
0)) Q2_results,
SUH (DECODE (GREATEST (ship_date. FC.q3_sdate).
DECODE (LEAST (shipjate, FC.q3_edate), sh1p_date, 1.
0). ~
0)) Q3_results.
SUM (DECODE (GREATEST (ship_date. FC.q4_sdate), ship_date,■
DECODE (LEAST (sh1p_date. FC.q4_edate). ship_date, 1.
0)) Q4_results FROM orders D,
fiscal_calendar FC
GROUP BY yearenumber;
Результат подобного запроса должен выглядеть примерно так:
YEARJUMBER
QI RESULTS
Q2 RESULTS
Q3 RESULTS
04 RESULTS
1993
12000
14005
22000
40000
1994
10000
15000
21000
55004
Хотя с помощью функции DECODE создавать такие отчеты очень удобно, однако код SQL при этом выглядит громоздко. Условие, которое применяется при формировании значения QI RESULTS, звучит так: если дата отгрузки больше или равна дате начала первого квартала и меньше или равна дате конца первого квартала, то к сумме общего числа заказов, отгруженных в этом квартале, необходимо добавить единицу, в противном случае — добавить нуль.
Тем, кто не имеет опыта работы с функцией DECODE, рассматриваемый код покажется сложным. Однако его можно несколько упростить путем создания хранимой функции incrj n_range:
FUNCTION incrjn_range
(ship_datejn IN DATE, sdatejn IN DATE, edatejn IN DATE)
RETURN INTEGER
IS
BEGIN
IF ship_date_1n BETWEEN sdate In AND edatejn
THEN ~~~
RETURN 1;
ELSE
RETURN 0;
END IF:
ENO:
При ее использовании SQL-код предыдущей программы будет выглядеть так:
SELECT FC,year_nufHber,
SUM (5ncr_1n_range (ship_date, ql_sdate, ql_mdate)) Ql_results,
SUM (1ncr_1n_range Cship_date, q2_sdate. q2_edate)) Q2_results,
SUM (1ncrjn_range (sh1p_date, q3_sdate, q3_edate)) Q3_results.
SUM (1ncr_1n_range Csh1p_date, q4_sdate. q4_udate)) Q4_results
FROM orders 0,
f1scal_calendar FC
GROUP BY year_nunber:
Хранимая функция 1ncr_in_range позволила избавиться от избыточного кода, после чего инструкция SELECT стала намного понятнее. Более того, теперь эту функцию можно использовать для аналогичных целей и в других инструкциях SQL,
Директива RESTRICT_REFERENCE (Oracle8 и более ранние версии)
В версиях РСУБД до Oracle8z вызов объявленной в пакете функции из SQL возможен только при наличии директивы компилятора RESTRICT REFERENCES. Указанная директива определяет «уровень чистоты» функции, сообщая Oracle о том, что функция создает определенные побочные эффекты или же что таковые у нее отсутствуют.
Работать с директивой RESTRICT_REFERENCE непросто, поэтому многих PL/SQL- разработчиков порадовало известие о том, что в Oraclc8i она стала необязательной. Материал, представленный в данном разделе, предназначен в первую очередь для тех, кто применяет Огас1е8 и более ранние версии РСУБД.
Для каждой пакетной функции, которую вы хотите использовать в инструкции SQL, требуется задать отдельную директиву и расположить ее после объявления функции в спецификации пакета. При установке «уровня чистоты» с помощью рассматриваемой директивы используется следующий синтаксис: pragma restrict一references•
(HMXjyHKQHi^ WNDS L WNPS]匚 RNDSJ [. RNP5])
Здесь имя^ункции — это имя используемой функции. Ее «уровень чистоты» задается с помощью четырех параметров, указывающих, чего функция не делает: О WNDS (Writes No Database State) - не модифицирует таблицы базы данных;
О WNPS (Writes No Package State) - не модифицирует переменные пакета;
О RNDS (Reads No Database State) 一 не считывает информацию, представленную в таблицах базы данных;
О RNPS (Reads No Package State) — не считывает значения переменных пакета.
Ниже приведен пример того, как устанавливается «уровень чистоты» для двух функций пакета company_f1nancials:
PACKAGE company_f1nancials
IS~
FUNCTION company_type Ctype_code_in IN VARCHAR2)
RETURN VARCHAR2: ~-
FUNCTION company_name (
company_id_in IN company.companyJdXTYPE)
RETURN VARCHAR2;~
PRAGMA RESTRICT一REFERENCES (
company_type, WNDS, RNDS. WNPS. RNPS):
PRAGMA RESTRICT_REFERENCES ( company_name. WNDS. WNPS. RNPS): END conipany_financials;
Функция company_name пакета считывает из базы данных имя указанной компании. Заметьте, что обе директивы размещены рядом, в конце спецификации пакета, поскольку никакая директива не должна следовать непосредственно за спецификацией функции. Кроме того, мы столкнулись здесь с проблемой указания аргументов WNPS и RNPS для обеих функций. Для того чтобы компилятор не смог проигнорировать функцию, Oracle рекомендует установить самый высокий из возможных «уровней чистоты».
1РИМЕЧАНИЕ Если функция, которую вы хотите использовать в SQL, вызывает какую-либо процедуру или функцию пакета, для последней следует задать директиву RESTRICT_REFER.ENCE. Следовательно, неявно вызываемые из SQL программы также должны подчиняться приведенным правилам.
Если функция нарупшт установленную для нее директиву, вы получите сообщение об ошибке PLS-00452: subprogram 'program' violates its associated pragma. Предположим, что тело пакетаcompany_financials выглядит следующим образом:
CREATE OR REPLACE PACKAGE BODY company financials
IS-
FUNCTION company_type (type_code_1n IN VARCHAR2)
RETURN VARCHAR2 -~
IS
v_sal NUMBER;
BEGIN■
SELECT sal INTO v_sal FROM emp WHERE empno = 1;
RETURN 'bigone': ~
END:
FUNCTION company_name (coinpanyjdjn IN company.company_idXTYPE)
RETURN VARCHAR2' ~-
IS
BEGIN
UPDATE emp SET sal = 0;
RETURN 'bigone':
END:
ENO compan^flnancials;
При его компиляции мы получим следующее сообщение об ошибке:
3/4 PLS-00452: Subprogram 'COMPANYJYPE' violates its associated pragma
Объясняется это тем, что функция company_type считывает информацию из базы данных, а для нее установлен уровень чистоты RNDS. Если убрать эту инструкцию SELECT, будет получено такое сообщение:
11/4 PLS-00452: Subprogram 'COMPANY_NAME' violates its associated pragma
В данном случае ошибка появляется потому, что функция compaпу_паше модифицирует базу данных, а для нее установлен уровень чистоты WNDS. Б некоторых случаях функция как будто не нарушает объявленный для нее уровень чистоты и не использует инструкции UPDATE, DELETE или UPDATE, но компилятор ее не обрабатывает. Это может случиться по той причине, что вы вызываете встроенный пакет или каким-либо другим образом нарушаете правила. Еще раз отметим, что при использовании Огас1с8: и более поздних версий РСУБД директива RESTRICT_ REFERENCE не требуется 一 исполнительное ядро автоматически проверит ваш код на наличие нарушений.
Табличные функции
Oracle8t и Oracle9i позволяют создавать табличные функции. Эти функции возвращают результирующий набор данных (коллекцию), который можно рассматривать как реляционную таблицу в предложении FROM запроса. Вот почему их называют табличными. Табличные функции мы рассмотрим в следующих разделах, сконцентрировав ваше внимание на тех из них, которые доступны в Oracle9z.
О Конвейерные функции. Возвращают результирующий набор данных в «конвейерном стиле», то есть возврат данных производится до завершения работы функции (поддерживается в Oracle9i). Конвейерные табличные функции удобно применять для выполнения последовательной трансформации данных, поскольку они позволяют не задействовать локальные структуры данных PL/SQL для хранения промежуточных результатов.
Q Функции трансформации. Участвуют в параллельном выполнении запросов, то есть они могут запускаться одновременно в нескольких параллельных процессах (поддерживается в Огас1е9г).
Далее будет рассказано, как табличные функции следует объявлять и использовать в приложениях.
Вызов функций в предложении FROM
Чтобы обеспечить вызов функции из предложения FROM, требуется выполнить следующие действия.
О В предложении RETURN определить тип возвращаемых функцией данных как коллекцию (вложенную таблицу или VARRAY).
О Убедиться, что все остальные параметры функции имеют режим использования IN и тип данных, поддерживаемый SQL. (В запросе нельзя вызывать функцию, имеющую аргумент типа BOOLEAN.)
О Поместить вызовы функции в операторы TABLE и CAST.
Рассмотрим пример, который работает как в Oracle8i, так и в Oracle9t. Сначала создадим вложенный табличный тип, базирующийся на объектном типе pet_t:
CREATE TYPE pet_t IS OBJECT (
NAME VARCHAR2 C60), breed VARCHAR2 (100). dob DATE):
CREATE ТУРЕ pet_nt.IS TABLE OF pet_t:
После этого создадим функцию pet_fam11y, которая принимает в качестве аргументов два объекта, типа pet_t - mom_i п (мать) и dadjn (отец), и в зависимости от вида животного возвращает вложенную таблицу с информацией о составе его «семьи», определенном в коллекции.
CREATE OR REPLACE FUNCTION pet_fam11y (dad in IN pet t, mom in IN pet t)
RETURN pet_nt""'~"
IS~
l_COUHt PLSJNTEGER;■
retval pet_nt :- pet_nt ();
PROCEDURE extend assign Cpet 1n IN pet t) IS
BEGIN~~
retval.EXTEND;
retval (retval.LAST) :- pet in;
END;"
BEGIN
extend_assign (dadjn):
extend^asslgn (momjn);
IF momjn.breed - 'RABBIT' THEN l_count :- 12:
ELSIF monijn.breed - 'DOG1 THEN. l_count :- 4:
ELSIF moni_in.breed ■ 'KANGAROO' THEN l_count :- 1:
END IF: ~-
FOR Indx IN 1 .. l_count
LOOP"
extend_ass1gn (pet_t (1 BABY' 11 Indx. mom in.breed. SYSDATE)):
ENO LOOP;""-
RETURN retval;
ENO:■
Эту функцию можно вызвать из предложения FROM запроса. Делается это следующим образом:
SQL> SELECT *
FROM TABLE (CAST C
pet_fam11y (
pet t ('Hoppy', 'RABBIT', SYSDATI),
реЛ ('Hippy', 'RABBIT', SYSDATE)
) AS pet_nt
));
NAME BREED DOB
11y叩1P11y叩1PRABBIT 27-FEB-02
11y
叩1P
11y
叩1P
RABBIT 27-FEB-02
RABBIT 27-FEB-02
RABBIT 27-FEB-02
BABY11 RABBIT 27-FEB-02
BABY12 RABBIT 27-FEB-02
Как было отмечено выше, этот подход можно реализовать в версиях Oracle8i и Огас1е9г. Сейчас мы рассмотрим дополнительные возможности, доступные только в Oracle9i.
Конвейерные функции
Конвейерные функции - это функции, которые получают набор строк и возвращают их результирующий набор итеративно, то есть по одной строке. Внутри такой функции содержится оператор PIPE ROW, который вьздает одну строку результата и приостанавливает выполнение функции до тех пор, пока среда, вызвавшая данную функцию, не потребует вывода следующей строки. Этот прием позволяет ♦нанизывать» такие функции друг на друга и организовывать конвейер преобразования данных.
Ниже приведена конвейерная версия функции pet_family:
CREATE FUNCTION pet_family (ddd_1n IN pet t. momjn IN pet_t)
RETURN pet_nt PIPELINED -~~
IS"
l_count PLS_INTEGER;
retval pet_nt :- pet nt ();
BEGIN
SPIPE ROW Cdadjn):
PIPE ROW (moni in):
一
ИIF momjn.breed - 'RABBIT' THEN l_count :- 12:
ELSIF momjn.breed - 'DOG' THEN l_count :- 4:
ELSIF momjn.breed - 'KANGAROO' THEN 1 count :- 1:
ENO IF;~
15
FOR Indx IN 1 ,. 1 count
LOOP~
PIPE ROW Cpet_t ('BABY1 11 Indx, moni In.breed, SYSDATE)):
END LOOP;
20
RETURN;
END:
Изменения, которые были внесены в исходную версию функции, перечислены в следующей таблице.
Строка Описание
2Введено ключевое слово PIPELINED, необходимое для указания Oracle на то, что
строки будут возвращаться последовательно
8, 9,18 Вместо присваивания строк данных коллекции подготавливается строка информации (имеющая ту же структуру, что и строка коллекции, тип которой указан в предложении RETURN функции), а затем, для последовательного возврата данных из функции, используется оператор PIPE ROW
21Введен пустой оператор RETURN (ранее доступный только в процедурах),
применяемый в конвейерных функциях для передачи управления вызывающему блоку
Конвейерные функции можно вызывать из инструкций SQL, как это показано выше, или используя более простой синтаксис:
SELECT *
FROM TABLE (pet_fdmily (
"pet_t ('Bob'. 'KANGAROO'. SYSDATE). pet_t 「Sally'. 'KANGAROO'. SYSDATE)));
Таким образом, теперь нет необходимости использовать функцию CAST для явного приведения типа коллекции к типу, распознаваемому базой данных.
Какие же преимущества дает использование конвейерных функций? В первую очередь повышается эффективность приложений, в которых работа со строками данных должна начинаться до формирования полного результирующего набора строк. В конце следующего раздела будет приведен сценарий, выполняющий сравнение производительности разных приложений.
Функции тра нсформа ции
Функции трансформации —. это конвейерные функции, которые принимают в качестве параметра результирующий набор данных (с помощью курсорного выражения) и возвращают также результирующий набор данных. Функции такого типа поддерживаются в Oracle9z и могут обеспечить существенное повышение производительности приложения.
Вы можете определить табличную функцию с IN-аргументом типа REF CURSOR и вызвать ее, задав курсорное выражение в качестве фактического параметра. Такой прием позволяет создать конвейер для передачи данных от одной функции к другой или из одной инструкции SQL в следующую, без организации промежуточного хранения данных. Рассмотрим, как использовать эти возможности.
ПРИМЕЧАНИЕ
Приведенный ниже код находится в файле tabfunc.sql на web-узле издательства O'Reilly.
Предположим, что у нас имеется таблица с информацией, полученной от тикера (биржевого аппарата, передающего котировки ценных бумаг). Она содержит отдельную строку для цен на момент открытия и закрытия биржи:
CREATE TABLE StockTdble (
ticker VARCHAR2(10),
open_price NUMBER.
close_price NUMBER):
Эту информацию необходимо поместить в другую таблицу:
CREATE TABLE TickerTable С
ticker VARCHAR2C10),
PrlceType VARCHAR2C1),
price NUMBER):
Другими словами, одна строка таблицы StockTabl е должна быть преобразована в две строки таблицы TickerTable. Существует несколько путей достижения этой цели. При использовании традиционных методов в версиях до Oracle9i можно написать код, подобный этому:
FOR rec IN (SELECT * FROM stocktable)
LOOP
INSERT INTO tickertable
(ticker, pricetype, price)
VALUES Cree.ticker. 'O'. rec.open_price);
INSERT INTO tickertable
(ticker, pricetype, price)
VALUES (rec.ticker, 'C. rec.close_price);
END LOOP;-
Однако при наличии больших объемов данных программа будет работать не так эффективно, как хотелось бы. Рассмотрим, как с помощью функции трансформации можно ускорить выполнение этой задачи.
Сначала создадим тип коллекции, которая будет использоваться в функции:
CREATE TYPE TickerType AS OBJECT (
ticker VARCHAR2(10).
PrlceType VARCHAR2(1),
price NUMBER);
CREATE TYPE TIckerTypeSet AS TABLE OF TickerType:
Затем создадим пакет, в спецификации которого содержится объявление типа
REF_CURSOR (что позволяет ссылаться на нега из функции):
CREATE OR REPLACE PACKAGE refeur pkg
IS"
TYPE refcur_t IS REF CURSOR
RETURN StockTableXROWTYPE;
End refeur^pkg:
Код функции трансформации имеет следующий вид:
CREATE OR REPLACE FUNCTION StockPIvot (
p refcur_pkg.refcur_t) RETURN TIckerTypeSet
PIPELINED ~
IS
out_rec TlckerType ;- TickerType (NULL. NULL. NULL):
in_rec pIROWTYPE;
BEGIlf
LOOP
FETCH p INTO in_rec;
EXIT WHEN pXNOTFQUND:
--первая строка
out_rec.ticker :- in_rec.Ticker;
out_rec.PrlceType :- 'O':
out_rec.price ;- in_rec.Open_Price;
PIPE ROW CouVec):" -
—вторая строка
out_rec.PriceType :- 'C ;
out_rec.Price :口 in_rec.Close_Price;
PIPE ROW (ouVec)~
END LOOP: ~
CLOSE p;
RETURN:
END;
Вот та же самая функция, определенная без использования конвейера:
CREATE OR REPLACE FUNCTION StockPIvot.nopl (
p refcurj)kg.refcur_t)'
RETURN TIckerTypeSet
IS
out_rec TlckerType :- TIcKerType (NULL, NULL, NULL);
1n_rec pXROWHPE;
retval HckerTypeSet :■ TIckerTypeSetO:
BEGIN
retval.DELETE:
LOOP
FETCH p INTO 1n rec;
EXIT WHEN pXNQTFOUND;.
out^rec.ticker :- In^rec.Ticker;
out_rec.PriceType :- 'O':
out rec.price :- in rec.Open Price;
retval.EXTEND: ~~
retval (retval.LAST) ;- out_rec;
out_rec.PriceType :- 'C ;
out_rec.Price :- 1n_rec.Close_Pr1ce:
retval.EXTEND;
retval (retval.LAST) ;- out_rec;.
END LOOP:
CLOSE p:
RETURN retval \
END;
Как видите, существует несколько подходов к решению одной и той же задачи. Вы спрашиваете, какой из них выбрать? При обработке больших объемов данных предпочтение следует отдать наиболее эффективной реализации. В файле tabfunc.sql1 вы найдете анонимный блок, который сравнивает производительность четырех подходов.
Непосредственная вставка из инструкции SELECT с использованием конвейерной функции:
INSERT INTO tickertable
SELECT *
FR酬 TABLE (StockPivot (CURSORCSELECT * FROM StockTdble)));
Непосредственная вставка из инструкции SELECT с использованием неконвейерной функции:
INSERT INTO tickertable
SELECT * F№ TABLE (StockPivotjopl (CURSORCSELECT * FROM Stock!a^e))k
О Хранение трансформируемых данных в локальной коллекции. Для передачи содержимого коллекции в таблицу можно использовать простой цикл:
OPEN curvar FOR
SELECT * FROM stocktable:
nystock :- stockpivot_nopl Ccurvar);
1ndx :- nystock.FIRST;
LOOP
EXIT WHEN 1ndx IS NULL:
INSERT INTO tickertable
(ticker, pricetype, price)
VALUES (叫耽ock (Indx) .ticker, mystock (Indx).prlcet^pe, щуstock (-lndx).price):
END LOOP;
О <Старый* метод - использование цикла FOR с курсором для преобразования одной строки таблицы StockTable в две строки таблицы TIckerTable:
FOR rec IN (SELECT * FROM stocktable)
LOOP
INSERT INTO tickertable
(ticker, pricetype, price)
VALUES (rec.ticker. 'O', rec.openjjrice);
INSERT INTO tickertable
《ticker, pricetype, price)
VALUES (rec.ticker. 'C, rec.close_pr1ce);
END LOOP:~
В файле tabfunc.sql используется механизм таймера PL/Vision (PLVtmr), предназначенный для вычисления временя выполнения программы. Этот пакет можно установить, залустив сценарий plvtmr.pkg.
При запуске блока, сравнивающего время выполнения этих четырех подходов в сеансе SQL*Plus, были получены такие результаты:
АП SQL with Pipelining function Elapsed; 2.47 seconds.
All SQL with non-pipelining function El叩sed: 1.78 seconds.
Intermediate collection El叩sed: 6.71 seconds.
Cursor FOR Loop and two inserts Elapsed: 6.9 seconds.
Проанализировав их, можно сделать следующие выводы.
О Использование табличных функций (как конвейерных, так и обычных) при трансформации данных в отдельной SQL-инструкции заметно улучшает производительность.
Q Конвейерная обработка не помогла, а наоборот, уменьшила скорость выполнения. Фактически мы не получили никаких преимуществ от использования конвейера. Во всех случаях, прежде чем приступить к осуществлению любых операций с данными, нам приходилось ждать, пока завершится выполнение логики программы.
Есть надежда добиться улучшения временных показателей при выполнении логики программы в параллельных вычислениях, то есть в случае, когда мы получаем первые ^ строк и начинаем обрабатывать их до поступления остальных данных. Эта ситуация сымитирована в файле tabfunc.sql.
Мы сравнили, сколько времени тратится на выполнение каждой из приведенных ниже инструкций:
--С конвейерной обработкой
INSERT INTO tickertable
SELECT *
FROM TABLE (StockPivot (CURSORtSELECT * FROM StocIcTable)))
WHERE RDWNUM < 10;'■
--Без конвейерной обработки
INSERT INTO tickertable
SELECT *■
FROM TABLE (StockPivot_nopl (CURSORCSELECT * FROM StockTable)))
WHERE ROWNUM < 10;'
Полученные нами результаты свидетельствуют о том, что конвейерные функции работают эффективно:
Pipelining first 10 rows Е】叩sed: .08 seconds.
No pipelining first 10 rows Elapsed: 1.77 seconds •
Функции, доступные для параллельного выполнения
В Oracle9z одним из важных новшеств, касающихся PL/SQL, является возможность выполнения функций в параллельных запросах. В предыдущих версиях Oracle вызов функции PL/SQL из SQL-инструкции приводил к сериализации запроса (это одна из основных проблем, характерных для приложений хранилиш данных). В Огас1е9г в заголовок функции можно добавить информацию» которая
указывает исполнительному ядру, каким образом может быть использована эта функция.
Если вы хотите, чтобы функция выполнялась в параллельном режиме, она должна иметь один строготипизированный входной параметр типа REF^CURSOR1. Примеры использования этого подхода приведены ниже.
О Функция может выполняться в параллельном режиме, и передаваемые в нее данные можно произвольно разбивать:
CREATE OR REPLACE FUNCTION my_transform_fn
Cp_input_rows in employee_1nfо.recur_t)
RETURN employee_info.transfonned_t
PIPELINED "
PARALLEL_ENAaLE CPARTITION p_input_rows BY ANY)
Ключевое слово ANY указывает, что результаты не зависят от порядка, в котором функция получает входные строки. Когда используется это ключевое слово, исполнительное ядро разбивает данные между параллельными процессами случайным образом. Оно подходит для использования в функциях, которые принимают одну строку таблицы, манипулируют ее столбцами и генерируют результирующие строки только на основе столбцов этой строки. Если программа реализует другие зависимости, результат будет непредсказуемым.
О Функция может выполняться параллельно, а все строки, относящиеся к определенному отделу, будут обрабатываться одним процессом и передаваться ему последовательно:'
CREATE OR REPLACE FUNCTION my_transforin_fn (
p_input_rows In employeejnfo.recur_t)
RETURN employee_i nfо.tcansformed_t
PIPELINED ~~
CLUSTER PJNPUT_ROWS BY (department)
PARALLEL EHABLE~
(PARTITION P_INPUT_ROWS BY HASH (department))
В Oracle применяется понятие «кластеризация», используемое для обозначения данного режима обработки, и термин «кластерный ключ» — для обозначения столбца (в данном случае 1 department'), по которому выполняется агрегирование. Важно, что алгоритм не зависит от порядка значений кластерного ключа, и Oracle вообще не гарантирует здесь какой-либо упорядоченности. Это позволяет использовать более быстрые алгоритмы, чем в случае, когда строки должны быть кластеризованы и доставлены в порядке, определяемом значенями кластерного ключа.
В зависимости от имеющейся у нас информации о распределении значений, в этом примере можно применять форму HASH (department) или RANGE (department). Форма HASH (department) обеспечивает большую скорость обработки, и будет естественным использовать ее совместно с предложением CLUSTER... BY.
Входной параметр тапа REF_CURSOR может не быть строготипизированным при разбиении данных с использованием ключевого слова ANY.
О функция может выполняться параллельно, а строки передаются конкретным параллельным процессам, как указано в предложении PARTITION...BY, и сортируются этим процессом локально. Это позволяет распараллелить сортировку:
CREATE OR REPLACE FUNCTION n\y_transform_fn (
p_input_rows In emp 1 oyeejnfо. recur_t)
RETURN empToyee_1nfo.transformed_t
PIPELINED "
ORDER PJNPUT ROWS BY CC1)
PARALLEL ENABLE'
(PARTITION P_INPUT_ROWS BY RANGE (CD)
Поскольку сортировка распараллелена, не нужно использовать предложение ORDER... 0Y в инструкции SELECT, которая применяется для вызова табличной функции. (Фактически наличие предложения ORDER...BY в инструкций SELECT не позволит распараллелить сортировку.) Более логично использовать ключевое слово RANGE с предложением ORDER-.BY, хотя программа и будет работать медленнее, чем с предложением CLUSTER…BY.
ПРИМЕЧАНИЕ Предложение CLUSTER...BY не может применяться вместе с предложением ORDER...BY в объявлении табличной функции. Таким образом, алгоритм, который зависит от кластеризации по одному ключу (например, cl) и использует упорядочивание строк с одинаковым значением ключа кластеризации по другому столбцу (например, с2), должен был бы распараллеливаться с использованием предложения ORDER...BY в объявлении табличной функции.
Детерминированные функции
Функция называется детерминированной, если при одном и том же наборе входных параметров она всегда возвращает одно и то же значение. Пример такой функции приведен ниже:
CREATE OR REPLACE FUNCTION betwnStr (■
stringjn IN VARCHAR2,start_1n IN INTEGER, endjn IN INTEGER'
)~
RETURN VARCHAR2 IS BEGIN RETURN (
SUBSTR C
string 1n. start In, end 1n - startjn + 1));
END;"""~
Если в качестве входных параметров использовать, например, последовательность символов «abcdef», значение 3 (параметр start_1n) и значение 5 (параметр end_in), то сколько бы раз вы ни вызывали функцию betwnStr с этим набором параметров, она всегда будет возвращать подстроку <cde>.
Почему бы Oracle не сохранить результаты, связанные со списком аргументов? Ведь в этом случае при последующем вызове функции с теми же аргументами можно получить результат, не выполняя ее. Такого эффекта можно добиться, добавив предложение DETERMINISTIC в заголовок функции:
CREATE OR REPLACE FUNCTION betwnStr 《
stringjn IN VARCHAR2, startjn IN INTEGER, endjn IN INTEGER
RETURN VARCHAR2 DETERMINISTIC IS
BEGIN'
RETURN (
SUBSTR (
strlngjn, startjn, endjn - startjn +1));
END:
Решение об использовании сохраненной копии возвращаемых функцией результатов (если такая копия доступна) принимается оптимизатором запросов Oracle. Сохраненные копии могут браться из материализованного представления, функционального индекса или повторного вызова одной и той же функции в SQL-инструкции.
ПРИМЕЧАНИЕ для того чтобы функцию можно было вызвать в выражении, основанном на функциональном индексе, или из запроса материализованного представления, помеченного как REFRESH FAST или * ENAMBLE QUERY REWRITE, ее следует объявить как DETERMINISTIC.
В Oracle не имеется надежного способа проверки того, что объявленная как DETERMINISTIC функция не имеет никаких побочных эффектов. Поэтому при использовании таких функций вся ответственность за последствия возлагается на протраммиста. Детерминированная функция не должна основываться на переменных пакета и иметь право изменять результирующие значения в базе данных.
Модульный подход — в жизнь!
По мере развития языка PL/SQL и средств Oracle становятся более сложными и приложения, которые разрабатываются с их помощью. Если вы будете плохо владеть техникой модуляризации в PL/SQL, то вряд ли сможете реализовать крупный проект.
Конечно, мы не смогли описать все особенности модуляризации в PL/SQL, однако надеемся, что представленного нами материала достаточно для того, чтобы подтолкнуть вас к мысли о необходимости совершенствоваться в этой технологии. Вам предстоит изучить полный спектр возможностей пакетов, способы расширения пакетов, которые поставляются совместно с инструментами и базами данных, различные приемы повторного использования кода и многое другое.
Когда вы овладеете техникой модуляризации, то заметите, что тратите больше времени на разработку, а процесс отладки проходит быстрее. Ваши процзаммы станут более читабельными и легкими в сопровождении. Словом, беритесь за дело и внедряйте модульный подход!
1717naKetbinaKetbi
17
17
naKetbi
naKetbi
А Для чего нужны пакеты
А Правила построения пакетов
А Правила вызова элементов пакета
А Работа с данными пакета
А В каких случаях следует использовать пакегты
А Пакеты и объектные типы
Пакет представляет собой сгруппированный по определенным правилам набор элементов кода PL/SQL. Он обеспечивает логическую структуру, предназначенную для организации программ, курсоров, типов данных и переменных. Значимость пакетов объясняется тем, что они позволяют скрывать логику и данные, а также определять глобальные данные, существующие в течение сеанса.
В предыдущих изданиях книги было отмечено, что пакеты относятся к числу наименее популярных средств PL/SQL, хотя на самом деле они крайне необходимы для разработки хорошо структурированных приложений PL/SQL. К счастью, с тех пор ситуация изменилась — разработчики оценили возможности пакетов и стали активно их применять.
Цля чего нужны пакеты
Пакеты — очень важная составная часть языка PL/SQL, без использования которой невозможно создать сложный прикладной проект. Чтобы это понять, рассмотрим, какие преимущества обеспечивают пакеты.
О Упрощают сопровождение и распшрение приложений. При увеличении объема кода качество приложений определяется тем, насколько легко их сопровождать. С этой точки зрения пакеты играют исключительно важную роль, поскольку обеспечивают инкапсуляцию кода (в частности, они позволяют скрыть SQL-инструкции за интерфейсом процедур), дают возможность определить
константы и сгруппировать логически связанные функции. В результате приложение становится более структурированным и надежным.
О Повышают производительность приложений. Во многих ситуациях использование пакетов позволяет повысить производительность и эффективность работы приложений. Определив постоянные структуры данных уровня пакета, можно кэшировать статические значения из базы данных. Это дает возможность избежать повторных запросов и, следовательно, значительно ускорить получение результата. Кроме того, подсистема управления памятью Oracle оптимизирована для доступа к откомпилированному коду пакетов (подробнее об этом рассказывается в главе 20).
О Поддержка приложений и исправление недостатков встроенных элементов. Некоторые из существующих программных компонентов Oracle имеют определенные недостатки, в частности не лучшим образом реализованы важнейшие функции встроенных пакетов UTL_FILE и DBMS_OUTPUT. Мириться с этим совсем не обязательно, можно разработать собственный пакет, устранить значительную часть недостатков и использовать его вместо стандартного. Например, в сценарии файла do.pkg, имеющемся на узле O'Reilly, реализована замена встроенной процедуры DBMS_OUTPUT .PUT_LINE перегруженной версией для типа данных XMLType. Подобного результата можно достичь и с помощью отдельных функций и процедур, но использование пакетов является более предпочтительным подходом.
Концепция пакетов очень проста. Единственная сложность заключается в том, чтобы научиться эффективно применять в приложении их богатые возможности. Прежде чем мы приступим к рассмотрению преимуществ пакетов и обсуждению синтаксиса их определения, внимательно ознакомьтесь с приведенной ниже рекомендацией.
Всегда стройте приложение на основе пакетов и старайтесь не применять отдельные процедуры и функции. Даже если вам сначала кажется, что для реализации определенной возможности достаточно одной процедуры или функции, в будущем наверняка к ней добавятся еще несколько. Когда вы поймете, что их лучше объединить в пакет, придётся искать все вызовы процедур и функций и прибавлять к ним префикс имя^пакета.
Демонстрация возможностей пакетов
Пакет состоит из двух частей — спецификации и тела. Спецификация является обязательной частью и определяет, как разработчик может использовать пакет: какие программы можно вызывать, какие курсоры открывать и т. д. Тело пакета —это необязательная, но почти всегда присутствующая часть, она содержит код перечисленных в спецификации программ (и, возможно, курсоров), а также другие необходимые элементы кода.
Предположим, что нам нужна программа для получения полного имени сотрудника, которое хранится в базе данных в виде двух отдельных элементов: имени и фамилии. Задача очень проста, и на первый взгляд кажется, что для ее решения достаточно создать приведенную далее процедуру.
CREATE OR REPLACE PROCEDURE process_efnployee ( employeejdjn IN employee.employeeJdXTYPE)
IS
l_fullname VARCHAR2C100):
BEGIN
SELECT last^name |||| f1rst_name
IMTO i_fuTlname
FROM employee
WHERE emplоуев_1d - employeeJd_1n:
END;
Однако этот простой код обладает рядом скрытых недостатков.
О Жестко закодирована длина переменной 1_ful 1 паше. Поскольку полное имя - это производное знахетше, представляющее собой конкатенацию содержимого двух столбцов, лучше так не делать. Если длина столбцов last_name и fi rst_na- me, хранящих фамилию и имя, будет увеличена, код данной процедуры придется изменять.
* О Жестко закодировано правило, согласно которому составляется полное имя. Поэтому если через определенное время пользователь захочет видеть полное имя в формате «ИМЯ ФАМИЛИЯ», вам придется во всем коде искать конструкции «ФАМИЛИЯ, ИМЯ».
О SQL-запрос этого вида в разных форматах может встречаться в нескольких местах приложения. Дублирование способно затруднить сопровождение приложения и его оптимизацию.
Приложения нужно строить таким образом, чтобы избежать жесткого кодирования подобных элементов. Определение типа данных для полного имени, его представление, запрос к базе данных и т. п. должны кодироваться один раз в строго определенном месте и быть доступны из любой точки приложения. Местом, которое удовлетворяет всем этим требованиям, является пакет.
Рассмотрим следующую спецификацию пакета:
CREATE OR REPLACE PACKAGE employeejkg
AS
max_salary NUMBER;
5 SU8HPE fullname_t IS VARCHAR2 (200);'
6
FUNCTION full name t
В1 employee. last_nameJ! TYPE.
f employee.first nameHYPE)
RETURN full name t;
FUNCTION full name t
enployee_1d_1n IN enployee,employee_1dXTYPE)'
RETURN full name t:
"
EM) empl oyeeJ3kg ;
Это список элементов, предназначенных для использования разработчиками, которые имеют доступ к данному пакету. В строке 5 объявляется новый тип данных fullname_t, максимальная длина его значений составляет 200 символов, но впоследствии ее легко будет изменить.
Затем объявляется функция full паше (строки 7-10), которая принимает фамилию, имя и возвращает полное имя. Обратите внимание, что способ составления полного имени в спецификации пакета не указан.
Наконец, в строках 12-14 объявляется вторая функция с именем ful 1 name. Она принимает первичный ключ таблицы сотрудников и возвращает полное имя сотрудника. Это типичный пример перегрузки, о которой рассказывалось в главе 16.
До реализации данного пакета перепишем исходный блок кода таким образом, чтобы в нем использовались элементы пакета (обратите внимание на точечный синтаксис, аналогичный применяемому при ссылке на столбец таблицы — табли- ца.стобец):
DECLARE
l_name employee_pkg.ful1nane_t:
employee idjn employee.employee idXtype :- 1;
BEGIN " ~"
Lname :- employee_pkg. ful 1 name (employeejdjn):
END;
Переменная l_name теперь объявлена с применением типа данных fullname_t пакета empl oyee_pkg, и для того чтобы ей присвоить значение, вызывается соответствующая функция этого же пакета. Таким образом, формула построения полного имени и SQL-запрос для выборки его составляющих вынесены из кода приложения в специальный «контейнер» с функциональными элементами, специфическими для обработки данных о сотрудниках. Полученный код прост и лаконичен. Если потребуется изменить формулу составления полного имени или увеличить размер его типа данных, достаточно внести соответствующие изменения в спецификацию или тело пакета и перекомпилировать его код.
Теперь рассмотрим, что собой представляет тело пакета:
CREATE OR REPLACE PACKAGE BODY employee_pkg
AS
FUNCTION full name (
1 empl oyee. 1 ast_name5:TYPE.
f employee.flrst_nameXTYPE
)-
RETURN full name t
IS~
BEGIN
RETURN 1 И И f;
END:
FUNCTION full name (employee Id 1n IN einployee. employee 1MTYPE)
RETURN fullnamej ~ ~一
IS'
retval full name t:
8EGIN'
SELECT full name Clast^name. flrst^name} INTO retval
IS FROM employee“
.WHERE employee J d - employeejdjn;
RETURN retval;
EXCEPTION
WHEN NO—DATA一FOUND THEN RETURN NULL;
WHEN TOO_MANY_ROWS THEN errpkg.recnstop;
END; ~"
END employee_pkg:'
В строках 3—11 находится функция-оболочка для формулы, задающей полное имя в формате «ФАМИЛИЯ, ИМЯ^. Вторая функция (строки 12-24) содержит типичный запрос на выборку одной строки, выполняемый с помощью неявного курсора. Обратите внимание на то, что в строке 17 запрос вызывает функцию full name, возвращающую комбинацию двух компонентов имени.
Если теперь пользователь попросит изменить формат представления полного имени сотрудника на «ИМЯ ФАМИЛИЯ», не нужно искать по всему приложению последовательность символов 11 1,1 11 — для установки нового формата достаточно модифицировать в пакете employee_pkg функцию full name.
Понятия и концепции, связанные с пакетами
Прежде чем рассматривать особенности синтаксиса и структуры пакетов, поясним некоторые связанные с ними понятия и концепции.
О Сокрытие информации. Выполняя сокрытие информации о системе или приложении, обычно преследуют две цели. Во-первых, освободить пользователя (разработчика приложения) от необходимости вникать в ненужные детали и помочь ему сконцентрироваться на важном. Во-вторых, воспрепятствовать доступу к конфиденциальной информации. Например, формула, вычисляющая некоторое значение, может быть не важна для разработчика, а может быть и секретна. В обоих случаях имеет смысл предоставить ему интерфейс для вызова функции, вычисляющей это значение.
О Общие и личные элементы. Концепция общих и личных элементов тесно связана с концепцией сокрытия информации. Общий код определяется в спецификации пакета и доступен любой схеме, имеющей на этот пакет привилегию EXECUTE. Личный код определяется и видим только внутри пакета. Внешние программы, использующие пакет, не могут видеть или вызывать личный код. Приступая к разработке пакета, нужно решить, какие из его элементов будут общими, а какие — личными. Кроме того, тело пакета можно скрыть от других схем/разработчиков. В таком случае пакет применяется для сокрытия деталей реализации программ. Это особенно важно, если требуется изолировать переменные компоненты приложения, такие как фрагменты кода, зависящие от платформы, часто меняющиеся структуры данных и временные решения.
О Спецификация пакета. Она содержит определения всех общедоступных элементов пакета, на которые можно ссылаться извне. Спецификация похожа на большой раздел объявлении, она не содержит блоков PL/SQL или исполняемого кода. Если спецификация хорошо спроектирована, разработчик может узнать из нее всю требуемую для использования пакета информацию. При этом никогда не возникнет необходимость заглянуть «за интерфейса, то есть в тело пакета, содержащее реализацию его компонентов.
О Тело пакета. Здесь находится весь код, который необходим для реализации элементов, определенных в спецификации пакета. Тело может содержать от* сутствующие в спецификации личные элементы, на которые нельзя ссылаться извне пакета, в частности объявления переменных и определения пакетных модулей. Кроме того, в теле пакета может находиться исполняемый (инициа- лизационныи) раздел, который выполняется только один раз для инициализации пакета.
О Инициализация. Концепция инициализации хорошо известна любому про- т^раммисту. Однако в контексте пакетов она имеет особое значение. В данном случае происходит не присвоение значения отдельной переменной, а инициализация всего пакета путем выполнения заданного кода произвольной сложности. При этом Oracle самостоятельно следит за тем, чтобы пакет инициализировался только один раз за сеанс.
О Постоянство в течение сеанса. Концепция постоянства (или сохраняемости) также хорошо знакома программистам. Когда вы подключаетесь к Oracle (начинаете сеанс) и выполняете программу, присваивающую значение переменной уровня пакета (то есть переменной, объявленной в пакете вне содержащихся в нем программ), эта переменная сохраняет значение в течение всего сеанса, даже если выполнение присвоившей его программы завершается.
Диаграмма Буча
Давайте подробнее рассмотрим концепцию личных и общих данных в пакетах, поскольку именно она обеспечивает программистам максимально гибкий контроль над структурами данных и программами. Для этого воспользуемся диаграммами Буча, названными так по имени их создателя - Гради Буча.
СнаружиСнаружиОбласть, обозначенная на диаграмме надписью «Снаружи» (рис. 17.1), включает все написанные вами программы, которые не входят в пакет (то есть являются внешними по отношению к нему). Область «Внутри» представляет собой тело пакета (его реализацию).
Снаружи
Снаружи
Внутри
Тело пакета
Внешняя программаВнешняя программаВнешняя программаВнешняя программаРис. 17.1. Диаграмма Буча для общих и личных элементов пакетаРис. 17.1. Диаграмма Буча для общих и личных элементов пакета
Внешняя программа
Внешняя программа
Внешняя программа
Внешняя программа
Рис. 17.1. Диаграмма Буча для общих и личных элементов пакета
Рис. 17.1. Диаграмма Буча для общих и личных элементов пакета
На основании диаграммы Буча можно сделать следующие выводы.
□ Внешние программы не имеют возможности <пересечъ» границу между наружной и внутренней областями. Это означает, что внешняя программа не может обращаться к элементам, определенным внутри пакета, они являются личными и невидимы извне.
О Для элементов, определенных в спецификации пакета (они обозначены на рисунке как «Общие»), граница между внутренней и наружной областями не является препятствием. Общие программы могут вызываться внешними и личными программами, а также сами вызывать любые элементы пакета.
О Общие элементы пакета являются единственной «дорогой» внутрь его, а спецификация пакета действует как <контрольно-пропускной механизм*.
◦ Чтобы личный объект (модуль или курсор) стал общим, нужно добавить его в спецификацию и перекомпилировать пакет. После этого объект будет видим извне.
Правила построения пакетов
Пакет имеет очень простую конструкцию. Хотя синтаксис и правила его построения вы изучите быстро, полное понимание нюансов реализации пакета придет через некоторое время. В данном разделе рассматриваются правила построения пакетов, а далее в этой главе рассказывается, в каких случаях пакеты могут быть вам полезны.
Чтобы создать пакет, необходимо написать его спецификацию и, как правило, тело. При этом нужно решить, какие элементы пакета будут указаны б спецификации, а какие — скрыты в теле. В пакет также можно включить блок кода, который Oracle будет выполнять при инициализации пакета.
Спецификация пакета
Спецификация пакета содержит список всех доступных его элементов и предоставляет разработчику информацию, необходимую для применения пакета б приложениях. Ее часто называют программным интерфейсом - API. Для того чтобы узнать, как применять описанные в спецификации элементы, разработчику не нужно изучать код, находящийся в теле пакета.
При разработке спецификации пакета необходимо учитывать следующие особенности.
Q Элементы, объявленные вне конкретных процедур и функций пакета, называются данными уровня пакета. В качестве таковых могут выступать переменные и константы практически любых типов — числа, строки, коллекции и т. п. Однако если объявление констант на уровне пакета является приемлемым, то объявления переменных лучше избегать.
В пакете (как в спецификации, так и в его теле) нельзя объявлять курсорные переменные (типа REF CURSOR), поскольку они не могут сохранять свое значение на протяжении сеанса (о постоянстве данных пакетов подробно рассказывается в разделе 4Работа с данными пакетам далее в этой главе).
О В спецификации допускается объявление типов для любых структур данных, таких как коллекции, записи или курсорные переменные.
О В спецификации можно объявлять процедуры и функции, но в ней должны быть указаны только их заголовки (часть определения процедуры или функции до ключевого слова IS или AS).
О В спецификацию пакета допускается включать явные курсоры. Они могут быть представлены в одной из двух форм: SQL-запрос является либо частью объявления курсора, либо он скрыт в теле пакета (тогда в объявлении будет присутствовать только предложение RETURN). Эта тема подробно рассматривается в разделе «Работа с пакетными курсорами».
О Если в спецификации пакета объявлены процедура или функция либо пакетный курсор без запроса, то тело пакета должно включать реализацию данных элементов.
О Спецификация пакета может содержать предложение AUTHID, определяющее, как будут разрешаться ссылки на объекты данных: в соответствии с привилегиями владельца пакета (AUTHID DEFINER) или того, кто его вызывает (AUTHID CURRENT_USER). Подробнее об этом рассказывается в главе 20.
О После оператора END в конце спецификации пакета можно поместить необязательную метку, идентифицирующую пакет.
END щу_раскаде:
Рассмотрим пример спецификации пакета, иллюстрирующий данные особенности и правила:
CREATE OR REPLACE PACKAGE favorites pkg■
AUTHID CURRENT_USER~
IS■
-- Две константы; обратите вникание на го.
-- что им присвоены информативные имена.
6
c_chocolate CONSTANT PLSJNTEGER 16:
c_strawberry CONSTANT PLVNTEGER :- 29:
~
-- Обьявление типа вложенной таблицы
TYPE codes_nt IS TABLE OF INTEGER;
12
-- Вложенная таблица, обьявленная на основе этого типа
ny_favorites codes_nt:
'-
-- REF CURSOR, возвращающий информацию из таблицы favorites
TYPE fav_info_rct IS REF CURSOR RETURN favor-itesXROWTYPE;
.
-- Процедура, принимающая список значений объявленного
-- выше типа codes_nt и выводящая соответствующую
-- информацию из таблицы
PROCEDURE sho^favorltes (list_1n IN codes_nt):
-- Функция, аозаратающан всю информацио из таблицы
favorites о самом популярном элементе
FUNCTION most_popular RETURN fav_info_rct;
27
END favoritesjikg; -- Закрывающая метка пакета
Как видите, пакет имеет почти такую же структуру спецификации, как раздел объявлений блока PL/SQL. Единственным отличием является то, что спецификация не может содержать кода реализации.
Тело пакета
Тело пакета содержит весь код, требуемый для реализации спецификации пакета. Оно не является обязательным компонентом (примеры пакетов без тела вы най・ дете в разделе <В каких случаях следует использовать пакеты»). Однако при наличии указанных ниже условий тело пакета является необходимым.
О В спецификации пакета содержится объявление курсора с предложением RETURN. В теле пакета нужно задать инструкцию SELECT.
О В спецификации пакета содержится объявление процедуры или функции.
В этом случае в теле пакета должна присутствовать полная реализация данного модуля.
О Необходимо выполнить код в инициализационном разделе тела пакета. Поскольку спецификация пакета не содержит исполняемого раздела (исполняемых операторов между ключевыми словами BEGIN и END), инициализационный раздел может размещаться исключительно в теле пакета.
Тело пакета по своей структуре очень похоже на процедуру, однако в отличие от нее характеризуется следующими особенностями.
О В теле пакета может находиться раздел объявлений, исполняемый раздел и раздел обработки исключений.
О Раздел объявлений включает полную реализацию курсоров и программ, объявленных в спецификации, а также определение личных элементов (не объявленных в спецификации). Он может быть пустым, если в пакете имеется раздел инициализации.
О Исполняемый раздел пакета (инициализационный) содержит необязательный код, выполняемый при создании и обработке пакета в течение сеанса. Эта тема подробно освещается в следующем разделе.
Q Раздел исключений содержит код для обработки исключений, которые генерируются при выполнении инициализационного раздела. Он может присутствовать в конце пакета лишь в том случае, когда в пакете имеется инициализационный раздел.
Q Тело пакета может включать: только раздел объявлений; только исполняемый раздел; исполняемый раздел и раздел исключений; раздел объявлений, исполняемый раздел и раздел исключений.
Q Тело пакета не может содержать предложения AUTHID, так как оно должно располагаться в спецификации. В теле пакета можно ссылаться на любые элементы, объявленные в спецификации.
О Все правила и ограничения, используемые при объявлении структур данных уровня пакета в его спецификации, относятся и к телу пакета. В частности, в теле пакета нельзя объявлять курсорные переменные.
О После ключевого слова END в конце тела пакета может располагаться необязательная метка, иденти фицирующая пакет:
END n\y_package:
Рассмотрим пример реализации тела пакета favari tes_pkg:
CREATE OR REPLACE PACKAGE BODY favorites_pkg
IS
--Личная переменная
g_most_popular PLS_INTEGER ;= 29:
Реализация процедуры
;PROCEDURE show_favorites (Ustjn IN codes_nt)
BEGIN
FOR Indx IN 11st_in.FIRST 11st in丄AST
LOOP~-
DBMS_OUTPUT.put_l 1 ne (Hst_1n (indx)):
END LOOP:--
END show_favorites;
--Реализация функции
FUNCTION niost_popular
RETURN favjnfo_rct
is - 一
retval fav_info_rct;
null cv fav Info ret;
BEGIN 一 -~
OPEN retval FOR
SELECT *
FROM favorites
WHERE code - g_niost_popular;
RETURN retval;
EXCEPTION
WHEN NO_DATA_FOUND THEN RETURN null_cv: END mast_popular;
END favorites_pkg; -- Закрывающая метка пакета
Другие примеры пакетов приводятся в разделе «В каких случаях следует использовать пакеты».
Инициализация пакетов
Пакет может содержать структуры данных, сохраняющиеся на протяжении всего сеанса (см. раздел «Работа с данными пакета»). Когда в течение сеанса впервые происходит обращение к пакету (вызывается объявленная в нем программа, считывается или записывается значение переменной либо используется объявленный в пакете тип), Oracle инициализирует его, выполняя следующие действия (одно или все).
Обрабатывает данные уровня пакета (значения переменных и констант).
Присваивает переменным и константам значения по умолчанию, указанные в их объявлениях.
Выполняет блок кода, содержащийся в инициализационном разделе.
ПРИМЕЧАНИЕ—
Если возникает необходимость перекомпиляции пакета или сбрасывается состояние всего сеанса, на что указывает ошибка ORA-04068: existing state of packages has been discarded, то во время сеанса пакет может быть инициализированповторно.
Инициализационный раздел пакета составляют все операторы, находящиеся между ключевым словом BEGIN (вне определений процедур и функций) и ключевым словом END, завершающим тело пакета. Например, иниииализационный раздел пакета favorites_pkg может выглядеть следующим образом:
CREATE OR REPLACE PACKAGE BODY favorites_pkg
IS
g^most_popular PLS_INTEGER:
PROCEDURE show_favor1tes (listjn IN codes_nt) ... END:
FUNCTION most_popular RETURN favjnfo_rct ... END:.
PROCEDURE analyze_favor1tes Cyearjn IN INTEGER) ... END;
--Инициализационный разцел
BEGIN
g_most_popular :- c_chocolate:
一 Используем новую функцию 0гас1е91 EXTRACT для получения
--года из значения, возвращенного функцией SYSDATE
analyze__favorltes (EXTRACT (YEAR FROM SYSDATE)):
END favor1tes_pkg;
PL/SQL автоматически определяет, когда должен выполняться код инициа- лизационного раздела. Это означает, что нет необходимости вызывать его явно и можно быть уверенным, что он будет выполнен только один раз. О назначении инициализационного раздела будет рассказано ниже.
Выполнение сложной логики при инициализации
Используемые по умолчанию значения данных пакета можно присвоить непосредственно в разделе объявлений. Однако этот подход имеет несколько недостатков.
О Логика, применяемая для присвоения значений по умолчанию, может быть довольно сложной.
Q Если в результате присвоения значения по умолчанию инициируется исключение, то его нельзя перехватить в пакете, а следовательно, оно выходит из пакета необработанным. Этот вопрос подробнее рассматривается в разделе «Когда инициализация не удается».
Таким образом, установка начальных значений данных в инициализапионном разделе пакета имеет определенные преимущества перед присвоением значений па умолчанию. Она позволяет при формировании значений воспользоваться всеми возможностями, которые обеспечивает программный код, и обработать внутри пакета все возникающие в ходе этого процесса исключения.
Кэширование статической информации сеанса
Еще одним важным мотивом для включения в пакет раздела инициализации является необходимость кэширования статической информации, не изменяющейся в течение всего сеанса. Если вам нужна уверенность, что информация извлекается только один раз за сеанс, идеальным местом для выполнения подобной операции является инициализационный раздел пакета.
При работе с кэшируемыми данными пакета важно поддерживать баланс между затратами памяти и процессорного времени. Кэширование данных в пакетных переменных позволяет ускорить доступ к данным за счет их перемещения в глобальную область программ каждого пользователя. Если существует 1000 разных сеансов, то существует и 1000 копий кэшируемых данных, что означает ощутимое увеличение потребления памяти при уменьшении процессорного времени, затрачиваемого на доступ к данным. Далее, в разделе «Кэширование статических данных сеанса», об этой технологии рассказывается подробнее.
Как избежать побочных эффектов инициализации
Старайтесь не устанавливать значения глобальных переменных пакета в других пакетах (не важно, в каком разделе). Это поможет избежать проблем при выполнении кода и упростит сопровождение кода другими программистами. Все, что происходит в разделе инициализации, должна касаться только текущего пакета. Помните: этот код выполняется при первом обращении приложения к данному пакету, поэтому не стоит заставлять пользователя ждать окончания операций, которые могли бы выполняться в других пакетах или триггерах. Ниже приведен пример того, как не следует поступать:
CREATE OR REPLACE PACKAGE BODY company IS
BEGIN
/*
В инициапиэационном разделе пакета company_pkg обновляется
глобальные данные другого пакета. Это очень плохо!
*/
SELECT SUM (salary)
INTO employee_pkg.max_salary
FROM employee:
END company;
Если задачи инициализации все же требуют установки значений переменных из других пакетов, та лучше объединить соответствующие операции в пакетную процедуру, присвоить ей информативное имя (например, init_environment) и вызвать ее в нужный момент при инициализации приложения.
Когда инициализация не удается
Процесс инициализации пакета включает объявление данных, присвоение значений по умолчанию и выполнение инициализационного раздела. Если в ходе данного процесса произойдет ошибка, которая вызовет его аварийное завершение, Oracle посчитает пакет инициализированным и не станет повторно выполнять инициализационный код в течение сеанса. Чтобы в этом убедиться» достаточно создать такой пакет:
/* Файл в web: val err.pkg */
CREATE OR REPLACE PACKAGE valerr
IS
FUNCTION get RETURN VARCHAR2:■
END valerr:
CREATE OR REPLACE PACKAGE BODY valerr
IS
--Обратите внимание: это глобальная переменная уровня пакета, но она является личной для данного пакета
V VARCHAR2(D :- 'АБВ';
FUNCTION get RERJRN VARCHAR2
IS
BEGIN
RETURN v:
END:
BEGIN
DBMS_OUTPUT.PUT_LINE С'Ранее я видел вас в...'):
EXCEPTION
WHEN OTHERS
THEN.
DBMS_DUTPUT.PUT_LINE ('Перехвачена ошибка!');'
END valerr;
Предположим, что мы подключились к Oracle из SQL*Plus и попытались вызвать функцию valerr.get (впервые за сеанс). Результат будет выглядеть следующим образом:
SQL> exec DBMS_OJTP(JT.P(JT_LINE (valerr.get)
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error: chsracter string buffer too small
Это означает, что попытка объявления переменной v с присвоением ей начального значения АБВ не удалась, — она вызвала исключение VALUE_ERROR. Раздел исключений в конце пакета его не перехватил, поскольку он может перехватывать только те ошибки, которые генерируются в инициализационном разделе. Таким образом, исключение осталось необработанным. Однако если вызвать функцию второй раз за сеанс, ошибки не будет:
SQL> BEGIN
DBMS_OUTPUT.PHT_LINE ('Значение переменкой V равно ' 11 NVL (valerr.get, 'NULL'));
END;
/
Значение переменной V равно NULL
Строка «Ранее я видел вас в...» так и не была выведена. Фактически отображающий ее оператор никогда не выполняется. Пакетная функция в первый раз завершается ошибкой, но затем каждый раз благополучно выполняется. Это пример классической невоспроизводимой ошибки, которая возникает во время сбоя при инициализации пакета.
Подобные ошибки находить очень трудно. А для того чтобы их избежать, достаточно переместить в раздел инициализации пакета присвоения всех значений по умолчанию и в разделе исключений обработать все возможные ошибки, как это сделано в следующем примере:
CREATE OR REPLACE PACKAGE BODY valerr
IS
v VARCHAR2C1);
FUNCTION get RETURN VARCHAR2 IS BEGIN ... END; BEGIN
v ;- 'АБ8';
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.PUT_LINE ('Ошибка инициализации пакета valerr:');
DBMS二 OUTPUT.PU匸LINE (SQLERRM);
END valerr;
Правила вызова элементов пакета
Говорить о вызове или выполнении пакета в целом не имеет смысла, поскольку он является всего лишь контейнером для элементов кода. Вы можете вызвать только элементы пакета (как изнутри, так и извне). Для ссылки на элементы пакета извне используется такой же точечный синтаксис, как для ссылки на столбцы таблиц. Давайте рассмотрим несколько примеров.
В следующей спецификации пакета объявляются константа, исключение, курсор и несколько модулей:
CREATE OR REPLACE PACKAGE petsjnc
IS~
max_pets_1n_facil1ty CONSTANT INTEGER :- 120;
pet_1s_sick EXCEPTION;■
CURSOR pet_cur (petjdjn IN pet.ldXTYPE) RETURN petXROWTYPE;
FUNCTION next_pet_shots (petjdjn IN pet.idXTYPE) RETURN DATE;
PROCEDURE set_schedule (petjdjn IN pet.idWPE):
END pets_1nc;
Для ссылки на любые из этих элементов имя элемента предваряется именем пакета:
DECLARE
--Константа объявлена на основе типа столбца Id таблицы pet
c_pet CONSTANT pBt.ldmPE:- 1099:
v next.叩叩。Stment DATE;
BEGIN ~
IF pets_1nc.max_pets_ln_fвс11ity > 100
THEN "
OPEN pets 1nc.pet_cur Cc_pet):
ELSE ”
v_next_apppo1ntment:■ pets_1nc.next_pet_shots (c_pet);
END IF:""
EXCEPTION
WHEN pets_1nc.pet_1s_s1clc '
THEN ""
pEts_lnc.set_schedule Cc_pet);
END: "~~
При обращении к элементам пакета необходимо соблюдать два правила.
О Если элемент определяется в спецификации пакета, то для ссылки на него из внешней программы следует использовать точечный синтаксис: ^мя_паке- тд.имяулемента.
О Когда ссылка на элемент осуществляется в самом пакете (спецификации или теле), имя пакета задавать не нужно, поскольку PL/SQL автоматически идентифицирует ее как обращение к элементу, объявленному в пределах пакета.
^бота с данными пакета
Данные пакета — это переменные и константы, определенные на уровне пакета, а не в конкретной его функции или процедуре. Их областью видимости является не отдельная программа, а весь пакет. Структуры данных пакета существуют (и сохраняют свои значения) на протяжении всего сеанса (а не только во время выполнения программы).
Если данные пакета объявлены в его теле, они сохраняются в течение сеанса, но доступны только элементам пакета (то есть являются личными).
В случае, когда данные пакета объявлены в его спецификации (то есть общие), они также сохраняются в течение всего сеанс% но доступны (для чтения и изменения) любой пользовательской программе, имеющей на пакет привилегию EXECUTE. Общие данные пакета похожи на GLOBAL-переменные Oracle Forms.
Если пакетная процедура открывает курсор, он остается открытым и доступным в продолжение всего сеанса. Нет необходимости объявлять курсор в каждой программе. Один модуль может его открыть, а другой — выполнить выборку данных, Переменные пакета могут применяться для передачи данных между транзакциями, поскольку они привязаны не к транзакции, а к сеансу.
Глобальные данные в одном сеансе Oracle
Благодаря архитектуре SGA структуры данных пакета функционируют в PL/SQL как глобальные. Однако они доступны только в пределах одного сеанса, или подключения к базе данных Oracle, и не могут совместно использоваться несколькими сеансами, Если доступ к данным нужно обеспечить для нескольких сеансов Oracle, используйте пакет DBMS_PIPE. (Его описание имеется в документации Oracle Built-in Packages.)
Разные части приложения не всегда работают с Oracle, используя одно подключение. В некоторых случаях среда, из которой выполняется компонент приложения, устанавливает для него новое подключение. При этом данные пакета, записанные первым подключением, будут недоступны для второго. Предположим, что форма генерирует отчет с помощью Oracle Reports. По умолчанию Oracle Reports создает для отчета отдельное подключение к базе данных с тем же именем пользователя и паролем. Даже если отчет обратится к тому же пакету и структурам данных, что и форма, значения, которые хранятся в структурах данных, доступных форме и отчету, будут разными, поскольку сеанс отчета имеет свой экземпляр пакета и всех его структур.
Как уже было сказано, глобальные данные пакета (то есть данные, объявленные вне процедур и функций пакета и поэтому существующие в течение всего сеанса) могут быть личными или общими. Давайте подробнее рассмотрим каждую из этих категорий.
Глобальные общие данные
Любые структуры данных, объявленные в спецификации пакета, являются глобальными и общими, то есть они существуют в течение всего сеанса и доступны протраммам вне этого пакета. Например, можно определить в спецификации пакета таблицу PL/SQL и применять ее для хранения списка сотрудников, которых предстоит повысить в должности. Можно создать пакет констант, используемых во всех программах проекта, и все разработчики смогут применять эти константы, вместо того чтобы жестко кодировать их значения в программах. Если глобальные общие данные не объявлены в пакете с ключевым словом CONSTANT, то их можно изменять.
Глобальные данные требуют тщательного планирования. Конечно, разработчикам удобно, когда информация доступна в любой момент времени и из любого места приложения. Однако в этом и таится главная опасность, поскольку вам неизвестно, где и как она изменяется.
Спецификация должна предоставлять разработчику необходимую информацию о модуле. Однако на основе спецификации глобальных данных невозможно определить, какие программы и пакеты считывают и изменяют их значения. Поэтому вы не знаете, что происходит в приложении, какие данные модифицируются и какие программы при этом используются.
Всегда предпочтительнее передавать данные модулям в качестве параметров. В таком случае структуры данных будут описаны в спецификации и видны разработчиками, Именованные глобальные структуры данных следует создавать для
хранения информации, которая глобальна на уровне приложения, в частности для констант и конфигурационных параметров.
Пакетные курсоры
Одним из наиболее интересных типов пакетных данных является явный курсор, о котором рассказывалось в главе 14. Его можно объявить в теле либо в спецификации пакета. Состояние курсора (открыт или закрыт), а также указатель на его набор данных сохраняются в течение всего сеанса. Это означает, что открыть пакетный курсор можно в одной программе, выбрать из него данные — во второй, а закрыть — в третьей. Такая гибкость курсоров предоставляет большие возможности, но в то же время она может стать источником проблем.
Объявление пакетных курсоров
Явный курсор в спецификации пакета можно объявлять двумя способами.
О Полностью (заголовок курсора и запрос). Именно так объявляются локальные курсоры в блоках PL/SQL.
О Частично (только заголовок курсора). В этом случае запрос определяется в теле пакета, поэтому реализация курсора оказывается скрытой от применяющего пакет разработчика.
При использовании второго способа в объявление нужно добавить предложение RETURN, указывающее, какие данные будут возвращены при выборке из курсора. На самом деле эти данные определяются инструкцией SELECT, но она в спецификации отсутствует.
В предложении RETURN можно задать одну из следующих структур данных:
О запись, объявленную на основе таблицы базы данных с использованием атрибута 双0仃YPE;
О запись, определенную программистом.
Объявление курсора в теле пакета осуществляется так же, как в блоке PL/SQL. Ниже приведен пример спецификации пакета, демонстрирующий оба подхода:
CREATE OR REPLACE PACKAGE bookjnfo
IS
CURSOR byduthor_cur 〔/
author in IN books.author^TYPE
)~
IS
SELECT *
FROM books
WHERE author = author_1n;
~
ИCURSOR bytitle_cur (
title_filter in IN books.tltle^TYPE
) RETLRN~booksXROWTYPE:■
14
TYPE authar_sunuiary_rt IS RECORD (
author books.authorXTYPE,
total_page_count PLS INTEGER.
totarbook~count PLS~INTEGER):
19
CURSOR surmiary_cur (
authoMn IN books.authorXTYPE
) RETURN author_summary_rt;
PROCEDURE display (
book_rec IN booksXROWTYPE
)「
END bookjnfo;
В строках 3-9 представлено типичное определение явного курсора, полностью заданное в спецификации пакета. Строки 11-13 содержат определение курсора без запроса, эта спецификация указывает, что, открыв курсор и выбрав из него данные, вы получите строки из таблицы books для заданных значений столбца books.title. В строках 15-18 определяется новый тип записи для хранения информации об авторе, а в строках 20-22 объявляется курсор, возвращающий информацию о заданном авторе (три определяемых записью значения).
Рассмотрим, какой код должно содержать тело пакета для трех объявленных курсоров:
CREATE OR REPLACE PACKAGE BODY bookJnfо
IS一
CURSOR byt1tle_cur (
ti.tle_filter_in IN books.tltle^TYPE
) RETURN-books«ROWTYPE
IS
SELECT *
FROM books
WHERE title LIKE UPPER Ctitle_f11terin);
~"
CURSOR summary_cur (
authorjn IN books.authorXTYPE
) RETURN author summaryrt
IS
SELECT author, SUM (page_count), COUNT C*)
FROM books-
WHERE author - authorjn:
PROCEDURE display (~
book_rec IN books福CMTYPE)
IS
BEGIN
NULL:
END:
ENO bookjnfo;
Поскольку у нас имеются два курсора с предложениями RETURN, их определения нужно завершить в теле пакета. Список выбираемых запросом элементов в теле пакета должен соответствовать (по количеству элементов и их типам данных) предложению RETURN в спецификации пакета. Если это условие не соблюдается, то при компиляции пакета вы получите одно из следующих сообщений: 20/11PLS-00323: subprogram or cursor '<cursor>' Is declared In a package
specification and must be defined 1n the package body
5/13PLS-00400: different number of columns between cursor SELECT statement and
return value
Работа с пакф'ными курсорами
Использование пакетных курсоров обеспечивает ряд преимуществ. Кроме того, для их открытия, выборки данных и закрытия не требуется изучать новый синтаксис, нужно только задать имя пакета перед именем курсора. Например, чтобы запросить информацию о книгах по PL/SQL, можно выполнить такой блок кода: DECLARE'
onebook book info.bytltle curXROWTYPE;
BEGIN--
OPEN book Info.bytitle cur ('XPL/SQLX');
LOOP '
EXIT WHEN bookJ nfо.byt1tle_curXNOTFOUND;
FETCH book_1nfo.bytitle_cur INTO onebook:
book Info.di splay (onebook):
END LOOP:
CLOSE book Info.bytitle cur:
ENO:-*
Как видите, на основе пакетного курсора, точно так же, как и на основе локального явного курсора, с помощью атрибута XROWTYPE можно объявить переменную для выборки результирующих данных.
Однако в этом простом фрагменте кода есть скрытый нюанс. Поскольку курсор объявлен в спецификации пакета, его область видимости не ограничена конкретным блоком PL/SQL. Предположим, что мы выполнили следующий код:
BEGIN
—Мы только открываем курсор
OPEN bookjnfo.byfitle_cur ('IPEACED;
END; 一~
Если затем в этом же сеансе запустить приведенный выше анонимный блок с циклом LOOP, то Oracle выдаст ошибку:
0RA-O65U: PL/SQL: cursor already open
В данном сообщении сказано, что курсор уже открыт. Так произошло потому, что блок, выполненный первым, не закрыл курсор и по завершении его работы курсор остался открытым.
Используя пакетные курсоры, никогда не рассчитывайте на то, что курсор закрыт (и его можно открыть) либо открыт (и его можно закрыть). Закончив работу с пакетным курсором, всегда явно его закрывайте.
ПРИМЕЧАНИЕ
Эти правила применимы также к курсорам других типов, например к локально определяемым явным курсорам и курсорам, создаваемым с помощью пакета DBMS.SQL, Однако для пакетных курсоров они особенно важны.
Если данными правилами пренебрегать) то работа созданного вами приложения будет нестабильной, в процессе его функционирования возможно появление неожиданных и необрабатываемых исключений. Поэтому лучше написать процедуры, которые выполняют открытие и закрытие курсоров и учитывают все возможные их состояния. Этот подход реализован в следующем пакете:
CREATE OR REPLACE PACKAGE personnel ' IS
CURSOR empjfojdept (
deptnojn IN employee.deptnoXTYPE)
IS "
SELECT * FROM employee
WHERE deptno - deptnojn;
PROCEDURE open_emps-_for_dept(
deptno_in IN empToyee.deptnoXTYPE,'
close if_open IN BOOLEAN TRUE'
);""
PROCEDURE close_enips_for_dept:'
END personnel;
Как видитв) вместе с курсором объявлены две сопутствующие процедуры, предназначенные для его открытия и закрытия. Если нам понадобится перебрать в цикле строки курсора, это можно сделать так:
DECLARE
one emp personnel .emps for_dept3:ROWrYPE;
BEGIN --
personnel .open_emps_for,_dept (1055):
LOOP
EXIT WHEN personnel.emps_far_deptXNOTFOUND;
FETCH personnel .emps__for_dept INTO one_emp;
END LOOP:
personnel.close_emps_for_dept:
END:~~ ~
В данном фрагменте кода не используются явные операторы OPEN и CLOSE. Вместо них вызываются соответствующие процедуры, скрывающие особенности работы с пакетными курсорами. Советуем проанализировать реализацию этих процедур в файле openclose.sql (имеющемся на узле O'Reilly).
В большом проекте, над которым работают несколько программистов, реализация курсоров в пакетах, доступных всем программистам, — не просто удобство, а жизненная необходимость. Как правило, многие программы проекта обращаются к одним и тем же данным, и проектирование оптимальных структур для зтнМ данных требует много времени и усилий. Поэтому такие структуры (записи, курсоры и сопутствующие процедуры) должны храниться централизованно и быть доступными из всех программ проекта. Иначе каждый программист будет определять собственные курсоры, причем по нескольку раз. В результате неизбежны расхождения и недоработки, которые отразятся как на производительности, так и на отладке и сопровождении проекта, В проектах небольшого размера, создаваемых одним программистом, наличие пакетных курсоров также обеспечивает согласованность и структурированность кода( ускоряет его разработку и упрощает сопровождение, Использование пакетных курсоров является одним из важнейших примеров того, как с помощью пакетов можно инкапсулировать доступ к структурам данных (подробнее об этом рассказывается далее, в разделе «В каких случаях следует использовать пакеты»).
Один из рецензентов этой книги, Дж. Т. Томас, предложил следующую альтернативную точку зрения: «Вместо того чтобы работать с пакетными курсорами, можно достичь такого же эффекта путем инкапсуляции логики и помещения данных в представления базы данных, чтобы обеспечить возможность доступа к ним всем разработчикам. Это позволит разработчикам самим отвечать за собственные курсоры. Идея заключается в том, что все равно невозможно обеспечить правильное использование разработчиками общедоступных пакетных курсоров, В частности, насколько мне известно, разработчики часто пренебрегают процедурами открытия и закрытия курсоров или забывают их вызвать, поскольку курсор все равно видим, разработчикам ничего не стоит непосредственно открыть или закрыть его. Таким образом, эта архитектура остается уязвимой и ее функционирование зависит исключительно от добросовестности и внимательности разработчиков. Кроме того, наличие пакетных курсоров и процедур их открытия и закрытия может создать у команды ложное ощущение надежности.»
Повторно инициализируемые пакеты
По умолчанию пакетные данные сохраняются в течение всего сеанса (или до перекомпиляции пакета). Это исключительно удобное свойство пакетов, но и у него имеются определенные отрицательные стороны.
О Постоянство глобально доступных (общих и личных) структур данных сопровождается нежелательными побочными эффектами. В частности, можно случайно оставить пакетный курсор открытым, а в другой программе попытаться открыть его без предварительной проверки, в результате чего будет сгенерирована ошибка.
О Если данные хранятся в структурах уровня пакетов (данные пакетов располагаются в пользовательской глобальной памяти — UGA), то может оказаться, что программы занимают очень большой объем памяти и не освобождают ее.
Для оптимального использования памяти при работе с пакетами можно применять директиву SERIALLY_REUSABLE. Она указывает Oracle, что пакет является повторно инициализируемым〉то есть его состояние (состояние переменных, открытых пакетных курсоров и т. п.) нужно сохранять не на протяжении сеанса, а на время одного вызова пакетной программы. Директива SERIALLY_REUSABLE должна располагаться и в теле, и в спецификации пакета.
Рассмотрим ее действие на примере пакета book.info. В нем имеются две отдельные программы: для заполнения списка книг и для вывода этого списка:
/* Файл в web: serialpkg.sql */
CREATE OR REPLACE PACKAGE book Info
IS"
PRAGMA SERIALLY_REU5A8LE;
PROCEDURE fIBJIst;
PROCEDURE show list:
END;~
Как показано ниже, в теле пакета список объявляется как личный глобальный ассоциативный массив:
/* Файл в web: serialpkg.sql */
CREATE OR REPLACE PACKAGE BODY book_info
IS
PRAGMA SERIALLY_REUSABLE:
TYPE bookjistj IS TABLE OF booksXROWTYPE
INDEX BY BINARY_INTEGER:
my_book5 book_11st_t:
PROCEDURE fill list
is -
BEGIN
FOR rec IN (SELECT * FROM books WHERE AUTHOR LIKE 'XFEUERSTEINX')
LOOP
my_books (NVL (my_books.LAST, 0) + 1) :- rec:
END LOOP:~
END mijist;
PROCEDURE shew list
IS-
BEGIN
IF my_books.COUNT - 0
THEN ~
DBMS OUTPUT.PUT LINE ('** Книг нет...');
ELSE 一 ~
FOR Indx IN гду_Ьоок5.FIRST .. my_books.LAST
LOOP~~
DBMS_OUTPUT.PUT_LINE (my_books (indx).title);
END LOOP;
ENO IF;
END showjist;
ENO; ~
Чтобы посмотреть, как это работает, заполним список и выведем его на экран. Сначала выполним данную операцию в одном блоке:
SQL> BEGIN
DBMS_OUTPUT.PUT_LINE (
'Заполняем и выводим список в одном блоке:'
4
bookl nfo .fllljlst;
bookjnfo.showjlst;
END; "~
/
Заполняем и выводим список 8 одном блоке:
Oracle PL/SQL Programming
Oracle PL/SQL Best Practices ■
Oracle PL/SQL Built-in Packages
Затем заполним и выведем список в двух разных блоках. В результате коллекция окажется пустой:
SQL> BEGIN
OBMS OUTPUT.PUT_LINE ('Заполняем список в первом блоке'):
book_1 nfo.finjist;
END; ~~
/
Заполняем список в первом блоке
SQL> BEGIN
DBMS_OJTPUT.PUT_LINE СВыводии список во втором блоке:1);
book_i n f о. show? i st ;
END;"—
/
Выводим СПИСОК 80 втором блоке:
** Нет книг...
Работая с повторно инициализируемыми пакетами, необходимо учитывать их следующие особенности.
Q Глобальная память для такого пакета выделяется в системной глобальной области (SGA), а не в пользовательской глобальной области (UGA), что позволяет повторно применять рабочую область пакета. При каждом новом обращении к пакету его общие переменные инициализируются значениями по умолчанию или значением NULL, а его инициализационный раздел выполняется повторно.
Q Максимальное количество рабочих областей, необходимых для повторно ини” диализируемого пакета, равняется количеству одновременно работающих с этим пакетом пользователей. Увеличение объема используемой памяти в SGA компенсируется уменьшением объема памяти, задействованного в UGA. Когда нужно предоставить память из SGA другим запросам, Oracle освобождает неиспользуемые области памяти.
В каких случаях следует использовать пакеты
Итак, мы уже изучили правила, синтаксис и особенности построения пакетов PL/SQL и теперь можем перейти к рассмотрению ситуаций, в которых их лучше применять.
О Инкапсуляция (сокрытие) операций с данными. Программисты, работая над проектом, не должны сами писать S QL-инструкции, поскольку это усложняет сопровождение проекта, гораздо эффективнее — создать общедоступный интерфейс для работы с данными.
О Исключение жесткого кодирования литералов. Для того чтобы избежать жесткого кодирования одних и тех же значений в нескольких программах, нужно определить все литеральные значения как константы и объединить их в пакеты. Можно также объявить константы в процедурах и функциях, но преимущество пакетных констант заключается в их общедоступности.
О Устранение недостатков встроенных функций. Некоторые встроенные пакеты Oracle, например UTL_FILE и DBMS^OUTPUT, далеки от совершенства, однако вы можете надстраивать над ними собственные пакеты, в которых значительная часть недостатков исправлена.
О Объединение в группу логически связанных функций. Если у вас имеется десяток процедур и функций, относящихся к реализации некоторой части приложения, сгруппируйте их в пакет, чтобы упростить управление и сопровождение кода.
О Кэширование статических данных для ускорения работы приложения. Для кэширования данных, не изменяющихся в течение сеанса, пользуйтесь постоянными структурами данных пакетов.
В следующих разделах каждая из описанных выше ситуаций рассматривается
.более подробно
Инкапсуляция операций с данными
Вместо того чтобы разрешить разработчикам писать все SQL-инструкции для доступа к данным, можно выделить набор типичных инструкций, реализовать их в виде пакета и предоставить всем членам группы единый интерфейс доступа к этим инструкциям.
Таким образом, разработчики смогут использовать готовый, протестированный и оптимизированный код, который будет выполнять все необходимые операции. Например, можно создать процедуру для добавления строк (перегруженную для поддержки записей), которая выполняет инструкцию INSERT и реализует стандартные правила обработки ошибок, функцию, предназначенную для извлечения одной строки на основе первичного ключа, а также набор курсоров для выполнения типичных запросов к отдельной таблице или группе связанных таблиц.
При эффективном использовании такого подхода разработчикам, отвечающим за реализацию пользовательского интерфейса приложения, нет необходимости знать, как для получения применяемого ими набора данных объединяются нескольких связанных нормализованных таблиц. Они будут использовать курсор, а анализ данных выполнит за них другая группа разработчиков. Им не нужно заботиться также о том, что происходит при попытке добавить в базу данных строку, которая уже существует в ней. Соответствующая логика инкапсулирована в процедуру добавления строки (и эту процедуру легко модифицировать для применения новой инструкции MERGE, введенной в Oracle9t).
Данный подход позволяет также упростить сопровождение кода приложения и его обновление при изменении структуры данных. Программистам, отвечающим за работу с таблицами и объектными типами, достаточно внести в один пакет необходимые изменения, и они автоматически распространятся на все программы, использующие пакет.
Инкапсуляция данных — довольно сложная задача, для ее успешной реализации требуется тщательный анализ и планирование. В файлах te.employee.pks и te_employee.pkb на узле O'Reilly описан пакет, инкапсулирующий доступ к таблице employee. Рассмотрим процедуру, с помощью которой назначается премия каждому
сотруднику заданного отдела, который проработал в компании минимум шесть месяцев. Ниже приведена часть такой процедуры, содержащая код SQL:
CREATE OR REPLACE PROCEDURE give_bonus C
deptjn IN empl oyee. department_idXTYPE.
bonusjn IN NUMBER)
IS 一
v_name VARCHAR2(50):.
CURSOR by_dept_cur '
IS 一 -
SELECT * FROM employee
WHERE department_id = dept_in:,
BEGIN~~■
/* Извлекаем информацис для заданного отдела */
SELECT name'
INTO v_name
FROM department
WHERE departrnentjd - deptjn;
/* Для каждого сотрудника из заданного отдела... */
FOR rec IN by_dept_cur
LOOP -~
IF ADD MONTHS (SYSDATE, -6) > rec.hi re date
THEN
UPDATE employee
SET salary = rec.salary + bonusjn
WHERE employeeJd - rec.employeejd;
END IF;--
END LOOP:
END:'
Сравните этот код с альтернативной реализацией в которой доступ к данным инкапсулирован в пакете te_employee:
CREATE OR REPLACE PROCEDURE give_bonus ( deptjn IN employee.departmentjdXTYPE.'
bonus in IN NUMBER)~.
IS "
dept_rec depa rtment^IROWTYPE: fdbk'lNTEGER;
BEGIN dept_rec te_department.onercw (deptjn);
/* Обязательно закрываем пакетный курсор */ te_empl oyee.close_emp_dept_lookup^a1l_cur:
FOR rec IN te_employee.emp_dept_lookup_all_cur (deptjn) ■
LOOP ~~-
IF ADD MONTHS (SY5DATE, -6) > rec.hi re date
THEN"
te__employee. upd$salary ( "rec.enployeejd, rec. sal ary + bonusjn. fdbk):-
END IF;
END LOOP;
END:
Как видите, весь SQL-код удален из программы и заменен вызовами общедоступных процедур и функций. Этот подход позволяет ускорить написание пр。- 1рамм и сделать их более надежными.
Разработка таких пакетов — дело непростое, и большинство программистов едва ли сможет или захочет внедрить практику стопроцентной инкапсуляции. Однако даже без кардинального пересмотра принципов разработки программного кода технология инкапсуляции может принести большую пользу. Как минимум имеет смысл осуществить следующее;
О Скрыть все запросы на выборку одной строки за интерфейсом функций. Это обеспечит возможность обработки ошибок и выбора оптимальной реализации (например, явного или неявного курсора).
О Идентифицировать таблицы, которые чаще всего используют разработчики, и создать для них программный интерфейсный доступ.
О Создать пакетные программы для выполнения сложных транзакций. Если добавление нового заказа требует вставки двух строк, обновления шести строк и т. д., то лучше заключить эту логику в пакетную процедуру, которая обеспечивает ее постоянную и точную реализацию и избавляет программиста от лишних деталей.
Исключение жесткого кодирования литералов
Практически любое приложение содержит множество ^магических» значений - литералов, имеющих особое предназначение в системе. Это могут быть коды типов, границы диапазонов и т. п. Пользователи будут говорить вам, что эти значения никогда не меняются.《9тот документ всегда содержит 25 строк», а «Родительская компания называется ATLAS HQ и всегда будет называться именно так» — подобные высказывания слышит от пользователя любой разработчик, но только наиболее наивные и неопытные опираются на них при создании кода. Для примера рассмотрим следующий оператор IF:
IF footing_difference BETWEEN 1 and 100
THEN ~
adj ust_l"ine_i tern;
END IF; 一 -
IF cust_status = 'C*
THEN 一
reopen_customer:
END IF: ~
Тот, кто пишет подобный код, собственноручно создает себе потенциальные проблемы. Серьезный и опытный разработчик сначала формирует пакет именованных констант.
closed_status open_status act1ve_status 1nactive_statusclosed_status open_status act1ve_status 1nactive_statusCREATE OR REPLACE PACKAGE configpkg IS
closed_status open_status act1ve_status 1nactive_status
closed_status open_status act1ve_status 1nactive_status
CONSTANT VARCHAR2(1) :- t'
CONSTANT VARCHAR2(1) := 'O'
CONSTANT VARCHAR2(1) - 'A'
CONSTANT VARCHAR2(1) :- 'Г
min_difference CONSTANT NUMBER := 1:
max_difference CONSTANT NUMBER :e 100:
earliest_date CONSTANT DATE := SYSDATE:
latest_date CONSTANT DATE :匸 ADD_MONTHS (SYSDATE. 120):
END config_pkg:
При использовании данного пакета два приведенных вьппе оператора IF примут такой вид:/
IF footi ng_di fference
BETWEEN confi g_pkg. mi n_di fference and conf 1 g_plcg.max_di fference
THEN一
adjust_Hne_1tem;
END IF;--
IF cust_status = config_pkg.closed_status
THEN ~一
reopen_customer;
END IF; ~
Если какое-либо из «магических» значений изменится, достаточно будет изменить соответствующую константу в пакете. Если в программах есть жестко закодированные литеральные значения, то готовьтесь к тому, что вам придется вносить в код повторяющиеся изменения. Это может случиться как во время разработки, так и при сопровождении приложения. Во избежание данной проблемы рекомендуем никогда не включать литеральные значения прямо в программы, а помещать их в пакет.
Устранение недостатков встроенных функций
Некоторые пакеты Oracle, в частности UTL_FILE и DBMS_OUTPUT, с функциональной точки зрения оставляют желать лучшего. Если вы не можете полностью их заменить, то надстройте над ними собственные пакеты, в которых устранены недостатки и в которые добавлены недостающие функции.
Часто вместо того чтобы заполнять книгу примерами, мы приводим имена файлов на узле O'Reilly, в которых содержится демонстрационный код. Так поступим и в данном случае и посоветуем вам просмотреть следующие пакеты.
Q filepath.pkg. Добавляет в пакет UTL_FILE поддержку пути, что позволяет искать нужный файл в нескольких заданных каталогах. ,
Q dbparm.pkg. Облегчает использование утилиты DBMS_UTI LITY. GET_PARAMETER_VA- LUE, предназначенной для извлечения значений из инициализационного файла (INIT.ORA).
Q do.pkg. Дорабатывает процедуру вывода строки (пакет DBMS_OUTPUT), имеющую некоторые конструктивные недостатки, например, она не может выводить значения типа BOOLEAN или строки длиннее 255 байт.
Группировка логически связанных функций
Если вы имеете десяток процедур и функций, связанных с реализацией некоторой части приложения, объедините их в пакет, чтобы облегчить сопровождение и поиск кода. Это особенно важно при кодировании бизнес-логики приложения.
Учтите, что не следует жестко кодировать бизнес-логику (тем более, многократно) в отдельных компонентах приложения или размещать ее в отдельных программах, которые чрезвычайно трудно сопровождать.
Разработку нового приложения нужно начинать с создания набора пакетов, инкапсулирующего все применяемые в нем бизнес-правила. Причем эти правила могут быть частью большого пакета, предоставляющего интерфейс к определенным структурам данных, или хранится в специа-тьно созданном для них пакете. Рассмотрим следующий пример:
/* Файл в web: custrules.pkg */
CREATE OR REPLACE PACKAGE customer_rules■
IS-
FUNCTION min_balance RETURN PLSJNTEGER:
FUNCTION el 1giЫe_for_discount
(customerjn IN customerXROWTYPE)
RETURN BOOLEAN;
FUNCTION eligible_for_discount
(customer_id_in IN customer.customer_idXTYPE)
RETURN BOOLEAN;~
END customer^ules;
Функция eHgible_fQr_discount, определяющая право на скидку, скрыта за интерфейсом пакета. Ее перегрузка обеспечивает два интерфейса к формуле: одна версия функции принимает первичный ключ и ищет s базе данных необходимую информацию о клиенте, а вторая — получает полную информацию о клиенте, уже загруженную в запись, объявленную как XROWTYPE. Если в какой-либо программе данные о клиенте уже прочитаны из базы данных, то вызов второй версии функции позволяет избежать повторного запроса к базе данных.
Конечно, в данном контексте под логически связанными понимаются не только функции, реализующие бизнес-логику приложения. Так, вы можете разработать и объединить в пакет функции, которые выполняют типичные операции со строками и дополняют встроенные функции Oracle.
Кэширование статических данных сеанса
Постоянство структур данных пакета позволяет ускорить работу приложения за счет кэширования статических данных. Приведем перечень возможных объектов кэширования и имена файлов с примерами, которые находятся на узле O'Reilly.
О Отдельные значения, s частности имя сеанса, возвращаемое функцией USER (например, thisuser.pkg и thisuser.tst),
Q Строка или набор разнотипных значений, таких как конфигурационная информация для данного пользователя (например, init.pkg и init.tst).
О Набор однотипных значений, в частности содержимое статической таблицы с набором кодов и описаний (например, таблица сотрудников emplu.pkg и еш- plu.tst).
Файлы с расширением .tst предназначены для сравнения производительности с кэшированием и без него.
При использовании данной технологии помните, что для каждого сеанса, работающего с определенным пакетом, данные кэшируются отдельно. Это означает, что если одна кэшируемая копия таблицы занимает 20 Кбайт и они создаются для 1000 пользователей, то для хранения этих копий потребуется дополнительно 2 Мбайт памяти.
Пакеты и объектные типы
Как пакеты, так и объектные типы представляют собой контейнеры, объединяющие данные и элементы кода. Однако нужно ли нам иметь два типа контейнеров и не дублируют ли они функции друг друга? Не вытесняются ли пакеты объектными типами, особенно после того как в Oracle была добавлена поддержка наследования? И если нет, то в каких случаях следует использовать объектные типы, а в каких - пакеты? Сейчас мы попытаемся ответить на эти вопросы.
Пакеты и объектные типы действительно имеют много общего: и те и другие могут содержать одну или более программ и структур данных, а также спецификацию и тело. Однако между ними есть и принципиальные отличия.
О Объектный тип — это шаблон данных, на основе которого можно создавать любое количество экземпляров типа (объектов). Каждый экземпляр имеет полный набор атрибутов (данных) и связан с методами (процедурами и функциями) шаблона. Экземпляры объектных типов могут храниться в базе данных. Пакет же является одиночной структурой, подобной статическому объектному типу, и создавать его экземпляры нельзя.
О В Огас1е9г и последующих версиях объектные типы поддерживают наследование. Это означает, что можно объявить объектный подтип, который унаследует все атрибуты и методы супертипа. Для пакетов концепция иерархии и наследования неприменима. Подробнее об объектной архитектуре рассказывается в главе 21.
О Пакеты позволяют создавать личные, скрытые данные и программы. Объектные типы не поддерживают скрытых методов и атрибутов — все их компоненты объявляются как общие и являются общедоступными (хотя реализация методов скрыта в теле пакета).
В настоящее время лишь немногие используют объектные типы и объектнореляционную модель Oracle. Для большинства разработчиков пакеты все еще остаются базовыми строительными блоками приложений PL/SQL.
Если вы планируете применять объектные типы (а об этом с выходом версии Огас1е9г стоит подумать весьма серьезно), рекомендуем поместить значительную часть сложного кода в пакеты и вызывать его из методов объектных типов. Это позволит сохранить гибкость кода, присущую объектному подходу, и обеспечить возможность совместного использования кода разными элементами приложения.
1818ТриггерыТриггеры
18
18
Триггеры
Триггеры
> Триггеры уровня инструкций DML > Триггеры уровня инструкций DDL > Триггеры событий базы данных А Замещающие триггеры А Триггеры AFTER SUSPEND
Триггеры - это особые хранимые процедуры, выполняемые в ответ на происходящие в базе данных события. Они относятся к числу наиболее важных элементов промышленных приложений базы данных Oracle и обычно связаны со следующими операциями и событиями,
О Проверка внесенных в таблицы изменений. Поскольку логика проверки данных непосредственно связана с конкретным объектом базы данных, использование триггеров гарантирует ее строгое выполнение и соблюдение.
О Автоматизация сопровождения базы данных. Уже в Oracle8z можно было использовать триггеры, запускаемые автоматически при загрузке и выгрузке базы данных для выполнения операций инициализации и очистки. Это значительно удобнее, чем создавать для этих операций внешние по отношению к базе данных сценарии.
О Установка ограничений иа выполнение операций над объектами базы данных. Триггеры можно использовать для проверки того, допускается ли выполнение определенной операции над конкретным объектом базы данных (например, удаление или модификация таблицы). Когда правила проверку реализованы в виде триггеров, обойти их очень трудно, если вообще возможно.
События и структурные компоненты процесса обработки данных, с которыми можно связывать триггеры, перечислены ниже.
О Инструкции языка манипулирования данными (DML). Триггеры DML запускаются в ответ на вставку, обновление или удаление строки таблицы базы данных. Их можно использовать с целью проверки значений, устанавливаемых
по умолчанию, выполнения аудита изменений и даже запрета определенных DML-инструкций.
О Инструкции языка определения данных (DDL). Триггеры DDL запускаются в ответ на выполнение D DL-инструкций, например, при создании таблицы. С их помощью можно выполнять аудит и запрещать определенные операции.
О События базы данных. Триггеры событий базы данных используются при запуске и остановке базы данных, при подключении и отключении сервера, а также в случае возникновения ошибок Oracle. Начиная с Oracle8i они также позволяют контролировать активность базы данных.
О Триггеры INSTEAD OF. Замещающие триггеры (триггеры INSTEAD OF) являются альтернативой триггерам DML. Они запускаются непосредственно перед операциями вставки, обновления, удаления, и их код определяет, какие действия следует выполнить вместо соответствующей операции. Триггеры INSTEAD OF управляют операциями над представлениями, но не над таблицами. С их помощью можно преобразовывать необновляемые представления в обновляемые, изменяя при необходимости их поведение.
О Приостановленные инструкции. В Oracle9t введена концепция приостановленных инструкций. Если в ходе выполнения инструкции возникла проблема доступности пространства (недостаточно табличного пространства или исчерпана квота), Oracle может перевести ее в режим приостановления до тех пор, пока эта проблема не будет решена. С данным событием можно связать триггер, автоматически уведомляющий пользователя или приложение о проблеме или даже самостоятельно ее устраняющий.
Все эти типы триггеров рассматриваются в данной главе. Для каждого из них приводится синтаксис, примеры и рекомендации по применению.
Триггеры уровня инструкций DML
Триггеры уровня инструкций языка манипулирования данными (триггеры DML) запускаются после вставки, обновления или удаления строки конкретной таблицы (рис. 18.1). Это наиболее распространенный тип триггеров, особенно часто применяемый разработчиками. Остальные триггеры используются преимущественно администраторами базы данных.
Существует несколько способов запуска триггеров DML. Это можно делать до или после выполнения DML-инструкции, а также до или после обработки каждой строки. Триггер может быть запущен при выполнении одной, двух или всех трех возможных инструкций (INSERT, UPDATE и DELETE). Для того чтобы правильно сконфигурировать триггер, необходимо ответить на ряд вопросов.
О Как триггер будет запускаться 一 по одному разу для каждой инструкции SQL или для каждой модифицируемой ею записи?
О Когда именно должен вызываться создаваемый триггер — до или после выполнения операции над записями?
О Какие действия должен выполнить триггер — вставку, обновление, удаление или все три операции сразу?
ПриложениеБаза данных
Рис. 18.1. Триггеры DML запускаются в ответ на изменения, производимые в таблице базы данных
Концепции триггеров
Прежде чем переходить к синтаксису и примерам использования триггеров DML, вам следует познакомиться с их концепциями и терминологией.
О Триггер BEFORE. Вызывается до внесения каких-либо изменений, в том числе до вставки записи (BEFORE INSERT).
О Триггер AFTER. Выполняется после того, как производятся все изменения, в частности после операции вставки записи (AFTER INSERT).
О Триггер уровня инструкции. Выполняется для отдельной SQL-инструкции, которая может обрабатывать одну или более записей базы данных.
О Триггер уровня записи. Вызывается для отдельной записи, обрабатываемой SQL-инструкцией. Если, предположим, таблица books содержит 1000 строк, то следующая инструкция UPDATE модифицирует все эти строки:
UPDATE books SET title = UPPER (title);
И если для данной таблицы определен триггер уровня записи, он будет запущен 1000 раз.
О Псевдозапись NEW. Структура данных с именем NEW, которая так же выглядит и обладает такими же свойствами, как и запись PL/SQL. Эта псевдозапись доступна только внутри триггеров обновления и вставки; она содержит значе- иия модифицированной записи после внесения изменений.
О Псевдозапись OLD. Структура данных с именем OLD, которая так же выглядит и обладает такими же свойствами, как и запись PL/SQL. Эта псевдозапись доступна только внутри триггеров обновления и удаления; она содержит значения модифицируемой записи до внесения изменений.
О Предложение WHEN. Часть триггера DML, определяющая условия выполнения кода триггера (и позволяющая избежать ненужных операций).
Сценарии-примеры для работы с триггерами DML
Мы поместили на web-узел издательства O'Reilly несколько сценариев, демонстрирующих работу перечисленных в предыдущем разделе типов триггеров.
Тип триггера
Файлы
Описание
Триггеры уровня инструкции и уровня записи
copy_tables.sql
statement vs row.sql
Создает две идентичные таблицы: одну с данными, а друг/ю пустую
Создает два простых триггера: один уровня инструкции, а другой уровня записи. После выполнения этих сценариев выполните следующую инструкцию и просмотрите результаты (с активизированной командой SERVEROUTPUT ON, чтобы они выводились на экран):
INSERT INTO to_table
SELECT * FROMfrom_table;
Триггеры BEFORE И AFTER
before_vs_after.sql ,
Создает триггеры BEFORE и AFTER. После выполнения сценария выполните следующую инструкцию и просмотрите результаты:
INSERT INTO to_table
SELECT * FROMfrom_table;
Триггеры для разных DML-операций
one_trlgger_per_type.sql
Создает триггеры AFTER INSERT, UPDATE и DELETE для таблицы testable. После выполнения сценария выполните следующие инструкции и просмотрите результаты:
INSERT INTO to_table
VALUES (1);
UPDATE to.table SET 8ll = 10;
DELETE to.table;
Триггеры в транзакциях
По умолчанию триггеры DML принимают участие в транзакциях, из которых они запущены. Это означает, что:
О если триггер инициирует исключение, будет выполнен откат соответствующей части транзакции;-
О если триггер сам выполнит DML-инструкцию (например, вставит запись в таблицу-журнал), она станет частью главной транзакции;
О в триггере DML нельзя выполнять инструкции COMMIT и ROLLBACK.
РИМЕЧАНИЕ
Если вы определите триггер DML как автономную транзакцию (см. главу 13), то все инструкции DML, выполняемые внутри данного триггера, будут сохраняться или отменятся независимо от основной транзакции.
В следующем разделе приводится синтаксис объявления триггера DML и рассматривается пример, в котором использована большая часть компонентов и параметров триггеров этого типа.
Создание триггера
Инструкция создания (или замены) триггера DML имеет следующий синтаксис:
CREATE [OR REPLACE] TRIGGER ипя_Траггерд
(BEFORE I AFTER]~
(INSERT I DELETE | UPDATE | UPDATE OF column list} ON имя_таблицы
[FOR EACH ROW]
[WHEN (...)]
[DECLARE ...]
BEGIN.
... исполняемые_операторы ...
[EXCEPTION j
END [имя_триггера];
Описание всех перечисленных здесь элементов приведено в таблице.
Страки Описание
Создание триггера с заданным именем. Предложение OR REPLACE не обязательно. Если триггер существует, а предложение OR REPLACE отсутствует, в ответ на попытку создать триггер будет сгенерирована ошибка ORA-4081
Задание условий запуска триггера: до (BEFORE) или после (AFTER) выполнения инструкции либо обработки строки
Определение типа DML-инсгрукции, с которой связывается триггер: INSERT, UPDATH или DELETE. Обратите внимание, что триггер, связанный с инструкцией UPDATE, может быть задан для всей строки или только для списка столбцов, разделенных запятыми. Столбцы можно комбинировать (с помощью оператора OR) и задавать в любом порядке. Кроме того, в строке 3 определяется таблица, с которой связывается данный триггер. Помните, что каждый триггер DML должен бьпъ связан с одной таблицей
Если задано предложение FOR EACH ROW, триггер будет запускаться для каждой обрабатываемой инструкцией строки. Но если это предложение отсутствует, по умолчанию триггер будет запускаться только по одному разу для каждой инструкции (то есть будет создан триггер уровня инструкции)
Необязательное предложение WHEN, позволяющее задать логику для избежания ненужного выполнения триггера
Необязательный раздел объявлений для анонимного блока, составляющего код триггера. Если объявлять локальные переменные не требуется, зто ключевое слово может отсутствовать. Никогда не объявляйте псевдозаписи NEW и OLD. Они создаются автоматически
, 8 Исполняемый раздел триггера. Он является обязательным и должен содержать как минимум одну инструкцию
Необязательный раздел исключений. В нем перехватываются и обрабатываются исключения, инициируемые только в исполняемом разделе
Обязательный оператор END. Для наглядности в него можно включить имя триггера
Приведем два примера использования триггеров DML, Сначала мы рассмотрим триггер, выполняющий несколько проверок при добавлении или изменении строки в таблице сотрудников. В нем содержимое полей псевдозаписи NEW передается отдельным программам проверки:
CREATE OR REPLACE TRIGGER val1date_employee_changes
AFTER INSERT OR UPDATE ~~
ON employee
FOR EACH ROW
BEGIN
check_age (:NEW.date_of_birth):
check_resume (:NEW.resume):
END:-
Следующий триггер, запускаемый перед вставкой данных, выполняет проверку изменений, производимых в таблице ceo_compensat1 on. Для сохранения новой строки таблицы аудита вне главной транзакции в нем используется введенная в Oracle8i технология автономных транзакций:
CREATE OR REPLACE TRIGGER bef Jns_ceo_comp
AFTER INSERT~ ~ ~
ON ceo_compensation
FOR EACH ROW
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN~
INSERT INTO ceo_conip_history
VALUES (:NEw.na储,
:OLD. compens ation, :NEW.conipensat1 on,
'AFTER INSERT'. SYSDATE);
COWIT:
END;
Предложение WHEN
Предложение WHEN предназначено для определения условий, при которых должен выполняться код триггера. В приведенном далее примере с его помощью мы указываем, что основной код триггера должен быть реализован только в том случае, если изменились значения в двух заданным столбцах таблицы:
CREATE OR REPLACE TRIGGER check_ra1se
AFTER UPDATE OF salary, commission
ON employee
FOR EACH ROW、
WHEN ((OLD.salary != NEW.salary DR'
(OLD.salary IS NULL AND NEW.salary IS NULL)) OR
(OLD.commission !-= NEW. commissi on OR
(OLD.commissi on IS NULL ANO NEW.commission IS NULL))) BEGIN
Иными словами, если при обновлении записи пользователь по какой-то причине повторно запишет в поле salary то же самое значение, триггер активизируется, но его основной код выполняться не будет. Поэтому, проверяя это условие в предложении WHEN, можно избежать расходов, связанных с запуском соответствующего кода PL/SQL.
ПРИМЕЧАНИЕ:
В файле genwhen.sp имеется процедура, формирующая предложение WHEN, в котором осуществляется проверка равенства нового (записываемого) и старого (первоначального) значений.
ПРИМЕЧАНИЕ
Предложение WHEN может использоваться только в триггерах уровня записи. Поместив его в триггер уровня инструкции, вы получите сообщение об ошибке компиляции (ORA-04077).
В большинстве случаев предложение WHEN содержит ссылки на поля псевдозаписей NEW и OLD. В это предложение разрешается также помещать вызовы встроенных функций, что и сделано в следующем примере, где с помощью функции SYSDATE ограничивается время вставки новых записей:
CREATE OR REPLACE TRIGGER valid_when__clause
BEFORE INSERT ON frame'
FOR EACH ROW'
WHEN C TO_CHAR(SYSDATE.'HH24') BETWEEN 9 AND 17 )
При использовании предложения WHEN необходимо соблюдать ряд правил. ■ О Логические выражения всегда должны заключаться в скобки. Эти скобки не обязательны в операторе IF, во необходимы в предложении WHEN триггера.
О Перед идентификаторами NEW и OLD нельзя вводить двоеточие (:). Этот символ, обозначающий хост-переменную, необходим в коде триггера PL/SQL, так как позволяет отличить хост-переменные от локальных переменных триггера, однако он не должен применяться в предложении WHEN.
О В предложении WHEN следует использовать только встроенные функции. Пользовательские функции или функции, определенные во встроенных пакетах (таких как DBMS_irnLITY), в нем вызывать нельзя (такой вызов приведет к ошибке 0RA-O4O76: Invalid NEW or OLD specification error). Чтобы вызвать встроенную функцию, переместите условия отбора записей в начало исполняемого раздела триггера.
ПРЕДЛОЖЕНИЕ FOR EACH ROW ПЕРЕД WHEN
В некоторых версиях PL/SQL перед предложением WHEN должно стоять предложение FOR EACH ROW, поскольку предполагается, что по умолчанию создается триггер уровня инструкции. Например:
SQL> CREATE OR REPLACE TRIGGER row_must_be_before_when
BEFORE INSERT ON frame -~-
WHEN ( new.strike = Y')
FOR EACH ROW
BEGIN NULL: END;
/
ERROR at line 3:
0RA-04077: WHEN clause cannot be used with table level triggers
SQL> CREATE OR REPLACE TRIGGER row_must_be_before_when
BEFORE INSERT ON frame-
FOR EACH ROW
WHEN ( new.strike ■ Y )
BEGIN NULL: END;
/
Trigger created.
Работа с псевдозаписями NEW и OLD
При запуске триггера уровня записи исполняющее ядро PL/SQL создает и заполняет две структуры данных, функционирующие подобно записям. Речь идет о псевдозаписях NEW и OLD (это не настоящие записи, поскольку они не обладают всеми свойствами записей PL/SQL). В псевдозаписи OLD хранятся исходные значения обрабатываемой триггером записи, а в псевдозаписи NEW — новые. Их структура идентична структуре записи, объявленной с атрибутом %ROWTYPE и создаваемой на основе таблицы, с которой связан триггер.
Вот несколько положений, которые следует принимать во внимание при работе с псевдозаписями NEW и OLD.
О Для триггеров, связанных с инструкцией INSERT, структура OLD не содержит данных, поскольку старого набора значений у операции вставки нет.
О Для триггеров, связанных с инструкцией UPDATE, заполняются обе структуры, и OLD и NEW. Структура OLD содержит исходные значения записи до обновления, a NEW — значения, которые будут содержаться в строке после обновления.
О Для триггеров, связанных с инструкцией DELETE, заполняется только структура OLD, а структура NEW остается пустой, поскольку запись удаляется.
О В поле ROWID обеих псевдозаписей, NEW и OLD, всегда помещается одно и то же значение.
О Значения полей записи OLD изменять нельзя. При попытке сделать это Oracle сгенерирует ошибку ORA-04085. Значения полей структуры NEW модифицировать можно.
О Структуры NEW и OLD нельзя передавать в качестве параметров процедурам или функциями, вызываемым из триггера. Разрешается передавать лишь их отдельные поля. В файле gentrigrec.sp имеется программа для генерирования кода, выполняющего копирование значений полей NEW и OLD в структуру, которую можно передать в качестве параметра.
О В ссылках на структуры NEW и OLD в анонимном блоке триггера соответствующие ключевые слова нужно предварять двоеточием:
IF :NEW.salary > 10000 THEN...
О Над структурами NEW и OLD нельзя выполнять операции уровня записи. Например, следующий оператор вызовет ошибку компиляции триггера:
BEGIN :new := NULL; END:
С помощью предложения REFERENCING в триггере, можно менять имена псевдозаписей базы данных, что позволяет писать более самодокументированный код, ориентированный на конкретное приложение. Приведем пример:
CREATE OR REPLACE TRIGGER audit_update
AFTER UPDATE
ON frame
REFERENCING OLD AS prior_to_cheat NEW AS after_cheat
FOR EACH ROW ■~ ~-
BEGIN
INSERT INTO frame_audit
(bowler_id, game id.
frame^umber, old_strike. new_strike, old_spare, new_spare, old_score.
"new_score, change_date, operation)
VALUES (:after_cheat.bowl erJ d. :after_cheat,game_1d, :after_cheat. frame_nuiriber, :pri or_to_cheat.stri ke. :after_cheat.strike. :prior_to_cheat.spare. :after_cheat.spare, :prior_to_cheat.score, :after_cheat,score.
SYSDATE.
''UPDATE');
END;
Запустите сценарий файла fulLold_and_new.5ql, и вы увидите, как ведут себя псевдозаписи NEW и OLD.
Определение инструкций DML
Oracle предлагает набор функций (они также называются операционными директивами), позволяющих определить, какая инструкция DML вызвала запуск триггера. О том, какое значение при каком условии возвращает каждая из этих функций, рассказывается ниже.
О INSERTING — возвращает TRUE, если триггер запущен в ответ на вставку записи в таблицу, с которой он связан, и FALSE в противном случае.
О UPDATING — возвращает TRUE, если триггер запущен в ответ на обновление записи в таблице, с которой он связан, и FALSE в противном случае.
О DELETING — возвращает TRUE, если триггер запущен в ответ на удаление записи из таблицы, с которой он связан, и FALSE в противном случае.
Пользуясь этими директивами, можно создать один триггер, который выполнял бы действия, связанные с несколькими операциями. Например:
/* Файл в web: one_trigger_does_it_an .sql */
CREATE OR REPLACE TRIGGER three_for_the_pr1ce_of_one
BEFORE DELETE OR INSERT DR UPDATE ON account_transact1on
FOR EACH ROW-
BEGIN
--Сохраняем информацию о пользователе, который вставил новую строку
IF INSERTING THEN
:NEW.created_by :- USER;
:NEW.created_date := SYSDATE;
Протоколируем удаление с помощью специальной программы аудита
ELSIF DELETING THEN
audit_cleletion(LJSER.SYSDATE):
__ Сохраняем информацию о пользователе, который последним обновлял строку
ELSIF UPDATING THEN
:NEW.LAST_UPDATED_8Y USER:
:NEW.LAST_UPDATED_OATE :- SYSDATE:
END IF: ~~
END:
Функция UPDATING представляет собой перегруженную версию, принимающую в качестве аргумента имя конкретного столбца. Перегрузка функций представляет собой удобный способ изоляции операций обновления отдельных столбцов.
/* Файл в web: overloaded_update.sql */
CREATE OR REPLACE TRIGGER^alidate_update
BEFORE UPDATE ON account_transaction
FOR EACH ROW~
BEGIN
IF UPDATING('ACCOUNT_NO') THEN
appl1cat1on_error_handler('ERROR'.
1 Account number cannot be updated');
END IF;
END:
Спецификация имени столбца не чувствительна к регистру клавиатуры. Имя столбца до запуска триггера не анализируется, и если в таблице, связанной с триггером, заданного столбца не оказывается, функция просто возвращает значение FALSE.
ПРИМЕЧАНИЕ Операционные директивы можно вызывать из любого кода PL/SQL, а не только в триггерах. Однако значение TRUE они возвращают лишь в том случае, если используются в триггерах или программах, вызванных из таковых.
Пример аудита, выполняемого с помощью триггера
Одной из задач приложения, для реализации которой идеально подходят триггеры, является аудит изменений, вносимых в базу данных. Аудит - это ряд манипуляций, позволяющих определить действия каждого в системе и их правомерность. Под обеспечением безопасности подразумевается прежде всего невозможность для пользователей выполнять недопустимые операции. С помощью аудита можно выявить и записи, введенные мошенническим путем, и несанкционированные запросы.
Рассмотрим простейшее решение задачи аудита с помощью приложения, написанного администратором кегельбана Паркером, который неожиданно стал получать от посетителей жалобы по поводу мошенничества отдельных игроков. Чтобы проверить справедливость жалоб, Паркер разработал данное приложение.
Основу приложения составляет таблица frame, в которую записывается счет (в отдельной рамке для каждого игрока):
/* Файл a web: bowlerama_tables.sql */
CREATE TABLE frame
(bowlerjdNUMBER,
game_1dNUMBER.
frame_nunber NUMBER, strikeVARCHAR2U) DEFAULT 'N'..
spareVARCHAR2(1) DEFAULT 'N'..
scoreNUMBER,
CONSTRAINT framej)k PRIMARY KEY (bowlerjd. game_1d. frame_number));
В данном фрагменте кода нас интересует только процедура регистрации доступа к данным, в частности сведения о том, кто добавлял или изменял строки (если, конечно, такие действия производились). Поэтому создается еще одна таблица, frame_audit( которая будет содержать измененные записи со старыми и новыми значениями. Сравнивая эти значения, можно выяснить, кто и когда модифицировал таблицу frame:
CREATE TABLE frame_audit (bowler_idNIMBER.
gamejdNUMBER.
frame_nunber NIMBER, old—Rrikc VARCHAR2(D. new_strike VARCHAR2(1). oltOpuc' VARCHAR2(1). new_spareVARCHAR2C1),
old_score NUMBER, new^score NUMBER. change_date DATE, operation VARCHAR2(6)):
При внесении изменений в таблицу frame Паркер сохраняет в таблице fra- me_audit значения модифицируемых записей до и после операции обновления. Это делается с помощью одного триггера:
/* Файл в web: bowlerama_full_audit.sql */
CREATE OR REPLACE TRIGGER audTtJrames
AFTER INSERT OR UPDATE OR DELETE DN frame
FOR EACH ROW
BEGIN
IF INSERTING THEN
INSERT INTO frame^audit(bowler_id.game_id,frame_number.'
new_stri ke.new_spare.new_score.
change_date.operation)
VALUES(:new.bowler_1d.:new.game_id,:new.frame_number,
11121314151617181920211112131415161718192021SYSDATE.'INSERT'):SYSDATE.'INSERT')::new.strike,:new.spare,:new.score,
11
12
13
14
15
16
17
18
19
20
21
11
12
13
14
15
16
17
18
19
20
21
SYSDATE.'INSERT'):
SYSDATE.'INSERT'):
ELSIF UPDATING THEN
INSERT INTO frame_audi t(bowl erJd,game_1d,frame_number.
old_strike,new_strike. old_spare,new_spare. old_score.new_score, change_date.operation)
VALUES(:new.bowler_id.:new.game_1d.;new.frame_number, :old.strike.mew.strike.
:old.spare.:new.spare.
:old.score.:new.score.
SYSDATE.'UPDATE');
24
ELSIF DELETING THEN
INSERT INTO frame^audit(bowlerjd,game_id,frame_number,
old_str1ke.old_spare,o'ld_score,
change_date.operation)
VALUESC :old.bow1er_1d. :old.ganie_1d, :old.frame_number.
:old.strike.:old.spare,:old.score,
SYSDATE.'DELETE');
END IF;
END:
Обратите внимание, что при добавлении записи в таблицу результатов (строки 6-11) для заполнения строки таблицы frame_audit применяется псевдозапись NEW. При обновлении записи (строки 14-23) используется информация из псевдозаписей NEW и OLD, а в случае ее удаления (строки 26-31) — из псевдозаписи OLD.
Конечно Паркер никому не сообщил о новой системе аудита. Сэм же (очень амбициозный, но не очень умелый игрок) решил любым способом повысить свой рейтинг в клубе. Поскольку владельцем кегельбана был его отец, он имел доступ к SQL*Plus. Этого было достаточно, чтобы однажды подключиться к базе данных прямо из SQL*Plus и попытаться «улучпгить» свои результаты.
Сначала Сэм проставил себе «сбито» в первой рамке:
SQL> INSERT INTO fraive
(BOWLER_ID,GAME_ID,FRAME_NW®ER,STRIKE)
VALUESCl.1,1 ~Y');
row created.
Но затем он решил быть осторожнее и изменил значение в первой рамке на «резерв»:
SOL> UPDAIt frame
SET strike = 'N',■
spare •= 'Y'
WHERE bowl er_id - 1
AND gane_id »1
AND frame_number - 1:
row updated.
Вдруг Сэм услышал в коридоре шум и поэтому сразу же восстановил записи:
SQL> DELETE frame
WHERE bowlerid = 1
AND game_1d - 1
AND frame_number = 1;」
row deleted.
SQL> COWIT:
Conmit complete.
Он даже проверил, действительно ли удалена введенная им запись:
SQL> SELECT * FROM frame: no rows selected
Все обошлось, и Сэм вздохнул с облегчением. Но он решил позже вернуться и все-таки воплотить задуманный план в жизнь.
Просматривая вечером таблицу frame_audit, Паркер сразу обнаружил проделки Сэма (он мог бы даже автоматизировать просмотр с помощью пакета DBMS〜JOB):
SELECT bowlerjd, gamejd. frame_number, old__strike, new_strike, old_spare. new__spare. change_date. operation
FROM frame_audit;
Вот результаты выполнения такого запроса:
BOWLER_ID GAMEJD FRAME_NUMBER 0 N 0 N CHANGE_DA OPERAT
111YN12-SEP-00 INSERT
111YNNY12-SEP-00 UPDATE
111NN12-SEP-OO DELETE
Ни одно из внесенных Сэмом изменений не укрылось от взгляда Паркера. Факт выполнения всех трех инструкций был зафиксирован триггером.
Предложение WHEN
После нескольких месяцев успешной эксплуатации приложения Паркер посчитал необходимым его усовершенствовать. Проанализировав логику приложения, он понял, что данные в таблице frame изменяются только в полях strike, spare и score. Поэтому и число операций, при выполнении которых запускается триггер, можно ограничить еще больше:
CREATE OR REPLACE TRIGGER audit_update
AFTER UPDATE OF strike, spare, score
ON frame.
REFERENCING OLD AS pr1or_to_cheat NEW AS after cheat
FOR EACH ROW* ~—
BEGIN
INSERT INTO frame_dudit (...)
VALUES (...):
END;
Прошло несколько недель, и Паркер решил еще раз модифицировать код приложения. Оказалось, что новые записи помещаются в таблицу frame_audit и в тех случаях, когда при обновлении содержимого строки значения в их полях на самом деле не менялись (то есть новое и старое значения одинаковы). В результате таблица контроля заполнялась ненужными записями, свидетельствующими лишь о том, что никаких изменении пока не произошло:
SQL> UPDATE FRAME
SET strike ■ strike;
row updated.
SQL> SELECT old_strike,
new_strike,
old_spare.
new_spare,
old_score,
new^score
FROM frame_audit;
0 N 0 N OLDJCORE NEWJCORE
Y Y N N
Паркер еще больше ограничил функции триггера, поместив в него предложение WHEN. В результате триггер стал запускаться только в том случае, если значения в полях strike, spare, score действительно изменялись:
/* Файл в web: final_audit.sql */
CREATE OR REPLACE TRIGGER aud1t_update
AFTER UPDATE OF STRIKE. SPARE, SCORE ON FRAME
REFERENCING OLD AS prior_to_cheat NEW AS after_cheat
FOR EACH ROW-一
WHEN ( prior_to_cheat.strlke !- after_cheat.strike DR
prior_to_cheat.spare != after_cheat.spare OR
prior to cheat.score != after cheat.score ) BEGIN
INSERT INTO FRAME_AUDIT(bowler_id.game_1d.frame_number, old_strlke,new_strike.
.old_spare,new_spare. old^score,new_score, change_date.operation)
VALUES(:after_cheat.bowler_i d,:a fter_cheat.game_i d,:after_cheat. frame_number,
:pri or_to_cheat.strike.:after_cheat.strike.
:prlor_to_cheat.spare,:after_cheat.spare,
:prior_to__cheat. score. :after_cheat. score,
SYSDATE,'UPDATE');一
END;
Теперь количество записей в таблице frame_audit будет минимальным, что позволит быстро идентифицировать вероятных нарушителей. Паркер провел завершающее тестирование триггера:
SQL> UPDATE frame '
2 SET strike = strike;■
row updated.
SQL> SELECT old_strike,
new_str1ke,
o1d_spare,
new_spare,
oldscore,
n 此 score
FROM frame_audit:
no rows selected
Использование псевдозаписей для оптимизации процесса выполнения триггеров
Обеспечив наиболее приемлемую, с его точки зрения, эффективность контроля, Паркер решил сделать приложение более дружественным. Главная задача теперь состояла в том, чтобы при записи в базу данных информации о бросках с результатами «сбито» и《резерв» система увеличивала счет в рамке на 10. Это позволило бы вводить только информацию о бросках, так как подсчет система должна производить автоматически.
CREATE OR REPLACE TRIGGER set_score
BEFORE INSERT ON frame
FOR EACH ROW
WHEN ( NEW.score IS NULL )
BEGIN
IF :NEW.strike = 丫 OR :NEW.spare = 'Y'
THEN
:NEW.score :- :NEW.score + 10;
END IF;
END:
РИМЕЧАНИЕ
Следует помнить, что значения палей в записи NEW могут изменять только триггеры BEFORE уровня строки.
А в завершение Паркер добавил в триггер логику проверки счета, с тем чтобы таким образом можно было отслеживать и отменять определенные изменения, которые запрещено производить в таблице базы данных:
/* Файл в web: validate_score.sq' */
CREATE OR REPLACE TRIGGER validate_score
AFTER INSERT OR UPDATE ON frame -
FOR EACH ROW
BEGIN
IF :NEW.strike 工'Y' AND
:NEW.score < 10 THEN.
RAISE APPLICATION_ERROR(-20001.'ERROR: Score For Strike Must Be >= 10'):
ELSIF ;NEW.spare 工'Y'~AND
;NEW.score v 10 THEN
RAISE_APPLICATION_ERROR(-20001.'ERROR: Score For Spare Must Be “ 10');
ELSIF :NEW.strike = 'Y' AND
:NEW.spare 工'Y' THEN
RAISE_APPLICATr0N_ERROR(-20001.'ERROR; Cannot Enter Spare And Strike For Same
Frame');一
END IF:
END;
Теперь попытка вставить строку, не соответствующую правилам, инициирует сообщение об ошибке:
SQL> INSERT INTO frame VALUES (1.1.1.NULL,NULL.5);
INSERT INTO frame
ERROR at Une 1:
DRA-20000: ERROR: Score For Strike Must Be Less >= 10
SQL> INSERT INTO frame VALUESCl.l.l, Y'.NULL.5):
INSERT INTO frame
ERROR at line 1:
ORA-20001: ERROR: Score For Strike Must >- 10
Триггеры одного типа
Oracle позволяет связать с таблицей базы данных несколько триггеров одного типа. Рассмотрим такую возможность еще на одном примере, на этот раз связанном с игрой в гольф. Следующий триггер уровня строки вызывается при вставке в таблицу новой записи и добавляет в нес комментарий, текст которого определяется соотношением текущего счета и номинального значения 72:
/* Файл в web: go!^commentary.sql */
CREATE OR REPLACE TRIGGER golf^coirmentary
BEFORE INSERT ON goK_scores
FOR EACH ROW ~
BEGIN
IF :NEW.score < 72 THEN
:NEW.commentary ;- 'Under Par';
ELSIF :NEW.score = 72 THEN
:NEW.conmentary 尸'Par';
ELSE
;NEW.commentary ;= 'Over Par';
END IF;
END;
Эти же действия можно выполнить и с помощью трех отдельных триггеров уровня строки типа BEFORE INSERT с взаимоисключающими условиями, задаваемыми в предложениях WHEN:
CREATE OR REPLACE TRIGGER golf_cofnnentary_under_par
BEFORE INSERT ON golf_scores -~
FOR EACH ROW -
WHEN (NEW.score < 72)
BEGIN
:NEW.co!rmentary :- 'Under Par';
END;
CREATE OR REPLACE TRIGGER golf_co!rmentary_par
BEFORE INSERT ON golf^scores
FDR EACH ROW 一
WHEN (NEW.score = 72)
BEGIN
:NEW.commentary := 'Par1;
END;
CREATE OR REPLACE TRIGGER golf_commentary_over_par
BEFORE INSERT ON golf_scores ~~
FOR EACH ROW —
WHEN (NEW.score > 72)
BEGIN
:NEW.c(wientary := 'Over Par';
END;
Каждая реализация триггера имеет свои достоинства и недостатки. Один триггер проще сопровождать, поскольку все правила сосредоточены в одном месте, однако в случае применения достаточно сложной программной логики предпочтительнее использовать отдельные триггеры для каждой операции проверки.
Главным недостатком использования нескольких триггеров для отслеживания одной операции является невозможность определить порядок их запуска. В приведенном выше примере это обстоятельство не имеет значения, но в других случаях оно может привести к серьезным проблемам. Скажем, вы можете определить, какое значение получено в результате выполнения запроса SELECT в данном фрагменте кода:
/* Файл в web: multiple3rigger_seq.sql */ DROP TABLE incremented_values;
CREATE TABLE incremented_va1ues
Cvaluejnserted NUMBER,
value_1ncremented NUMBER):
CREATE OR REPLACE TRIGGER increment_by_one
BEFORE INSERT ON incremented_values
FOR EACH ROW~
BEGIN
:new.value_incremented := :new.value_1ncremented + 1;
END:~~
CREATE OR REPLACE TRIGGER increment_by_two
BEFORE INSERT ON 1ncreniented_values
FOR EACH ROW-
8EGIN
IF :new.va1ue_incremented > 1 THEN
:new.va1ue_1ncremented := :new.value_incremented + 2;
END IF: ~-
END:
INSERT INTO 1ncremented_values
VALUES(l.l);-
SFLFCT *
FROM incremented_values:
Есть какие-нибудь предположения? Для моей базы данных результаты получились такими:
SQL> SELECT *
2 FROM 1ncremented_values;
VALUEJNSERTED VALUE_INCREMENTED
1 2
Это означает, что первым сработал триггер increment_by_tW0! который не выполнил никаких действий, поскольку значение в столбце value_1 ncremented не превышало 1; затем сработал триггер 1псгегпеп1_Ьу_опе, увеличивший значение столбца value_incremented на 1. А вы тоже должны получить такой результат? Вовсе не обязательно. Будет ли этот результат всегда одним и тем же? Опять-таки, ничего
нельзя гарантировать. Oracle утверждает, что порядок запуска однотипных триггеров, связанных с одной таблицей, не определен и произволен, поэтому задать его явно невозможно. На этот счет имеется несколько мнений, среди которых наиболее популярны два следующих: триггеры запускаются в порядке, обратном порядку их создания, или же в соответствии с идентификаторами их объектов. Однако полагаться на подобные предположения не стоит. '
Ошибки, возникающие при изменении таблицы: проблема и решение
Известно, что изменяющиеся объекты всегда трудно анализировать и оценивать. Поэтому когда триггер уровня строки пытается прочитать или изменить данные в таблице, находящейся в состоянии изменения (с помощью инструкции UPDATE, INSERT или DELETE), генерируется ошибка с кодом 0RA-4O91.
Предположим, для таблицы сотрудников требуется задать некоторое ограничение на значения в столбцах, заключающееся в том, что яри повьппении оклада сотрудника новый его оклад не должен превышать максимальный по подразделению более чем на 20 %.
Проверку этого условия можно было бы реализовать с помощью следующего триггера:
CREATE OR REPLACE TRIGGER brake_on_raises
BEFORE UPDATE OF salary ON employee
FOR EACH ROW
DECLARE
l_curr_max NUMBER;
BEGIN ~
SELECT MAX (salary) INTO l_curr_max
FROM employee:
IF 1 curr max * 1.20 < ;NEW.sal ary
THEN"—
errpkg.RAISE (
employee_rules.en_salary_1ncrease_too_large.
iNEW.empToyeeJd."~ -
:NEW.salary
END IF:■
END:
Однако яри попытке удвоить, скажем, оклад программиста PL/SQL Oracle выдаст такое сообщение об ошибке:
0RA-04091: table SCOTT.EMPLOYEE is mutating, trigger/function may not see it
Тем не менее существует несколько приемов, которые помогут предотвратить появление сообщения о данной ошибке.
О В общем случае триггер уровня строки не может считывать или записывать данные таблицы, с которой он связан. Но подобное ограничение относится только к триггерам уровня строки. Триггеры уровня инструкции могут и считывать и записывать данные своей таблицы, что дает возможность произвести необходимые действия.
О Если триггер выполняется как автономная транзакция (для этого в него нужно добавить директиву PRAGMA AUTONOMOUS TRANSACTION и выполнить в его теле
инструкцию COMMIT), тогда в нем можно запрашивать содержимое его таблицы. Однако модификация такой таблицы все равно будет запрещена.
Изменяемые таблицы и внешние ключи
Ошибка, возникающая при модификации таблицы, которая находится в состоянии изменения, часто связана с внешними ключами. Рассмотрим еще один пример. Предположим, система управления транзакциями банка реализована на основе Огас1е7.3.4. Владельцы банка оказались столь щедры, что запланировали выплачивать по 100 долларов каждому клиенту, открывающему новый счет. Как обычно, разработчикам сообтцили об этом в пятницу вечером, поэтому бедняжке Кэй пришлось поработать и в выходные.
Кэй подумала, что для решения этой задачи больше всего подходит триггер INSERT, который должен вносить соответствующие изменения в таблицу базы данных. То есть, как только в таблице account (содержащей список счетов) будет создана очередная новая запись, триггер тут же добавит в таблицу account_tran- saction (с результатами траязакций счетов) запись об операции депозита. Такое решение показалось Кэй просто замечательным, поскольку на его реализацию вместе с отладкой и тестированием, как она предполагала, уйдет всего несколько часов и остаток выходных можно будет провести за игрой в кегли.
Зная, что таблицы account и account_transaction связаны между собой внешним ключом, образованным на основе столбца accounted, Кэй создала следующий триггер:
CREATE TRIGGER g1ve_away_free_money
AFTER INSERT ON account
FOR EACH ROW
BEGIN
INSERT INTO account_transaction
(transactionjd.
account_id.
transaction_type,
transaction_amount, comments)
VALUES(account_transaction_seq.nextval.
iNEW.accountJd.
'DEP', 一
100,
'Free Money!');
END:
Кэй была полностью уверена в своем триггере (что могло быть не так со столь простым фрагментом кода?), но все-таки решила проверить его. К своему огромному удивлению она тут же получила такой ответ:
SQL> INSERT INTO account
(accounted, account_owner)
VALUES(匸'Test'):-
INSERT INTO account
ERROR at line 1:
ORA-04091: table SCOTT.ACCOUNT is mutating, trigger/function may not see it
ORA-06512: at 飞COTT.CIVE_AWAY_FREE一MONEY」 line 2
0RA-O4088: error during execution of trigger 'SCOTT.GIVE_AUAY_FREE_MONEY'
Таблица account в этой транзакции действительно изменяется из-за вставки строки с новым счетом. Когда триггер пытается добавить новую запись в таблицу account_transaction, Oracle должна проверить значение в столбце account_id таблицы account. Вот тут-то и происходит ошибка: читать данные из таблицы account пока нельзя!
Конечно, можно сказать, что эта ошибка не имеет смысла, поскольку вся необходимая информация легко доступна; первичный ключ задан в исходной инструкции INSERT. Как бы там ни было, факт остается фактом: созданный триггер нарушает ограничение целостности, налагаемое на триггеры уровня строки.
ПРИМЕЧАНИЕ
Начиная с Oracle 8.1.5 указанная ошибка больше не генерируется.
Описанное ограничение относится только к триггерам уровня строки; на триггеры уровня инструкции оно не распространяется.
Как обойти ошибки при изменении таблицы
Какие же шаги следует предпринять, встретившись с такой ошибкой? Некоторые разработчики отказываются от использования триггера и ищут другой способ реализации этой же логики. Однако отказываться от триггеров вовсе не обязательно —можно избежать ошибки, воспользовавшись тем обстоятельством, что для триггеров уровня инструкции не существует ограничений на доступ к таблице, с которой они связаны, а. также тем, что структуры данных уровня пакета сохраняются в течение всего сеанса.
Один из способов избежать ошибки указанного типа состоит в использовании коллекций. Предположим, Oracle не позволяет выполнять определенные действия в триггере уровня строки, требуя отложить их до момента запуска триггера уровня инструкции. Следовательно, вы должны побеспокоиться, чтобы эти действия были выполнены позже, то есть сохранить информацию о том, что нужно делать. На роль «списка задача отлично подходят коллекции PL/SQL.
Итак, триггер уровня строки, вместо того чтобы выполнить необходимые действия, просто сохраняет в коллекции всю информацию, нужную для выполнения этих же действий на уровне инструкции. В триггере уровня инструкции можно просмотреть всю коллекцию и выполнить тот код, который нужно было выполнить в триггере уровня строки (эти действия показаны на рис. 18.2). Посмотрим, как Кэй применила описанную технологию.
Список задач (коллекция)Список задач (коллекция)Запускается триггер строки N ■'Запускается триггер строки N ■'Считывание списка задач и выполнение соответствующих действийСчитывание списка задач и выполнение соответствующих действий.Добавление задачи в список Запускается триггер строки 1 -
Список задач (коллекция)
Список задач (коллекция)
Запускается триггер строки N ■'
Запускается триггер строки N ■'
Считывание списка задач и выполнение соответствующих действий
Считывание списка задач и выполнение соответствующих действий
Трипвр инструкции
Обработка строк закончена;переход к триггеру уровняинструкции
Рис 18.2. Использование коллекции для обхода ошибки,связанной с внесением изменений в таблицу
Итак, Кэй реализовала следующий алгоритм.
Инициализация коллекций в пакете PL/SQL в начале выполнения инструкции.
Сохранение в коллекции идентификатора каждой обрабатываемой строки.
Обработка каждого элемента коллекции по завершении инструкции.
Вот какой пакет PL/SQL она создала (мы приводим лишь его начало):
/* Файл в web: mutation_zone.sql */
CREATE OR REPLACE PACKAGE give_away_money AS
PROCEDURE 1nit_tables:
END give_away_money;
CREATE OR REPLACE PACKAGE BODY give_away_money AS
--структура для хранения номероз счетоз
TYPE v_account_table_type IS TABLE OF NUMBER
INDEX BY BINARYJNTEGER;'
v_account_table v_account_table_type:
/★*/
PROCEDURE 1nit_tables IS
/**/
BEGIN -- Инициализация списка номеров счетоз путем его очистки
v_account_tablе DELETE;
END init_tables;
END g1ve_away_money:
Эта простая инициализационная процедура очищает коллекцию, в которую добавляются номера новых счетов. Ее нужно связать с инструкцией INSERT, вставляющей новые записи в таблицу account:
CREATE OR REPLACE TRIGGER before_insert statement
BEFORE INSERT ON account ~~
BEGIN
11 Инициализируем таблицы PL/SQL для хранения счетов, на
|| которые нам предстоит положить по 100 доллароа.
*/.
glve_away_money.in1t_tables:
END; 一~
Теперь при каждом выполнении инструкции INSERT для таблицы account коллекция будет очищаться.
Далее нам нужно сохранить номера добавляемых в таблицу account счетов. Для этого поместим в пакет give_away_money следующую процедуру;
PROCEDURE add__account_t°Jist ( p_account NUMBER ) 15
BEGIN ~~-
v_account__tableCNVLCv_account_table.LAST,0) + 1) :- p_account:
END add_account_ta_l1 st:
Данная процедура вызывается из триггера AFTER INSERT уровня строки:
CREATE OR REPLACE TRIGGER after_1n5ert_row
AFTER INSERT ON account
FOR EACH ROW
BEGIN
/*
11 Добавляем в список новый счет, на который нужно || будет положить 100 долларов.
*/ ■
'give_away_money.add_account_to_l 1 st(:NEW.account_id):
END; ~~~~ ~-
Наконец, последняя процедура пакета выполняет то, ради чего, собственно, мы все это затеяли: добавляет в таблицу записи об открытии новых счетов по 100 долларов:
PROCEDURE give_it_away_now
IS---
Кладем 100 долларов на каждый новый счет v_element PLS_INTEGER;
BEGIN'~
v element := v account table.FIRST:
LOOP一 ~
EXIT WHEN v_element IS NULL;
INSERT INTO account_tran5action (transactionjd, account_1d. transact!on_type, transaction_amount, comments)
VALUESCaccount_transaction_seq.nextval,'
v account table(v elenent), 'DEP'."—
100. 'Free Money!'); v_element ;= v account_table.NEXT(v_element): end"loop; ~~
END g1ve_it_away_now;
Эта процедура вызывается из триггера:
CREATE OR REPLACE TRIGGER after_insert_statenient
AFTER INSERT ON account
BEGIN■
♦- Наконец кладем деньги на счет! gi ve_away_money. gi ve_it_away_now;
END; ~~ 一 ~-
И вот теперь, после удаления исходного триггера Кэй м замены его пакетом с новыми триггерами, тестирование прошло успешно:
SQL> INSERT INTO account Caccount_id, account_owner) 2 VALUESCl.'Test1):"~
SQL> SELECT * FROM account_transactlon;
2 WHERE account id - 1;/
TRANSACTION_ID ACCOUNT_ID TRA TRANSACTION_AMOUNT COMMENTS
1 DEP1 DEP100 Free Money!
1 DEP
1 DEP
SQL> BEGIN
FOR counter IN 20..25 LOOP
INSERT INTO account
(a ccou nt_id, accountowne r)
VALUESCcounter.'Test'):
END LOOP;
END;
SOL> SELECT *
FROM account_transact1on
WHERE accounted BETWEEN 20 AND 25
TRANSACTION」。ACCOUNTJD TRA TRAI4SACTION_AMOUNT CO刪ENTS
2
3
20 DEP
100 Free Money!
100 Free Money!
21
DEP
4
22
DEP
100
Free
Money!
5
23
DEP
100
Free
Money!
6
24
DEP
100
Free
Money!
7
25
DEP
100
Free
Money!
6 rows seiected.
Таким образом, благодаря слаженному взаимодействию триггеров уровня инструкции, триггеров уровня строки и пакетных процедур PL/SQL Кэй, наконец, свободна и может провести остаток уик-энда, не беспокоясь о проблеме, связанной с изменением таблиц:
Частичное решение проблемы изменяемых таблиц
По мере усовершенствования Oracle проблема изменяемых таблиц постепенно решается. Исходный трихтер Кэй будет прекрасно работать уже в Огас1е8.1.5, поскольку проверка ограничения внешнего ключа теперь выполняется после запуска триггера уровня строки. Правда, при наличии внешнего ключа, определенного с предложением ON DELETE CASCADE, ошибка изменяемых таблиц по-прежнему проявляется, но это единственный случай.
Рассмотрим простой пример, в котором используются две таблицы - главная и подчиненная. Они связаны ограничением внешнего ключа, заданным предложением DN DELETE CASCADE:
CREATE TABLE master_table
Cmasterjd NUMBER NOT NULL PRIMARY KEY):
CREATE TABLE deta1l_table
(detalljd NUMBER NOT NULL,
masterJd NUMBER NDT NULL,.
CONSTRAINT detail_to_enip
FOREIGN KEY (master ")
REFERENCES master_table (masterjd)
ON DELETE CASCADE);
CREATE OR REPLACE TRIGGER after_delete_master
AFTER DELETE ON master_table
FOR EACH ROW
DECIARE
CURSOR cu”_8unt detail IS
SELECT COUNTC*)"
FROM detail^table;
v_detail_count NUMBER;
BEGIN ~
OPEN curs_count_detail,
FETCH curs_count_deta11 INTO v^detail_count:
CLOSE curs count detail:
END;——
Удаление строки из таблицы master_tab4e лриведетк исчезновению связанных с ней строк в таблице detai l_tablе. Oracle не может гарантировать достоверность результата, возвращаемого функцией COUNT, поскольку неизвестно, сколько строк после этого останется. Нам же не остается ничего другого, как объявить о возможном изменении таблицы:
SQL> DELETE n«ster_table;
ERROR at line 1:
ORA-04091; table SCOTT.DETAIL_TABLE Is mutating, trigger/functlon may not see it
DRA^06512: at "SCOTT.AFTER_DELETE_MASTER". line 3 .
ORA-06512: at "SCOn.AHER_DELETE_MASTER". Une 7
ORA-04088: error during execution of trigger 'SCOTT.AFTER_DELETE_MASTER'
Но уже в Oracle8.1 подобной ситуации можно избежать, если триггер выполняется в режиме автономной транзакции:
CREATE OR REPLACE TRIGGER after_delete_master
AFTER DELETE ON master_table
FOR EACH ROW-*
DECLARE
--автономная трзнзакция
PRAGMA AUTONOMOUSJRANSACTION:
CURSOR curs_count_detail IS
SELECT COUNTC*)
FROM detail^table:
v_detail_count NUMBER;
BEGIN~
OPEN curs_count_deta1l:
FETCH curs_count_detan INTO v_detail_count;
DBMS_ OUTPUT.PUT丄【NE('detail count = '|| v_detail_count):
CLOSE curs_count_detai1:
end;
Теперь при запуске триггера ошибок не будет, поскольку он больше не является частью транзакции, при выполнении которой удаляются строки главной таблицы:
SQL> DELETE mastertable:
detail count • 1
row deleted.
Как видите, проблем с изменяемыми таблицами становится все меньше, а еле-, довательно, становятся все менее заметными связанные с ними неудобства.
Триггеры уровня DDL
В Огас1е8 была введена долгожданная функция запуска триггеров при выполнении инструкций языка определения данных (Data Definition Lanquage, DDL). Если говорить проще, инструкцией DDL является любая SQL-инструкция, создающая или модифицирующая объект базы данных, такой как таблица или индекс. Примерами DDL-инструкций могут служить инструкции CREATE TABLE/ALTER TABLE, CREATE INDEX, CREATE TRIGGER/DROP TRIGGER, каждая из которых создает, изменяет или удаляет объект базы данных.
Синтаксис триггеров DDL-инструкций тот же, что и у триггеров DML. Они различаются лишь перечнем событий и тем, что триггеры DDL не связываются с конкретными таблицами.
Создание триггера
Инструкция создания (или замены) триггера DDL имеет следующий синтаксис:
CREATE [OR REPLACE] TRIGGER имя^григгера
{BEFORE I AFTER) {co6biTHeJ)DL} ON {DATABASE | SCHEMA}
DECLARE一
Объявления переменных'
BEGIN
... код триггере...
END:
Элементы триггера описаны в приведенной ггиже таблице.
Строки Описание
Создание триггера с указанным именем. Если триггер существует, но строка OR
REPLACE не задана, об этом вас уведомит уже знакомое сообщение об ошибке Oracle 4081
Данная строка очень информативна. Она определяет, должен триггер
запускаться до или после наступления заданного события DDL, а также должен он запускаться для всех операций в базе данньк или только в заданной осеме
Э-7Образец тела триггера
Приведем пример простого триггера с функциями информатора, объявляющего о создании всех объектов:
/* файл в web: uninformed_t°wrl_cr'ier-scl^
SQL> CREATE OR REPLACE TRIGGER town_crier
AFTER CREATE ON SCHEMA
BEGIN
08MS_0UTPUT.PUT_LI NE('I believe you have created something!'):
END: -~
/
Trigger created.
SQL> SET SERVEROUTPUT ON-'
SQL> CREATE TABLE d」db〕e
2 (coll NUMBER)〕
Table created.
SQL> CREATE INDEX anjndex ON a_table(coll);
Index created.
SQL> CREATE FUNCTION a_funct1on RETURN BOOLEAN AS
BEGIN-
RETURNCTRUE);
END;
/
Function created.
SQL> /*-- очистка буфера DBMS_OUTPUT */
5QL> BEGIN NULL; END: -
/
I believe you have created something!
I believe you have created something!
I believe you have created something!
PL/SQL procedure successfully completed.
ВНИМАНИЕ
Текст, возвращаемый встроенным пакетом DBMS_OUTPUT, не будет выводиться триггером DDL до тех пор, пока вы не выполните блок PL/SQL, даже если этот блок не реализует никаких действий.
Со временем пользователи начнут просто игнорировать информатор, поскольку, сообщая о создании чего-то, он не уточняет, что же именно создается. Но на самом деле триггеры DDL способны предоставлять более полную информацию, ведь триггер можно переписать и таким образом:
* Фейл 8 web; 1nformed_town_crier.sql */
SQL> CREATE OR REPLACE TRIGGER town_crier
AFTER CREATE ON SCHEMA ~
BEGIN
— используем атрибуты события для получения более полной информации
08MS_0UTPUT.PUT_LINE('I believe you have created a ' ||
~~ ORAJICT_OBJ_TYPE 11 ' called ' j|
ORA~DICT~OBJ~NAME);
END;—_ _.
/
Trigger created.
SQL> SET SERVEROUTPJT ON
SQL> CREATE TABLE a_table
(coll NUMBER);.
Table created.
SQL> CREATE INDEX anjndex ON a_table(col 1);
Index created.
SQL> CREATE FUNCTION a_function RETURN BOOLEAN AS
BEGIN-
RETURN(TRUE):
'4 END;
/.
Function created.
SQL* /*-- очистка буфера DBMS_DUTP1JT */■
SQL> BEGIN NULL; END; ~
/.
I believe you have created a TABLE called A_TABLE
I believe you have created a INDEX called 兩.INDEX
I believe you have created a FUNCTION called A_FUNCTION
PL/SQL procedure successfully completed.
Теперь к информатору станут, наконец, прислушиваться. В приведенных выше примерах задействованы два важнейших элемента, необходимых для создания триггеров DDL: события, с которыми они связываются, и атрибуты событий, которые в них доступны.
События триггеров
Список событий, которые можно связать с триггерами DDL (одним из таких событий является выдача определенной S QL-инструкции, например инструкции CREATE), приведен в табл. 18.1. Любой триггер DDL может вызываться до или после наступления описанных здесь событий.
Таблица 18.1, События, связываемые с триггерами DDL
Событие
Описание
CREATE
Создание объектв базы данных с применением SQL-инсгрукции CREATE
ALTER
Изменение объекта базы данных с помощью SQL-инструкции ALTER
DROP
Удаление объекта базы данных с использованием SQL-инструкции DROP
ANALYZE
Анализ состояния объектв базы данных с помощью SQL-инструкции ANALYZE
ASSOCIATE STATISnCS
Связывание статистики с объектам базы данных
продолжение^
Таблица 18.1 (продолжение)
Событие
Описание
AUDIT
Включение средства аудита базы данных с помощью SQL-инструкции AUDIT
NOAUDIT
Выключение средств аудита базы данных с помощью
SQL-инструкции NOAUDIT
COMMENT
Создание комментария для объекта базы данных
DDL
Наступление любого из перечисленных выше событий
DISASSOCIATE STATISTICS
Удаление статистики объекта базы данных
GRANT
Назначение прав с использованием SQL-инструкции GRANT
RENAME
Переименование объекта базы данных с помощью SQL-инструкции RENAME
REVOKE
Отмена прав с применением SQL-инструкции REVOKE
TRUNCATE
Очистка таблицы посредством SQL-инструкции TRUNCATE
Как и триггеры DML, триггеры DDL запускаются, когда в заданной базе данных или схеме происходят события, с которыми они связаны. Никаких ограничений на количество типов триггеров, которые могут быть определены в базе данных или схеме, не существует.
Параметры триггеров
Oracle предоставляет набор функций (определены в пакете DBMS_STANDARD), возвращающих информацию о причине запуска триггера DDL и другие связанные с ним параметры (например, имя удаляемой таблицы). Перечень этих функций приведен в табл. 18.2, а примеры их использования — в следующих разделах.
Таблица 18.2. Список функций пакета DBMS_STANDARD
Функция
Что возвра щает
ORA_SYS EVENT
Тип события, вызвавшего запуск триггера DDL (например, CREATE, DROP или ALTER)
ORA_LOGIN_USER
Имя пользователя Oracle, для которого запущен триггер
ORA_INSTANCE_NUM
Номер экземпляра базы данных
ORA_DATABASE_NAME
Имя базы данных
ORA_CLIENT_IP_ADDRESS
IP-адрес клиента
ORA^DICT_OBJ_TYPE
Тип объекта базы данных, связанного с DDL-инсгрукцией^ вызвавшей запуск триггера (например, TABLE или INDEX)
ORA_DICT_OBJ_NAME
Имя объекта базы данных, связанного с DDL-инструкцией, которая вызвала запуск триггера
ORA_DIC T_OBJ_OWNER
Возвращает имя владельца объеета базы данных, связанного с DDL-инструкцией, которая вызвала запуск триггера
ORA_IS_CREATING_NESTED_TABLE Значение TRUE, если создается вложенная таблица, и FALSE в противном случае
функция
Что возвращает
ORA_DES_ENCRYPTED_PASSWORD
Зашифрованный пароль текущего пользователя. При шифровании используется алгоритм DES
ORA_IS_AL7ER_COLU М N
Значение TRUE, если изменяется столбец, заданный параметром COLUMN_NAME, или FALSE в противном случае
ORA_IS_DROP_COLUMN
Значение TRUE, если удаляется столбец, заданный параметром COLUMN一NAME, или FALSE в противном случае
ORA_DICT_OBJ_NAME_LIST
Количество обработанных инструкцией объектов. Кроме того, в параметре NAME_LIST возвращается полный список этих объектов, представляющий собой коллекцию типа DBMS_STANDARD.ORA_NAME„LIST_T
ORA__DICT_OaJ_OWNER_LIST
Количество обработанных инструкцией объектов. Кроме того, в параметре NAME_LIST возвращается полный список имен владельцев этих объектов, представляющий собой коллекцию типа DBMS_STANDARD.ORA_NAME_LIST_T
ORA_GRANTEE
Количество пользователей, получивших права. В 叩гументе USERJJST содержится полный список этих пользователей, представляющий собой коллекцию типа DBMS_S77WDARD.ORA_NAME_LIS7LT
ORAJ/VITH—GRAN 匸OPTION
Значение TRUE, если права предоставлены с правом предоставления привилегий другим пользователям, и FALSE в противном случае
ORA_PRMLEGE_LIST
Количество предоставленных или отмененных прав. В аргументе PRIVILEGE_LIST содержится полный список этих разрешений, представляющий о>бой коллекцию типа DBMS_STANDARD.ORA_NAME_LIST_T
ORA_REVOKEE
Возвращает количество пользователей, лишенных привилегий. В аргументе USER_LIST содержится полный список этих пользователей, представляющий собой коллекцию типа DBMS_STANDARD.ORA_NAME_LIST_T
Относительно этой таблицы хотелось бы добавить следующее.
О Тип данных ORA_NAME_LIST_T определен в пакете DBMS_STANDARD как
TYPE ora_name_list_t IS TABLE OF VARCHAR2C64):
Иными словами, это вложенная таблица строк, каждая из которых может содержать до 64 символов.
О События триггеров DDL и функции параметров также определены в пакете DBMS_STANDARD. Для каждой из функций этого пакета Oracle создает независимую функцию, добавляя к ее имени префикс 0RA_, для чего при создании базы данных выполняется сценарий $ORACLE_HQME/rdbms/dbmstrig,sqi. В отдельных версиях Oracle в этом сценарии имеются ошибки, из-за которых независимые функции невидимы или не выполняются. Приведем пример из сценария для Огас1е8.1.7, с тем чтобы показать вам ошибку в имени функции pri vi 1 ege_l 1 st; create public synonym ora_privilege_list for privilegalist
В OracleSi эта ошибка уже исправлена, но могут встретиться другие ошибки. В таком случае можно попросить администратора базы данных проверить сценарий и внести необходимые исправления.
О Представление словаря данных USER__SOURCE не обновляется до тех пор, пока не выполнятся триггеры BEFORE и AFTER. Иными словами, с помощью описанных функций невозможно создать систему управления версиями, основанную на триггерах базы данных.
Применение событий и параметров
Возможности триггеров DDL лучше всего продемонстрировать на примерах. Для начала рассмотрим триггер, который блокирует создание любых объектов базы данных:
CREATE OR REPLACE TRIGGER no_create
AFTER CREATE ON SCHEMA
BEGIN
RAISE_APPLICATION_ERROR 【
-20000.
'ERROR : Objects cannot be crested in the production database.'
END;
После его создания в базе данных нельзя больше создать ни одного объекта.
SQL> CREATE TABLE demo (coll NUMBER);
ERROR at line 1:
0RA-20000: Objects cannot be created in the production database.
Данное сообщение об ошибке не назовешь информативным. Из него лишь понятно, что в базе данных нельзя создать объект. Можно было бы вывести более информативное сообщение, указав, например, какой именно объект пытался создать пользователь:
/* Файл в web: no_create.sql */
CREATE OR REPLACE"TRIGGER no_create
AFTER DDL ON SCHEMA
BEGIN
IF ORA_SYSEVENT = 'CREATE' THEN.
RAISE_APPLICATIQN_ERROR(-20000.
'Cannot create the 1 || ORA_DICT_DBJ_TYPE ||
'named '|| ORA^HC匸。BJjJAME ||
'as requested by 7 | j ORAjlCTJBJ^OWNER);
END IF;-- ~
END:
При наличии такого триггера попытка создать таблицу в базе данных приведет к появлению более подробного сообщения об ошибке:
SQL> CREATE TABLE demo (coll NUMBER);
ERROR at line 1:
ORA-20000; Cannot create the TABLE named DEMO as requested by SCOTT
Можно было бы даже реализовать эту логику в виде триггера BEFORE и воспользоваться событием ORA_SYSEVENT:
CREATE OR REPLACE TRIGGER no create
BEFORE DDL ON SCHEMA -
BEGIN
IF ORA_SYSEVENT ■ 'CREATE' THEN
RArSE_ApPLIcATI0N_ERR0Rt-20000''
'Cannot create the 1 11 0RA_DICT_0BJ_TYPE 11
'named '|| 口RA 二D】CT二OBJ二NAME ||
■ as requested by 1 || ORA_DICT_OBJ_OWNERj:
ELSIF ORA^SYSEVENT = 'DROP' ---
THEN
―Логика операций DROP
ENO IF;.
END:
Как определить, какой столбец изменен
С помощью функции ORA_IS_ALTER_COLUMN можно определить, какой столбец был изменен в результате применения инструкции ALTER TABLE:
/* Файл в web: preservj叩p/ols.sql */
CREATE OR REPLACE TRIGGER preserve_app_cols
AFTER ALTER ON SCHEMA~ -
DECLARE
курсор для чтения имен столбцов таблицы
CURSOR curs_get_co'l umns (cp_owner VARCHAR2, cp_tabl e VARCHAR2)
SELECT column_name
FROM all^tab^colunins
WHERE owner = cp_owner AND table_name = cp_table;
BEGIN~~ 一
--если это та таблица, которая была изменена...
IF ora_dict_obj_type ₽ 'TABLE'
THEN~
-для каждого столбца таблицы...
FOR v_column_rec IN curs_get_columns ( ora_dict_obj_owner.■■
ora_dict_obj_name
LOOP
-если текущий столбец был изменен, собщин об этан
IF ORA_IS_ALTER_COLUMN (v_colLffnn_rec.colurnn_name)
THEN-~--
-если таблица/столбец является базовым...
IF mycheck.1s_applicat1on_column ( ora_dict_obj_owner, ora_dict_obj_name, v_column_rec.column name
THEN
RAISE_APPLICATIONJRROR (
-20001. 一
'Cannot alter core application attributes'■
);
END IF; -- таблица/столбец является базовым
END IF: -- изменен текущий столбец
END LOOP; -- каждый столбец в таблице
END IF; -- табпица была изменена
END;
Теперь все попытки изменить базовые столбцы приложения будут пресекаться системой.
Результаты проверки в триггере наличия запрета на изменение содержимого таблицы не влияют на возможность добавления новых столбцов. Когда триггер DDL вызывается для только что добавленного столбца, информации об этом столбце еще нет в словаре данных.
Чтобы получить возможность выявлять попытки удаления определенных столбцов, следует добавить следующий код:
IF ORA IS DROP COLUMN ('C0L2')
THEN — 一 - выполнить код!
ELSE
— выполнить другой код!
END IF;
ПРИМЕЧАНИЕ
Функциям ORA_IS_DROP_COLUМN и ORA_IS_ALTER_COLUMN ничего не известно о таблице, в состав которой входит определенный столбец; в качестве параметра им передается только имя столбца.
Параметры, возвращаемые функциями
Некоторые функции возвращают параметры двух типов; список элементов и количество элементов. Например, функция ORA_GRANTEE возвращает список и количество пользователей, которым предоставлены определенные права, а функция ORA_PRIVILEGE_LIST — список и количество предоставленных прав. Обычно обе они применяются в триггерах AFTER GRANT. Примером использования этих функций может служить файл whaLprivs.sql, представленный на узле издательства O'Reilly. Вот часть кода данного примера:
/* Файл в web: what_privs.sql */
CREATE OR REPLACE TRIGGER what_privs
AFTER GRANT ON SCHEMA
DECLARE
v_grant_type v_num_grantees v_granteeJ1st v_num_privs v_priv_HstBEGIN ~v_grant_type v_num_grantees v_granteeJ1st v_num_privs v_priv_HstBEGIN ~VARCHAR2 (30);BINARY」NTEGER; ora name_l1st_t 0INARY_INTEGER; ora_name_list_tVARCHAR2 (30);BINARY」NTEGER; ora name_l1st_t 0INARY_INTEGER; ora_name_list_t
v_grant_type v_num_grantees v_granteeJ1st v_num_privs v_priv_Hst
BEGIN ~
v_grant_type v_num_grantees v_granteeJ1st v_num_privs v_priv_Hst
BEGIN ~
VARCHAR2 (30);
BINARY」NTEGER; ora name_l1st_t 0INARY_INTEGER; ora_name_list_t
VARCHAR2 (30);
BINARY」NTEGER; ora name_l1st_t 0INARY_INTEGER; ora_name_list_t
v_grant_type := ora_dict_obj_type;
v_num_grantees := orajjrantee (v_grantee_list):
v_num_privs := ora_privilege_list (v_pr1v_list):
IF v_grant_type = 'ROLE PRIVILEGE1
THEN
DBMS_OUTPUT.put_linE (
CHR (9) 11 'The following roles/privileges were granted'
FOR counter IN 1 .. v_num_privs LOOP— ~
DBMS_DUTPUT.putJine С
CHR (9) ]| CHR (9) И 'Privilege ' 11 v_priv_Hst (counter) );
END LOOP;
Такой триггер прекрасно подходит для получения подробной информации о правах пользователей и объектах базы данных, указанных в инструкциях GRANT. Эту информацию можно также сохранить в базе данных, добавив журнал с подробными сведениями об изменениях,
SQL> GRAKT DBA ТО book WITH ADMIN OPTION:
Grant succeeded.
SQL> EXEC DBMS_OUTPUT.PUT_LINEC'Flush buffer');
The following roles/prlvlleges were granted
Privilege UNLIMITED TABLESPACE
Privilege DBA
Grant Recipient BOOK
Flush buffer
SQL> GRANT SELECT ON x TO system WITH GRANT OPTION:
Grant succeeded.
SQL> EXEC DBMS_OUTPUT.PUT_LINE('Flush buffer1);
The following object privileges were granted
Privilege SELECT
On X with grant option
Grant Recipient SYSTEM
Fl ush buffer
Удаление неудаляемого триггера
Мы только что продемонстрировали одну из возможных целей применения триггеров DDL, а именно блокировку выполнения DDL-инструкций, связанных с конкретным объектом или типом объектов. Но что если мы создадим триггер, препятствующий выполнению инструкции DROP, а затем попытаемся его удалить? Не получится ли так, что будет создан неудаляемый триггер? К счастью, как показывает этот пример, Oracle позаботилась о том, чтобы такого не случилось:
SQL> CREATE OR REPLACE TRIGGER undroppable
2 BEFORE DROP ON SCHEMA
3 BEGIN
4 RAISE APPLICATION_ERROR(-20000,'You cannot drop nel I an invlnclblel');
5 END; ~~
SQL> DROP TABLE employee;
ERRDR at line 1:
ORA-20000: You cannot drop me! I am invincible!
SQL> DROP TRIGGER undroppable;
Trigger dropped.
Триггеры событий базы данных
Триггеры событий базы данных запускаются, когда происходит событие уровня базы данных. Перечислим их с указанием условия запуска:
О STARTUP 一 при открытии базы данных;
О SHUTDOWN — при нормальном закрытии базы данных;
О SERVERERROR 一 при возникновении ошибки Oracle;
О LOGON — при запуске сеанса Oracle;
О LOGOFF — при нормальном завершении сеанса Oracle.
Эти триггеры предоставляют прекрасные возможности для автоматизации процесса администрирования базы данных и управления ее функционированием.
Создание триггера события базы данных
Синтаксис объявления этих триггеров очень похож на синтаксис триггеров DDL.
CREATE [OR REPLACE] TRIGGER иня^триггера
{BEFORE I AFTER) {событие_базыДанных} ON {DATABASE | SCHEMA)
DECLARE~
Объявления лерененных
BEGIN
... некоторый код ...
END:
He каждое событие может иметь оба триггера: BEFORE и AFTER. Объясняется это тем, что некоторые события просто невозможно связать с триггерами. Перечислим такие случаи.
О BEFORE STARTUP. Даже если такие триггеры можно было бы создавать, когда их следует запускать? Попытка создать триггер этого типа вызывает сообщение об ошибке:
ORA-30500: database open triggers and server error triggers cannot have BEFORE type
О AFTER SHUTDOWN. Опять же, когда должны запускаться такие триггеры? Попытка создать триггеры этого типа вызывает сообщение об ошибке;
ORA-30501: Instance shutdown triggers cannot have AFTER type
О BEFORE LOGON. Эти триггеры должны содержать удивительно чуткий код: «Кажется, кто-то собирается подключиться — сделаем что-нибудь!» Поскольку Oracle настроена очень реалистично, она не позволяет создавать такие триггеры и возвращает следующее сообщение:
ORA-30508: client logon triggers cannot have BEFORE type
О AFTER LOGOFF. В данном случае была бы уместна такая фраза: «Подождите, пожалуйста, вернитесь! Не отключайтесь!» Попытка создать триггер этого типа также вызывает сообщение об ошибке:
ORA-30509: client logoff triggers cannot have AFTER type
О BEFORE SERVERERROR. Такой триггеры был бы мечтой любого программиста. Только представьте себе:
CREATE OR REPLACE TRIGGER 8EF0RE_SERVERERR0R
BEFORE SERVERERROR ON DATABASE
BEGIN
diagnose_impend1ng_error; -- обнаруживаем будущую оиибку fix_error_condition:-- uc叩авлнем ее причину
cont1nue_asJf_noth1ng_happened; -- продолжаем, как будто ничего не случилось
END:~ ~ ~~
К сожалению, наши мечты будут развеяны таким сообщением об ошибке:
0RA-30500: database open triggers and server error triggers cannot have BEFORE type
Триггеры STARTUP
Триггеры типа STARTUP запускаются при загрузке базы данных. Это прекрасный момент для выполнения подготовительных работ, в частности для фиксации объектов в общем пуле (иначе они могут быть удалены в соответствии с алгоритмом «дольше всех не использовавшийся»).
ПРИМЕЧАНИЕ
Для того чтобы создать триггер события STARTUP, пользователь должен иметь привилегию ADMINISTER DATABASE TRIGGER.
Вот как создается триггер STARTUP:
CREATE OR REPLACE TRIGGER startup pinner
AFTER STARTUP ON DATABASE ~
BEGIN
pin_plsql_packages;
pln_application packages;
END;-一
Триггеры SHUTDOWN
Триггеры BEFORE SHUTDOWN запускаются перед закрытием базы данных. Они представляют собой прекрасное средство для сбора статистических данных о системе. Приведем пример создания триггера события SHUTDOWN:
CREATE OR REPLACE TRIGGER after shutdown
AFTER SHUTDOWN ON DATABASE -
BEGIN
gather systeni_stats;
END: --
ПРИМЕЧАНИЕ
Триггеры SHUTDOWN запускаются только в случае останова базы данных в режиме NORMAL или IMMEDIATE. Но они не выполняются, когда база данных останавливается в режиме ABORT, а также при аварийном завершении ее работы.
Триггеры LOGON
Триггеры AFTER LOGON запускаются в начале сеанса Oracle. Их удобно использовать для формирования контекста сеанса и выполнения других установочно-настроечных задач. Вот пример триггера события LOGON:
CREATE OR REPLACE TRIGGER afterjogon
AFTER LOGON ON SCHEMA
DECLARE
v_sql VARCHAR2(1DD) 'ALTER SESSION ENABLE RESUMABLE ' ||
~'TIMEOUT 10 NAME ' 11 " " 11
'OLAP Session' || "";'
BEGIN
EXECUTE IMMEDIATE v_sql;
DBMS_SESSION.SET_CONTEXT('OLAP Namespace'.
'Customer ID'.,
load_user_customer_-id):
END:“ 一 ~
Триггеры LOGOFF
Триггеры BEFORE LOGOFF запускаются при нормальном завершении сеанса, то есть при отсоединении пользователя от базы данных. Это удобное средство для сбора статистических сведений о параметрах сеансов:
CREATE OR REPLACE TRIGGER before_Logoff
BEFORE LOGOFF ON DATABASE ~
BEGIN
gather_sess1on_stats:
END; 一 -
Триггеры SERVERERROR
Триггеры AFTER SERVERERROR запускаются в момент, когда Oracle генерирует какую-либо ошибку. Исключение составляют следующие ошибки (для них триггеры не создаются):
О 0RA-00600 — внутренняя ошибка Oracle (Oracle internal error);
О 0RA-01034 — Oracle недоступна (Oracle not available);
О 0RA-01403 — данные не найдены (No data found);
О ORA-01422 — при выборке данных возвращено больше строк, чем указано в запросе (Exact fetch returns more than requested number of rows);
О ORA-01423 — при попытке выборки дополнительных строк произошла ошибка (Error encountered while checking for extra rows in an exact fetch);
О ORA-04030 — нехватка памяти процесса при попытке выделения N байт памяти (Out-of-process memory when trying to allocate N bytes).
Кроме того, триггеры AFTER SERVERERROR не запускаются, когда исключение инициируется внутри триггера (во избежание рекурсивного вылолнения триггера).
Oracle предоставляет две встроенные функции (как и остальные, они определены в пакете D8MS_STANDARD), возвращающие информацию о стеке ошибок, который формируется при возникновении исключения.
О ORA_SERVER_ERROR — возвращает номер ошибки Oracle в заданной позиции стека. Если в этой позиции ошибки нет, возвращает 0.
О ORA_IS_SERVERERROR — возвращает значение TRUE, если в стеке ошибок текущего исключения имеется ошибка с заданным номером.
Примеры триггеров события SERVERERROR
Рассмотрим несколько примеров использования функций SERVERERROR. Начнем с очень простого примера триггера, сообщающего о том, что произошла ошибка:
CREATE OR REPLACE TRIGGER error_echo
AFTER SERVERERRQR ON SCHEMA —
BEGIN
DBMS OUTPUT.PUT LINE ('You experienced an error');
ENO:~
После возникновения ошибки Oracle (и при условии, что параметр SERVEROUTPUT имеет значение 0N) на экран будет выведено такое сообщение:
SQL> SET SERVEROUTPUT ON
SQL> EXEC DBMS_OUTPUT.PLR_LINE[TO_NUHBER('A'));
You experienced an error
BEGIN DBMS_OUTPUT.PUT_LINE(TO_NUMBER()); ENO;
ERROR at Une 1:
ORA-06502: PL/SOL: numeric or value error: character to number conversion error
ORA-06512: at Une 1
Обратите внимание на тот факт, что сообщение об ошибке Oracle выводится после сообщения триггера. Благодаря этому информацию об ошибке можно получить еще до того, как Oracle предпримет какие-либо действия (см. следующий пример).
РИМЕЧАНИЕ
Триггеры SERVERERROR способны автоматически изолироваться в собственной автономной транзакции (об автономных транзакциях рассказывается в главе 13). Это означает, что можно, к примеру, записать информацию об ошибке в таблицу-журнал и сохранить ее с помощью инструкции COMMIT, не затрагивая транзакцию сеанса, в которой произошла ошибка.
Рассмотрим пример, в котором триггер error_echo обеспечивает гарантию того, что информация обо всех ошибках (кроме нескольких перечисленньпс выше) будет автоматически записываться в журнал. При этом не имеет значения, кем или чем была вызвана ошибка — пользователем, самим приложением или какой-то программой.
/* Файл в web: error log.sql */
CREATE OR REPLACE TRIGGER errorjog
AFTER SERVERERRQR-
ON SCHEMA
DECLARE
v__errnum NUMBER; v_now DATE :- jcoLinter NUMBER :v__errnum NUMBER; v_now DATE :- jcoLinter NUMBER :--номер ошибки Oracle SYSDATE; -- текущее время
v__errnum NUMBER; v_now DATE :- jcoLinter NUMBER :
v__errnum NUMBER; v_now DATE :- jcoLinter NUMBER :
-1:-- счетчик стека
BEGIN
--для каждой ашибки в стеке ...
LOOP
—считывание из стека номера ашибки v_errnum :- ORA_SERVER_ERROR(v_caunter);
--если номер ошибки равен 0. выполнение кода завершается EXIT WHEN v_errnum 匸 0;
--запись информации об ошибке в журнал; фиксация транзакции
--не требуется, поскольку данная транзакция автономна INSERT INTO error_log(usernaine, errar_nufnber.
sequence.
timestamp)
VALUES(USER,■
v_errnum, v_counter. v_now);
--увеличение значения счетчика и повторение операции v_counter v_counter + 1:
END LOOP; -- дпя каждой ошибки в стеке
END;
Помните, что все новые строки таблицы error_log сохраняются в момент выполнения оператора END, поскольку триггер запускается как автономная транзакция. Действие самого триггера демонстрируется в следующем примере:
SQL> EXEC DBMS_OUTPUT.PLn-_LINE(TO_NUMBER('А'));
ERROR at line 1:
0RA-O65O2: PL/SQL: numeric or value error: character to number conversion error
SQL> SELECT * FROM error_log;
USERNAMEERRORJ4UMBERSEQUENCETIMESTAMP
BOOK6502104-JAN-02
BOOK6512204-JAN-02
Почему же в таблице содержатся записи о двух ошибках, если Oracle возвратила только одну из них? Реальный стек ошибок Oracle содержит описание ошибок ORA-06502 и ORA-06512, так что они обе были запротоколированы в порядке их расположения в стеке.
Если вам нужно быстро определить, имеется ли в стеке информация об ошибке с определенным номером, воспользуйтесь функцией ORA_I S_SERVЕRERROR. Эта
функция особенно пригодится для мониторинга конкретных ошибок, наподобие пользовательских исключений, требующих дополнительной обработки. Вот пример кода, в котором применена данная функция:
一 нестандартная обработка пользовательских ошибок
с номерами от 20000 до 20010, генерируемых■
с помощью вызоаов RAISE_APPLICATION_ERROR
FOR errnurn IN 20000 .. 20010
LOOP
IF ORA_IS_SERVERERROR (errnum)
THEN
log user defined error Cerrnum):
END IF? "~
ENO LOOP:
ПРИМЕЧАНИЕ
Номера всех ошибок Oracle представлены отрицательными значениями, за исключением 1 (пользовательское исключение) и 100 (синоним ошибки -1403, NO_DATA_FOUND). Однако в вызове функции ORA_IS_SERVERERROR. должен задаваться положительный номер ошибки, как в приведенном выше примере.
Централизованный обработчик ошибок
Хотя для каждой схемы базы данных можно определить отдельный триггер SERVERERROR, мы рекомендуем создать один централизованный триггер и сопутствующий пакет PL/SQL. Такой прием дает вам ряд преимуществ.
О Централизованная обработка ошибок. В памяти Oracle хранится только один пакет и один триггер для обработки ошибок.
О Возможность использовать один журнал ошибок в течение всего сеанса. В журнале накапливается информация об ошибках, возникающих на протяжении всего сеанса. Благодаря этому существует возможность получать сведения о количестве повторений данной ошибки, времени и дате ее первого или последнего возникновения и т. п. Журнал может очищаться по мере необходимости. При желании его можно сохранить в таблице базы данных.
О Отбор информации из журнала ошибок. Из журнала ошибок можно извлекать информацию об ошибках с определенным номером и/нли ошибках, происшедших в указанном временном диапазоне.
Готовый централизованный пакет для обработки ошибок содержится в файле errorjog.sql, представленном на узле издательства O'Reilly. Имея подобный пакет, можно создать следующий триггер обработки события SERVERERROR:
CREATE OR REPLACE TRIGGER errorjog'
AFTER SERVERERROR ON DATABASE —
BEGIN
central error log.log error:
END: 一 一 ~
Ниже приводится несколько примеров' его использования. Сначала генерируем ошибку:
SQL> EXEC OBMS_OUTPUT.PUT_LINECTO_NUMBER('A'}):
ERROR at line 1:
ORA-06502: PL/SOL: numeric or value error: character to number conversion error
Теперь можно найти ошибку с заданным номером и сохранить информацию о ней в записи:
DECLARE
v_find_record central_error_log.v_find_record:
BEGIN -
central_error_log.f1nd_error{6502. v_rind_record):■
DBMS_OUTPUT.PUT_LINE('Total Found = ' || v_find_record.total_found);
D8MS_OUTPUT.PUT_LINE('Min Timestamp - ' || v_find_record.min_timestamp);
DBMS_OUTPUT.PUTJJNE('Max Timestamp = ' 11 v_find_record.max_t1mestamp):
END:
Результат выполнения этого кода таков:
Total Found = 1
Min Timestamp - 04-JAN-02
Max Timestamp = 04-JAN-02
Неработоспособные триггеры
Неработоспособный триггер может оказать отрицательное влияние на функционирование объектов базы данных. Что мы имеем в виду, вы узнаете из представленной ниже таблицы.
Событие
Что произойдет, если триггер неработоспособен
AFTER SERVERERROR
Для всех транзакций, в которых возникнут ошибки Oracle, будет выполняться откат с выводом сообщения о неработоспособном триггере
AFTER LOGON
Подключатьо) к Oracle смогут только пользователи с привилегией ADMINISTER DATABASE TRIGGER
BEFORE LOGOFF
Отсоединяясь от базы данных, пользователи будут получать сообщение о неработоспособном триггере
BEFORE SHUTDOWN
В журнал сообщений будет записано сообщение о неработоспособном триггере, но останов базы данных будет выполняться корректно
AFTER STARTUP
В журнал ссхзбщений базы данных будет записываться сообщение о неработоспособном триггере, но запуск базы данных будет выполняться корректно
Триггеры INSTEAD OF
Триггеры INSTEAD OF (замещающие триггеры) предназначены для выполнения операций вставки, обновления и удаления элементов представлений, но не таблиц. С их помощью можно сделать необновляемое представление обновляемым и изменить поведение обновляемого представления по умолчанию.
Создание триггера INSTEAD OF
Инструкция создания (или замены) триггера INSTEAD OF имеет следующий синтаксис:
CREATE [OR REPLACE] TRIGGER n^jptf「「epd
INSTEAD OF операция ON имя^прецставления
ON имя_пре/1ставления
FOR EACH ROW
BEGIN
... програмнныИ коп ...
END;
Более подробно о выполняемых ею действиях вы узнаете из таблицы.
Строки
Описание
1
Создается триггер с заданным именем. Если триггер существует, но предложение OR REPLACE не задано, Oracle сгенерирует ошибку ORA-4O81
2
Строка идентифицирует триггеры INSTEAD OF. Поскольку они запускаются не в ответ на определенные события, а выполняются вместо инструкции SQL, для них не нужно указывать ни ключевое слово AFTER или BEFORE, ни имя события. Правда, необходимо задать инструкцию — INSERT, UPDATE или DELETE, — которую должен выполнять триггер
3
Зга строка подобна строке ON триггеров DDL и событий базы данных, однако вместо ключевого слова DATABASE или SCHEMA в ней задается имя представления, с которым связан триггер
4-7
Вставляется стандартный код PL/SQL
Принцип действия триггеров INSTEAD OF проще всего объяснить на примере системы доставки пиццы (это моя любимая тема). Наша система учета доставки будет состоять из трех таблиц: одна для учета доставленной продукции, вторая - для хранения списка районов доставки, а третья — для учета объемов работы, выполненной курьерами.
/* Файл в web: pizza_tab1es.sql */
CREATE TABLE delivery
(deliveryjd NUMBER.
deli very_start DATE.
delivery_end DATE.■
area_id NUMBER, driverjd NUMBER):
CREATE TABLE area
(areajd NUMBER, area_desc VARCHAR2(30));
CREATE TABLE driver
(driverjd NUMBER. dr1ver_name VARCHAR2C30]):
Желая упростить пример, мы решили отказаться как от первичных, так и от внешних ключей.
Нам нужны три последовательности, обеспечивающие таблицы уникальными идентификаторами:
CREATE SEQUENCE del1very_1d_seq:
CREATE SEQUENCE areajdjeq;
CREATE SEQUENCE driver_id_seq:
Чтобы не усложнять структуру базы данных, объединяем всю информацию о доставках, районах и курьрах в одно представление:
CREATE OR REPLACE VIEW deliveryjnfo AS SELECT d.deliveryjd, d.delivery—Stdrt. d.del1very_end, a.area_desc. dr.driver_name
FROM deliveryd,
areaa.
driverdr
WHERE a.areajd = d.area_id ANO dr.driverjd ■ d.driyer_id:
Поскольку вся система запросов строится вокруг этого представления, почему бы нам заодно не выполнять с его помощью вставку, обновление и удаление данных? В этом деле нам помогут триггеры INSTEAD OF. Наша же задача — сообщить базе данных, как выполнять операции вставки, обновления и удаления для представления deliveryjnfo. Начнем с триггера INSTEAD OF INSERT.
Триггер INSTEAD OF INSERT
Триггер INSERT должен выполнить четыре основные функции:
Q предоставить гарантию того, что столбец delivery_end будет содержать значение NULL (на факт доставки указывает только обновление записи);
Q определить идентификатор курьера с учетом заданного имени, но если нужное имя в таблице отсутствует, триггер присвоит курьеру другой идентификатор и добавит строку в таблицу курьеров, записав в нее заданное имя и новый идентификатор;
Q определить идентификатор района на основе заданного названия, но если нужное название в таблице отсутствует, триггер присвоит району другой идентификатор и добавит строку в таблицу районов, записав в нее заданное название и новый идентификатор;
Q добавить строку в таблицу доставки.
Имейте в виду, что данный пример демонстрирует использование триггеров, а не эффективный способ построения бизнес-систем! Через некоторое время у нас наверняка скопятся повторяющиеся строки с идентификаторами. Однако это представление ускоряет работу, не требуя обязательного предварительного определения курьеров и районов. А ведь при доставке пиццы время — деньги!
/* Файл 8 web; pizza_triggers.sql */
CREATE OR REPLACE TRIGGER deli very_i nfo_1nsert INSTEAD OF INSERT ON delivery info
0ECLARE
курсор для получения идентификатора курьера исходя из его имени
CURSOR curs_get_driver_1d ( cp_drlver_name VARCHAR2 ) IS
SELECT driver_id ~-
FROM driver
WHERE driver_name = cp_driver_name;
v_driver_id NUMBER;
курсор для получения идентификатора района исходя из его названия
CURSOR curs_get_area_id ( cp_area_desc VARCHAR2 ) IS
SELECT areajd
FROM area
明ERE area_desc - cp_area_desc:
v_area_id NUMBER;
BEGIN
/*
IJ Значение столбца delivery_end должно быть равно NULL
IF :new.delivery_end IS NOT NULL THEN
RAISE_APPLICATION_ERRORC-20000.'Delivery end date value must be NULL when delivery created');
END IF;
/*
Ij Пытаемся получить идентификатор курьера исходя из его имени
Если имя не найдено, создаем ноэый идентификатор
на основе последовательности
OPEN curs_get_driver_id(UPPER(:new.driver_name)):
FETCH curs_get__driver_1d INTO v_driver_id;
IF curs__get_driver_1 dXNOTFOUND THEN
SELECT dri ver_1 d__seq. nextval
INTO v_driver_id
FROM DUAL; ~'
INSERT INTO driver(driver_1d,driver_name)
VALUESCv driver id.UPPER(;new.driver name)):
ENO IF; 一 --
CLOSE curs_get_driver_1d;
"Пытаемся получить идентификатор района исходя из его названия
II Если наззание в таблице отсутствует, создаем идентификатор
IJ на основе последовательности 1'
*/
OPEN curs_get_areaJd(UPPER(:new.area_desc));
FETCH curs_get_areajd INTO v_area_1d:
IF curs_get_area_idXNOTFOUND THEN ~
SELECT areajd_seq.nextval
INTO v_area_1d
FROM DUAL; ~
INSERT INTO areaCarea id.area desc)
VALUES(v_area_id.UPPERC:new.area_desc));
END IF:--
CLOSE curs_get_area_1d;
/*
]I Добавляем строку с информацией о доставленной продукции
*/
INSERT INTO deli very(delivery_id. delivery_start, delivery_end. areajd, driver_id)
VALUES(deliveryJd_seq.nextval.
NVL(rNEW^deTivery^start.SYSDATE).
NULL,一
v_area_id.
v_driver_1d);
END:
Триггер INSTEAD OF UPDATE
Теперь рассмотрим триггер UPDATE. Для того чтобы упростить код, мы будем обновлять только столбец dell very _end, причем только в том случае, если он содержит значение NULL (курьеры должны строго соблюдать время доставки):
CREATE OR REPLACE TRIGGER delivery_info_update
INSTEAD DF UPDATE ON delivery_info
DECLARE-
курсор для получения строки доставки
CURSOR curs_get_de!1very ( cp_del1very_1d NUMBER ) IS
SELECT delivery_end
FROM delivery
WHERE delivery_1d - cp_deHvery_id
FDR UPDATE OF delivery_end;
v_de!1very_end DATE:
BEGIN
OPEN curs_get_delivery(:NEW.delivery_id);'
FETCH curs_get_delivery INTO v_del1very_end:
IF v_delivery_end IS NDT NULL THEN
RaIse_APPLICATION_ERROR(-20000.'The delivery end date hss already been set');.
ELSE 一一
UPDATE delivery
SET delivery_end = :new.delivery_end
WHERE CURRENT OF curs_get_del1very;
END IF;- ~
CLOSE curs_get_delivery:
■END:
Триггер INSTEAD OF DELETE
В рассматриваемом примере триггер DELETE имеет самую простую структуру. Его основная задача — следить за тем, чтобы никто не удалил заполненную строку, а затем самому удалить указанную строку доставки. Строки в таблицах курьеров и районов остаются неизменными.
CREATE OR REPLACE TRIGGER delivery—infO—delete
INSTEAD OF DELETE
ON delivery_info
BEGIN ~
IF :new.delivery_end IS NOT NULL THEN
RAISE_APPLICAnON_ERROR(-20000,'Completed deliveries cannot be deleted'):
END IF;~-
DELETE delivery
WHERE deliveryjid = :new.delivery_1d:
END:—-
Заполнение таблиц
Теперь мы можем заполнить все три таблицы, воспользовавшись только инструкцией INSERT, которая содержит полную информацию о доставке:
SQL> INSERT INTO del 1 veryj nfo(del 1 veryj d,
del1very_start,
delivery_end,
area_desc,
driverjiame)
VALUES-
(NULL, NULL, NULL, 'LOCAL COLLEGE', 'BIG TED'):
1 row created.
SQL> SELECT * FROM delivery;
DELIVERY_ID DELIVERY, DELIVERY一 AREA_ID DRIVERJD
1 13-JAN-0211
SQL> SELECT * FROM area;
AREA」D AREA_DESC
1 LOCAL COLLEGE
SQL> SELECT * FROM driver;
DRIVER_ID DRIVER_NAME
1
BIG TED
Триггеры AFTER SUSPEND
В Огас1е9: был введен новый тип триггеров, запускаемых в ответ на приостановление выполнения инструкции. Одной из причин такого приостановления может быть нехватка памяти, например, вследствие превышения квоты выделенного процессу табличного пространства. Триггер AFTER SUSPEND может использоваться для урегулирования ситуации, в результате чего выполнение инструкции будет возобновлено. Это просто подарок для разработчиков, которым надоели проблемы, связанные с памятью, и для администраторов баз данных, которым приходится их решать.
В качестве примера рассмотрим проблему, с которой столкнулся Бэч Онли - лучший разработчик компании Totally Controlled Systems. Он отвечал за сопрово- вдение сотен программ, работающих на протяжении ночи и выполняющих длинные транзакции, объединяющих информацию и распределяющих ее между отдельными приложениями. Как минимум дважды в неделю в предрассветные часы его будил пейджер, из-за того что в одной из программ возникала следующая ошибка Oracle:
ERROR at line 1:
ORA-01536: space quota exceeded for tablespace 'USERS'
И тогда Беч должен был звонить главному администратору базы данных Дону Планахеду и просить его увеличить квоту табличного пространства. Обычно Дон спрашивал: «Сколько тебе нужно?», на что Бэч бормотал:《Не знаю, объем данных каждый раз иной». И оба они оставались недовольны друг другом.
Предпосылки создания триггера
Наилучшим выходом из сложившейся ситуации, конечно же, стало использование триггера AFTER SUSPEND. Вот как все было реализовано.
Бэч обнаружил в коде строку, при выполнении которой чаще всего происходила ошибка. Это была инструкция INSERT е конце программы, на выполнение которой уходило много времени:.
INSERT*INTO monthly_summary (
acct_no, trx_count. totaljn, total_out)
VALUES ?~~
v acct, v trx count, v total in, v total out);
А самое неприятное было то, что значения, вычислявшиеся часами, могли быть утеряны в случае неудачного выполнения единственной инструкции INSERT. Бэч, естественно, предложил приостанавливать работу программы на то время, пока он будет просить Дона о выделении дополнительного табличного пространства. Оказалось, что это нетрудно сделать с помощью простой инструкции ALTER SESSION: ALTER SESSION ENABLE RESUMABLE TIMEOUT 3600 NAME 'Monthly Suirmary':
Как только в сеансе Oracle возникнет ошибка из-за нехватки памяти, выполнение сеанса должно приостановиться (с потенциальным возобновлением) на 3600 с, то есть на 1 ч. Этого вполне достаточно, чтобы система мониторинга передала сообщение на пейджер Бэчу, тот позвонил Дону, а Дон выделил дополнительную
память. Решение, конечно, далеко от совершенства, но по крайней мере вычисленные данные больше не будут теряться.
Еще одна проблема заключалась в том, что ошибка возникала ночью, когда оба сотрудника не могли быстро принимать решения из-за усталости или просто по причине взаимного непонимания. Однако оказалось, что от такого рода объяснений легко уйти, воспользовавшись еще одним элементом системы приостанов- ления/возобновления — представлением DBA_RESUMABLE. Оно возвращает информацию обо всех сеансах, в которых выполнялась приведенная выше инструкция ALTER SESSION.
ПРИМЕЧАНИЕ
Для того чтобы пользователь мог задать опцию RESUMABLE, он должен иметь системную привилегию с таким же именем.
Теперь во время приостановления программы Взчу достаточно позвонить Дону и обратиться к нему с просьбой проверить представление DBA_RESUMABLE. Дон выполняет запрос для этого представления, и ему сразу становится ясно, в чем дело:
SOL> run
SELECT sess1on_1d,
name,
status,
error_number
FROM dba^resumable'
SESSIONJD NAMESTATUSERROR_NUMBER'
8Monthly SummarySUSPENDED1536
row selected.
Данные, полученные в результате выполнения этого запроса, говорили о том, что сеанс 8 был присютановлен из-за ошибки ORA-01536: space quota exceeded for tablespace 'имя табличного пространства'. По своему опыту Дон знал, о какой схеме и о каком табличном пространстве идет речь, поэтому он сразу же оценивал ситуацию и сообщал Бэчу об объеме выделяемой памяти. Выполнение приостановленной инструкции в программе Бэча немедленно возобновлялось, а Дон и Бэч с чувством исполненного долга отправлялись спать. .
Пример реализации
В конце концов Дону и Бэчу надоели постоянные ночные разговоры об одном и том же, и Дон решил автоматизировать процесс выделения дополнительной памяти с помощью трштера AFTER SUSPEND. Вот какой триггер он написал и установил, подключившись к базе данных с учетной записью администратора:
/* Файл з web: smart_space_quota.sq1 */
CREATE OR REPLACE TRIGGER after_suspend
AFTER SUSPEND-
ON DATABASE
DECLARE
--курсор для получения имени пользователя текущего сеанса CURSOR curs_get_username IS ■
SELECT username
FROM vSsesslon
WHERE audsid - SYS_CONTEXT('USERENV'.'SESSIONID'): v_userndme VARCHAR2?30);
--курсор для 0npe口еленхя квоты для CURSOR curs_get_ts_quota ( cp_tbspc cp_user--курсор для 0npe口еленхя квоты для CURSOR curs_get_ts_quota ( cp_tbspc cp_userпользователя и табличного пространстваVARCHAR2,VARCHAR2 ) ISпользователя и табличного пространстваVARCHAR2,VARCHAR2 ) ISSELECT max_bytesFROM dba_ts_quotasWHERE tab!espace_name = cp_tbspcANO username = cp_user;SELECT max_bytesFROM dba_ts_quotasWHERE tab!espace_name = cp_tbspcANO username = cp_user;v_old_quota v_new_quotav_old_quota v_new_quotaNUMBER;NUMBER:NUMBER;NUMBER:
--курсор для 0npe口еленхя квоты для CURSOR curs_get_ts_quota ( cp_tbspc cp_user
--курсор для 0npe口еленхя квоты для CURSOR curs_get_ts_quota ( cp_tbspc cp_user
пользователя и табличного пространства
VARCHAR2,
VARCHAR2 ) IS
пользователя и табличного пространства
VARCHAR2,
VARCHAR2 ) IS
SELECT max_bytes
FROM dba_ts_quotas
WHERE tab!espace_name = cp_tbspc
ANO username = cp_user;
SELECT max_bytes
FROM dba_ts_quotas
WHERE tab!espace_name = cp_tbspc
ANO username = cp_user;
v_old_quota v_new_quota
v_old_quota v_new_quota
NUMBER;
NUMBER:
NUMBER;
NUMBER:
--содержит I v_error_type--содержит I v_error_typeинформацию из таблицы SPACE_ERRDR_INFOинформацию из таблицы SPACE_ERRDR_INFOv__object_type v_object_owner v_tbspc_name v_object_name v_subobject_namev__object_type v_object_owner v_tbspc_name v_object_name v_subobject_nameVARCHAR2(30)VARCHAR2(30)VARCHAR2C30)VARCHAR2(30)VARCHAR2(30)VARCHAR2(30)VARCHAR2(30)VARCHAR2(30)VARCHAR2C30)VARCHAR2(30)VARCHAR2(30)VARCHAR2(30)
--содержит I v_error_type
--содержит I v_error_type
информацию из таблицы SPACE_ERRDR_INFO
информацию из таблицы SPACE_ERRDR_INFO
v__object_type v_object_owner v_tbspc_name v_object_name v_subobject_name
v__object_type v_object_owner v_tbspc_name v_object_name v_subobject_name
VARCHAR2(30)
VARCHAR2(30)
VARCHAR2C30)
VARCHAR2(30)
VARCHAR2(30)
VARCHAR2(30)
VARCHAR2(30)
VARCHAR2(30)
VARCHAR2C30)
VARCHAR2(30)
VARCHAR2(30)
VARCHAR2(30)
--код SQL 口ля выделения дополнительных ресурсов v_sqlVARCHAR2(1000);
BEGIN
--если ошибка связана с пространством...
IF ORA_SPACE_ERROR_INFO С error_typs => v_error_type, object—type => v_object_type. object_owner => v_object_owner. table_space_name 力 v_tbspc_name. object_nanie v_object_name. sub_object_name => v_subobject_name ) THEN
если ошибка обусловлена превышением касты табличного пространства...
IF v_error_type = 'SPACE QUOTA EXCEEDED' AND v_object_type ■ 'TABLE SPACE' THEN
--получаем имя пользователя
OPEN curs_get_username;
FETCH curs_get_username INTO v_username:
CLOSE curs_get_username:
--определяем текущую квоту
OPEN curs_get_ts_quota(v_object_name. v_us ername);
FETCH curs_get_ts_quotd INTO v_ald_quota;
CLOSE curs_get_ts_quota;
-Формируем инструкцию ALTER USER и оправляем ее
заданию fixer, поскольку, если попытаться выполнить ее прямо отсюда. произойдет ошибка
-ORA-30511: invalid DDL operation 1п system triggers
v_new_quota :- v_old_quota + 40960;
v_sql :- 'ALTER USER ' || v_username || ' ' || 'QUOTA '11 v_new_quota 11 j|
'ON ' j j v_object_name:
fixer.fix_th1s(v_sql);
END IF; -- превышена квота табличного 叩острахства
END IF:ошибка, связанная с пространством
END:
Созданный триггер запускается в случае приостановления выполнения инструкции и пытается изменить ситуацию. (Обратите внимание, что в данном примере обрабатывается единственная ошибка, которая возможна при недостатке табличного пространства.)
НЕДОПУСТИМЫЕ ОПЕРАЦИИ DDL В СИСТЕМНЫХ ТРИГГЕРАХ
Системным триггерам AFTER SUSPEND не разрешается выполнять определенные DDL-инструкции, в том числе ALTER USER и ALTER TABLESPACE. Поэтому они не имеют возможности непосредственно исправить выявленную ошибку. Единственное, что им остается, — это сгенерировать сообщение об ошибке ORA-30511: Invalid DDL operation in system triggers. Но при необходимости вы можете обойти это ограничения, выполнив следующие действия.
В триггере AFTER SUSPEND составьте SQL-инструкцию, способную устранить проблему, и запишите ее в таблицу.
Создайте пакет PL/SQL для считывания указанной SQL-инструкции из таблицы и последующего ее выполнения.
Определите этот пакет в качестве задания, выполняемого с помощью пакета DBMS_JOB, предположим, через каждую минуту.
Теперь когда при работе приложения возникает ошибка из-за недостатка табличной памяти, запускается триггер AFTER SUSPEND уровня базы данных, который генерирует SQL-инструкцию, увеличивающую размеры памяти, и с помощью пакета fixer помещает ее в специальную таблицу. Выполняемое в фоновом режиме задание fixer считывает эту SQL-инструкцию и запускает ее. Таким образом, проблема недостаточной квоты решается без единого телефонного звонка.
1РИМЕЧАНИЕ—
Полный исходный код триггера after_suspend и пакета fixer имеется в файле fixer.sql на web-узле издательства O4eilly.
Создание триггера
Синтаксис триггера AFTER SUSPEND тот же, что и у триггеров событий базы данных. В объявлении этого триггера определяется событие (SUSPEND), время (AFTER) и область его действия (DATABASE или SCHEMA)
CREATE [OR REPLACE] TRIGGER иня_триггера
AFTER SUSPEND ON {DATABASE | SCHEMA]
BEGIN
…код
END;
Функция ORA_SPACE_ERROR_INFO
Информацию о причине приостановления выполнения инструкции можно получить с помощью функции ORA_SPACE_ERROR_INFO, как показано в приведенных выше примерах. Рассмотрим синтаксис этой функции. Ее параметры описаны в табл. 18.3.
Таблица 18.3. Параметры функции ORA_SPACE_ERROR_INFO
Параметр
Описание
ERROR_TYPE
Тип ошибки; может быть одним из следующих: SPACE QUOTA EXCEEDED — пользователь превысил свою квоту табличного пространства, MAX EXTENTS REACHED — объект пытается выйти за пределы выделенной для него памяти (измеряется в экстентах), NO MORE SPACE — в табличном пространстве недостаточно места для записи новой информации
OBJECT_TYPE OBJECT—OWNER TABLE_SPACE一NAME
OBJECT一NAME SUB_OBJFCT_NAME
Тип объекта, с которым связана ошибка
Имя владелец объекта, с которым связана ошибка Табличное пространство, с которым связана ошибка
Имя объекта, с которым связана ошибка
Имя подчиненного объекта, с которым связана ошибка
Данная функция возвращает значение TRUE, если приостановление произошло вследствие одной из указанных в таблице ошибок, или значение FALSE в противном случае.
Функция ORA_SPACE_ERROR_INFO не устраняет возникшие в системе проблемы; ее задача 一 предоставление информации, необходимой для их решения. В описанном выше случае ошибка возникала из-за недостатка квоты табличного пространства. Приведем еще два примера SQL-инструкций, которые могут использоваться для исправления ошибок, обнаруженных с помощью функции ORA_SPA- CE_ERROR_INFO. ~
Если таблица или индекс заняли всю выделенную для них память, можно выполнить инструкцию
ALTER <гип_рбьекта> <влдделец_рбьектд>.<имя^ъектд> STORAGE (MAXEXTENTS UNLIMITED);
Когда же вам не хватает табличного пространства, воспользуйтесь инструкцией
/* Предполагается, что используются Oracle Managed Files (9i), поэтому явное объявление файла данных не требуется */
ALTER TABLESPACE <table_space_name> ADD DATAFILE:
Пакет DBMS_RESUMABLE
Если функция ORA_SPACE_ERROR_INFO возвращает значение FALSE, то причину приостановления инструкции устранить невозможно. Нельзя также допустить, чтобы инструкция оставалась приостановленной. Ее выполнение должно быть завершено путем вызова из триггера AFTER_SUSPEND процедуры ABORT, которая входит в состав пакета OBMS__RESUMABLE. Проиллюстрируем сказанное следующим примером:
/* Файп в web: lccal_abort.sql */
CREATE OR REPLACE TRIGGER after_suspend
AFTER SUSPEND-
ON SCHEMA
DECLARE
CURSOR curs_get_sid IS
SELECT Sid - ~
FROM v$session
WHERE audsid 工 SVS_CONTEXT('USERENV'.'SESSIONID');
v_s1d NUMBER:
BEGIN
IF ORA_SPACE_ERROR_INFO( пытаемся изменить ситуацию....
ELSE -- но сделать зто невозможно
OPEN curs_get_sid;
FETCH curs_get_sid INTO v_sid:
CLOSE curs_get_sid:
DBMS_RESUMABLE7ABORT(v_sid);
END~
ENO:
Процедура ABORT принимает единственный параметр — идентификатор сеанса, в котором операция должна быть завершена. Это позволяет вызывать данную процедуру из триггеров уровня схемы и базы данных. Если пользователь потребовал отменить текущую операцию, но не указал, какую именно, будет получено сообщение об ошибке такого содержания:
ORA-01013: user requested cancel of current operation
Функция GET_SESSION_TIMEOUT возвращает, а функция SET_SESSION_TIMEOUT устанавливает значение тайм-аута приостановленного сеанса, определяемого заданным идентификатором:
FUNCTION DBMS_REUSABLE,GET_SESSION_T[MEOUT (sessionid IN NUMBER) RETURN NUMBER;
PROCEDURE DBMS_REUSABLE.SET_SESSIONJIMEOUT C
sessionid IN NUMBER. TIMEOUT IN NUMBER):
Функция GET_TIMEOUT возвращает, а функция SET_TIMEOUT устанавливает значение тайм-аута текущего сеанса:
FUNCTION DBMS_REUSABLE.GET_TIMEOUT RETURN NUMBER:
PROCEDURE D8MS_REUSABLE.SET_TIME0UT (TIMEOUT IN NUMBER):
ПРИМЕЧАНИЕ
Новое значение тайм-аута вступает в действие немедленно; только не следует задавать его равным нулю.
Многократный запуск триггера
Триггер AFTER_SUSPEND запускается в случае приостановления инструкции. Поэтому в течение выполнения одной инструкции он может запускаться неоднократно. В качестве примера рассмотрим следующий триггер:
/* Файл в web: 1ncrement_extents.sql */
CREATE OR REPLACE TRIGGER after_suspend
AFTER SUSPEND"
UN SCHEMA
DECLARE
--получение нового максимального значения СтЕодее плюс единица)
CURSOR curs_get_Extents IS
SELECT max_extents + 1
FROM user_tables
WHERE table_name = 1MONTHLY_SUMMARY'; v_new_max NUMBER;
BEGIN -
выбор нового максимального количества экстентов
OPEN curs_get_extEnts;
FETCH curs_get_extents INTO v_new_max:
CLOSE curs_get_extents;
--изменение таблицы путем задания нового максимального количества экстентов
EXECUTE IMMEDIATE 'ALTER TABLE MONTHLY_SUMMARY ' || 'STORAGE ( MAXEXTENTS '||
v_new_max|| ')';
DBMS_OUTPUT.PUT_LINE('Incremented MAXEXTENTS to ' || v_new_max):
ENO;-—
Если мы создадим пустую таблицу, задав для ее параметра MAXEXTENTS (максимальное количество экстентов) значение 1, то при попытке поместить в таблицу данные объемом в 4 экстента получим такой результат:
SQL> @test'■
Incremented MAXEXTENTS to 2
Incremented MAXEXTENTS to 3
Incremented MAXEXTENTS to 4
PL/SQL procedure successfully completed.
В предыдущих примерах было показано, как обрабатывать ошибки, которые возникают из-за недостатка табличного пространства, приостанавливая инструкции до тех пор, пока не будет обеспечена возможность их продолжения. Такой
подход позволяет устанавливать для приложения минимальные параметры табличного пространства, квоты, а также выделять необходимое количество экстентов памяти, изменяя его по мере необходимости. И хотя для администратора базы данных это может показаться пределом мечтаний, на самом деле подобный подход имеет существенные недостатки.
О Периодические приостановления. Периодические приостановления инструкции могут значительно затруднить работу приложений, предназначенных для оперативной обработки транзакций (OLTP) и отличающихся особо высокой нагрузкой. Этот прием может быть совершенно недопустим, если на устранение проблем уходит много времени.
О Соревнование за рееурсы. Если таблица, которая связана с приостановленной инструкцией, окажется заблокированной, выполнение других инструкций будет отменено или отложено на неопределенное время.
О Повышенные затраты. Дополнительные затраты ресурсов системы из-за постоянного подключения новых экстентов или файлов данных. Затраты могут быть большими, чем при нормальном выполнении приложения.
По этим причинам мы бы рекомендовали пользоваться триггерами AFTER SUSPEND очень осторожно. Они подходят для восстановления длительных или итеративных процессов, перед перезапуском которых необходимо вызывать DML-hh- струкции, отменяющие уже выполненные действия. Однако они неприемлемы для приложений оперативной обработки транзакций.
Сопровождение триггеров
Oracle поддерживает ряд DDL-инструкций, которые позволяют повысить эффективность управления триггерами. С их помощью можно включать, отключать и удалять триггеры, просматривать информацию о ник, проверять их состояние.
Отключение, включение и удаление триггеров
Отключенный триггер не запускается в ответ на событие, с которым он связан. Синтаксис отключения триггера очень прост:
ALTER TRIGGER инл^григгера DISABLE:
Например, триггер e(np_afterjnsert отключается таким образом:
ALTER TRIGGER enip_after_insert DISABLE:
Отключенный триггер можно включить повторно с помощью следующей инструкции:
ALTER IKIGGER имя_григгера ENABLE:
В инструкции ALTER TRIGGER задается только имя триггера; ни его тип, ни что- либо иное задавать не нужно. Эту инструкцию можно вызывать из хранимой процедуры, как в следующем примере, где включение и отключение всех триггеров таблицы выполняется в помощью динамического SQL:
/* Файл 8 web: settrig.sp */
CREATE OR REPLACE PROCEDURE settrig (tab IN VARCHAR2. action IN VARCHAR2)
IS
v_actionVARCHAR2 (10) :- UPPER (action);
v other_action VARCRAR2 (10) :■ 'DISABLED';
BEGIN ~
IF v action - 'DISABLE'
THEN”
v other action 尸'ENABLED';
END IF;〜
FOR rec IN CSELECT trigger_name
FROM user_triggers
WHERE table_owner - USER
AND tabie_name - UPPER (tab)
AND status - v_other_act1on)
LOOP~-
EXECUTE IMMEDIATE 'ALTER TRIGGER ' || rEC.trigger_name || ' ' II v_action;
DBMS_OUTPUT. put Jine (
'Set status of ' || rec.trlgger_name || ' to ' |j v_action
END LOOP;
END:
Инструкция, выполняющая удаление триггера, так же проста:
DROP TRIGGER ш_триггера:
Просмотр триггеров
В словаре данных Oracle имеется несколько лредставлевий, возвращающих разнообразную информацию о триггерах:
Q DBAJRIGGERS — все триггеры базы данных;
О ALL_TRIGGERS — все триггеры, доступные текущему пользователю;
О USER_TRIGGERS — все триггеры, принадлежащие текущему пользователю.
В табл. 18.4 перечислены столбцы представлений, содержимое которых наиболее важно для пользователей и системы.
Таблица ХВ4 Содержимое столбцов представлений, возвращающих наибопее
ценную информацию □ триггерах
Имя столбца
Содержимое
TRIGGER一NAME
Имя триггера
TRIGGER.TYPE
Тип триггера: для триггеров DML — BEFORE STATEMENT, BEFORE EACH ROW, AFTER EACH ROW или AFTER STATEMENT; для триггеров DDL — BEFORE EVENT или AFTER EVENT; для триггеров INSTEAD OF — INSTEAD OF; для триггеров AFTER.SUSPEND — AFTER EVENT
TRIGGERING EVENT
Событие, вызвавшее запуск триггера: для триггеров DML — UPDATE, INSERT или DELETE; для триггеров DDL — операция DDL (полный список собыий приведен выше, в разделе, посвященном триггерам данного типа); для триггеров событий базы данных — ERROR, LOGON, LOGOFF, STARTUP или SHUTDOWN; для триггеров INSTEAD OF 一 INSERT, UPDATB ИЛИ DELETE; для триггеров AFTER SUSPEND — SUSPEND
Имя столбца
Содержимое
TABLE一OWNER
Различная информация в зааисимости от типа триггера: для триггеров DML — имя владельца таблицы, с которой связан данный триггер; для триггеров DDL, при условии, что это триггеры уровня базы данных,— SYS, в противном случае — имя владельца триггера; для триггеров событий базы данных, при условии, что это триггеры уровня базы данных, — SYS, в противном случае — имя владельца триггера; для триггеров INSTEAD OF — имя владельца предстазлеяия, с которым связан данный триггер; для триггеров AFTER SUSPEND, при условии, что это триггеры уровня бвзы данных, — SYS, в противном случае — имя владельца триггера
BASe_OBJECLJYPE
Тип объекта, с которым связан триггер: для триггеров DML — TABLE; для триггеров DDL—SCHEMA или DATABASE; для триггеров сабьгтий базы данных — SCHEMA или DATABASE; для триггеров INSTEAD OF — VIEW; для триггеров AFTER SUSPEND — SCHEMA или DATABASE
TABLE_NAME
Для триггеров DML — имя таблицы, с которой связан триггер; для остальных типов триггеров —• значение NULL
REFERENCING—NAMES Для триггеров DML (уровня стропи) — предложение, используемое для определения псевдонимов записей OLD и NEW; для остальных типов триггеров — текст «REFERENCING NFW AS NEW OLD AS OLD»
WHEN-CLAUSE
Для триггеров DML — предложение с условием выполнения триггера
STATUS
Состояние триггера (ENABLED или DISABLED)
ACTION.TYPe
Определение того, выгюпняет триггер вызов (CALL) или содержит код 叫SQL (PL/SQL)
TRIGGER. BODY
Текст тела триггера (столбец типа LONG)
Проверка состояния триггера VALID
Как это ни странно, но ни одно из указанных выше представлений не содержит информации о том, находится ли триггер в работоспособном состоянии. Если триггер создан с помощью ошибочной инструкции PL/SQL, он сохраняется в базе даН- ныхг но помечается как неработоспособный (INVALID). Для того чтобы определить, находится ли триггер в этом состоянии, нужно извлечь информацию из представления USER_OBJECTS или ALL_OBJECTS( как показано в следующем примере:
SQL> CREATE OR REPLACE TRIGGER Invalid trigger
AFTER DOL ON SCHEMA"
8EGIN
NULL
END;
/
Warning: Trigger created with compilation errors.
SQL> SELECT objecjname.
objecOype,
status'
FROM user objects
WHERE object_name - 'INVALID-TRIGGER';
QBJECT_NAMEOBJECT.TYPE STATUS
INVALID STATE TRIGGER INVALID
Q Управление
I h приложениями PL/SQL
А Управление программным кодом и его анализ в базе данных
А Защита кода хранимой программы
А Встроенная компиляция
А Тестирование программ PU/5QL
А Отладка программ PL/SQL
А Оптимизация программ PLJSQL
А Повышение производительности приложений
Написание программного кода — это лишь одна из составляющих длительного процесса разработки и сопровождения приложений. Осветить данную тему полностью в одной главе невозможно, мы только предлагаем вашему вниманию ряд идей и советов по следующим вопросам.
О Управление программным кодом и его анализ в базе данных. При компиляции программ PL/SQL исходный код загружается в словарь базы данных в разных формах (в виде текста программы, зависимостей, информации о параметрах и т. п.). С помощью SQL из этого словаря можно запрашивать информацию, необходимую для управления программным кодом.
О Защита кода хранимой программы. Oracle предлагает такой способ хранения исходного кода, при котором конфиденциальная информация скрыта от посторонних глаз. Указанный способ полезен для производителей, разрабатывающих и реализующих приложения на основе хранимого кода PL/SQL.
О Использование встроенной компиляции. Начиная с Oracle9z исходный код PL/SQL можно скомпилировать во встроенный объектный код, компонуемый в Oracle. Встроенная компиляция позволяет значительно повысить производительность приложения- (Ее влияние особенно заметно в программах, выполняющих большой объем вычислений, однако на производительности SQL не отражается.)
О Тестирование программ PL/SQL. Ниже будут рассмотрены способы тестирования программ PL/SQL с помощью утилиты тестирования исходного кода utPLSQL.■
О Отладка программ PL/SQ.L. В состав многих средств разработки сейчас входят графические отладчики на основе API. Это очень мощные инструменты, но все же они охватывают лишь малую часть всего процесса отладки. В данной главе рассматриваются некоторые технологии и подходы, которые помогут вам выработать собственные концепции отладки, эффективно спланировать и выполнить этот процесс.
О Настройка программ PL/SQL. Подробное описание процесса настройки кода PL/SQL невозможно уместить в одну главу. Однако мы приведем ряд наиболее важных советов и рекомендаций по анализу процесса выполнения программ с помощью встроенных утилит трассировки.
Управление программным кодом и его анализ в базе данных
При выполнении SQL-инструкции CREATE OR REPLACE исходный код программы и другие ее представления сохраняются в базе данных. Это дает разработчикам следующие преимущества.
О Информацию о программном коде можно получить с помотцью запросов SQL. Сформировав запрос к представлению словаря данных, вы получите список программ, модифицированных в определенный день, или узнаете, какие из программ помечены как неработоспособные и нуждаются в перекомпиляции.
О База данных управляет зависимостями между хранимыми объектами. Если, предположим, структура таблицы, с которой работает хранимая функция, изменилась, состояние функции автоматически устанавливается в INVALID. В случае вызова данной функции она будет автоматически перекомпилирована.
Таким образом, с помощью SQL программист может работать с хранящимся программным кодом, выполнять его анализ, документировать изменения и дополнения и т. п. В следующем разделе рассказывается об источниках информации, находящейся в словаре данных.
Представления словаря данных для программистов PL/SQL
Словарь данных Oracle — это настоящие джунгли! В нем сотни представлений, основанных на сотнях таблиц, множество сложных взаимосвязей, специальных кодов и слишком много неоптимизированных определений представлений. В целом можно выделить три типа, или уровня, представлений словаря данных: О U5ER_* - представления, которые содержат информацию об объектах базы дан- ных( принадлежащих текущей схеме;
О ALL_* — представления, включающие сведения об объектах базы данных, доступных в текущей схеме (либо принадлежащих ей, либо доступных благодаря соответствующим привилегиям);
О DBA」—представления с информацией обо всех объектах базы данных.
Поскольку хранимые объекты содержатся в таблицах словаря данных, для получения информации о доступных программах можно использовать SQL. С его помощью запрашивается информация из таких представлений:
О USER_DEPENDENCIES - все зависимости объектов, принадлежащих текущей схеме;
О U5ER_ERR0RS — текущий набор ошибок для всех принадлежащих схеме объектов; доступ к этому представлению осуществляется посредством команды SHOW ERRORS приложения SQL*Plus, описанной в главе 2;
О USER_OBJECTS — принадлежащие текущей схеме объекты;
О USER_CIBJECT_SIZE — размер принадлежащих текущей схеме объектов:
О USERJOURCE — исходный код всех принадлежащих текущей схеме объектов в текстовом виде;
О U5ERJRIGGERS — принадлежащие текущей схеме триггеры базы данных;
О USER_ARGUMENTS - аргументы (параметры) всех принадлежащих текущей схеме процедур и функций.
Для просмотра структуры любого из этих представлений можно или активизировать команду DESC (describe — описать) в SQL*Plus, или обратиться к документации Oracle. Примеры использования перечисленных представлений приведены в следующем разделе.
Вывод информации о хранимых объектах
В представлении USER_OBJECTS возвращается следующая ключевая информация об объекте:
О OBJECT_MAME — имя объекта;
О OBJECT_TYP£ - тип объекта;
О STATUS — состояние объекта, VALID или INVALID.
Вот некоторые типы объектов (интересующие разработчиков PL/SQL в наибольшей степени), которые доступны посредством этого представления:
SQL> SELECT distinct object〜type FROM user_objects;
OBJECT_TYPE
FUNCTION
INDEX
JAVA CLASS
JAVA SOURCE
LOB
PACKAGE
PACKAGE BODY.
PROCEDURE
SEQUENCE
SYNONYM
TABLE
TRIGGER
TYPE -
TYPE BODY
VIEW
Используя представление USER_OBJECTS, вы можете получить полный список объектов PL/SQL, имеющихся в базе данных. Для этого создан следующий сценарий SQL*Plus (вы найдете его на узле O'Reilly э файле psobj.sql):
/* Файл 8 web: psobj.sql */
SET PAGESIZE 66
COLUMN object_type FORMAT A20
COLUMN object_name FORMAT A30
COLUMN status~FORMAT A10
BREAK ON object_type SKIP 1
SPOOL psobj.lis
SELECT object_type, object_name, status
FROM user_objects
WHERE Object_type IN (
'PACKAGE'PACKAGE BODY'. 1 FUNCTION二'PROCEDURE'.
'TYPE'. 'TYPE BODY')
ORDER BY object_type, status, object_nanie:
SPOOL OFF
В результате выполнения этого сценария выводится приведенный ниже спи-
сок объектов:
OBJECT_nPE
OBJECT_NAME
STATUS
FUNCTION
DEVELOP ANALYSIS NUMBER_OF ATOMICS FREQ_INSTR
INVALID INVALID
VALID
PACKAGE
CONFIG_PKG
EXCHDLR_PKG
VALID
VALID
PACKAGE BODY
EXCHDLR_PKG
VALID
PROCEDURE
AS$eSS_PDPULARin ASSER匸 CONDITION
INVALID
VALID
TYPE
DESSERT_T
VALID
TYPE BODY
DESSERT T
VALID
Обратите внимание, что значительная часть модулей помечена как INVALID. Причиной этого могут быть изменения в таблицах, на которые ссылается модуль, или изменения в программах, которые он вызывает. СУБД автоматически перекомпилирует эти объекты, когда программа пытается к ним обратиться. Иными словами, перекомпиляция происходит во время выполнения, так что пользователь запускает программу и ждет, пока не будут перекомпилированы все необходимые для ее работы объекты. Чтобы избежать задержки, связанной с автоматической перекомпиляцией, можно вручную откомпилировать недействительные модули. С этой целью лучше всего воспользоваться утилитой для перекомпиляции, разработанной Соломоном Якобсоном, которую вы найдете в файле recom- pile.sQl на узле O'Reilly.
Вывод и поиск исходного кода
Исходный код программ следует всегда хранить в текстовых файлах. Вы можете также пользоваться специальными средствами, в функции которых входит управление исходным кодом и хранение его вне базы данных. Однако у программ, находящихся в базе данных, есть важное преимущество: их исходный код можно анализировать с помощью SQL-запросов.
Представление USER_SOURCE возвращает исходный код всех объектов, принадлежащих текущему пользователю. Его структура такова:
N&me Null? Type
NAMENOT NULL VARCHAR2O0)
TYPEVARCHAR2(12}
LINENOT NULL NIWBER
TEXTVARCHAR2(2000)
Здесь NAME — это имя объекта; TYPE — его тип (PROCEDURE, FUNCTION, PACKAGE, PACKAGE BODY); LINE — номер строки, a TEXT — текст исходного кода.
Для разработчиков представление USER_SOURCE является очень ценным ресурс сом. Вот что оно позволяет сделать посредством соответствующих запросов: О вывести строку исходного кода с заданным номером;
О проверить стандарт кодирования;
О идентифицировать возможные ошибки и слабые места исходного кода.
Предположим, что мы установили правило, согласно которому отдельные разработчики никогда не должны жестко кодировать пользовательские номера ошибок, лежащие в диапазоне от -20 999 до -20 000 (поскольку такое кодирование может привести к возникновению конфликтов). Конечно, мы не сможем воспрепятствовать написанию разработчиком такой строки:
RAISE_APPLICATION_ERROR (-20306. 'Balance too low');
но зато способны создать пакет, идентифицирующий программы с подобными фрагментами кода. Он очень прост, а его главная процедура имеет вид:
/* Файл в web: valstd.pkg */
CREATE OR REPLACE PACKAGE BODY valstd
IS'
CURSOR objwith cur (str IN VARCHAR2)■
is -
SELECT name, text
FROM USER_SOURCE
WHERE UPPER (text) LIKE %’ || UPPER (str) j| 'X' MD патле !■ 'VALSTD':
PROCEDURE progwith (str IN VARCHAR2)
IS
BEGIN、
FOR prog_rec IN objwith_cur (str)
LOOP ~'
DBMS^OUTPUT.PUT_LINE (prog^rec.name 11 ': 1 11 prog_rec.text3;
END LOOP; ~~~
END;
PROCEDURE pw_rae
IS~
prog_rec objwith_curXROWTYPE;
BEGIN ~~
progwith ('RAISE_APPLICATION_ERROR');■
END:~"
END valstd:
После компиляции этого пакета в схеме можно проверить наличие в прорам- мах значений -20 NNN:
SQL* EXEC valstd.progwith ('-20');.
CHECKJALANCE - RAISE APPLICATIONJRROR (-20306, 'Balance too low');
MY_5eG5ION - PRAGMA EXCEPTI0NJNindblink_not_open.-2081):
VS区STAT - CREATE DATE : 1999-07-20
Обратите внимание, что вывод прораммой третьей строки не противоречит правилам; она возвращена только потому, что условие отбора является недостаточно жестким.
Еще одним примером применения данного пакета служит поиск всех объявлений с использованием типа данных фиксированной длины CHAR:
SQL> EXEC valstd.progwith ('CHAR');
Конечно, данное аналитическое средство является достаточно примитивным, но ничто не мешает его усложнить. Можно, к примеру, разместить в интранете HTML-документ с информацией об исправлениях, которые нужно внести в программный код, и автоматически вызывать его каждое воскресенье с помощью пакета DBMS_JOB, с тем чтобы в понедельник утром администратор проверял наличие в сети интранет обратной связи.
Следующая процедура идентифицирует все независимые процедуры и функции, не имеющие раздела исключений (к сожалению, для пакетных программ она не подходит). Как и предыдущая, она хороша прежде всего своей простотой, хотя и не отличается интеллектуальностью (в частности, проверяет сама себя).
/* Файл 8 web: shownoexc.sp */
CREATE OR REPLACE PROCEDURE show_no exc_sections
IS- f -
CURSOR, check for_exc (nm IN VARCHAR2)
IS" ~
SELECT line
FROM user_source
WHERE NAME"- nin AND INSTR (UPPER (text). 'EXCEPTION')〉0:
check rec check for excXRCMTYPE;
BEGIN "~ "
FOR obj_rec IN (SELECT object_nane, object-type
FROM user_objects
WHERE object_type IN ('PROCEDURE'. 'FUNCTION'))
LOOP~
OPEN chEck_for_exc (obj_rec.object_name):
FETCH checic_for_exc INTO check rec:
IF ctieck_for_exc?FOUNO
THEN ~ ~
NULL;.
ELSE
OBMS_OUTPUT. put Jine《
-obj_rec.object_type
II ''
obj_rec.object_name
' does not contain the EXCEPTICW keyword.
END IF;
CLOSE check_for_exc:
END LOOP: ~
END:
Защита кода хранимой программы
Почти все приложения содержат закрытую информацию. Например, разработав приложение PL/SQL для продажи, программисты обычно не хотят, чтобы пользователи (или конкуренты) получили доступ к их коду. Oracle предоставляет специальную программу под названием wrap (что в переводе с английского означает 4скрывать«>), предназначенную дл兄 сокрытая большинства или даже всех секретов приложения.
ПРИ М ЕЧАНИЕ
Некоторые разработчики считают, что программа wrap «шифрует» код, но это неверно. Если вам нужно передать действительно секретную информацию, например пароль, полагаться на эту программу не стоит. Однако следует иметь в виду, что с помощью встроенного пакета DBMS_OBFUS- CATION_TOOLKIT Oracle позволяет интегрировать в приложение защиту DES (Data Encryption Standard — стандарт шифрования данньпс). Более подробные сведения по этому вопросу в& найдете в документации Grade.
Когда вы скрываете исходный код PL/SQL с помощью программы wrap, читабельный ASCII-текст преобразуется в нечитабельный. Результирующий код может передаваться пользователям, в региональные офисы и т. д. для создания соответствующих программ в других экземплярах базы данных. Он характеризуется такой же переносимостью, что и исходный код PL/SQL, и может импортироваться и экспортироваться. Ваза данных Oracle воспринимает программы со скрытым кодом точно так же, как и программы, текст которых является читабельным. Иными словами, скрытые программы в базе данных с функциональной точки зрения ничем не отличаются от обычных программ PL/SQL; единственное различие заключается в том, что их нельзя прочитать, запросив через представление USER. SOURCE.-
Как скрыть исходный код
Для того чтобы скрыть исходный код PL/SQL, нужно запустить выполняемый файл wrap.exe из каталога bin экземпляра Oracle. Формат команды wrap таков:
編「叩 1пате=йсх_(райл [опате^ез_0йхл]
Здесь исходил — это исходная, читабельная версия программы, а рез_файл — результирующий файл, куда будет записана скрытая версия кода. Если исходный файл задан без расширения, по умолчанию принимается расширение .sql.
ПРИМЕЧАНИЕ——
До появления OracleBI выполняемые файлы программы wrap назывались wrapNN.exe, где NN — номер версии Grade (например, wrap72.exe или wrap73.exe).
Если аргумент oname ке задан, программа wrap создает файл с именем, которое совпадает с именем исходного файла, и расширением .plb, назначаемым по умолчанию. Расширение расшифровывается как «PL/SQL binary» (это название является не совсем точным, однако оно отражает суть процесса и означает, что двО' ичные файлы тоже нечитабельны).
Программа wrap, в частности, применяется, если нужно сделать следующее:
О скрыть программу, используя установки по умолчанию:
C:\oracle\ora90\bin\wrap iname-secretprog
О скрыть тело пакета, явно задав расширение исходного файла и имя результирующего; обратите внимание, что скрытый файл не обязательно должен иметь то же имя или расширение, что и исходный:
c:\oracle\ora90\bin\wrap 1name-secretbody.spb oname-shhhhh.bin
Работа со скрытым кодом
Ниже приведено несколько советов по работе со скрытым кодом.
О Создавайте командные файлы для быстрого и унифицировакного сокрытия содержимого одного или более файлов. В Windows NT в каталоге своих исходных файлов вы можете создать файл с расширением .bat и включить в него следующую строку:
c:\orant\b1n\wrap iname-plvrep.sps oname-plvreD.pls
Конечно, можно также создать командный файл с параметрами, и передавать ему имена скрываемых файлов.
О Для того чтобы скрыть исходный код, нужно поместить его в файл операционной системы. Если же вы работаете в специализированной среде разработки PL/SQL, оперирующей кодом, который хранится прямо в базе данных, придется сохранить исходный код в файле, скрыть его, а затем откомпилировать и поместить обратно в базу данных, в результате чего он станет нечитабельным. При передаче кода пользователям зтс допустимо, ио для разработки и сопровождения приложений совершенно не годится.
О Скрывать можно только спецификации пакетов, тела пакетов, независимые функции и процедуры. Если программе wrap передать файл, содержащий другой код SQL или PL/SQL, этот файл изменен не будет.
О Для того чтобы узнать, скрыта ли программа, достаточно посмотреть на ее заголовок. Если программа скрыта, он будет содержать ключевое слово WRAPPED:
PACKAGE BODY m пакета WRAPPED
Впрочем, даже если вы не заметите указанного слова в заголовке, то, посмотрев ниже, поймете, что исходный текст скрыт, поскольку он будет выглядеть примерно так:
UNE TEXT
45 dbcd
46 95d425ff
47
48 7 PACKAGE:
Каким бы плохим ни был ваш стиль программирования, все же он наверняка не настолько ужасен!
О Скрытый код имеет намного больший объем, чем исходный. Так, пакет размером 57 Кбайт после сокрытия занял 153 Кбайт, а пакет размером 86 Кбайт - замял 357 Кбайт. Таким образом, для хранения скрытого кода в базе данных требуется больше места. Размер откомпилированного кода остается неизменным, хотя время компиляции также может увеличиться-
■строенная компиляция
В версиях, предшествовавших Oracle9z( после компиляции исходного кода всегда создавалось представление (обычно называемое байт-кодом), которое сохранялось в базе данных и интерпретировалось во время выполнения программы виртуальной машиной, входящей в состав Oracle. Выполняемый код, обрабатываемый этой виртуальной машиной, соответствовал платформе, где он выполнялся. В Oracle9i наряду с существующим предусмотрен новый подход к компиляции программ. Теперь исходный код можно, при желании, откомпилировать во встроенный объектный код, а затем скомпоновать его с Oracle. Этот процесс называется встроенной компиляцией. (Однако учтите, что к анонимным блокам PL/SQL встроенная компиляция неприменима^)
Как выполнять встроенную компиляцию? Что хорошего в данной возможности? В каких случаях ею пользоваться? Ответы на все эти вопросы вы найдете в настоящем разделе.
PL/SQL часто применяется в качестве оболочки при выполнении SQL-инструкций, установке переменных привязки и обработки результирующего набора строк. Для программ, осуществляющих операции подобного типа, скорость выполнения кода PL/SQL обычно не очень важна, поскольку их производительность определяется скоростью выполнения SQL-инструкции. (Некоторое замедление способна вызвать неэффективная организация переключения контекста, но это можно исправить с помощью оператора FORALL и предложения BULK COLLECT, рассматриваемых в главе 13.)
Существует, однако, множество других приложений и программ, выполняющих большой объем вычислений с помощью PL/SQL независимо от базы данных. Ведь PL/SQL — это еще и полнофункциональный процедурный язык. Предположим, что мы написали программу для поиска всех прямоугольных треуголъ- ннков с целочисленными значением длины сторон. Причем требуется отобрать только уникальные треугольники, то есть такие, стороны которых не кратны сторонам одного из уже найденных треугольников.
Наша программа выполняет поиск всех возможных комбинаций длины двух катетов треугольников в диапазоне от единицы до заданного максимального значения. При этом отбираются лишь те треугольники, у которых квадратный корень суммы квадратов катетов отличается от целого числа не более чем на 0,01. Треугольники, для которых данное условие вьшолняется, проверяются затем по теореме Пифагора с использованием целочисленной арифметики. После этого найденный треугольник сравнивается с полученным ранее. Каждый новый треугольник сохраняется в таблице PL/SQL, а полный набор кратных ему треугольников (включая треугольники с максимальными параметрами) сохраняется в отдельной таблице PL/SQL, что позволяет ускорить проверку на уникальность.
Для реализации этого алгоритма требуются следующие пограммные компоненты: два цикла (один из них должен быть вложен в другой), э теле которых выполняется несколько арифметических операций, отбор и сравнение; процедуры, осуществляющие сравнение путем перебора значений таблицы PL/SQL (с дополнительными арифметическими операциями), и дополнение таблиц PL/SQL в случае необходимости.
Так как же встроенная компиляция отразится на функционировании подобного кода? Мы измерили время выполнения двух версий данной программы (интерпретируемой и откомпилированной встроенным компилятором) для р_гпах - 5000, то есть для 12,5 млн. повторений тела внутреннего цикла. Получилось соответственно 548 и 366 с (на компьютере Sun Ultera60, без учета нагрузки программы- тестировщика). Таким образом, скорость вьтолнения программы, откомпилированной встроенным компилятором, на 33 % выше.
Совсем неплохо, если учесть, что речь идет о полупрозрачном усовершенствовании, использование которого не требует изменения программного кода! При этом важно, что, ускоряя работу одних программ (производящих большой объем вычислений), встроенная компиляция не замедляет выполнения друпос Вы спрашиваете, как же ее реализовать? Об этом мы расскажем дальше.
Настройка, выполняемая администратором базы данных
Встроенная компиляция PL/SQL заключается в преобразовании исходного кода PL/SQL в исходный С-код с последующей компиляцией для той платформы, на которой она выполняется. Компиляция и компоновка исходного С-кода производятся с помощью программ сторонних производителей, местоположение которых задается администратором базы данных, обычно в файле INTT.ORA, содержащем инициализационные параметры. Объектный код каждого откомпилированного таким образом библиотечного модуля PL/SQL сохраняется в каталоге файловой системы текущей платформы, также указанном администратором базы данных. Естественно, что встроенная компиляция выполняется дольше, чем компиляция в интерпретируемую форму (по нашему опыту, примерно вдвое дольше). Она требует ряда дополнительных действий: генерации С-кода на основе выходных данных компилятора PL/SQL, его сохранения, вызова компилятора С и выполнения им компиляции, компоновки результирующего объектного кода в Oracle.
Корпорация Oracle рекомендует настроить С-компилятор таким образом, чтобы он не вьшолнял никакой оптимизации. Наши тесты показали, что оптимизация
С-кода незначительно повышает скорость его выполнения, но значительно замедляет компиляцию.
Сравнение интерпретируемого и компилируемого режимов
Режим компиляции программ PL/SQL определяется значением параметра PLSQt_ C0MPILER_FLAGS, которое задается следующим образом:
ALTER SESSION
SET pisql compller_flags =
'NATIVE' 7* или 'IHTERPRETEO' */:
Установленный режим компиляции действует для всех библиотек PL/SQL, компилируемых в течение сеанса. Этот режим сохраняется вместе с метаданными библиотечного модуля, так что если программа в дальнейшем автоматически перекомпилируется в результате проверки зависимостей, будет использоваться первоначально заданный режим.
Для того чтобы узнать, в каком режиме компилируется данная программа, нужно выполнить следующий запрос к словарю данных:
SELECT о.оЬзес1_пате NAME, s.param_'/alue conp_mode FROM USERJTOREDJETTINGS s.
USER_OBJECTS о
WHERE o.object_id - s.objectJd
AND param_name - 'plsql_con)piler flags'
ANO o.obj^ct.typu IN ('PACKAGE',"'PROCEDURE'. 'FUNCTION');
Важно помнить следующее: если для перекомпиляции всех неработоспособных программных модулей схемы обратиться к встроенному пакету DBMS_UTILI- TY.COMPILE_SCHEMA, он будет использовать текущее значение параметра PLSQL_COM- PILER—FLAGS, а не режим компиляции, заданный для каждого программного модуля- Oracle рекомендует вызывать все библиотечные модули PL/SQL из одного модуля верхнего уровня (см. врезку), что позволит избежать переключения контекста, который происходит, когда библиотечный модуль, откомпилированный в одном режиме, вызывает модуль, откомпилированный в другом. Эта рекомендация касается и библиотечных модулей, предоставленных Oracle. Они всегда поставляются откомпилированными в интерпретируемом режиме.
ПЕРЕВОД ВСЕЙ БАЗЫ ДАННЫХ В РЕЖИМ ВСТРОЕННОЙ КОМПИЛЯЦИИ Простейший способ выполнить рекомендации Oracle относительно компиляции в одном режиме всех библиотечных модулей, вызываемых из одного модуля верхнего уровня, — это компилировать все эти модули базы данных в режиме NATIVE. В составе будущих версий базы данных (тех, что выйдут после Oracle9i Release 2) будут поставляться сценарий, выполняющий эту задачу, а также сценарий, переводящий всю базу данных в режим INTERPRETED. Пока же при переводе базы данных в режим NATIVE можно руководствоваться инструкциями, опубликованными на узле Oracle Technology Network (http://otn.oracle.com).
Какой же вывод можно сделать из сказанного? Если приложение выполняет значительное количество вычислений, стоит всерьез задуматься о переходе к режиму встроенной компиляции.
Тестирование программ PL/SQL
Каждый программист согласится с тем, что заниматься созданием программы или искать пути реализации какой-либо новой идеи всегда намного приятнее, чем «доводить до ума» уже готовый проект. Никому не хочется тратить время на тес' тирование протраммного обеспеченияг не хочется писать к нему документацию. В большинстве случае разработчики решают, что если пользователи не нашли ошибок сразу, значит, их нет, и не выполняют достаточного количества тестов, С чем же все это связано?
О Ориентирование лишь на успех. Мы настолько убеждены в том, что наш код будет работать правильно, что предпочитаем даже не задумываться о возможности появления плохих новостей. Мы проводим некоторое поверхностное тестирование, убеждаемся, что все работает, и предоставляем возможность другим искать ошибки, если они вообще есть.
О Сроки. Многие рассуждают так: сейчас время Интернета! Рынок есть рынок. Все должно быть готово на завтра, так что воспользуемся стратегией компаний Microsoft и Netscape и станем выпускать «бета-версии» программного обеспечения как готовые продукты, а пользователи пусть их тестируют.
О Некомпетентность руководства. Как правило, руководители информационных отделов ничего не смыслят з разработке программного обеспечения, не понимают сути этого процесса. И если у нас нет времени на создание полноценного проекта (включая проектирование, написание программного кода, тестирование, документирование и усовершенствование), результатом работы станет создание полной ошибок неработоспособной программы.
О Нежелание тратить время на тестирование. Создание тестовых программ обычно считается пустой тратой времени, ведь всегда найдется более важная работа. Одним из следствий подобного похода является то, что значительная часть обязанностей по тестированию возлагается на отдел контроля качества (если таковой имеется в компании). Привлечение к работе профессионалов в области контроля качества может оказать огромное влияние на результат, однако и разработчики не должны снимать с себя функцию тестирования собственного кода.
Таким образом, программный код всегда нуждается в дополнительном тестировании. Автор данной книги также в свое время потратил не мало сил на усовершенствование процесса тестирования программ. Он изучил схемы тестирования, предложенные другими программистами, работающими с объектно-ориентированными языками. Затем создал собственную схему для тестирования программ PL/SQL и реализовал ее в виде утилиты utPLSQL Зто проект с открытым кодом, которым сейчас пользуются разработчики по всему миру.
В следующих разделах мы рассмотрим общепринятые способы тестирования программного кода. После этого расскажем об утилите utPLSQL, о том, как с ней работать и где найти требуемое программное обеспечение.
Неэффективные технологии тестирования
Предположим, требуется создать приложение, обрабатывающее большое количество строк, В PL/SQL имеется функция SUBSTR, которая возвращает заданную часть строки. Однако ее использование связано с рядом проблем. Дело в том, что эту функцию удобно применять, когда вы знаете, где начинается подстрока и какой она длины. Однако очень часто известно лишь местоположение начального и конечного символов, а длину подстроки нужно вычислять:
叱string :-SUBSTR(full_string.5.17):--начало и конец? 0йгнет...
mystring :-SU8STR(fullustring,5.12);--конец - начало?
niystring :=SUBSTR{fu】l_string,5.13):--конец - начало + 1?mystring尸 SUBSTR
(full_$tring. 5. 11); -- конец - начало - 1?
Почему мы должны помнить, по какой формуле вычисляется длина подстроки? В конце концов мы снова возвращаемся к старому проверенному способу: берем лист бумаги, пишем «abcdefgh», ставим пометку над «с», затем еще одну над 《g» и считаем иа пальцах, после чего вспоминаем, что формула имеет такой вид: «конец - начало +1».
Не проще ли создать такую функцию, которая производила бы подобные вычисления вместо нас?
CREATE OR REPLACE FUNCTION betwnStr (
strlngjn IN VARCHAR2.
startjn IN INTEGER.
endjn IN INTEGER)
RETURN VARCHAR2
IS
BEGIN
RETURN (
SUBSTR (
stringjn.■
startjn,
endjn - startjn + 1
END;
Итак, мы создали функцию betwnstr. Теперь ее следует протестировать, для чего пишем тестовый сценарий:
BEGIN
OBMS_(XJTPUT.Pl/T_LINE (betwnstr ('abcdefgh'. 3. 5)):
END:-
Запускаем данный сценарий (предварительно сохранив его в файле betwnstr.tst) и убеждаемся, что все работает корректно:
SQL* Pbetwnstr.tst
cde
Все? Тестирование завершено? Отнюдь. Существует еще множество условий, которые нужно проверить, чтобы с уверенностью сказать, что функция работает правильно. Например, выясним, будет ли функционировать наш сценарий, если конечную позицию подстроки задать за пределами строки. С этой целью открываем сценарий, вносим соответствующие изменения:
BEGIN
DBMS_OJTPJT.PUT_LINE (betwnstr Cabcdefgh', 5. 10));
ENO:-
и снова выполняем:
SQL> №etwnstr.tst efgh
Вроде бы, опять все в порядке. Подумав еще немного, можно добавить ряд других условий (см. приведенную ниже таблицу).
Начало
Конец
NULL
NOT NULL
NOT NULL
NULL'
NULL
NULL
3 (положительное число)
1 (меньшее положительное число)
3 (положительное число)
100 (болыие дойны строки)
Для того чтобы проверить все эти условия, нужно снова обратиться к файлу betwnstr.tst и провести соответствующее количество тестов. И лишь после этого можно с чувством выполненного долга откинуться на спинку стула и немного отдохнуть.
Может показаться, что тестирование программы проведено очень тщательно. Однако у этого подхода имеются серьезные недостатки.
О Правильность результатов тестирования приходится проверять визуально. Но как определить, какой ответ является корректным - «cde» коли, может быть, <efg>? Нам как разработчикам)гужно вновь принимать самостоятельное ре- гаение, на что тратится дополнительное время.
О При выполнении очередного тестирования код предыдущей тестовой программы пропадает, так как новые значения вводятся поверх старых.
Q Мы не отслеживаем, какие именно тесты проводятся, а по окончании процедуры удаляем тестовый сценарий.
Очевидно, что последствия подобного подхода к делу плачевны. Допустим, что в программу потребуется добавить дополнительные функции или что через некоторое время при ее использовании будет обнаружена какая-либо ошибка. В нашем примере подобной ошибкой является передача отрицательного начального значения. В ответ на это функция 3U8STR отсчитывает or конца строки ^ позиций и возвращает заданное количество символов (слева направо). В частности, в следующем случае:
BEGIN'
DBMS__OJTPUT.PUT_LINE (SUBSTR Cabcdefgh'. -3. 5));
END;
функция SUBSTR возвращает значение fgh. Если те же аргументы передать функции betwnstr, она вернет очень похожий, но несколько подозрительный результат:
SQL> BEGIN
2 09MS_0UTPUT.PUT_LINE (SUBSTR ('abcdefgh'. -3, 5));
3 OBMS二 OUTPUT.PUT_LINE (betwnstr ('abcdefgh', -3, 5));
END:--
/ fgh fgh
Но что за строка будет содержаться между позициями -3 и -5? Скорее всего, функция betwnstr должна принимать значение -3 как начальную позицию, а значение -5 — как конечную, после чего возвращать def. Оставляем задачу измене' нил кода данной функции читателю в качестве домашнего упражнения, поскольку к тестированию модулей это не относится. А пока давайте предположим, что функция betwnstr была модифицирована так, чтобы интерпретировать отрицательные значения как начальную и конечную позиции.
Рассмотрим ситуацию с точки зрения тестирования. Для того чтобы убедиться, что код работает, нужно, повторив все предыдущие тесты, выполнить ряд дополнительных проверок, используя в качестве входных данных разные сочетания положгггельных и отрицательных чисел. Но что делать, если все выполненные ранее тестовые программы не были сохранены? Мы просто запускали их, убеждались, что код работает, а затем заменяли новыми.
Итак, нужно воссоздать тесты, которые были проведены ранее. Но, как всегда, оказывается, что на это совершенно не осталось времени. По плану программа должна быть сдана, как бы плохо она ни была протестирована. Подобные слова не раз приходилось слышать, не правда ли? Это явный признак того, что нужно остановиться и тщательно обдумать свои дальнейшие действия.
Прежде всего, безусловно, в тестовом сценарии нужно проверить работу функции с разными значениями входных параметров:
SET SERVEROUTPUT ON FORMAT WRAPPED
BEGIN
DBMS_OUTPUT.PUT_LINE DBMS OUTPUT.PUT\lNED0MS_OUTPUT.PUT_LINE DBMS—OUTPUT.PU匸LINE D8MS_OUTPUT.PUTJINE d8ms_output.put2line D8MS_0UTPUI.PUT-LINEEND;-DBMS_OUTPUT.PUT_LINE DBMS OUTPUT.PUT\lNED0MS_OUTPUT.PUT_LINE DBMS—OUTPUT.PU匸LINE D8MS_OUTPUT.PUTJINE d8ms_output.put2line D8MS_0UTPUI.PUT-LINEEND;-(betwnstr (*dbcdefgh,, 3. 5));
DBMS_OUTPUT.PUT_LINE DBMS OUTPUT.PUT\lNE
D0MS_OUTPUT.PUT_LINE DBMS—OUTPUT.PU匸LINE D8MS_OUTPUT.PUTJINE d8ms_output.put2line D8MS_0UTPUI.PUT-LINE
END;-
DBMS_OUTPUT.PUT_LINE DBMS OUTPUT.PUT\lNE
D0MS_OUTPUT.PUT_LINE DBMS—OUTPUT.PU匸LINE D8MS_OUTPUT.PUTJINE d8ms_output.put2line D8MS_0UTPUI.PUT-LINE
END;-
(betwnstr ('abcdefgh'. 0, 2));
(betwnstr ('abcdefgh'. NULL. 5)):
(betwnstr ('abcdefgh'. 3, NULL)):
(betwnstr ('abcdefgh'. 3, 100));
(betwnstr ('abcdefgh'. -3. -5)):
(betwnstr ('abcdefgh', -3. 0)):
Выполнение данного сценария (который в случае модификации программы без труда можно дополнить) приводит к следующим результатам:
SQL> gbetwnstr.tst
cde
abc
cdefgh def Bbcdef
К сожалению, правильность этих результатов установить нелегко. Не кажется ли вам, что должен существовать более удобный способ тестирования кода, позволяющий выполнить исчерпывающий набор тестов без больших затрат времени? Для подобных случаев автором была разработана утилита utPLSQL, о которой выше уже упоминалось. Чтобы протестировать программу betwnstr с помощью этой утилиты, в SQL*Plus нужно выполнить такую команду:
SQL> exec utplsql.test ('betwnstr')
Если тестируемая программа содержит ошибки, на экран будет выведено сообщение:
>
FFFFFFF ДА
III
L
и
и RRRRR
ЕЕЕЕЕЕЕ
>
F
А А
I
L
и
и
R R
Е ,
>
F
А А
I
L
и
и
R R
Е
>
F
АА
!
L
и
и
R R
Е
>
FFFF
АА
[
L
и
и
RRRRRR
ЕЕ£Е
>
F
АААААААА
I
L
и
и
R R
Е
>
Г
АА
L
и
и
R R
Е
>
F
АА
I
L
и
и
R R
Е
>
F
АА
Ш
HLLLLL
иии
R R
ЕЕЕЕЕЕЕ
FAILURE: "betwnstr"
FAILURE - EQ "normal" Expected "cde" and got "c"
FAILURE - EQ "zero start" Expected “Oc" and got "a"
SUCCESS - ISNULL "null start" Expected and got ""
SUCCESS - ISNULL "big start small end" Expected "" and got ""
Но если после исправления ошибки тестирование пройдет успешно, появится другое сообщение:
SOL> exec utplsql.test ('betwnstr')
>
SSSS
и
и
ссс
ссс
ЕЕЕЕЕЕЕ
SSSS
SSSS
〉
S S
и
и
с с
с с
Е
S S
s s
>
S
и
и
с с
с с
Е
S
s
>
S
и
и
с
с
Е
5
s
>
SSSS
U
и
с
с
ЕЕЕЕ
SSSS
SSSS
>
S
и
и
с
с
Е
s
s
S
и
и
с с
с с
Е
s
S'
>
s S
и
и
с с
с с
Е
55
s s
>
SSSS
иии
ссс
ссс
ЕЕЕЕЕЕЕ
SSSS
SSSS
SUCCESS: "betwnstr'1''
SUCCESS - EQ "normal" Expected "cdeu and got "cde11
SUCCESS - EQ "zero start" Expected *dbc" and got "abc"
SUCCESS - ISNULL "null start" Expected "" and got
SUCCESS - ISNULL "big start small end" Expected "" and got и"
Замечательно, правда? Утилита utPLSQL сообщает, успешно ли прошло тестирование, и даже информирует о результатах каждого из тестов. Как же это делается?
К сожалению, не все здесь происходит автоматически. Вам нужно создать тестирующий пакет в соответствии с определенными правилами (касающимися,
прежде всего, именования процедур н спецификации). Например, спецификация тестирующего пакета для программы betwnstr выглядит так:
CREATE OR REPLACE PACKAGE ut_betwnstr
IS
PROCEDURE ut_setup;
PROCEDURE utjeardown:
--Для каждой тестируеной программы...'
PROCEDURE ut_betwnstr;
END ut_betwnstr;
Иными словами, создаются процедура инициализации теста ut_setup, запускаемая до тестирования модуля, и процедура ut^teardown, которая выполняется после тестирования. Сама тестирующая процедура называется ut betwnstr. Вот фрагмент ее кода:
PROCEDURE UJBETWN3TR IS.
BEGIN ”
utAssert.eq (
'normal'.
BETWNSTHC'abcdefg'. 3, 5),
'def
);
utAssert.isnull ( 'null start'. BE^NSTRt'abcdefg', NULL, 5).
END ut_BETWNSTR:
Процедура вызывает программы утилиты utPLSQL, проверяющие, соответствуют ли выходные даяньге программы betwnstr ожидаемым. Результаты тестирования сохраняются в таблице базы данных. После успешного выполнения всех тестов утилита utPLSQL запрашивает их из таблицы и выводит на экран. Полный цикл выполнения упоминаемых выше тестовых программ с помощью утилиты utPLSQL показан на рис. 19.1.
Даже не вникая во все детали кода и архитектуры, вы наверняка заметите преимущества этой технологии.
О Для поведения тестов используется специальная программа utPLSQL-test. Она сообщает программисту о том, успешно ли выполнены разработанные им тесты, тем самым избавляя его ог необходимости самостоятельно проверять результаты.
Q Для тестирования программы с помощью утилиты utPLSQL создается процедура тестирования (в нашем случае — ut_betwnstr). Таким образом, тесты для многократного выполнения 4кэшируются» автоматически и после каждого изменения программного кода его тестирование можно быстро повторить. Мы уверены, что представленная программа тестирования будет полезна любо- ^азрабогчику.
Проверка результетовПроверка результетовВаш тестовый пяшВаш тестовый пяшРис. 19.1. Последовательность обработки данных утилитой utPLSQLРис. 19.1. Последовательность обработки данных утилитой utPLSQL
Проверка результетов
Проверка результетов
Ваш тестовый пяш
Ваш тестовый пяш
Рис. 19.1. Последовательность обработки данных утилитой utPLSQL
Рис. 19.1. Последовательность обработки данных утилитой utPLSQL
Где найти дополнительную информацию
Дополнительные сведения об утилите utPLSQL вы получите, посетив домашнюю страницу проекта по адресу http://soureeforge,net/projects/utplsqI/.
Загрузите это программное обеспечение и, пожалуйста, присылайте нам сообщения о возможных способах его усовершенствования (и, конечно, отчеты об ошибках). Но даже если вы не станете пользоваться этой утилитой, надеемся, что она подскажет вам несколько идей относительно усовершенствования вашего собственного процесса тестирования.
Отладка программ PL/SQL
Тестирование программы — это поиск ошибок в программном коде, а ее отладка —это определение и устранение причин, обусловивших их появление. Речь идет о двух совершенно разных процессах, и путать их не следует. Протестировав программу и выявив ошибки, разработчик должен их исправить. С этого момента и начинается отладка.
Многие программисты считают отладку самой трудной частью программирования. Причины ее сложности заключаются в следующем.
О Недостаточное понимание задачи, решаемой данной программой. Большинство разработчиков предпочитают заниматься лишь написанием программного кода и не любят тратить время на то, чтобы разбираться в спецификации. Поэтому вероятность того, что созданная таким образом программа будет отвечать требованиям, ничтожно мала.
О Плохой стиль программирования. Трудные для чтения программы (то есть плохо документированные, содержащие слишком много комментариев, с неудачными именами переменных и т. п.), а также программы, неправильно разделенные на модули, отлаживать гораздо сложнее, чем корректно спроектированный и структурированный код.
О Большое количество ошибок в программе. Не имея необходимых навыков анализа и кодирования, можно написать программу, включающую множество ошибок.
О Слабые навыки отладки. Существует множество разных подходов к выявлению причин появления ошибок. Некоторые из них трлько затрудняют работу, поэтому очень важным является выбор правильной технологии отладки.
В следующих разделах рассматриваются методы отладки, которыми лучше не пользоваться, а также методы, способные значительно облепить этот процесс и сделать его более эффективным.
Неупорядоченная отладка
Столкнувшись с ошибкой, вы начинаете бурную деятельность по поиску причины ее возникновения. И хотя сам факт ошибки может свидетельствовать о том, что задача плохо проанализирована или что не найден наиболее удачный метод ее решения, вы и сейчас не склонны к анализу. Вместо этого вы помещаете в про- 1рамму множество операторов MESSAGE (в Oracle Forms), SRW.MESSAGE (в Oracle Reports) или DBMS_OUTPUT. PIPELINE (в хранимых модулях) и надеетесь, что это поможет.
Причем вы даже не сохраняете копию программы перед внесением изменения, поскольку считаете, что это займет слишком много времени. Вам, как всегда, некогда, поэтому вы уверены, что ответы на все вопросы появятся сами собой, после чего можно будет удалить отладочные операторы.
В результате оказывается, что потрачена масса времени на просмотр ненужной информации. Ведь вы проверяете все подряд, хотя большинство хспользуе- мых конструкций давно выверены и отлажены.
Вы пропускаете обед, чтобы не отвлекаться от работы, поглощаете не одну чашку кофе, пытаясь максимально сконцентрировать свое внимание. Даже не имея ни малейшего понятия о причине возникновения проблемы, вы думаете, что если попробовать ТО'ТО и то-то, нужный результат таки будет получен. Вы вносите изменения, после чего тратите несколько минут на компиляцию, написание и выполнение теста лишь для того, чтобы убедиться в бесплодности всех своих усилий. В результате появляется новая проблема, поскольку вы не учли влияния внесенного изменения на другие части программы.
Затем вы возвращаете программу в исходное состояние (и дай Бог, чтобы вам удалось сделать это аккуратно, больше ничего не испортив) и пытаетесь изменить еще что-нибудь в тщетной надежде, что это, наконец, поможет. Но не тут-то было. Ваш сослуживец, заметив, что у вас уже дрожат руки, предлагает свою помощь. Однако вы не знаете, как описать проблему, поскольку так и не смогли выяснить, что же здесь не так. Более того, смущены тем, что уже успели сделать (превратив программу в минное поле, сплошь усеянное операторами трассировки),
и осознаете, что у вас даже нет исходной версии, которую можно было бы показать коллеге. Поэтому вы в резкой форме отвергаете помощь лучшего в группе программиста, звоните домой и сообщайте, что к ужину сегодня не явитесь.
Почему? Потому, что нужно устранить ошибку!
Иррациональная отладка
Вы сгенерировали отчет, а он оказался пустым. При этом вы уверены, что вашей вины здесь быть не может, хотя в течение последнего часа занимались тем, что вносили изменения в структуры данных и программный код, который их запрашивает и форматирует.
Позвонив в отдел внутренней поддержки, вы спрашиваете, нет ли проблем с сетью, хотя File Manager сообщает, что все сетевые диски доступны. Потом вы интересуетесь, не девалась ли куда-нибудь база данных, хотя только что успешно к ней подключались, отбираете десять минут времени у специалиста из отдела поддержки и, ничего от него не добившись^ в раздражении вешаете трубку.
《Они там ничего не понимают», — бормочете вы. Но как бы там ни было, источник проблемы приходится искать самостоятельно. Так что вы углубляетесь в только что модифицироватшый код и проверяете каждую строчку, пока не найдете причину своих неприятностей. Следующие два часа вы вслух разговариваете с самим собой: «Посмотрю-ка сюда! Я вызвал хранимую процедуру внутри оператора IF. Раньше я этого никогда не делал. Может быть, хранимые программы так вызывать нельзя?» Вы удаляете оператор IF, заменив его оператором GOTO, но это не решает проблему.
Поиски продолжаются. «Код кажется правильным. Но он вызывает программу, которую когда-то написал Джо*. Джо уже давно уволился, так что, конечно же, виноват он: «Программа, наверное, уже не работает, тем более что мы обновили систему голосовой почты». Вы решаете протестировать программу Джо, которая не обновлялась уже два года и не поддерживает голосовую почту. Однако эта программа, как ни странно, функционирует.
Вы начинаете отчаиваться: -«Может быть, этот отчет следует выполнять только по выходным? А допускается ли помещать локальные модули в анонимные блоки? Вероятно, их можно использовать только в процедурах и функциях! Кажется, я слышал о подобной ошибке. Надо бы поискать обходные пути...»
Теперь вы начинаете сердиться и понимаете, почему ваш восьмилетний сын лупит по монитору, когда не может выйти на последний уровень, играя в Return to Castle Wolfenstein. И уже собравпшеь домой, вдруг осознаете, что подключены к базе данных, предназначенной специально для разработки и не содержащей никаких данных. Вы подключаетесь к другому экземпляру базы данных, запускаете отчет, и все оказывается в порядке. За исключением того, что теперь ваш отчет содержит оператор GOTO и массу других добавленных вами конструкций.
Советы и стратегии отладки
Эта глава не претендует на звание исчерпывающего руководства по отладке. Однако приведенные здесь советы и приемы помогут вам упорядочить собственные подходы и повысить навыки.
Пользуйтесь отладчиком исходного кода
Наиболее эффективным способом минимизации времени отладки кода является использование отладчика. Отладчик входит в состав практически любой интег^ рированной среды разработки приложений PL/SQL (IDE). И если вы пользуетесь TOAD или SQL Navigator от Quest, PL/SQL Developer от Allround Automations или SQL Station от Computer Associates (либо другими подобными средствами с графическим пользовательским интерфейсом), то сможете устанавливать в программах точки останова посредством щелчков мышью, выполнять код в пошаговом режиме, просматривать значения переменных и т. д.
Советы, приведенные в этом разделе, будут вам полезны только в том случае, если вы пользуетесь отладчиком с графическим пользовательским интерфейсом. Если же вы отлаживаете программы старым дедовским методом (вставляя в программный код множество вызовов процедуры DBMS^OUTPUT,PUT^LINE), то зря тратите время.
Собирайте информацию
Соберите как можно больше сведений о том, где, когда н при каких условиях про' изошла ошибка. Маловероятно, что столкнувшись с этой ошибкой впервые, вы получите о ней все необходимые данные. И помните, что многие проблемы, которые на первый взгляд кажутся простыми, на самом деле могут потребовать тщательного тестирования и анализа. Так что не бросайтесь сразу же модифицировать код в уверенности, что вы понимаете, в чем здесь дело, а сначала выполните следующие действия.
Запустите программу еще раз, чтобы посмотреть, воспроизводится ли ошибка. Если ошибка не повторяется, вам вряд ли удастся понять, чем она была обусловлена, и исправить ее. Так что постарайтесь определить условия, при которых ошибка повторяется, и воспроизвести ее еще раз.
Ограничьте область тестирования и отладки программы — не исключено, что так вы значительно быстрее найдете ошибку.
Проанализируйте обстоятельства, при которых ошибка не возникает. Выявление подобных ситуаций также помогает сузить область поиска и осознать причину ошибки.
Итак, чем больше информации об ошибке собрано, тем легче найти и устранить причину ее возникновения. Поэтому не жалейте времени на дополнительное тестирование и анализ поведения программы.
Не теряйте здравый смысл
Вие зависимости от того, какой язык программирования используется, все программы подчиняются определенным логическим правилам. Язык PL/SQL имеет один синтаксис, язык С — другой. В них применяются разные ключевые слова и операторы (хотя есть и общие, например IF, но их спецификации несколько разные). И уж совсем иначе выглядит программа на языке LISP — элегантная и требующая особого подхода к реализации программной логики. Однако за всем этим стоит логика, выражаемая с помощью тех или иных операторов.
Именно логическая строгость и облегчает изучение новых языков программирования (по сравнению с обычными национальными языками). Если вы в состоянии четко определить для себя задачу и выработать последовательность ее решения, особенности конкретного языка не имеют особого значения для успешного создания программы.
Тем не менее программисты удивительным образом ухитряются обходить всякую логику и здравый смысл и выбирают самые иррациональные пути решения проблем. Как только наша «репутация» оказывается под угрозой, как только что- то идет не так, мы начинаем подозревать и обвинять всех и все вокруг: коллег, компьютер, компилятор, текстовый процессор 一 кого угодно, только не себя. Но помните, что, не желая признать свой промах, вы отказываетесь от поиска путей решения проблемы. Компьютеры и компиляторы не отличаются интеллектом, но они быстры, надежны и последовательны. Все, что они умеют, - это следовать правилам, которые записаны в вашей программе. Так что, обнаружив ошибку в программном коде, примите ответственность за нее на себя. Признайте, что именно вы сделали что-то не так. а не компилятор PL/SQL, не Oracle Forms и не текстовый редактор.
Обнаружив, что один из базовых элементов кода не работает, сделайте перерыв. А лучше попросите кого-нибудь из коллег взглянуть на ваш код. Ведь, посмотрев со стороны, часто проще заметить то, что лежит на поверхности, но чего вы сами не можете увидеть уже битый час.
Выполняйте анализ вместо попыток
Итак, после того как вы собрали всю необходимую информацию об ошибке, ее нужно проанализировать. У многих программистов анализ принимает такую форму: «Хм, кажется, причина в этом. Сейчас внесем изменения, нерекомпилируем и проверим, что получилось»-.
Чем плох подобный подход? Если вы ищете решение путем проверки возможных вариантов, это означает, что:
О вы не уверены в способности данного изменения действительно решить проблему; имея такую уверенность, вы бы не «проверяли», а просто тестировали внесенное изменение;
О вы не проанализировали ошибку, чтобы найти причины ее появления; если бы вы имели представление о том, чем вызвана ошибка, то знали бы, как ее исправить; не будучи уверенным, вы действуете методом проб и ошибок, а это плохая практика;
О даже если внесенное вами изменение устраняет ошибку, вы не знаете наверняка, не приняла ли она другую форму и не появится ли вновь (если вы не понимаете, в чем состоит проблема, ее внешнее исчезновение еще не означает, что она действительно решена); все, что можно сказать в данном случае, - это то, что при известных вам условиях данная проблема больше ие появляется.
Чтобы окончательно избавиться от проблемы, таковую нужно полностью проанализировать и определить ее источник. Только тогда будет найдено правильное и надежное решение.
Отыскав потенциальное решение, очень внимательно проанализируйте программный код, не выполняя его. Попробуйте придумать разные сценарии для
проверки своей гипотезы. И лишь обретя уверенность в том, что вы действительно поняли проблему и нашли ее решение, внесите изменения и протестируйте новую версию программы. Помните: нужно не пробовать, а исправлять и тестировать.
Делайте перерывы и обращайтесь за помощью
Слишком погрузившись в проблему, трудно сохранить способность оценивать ее объективно. Поэтому если вы чувствуете, что застряли и не продвигаетесь вперед, когда все средства перепробованы, а решения все нет, примите следующие радикальные меры:
Сделайте перерыв.
Обратитесь за помощью.
Попытки «высидеть» решение обычно не приводят к успеху. Если накопились усталость и психологическое напряжение, если зы потеряли цель и начали действовать нерационально, если вы уже много часов не отрываете взгляда от экрана, попробуйте отвлечься и немного передохнуть - есть вероятность, что решение появится само собой.
Работая за компьютером, возьмите себе за правило как минимум один раз в час вставать из-за стола, чтобы немного прогуляться. Расслабьтесь и позвольте «нейросетям» вашего мозга установить новые соединения, и, вероятно, у вас появятся новые идеи. Ведь окружающий мир не прекращает существовать, даже когда ваши глаза прикованы к экрану. Взгляните, что происходит за окном.
Еще более эффективный путь - попросить кого-нибудь просмотреть ваш код. Это действительно очень помогает. Можно часами сражаться с программой, а затем, рассказывая кому-нибудь о своих затруднених, вдруг поиятт>, в чем дело. Ошибка может быть очень простой, например она может состоять в несоответствии имен, в неверном предположении или в неправильной логике оператора IF. В любом случае существует огромная вероятность того, что, позвав кого-либо на помощь, вы тут же сами во всем разберетесь. Но даже если на вас нс снизойдет неожиданное озарение, причину ошибки поможет выявить свеж.ги взгляд постороннего человека, который:
О не писал этот код и не имеет неверных предположений о том, как он должен работать;
О не зациклен на программе и вообще не волнуется о том, будет она функционировать или нет.
Кроме того, обращаясь за помощью к коллегам, вы демонстрируете им, что уважаете их мнение и квалификацию. Если же вы один из лучших программистов в группе, то признаете, что также иногда делаете ошибки и нуждаетесь в помощи. Это помогает создать атмосферу доверия, взаимопомощи и сотрудничества, что в конечном счете повышает эффективность работы всей группы.
Изменяйте и тестируйте разные области кода по очереди
Одним из главных недостатков хороших программистов является то, что они слишком уверены в своих способностях и квалификации и поэтому пытаются решать одним махом множество проблем. Внеся определенное число изменений и запустив тест, мы получаем неутешительные результаты. Оказывается, что, во-первых, изменения вызвали новые проблемы (это часто случается и является
явным признаком того, что программа требует длительного тестирования и отладки), а во-вторых, исчезли не все исходные ошибки. Но хуже всего то, что мы понятия не имеем, какие изменения способствовали устранению ошибок, а какие послужили причиной появления новых.
Подобный способ отладки вносит в программу лишь сумбур и путаницу, поэтому нужно вернуться к исходной точке и действовать более последовательно.
Если вы вносите нс самые элементарные изменения, исправляйте по одной ошибке за раз и сразу тестируйте программу Применение такой тактики подразумевает выполнение большого количества перекомпиляций и тестов, но в конечном счете помогает сэкономить время и силы*
Описанная методика тестирования называется инкрементальной. Она требует, чтобы перед тестированием программы, в которой вызываются другие модули, таковые тестировались по отдельности. Когда дело дойдет до тестирования всей программы, вы точно будете знать, что возникающие проблемы обусловлены не действием отдельных модулей, а их взаимодействием, (О том, как тестировать модули, рассказывалось ранее, в разделе «Тестирование программ PL/SQL».)
Оптимизация программ PL/SQL
Оптимизация приложения Oracle — это сложный процесс, включающий оптимизацию SQL-инструкций, проверку правильности конфигурации системной глобальной области (SGA), оптимизацию алгоритмов и т, д. Настройка отдельных программ PL/SQL немного проще, но тоже сопряжена с преодолением ряда трудностей. Прежде чем тратить время на попытку повысить производительность кода PL/SQL, необходимо сделать следующее.
О Настроить доступ к коду и данным в SGA. Перед выполнением программный код загружается в область памяти Oracle, называемую SGA. Этот процесс может потребовать оптимизации, как правило, осуществляемой администратором базы данных. О SGA и этапах выполнения программ PL/SQL подробно рассказывается в главе 20.
О Оптимизировать SQL-инструкции. Практически в любом приложении, разработанном для РСУБД Oracle, большая часть операций по настройке связана с оптимизацией SQL-инструкций. Например, если в программе производится неэффективное объединение данных из 16 таблиц, это сводит на нет все попытки усовершенствования процедурной части блока кода. Иными словами, если программа выполняется, скажем, 20 ч, а вам нужно, чтобы она решала некоторую задачу максимум за 30 мин, все надежды возлагаются на оптимиза- :цию SQL. Существует множество инструментальных средств сторонних про
изводителей, помогающих администраторам баз данных и разработчикам в выполнении сложного анализа SQL-кода и предлагающих другие альтернативы.
Итак, убедившись, что код PL/SQL выполняется достаточно эффективно, следует «переключиться» на программный код. Вот наши рекомендации в этой связи.
О Используйте для разработки приложения лучпше из известных вам стратегий и стандартов. При реализации исходных требований к программе старайтесь
применять наиболее эффективные подходы, однако не стоит чрезмерно беспокоиться о совершенствовании каждой строки кода. Помните, что большая часть написанного кода не является слабым местом программы и не нуждается в оптимизации.
О Анализируйте эффективность выполнения различных компонентов приложения. Если скорость работы приложения недостаточно высока, нужно эыяс- нить, какие именно элементы приложения замедляют его работу и целиком сконцентрироваться на них.
О Оптимизируйте алгоритмы. Поскольку PL/SQL является процедурным язы^ ком программирования, его часто применяют для реализации сложных формул и алгоритмов. В программах можно встретить условные операторы, циклы, даже операторы GOTO и многократно используемые модули. Программные алгоритмы могут быть реализованы множеством разных способов, причем не всегда успешно. Как же оптимизировать неудачно написанные алгоритмы? Это непростой вопрос. Ведь процесс настройки алгоритмов гораздо сложнее процесса оптимизации инструкций языка SQL (который структурирован, поэтому по отношению к нему может применяться автоматизированный анализ).
О Пользуйтесь всеми доступными средствами повышения производительности программ PL/SQL, Корпорация Oracle наполнила свой продукт множеством средств, повышающих эффективность выполнения йрограммного кода. Пользуйтесь такими операторами, как RETURNING и FORALL, и ни в коем случае не применяйте устаревшие методы.
Подробное рассмотрение таких операций, как оптимизация SQL, а также конфигурирование базы данных и SGA, эыходит за рамки нашей книги. В частности, для более детального изложения одной из перечисленных тем — оптимизации PL/SQL — потребовалось бы несколько глав. Кроме того, разработчики часто обнаруживают, что большинство советов нередко оказываются неприменимыми к конкретным приложениям и средствам. Так что оставшуюся часть этой главы мы посвятим анализу производительности программного кода и лишь в конце приведем некоторые советы, относящиеся к оптимизации приложений самого широкого диалазона.
Анализ производительности PL/SQL
Перед тем как приступать к оптимизации приложения, нужно выяснить, какие из его компонентов замедляют работу, а уж затем прилагать определенные усилия. И Oracle, и сторонние производители предлагают множество инструментов, помогающих проанализировать работу приложения. Большинство из них выполняют прежде всего анализ содержащихся в программе SQL-инструкций, предлагают их альтернативную реализацию и т. и. Это очень мощные средства, но несмотря на огромное количество предоставляемых ими вспомогательных данных, они не дают разработчикам отэета на главный вопрос: как быстро выполняется конкретная программа и насколько повысится скорость ее работы после внесения определенного изменения?
Для получения ответов на приведенные вопросы Oracle предлагает множество встроенных средств. Перечислим наиболее полезные из них.
□ DBHS_PROFILER — встроенный пакет, который позволяет активизировать для те- кущего сеанса профайлер (подпрограмму протоколирования, позволяющую оценить время выполнения отдельных функций). Это означает, что при запуске программного кода Oracle начнет записывать в таблицы подробную информацию о том, как долго выполняется каждая строка кода. После этого можно будет выполнять запросы к этим таблицам или, чго более предпочтительно, выводить данные в удобной графической форме, используя такие средства, как TOAD и SQL Navigator.
О DBMS_UTILITY.GET_TIME - встроенная функция, вычисляющая время выполнения кода с точностью до сотой доли секунды. С этой функцией взаимодействуют сценарии файлов tmr81.ot и plvtmr.pkg (имеются на узле O'Reilly). Таким образом можно четко фиксировать время выполнения каждой операции в программе и даже сравнивать разные реализации одной и той же логики.
Ниже приводится несколько советов и примеров, касающихся использования инструментов, которые обеспечивают взаимодействие с программами пакета D8MS_ PROFILER.~
Oracle пока ке устанавливает пакет D8MS_PROFILER автоматически. Для того чтобы узнать, имеется ли он в вашей системе, подключитесь к своей схеме в SQ,L*Plus и выполните такую команду:
SQL> DESC DBMS_PRQFILER
Появление сообщения вида
ERROR;
ORA-04043: object dbms_profiler does not exist
свидетельствует о том, что этот пакет нужно инсталлировать.
Если вы работаете с Oracle 7.x или 8.0, попросите администратора базы данных выполнить от имени пользователя, имеющего привилегию SYSDBA, следующие сценарии (первый создает спецификацию пакета, а второй - его тело):
$KdTdno「_pRACLE『 rdbms/admln/dbms pbp.sql
$Karanor_ORACLE/rdbms/admi n/prvtpbp.plb
Для OracleSf и Огас1е9г, также от имени пользователя, имеющего привилегию SYSDBA,. надлежит выполнить такой сценарий:
IXardnor_0/WCLE/rdbnis/adinin/profloa(l.sql
Затем в собственной схеме требуется запустить сценарий:
WaraTOr_O/MCLE/rdbms/aciniin/proftab.sql
Это приведет к созданию трех таблиц, которые будут заполняться информацией, полученной с помощью программ пакета DBMS.PROFILER:
О PLSQL_PROFILER_RUNS — родительская таблица с информацией о запускаемых программах;
О PLSQL_PROFILER_UNITS — программные модули, выполняемые в составе дайной программы;
О PLSQL^PROFl LER_DATA - данные профайлера для каждой строки программного модуля.
Возможно, вы найдете полезными некоторые образцы залросов и пакеты для вывода отчетов, предлагаемые Oracle в следующих файлах:
$Каталог_ОЯАС1Г/р1sql/demo/profrep,sql
$俗力加/■—照 af/Dlsql/demo/profsuniiql
После оггределения всех этих объектов можно собирать протоколируемую информацию о приложении с помощью приведенного ниже кода.-
BEGIN
DBMS_OUTPUT.PUT LINE (
D0MS_PROFILER.STARTJROFILER (
'showemps ' ||
TO_CHAR (SYSDATE, 'YYYYMMDD HH24:MI:SS')
);'
stiowemps;
D0MS_OlirPirr.PUr_LlNE (
D丽S_PROFILEn.STOP一PROFILER):
END:
По окончании выполнения программы вы можете осуществить запросы к таблицам PLSQL_PROFILEK_..., чтобы просмотреть результаты работы профайлера. Вот пример такого запроса, выводящий строки коза, выполнение которых заняло не менее 1 % общего времени работы программы:
/* Файл в web: slowest.sql */
update plsql_profller_units set total_t1me - 0;
execute prof_report_utilities-rol1up_alInruns:
set pagesize 999
set 1inesize 120
column unit format a20
coliMi line# format 99999
column tlme_Count format al5 column text format a60
spool slowest2.txt
select
to_char(pl.total_time/10000Q00. '99999999k) || '-' ||
T0_CHAR Cpl.totaT_occur) as t1me_count.
substr(p2.un1t_owner. 1, 20) ||' ||
Gecode(p2.un1t_name. ''. '<апопупюи5^',■
substr(p2.unit_name,l, 20)) as unit.
T0_ClW< (pl.l 1пе#Т II И p3.text text
from
plsqljjroflle^data pl.
plsqlj3rofiler_units p2,
all_source p3, plsql_prof11er_grdnd_total p4
where- 一 一
p2.un1t_owner NOT IN ('SYS'. 'SYSTEM') AND
pl.runID нrstparm AND
(pl.total_tinie х p4.grand_total/100) AND
pl.runlD - p2.runid ANO
p2.unit_number=pl.unit number AND
p3.type^'PACKAGE 0ODV ~AND
p3.owner - p2.unit_owner AND'
рЗ.Ипе = pl.line# AND
p3.name-p2.uni t_name'
order by pl.total_t1rne de$c:
spool off
Как видите, эти запросы очень сложны (для создания такого объединения четырех таблиц мы обратились к одному из готовых запросов, предоставленных Oracle). Поэтому гораздо удобнее применять специальные инструментальные средства PL/SQL с графическим пользовательским интерфейсом.
Проанализировав программный код приложения и выявив его слабые места, нужно решить, какие изменения позволят повысить его прошводительность. В этом вам поможет материал следуюпшх разделов.
Трассировка выполнения кода
В ранних версиях Oracle уже можно было производить трассировку кода PL/SQL, но только в Oracle8i появился настоящий API, позволяющий осуществлять полноценную трассировку процедур, функции и исключений PL/SQL. Программы для запуска и прекращения трассировки сеанса находятся во встроенном пакете DBMSJRACE. Когда производится трассировка, данные о выполнении программа записываются в соответствующий файл трассировки.
ПРИМЕЧАНИЕ———
Средства трассировки PL/SQL позволяют записывать действия, производимые кодом, в специальный файл. А средства профилирования (реализованные в виде описанного выше пакета DBM5_PRO- FILER) предназначены для проведения более Подробного анализа работы приложения, включая хронометраж операций и подсчет количества выполнений конкретной строки.
Установка пакета DBMS_TRACE
Этот пакет не всегда автоматически инсталлируется вместе со всеми остальными встроенными пакетами. Для того чтобы определить, установлен ли он, нужно подключиться к базе данных в качестве пользователя с учетной записью SYS (или с другой учетной записью, имеющей привилегии SYSDBA) и выполнить команду:
BEGIN
D8MS_TRACE.CLEAR_PLSQL TRACE:
END:
Если на экране появится строка вида
PLS-00201identifier 1D8MSJRACE.CLEAR_PLSQL__TRACE' must be declared
значит, пакет нужно устанавливать. С этой целью, используя для подключения ту же учетную запись, выполните следующие файлы в указанном порядке:
欽&丁&/7"_0例 ClE/rdbms/admin/dbmspbt.Eql
SHaraBorJJRACLE/ rdtxns / admin/prvtbpt. pl b
Программы из пакета DBMS_TRACE
В пакете DBMS TRACE содержатся три программы:
О 5ET_PL5QL_TRACE — активизирует трассировку в текущем сеансе;
О CLEAR_PLSQL_TRACE — останавливает трассировку в текущем сеансе;
О PLSQL_TRACE_VERSION - возвращает основной и дополнительный номера версий текущего трассируемого пакета PL/SQL
Для трассировки выполняемого кода PL/SQL сначала нужно осуществить в текущем сеансе такой вызов:
DBMS_TRACE.SET_PLSQL_TRACE (дааень_трассировки INTEGER);
Здесь значением аргумента уровень^грдссировки служит одна из констант, перечисленных ниже.
constant
INTEGER
:-1:
constant
INTEGER
:=2;
constant
INTEGER
:=4;
constant
INTEGER
-8;
constant
INTEGER
.-32:
constant
INTEGER
.-64;
constant
INTEGER
=128:
constant
INTEGER
-256;
О Константы, определяющие, какие элементы программы PL/SQL подлежат трассировке:
OBMS_TRACE.trace_all_calls OBMS_TRACE.trace_enabled_caHs OBMS_TRACE.trace_all_exceptions OBMS_TRACE.trace_enabled_exceptions DBMS_TRACE.tracers1l_sql DBMS_TRACE ,trace__enabl ed_sql DBMS_TRACE.trace_al1_1Ines DBMS_TRACE.trace_enabled_lines
О Константы, управляющие процессом трассировки:
DBMS_TRACE.trace_stop DBMS_TRACE.trace_pause D6HS_TRACE.trace_resume DBMS_TRACE.trace limitDBMS_TRACE.trace_stop DBMS_TRACE.trace_pause D6HS_TRACE.trace_resume DBMS_TRACE.trace limitconstantINTEGER:・16384:
DBMS_TRACE.trace_stop DBMS_TRACE.trace_pause D6HS_TRACE.trace_resume DBMS_TRACE.trace limit
DBMS_TRACE.trace_stop DBMS_TRACE.trace_pause D6HS_TRACE.trace_resume DBMS_TRACE.trace limit
constantINTEGER:*4096;
constantI盯EGER:-8192:
constantINTEGER:■16:
ПРИМЕЧАНИЕ
Комбинируя константы из пакета DBMS_TRACE, можно активизировать одновременную трассировку нескольких элементов языка PL/SQL. Обратите внимание, что конеиты, управляющие процессом трассировки (в частности, DBMS_TRACE.trace_pause) не могут использоваться в сочетании с другими константами (напркмер, с DBMS~TRACE.trace_enabled_calls).
Для того чтобы активизировать трассировку всех программ, выполняемых в текущем сеансе, нужно произвести такоЗ вызов:
DBMS_TRACE.SET_PLSQL_TRACE (DBMS_TRACE.trace_aH_calls);
Трассировка всех исключений, инициируемых в течение текущего сеанса, активизируется следующим образом:
OBMS_TRACE.SET_PLSQL_TRACЕ СDBMS_TRACE.teaсе_а1]_except1ons);
Далее запускается программный код. При необходимости остановить трассировку выполняется вызов:
DBMSJRACE .CLEAR_PLSQL_TRACE;
После этого можно проанализировать содержимое файла трассировки. Имена файлов трассировки генерируются СУБД Oracle; просмотрев даты модификации
всех файлов, легко найти нужный. О том, где эти файлы хранятся, рассказывается далее, в разделе «Форматирование собранных данных».
Учтите, что трассировка PL/SQL на многопоточном сервере невозможна.
Управление содержимым файла трассировки
Файлы трассировки, генерируемые пакетом D8MSJRACE, могут быть очень большими. Чтобы уменьшить объем выходных данных, следует трассировать не все подряд, а только конкретные программы.
ПРИМЕЧАНИЕ
Указанный подход нельзя использовать с удаленными вызовами процедур.
Для того чтобы созданную или измененную в течение сеанса программу сделать доступной для проведения трассировки, следует изменить установку сеанса, управляющую отладкой:
ALTER SESSION SET PLSQL_0E9UG-TRUt;
Если вы не хотите менять установку всего сеанса, перекомпилируйте конкретный программный модуль в режиме отладки следующим образом (к анонимным блокам это неприменимо);
ALTER [PROCEDURE | FUNCTION | PACKAGE 8Q0Y] имя_программы COMPILE DEBUG:
После того как вы сделаете нужные программы доступными для выполнения трассировки, инициируйте трассировку только этих программных модулей:
OBMSJRACE.SET_PLSQL_TRACE (DBMSJRACE.trace^enabled_calIs);
Трассировку исключений можно отраничить лишь теми исключениями, кото рые инициируются в доступных для данного процесса программах:
OBMSJRACE,SET_PLSQL_TRACE (DBMSJRACE.trace_enabled_exc印tions);
Если вы установили два уровня трассировки (для всех программ или исключений, а также для заданных программ или исключений), будет действовать уровень «для всех».
Приостановление и возобновление процесса трассировки
Процедура SET_PLSQL_TRACE способна не только определять, какая информация подлежит трассировке. С ее помощью можно также приостанавливать и возобновлять процесс трассировки. В следующем вызове запрашивается информация, которая не фиксировалась, пока трассировка не была возобновлена:
DBMS_TRACE.SET_PL5QL_TRACE(DBMS_TRACE.trace_pause);
Пакет DBMSJRACE помещает в файл трассировки запись, указывающую на то, когда трассировка была приостановлена и когда возобновлена.
Посредством константы DBMS_™CE.traceJ imlt можно указать, что в файле трассировки должна сохраняться информация только о 8192 трассируемых событиях. Этот подход гарантирует, что включение трассировки не приведет к перегрузке базы данных. По завершении сеанса трассировки будут сохранены лишь 8192 последних записей.
Форматирование собранных данных
Если вы активизировали трассировку только определенных программных модулей, но не текущей программы, данные трассировки записаны не будут. Если же назначена трассировка текущей программы, в файл трассировки записывается ее тип, имя и глубина стека.
При трассировке исключении определяется номер строки программы, в процессе выполнения которой оно было инициировано, его тип (пользовательское или предопределенное) и, в случае предопределенного исключения, его номер (номер ошибки, связанной с пользовательским исключением, всегда равен 1).
В качестве примера приведем результат трассировки процедуры showemps:
*** 1999.06.14.09.59.25.394
*** SESSION 10:(9.7) 1999.06.14.09.59.25.344
PL/SOL TRACE INFORMATION
Levels set : 1
Trace: ANONYMOUS BLOCK: Stack depth - 1
Trace: PROCEDURE SCOTT.SHOWEMPS: Call to entry at line 5 Stack depth = 2
Trace: PACKAGE BODY SYS.DBMS SQL: Call to entry at line 1 Stack depth - 3
Trace;PACKAGE BODY SYS.QBMS^SYS-SQL: Call to entry at line 1 Stack depth - 4
Trace:PACKAGE BODY SYS.DBMSJYS_5QL: ICD vector index - 21 Stack depth = 4
Trace: PACKAGE PLVPRO.P: Call to entry at line 26 Stack depth - 3
Trace:PACKAGE PLVPRO.P: ICD vector index -6 Stackdepth -3■
Trace;PACKAGE BODY PLVPRO.P: Call to entryat line1 Stackdepth=3
Trace:PACKAGE BODY PLVPRO.P: Call to entryat line1 Stackdepth-3
Trace: PACKAGE BODY PLVPRO.P: Call to entry at line 1 Stack depth - 4
Повышение производительности приложения
После того как мы рассмотрели некоторые способы анализа работы приложения, предлагаем вашему вниманию несколько советов по повышению его производительности.
Как избежать выполнения ненужного кода
Ответить на этот вопрос непросто. И совсем уж не понятно, зачем подобный код создавать и вводить его в программу.
Вот только реальность такова, что процесс программирования нередко сопровождается спешкой. Мы нервничаем, опасаясь, что не уложимся в установленные сроки, например из-за того, что начальство решило сэкономить на необходимых инструмеитальных средствах. В подобных условиях ничего не стоит вопреки законам логики написать несколько «холостых»- модулей или фрагментов кода, которые выполняются впустую и замедляют работу приложения. Удаление этого кода позволяет повысить производительность приложения, причем иногда довольно существенно. Поэтому никогда не стоит жалеть времени на оптимизацию своей программы.
Поиск ненужного кода
Как же отыскать неэффективные фрагменты в приложении, содержащем тысячи строк кода? Вот несколько рекомендаций.
Q Проверьте циклы. Код в теле цикла (такого как FOR, WHILE либо простого цикла) обычно выполняется более одного раза. Поэтому шеияние любого неэф- фектггвного фрагмента в нем многократно усиливается.
О Проверьте SQL-инструкции, Прежде scero следует убедиться, что все SQL-ин- струкхши оптимизированы. Эта тема выходит за рамки нашей книги, однако имеется множество других книг, где освещается данный материал. Имейте в виду, что в некоторых случаях выполнение определенной операции только при помощи SQL неэффективно, и поэтому, применив для указанной цели PL/SQL, можно ускорить дело.
О Просмотрите фрагменты кода, подвергавшиеся многократным исправлениям. В любой сложной программе, которая эксплуатировалась более шести месяцев, обычно можно найти модифицированные фрагменты кода, подвергавшиеся многочисленным изменениям. Результатом таких изменений часто становится неэффективный код.
Q Проверьте разделы объявлений. В разделе объявлений объявляются переменные, им присваиваются начальные значения и т. п. Однако в этом разделе могут выполняться действия (скажем, формирование значении переменных по умолчанию), которые не всегда нужны и поэтому их не стоит выполнять в начале блока.
Давайте рассмотрим некоторые из перечисленных рекомендаций подробнее.
Проверка циклов
Как уже говорилось, код в теле циклов обычно выполняется более одного раза, поэтому влияние каждого его неэффективного фрагмента усиливается во много раз. Приведем пример. Занимаясь оптимизацией кода одного из клиентов, программист обнаружил 30-строчную функцию, которая выполнялась за полсекунды, но вызывалась так часто, что общее время ее работы составляло пять часов. Оптимизировав данную функцию, он смог добиться сокращения времени работы всей программы до двадцати минут. Так что всегда первым делом обращайтесь к циклам и проверяйте, нет ли у вас подобных проблем.
Вот еще один пример. Процедура принимает в качестве аргумента некоторое имя и по очереди обрабатывает строки курсора:
PROCEDURE process_data (nmjn IN VARCHAR2) IS BEGIN
FOR rec IM my_package.my_cursor
LOOP~
process_record (
UPPER (m_in), rec.total_production);i
END LOOP;'
END:
В данном случае проблема заключается в том, что функция UPPER является аргументом процедуры на каждой итерации цикла. Это нецелесообразно, поскольку
значение аргумента не изменяется. Объявив переменную для хранения того же имени в верхнем регистре и присвоив ей значение перед входом в цикл, мы избавляем программу от лишней и совершенно бессмысленной работы:
PROCEDURE process_data (nm_in IN VARCHAR2)
IS~"
v some table.some columnXTYPE 尸 UPPER (nm_jc):
BEGIN ~-
FOR rec IN my_package.my_cursor
LOOP~'
process_record tv nm. rec.total_production):
ENO LOOP: ~ f
ENO;
Конечно, выявить факт нерационального выполнения кода не всегда просто. Обнаружив, в частности, что процедура process^data является слабым местом про- траммы (ограничивает ее производительность), мы постарались бы проверить, как работает код, где она используется. Иногда неверные предположения о работе вызываемого кода могут послужить причиной задания излишних или даже некорректных действий программы. Например, в результате исследования могло бы выясниться, что процедура process_record первым делом всегда переводит полученное имя в верхний регистр, а следовательно, сразу исчезла бы необходимость в вызове функции UPPER.
Ниже приведен еще один пример программы с интенсивными вычислениями в теле цикла. Какие действия в данном случае являются излишними?
CREATE OR REPLACE PROCEDURE process_data (tab in IN VARCHAR2)
IS~
cursorjd PL$」NTEGER;
exec_stat PL5JNTEGER;
BEGIN ~~
FOR rec IN (SELECT ... FROM
LOOP
cursorjd := D8MS_SQL.open_cursor:
- Г
DBMS_SQL.parse (cursorjd,
'SELECT~
FROM employee E, ' || tab_in ||
'WHERE D.department_id 三'j| rec.Id ||
'AND~
DBMSJQL. native
);'.
exec_stat :- DBMS_SQL.execute (cursorjd);
DBMS_SQL.close_cursor (cursorjd);
END LOOP? -~
END:
Ответить на этот вопрос не так-то просто. Однако если вы знаете, как работает пакет D8MS_SQL (встроенный пакет для выполнения динамического SQL, описанный в главе 15), вам известно следующее.
О Возможно многократное использование курсора, выделенного с помощью функции OBMS_SQL. OREN^CURSOR. Поэтому незачем открывать и закрывать курсор на каждой итерации цикла.
О При работе с динамическим SQL нужно внимательно проанализировать SQL- строки. В данном случае, взглянув на строки 11-14, мы видим, что на каждой итерации цикла меняется только одно значение — department_id. В этом варианте кода процедуры оно является частью SQL-строки, в результате чего для каждого нового значения синтаксический анализ запроса выполняется по нескольку раз. Эффективнее было бы использовать привязку, как показано ниже.
Вот новый вариант процедуры process_data, где в цикле производятся только необходимые операции:
CREATE OR REPUCE PROCEDURE proce55_data (tabjn IN VARCHAR?)
IS一
cursor J d PLSJNTEGER:
exec_stat PLS_1NTEGER;
BEGIN ~ 一
cursorjd := D8MS_SQL.open^cursor;
DBMS_SQL.parse (
cursorjd.
'SELECT FRO emplovee *
II tabjn
II 'WHERE D.deparlmentjd - :ny」D AND
D8MSJQL. native):~"
FOR rec 】N (SELECT . FROM
LOOP..
DBMS_SQL.bind_variable (cursorjd, WJd', rec.id);
exec^stat 尸 DBMS_SQL,EXECUTE (cursorjd):
ENO LOOP;"
DBMS_SQL.close_cursor (cursorjd):
END: ~~'
Своевременное выполнение кода
Тот факт, что раздел объявлений располагается перед выполняемым разделом, вовсе ие означает, что все переменные программы должны быть объявлены именно в ием. Вполне возможно, что некоторые производимые здесь действия (например, формирование значений переменных по умолчанию) лучше перенести в другие части код^ поскольку логически они должны выполняться один раз.
Рассмотрим следующий блок кода:
PROCEDURE always do_everything (criteria In IN BOOLEAN)
IS…f
b1g_string VARCHAR2C32767) :- ten_niinLite_look-Up (...):.
bigjist一
Tlst_types.big_stfings_tt :- two^minute_niunber_cruncher (...);
BEGIN~ l --
IF NOT criteria_1n
THEN~
use_big_string (big_5tring); process_big_list (bigjist): ELSE ---
/* Никакие аажные операции здесь не выполняется */
END IF;
END;
В этой программе объявляется длинная строка и вызывается функция, которая тратит на ее заполнение 10 мин. Кроме того, здесь объявляется и заполняется большая коллекция (объявленного в пакете табличного типа), для инициализации которой используется функция, выполняющая длительные и интенсивные вычисления. Оба этих действия производятся в разделе инициализации. После тестироваиия приложения, вызывающего приведенную процедуру, оказалось, что оно работает чересчур медленно. Проанализировав его логику, мы выяснили, что та часть процедуры, где используются две огромные структуры, выполняется при каждом ее вызове, а не только когда значение переменной criteria^! п равно fa 1 se.
Отыскав источник проблемы, мы можем переписать программу следующим образом (с использованием вложенных блоков):
PROCEDURE only_as_needed (criteria in IN BOOLEAN)
IS~
PROCEDURE heavy_duty_processing
IS
big_5tring VARCHAR2 (32767)
:-ten_nrinute_looicup (...);
bigjist 1 ist_type$bg一siring51t
:=two_m1 nLite_nunber_cruncher (..J:
BEGIN-
use_big_string (big_string): process_big_list (big_list): END:~_
BEGIN
IF NOT criteria in
heavy_duty_processing;
ELSE ~/
/* Никакие важные операции зпесь не виполняптся */
ENO IF:
ENO;
Теперь длительные операции вызываются только при условии, что они действительно необходимы. У предложенного решения имеется еще одно достоинство: когда выполнение вложенного блока завершается, освобождается занимаемая им память. И если программа будет еще достаточно долго работать, все это время в памяти не будут содержаться липшие структуры данных.
Умение слушать
Умеете ли вы слушать? О чем вы думаете, когда вам что-либо говорят: о своем ответе или о том, какую информацию хочет донести до вас собеседник?
Умение слушать — это качество, присущее каждому умному человеку. Но оно является особенно важным для программиста, которому нужно выяснить требования пользователя и воплотить их в программе. Невнимательность в этом случае обходится очень дорого и приводит к написанию кода, который не соответствует требованиям или работает неэффективно. Рассмотрим приведенный ниже фрагмент программы:
CREATE OR REPLACE PROCEDURE removejept (
deptnojn IM efflp.deptnoZTYPE,
newjjeptriQ in IN emp.deptnoVYPE)
IS~
einp_count NUMBER;
BEGIN -
SELECT COUNT(*) INTO emp_count
FROM enip WHERE deptno - deptnojn;
IF emp_count > 0
THEN -
UPDATE emp
SET deptno н new_deptno_in
WHERE deptn。 - deptno_in;
END IF;~
DELETE FROM dept WHERE deptno = deptnoJn:
END d2p_d印t;
Используемая в нем процедура удаляет строку из таблицы подразделений, но сначала назначает входящим в указанное подразделение сотрудникам другое подразделение. Логика программы очень проста: если сотрудник относится к удаляемому подразделению, нужно обновить его строку, записав в нее другой номер подразделения. После перевода всех сотрудников в новое подразделение старое удаляется,
Как по-вашему, что здесь неправильно? Явно неверных действий программа не выполняет, но значительная часть ее кода является избыточной. Прежде всего, если инструкция UPDATE не обновляет ни одной строки, она не может генерировать ошибку. Поэтому предварительная проверка наличия сотрудников в удаляемом подразделении не нужна, а следовательно код, процедуры можно сократить следующим образом:
CREATE OR R£PMCE PROCEDURE remove_dept (
deptno_in IN emp.deptnoXTYPE.
new_deptno_in IN emp,d印tnoXTYPE)
is ~-
emp_count NIMBER:
8EGIN ~
UPDATE eirp'
SET deptno - new^deptnojn
WHERE deptno ■ deptno_in;
DELETE FROM dept WHERE deptno - deptnojn;
END dBp_dept:
Предположим, что проверка наличия сотрудников необходима. Давайте разберемся, что происходит. Нам нужно получить ответ на вопрос, есть ли в подразделении сотрудники, но вместо этого код определяет, сколько сотрудников в подразделении. И хотя ответ на второй вопрос легко преобразовать в ответ на первый, для чего достаточно проверить логическое выражение (emp_count > 0), программа явно выполняет лишние действия.
Существует множество способов аияснить, имеется ля в подразделении хоть один сотрудник. И выбранный метод может в значительной степени повлиять на производительность. Для сравнения разных подходов к решению этой задачи обратитесь к сценарию atieastone,sqlf который находится на узле O'Reilly.
ПРИМЕЧАНИЕ
В начале сценария atleastone.sql создается довольно большая копия таблицы employee. Этот код закомментирован, чтобы избежать его выполнения, если такая таблица действительно присутствует в базе данных. Ради эксперимента вы можете удалить символы коммент叩ня*
Просмотрев указанный сценарий, можно сделать следующий вывод: при необходимости выполнить одноразовую выборку данных и выявить наличие хотя бы одного элемента в некотором списке одним из наиболее эффективных решений является использование явного курсора. А самое главное в том, что это решение позволяет получить конкретный ответ на поставленный пользователем вопрос, а следовательно, удовлетворяет его требованиям.
Использование пакетных данных для минимизации SQL-обращений к базе данных
Когда переменная объявляется в теле или спецификации пакета, область ее видимости не ограничивается конкретной процедурой или функцией. Областью видимости данных уровня пакета является «весь сеанс подключения к Oracle», и значения этих данных сохраняются в течение всего сеанса. Этот факт можно использовать для минимизации частоты обращений к системной глобальной области (SGA). Поиск в структурах данных, хранящихся в собственной глобальной области программы (PGA), выполняется гораздо быстрее поиска в SGA, даже если нужная вам информация располагается в общей памяти.
Минимизация особенно важна, когда приложению нужно многократно обращаться к данным, не изменяющимся в течение сеанса. Самым очевидным примером является значение, возвращаемое встроенной функцией USER. Оно не меняется в течение сеанса, поскольку идентифицирует текущего пользователя. Так зачем же вызывать эту функцию более одного раза?
Иногда кажется, что встроенная функция работает очень медленно. Однако это не так: сама функция выполняется достаточно быстро, но в связи с тем, что она, во-первых, вызывается приложением неоднократно, а во-вторых, выполняет инструкцию SELECT.. .FROM dual, суммарное время ее работы оказывается весьма ощутимым. Несколько лет назад автор столкнулся с такой ситуацией при разработке генератора кода PL/Generator. После анализа процесса выполнения своей программы он обнаружил, что функция USER вызывается в пакете 15 000 раз. Это свидетельствовало о том, что программный код составлен нерационально, но у автора не было времени на его просмотр и исправление* Поэтому он поступил иначе — создал следующий пакет:
CREATE OR REPLACE PACKAGE thlsuser IS
name CONSTANT VARCHAR2(30) := USER;
END.
.а затем выполнил операцию глобального поиска и замены (заменив имя функции USER именем переменной thisuser.nauie). После перекомпилирования программы и ее запуска выяснилось, что сэкономлено несколько секунд 45-сек^ндиого процесса. Неплохо. Воспользовавшись пакетной переменной, автор обошелся единственным вызовом функции USER, после чего просто обращался к константе thi- suser.name.
Это основная идея, лежащая в основе кэширования данных в структурах уровня сеанса. Конечно, требования могут быть гораздо более сложными — возможно, вы захотите кэшировать набор связанных данных, например структуру записи или даже список запрошенных данных.
Вместо того чтобы подробно описывать каждый из этих случаев, предлагаем вам несколько сценариев (имеются на узле O'Reilly). Поэкспериментировав с ними и убедившись в полезности используемых там приемов, вы сможете адаптировать их код для собственных нужд.
О Кэширование одного значения. Следует избегать повторяющихся вызовов таких функций, как USER, SYSOATE (возвращаемые значения которых в Oracle9z больше не извлекаются с помощью запроса SELECT.. .FROM dual), и даже использования специфических для приложения статических значений. Обратите внимание на два следующих сценария:
thlsuser.pkg — позволяет создать пакет, который кэширует значение, возвращаемое функцией USER двумя способами: с помощью именованной константы и с помощью функции (вызываемой из клиентского PL/SQL);
thisuser.tst - тестовый сценарий, демонстрирующий способ повышения производительности за счет кэширования.
О Кэширование одной строки данных. Тестовый сценарий извлекает данные из конфигурационной таблицы текущего пользователя и кэширует их в записи, определенной на уровне пакета:
initpkg ~ создает таблицу пользовательской конфигурации и пакет, заполняет таблицу тестовыми данными;
init.tst — тестовый сценарий.
О Кэширование нескольких строк данных. Более интересный и сложный пример. Следующие сценарии демонстрируют принципы использования коллекции для кэширования нескольких строк данных, что позволяет производить в течение сеанса не более одного запроса к каждой строке (указанная технология работает только в том случае, когда данные не меняются в течение всего сеанса; пожалуй, она больше подходит для маленьких поисковых таблиц типа <код-описание>):
emplu.pkg - создает два пакета, один из которых извлекает данные с помощью стандартного запроса к базе данных, а другой обращается за этими данными в кэш, созданный на основе коллекции;
emplu.tst — тестовый сценарий, демонстрирующий повышение производительности программы за счет кэширования.
Использование предложения BULK COLLECT и оператора FORALL
В Oracle9i были значительно усовершенствованы два элемента, которые появились в Oracle8i: предложение BULK COLLECT и оператор FORALL. Эти элементы позволяют кардинально уменьшить время выполнения SQL-операций, обрабатывающих большое количество строк данных. Если вы выполняете S QL-инструкции, обрабатывающие более десяти строк за раз, воспользуйтесь такими элементами для ускорения работы программы.
Действия оператора FORALL похожи на действия цикла FOR. Указанный оператор «собирает» однородные DМL-инструкции в группу и направляет их ядру SQL для пакетной обработки. Приведем пример:
CREATE OR REPLACE PROCEDURE update_tragedies (
warcr1m_ids IN name_varray.
nian_victims IN number_varray
)~
IS
BEGIN
FORALL indx IN warcrimjds.FIRST …warcr1m_ids.LAST
UPDATE war_criminal
SET v1ctim_count - nuni__vict-ims (indx)
WHERE war_cr1n1nal_1d -""warcriwjds Clndx):
END;""
Предложение BULK COLLECT может использоваться для извлечения нескольких строк за одно обращение при выполнении операций выборки из явных и неявных курсоров:
DECLARE
names naine_varray:
mileages nimber_varray;
BEGIN~
SELECT name, mileage
BULK COLLECT INTO names, mileages
FROM transportation
WHERE TYPE - 'AUTOMOBILE'
ANO mileage < 20:
--А теперь работаем с данными в коллекциях
END:"
И наконец, ниже приведен пример совместного использования оператора FORALL и предложения BULK COLLECT, в котором последнее дополнено предложением RETURNING:
CREATE OR REPLACE FUNCTION remove__emps_by_dept (deptBst dlist_t)
RETURN enolist t----
IS_
enolist enolist_t;
BEGIN—•
FORALL aDept IN deptl1 st.FIRST..deptl1st.LAST
DELETE FRO1 emp WHERE deptno IN deptlist(aDept)
RETURNING empno BULK COLLECT INTO enolist:
RETURN enolist: ENO;
Как видите, чтобы воспользоваться преимуществами оператора FORALL и предложения BULK COLLECT, нужно свободно владеть приемами работы с коллекциями. Об этих массивоподобных структурах подробно рассказывается в главе 11. В главе 14 рассматривается предложение BULK COLLECT, а в главе 13 — оператор FORALL.
Часть VI
Особые возможности PL/SQL
В арсенале такого мощного и богатого языка, как PL/SQLt имеется множество элементов, которыми программисты пользуются сравнительно редко, но обойтись без которых в особо сложных и ответственнъ1Х ситуациях просто невозможно, В этой части книги затрагиваются наиболее сложные аспекты в изучении компонентов и возможностей РДЛУ〃. В главе 20 рассматриваются этапы и условия выполнения программ PL/SQJ^ механизмы управления памятью, особенности клиентского и серверного PL/SQL. Глава 21 посвящена использованию объектно-ориентира - ванных средств Oracle (объектных типов и объектных представлений). В главах 22 и 23 рассказывается о методике вызова кода 〃加 иСиз приложений PL/S QL.
П Глава 20. Выполнение программ PL/SQL
口 Глава 21. Объектно-ориентированные возможности PL/SQL
□ Глава 22. Взаимодействие Java и PL/SQL
口 Глава 23. Внешние процедуры
)П Выполнение
/U программ PL/SQL
А Заглянем внутрь
А Управление зависимостями
А Как PL/SQL использует память сервера Oracle
А Выполнение серверного кода PL/SQL
А Клиентский код PL/SQL
А Модели разрешений
А Аппаратное обеспечение для PL/SQL: больше — лучше?
А Что еще вам нужно знать
Вспомним, что в главе 2 рассказывалось о множестве программных сред, из которых можно вызывать программы PL/SQL. Однако исполняющее ядро PL/SQL имеется только внутри сервера базы данных Oracle и в средствах Oracle Developer, таких как Oracle Forms Builder и Oracle Reports Builder, выполняемых на клиентской машине. Поэтому разработчик приложения должен принять ряд конструкторских решений, ответив на следующие вопросы.
О Где следует разместить код приложения — на сервере, на клиенте или и там и там?
О Что лучше использовать — анонимный блок, процедуру верхнего уровня или пакет?
О Где выполнять компиляцию серверного кода — на сервере или на клиентской машине?~
О Какие из клиентских программ лучше хранить не в иротрамме Oracle Forms, а в библиотеках PL/SQL?
Кроме того, разработчик должен решить, следует ли учитывать при выполнении программы привилегии ее владельца или пользователя.
Даже опытный программист не сразу сможет дать ответы на все перечисленные вопросы- И здесь ему пригодится понимание принципов функционирования PL/SQL. Задача этой главы - помочь вам изучить архитектуру приложений PL/SQL и сделать все возможное для у совершенств ования их структуры и повышения производительности.
Основной акцент в настоящей главе делается на тех аспектах PL/SQL, которые интересуют разработчика приложений. Но и те вопроод, которыми занимается администратор базы данных, вам, думаем, тоже будут небезынтересны. Например, как сервер выделяет память выполняющимся программам, зависит от того, в каком режиме, разделяемого или выделенного сервера, работает Oracle. Это может оказать существенное влияние на производительность приложения, а потому понимание подобного рода вопросов для разработчика приложений не менее важно, чем для администратора базы данных.
Заглянем внутрь
О PL/SQL обычно говорят как об интерпретируемом языке. Однако в книгах, подобных этой, упоминается компилятор PL/SQL. Так с каким же языком мы имеем дело: интерпретируемым или компилируемым? Истина, как это часто бывает, лежит где-то посередине. Сначала исходный код обрабатывается компилятором, который генерирует машинно-независимый байт-код. Затем виртуальная машина PL/SQL, входящая в состав сервера Oracle, во время выполнения интерпретирует этот байт-код. И, что еще более интересно, Огас1е9г может по вашему указанию преобразовать программу на языке PL/SQL в код С, откомпи/сировать ее в совместно используемую библиотеку с помощью компилятора конкретной машины, которая будет динамически загружать данную версию во время выполнения.
Концепции PL/SQL
Начнем мы с некоторых важных терминов и концепций, относящихся к обработке исходного кода PL/SQL в Oracle.
О Компилятор PL/SQL — это компонент Oracle, который выполняет проверку синтаксиса кода и синтаксический анализ, проверяет допустимость и семантику имен (например, соответствие вызова хранимой программы ее сигнатуре) и генерирует два варианта откомпилированного двоичного кода.
О DIANA — промежуточный вариант кода PL/SQL, генерируемый компилятором с целью выполнения синтаксического и семантического анализа. Это первый из двух основных результатов работы компилятора. Код DIANA включает представление спецификации вызова хранимого объекта, то есть информацию из заголовка программы, и в частности имена параметров, их последовательность и типы данных. Код DIANA генерируется даже для таблиц, представлений и последовательностей. Аббревиатура DIANA расшифровывается как Distributed Intermediate Annotated Notation for Ada (распределенная промежуточная аннотированная нотация для Ada), хотя DIANA-представление Oracle отличается от варианта для Ada.
О Машинно-зависимый псевдокод (байт-код) — исполняемый вариант откомпилированной программы PL/SQL; второй из двух основных результатов работы компилятора, иногда называемый в документации Oracle т~кодом или P-кодом, Этот код передается исполняемому ядру PL/SQL. Полезно знать, что
байт-код очень похож на объектный файл, который, как вы помните, имеет расширение .о.
Вам может встретиться (например, в сценарии создания базы данных sql.bsq) упоминание и о третьем вариаяте откомпилированного кода PL/SQL, называемом переносимым p-кодом, но Oracle такой код уже не генерирует. Однако про- страяство для хранения переносимого p-кода по-прежнему используется в процессе работы с символической информацией, генерируемой компилятором PL/SQL при обработке программы с параметром DEBUG.
Вы, вероятно, уже знаете, что компилятор PL/SQL не запускается программистом, как компилятор С или даже Java. Система Oracle вызывает его сама, когда это необходимо. Фактически начинающий программист PL/SQL может даже не знать о его существовании! Каждый раз, когда вы создаете программу PL/SQL с помощью инструкции CREATE PROCEDURE, Oracle запускает для нее компилятор. Ниже мы расскажем о том, каким образом происходит в Oracle перекомпиляция хранимого кода после каждого изменения объектов, от которых он зависит.
Хотя Oracle и «скрывает» наличие компилятора, хороший программист всегда знает, что происходит внутри системы. С процессом выполнения откомпилированного кода PL/SQL связаны два основных понятия.
О Исполняющее ядро PL/SQ (виртуальная машина PL/SQL). Так называется компонент Oracle, выполняющий байт-код программ PL/SQL, который при необходимости вызывает серверное ядро SQL и возвращает результаты вызывающей среде. В таких клиентских приложениях, как Oracle Forms, исполняющее ядро обычно открывает сеанс подключения к удаленной базе данных и взаимодействует с ядром SQL через сетевой протокол.
Q Сеанс (Oracle). Относительно сервера PL/SQL-сеансом называются процесс и пространство памяти, связанные с аутентифицироваяным пользователем че^ рез сеть или межпроцессное соединение. Каждый сеанс имеет собственную область памяти, где хранятся данные выполняющейся программы. Хотя многие полагают, что сеанс начинается в момент входа пользователя в систему, а заканчивается при выходе из нее, существуют и рекурсивные сеансы, для которых аутентификация не выполняется — Oracle использует их как временное ере* ство изменения идентификационных данных пользователя. Например, в рекурсивном сеансе Oracle всегда выполняет программы с правами создателя.
Рассмотрим несколько способов реализации простейшей программы из популярной клиентской среды SQL*Plus. Это прекрасный пример приложения, ориентированного на выполнение в рамках сеанса и предоставляющего доступ к окружению PL/SQL, которое входит в состав сервера базы данных. (Об SQL*Plus и его использовании для выполнения кода PL/SQL рассказывается в главе 2.) Конечно, при желании сервер можно вызывать из других приложений, например из клиентских программ Oracle, и даже из таких языков, как Perl, С или Java. Но все происходящее на сервере не зависит от клиентского окружения.
Для выполнения кода PL/SQL из SQL*Plus нужен анонимный блок верхнего уровня. Вы, вероятно, знаете, что команда EXECUTE a SQL*Plus преобразует вызов программы в анонимный блок, но известно ли вам, что SQL-инструкция CALL тоже генерирует упрощенный вариант анонимного блока? По сути дела, прежде
чем в Огас1е9г появилась возможность непосредственного вызова PL/SQL из SQL, ©се такого рода вызовы производились с использованием анонимных блоков.
Рассмотрим простейший анонимный блок:•■
BEGIN
NULL:
END;
Что происходит при его отправке серверу Oracle? Для того чтобы ответить на данный вопрос, обратимся к рис. 20.1.
Проанализируем представленный на этом рисунке процесс.
Пользователь построчно вводит блок, а затем дает PL/SQL команду <начать> (косая черта). Как следует из рисунка, SQL*Plus отправляет весь блок кода, за исключением косой черты, серверу. Передача осуществляется через соединение, установленное для текущего сеанса (например, через Oracle Net).
Компилятор PL/SQL пытается откомпилировать полученный анонимный блок и сгенерировать внутренние структуры данных (в том числе DIANA-код), необходимые для анализа кода и генерирования байт-кода1. На первом этапе компилятор проверяет синтаксис, чтобы убедиться, что программа соответствует грамматике языка. Код рассматриваемого примера не содержит ни одного идентификатора, а только ключевые слова языка. Если компиляция проходит успешно, Oracle помещает байт-код блока в общую область памяти; в противном случае компилятор возвращает сообщения об ошибках сеансу SQL*Plus.
Исполняющее ядро PL/SQL интерпретирует байт-код и возвращает сеансу SQL*Plus код, свидетельствующий об успешном или неудачном завершении сеанса.
Добавим в анонимный блок SQL-запрос и посмотрим, как изменится процесс его выполнения. На рис. 20.2 показаны некоторые компоненты сервера Oracle, которые участвуют в выполнении S QL-инструкций.
Если Oracle уже откомпилировала tblk。* же код данного сеанса, вполне вероятно, что повторная компиляцня не понадобится. Дело в том( что результаты столь дорогостоящей операции, как компиляция, сервер кэширует в памяти и старается использовать повторно.
Рис 20.2. Этапы выполнения анонимного блока, содержащего SQL-инструкцию
В этом примере значения столбца извлекаются из известной вам встроенной таблицы DUAL. Такое незначительное изменение связано с множеством новых концепций.
Убедившись, что код PL/SQL не содержит синтаксических ошибок, компилятор PL/SQL передает SQL-инструкцию синтаксическому анализатору SQL (синтаксический анализ является первой стадией компиляции). Анализатор проверяет синтаксис инструкции, после чего преобразует разрешенные имена в соответствующие адреса, (разрешение имен). На этой стадии он определяет, что представляет собой каждый идентификатор (не ключевое слово). В нашем примере SQL” инструкция содержит идентификаторы DUAL, dummy и str. Анализатор выяснит, что DUAL - это имя таблицы, a dummy — ее столбец. Что касается идентификатора str, то он будет возвращен компилятору PL/SQL для проверки разрешения имен.
Кроме того, анализатор SQL, проверин разрешения, должен убедиться, что исходный сеанс имеет полномочия выполнять нызываемую SQL-операцию.
ПРИМЕЧАНИЕ—
Б Oracle9i для языков PL7SQL и SQL используется общий анализатор SQL. В ранних версиях РСУБД Y PL/SQL бып собственный анализатор SQL, но это привело к некоторым разногласиям между SQL, выполнявшинся интерпретатором команд; и SQL, вызываемым из программ PL/SQL, Например, в Grade 8.1.7 компилятор SQL в PL/SQL не знал。новой функции NVL2. Поэтому оператор SELECT NVL2(NULL, 1,1) FROM DUAL; можно было выполнить из командной строки, но а PL/SQL он выдавал ошибку «PLS-00201: identifier WL2' must be declared».
Хотелось бы напомнить, что исполняемый байт-код PL/SQL содержит не только двоичный код программной логики, но и все статические S Q.L-инструкции в текстовой форме. Обычно PL/SQL слегка модифицирует SQL-код, удаляя предложения INTO, подставляет вместо переменных привязки локальные переменные программы и преобразует первое ключевое слово SQL-инструкции (SELECT, UPDATE и т. п.) в символы верхнего регистра. Поэтому если, к примеру, myvar является локальной переменной программы, PL/SQL преобразует инструкцию
select dunmy Into str from dual where x * m^var
в инструкцию
select dumny from dual where x - :bl
Но возвратимся к нашему примеру. После всех проверок на этапе компиляции Oracle переводит анонимный блок на этап выполнения. Компилятор еще раз проверяет синтаксис, разрешение имен, семантику и привилегии, после чего гене~ рируег наиболее эффективный, с его точки зрения, план выполнения инструкции —в нашем примере это сделать совсем не сложно. Готовый план он направляет исполняющему ядру SQL, которое производит выборку строк и передает результаты обратно PL/S QL.
А что произойдет, если анонимный блок вызовет хранимую программу? Подобная ситуация проиллюстрирована на рис. 20.3. Для упрощения картины SQL- икструкция удалена из блока.
На этот раз компилятору нужно разрешить внешнюю ссылку foot чтобы выяснить, что она указывает на программу, которую разрешено выполнять данному пользователю. Кроме того, ему требуется DIANA-представление процедуры foo, позволяющее определить, что анонимный блок вызывает ее правильно. Обратите внимание на одно из преимуществ хранимого кода: поскольку и DIANA-иред- ставление и байт-код уже хранятся в словаре данных, Oracle не приходится тратить время на перекомпиляцию процедуры. И более того, как только код прочитан с диска, Oracle записывает его в область памяти, называемую библиотпечним кэшем, устраняя тем самым необходимость его повторного считывания с диска при последующих вызовах.
После первоначальной компиляции (если зависимости, требующие автоматической перекомпиляции, не изменяются) для выполнения программы Oracle требуется только ее байт-код. Иными словами, если программа foo вызовет программу bar, Oracle не нужно считывать DIANA-представление bar, благодаря чему экономится время на операциях ввода-вывода и пространство памяти в библиотечном кэше.
Рис. 20.3. Выполнение программы, вызывающей хранимую процедуру
, Предположим, что хранимая процедура включает длинную инструкцию SELECT. Поскольку перед записью программы PL/SQL в словарь данных Oracle ее компилирует, можно было бы предположить, что будет откомпилирована и входящая в состав программы SQL-инструкция. Но не тут то было. Oracle поступает с SQL-инструкциями в хранимых процедурах точно так же, как и в анонимных блоках, то есть сохраняет их в текстовой форме внутри байт-кода программы и компилирует во время выполнения.
Однако во время выполнения программы PL/SQL делает все возможное для того, чтобы избежать мягкого синтаксического анализа строки запроса (этим термином принято называть осуществляемое Oracle преобразование строки запроса в откомпилированную форму в памяти). При выполнении SQL-инструкции из такого приложения, как SQ,L*Plus, мягкого синтаксического анализа не избежать. Поэтому для ускорения анализа SQL-залроса достаточно поместить ее в блок PL/SQL. В некоторых других приложениях мягкий анализ тоже можно не производить, но даже низкоуровневый С-интерфейс, известный как Oracle Call Interface (ОС1)Т имеет коммуникационные издержки (сетевые или IPC), не свойственные хранимому коду PL/SQL.
В завершение давайте рассмотрим встроенную компиляцию — процесс, который правильнее было бы назвать встроенным выполнением. Эта функция Oracle9i позволяет перевести серверный код PL/SQL на С, выполнить его компиляцию и связывание и получить совместно используемый объектный файл. Такой код обычно обрабатывается быстрее, чем обычный откомпилированный PL/SQL-код, если только программа большую часть времени не занимается выполнением SQL- инструкций.
При наличии процедуры с именем Ьа г, откомпилированной таким образом, Oracle хранит ее новую версию в операционной системе (рис. 20.4).
В данном случае компилятор генерирует обычный код DIANA, но без байт-коДа. Вместо него в словарь данных и структуры библиотечного кэша залисывают- ся имя и информация о местоположении объектного файла. Вызываемая программа, хранящаяся в файле совместно используемой библиотеки, не содержит
интерпретируемого байт-кода; для ее динамической загрузки и выполнения Oracle осуществляет вызов на уровне операционной системы.
Рис. 204 Выполнение анонимного блока, вызывающего программу
Существует много других способов генерации хранимого кода и его вызова из PL/SQL. Во-первых, это можно делать с помощью хранимых процедур Java. Стандартная установка сервера Oracle включает не только виртуальную машину PL/SQL, ио и виртуальную машину Java. Вам же достаточно написать специфик кацию вызова PL/SQL, логика которого будет реализована в виде статического Java-класса. Во-вторых, можно воспользоваться внешними процедурами. Исполняемую часть подпрограммы PL/SQL можно реализовать в виде пользовательского кода С, и Oracle будет выполнять этот код как независимый от сервера Oracle процесс в выделенном ему пространстве памяти. Мы расскажем об обоих подходах несколько позже, в специально посвященных им главах.
Хранение PL/SQL-кода на сервере
Теперь, когда вам известно, какую роль играют различные компоненты в процессах компиляции и выполнения кода PL/SQL, давайте разберемся, как различные варианты программного кода хранятся на диске. Возможные места его хранения на сервере базы данных Oracle перечислены в табл. 20.1,
Таблица 20,1. Хранение серверного кода PL/SQL и метаданных
Месторасположение (все таблицы лринадлемат SYS)
Имя представления, обычно используемое разработчиками приложений
Использующиеся объекты PL/SQL
Наиболее полезное содержимое
ОВ1$
USER„OBJECTS
Все, кроме анонимных блоков
Имена, объектные типы,время выполнения последней DDL-инструкции, состояние компиляции (VALID или INVALID)
SOURCE^
U5ER_S0URCE
Все, кроме триггеров и анонимных блоков
Исходный код
TRIGGER$
USER_TRIGGERS
Триггеры
Исходный код, описание события, к которому присоединен триггер
ERROR$
USER_ERRORS
Все, кроме анонимных блоков
Последняя ошибка компиляции для любой хранимой программы PL/SQL (включая триггеры)
DEPENDENCE
USER_DEPENDENCIES
Все, кроме анонимных блоков
Иерархия объектных зависимостей
SEmNGS$
USER^STORED .SETHNGS
Все, кроме анонимных блоков
Флаги компилятора PL/SQL
IDL_UB1$ IDL_CHAR$
IDL_UB2$
IDL„SB4$
USER_OBJECT_SIZE
Все, кроме анонимных блоков
Внутреннее представление DIANA- и байт-кода, а также отладочных данных; используется компилятором и ИСПОЛНЯЮЩИМ ядром
Каталог операционной системы, заданный в параметре базы данных PLSQL—NATIVJ UBRARY-DIR
Откомпилированные встроенным компилятором
Совместно используемые объектные файлы, полученные путем компиляции программ PL/SQL через С
С помощью указанных выше представлений можно, в частности, выполнить следующие действия:
Q определить, какие объекты находятся в состоянии INVALID (US£R_OBJECTS);
Q установить, какие программы откомпилированы путем встроенной компиляции (USER_STORED_ SETTINGS);
Q выяснить, какие локальные объекты стали недействительными после изменения в конкретной программе PL/SQL (USER_DEPENDENCIES);
О восстановить исходный код программы, если отсутствует сценарий, с помощью которого она была создана (USER SOURCE, USER_TRIGGERS);
О определить размер хранимых программ (USER_OBJECT_SIZE).
На последней возможности стоит остановиться подробнее. Обратившись к представлению USER_OBJECT_SIZE, можно узнать размер каждой части хранимой программы. Столбцы этого представления содержат информацию о количестве байтов, занимаемых на диске определенным кодом или данными определенного типа: О столбец 50URCE_SIZE — исходный код;
Q столбец PARSED_SIZE — DIANA-код;
О столбец CODE_SIZE 一 байт-код, а также информация полученная в процессе отладки (сохраняется только в том случае, если программа откомпилирована с указанием параметра DEBUG);
О столбец ERROR SIZE - текст сообщений об ошибках (если ошибки отсутствуют, содержимое столбца равно 0).
Правда, эта информация не так полезна, как может показаться на первый взгляд. Например, когда Oracle помещает в таблицы словаря данных DIANA-код, она записывает его не совсем в той форме, в какой он находится в памяти. На диске он записан в виде сжатого линейного списка, а в памяти разворачивается в древообразную структуру. Рамо или поздно вам потребуется выяснить, на что именно память израсходуется во время выполнения программ; некоторые сведения по этой важной теме вы найдете ниже, в разделе «Как PL/SQL использует память сервера Oracle》. Однако информация, возвращаемая представлением USER_0BJECT_5IZE, подтверждает один важный факт: Oracle не хранит DIANA-код для тела пакета. Значение столбца PARSED_SIZE для тела пакета всегда равно нулю.
ПРИМЕЧАНиЕ——
Поскольку Grade не хранит DIANA-код для тела пакета, помещение PL/SQL-программ в пакеты в виде функций, процедур или типов данных верхнего уровня слособствует уменьшению общей нагрузки на сервер.
А нужен ли Oracle вообще DIANA-код тела пакета? Да, нужен, но только на этапе компиляции, поскольку компилятор использует его для генерации байт-кода, а также для проверки соответствия сигнатур, имеющихся в теле пакета, сигнатурам в его спецификации. Поскольку тело пакета хранимым объектам теперь не нужно, Oracle удаляет его DIANA-код. А если впоследствии тело пакета потребуется перекомпилировать (например, из-за изменений в объектах, от которых оно зависит), компилятор всегда сможет заново сгенерировать DIANA-код, повторно проверить типы данных, еще раз сгенерировать байт-код и удалить DIANA-код. Даже если бы Oracle сохраняла старый DIANA-код, для перекомпиляции он был бы бесполезным,
Мк, конечно же, не утверждаем, что все без разбора компоненты приложения должны помещаться в пакеты. Такие преимущества, как простота разработки, могут быть важнее стремления максимально повысить производительность и снизить нагрузку на сервер базы данных. В качестве примера можно упомянуть о компоненте Oracle PL/SQL Server Pages (PSP), который генерирует процедуры PL/SQL
верхнего уровня на основе HTML-файлов. Но в целом чем больше объем кода приложения, тем существеннее преимущества от помещения его в пакеты.
Слишком большой DIANA-код
При компиляции очень большой программы PL/SQL от сервера можно получить сообщение об ошибке PLS-00123: program too large, а от PL/SQL с клиентской стороны —сообщение CDI-11005; (SQL execution error). Это означает, что при попытке создать структуру DIANA-кода компилятор вышел за пределы максимально допустимого количества узлов дерева. В таком случае проще всего разбить программу не несколько подпрограмм. Но поступать подобным образом не всегда целесообразно.
Трудно предсказать, какая структура потребуется для реализации программы, поскольку не каждый ее узел соответствует какой-либо единице исходного кода, например, токену или строке кода. Верхние границы количества узлов для серверов разных версий и программ PL/SQL различных типов приведены в табл. 20.2. Хотя крайний справа столбец и дает приблизительное представление о размере исходного кода, не воспринимайте указанные в нем значения буквально. Эти оценки основаны на «типичном* коде (в среднем по четыре байта на один узел DIANA-кода), но ваш код может оказаться более сложным.
Таблица 20.2. Верхние границы размеров программ PL/SQL
Тил кода PL/SQL
Версия сервера
Максимальный размер дерева DIANA-кода (количество узлов)
Оценка максимального размера исходного кода н байтах
Тело пакета или типа
7.3
2й (16 384)
64 К
данных, отдельная
8.0.x и выше
215 (32768)
128 К
функция или процедура
8.1.5 и выше
2" (67 108 854)
256 M
Сигнатура (заголовок)
7.3
2м
64 К
отдельной функции или
8.0.x и выше
215
128 K
процедуры
Спецификация пакета или
7.3
2й (теоретически)
64 К (теоретически)
типа данных, анонимный
8.1.6. и ниже
2* (теоретически),
128 К (теоретически).
блок
8.1.7 и выше
от 館з до 214(на практике)
от 32 К до 64 К
24
(на практике)
128 К
Из таблицы видно, что компания Oracle постоянно работает над увеличением предельного размера программ. В последних версиях выход за эти пределы уже маловероятен.
У компилятора PL/SQL имеются и другие ограничения, касающиеся степени сложности кода, в частности максимального количества уровней вложенности блоков (255) и максимального количества передаваемых процедуре или функции параметров (65 536). Некоторые из них могут стать причиной возникновения
проблем. Полный список таких ограничений приведен в приложении к официальной документации PLQa User's Guide and Reference.
Управление зависимостями
Еще одной важной фазой компиляции и выполнения PL/SQL-программы является проверка ее зависимостей. Зависимость в PL/SQL — вид связи между программой и некоторым объектом Oracle вне этой программы. Серверные программы PL/SQL могут зависеть от таблиц, представлений, типов данных, процедур, функций, последовательностей и спецификаций пакетов, но не от их тела пакетов или тела типов данных. Клиентские программы PL/SQL могут иметь дополнительные зависимости от таких существующих лишь в клиентских модулях элементов, как поля формы.
Основная цель проверки зависимостей в PL/SQL - не допустить выполнения программы, если хоть один из объектов, от которых она зависит, изменился со времени ее последней компиляции.
Управление зависимостями производится автоматически: система Oracle сама отслеживает программные компоненты и осуществляет их перекомпиляцию, когда это необходимо. Тем не менее некоторая ответственность за синхронизацию кода лежит на программистах, и в следующих разделах рассказывается, как, когда и для чего им нужно вмешиваться в данный процесс.
Зависимости в серверном PL/SQL
Если вы работаете с серверными программами PL/SQL, для получения информации о зависимостях можно обращаться к словарю данных. Ниже показано, как получить из него сведения о происходящем в базе данных. Предположим, что на сервере имеется пакет bookworm, а в нем функция, извлекающая данные из таблицы books. Если сначала была создана таблица, а затем пакет, в словаре данных должна содержаться следующая информация:
SQL> SELECT object_name, object_type. status
FROM USER_OWECTS ~
WHERE object_name - 'BOOKWORM1;
OBJECT_NAMEOBJECT一TYPESTATUS
BOOKWORMPACKAGEVALID
BOOKWOW1PACKAGE 800YVALID
Это означает, что существуют два объекта с именем BOOKWORM: спецификация пакета и его тело. И оба они находятся в состоянии VALID.
При компиляции пакета Oracle с помощью DIANA-кода формирует список Других объектов, необходимых для успешной компиляции пакета BOOKWORM. Все зависимости объектов можно исследовать, выпалыив дорогостоящий (то есть медленный) запрос к представлению из словаря данных USER_DEPENDENCIES:
SOL> SELECT name, type. referencect_nanie, referenced type
FROM USER_DEPENDENCIE5 -“
3 WHERE па・е = 'BOOKWORM';
NAME
TYPE
REFERNCED-NAME
REFERNCEDJYPE
BOOKWORM
PACKAGE
STANDARD
PACKAGE
BOOKWORM
PACKAGE
BODY
STANDARD
PACKAGE
BOOKWORM
PACKAGE
BODY
BOOKS
TABLE
BOOKWORM
PACKAGE
BODY
BOOKWORM
PACKAGE
На рис. 20.5 эта же информация представлена в виде ориентированного графа, в котором стрелки обозначают отношения типа «зависит от».
Рис, 20.5. Граф зависимостей пакета bookworm
Иными словами» на рис. 20.5 показано, что спецификация и тело пакета bookworm зависят от встроенного пакета с именем STANDARD (см. врезку «Пакет STANDARD»), а тело пакета bookworm — от его же спецификации и от таблицы books.
Для отслеживания зависимостей Oracle записывает в словарь данных тело и спецификацию пакета как два разных элемента. Тело любого пакета зависит от его спецификации, но спецификация никогда не зависит от тела. От тела пакета не зависит ни один объект — собственно говоря, его может и вовсе не быть.
Если вам приходилось достаточно долго заниматься сопровождением программного обеспечения, вы знаете, что при подобном анализе учитываются не столько зависимости, сколько ссылки одних объектов на другие. Предположим, мне нужно изменить структуру таблицы books. Прежде всего необходимо выяснить, ыа какие из объектов это может повлиять:
SQL?SELECT паше. type.
FROM USERJEPENDENCIES
WHERE referenced_name - 'BOOKS'
AND referenced__type - 'TABLE':
TYPE
ADD_BOOK
procedure
tes[book
PACKAGE BODY
BOOK
PACKAGE BODY
BOOKWORM
PACKAGE BODY
FORMSTEST
PACKAGE
Как видите, в приведенной выше схеме помимо пакета bookworm имеются другие программы, о которых ранее не упоминалось. Но Oracle ничего не забывает.
И хотя Oracle очень тщательно контролирует зависимости, все их предусмотреть невозможно. РСУБД фиксирует локальные хранимые объекты, ссылки на которые оформлены в виде статических вызовов. Но существует множество способов создания программ, которых не будет в представлении USER_DEPENDENCIES. Речь идет о внешних программах с интегрированным кодом SQL или PL/SQ.L, удаленных хранимых процедурах, клиентских программах, вызывающих локальные хранимые объекты, и, наконец, о локальных программах, в которых используется динамический SQL.
Итак, попробуем изменить структуру таблицы books, добавив в нее один новый столбец:
ALTER TABLE books ADD popularityJndex NUMBER;
Oracle сразу же автоматически пометит все ссылки на таблицу books как недействительные (INVALID). При любом изменении определения объекта, даже при его перекомпиляции без внесения изменений, все ссылки на объект также будут помечены как недействительные (см. ниже врезку *Перевод объектов в недействительное состояние»). Но на практике этот процесс еще более сложный. Если вы являетесь владельцем программы, вьшолняющей заданную DML-инструкцию, которая содержит ссылку на таблицу другой схемы, то после лишения вас привилегии на выполнение указанной инструкции, вся программа будет помечена как неработоспособная.
Вот что можно получить после изменения структуры таблицы:
SQL> SELECT object_name. object type, status
2 FROM USER_OBJECTS 一
3 WHERE object_iiame = 'INVALID';
OSJECT_NAME
OBJECTjrPESTATUS
ADD_800K BOOK BOOKWORM FORMSTEST FORMSTEST TESTBOOK
PROCEDUREINVALID
PACKAGE BOOYINVALID
PACKAGE BODYINVALID
PACKAGEINVALID
PACKAGE BODYINVALID
PACKAGE 80DYINVALID
Данный пример иллюстрирует преимущества организации пакетов в виде двух частей: тело пакета оказалось в этом списке помечено как INVALID, а его спецификация в полном порядке. И это замечательно. Ведь если бы спецификация тоже была помечена как недействительная, таковыми стали бы и все зависящие от нее объекты.
1РИМЕЧАНИЕ-
Использование пакетов позволяет разорвать цепь зависимостей и избавляет от необходимое™ осу ществления перекомпиляций.
ПАКЕТ STANDARD
В любой стандартной инсталляции Oracle имеется встроенный пакет STANDARD, который содержит множество базовых элементов языка PL/SQL, в том числе:
О функции, например INSTR и LOWER;
О операторы сравнения, такие как NOT, = (равно) и > (больше);
О предопределенные исключения, в частности DUP_VAL_ON_INDEX и VALUE_ERROR;~'
О подтипы, например STRING и INTEGER.
Если вы хотите ознакомиться с исходным кодом пакета, откройте файл standard.sql, обычно находящийся в каталоге $ORACLE_HOME/rdbms/admln.
Спецификация пакета STANDARD представляет собой корневой узел графа зависимостей PL/SQL, то есть она не зависит от других программ PL/SQL (но большая часть программ PL/SQL зависит от нее). Если вы перекомпилируете данную спецификацию, Oracle пометит практически все ссылки на программы PL/SQL как недействительные.
В состав клиентской среды PL/SQL входит и версия пакета STANDARD, основные компоненты которой такие же, как у серверной версии. Кроме того, имеются расширения этого пакета, включающие программы, специфические для данного приложения, в том числе CALL~FQRM・
Еще одним средством просмотра программных зависимостей является процедура Oracle DEPTREE_FILL, используемая в сочетании с представлениями DEPTREE и IDEPTREE. Вызовите эту процедуру, предположим, с такими параметрами:
SQL> EXEC DEPTREEJILLCTABLE', 'SCOTT'. 'BOOKS')
После того как вы выполните запрос к представлению IDEPTREE, будет получен следующий листинг:
SQL> SELECT * FROM IDEPTBEE:
DEPE^FNCIES
TABLE 5С0П. BOOKS
PROCEDURE SCOn.ADD_BOOK
PROCEWRE BODY SCOTTJOOK
PACKAGE BODY SCOH.TEST^BOOK
PACKAGE BODY scon.BOOKWORM
PACKAGE SCOH.FORMSTEST
PACKAGE BODY SCOTT.FDRMSTEST
Листинг содержит результаты выполнения рекурсивного запроса, просматривающего все ссылки объекта. Чтобы их получить, запустите сценарий utldtree.sql (из каталога rdbms/admin) и создайте процедуру и представления в своей схеме. Но вы можете эмулировать его также с помощью другого запроса:
SELECT RPAD (' 3*(LEVEL-D) || name || ' Г || type || ') '
FROM user一dependencies
CONNECT BY PRIOR RTRIMfname |[ type)-
RTRIM(referenced_name j| referenced_type)
START WITH referenced_name 口 'name'柘 referenced_type - 'type';
Теперь, когда вьг знаете, как сервер отслеживает связи между объектами, давайте посмотрим, что Oracle делает с такого рода информацией.
Восстановление работоспособности
Программа может стать неработоспособной либо в результате изменения объектов, на которые она ссылается, либо в результате неудачной компиляции. В любом случае такую программу нельзя будет выполнить, пока успешная перекомпиляция не восстановит ее. Это можно сделать тремя способами:
О вручную, выполнив перекомпиляцию пакета с помощью команды ALTER;
О с помощью сценария, то есть программы, которая ищет недействительные пакеты и вызывает для них команду ALTER;
О автоматически, положившись на встроенные правила перекомпиляции Oracle.
Перекомпиляция вручную
Этот метод прост, но утомителен. Предположим, вы заглянули в словарь данных и выяснили, что в перекомпиляции нуждаются три хранимые программы:
ALTER PACKAGE bookworm COMPILE BODY;
ALTER PACKAGE book COMPILE BODY:
ALTER PROCEDURE add_book COMPILE:
Однако не всегда простой перекомпиляции может быть достаточно. Если изменилась сигнатура вызываемой программы,, сначала придется отредактировать вызывающий код!,
Перекомпиляция с помощью сценария
Существует несколько сценариев, облегчающих выполнение этой задачи. Собственный сценарий Oracle, предназначенный для данной цели, называется utlrp.sql. Он находится нее в том же подкаталоге rdbms/admfn и прекрасно работает, но для его запуска требуется иметь привилегии администратора базы данных. В общем случае сценарии этого типа недостаточно интеллектуальны, чтобы при перекомпиляции учитывать зависимость между объектами. Они просто проверяют список неработоспособных программ и поочередно их компилируют; затем снова проверяют список и повторяют данную операцию до тех пор, пока все программы не окажутся работоспособными или пока не будут перекомпилированы все необходимые программы. Причем заголовки пакетов они компилируют до компиляции тела пакета.
Если вам нужно перекомпилировать большое количество программ и ресурсы машины (процессор, память, диск) позволяют это сделать, попробуйте воспользоваться пакетом Oracle UTL_RECOMP (utlrcmp.sql), с помощью которого можно выполнять перекомпиляцию программ параллельно, помещая соответствующие запросы в очередь заданий Oracle. Однако даже сама система предупреждает, что пакет не в состоянии в значительной степени ускорить перекомпиляцию из-за существующей конкуренции при записи в системные таблицы.
ПРИМЕЧАНИЕ
Соломон Якобсон (Solomon Yakobson) предложил утилиту перекомпиляции, которая обрабатывает объекты с учетом существующих между ними зависимостей. Этот сценарий под заголовком «А тоге sophisticated Recompile Utility» содержится на web-узле Quest Pipelines, в архиве PL/SQL, расположенном по адресу http://www.quest-pipelines.com/Pfpellnes/PLSQL/archlves.CQm. Вы найдете его также в 力日йле recompile.sql на узле издательства。仅日 11у.
ПЕРЕВОД ОБЪЕКТОВ В НЕРАБОТОСПОСОБНОЕ СОСТОЯНИЕ
Если время создания объекта базы данных меняется, Oracle, как правило, сразу помечает все зависимые от него объекты локальной базы данных как неработоспособные. Однако в версии 8.1.7.3 появилась возможность сообщить РСУБД, что вы хотите пометить зависимые объекты как неработоспособные лишь в том случае, если исходный код объекта действительно изменился.
Однако имейте в виду, что речь идет о недокументированной и неподдерживаемой функции, и пользователям можно ее применять только в том случае, если это им рекомендовали технические службы Oracle. Иными словами, компания не несет ответственности за те негативные последствия, к которым может привести ваша самодеятельность. Данная функция для текущего сеанса задается так:
3QL> ALTER SESSION SET EVENTS
2'10520 ШСЕ NAME CONTEXT FOREVER, LEVEL ID1:
Session altered.
Инсталлируйте новый код и запустите сценарии перекомпиляции, например JORACUE-HOME/rdbms/admin/utlrp.sql, затем отключите указанную функцию:
SQL> ALTER SESSION SET EVENTS
2'10520 TRACE NAME CONTEXT。汗’:
Session altered.
Описанная функция разработана по инициативе Oracle и призвана ускорить процесс внесения исправлений и обновление базы данных. Для других целей она не предназначена.
Автоматическая перекомпиляция
Третьим методом перекомпиляции, согласно стратегии Oracle, является автоматическая п ере компиляция программ. Этот метод проще других, но имеет определенные недостатки. Если вы захотите выполнить одну из программ, реализованных в теле пакета bookworm, Oracle сначала перекомпилирует его.
При эксплуатации в промышленных условиях обычно действуют жесткие правила относительно того, когда можно осуществлять перекомпиляцию программ. Установка новой версии программы PL/SQL может потребовать выполнения множества перекомпиляций, что отразиться на активных программах. Возможно, чтобы найти и перекомпилировать все неработоспособные объекты, вы захотите
выполнить сценарий, подобный utlrp.sql. Администратор базы данных должен по- мочь вам составить график вызова процедуры обновления, включающей запуск сценария перекомпиляции.
Помимо снижения производительности одним из самых больших недостатков автоматической перекомпиляции программ является то, что она может влиять на выполняющиеся в данный момент пакеты. В результате можно получить та^ю серию сообщений:
0RA-04068: existing state of packages has been discarded
DRA-04061: existing state of package "SCOTT.Pl" has been invalidated
ORA-04065: not executed, altered or dropped package 'SCOTT.Pl"
0RA-065O8: PL/SQL: could not find program unit being called
Объясняется это тем, что активный в данный момент пакет стал неработоспособным из-за автоматической перекомпиляции. Более того, поскольку Oracle позволяет совместно использовать один и тот же байт-код в нескольких сеансах, сбой произойдет и во всех остальных сеансах, в которых выполняется данный па- кет. Последствия обычно бывают самими разнообразными.
Q Выполнение программы прерывается сообщением об ошибке 0RA-04068 (если только программы не включают сложные обработчики ошибок).
Q Всем общим и личным переменным пакетов присваиваются значения по умолчанию; если такое значение явно не задано, переменная принимает значение NULL. Сказанное относится ко всем пакетам, выполняемым в данном сеансе, а не только к тому, который подлежит перекомпиляции. Следовательно, если в этот момент производятся вычисления с использованием переменных пакетов, содержимое переменных будет утеряно.
О Пакет DBMS_OUTPUT прекращает работу, но лишь в том случае, если он ранее находился в режиме выполнения. Объясняется это тем, что переключатель пакета устанавливается в состояние по умолчанию off.
Поскольку пакеты, выполняемые в текущем сеансе, возвращаются в исходное состояние, делу поможет простое повторение вызова; иными словами, повторный вызов не повлечет за собой ошибку URA-04068.
Автор книги написал множество сценариев перекомпиляции, и при их проверке столкнулся со странными результатами. В частности, каждые два из десяти запусков одного и того же сценария завершались сообщениями об ошибке. Кроме того, было замечено, что перекомпиляция пакетов с помощью явной инструкции ALTER.. .COMPILE снижает «волновой» эффект; похоже, что вместо возврата в исходное состояние всех пакетов текущего сеанса она переводит в таковое лишь компилируемый пакет.
На основании изложенного можно сделать следующие выводы. При промышленной эксплуатации системы не следует делать ничего, что может повлечь за собой перевод в неработоспособное состояние или перекомпиляцию (автоматическую либо выполняемую вручную) любых хранимых объектов в текущих сеансах. Используйте сценарии перекомпиляции — на этапе разработки их применение не вызывает никаких проблем.
Взаимосвязи в клиентском PL/SQL
В приложении, разрабатываемом на основе Oracle Forms и родственных ей приложений, клиентская программа PL/SQL может зависеть от многих компонентов:
О других клиентских программ PL/SQL;
О серверных объектов, таких как таблицы, представления и программы PL/SQL;
О элементов уровня модуля, в том числе от экранных полей в Oracle Forms;
О так называемых системных переменных, поддерживаемых средой разработки.
Вы можете проанализировать все эти зависимости, просмотрев элементы References под именами всех программ (рис. 20.6).
!0Ubi£Ct NJYigclcr!0Ubi£Ct NJYigclcr1OPU (Package Body)1OPU (Package Body)^- > ■^- > ■i岁 Fohfe'^Ol/SQL LibrariBS 'CBOOKLTB©■Program Urits&И EXC* (Package Spec) в ^>Й EXC* (Package Sndy) ffitfS LOPU* ^Package Spec) в 6更取瓢脇糠礴i岁 Fohfe'^Ol/SQL LibrariBS 'CBOOKLTB©■Program Urits&И EXC* (Package Spec) в ^>Й EXC* (Package Sndy) ffitfS LOPU* ^Package Spec) в 6更取瓢脇糠礴F Suter cqra»sefereficesF Suter cqra»seferefices'I'I-LOPU (Paclage Spec)-ElfC【Package Spec)-PUBLIC.ILS SESSION PARAKKRS (SAHQ〉-LOPU (Paclage Spec)-ElfC【Package Spec)-PUBLIC.ILS SESSION PARAKKRS (SAHQ〉-SYSJLS_SESSI0l PARAKTERS Ctiew) -PUBLIC.DBKSOSFUSCAnOH ТО01Ш £3упот)«) -SYS. DBMS 08FUSCAn0H_TMLKn CPsckage Spec) -PUBLIC血_MU (Synnn)»>-SYS.UTL 3 (Package Spec)-PUBL1C.UTL_SMTP (Synajje)-SVS.UR_SHTp (Package Spec)-Referenced By-SYSJLS_SESSI0l PARAKTERS Ctiew) -PUBLIC.DBKSOSFUSCAnOH ТО01Ш £3упот)«) -SYS. DBMS 08FUSCAn0H_TMLKn CPsckage Spec) -PUBLIC血_MU (Synnn)»>-SYS.UTL 3 (Package Spec)-PUBL1C.UTL_SMTP (Synajje)-SVS.UR_SHTp (Package Spec)-Referenced By-Attached Lbrartes-Attached Lbrartes£ Object Liranes i?單代句 Packages :e®STANDARO Extensions©®FBEAN (Package Spec),0FTREE (Pxk&geSpec)(PackaQe Spec)IJAVA (Package Spec)£ Object Liranes i?單代句 Packages :e®STANDARO Extensions©®FBEAN (Package Spec),0FTREE (Pxk&geSpec)(PackaQe Spec)IJAVA (Package Spec)®^EXEC_5Qt (Package Spec) ^ffiORAJH (Package Spec)®^EXEC_5Qt (Package Spec) ^ffiORAJH (Package Spec)^SORA_PROF (Padujqe Spec)^SORA_PROF (Padujqe Spec)4>ЙВойА_М.5 (Pdctege Spec)4>ЙВойА_М.5 (Pdctege Spec)中 ^TO0<RE5 (Package Spec)中 ^TO0<RE5 (Package Spec)卜RFCLOSE (FILfHANDLE IN OUT RFHANDLE);卜"OPEN (SPEC IN VARCHAR2) RETURN RFHANtXE;卜RFCLOSE (FILfHANDLE IN OUT RFHANDLE);卜"OPEN (SPEC IN VARCHAR2) RETURN RFHANtXE;
!0Ubi£Ct NJYigclcr
!0Ubi£Ct NJYigclcr
1OPU (Package Body)
1OPU (Package Body)
^- > ■
^- > ■
i岁 Fohfe
'^Ol/SQL LibrariBS 'CBOOKLTB
©■Program Urits
&И EXC* (Package Spec) в ^>Й EXC* (Package Sndy) ffitfS LOPU* ^Package Spec) в 6更取瓢脇糠礴
i岁 Fohfe
'^Ol/SQL LibrariBS 'CBOOKLTB
©■Program Urits
&И EXC* (Package Spec) в ^>Й EXC* (Package Sndy) ffitfS LOPU* ^Package Spec) в 6更取瓢脇糠礴
F Suter cqra»seferefices
F Suter cqra»seferefices
'I
'I
-LOPU (Paclage Spec)
-ElfC【Package Spec)
-PUBLIC.ILS SESSION PARAKKRS (SAHQ〉
-LOPU (Paclage Spec)
-ElfC【Package Spec)
-PUBLIC.ILS SESSION PARAKKRS (SAHQ〉
-SYSJLS_SESSI0l PARAKTERS Ctiew) -PUBLIC.DBKSOSFUSCAnOH ТО01Ш £3упот)«) -SYS. DBMS 08FUSCAn0H_TMLKn CPsckage Spec) -PUBLIC血_MU (Synnn)»>
-SYS.UTL 3 (Package Spec)
-PUBL1C.UTL_SMTP (Synajje)
-SVS.UR_SHTp (Package Spec)
-Referenced By
-SYSJLS_SESSI0l PARAKTERS Ctiew) -PUBLIC.DBKSOSFUSCAnOH ТО01Ш £3упот)«) -SYS. DBMS 08FUSCAn0H_TMLKn CPsckage Spec) -PUBLIC血_MU (Synnn)»>
-SYS.UTL 3 (Package Spec)
-PUBL1C.UTL_SMTP (Synajje)
-SVS.UR_SHTp (Package Spec)
-Referenced By
-Attached Lbrartes
-Attached Lbrartes
£ Object Liranes i?單代句 Packages :e®STANDARO Extensions
©®FBEAN (Package Spec),0FTREE (Pxk&geSpec)
(PackaQe Spec)I
JAVA (Package Spec)
£ Object Liranes i?單代句 Packages :e®STANDARO Extensions
©®FBEAN (Package Spec),0FTREE (Pxk&geSpec)
(PackaQe Spec)I
JAVA (Package Spec)
®^EXEC_5Qt (Package Spec) ^ffiORAJH (Package Spec)
®^EXEC_5Qt (Package Spec) ^ffiORAJH (Package Spec)
^SORA_PROF (Padujqe Spec)
^SORA_PROF (Padujqe Spec)
4>ЙВойА_М.5 (Pdctege Spec)
4>ЙВойА_М.5 (Pdctege Spec)
中 ^TO0<RE5 (Package Spec)
中 ^TO0<RE5 (Package Spec)
卜RFCLOSE (FILfHANDLE IN OUT RFHANDLE);
卜"OPEN (SPEC IN VARCHAR2) RETURN RFHANtXE;
卜RFCLOSE (FILfHANDLE IN OUT RFHANDLE);
卜"OPEN (SPEC IN VARCHAR2) RETURN RFHANtXE;
Рис. 20.6. Информация элемента References из пакетаклиентской библиотеки PL/SQL
Хотя в окне утилиты Object Navigator хорошо видны все внешние ссылки клиентских программ, зависимости клиентских триггеров в нем не отображаются. Более того, в списке зависимостей Referenced By показаны только имена программ текущего модуля.
Предположим, мы создали клиентскую библиотеку PL/SQL, которой пять разработчиков пользуются в нескольких модулях Oracle Forms. Открыв библиотеку в среде разработки и щелкнув на элементе Referenced By, мы увидим список, подобный представленному на рис. 20.7.
j 歸吨 K“NaYi0atcrj 歸吨 K“NaYi0atcr^eTgrgnced By^eTgrgnced By2 ,2 ,「Forms j-Menus У PL/SQL libraries ]$ ⑥ BOOXLIB вЯподг«п Ub<s 哈亩 EK (PeckaBSpec) & &ЙЕХС (Package Body) e 5<3lOPu (Рккма 5pec)@ ©■SuDprograres ^■RFferpnces 守Referenced » ij r LOPU iPactagesodyJei! - EXC (Package Bcdy)ej 5-E3LOPU (Package BadvJti I ^--Attached Lbraries 色 Object LArafies § SdlNn Packaoes ^OSTANDARD Extensions 虫餌 BEAM (Peckago Spec) 蝇FTREENackw 5pec) 处IWEB (Package Spec) JfflkiRAJAVA (Package SpM) 电*CJ5QL (Package Spec) 》^ORA尸j (Package Spec) рЙ0ОЯА_РЯО (Package Spec) »ЙЗ<ЖА_М.5 (Package 5pec) i Ф ^TOOL_RE5 (Package Sp«) IHRFCLO5E (FILEHANDIE W OUT ftFHANDLE);j pRFOPEN (SPEC IN VARCHA«2) RETURN RFHMOLE;UrFREAD (RESID in VARCHAMj RE5TYPE 例 VARCHAR2) SETlAN YARCHflRZ;LrFHEAD (FILE IN RFHANDlf, RESID IN VARCHAR2, RESTYPE 例 VARCHAR2) RETURN VARCHARZ; f >fflTOOL„ENV (Package Spec)JfflTEXTJO (Package Spec).S ^TOOLJRR (Package Spec)'T CLEAR;卜CODE (UN PISJMTEGER) RETURN NUMBER:{-RFOPEN (SPECIN VARCHAR2) RETURN RF WE;「Forms j-Menus У PL/SQL libraries ]$ ⑥ BOOXLIB вЯподг«п Ub<s 哈亩 EK (PeckaBSpec) & &ЙЕХС (Package Body) e 5<3lOPu (Рккма 5pec)@ ©■SuDprograres ^■RFferpnces 守Referenced » ij r LOPU iPactagesodyJei! - EXC (Package Bcdy)ej 5-E3LOPU (Package BadvJti I ^--Attached Lbraries 色 Object LArafies § SdlNn Packaoes ^OSTANDARD Extensions 虫餌 BEAM (Peckago Spec) 蝇FTREENackw 5pec) 处IWEB (Package Spec) JfflkiRAJAVA (Package SpM) 电*CJ5QL (Package Spec) 》^ORA尸j (Package Spec) рЙ0ОЯА_РЯО (Package Spec) »ЙЗ<ЖА_М.5 (Package 5pec) i Ф ^TOOL_RE5 (Package Sp«) IHRFCLO5E (FILEHANDIE W OUT ftFHANDLE);j pRFOPEN (SPEC IN VARCHA«2) RETURN RFHMOLE;UrFREAD (RESID in VARCHAMj RE5TYPE 例 VARCHAR2) SETlAN YARCHflRZ;LrFHEAD (FILE IN RFHANDlf, RESID IN VARCHAR2, RESTYPE 例 VARCHAR2) RETURN VARCHARZ; f >fflTOOL„ENV (Package Spec)JfflTEXTJO (Package Spec).S ^TOOLJRR (Package Spec)'T CLEAR;卜CODE (UN PISJMTEGER) RETURN NUMBER:{-RFOPEN (SPECIN VARCHAR2) RETURN RF WE;
j 歸吨 K“NaYi0atcr
j 歸吨 K“NaYi0atcr
^eTgrgnced By
^eTgrgnced By
2 ,
2 ,
「Forms j-Menus У PL/SQL libraries ]$ ⑥ BOOXLIB вЯподг«п Ub<s 哈亩 EK (PeckaBSpec) & &ЙЕХС (Package Body) e 5<3lOPu (Рккма 5pec)@ ©■SuDprograres ^■RFferpnces 守Referenced » ij r LOPU iPactagesodyJe
i! - EXC (Package Bcdy)e
j 5-E3LOPU (Package BadvJti I ^--Attached Lbraries 色 Object LArafies § SdlNn Packaoes ^OSTANDARD Extensions 虫餌 BEAM (Peckago Spec) 蝇FTREENackw 5pec) 处IWEB (Package Spec) JfflkiRAJAVA (Package SpM) 电*CJ5QL (Package Spec) 》^ORA尸j (Package Spec) рЙ0ОЯА_РЯО (Package Spec) »ЙЗ<ЖА_М.5 (Package 5pec) i Ф ^TOOL_RE5 (Package Sp«) IHRFCLO5E (FILEHANDIE W OUT ftFHANDLE);
j pRFOPEN (SPEC IN VARCHA«2) RETURN RFHMOLE;
UrFREAD (RESID in VARCHAMj RE5TYPE 例 VARCHAR2) SETlAN YARCHflRZ;
LrFHEAD (FILE IN RFHANDlf, RESID IN VARCHAR2, RESTYPE 例 VARCHAR2) RETURN VARCHARZ; f >fflTOOL„ENV (Package Spec)
JfflTEXTJO (Package Spec).
S ^TOOLJRR (Package Spec)'
T CLEAR;
卜CODE (UN PISJMTEGER) RETURN NUMBER:
{-RFOPEN (SPECIN VARCHAR2) RETURN RF WE;
「Forms j-Menus У PL/SQL libraries ]$ ⑥ BOOXLIB вЯподг«п Ub<s 哈亩 EK (PeckaBSpec) & &ЙЕХС (Package Body) e 5<3lOPu (Рккма 5pec)@ ©■SuDprograres ^■RFferpnces 守Referenced » ij r LOPU iPactagesodyJe
i! - EXC (Package Bcdy)e
j 5-E3LOPU (Package BadvJti I ^--Attached Lbraries 色 Object LArafies § SdlNn Packaoes ^OSTANDARD Extensions 虫餌 BEAM (Peckago Spec) 蝇FTREENackw 5pec) 处IWEB (Package Spec) JfflkiRAJAVA (Package SpM) 电*CJ5QL (Package Spec) 》^ORA尸j (Package Spec) рЙ0ОЯА_РЯО (Package Spec) »ЙЗ<ЖА_М.5 (Package 5pec) i Ф ^TOOL_RE5 (Package Sp«) IHRFCLO5E (FILEHANDIE W OUT ftFHANDLE);
j pRFOPEN (SPEC IN VARCHA«2) RETURN RFHMOLE;
UrFREAD (RESID in VARCHAMj RE5TYPE 例 VARCHAR2) SETlAN YARCHflRZ;
LrFHEAD (FILE IN RFHANDlf, RESID IN VARCHAR2, RESTYPE 例 VARCHAR2) RETURN VARCHARZ; f >fflTOOL„ENV (Package Spec)
JfflTEXTJO (Package Spec).
S ^TOOLJRR (Package Spec)'
T CLEAR;
卜CODE (UN PISJMTEGER) RETURN NUMBER:
{-RFOPEN (SPECIN VARCHAR2) RETURN RF WE;
Рис, 20,7. В списке элемента Referenced By представленытолько зависимости текущего модуля
Проще говоря, мы ничего не увидим. А ведь при модификации интерфейса программы, входящей в библиотеку PL/SQL, любого из вас наверняка будет интересовать, к каким последствиям это приведет, и узнать об этом вы захотите еще' до того, как ваш телефон начнет разрываться от звонков пользователей. Так что придется заняться поиском самостоятельно, воспользовавшись разве что какой* нибудь примитивной утилитой вроде программы grep для Unix.
Если говорить откровенно, недостаток информации о ссылках — черта, свойственная не только клиентскому PL/SQL; с подобной проблемой приходится сталкиваться каждому, кто выпускает новую версию вызываемой программы. Правда, это помогает оценить полноту информации, которая имеется в словаре данных на сервере!
Удаленные зависимости
Серверная программа PL/SQL становится неработоспособной, как только изменяется код какого-либо локального объекта, от которого она занисит. Однако если такой объект находится на другом компьютере, Oracle не пытается тут же изменить состояние программы. Эта задача откладывается до вызова программы, а выполняется она в двух случаях:'
О когда клиентская программа PL/SQL скажем, процедура в модуле Oracle Forms, вызывает хранимую программу на любом сервере базы данных;
О если серверная программа производит вызов удаленной процедуры через соединение с базой данных, например:
PROCEDURE synch em_up (tax_s1te in IN VARCHAR2. sincejn IN DATE)
IS~ 一-
BEGIN
IF tax_site in = 'LONDON'
THEN ~~
recompute prices&f1ndat.1dn.world(cutoff time -> since in);
ENO IF: 一一"
В подобных случаях, если исполняющее ядро не будет выполнять удаленную программу, вы получите сообщение об ошибке ORA-04062 с соответствующим текстом, например таким: timestamp or signature of package "SCOTT. recompute_pr1ces" has been changed. Для того чтобы понять, как исполняющее ядро принимает подобное решение, нужно знать, что компилятор PL/SQL всегда хранит для каждой удаленной процедуры временную метку и сигнатуру.
О Временная метка — дата и время (с точностью до секунд) последнего изменения спецификации объекта, взятые из столбца TIMESTAMP представления USER_ OBJECTS. В программах PL/SQL это не обязательно то же самое, что время последней компиляции, поскольку возможна перекомпиляция объекта без изменения его спецификации. (Указанный столбец имеет тип данных DATE, но совпадение его имени с новым типом данных случайно.)
Q Сигнатура — упрощенная спецификация объекта. Сигнатура определяет имя объекта, а также типы данных, порядок и режимы использования его параметров.
Таким образом, когда вы компилируете процедуру synch_em_up, Oracle извлекает временную метку и сигнатуру удаленной процедуры recofflputed_prices и сохраняет их в байт-коде процедуры synch_em_up.
Дальше все просто. Во время выполнения программы в зависимости от текущего значения параметра REMOTE_DEPENOECCIES_MODE Oracle сравнивает либо временную метку, либо сигнатуру удаленной процедуры со значением в байт-коде локальной процедуры и, если они не совпадают, генерирует ошибку ORA- 04062.
По умолчанию сравнивается временная метка, ио иногда это ведет к ненужным перекомпиляциям. В таком случае администратор базы данных изменяет ее в системном икициализационном файле либо с помощью команды ALTER SYSTEM. Разработчик приложения может установить ее для текущего сеанса посредством команды
ALTER SYSTEM SET REMOTE DEPENDECCIES MODE - SIGNATURE;
а в программе PL/SQL - с помощью инструкции
EXECUTE IMMEDIATE 'ALTER SYSTEM SET REMOTE_OEPENDECCIES_MODE * SIGNATURE':
После этого до конца сеанса во всех программах PL/SQL будет использоваться метод проверки сигнатуры. Клиентские средства Oracle всегда выполняют указанную выше команду сразу после подключения к базе данных, чтобы не зависеть от параметров, заданных в инициализационном файле Oracle (INIT.ORA).
Oracle рекомендует использовать метод проверки сигнатуры в клиентских средствах, например в Oracle Forms, а метод проверки временной метки — в межсерверных вызовах процедур. Однако учтите, что первый метод может стать причиной возникновения псевдоошибок в приложении. Если исполняющее ядро посчитает, что сигнатура программы не изменилась, хотя на самом деле она стала другой, Oracle не станет выполнять ее перекомпиляцию, и тогда могут появиться ошибки вычислений, которые будет трудно обнаружить. Перечислим несколько ситуаций, когда события могут развиваться по описанному сценарию,
О Изменение только значения по умолчанию одного из формальных параметров процедуры. Вызывающая программа будет продолжать использовать старое значение.
О Добавление в существующий пакет еще одного экземпляра перегруженной программы. Он не будет активизирован, когда это потребуется.
О Изменение имени формального параметра. Проблема обычно возникает, если в вызывающей программе производится передача параметров по именам.
Во всех этих случаях вы вынуждены будете перекомпилировать вызывающую программу вручную. Метод же проверки временной метки; хотя иногда и приводит к ненужной компиляции, позволяет перекомпилировать программу всегда, I когда это требуется. Следовательно, одним из его достоинств является надежность. Именно поэтому Oracle использует данный метод по умолчанию для удаленных вызовов процедур, находящихся на различных серверах.
ПРИМЕЧАНИЕ
Если вы пользуетесь методом проверки сигнатуры, Oracle рекомендует добавлять новые функции м процедуры в конец спецификации пакета, что поможет сократить количество ненужных перекомпиляций.
Как PL/SQL использует память сервера Oracle
Экономно используя машинные ресурсы, в том числе память и процессор, Oracle может поддерживать десятки тысяч одновременно работающих пользователей базы данных. Технологии управления памятью Oracle в последние годы стали довольно сложными, и освоить их бывает непросто. Хотя хорошо разбираться в механизмах управления памятью должны в первую очередь администраторы промышленных баз данных, такого рода знания необходимы и программистам. И, конечно же, каждому программисту PL/SQL нужно знать, как избежать сбоев используемого в Oracle алгоритма распределения памяти.
Память сервера
Каждый экземпляр базы данных Oracle формирует в оперативной или виртуальной памяти системную глобальную область (System Global Area, SGA) и запускает множество фоновых процессов1. Значительную часть SGA обычно занимают буферные пулы, в которых кэшируются табличные данные. В этой же области памяти имеется еще одна часть, особенно важная для работы PL/SQL — общий пул. В таком кэш-пуле содержатся, во-первых, метаданные из словаря данных, а во- вторых, откомпилированные представления SQL-инструкции и программ PL/SQL
Когда пользовательский сеанс впервые запускает кошфетную программу PL/SQL, Oracle помещает ее исполняемую часть в общий пул, называемый библиотечным кэшем. При повторном вызове этой программы, Oracle использует ее кэшированную копию, экономя время на обращении к относительно медленному жесткому диску. РСУБД может использовать кэшированную версию и при вызове про- граммы другими сеансами. Однако совместный доступ к байт-коду она предоставляет сеансам только при определенных условиях. Но прежде чем приступить к обсуждению этих условий, необходимо рассказать о других важных для разработчиков приложений процессах и областях памяти, включая области PGA (Program Global Area), CGA (Call Global Area) и UGA (User Global Area), которые часто путают.
Когда клиентская программа, скажем SQL*Plus, инициирует сеанс подключения к базе данных, Oracle запускает на сервере базы данных «теневой» процесс для его обслуживания. Глобальная область программы (Program Global Area, PGA)- это название области памяти, связанной с данным процессом. Программисты поч- ти не имеют контроля над той частью PGA, где хранится информация о состоянии процесса сеанса и используемых им ресурсах операционной системы; еще одна часть PGA содержит данные приложений — она называется CGA; в режиме выделенного сервера в PGA может также располагаться область UGA, предназначенная для хранения информации пользователя.
Когда сеанс выполняет SQL-инструкцию или блок PL/SQL, Oracle выделяет для них временную память. Это рабочее пространство выделяется в глобальной области программы и называется глобальной областью вызова (Call Global Area, CGA). Используется оно только в течение данного вызова, то есть в течение выполнения инструкции или блока. По завершении вызова область CGA немедленно освобождается.
Еще одна важная область памяти называется глобальной областью пользователя (User Global Area, UGA). В ней содержатся данные о состоянии сеанса и собственные области SQ.L и PL/SQL (здесь располагаются переменные и константы пакета PL/SQL). В отличие от CGA, область UGA сохраняется между вызовами и, как правило, не уменьшается и не освобождается до конца сеанса.
Бытует ошибочное мнение, что UGA может располагаться либо в программной, либо в системной глобальной области. Но самом деле все зависит от того, какая конфигурация, выделенного или разделяемого сервера, используется.
1 Фоновые процессы управляют дисковым вводом-выводом, восстанавливают процессы и транзакции после сбоев, осуществляют мониторинг процессов базы данных.
О Выделенный сервер. Для каждого сеанса Oracle создает выделенный процесс и помещает UGA и CGA в программную глобальную область, выделяемую процессу операционной системой, в среде которой работает сервер. Такая конфигурация целесообразна при больших нагрузках, например при сюуществле- нии интенсивной пакетной обработай и обработки транзакций в реальном времени (On-Line Transaction Processing, OLTP), долго выполняющихся запросов и резервного копирования базы данных.
О Разделяемый сервер. Раньше его называли «многопотоковым* или MTS. Oracle связывает несколько сеансов с одним совместно используемым северным процессом и выделяет для глобальной области пользователя память в системной глобальной области. В такой конфигурации теневой процесс и фиксированные части PGA совместно используются несколькими клиентскими процессами. Однако с ней связаны некоторые издержки и едва ли она тЕринесет действительную выгоду, если только в системе не будут выполняться одновременно сотни сеансов, каждый из которых может простаивать значительную часть времени.
Общий объем PGA может быть разным в зависимости от вида операций, выполняемых сервером для приложения. Например, для инструкций DML, осуществляющих сортировку большого количества данных, может понадобиться много памяти CGA, а для пакетов PL/SQL, заполняющих коллекции, — памяти UGA. Упрощенная структура двух последних конфигураций сервера представлена на рис. 20.81.
Выделенный серверВыделенный серверРазделяемый серверРазделяемый серверФоновый процесс сервера GradeФоновый процесс сервера GradeРис. 20.В. Архитектура памяти и процессов Oracle 日 конфигурациях на основе выделенного и разделяемого серверовРис. 20.В. Архитектура памяти и процессов Oracle 日 конфигурациях на основе выделенного и разделяемого серверов
Выделенный сервер
Выделенный сервер
Разделяемый сервер
Разделяемый сервер
Фоновый процесс сервера Grade
Фоновый процесс сервера Grade
Рис. 20.В. Архитектура памяти и процессов Oracle 日 конфигурациях на основе выделенного и разделяемого серверов
Рис. 20.В. Архитектура памяти и процессов Oracle 日 конфигурациях на основе выделенного и разделяемого серверов
На рисунке не показаны очереди диспетчеризации и эапросов/ответов разделяемого сервера.
Вы должны понимать, что если приложение работает с разделяемым сервером, пользовательские процессы могут конкурировать за время и память с теневым процессом. И если ваш пользовательский процесс запускает долго выполняющиеся блоки PL/SQL или SQL-инструкции, системный администратор должен либо сконфигурировать сервер с большим количеством теневых процессов, либо запустить эти сеансы на выделенном сервере.
Теперь, разобравшись в том, что собой представляет память сервера, мы попытаемся выяснить, как выглядит память с точки зрения выполняющейся программы.
Курсоры и память
Вы наверняка уже знаете, что такое курсоры. Не исключено, что вам сотни раз приходилось объявлять и открывать их, выбирать из них строки, а затем закрывать их. Возможно, вы даже работали с курсорами, используя встроенный пакет DBMS_SOL. Ни SQL, ни PL/SQL не могут функционировать без них; многие операторы для выполнения дополнительных операций неявно открывают рекурсивные или дочерние курсоры. А поскольку каждый курсор, явный или неявный, занимает память сервера базы данных, процесс оптимизации настройки Oracle часто подразумевает и сокращение количества необходимых приложению курсоров.
Oracle ассоциирует курсоры с анонимными блоками PL/SQL практически так же, как с 50,Ь-инструкциями. Например, при обработке первого вызова в текущем сеансе пользователя Oracle открывает в памяти PGA область («собственную» область SQL), в которой размещаются переменные связывания и другая специфическая для этого вызова информация.
Оказывается, что одни связанные с курсорами серверные структуры данных располагаются в глобальной области пользователя, а другие — в глобальной области вызова. Например, поскольку инструкция SELECT идентифицирует записи, которые должны быть доступны при выполнении нескольких выборок, Oracle выделяет для курсора память в области UGA, а для инструкций DML, завершающихся за один вызов, — память в области CGA. Аналогичным образом, при выполнении PL/SQL-программ для хранения информации о состоянии выделяется память в UGA, а для других данных - память в CGA.
При выполнении SQL-инструкции или блока PL/SQL сервер сначала проверяет содержимое библиотечного кэша на предмет того, нет ли там уже подготовленного представления этого кода. Если такая общедоступная область PL/SQL найдена, исполняющее ядро связывает ее и личную область PL/SQL. В противном случае Oracle придется выполнить синтаксический анализ инструкции или блока. (Кроме того, Oracle подготавливает и кэширует шхан выполнения анонимных блоков PL/SQL включающий вызов ядра PL/SQL для интерпретации байт-кода.)
Oracle интерпретирует простейшие блоки PL/SQL (блоки, вызывающие подпрограммы, и блоки, не содержахцие интегрированных S QL-инструкций), используя только ту память, которая выделена для главного курсора. Если программа содержит вызовы PL/SQL или вызовы SQL, Oracle требуются дополнительные собственные области в памяти PGA. РСУБД освобождает собственные области SQL, которые используются дочерними курсорами, только после освобождения области SQL родительского курсора.
Таким образом, существует два способа закрытия курсора. Если это делается программно, его больше нельзя использовать в приложении, не открыв снова. Программное закрытие выполняется с помощью следующего оператора:
CLOSE HMsj^ypcops :
или при автоматическом закрытии неявного курсора. Однако PL/SQL не сразу освобождает области памяти, связанные с этим курсором. После закрытия курсор существует как объект базы данных. Заглянув в представление V$OPEN_CURSOR( вы увидите, что выполнение операции CLOSE не уменьшило количество открытых курсоров сеанса.
Как оказалось, PL/SQL поддерживает собственный «сеансовый кэш курс。、 ров», то есть сам решает, когда какой курсор необходимо освободить. Максимальное количество курсоров в этом кэше задается инициализационным параметром OPEN_CURBORS базы данных. Выбор курсора, который должен освободить память, производится на основании алгоритма LRU (Least Recently Used — «дольше scex не использовавшийся»). При аккуратном программировании, когда все курсоры закрываются вовремя, данный подход дает возможность значительно повысить производительность приложений за счет уменьшения сетевого трафика.
ПРИМЕЧАНИЕ
Всегда явно закрывайте курсоры, открытые явным образом. Если оставить курсор незакрытым, может нарушиться работа алгоритма управления курсорами PL/SQL.
У программиста имеется несколько возможностей изменить стандартное поведение Oracle, Одна из них состоит в закрытии всех курсоров сеанса до его завершения. Кроме этого кардинального метода существуют и другие:
О восстановление исходного состояния пакета (см. ниже раздел «Большие коллекции в PL/SQL»);
О управление поведением курсоров с помощью пакета DBMS SQL (правда, выигрыш от использования данного подхода может быть значительно меньше, чем потери от снижения производительности и усложнения программирования).
Советы по экономии памяти
Вооружив вас теоретически, дадим несколько практических советов, которыми вы сможете воспользоваться при программировании.
Совместное использование инструкций
Oracle способна предоставить программам совместный доступ к откомпилированным версиям SQL-инструкций и анонимных блоков даже в том случае, если они получены от разных сеансов и разных пользователей. Однако это возможно лшпь при соблюдении определенных условий. Чтобы система могла обеспечить совместный доступ к SQL-инструкциям, необходимо придерживаться пяти основных правил, первые три из которых относятся и к анонимным блокам PL/SQL.
Соглашения о регистре символов и пробелах в исходном коде должны быть одинаковыми.
Внешние ссылки должны разрешаться как ссылки на один и тот же объект.
Значения данных необходимо задавать с помощью переменных привязки, а не в виде символьных строк (или же параметр CUR50R SHARING должен иметь соответствующее значение).
Oracle кэширует SQL-HHCTpyKiiMH отдельно от содержащих их программ, поэтому правила их совместного использования несколько иные. Для SQL-инструкций существует два дополнительных правила.
Все параметры базы данных, влияющие на работу оптимизатора запросов SQL, должны совпадать. Например, в вызывающих сеансах необходимо задавать одинаковый «критерий оптимизации» (установка ALL.ROWS или FIRST_ROWS).
Вызывающие сеансы должны поддерживать одни и те же национальные языки (National Lanquage Support, NLS).
Мы не станем останавливаться на двух последних правилах, но о значении первых трех правил для программ PL/SQ.L следует сказать несколько слов.
Первое правило, определяющее необходимость наличия пробелов и учета регистра символов, является хорошо известным условием совместного использования инструкций. Несмотря на то что PL/SQL обычно не учитывает регистр, следующие три блока он не воспримет как одинаковые:
8EGIN NULL; END: begin null; end: BEGIN NULL; END;
Этот малоприятный факт относится и к SQL. Однако если все ваши анонимные блоки Коротки и все «реальные программы» реализованы в виде хранимого кода, например в виде пакетов, вероятность ошибочно запретить их совместное использование значительно меньше. В этой связи хотелось бы дать такой совет: реализуйте код SQL и PL/SQL в виде хранимых программ. Анонимные блоки должны быть как можно более короткими, в общем случае состоящими из единственного вызова хранимой программы. Кроме того, можно дать еще одну рекомендацию по SQL: для того чтобы обеспечить возможнекть совместного использования S Q.L-инструкций, помещайте их в программы, которые вызываются из разных мест приложения. Это избавит вас от необходимости по нескольку раз писать одни и те же инструкции.
С другой стороны, автор всегда чувствовал, что попытка строго придерживаться соглашений о форматировании — неудобный и ненадежный способ поддержки совместного использования кода; гораздо проще помещать SQL-инструкции в вызываемые программы.
Второе правило гласит, что внешние ссылки (на таблицы, процедуры и т. д.) должны разрешаться как ссылки на один и тот же объект. Предположим, что вы со Скоттом подключились к Oracle и оба запустили такой блок:
BEGIN
fQQ:
END:
Решение Oracle о том, следует ли вам обоим предоставить возможность использовать кэшируемую форму данного блока, зависит от того, является ли ссылка foo именем одной и той же хранимой процедуры, Если у Скотта имеется синоним foQ)указывающий на вашу копию процедуры, РСУБД разрешит совместный
доступ к анонимному блоку. При наличии у каждого из вас своей копии процедуры каждый будет работать со своим блоком. И даже если обе эти копии процедуры f。。абсолютно идентичны, Oracle будет кэшировать их как разные объекты. Аналогичным образом, она кэширует как различные объекты идентичные триггс- ры разных таблиц. Из сказанного вытекает такой вывод: следует избегать создания одинаковых копий таблиц и программ, соответствующих разным учетным записям. И более того, для экономии памяти нужно отделить программный код, общий для нескольких программ (и в особенности триггеров), и реализовать его как отдельный вызов. Систему необходимо сконфигурировать таким образом, чтобы только один пользователь базы данных владел црограммами PL/SQL, предназначенными для совместного использования, и предоставлял разрешение EXECUTE всем, кто в нем нуждается. (О разрешениях на выполнение речь цойдет далее в этой главе.)
Из последнего правила существует важное исключение, действующее в среде с высокой степенью параллелизма, то есть с большим количеством пользователей, одновременно выполняющих одну и ту же программу PL/SQL. При выполнении совместно используемых фрагментов кода для закрепления и освобождения объекта используются так называемые защелки библиотечного кэша. В среде с высокой степенью параллелизма это может привести к конкуренции за владение защелками. В таких случаях дублирование кода действительно полезно и помогает повысить производительность системы.
Третье правило, касающееся переменных привязки, настолько важно, что ему стоит посвятить отдельный раздел.
Привязка переменных
В PL/SQL любая переменная, используемая в анализируемой статической SQL- инструкции, автоматически становится переменной привязки. Если вы следуете хорошей программистской практике задавать символьные значения в виде параметров и констант, вместо того чтобы ссылаться на них непосредственно, то никаких проблем у вас не будет. Приведем простой пример, в котором PL/SQL преобразует параметр thresholdjn в переменную привязки:
FUNCTION maxcats (threshholdjn IN NUMBER DEFAULT 100)
RETURN NUMBER-
IS
CURSOR qcur 15
SELECT category, COUNK*).
FROM booklist
CROUP BY category
having COUNT(*) > threshholdjn: и так далее...
Менее известым является тот факт, что переменные привязки можно использовать и в анонимных блоках SQL*Plus:
SQL> VARIABLE howmany NUMBER
SQL> EXEC :howmany :■ maxcats
Однако учтите, что присвоить значение переменной привязки в SQI-*Plus можно только в анонимном блоке.
Еще одна проблема, связанная с переменными привязки, возникает при формировании динамических SQL-инструкций во время выполнения программы. Небрежное программирование может привести к тому, что такие инструкции будут содержать символьные значения:
CREATE OR 2EPSCE FUNCTION count_recent_recor6s (
tablename_in IN VARCHAR2.s1nce_1n IN DATE)
RETURN PLS INTEGER
AS -
count」PL5JNTEGER;
BEGIN
EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM ' |[ tablenamejn
j] ' WHERE lastupdate > TO_DATE('"
II TO_CHAR(since in, 'YYVYMMDD')
II "7. "YYYYMMDD")r.
INTO countj:
RETURN countj:
ENO:
Этот код динамически формирует инструкцию
SELECT COUMTC*) FROM Ш_Тдблт WHERE lastupdate > TO_DATE('20020315'. 'YYYYMMDD')
При повторных вызовах данной функции с разными значениями аргумента s i nce_i п может быть создано множество инструкций, не пригодных для использования в нескольких сеансах. Например:
SELECT COUNTt*)FROM ^ja6nw WHERE lastupdate>TO_DATEC'20020105'.'YYVYMMDD'}
SELECT COUNTC*)FROM ^я^дбпииы WHERE lastupdate>TOJATE('20010704'.'YYYYMMDD')
SELECT COUNT(*)FROM амя^дблицы WHERE lastupdate>Т020АТЕ{'2003010Г.'YYYYMMDD')
Если переписать эту же функцию с использованием переменной привязки, получится такой код:
CREATE OR REPLACE FUNCTION count_recent_records (tablename_1n IN VARCHAR2,s1 nee in IN DATE) RETURN PLS INTEGER'~~”
AS
countj PLS_INTEGER;
BEGIN
EXECUTE I懵1EDIATE 'SELECT COUNK*) FROM '|| tablenamejn
11 ' WHERE lastupdate > thedate'
INTO count_l
USING sincejn;
RETURN count_l:
ENO:—
Он генерирует следующую инструкцию:
SELECT COUNK*) FRCM “mj?_t■舒me物照ERE lastupdate > :thedate
Вторая версия не только проще и привлекательнее, но, главное, не зависит от имени таблицы и значений аргумента sincejn в вызове функции.
В Oracle 8.1.6 появилась возможность обойти ограничение оптимизатора относительно символьных строк. Речь идет об использовании параметра CURSOR_ SHARING (совместное использование курсора). Эту функцию можно активизировать с помощью команды
ALTER SESSION SET CURSORJHARING m FORCE; /* а аерсиях 8.1.6 и выше */
или команды
ALTER SESSION SET CURSOR_SHARING = SIMILAR: /* в версиях 9.0 и выше */
Далее Oracle до конца сеанса будет в скрытом режиме преобразовывать литеральные запросы, генерируемые первой функцией, в инструкции, подобные следующей:
SELECT COUNTC*) FROM ш^тдблицы WHERE lastupdate > TO_DATE(:"SYS_8_0", :"$YS_B_1")
Для приложения результаты будут теми же, однако такая форма инструкции позволит уменьшить нагрузку на библиотечный кэш.
Отменить возможность совместного использования курсора позволит такая команда:
ALTER SESSION SET CURSORJHARING - EXACT:
В Oracle8i параметру CURSOR_SHARING невозможно присвоить значение SIMILAR — он может принимать только значение EXACT или FORCE. Если он имеет значение FORCE, SQL-инструкции с символьными значениями всегда переписываются. Но в некоторых случаях зто может оказать отрицательное влияние на производительность. В версиях Oracle, поддерживающих параметр SIMILAR, литералы заменяются переменными привязки лишь при условии, что оптимизатор не учитывает их при анализе.
ПРИМЕЧАНИЕ
Переменные привязки и соответствующая установка параметра CURSOR_SHARING нужны только для того, чтобы Grade мпгла обеспечить совместное использование SQL-инструкций. Помните, что типы данных и максимальная длина переменных привязки должны соответствовать задаваемым значениям.
Пакеты и эффективное использование памяти
При извлечении байт-кода (или DIANA-кода) хранимой прохраммы PL/SQL считывается вся эта программа. Речь идет не только о процедурах и функциях, но и о пакетах базы данных. Иными словами, не существует возможности сделать так, чтобы Oracle считывала лишь часть пакета — при обращении к любому его элементу, даже к одной-единственной переменной, в библиотечный кэш загружается весь откомпилированный код пакета. Поэтому формирование и совместное хранение элементов пакета полезно не только с точки зрения разработчика, но и с точки зрения производительности системы,
ПРИМЕЧАНИЕ
Поскольку Orade считывает в памэть весь пакет, объединять в пакеты следует только функционально связанные элементы, та есть такие, которые с большой вероятностью будут вызываться в одном сеансе.
Большие коллекции в PL/SQL
Совместное использование объектов — прекрасное решение, но допустимо око далеко не для всех элементов программы. Даже если несколько пользователей выполняют одну и ту же программу, принадлежащую одной и той же схеме Oracle, каждый сеанс имеет свою собственную область памяти, в которой содержатся специфические для данного вызова данные — значения локальных переменных
и переменных пакета, константы и курсоры. И пытаться организовать совместное использование этих значений нет никакого смысла.、
Наиболее типичные проблемы, связанные с памятью, при вьшолнении программ PL/SQL возникают во время операций над большими коллекциями, которые являются прекрасным примером данных, не подлежащих совместному использованию. (О коллекциях подробно рассказывалось в главе И.) Предположим, что мы объявили ассоциативный массив (ранее называвшийся индексной таблицей):
DECLARE
TYPE nufflber_tab_t IS TABLE OF OISER INDEX BY BINARY」仃EGER;
number_tab nunter_tabjt:
empty_tab number■主bjE:
Добавим в эту таблицу большое, количество элементов:
FOR i IN i..100000
LOOP
number_tab(1):- i;
END LOOP;"
Oracle должна их где-то разместить. Согласно изложенным выше правилам, память для этого массива будет выделена в глобальной области пользователя, если он объявлен на уровне пакета, или в глобальной области вызова, если это данные анонимного блока, процедуры или функции верхнего уровня.
У вас наверняка возникнет вопрос, каким образом можно будет освободить эту память после завершения работы с коллекцией. Нужно выполнить оператор nuTiber_tab. DELETE;
или оператор
number_tab :- empty_tdb:
Oracle освободит память, зафиксировав ее в своем списке свободных областей. Это означает, что память для хранения переменной пакета будет возвращена в глобальную область пользователя, а память переменной уровня вызова — в глобальную область программы. То же произойдет и в том случае, если выполнение программы выйдет за пределы области видимости переменной, объявленной как коллекция. Например, если коллекция будет объявлена в отдельной процедуре, Oracle освободит занимаемую ею память сразу по окончании работы процедуры. Однако такая память не будет доступна другим сеансам или текущему сеансу, если ему понадобится память из глобальной области вызова. Поэтому, если в последующих операциях DML, допустим, будет выполняться сортировка большого количества данных, требуемый приложению объем памяти может оказаться просто огромным. По завершении сеанса Oracle освободит память полностью, вернув ее в свою кучу.
Следует, однако, подчеркнуть, что для системы управления виртуальной памятью не составляет труда управлять файлом подкачки большого объема, особенно если процесс удерживает в своем адресном пространстве большое количество неактивной виртуальной памяти. Эта неактивная память занимает только место на жестком диске, а не реальную память. Однако иногда нежелательно заполнять страничное пространство, поэтому будет лучше, если Oracle освободит
память полностью. Для таких случаев в ней предусмотрена специальная процедур ра «уборки мусора* с очень простым синтаксисом:
DBMS^SESSION.FREE_UNUSED_USER_MEMORY:'
Эта встроенная процедура найдет большую часть памяти, не используемой переменными программы, и возвратит ее в родительскую кучу: при наличия выделенного сервера — в PGA, а в случае разделяемого сервера — в SGA.
Существует два способа передачи Oracle информации о том, что конкретная коллекция больше не нужна. Один из них, автоматический, производится, когда выполнение программы выходит за пределы области видимости коллекции. Однако при работе с данными уровня пакета имеет смысл воспользоваться описанным выше методом DELETE.
Автор протестировал лроцесс сювобождения занимаемой коллекциями памяти в разных ситуациях, в частности, при использовании ассоциативных массивов и вложенных таблиц для выделенного и разделяемого сервера, анонимных блоков и данных пакетов. В результате были сделаны следующие заключения.
О Для освобождения занимаемой памяти недостаточно присяоить вложенной таблице или массиву VARRAY значение NULL. Нужно либо вызвать метод коллекции DELETE, либо присвоить коллекции другую пустую, но инициализированную, коллекцию, либо подождать, пока выполнение программы выйдет за пределы области видимости коллекции.
О Чтобы освободить память и поместить ее в родительскую кучу, следует применить программу DBMS_SESSION. FREE_UNUSED_USER_MEMORY. Ее можно вызвать после заполнения приложением одной или нескольких больших таблиц PL/SQL, которые больше не будут использоваться.
О В режиме разделяемого серяера обычно возникает больше ошибок из-за нехватки памяти, чем в режиме выделенного сервера, поскольку глобальная область пользователя принадлежит системной глобальной области, имеющей фиксированный размер. Как рассказывается в разделе «Трассировка памяти», существует вероятность возникновения ошибки ORA-04031.
О При подключении к разделяемому серверу освободить память, которую занимают таблицы PL/SQL, объявленные на уровне пакета, невозможно.
На практике объем памяти, занимаемый коллекцией элементов типа NUMBER, не зависит от того, являются элементы значениями NULL или 38-значными числами. А вот для значений типа VARCHAR2, длина которых превышает 30 символов, Oracle, похоже, выделяет память динамически.
В режиме выделенного сервера массив, состоящий из миллиона значений типа NUMBER, занимает около 38 Мбайт. И даже если элементы массива имеют тип BOOLEAN, OracleSi использует для него почти 15 Мбайт памяти. Умножьте это значение на количество пользователей, например на 100, и вы получите огромные цифры. Их следует учитывать, особенно в тех случаях, если необходимо, чтобы операционная система выполняла свопинг страниц на диск.
Для того чтобы определить, сколько памяти UGA и PGA доступно в текущем сеансе, можно запустить такой запрос:
SELECT n.name. ROUNDCin.value/1024) kbytes
FROM V$5TATNME n. V$MYSTAT m
WHERE n.statistic# - m.statistic#
AND л.name LIKE 'sess1onЗ;пlemoryJ:,
(Для чтения представлений V$STATNAME и V$MYSTAT недостаточно привилегий по умолчанию.)
Единственным способом освобождения памяти PGA при условии, что вы не хотите завершать сеанс( является принудительный перевод пакетов в исходное состояние с помощью одной из двух следующих процедур.
О DBMS_SESSION・ RESET一PACKAGE. Освобождает всю память, выделенную для хранения информации о состоянии пакета. В результате все переменные пакета получают значения по умолчанию. Для пакетов эта встроенная процедура делает больше, чем пакет FREE_UNUSED_USER_MEMORY, поскольку она не заботится тем, как используется память.
О DBNS_SESSION.MODIFY_PACKAGE_STAT£(0narM_onepauHM IN PLS」NTEGER). В качестве флагов операций можно задать одну из двух констант, а именно DBMS^SESSIQN.FREE- ALl_RESOURCES или DBMS_SESSION. REINITIALIZE. Использование цервой приводит к тому же эффекту, что и применение процедуры RE5ET_PACKAGE. Вторая константа восстанавливает переменные состояния, присваивая им значения по умолчанию, но не освобождает и не восстанавливает пакет с нуля. Кроме того, она программно закрывает открытые курсоры и не очищает их кэш. Если ваше приложение допускает подобное, используйте вторую константу, поскольку это лучше, чем возврат пакета в исходное состояние.
Сохранение состояния объектов
Обычно Oracle сохраняет до окончания сеанса значения констант уровня пакета, переменных и состояние курсора в глобальной области пользователя. С переменными, которые объявлены внутри модуля, она обходится совсем иначе. Поскольку их область видимости ограничена модулем, по завершении его работы занимаемая данными переменными память освобождается. С этого момента их самих больше не существует.
Причиной уничтожения информации о состоянии пакета, помимо отсоединения от сервера базы данных, могут послужить еще несколько обстоятельств;
О программа стала неработоспособной (например, в результате перекомпиляции);
О в текущем сеансе выполнена встроенная процедура DBMS_SESSION. RESET一PACKAGE;
О в код программы добавлена директива компилятора SERIALLY_REUSABLE (см. главу 17), предписывающая Oracle сохранять параметры состояния только на время вызова, а не в течение всего сеанса;
О использование web-пглюза в режиме по умолчанию, в котором параметры сеанса каждого клиента не сохраняются.
Структуры данных пакета могут действовать в среде PL/SQL как глобальные. Иными словами, с их помощью программы PL/SQL, выполняющиеся в течение одного сеанса, могут обмениваться данными.
С точки зрения архитектуры приложения существует два вида глобальных данных: общие и личные.
О Общие данные. Структура данных, объявленная в спецификации пакета, является глобальной общей структурой данных. Доступ к ней имеют любые
программы и пользователи с привилегией EXECUTE. Программы могут присваивать переменным пакета, которые не являются константами (CONSTANT), любые, даже бессмысленные значения. Как известно, использование общих глобальных данных является потенциальной причиной самых разнообразных ошибок, поскольку существует большое искушение применять их без необходимости и не по назначению, из-за чего код получается неструктурированным и сопровождается опасными побочными эффектами.
Спецификация модуля должна содержать всю информацию, необходимую для его вызова и использования. Если программа выполняет чтение или запись глобальных структур, в спецификации об этом ничего не сказано. Вот почему вы не знаете, что происходит в приложении и какие из программ изменяют данные каждой конкретной структуры. Из этого следует вывод: передавать данные в модули и получать их из таковых всегда лучше в виде параметров. Тогда доступ к такой информации будет документирован в спецификации и программист сможет его учитывать. Например, автор книги старается ограничивать глобальные данные только константами.
О Личные данные. Личные глобальные структуры данных (называемые также данными уровня пакета) не вызывают столько проблем. В спецификации пакета они отсутствуют, и извне такового на них ссылаться нельзя. Эти данные предназначены только для использования внутри пакета и только его элементами.
Глобальные данные, существующие в пределах одного сеанса Oracle
Структуры данных пакета являются глобальными только в пределах одного сеанса или подключения к базе данных Oracle. Но они не могут совместно использоваться несколькими сеансами. Для обеспечения такой возможности имеются другие средства, в том числе пакет OBMS^PIPE, средство Oracle Advanced Queuing и пакет UTL_TCP.
Разработчики клиентских приложений должны помнить о том, что не всегда разные модули используют единое подключение к Oracle. Бывает, что для выполнения некоторой операции приложение устанавливает дополнительное подключение к базе данных. В этом случае данные, которые хранятся в пакете, открытом через первое подключение, оказываются недоступными.
Проанализируем ситуацию, показанную на рис. 20.9. Приложение Oracle Forms сохранило информацию в структурах данных серверного пакета. Хранимая процедура, вызываемая из кода формы, может обращаться к тем же переменным пакета, что и сама форма, поскольку обе используют общее подключение к Oracle. Если форма вызывает встроенную программу RUN_PRODUCT для вывода диаграммы с помощью Oracle Graphics, для формирования отчета по умолчанию создается второе подключение к базе данных (с тем же именем пользователя и паролем). Поэтому даже в том случае, когда программа при формиропании отчета обратится к структурам данных того же пакета, в них будут записаны не те данные, кото^ рые используются формой. Объясняется это прсхгго: открыт другой сеанс Oracle, а значит, формируются другие экземпляры структур данных. (Кроме того, общие транзакции и согласованные представления данных не будут использоваться в разных сеансах.)
Клиентская сторонаКлиентская сторонаСерверная сторотСерверная сторот鸵陶)верРСУВДб曬鸵陶)верРСУВДб曬->->Сеанс 1■ Даннь>е пакета• Контекст транзакции■ Сотасованностъ считанных данныхСеанс 1■ Даннь>е пакета• Контекст транзакции■ Сотасованностъ считанных данныхСеанс 2-Данные пакета-Контекст транзакции• Согласоааниостъ считанных данныхСеанс 2-Данные пакета-Контекст транзакции• Согласоааниостъ считанных данных'"而HUU2一一'"而HUU2一一
Клиентская сторона
Клиентская сторона
Серверная сторот
Серверная сторот
鸵陶)верРСУВДб曬
鸵陶)верРСУВДб曬
->
->
Сеанс 1
■ Даннь>е пакета
• Контекст транзакции
■ Сотасованностъ считанных данных
Сеанс 1
■ Даннь>е пакета
• Контекст транзакции
■ Сотасованностъ считанных данных
Сеанс 2
-Данные пакета
-Контекст транзакции
• Согласоааниостъ считанных данных
Сеанс 2
-Данные пакета
-Контекст транзакции
• Согласоааниостъ считанных данных
'"而HUU2一一
'"而HUU2一一
Рис. 20.9. Два подключения к Oracle — для Oracle Forms и Oracle Graphics
Для обеспечения возможности совместно использовать параметры состояния пакета потребуются дополвительные действия с клиентской стороны PL/SQL, О них рассказывается далее, в разделе <Клиентские библиотеки PL/SQL во время выполнения приложения>.
Трассировка памяти
Предположим, что ваше приложение базы данных прекрасно работало, выполняло большое количество инструкций PL/SQL и SQL и вдруг выдало сообщение об ошибке ORA-04031: unable to allocate n bytes of shared memory (невозможно выделить л байт общей памяти). Эту ошибку можно встретить только в режиме разделяемого сервера, когда речь идет об обращении к памяти глобальной области пользователя; в режиме выделенного сервера Oracle в таких случаях просто добавляет виртуальную память.
Попытаемся выяснить, с чем связано появление указанного сообщения. Прежде чем запустить программу PL/SQL на выполнение, Oracle должна загрузить в память весь ее баЙАКод. Для того чтобы узнать, какой объем памяти занимает объект в общем пуле, нужно вызвать встроенную процедуру DBMS_SHARED_POOl.SIZES. Эта процедура выводит список всех объектов, размер которых превышает оговоренный ранее. Приведем пример:
SQL> SET SERVEROUTPUT ON SIZE 1000000
SQL> EXEC DBKS_SHARED_P00L.SlZES(m1nsize => 100)
SIZE(K) KEPT NAME
371 SYS.STANDARD《PACKASE)
166SYS./5ее89977JamespaceRDBMS(JAVA CLASS)
101 YES SYS.java/math/Blglnteger(JAVA CLASS)
Как следует из приведенного выше код% пакет STANDARD занимает в общей памяти 371 Кбайт. Эта информация очень важная, но недостаточно полная. Вы должны знать объем памяти общего пула и то, какая его часть занята <многократ- н。создаваемыми» объектами, то есть объектами, которые могут удаляться из памяти и загружаться туда повторно по мере необходимости. А еще вам как разработчику нужно знать, содержит ли ваше приложение код, который мог бы использоваться совместно, и каков размер такого кода.
Существует несколько способов избежать появления сообщения об ошибке ORA-04031. Опытный администратор базы данных знает, как настроить общий дул, откорректировав значения перечисленных ниже параметров:
SHARED_P00L_SI2E — определяет объем памяти, выделяемый для общего пула;
О SHARED_POOL_RESERVED_SIZE - определяет поддерживаемый Oracle объем непрерывной памяти в общем пуле;
О LARGE_POOL_SIZE — задает необязательную область памяти, не связанную с общим пулом, в которой располагается глобальная область вызова, необходимая для подключений к разделяемому серверу (такой подход предотвращает конкуренцию глобальной области вызова за использование общего пула);
SESSION_CACHED_CURSORS — устанавливает количество элементов в кэше курсоров сеанса;
О CURSOR_SPACE_FOR_T I ME — если значение этого параметра равно TRUE, Oracle освобождает память, занимаемую курсором в библиотечном кэше, только при условии, что все приложения закрыли связанные с ним курсоры (обычно он имеет значение TRUE).
Разработчик приложения имеет возможность:
О модифицировать код таким образом, чтобы количество совместно используемых S QL-инструкций было максимальным;
О с разрешения администратора базы данных постоянно хранить в буферной памяти определенные прохраммы PL/SQL и/или курсоры.
Для того чтобы сохранить в общем пуле конкретные программы, нужно вызвать процедуру D8MS_SHARED_PQQL. KEEP с соответствующим параметром. Например, вызов
BEGIN
DBMS_SHARED_POOL.КЕЕР('SYS.STANDARD'): END;
указывает Oracle на необходимость сохранить в памяти пакет SYS. STANDARD.
ПРИМЕЧАНИЕ
В словаре данных Oracle имеется множество представлений, помогающих решить, какие модули стоит держать в памяти. Хотя обычно в общем пуле рекомендуется хранить пакет STANDARD, эксперт Сгив Адамс (Steve Adams) советует держать там «все, что перемещается», Но начиная с Oracle версии 8.1.6 это не так важно, поскольку была введена функция, которая позволила устранить конкуренцию за защелки при фрагментации общего пула. Информацию мистера Адамса о возможностях Orade вы найдете на узле http://www.ixora.com.au/.
Описанная тактика наиболее полезна при работе с большими и подолгу не используемыми программами, которые при перезагрузке выталкивают из памяти много маленьких объектов. Процедура KEEP может вызываться для пакетов, процедур, функций, триггеров, различных последовательностей, а также, если программист приложит определенные усилия, для курсоров.
При использовавии программ PL/SQL, для которых объектный С-код получен путем встроенной компиляции, Oracle применяет другую тактику доступа к общей памяти. Эти программы компонуются с файлами совместно используемых библиотек, общий код которых загружается в память один раз, причем память для них выделяется операционной системой. Другие программы, вызываемые сервером, в частности хранимые процедуры Java и внешние процедуры С, также используют общую память.
В режиме разделяемого сервера может возникнуть еще одна ошибка доступа к памяти — ORA-06500: PL/SQL: storage error. Обычно ее устраняют путем увеличения значения параметра LARGE_POOL_SIZE.
Выполнение серверного кода PL/SQL
В этом разделе описываются действия, которые вьшолняются Oracle, когда вы с помощью программы SQL* Plus или подобного ей приложения направляете серверу код SQL*Plus.
Компиляция анонимного блока
Компиляция анонимного блока выполняется следующим образом.
Компилятор PL/SQL производит синтаксический анализ кода. При обнаружении синтаксической ошибки анализ прекращается и вызывающей программе возвращается сообщение об ошибке компиляции.
Сервер Oracle определяет наличие в библиотечном кэше реально исполняемых версий анонимного блока. Для этого вычисляется значение хэш-функции для всего текста блока и производится текстовый поиск с использованием процедуры, подобной memcmp. Если поиск завершился успешно, выполняется ряд дополнительных проверок, цель которых — удостовериться, что серверное окружение не изменилось. При условии, что результаты всех проверок положительны, начинается выполнение кода.
Если блок в библиотечном кэше отсутствует, Oracle направляет его компилятору PL/SQL
Компилятор PL/SQL осуществляет преобразование имен идентификаторов PL/SQL, направляя все встроенные SQL-инструкции анализатору SQL.
Анализатор SQL выполняет собственную синтаксическую проверку, определяет корректность имен и производит семантический контроль, после чего снова вызывает PL/SQL для преобразования неразрешенных имен (таких как переменные привязки).
Во время компиляции проверяется, действительно ли пользователь, компилирующий код, имеет разрешение на выполнение, во-первых, всех программ, на которые есть ссылки, а во-вторых, SQL-запросов к указанным структурам данных; таким как таблицы. Подобная проверка производится даже при компиляции протрамм, на вьшолнение которых имеется разрешение вызывающего.
Автоматически перекомпилируются объекты, помеченные как неработоспособные, на которые имеются ссылки (даже из удаленных процедур).
Считывается DIANA-код объектов, на которые имеются ссылки (в том числе удаленных процедур). Делается это с целью проверки соответствия вызовов объектов их сигнатурам. Если в словаре данных еще нет DIANA-кода объекта, таковой генерируется и сохраняется в этом словаре.
В случае успешной компиляции создается байт-код, который загружается в библиотечный кэш. DIANA-код анонимного блока удаляется.
Компиляция хранимого объекта
Существует несколько вариантов процесса компиляции хранимых объектов (пакета, тела пакета, процедуры, функции, триггера, типа и тела типа).
Перед началом перекомпиляции созданной программы Oracle проверяет, не выполняется ли она в данный момент, н, если это так, ожидает момента ее завершения. Стив Адамс отмечает, что для программы на время выполнения устанавливается режим общего доступа (S), а на время компиляции — режим монопольного доступа (X). Учтите, что эти установки могут стать причиной взаимных блокировок процессов или долгих ожиданий.
Если компилируемый объект представляет собой пакет с информацией о состоянии, Oracle восстанавливает его исходное состояние и, возможно, других пакетов текущего сеанса.
При выполнении DDL-инструкции, например CREATE OR REPLACE PROCEDURE, Oracle сохраняет исходный код в таблице SOURCES даже в случае неудачной компидя- Ции. Кроме того, для каждой хранимой прохраммы она сохраняет в таблице SET- TINGS$ текущие флаги компилятора.
После успешной компиляции любого объекта, кроме тела пакета и тела типа (даже если речь идет о программах, откомпилированных путем встроенной компиляции), его DIANA-код сохраняется в базе данных.
Сохранив DIANA-код хранимой программы, Oracle записывает в таблицу DE・ PENDENCY$ информацию об объектах, от которых эта программа зависит.
Если в программе используются вызовы удаленных процедур, Oracle во время компиляции извлекает их зремениые метки, а также сигнатуры, и сохраняет их вместе с откомпилированным кодом локальной программы.
Если флаги сеанса или базы данных задают режим встроенной компиляции, PL/SQL выполняет обычный анализ синтаксиса и семантики. Затем внутренний транслятор преобразует код PL/SQL в С-код и вызывает С-компилятор операционной системы, генерирующий совместно используемый объектный файл.
Когда откомпилировать хранимый объект не удается, Oracle вносит соответствующую запись в таблицу ERRORS, которую можно просмотреть через представление USER_ERRORS. После успешной компиляции информация о предыдущей ошибке компиляции удаляется из таблицы ERRORS.
При успешной компиляции все варианты откомпилированного кода загружаются в библиотечный кэш. Oracle записывает DIANA-код хранимых программ, байт-код и данные отладки в словарь данных.
Выполнение программ PL/SQL
Записав байт-код в библиотечный кэш, Oracle может приступать к его реализации. Программы PL/SQL выполняются в несколько этапов.
Если пользователь, запускающий программу, ве является ее владельцем, Oracle проверяет наличие у него привилегии EXECUTE.
Когда вызывается хранимая программа с атрибутом AUTHID CURRENT_USER (разрешения вызывающего), исполняющее ядро PL/SQL повторно производит разрешение внешних ссылок на объекты SQL с учетом идентификационных данных вызывающего. Разрешение внешних ссылок на программы PL/SQL повторно не выполняется, если только они не встроены в анонимный блок.
Oracle открывает курсор и связывает его с программой^ даже если это анонимным блок.
Если в коде имеются удаленные процедуры, исполняющее ядро PL/SQL сравнивает локальную копию временной метки или сигнатуры с данными удаленной стороны. Если они не соответствуют друг другу, Oracle сбрасывает состояние текущего сеанса (ORA-04068) и выдает ошибку ORA-04062: timestamp or signature of procedure ,1имя_процедурын has been changed. Вторая попытка разрешения ссылок приводит к автоматической перекомпиляции локальной программы; в случае успеха таковая осуществляется.
Исполняющее ядро PL/SQL производит все необходимые вызовы SQL-процессора Oracle, открывающего и кэширующего курсоры.
Обычно программа PL/SQL реализуется в контексте транзакции, созданной текущим сеансом. Однако если исполняющее ядро вызывает программу, выполняющую автономную транзакцию, ядро приостанавливает текущую тран- занкцию и создает для вызванной программы второй контекст транзакции. После фиксации или отката автономной транзакции выполнение исходной транзакции возобновляется. (Подробнее об этом рассказывается в главе 13.)
Когда вызывающая програлша PL/SQL передает параметры вызываемой программе, создается копия аргументов. Если вызываемая программа не смогла обработать все возникшие исключения, исполнительное ядро возвратит исходные значения артументов. Но если для компилятора установлен режим IN OUT NCOPYj ядро не создает копии аргументов и не возвращает их исходные значения. Вследствие этого некоторые изменения в вызываемой программе могут остаться незавершенными. (Аналогичная ситуация рассматривается в главе 16.)
Исполняющее ядро вызывает для каждой откомпилированной программы PL/SQL совместно используемую библиотеку, уже связанную с исполняемым файлом Oracle (см. главу 19).
Для внешней процедуры Oracle Net создает специальный процесс extproc, посредством которого исполняющее ядро PL/SQL обменивается аргументами с общей библиотекой, содержащей внешнюю процедуру. (Более подробно об этом рассказывается в главах 22 и 23.)
После завершения работы вызываемая программа возвращает информацию о состоянии и/или выходные параметры. Если вызываемой программе не удалось перехватить и обработать абсолютно все исключения, формируется стек ошибок с информацией об этих исключениях, которая обычно предоставляется пользователю.
Клиентский код PL/SQL
В случае применения таких средств разработки приложений Oracle, как Oracle Forms Builder или, скажем, Oracle Reports Builder, пользовательский интерфейс и бизнес-логика приложений чаще всего реализуются на языке PL/SQL. Вы можете использовать их, даже не подключившись к базе данных. Приложив неболь- пгие усилия, вы можете связать эти клиентские приложения с базой данных любого типа, однако такая потребность возникает нечасто. Стандартная конфигурация среды выполнения представлена на рис. 20.10.
Как следует из этого рисунка, клиентская среда времени выполнения, которая на самом деле может работать на промежуточном уровне как «сервер* Oracle Forms, взаимодействует и с серверным исполняющим ядром PL/SQL, и с серверными компонентами, выполняющими SQL. Удаленные вызовы процедур не требуют чтения сервером DIANA-кода вызываемой программы. Средства разработки приложений Oracle предоставляют программисту широчайший набор функций, реализованных во встроенных пакетах. В частности, на клиентском компьютере PL/SQL позволяет выполнять следующие операции:
О определять триггеры, обрабатывающие некоторые события (например, щелчки кнопкой мыши);
О помогать пользователю при вводе данных, осуществляя их автоматический поиск и проверку;
Q создавать код, который будет выполняться при выборе пользователем элемента мешо;
О сохранять данные в локальных файлах н восстанавливать их из файлов с помощью пакета ТЕХТ_Ю;
О программно изменять поведение и внешний вид элементов пользовательского интерфейса.
Рис. 20.10. Клиентская среда выполнения, вызывающая хранимую процедуру
Во всех версиях Oracle, вплоть до Developer 6d PL/SQL поддерживал взаимодействие с клиентской операционной системой. Например, при работе в среде Microsoft Windows можно было непосредственно вызывать DLL, использовать объекты OLE (Object Linking and Embedding), выполнять вызовы функций Windows APL Однако в РСУБД версии Developer 9f все эти специфические для Microsoft возможности были отменены. Без комментариев.
К счастью, непосредственный вызов серверного PL/SQL из клиентской среды PL/SQL выполняется элементарно. Необходимо только (комментарий для новичков) иметь в виду, что клиентские и серверные версии выпускаются не одновременно, и поэтому вам могут быть доступны не все возможности языка. Вы должны знать, какая версия PL/SQL установлена на клиентской машине.
Поддерживаемые версии и функциональные возможности
Для того чтобы узнать, какая версия PL/SQL установлена на клиентской маши* не, достаточно выбрать Developer > Help ► About Перечень всех существующих версий приведен в табл. 20.3, но имейте в виду, что установленные вами пакеты исправлений могут изменить десятичный номер версии.
Таблица 20.3. Соответствие клиентских версий PL/SQL версиям Oracle Developer
Версия Oracle Developer (базовый выпуск)
Версия О га de Forms
Версия PL/SQL
1.x
4.5
1.2
2.Х
5.0
2.3
6.0
6.0.5
8.0.5
6i
6.0.8
8.0.6
9i
9.0.2
9.0.1
Правда, факт установки на клиентской машине той или иной версии PL/SQL еще не означает, что вы получите доступ ко всем соответствующим ей серверным функциям. Например, в клиентской версии PL/SQL 9.0.2 доступны следующие операции:
О объявление и использование в клиентской программе локальных типов коллекций и переменных этих типов;
О применение подтипов, типов коллекций, типов записей и курсорных переменных, объявленных в спецификации пакета, который хранится на сервере;
О определение в клиентской программе переменных типа, объявленного в спецификации серверного пакета, в том числе записей и одноуровневых коллекций;
Q объявление и использование переменных в качестве ссылок на определенные пользователем объектные типы;
Q формирование блока на основе таблицы с атрибутом типа коллекции;
О создание блока на основе таблицы объектов или таблицы с объектным столбцом при условии, что объектный тип не содержит атрибута типа коллекции.
Однако некоторые операции на стороне клиента выполнять нельзя. К их числу, в частности, относятся:
О создание отдельного пользовательского типа (объектного или типа коллекции);
О определение переменной пользовательского типа, объявленного на сервере.
Решить вторую проблему можно двумя способами:
О не использовать неподдерживаемых типов данных в спецификациях серверных модулей;
Q написать хранимую процедуру, которая будет выполнять преобразование типов данных сервера PL/SQL в соответствующие типы данных клиента (где это возможно).
Второй способ, надо полагать, более привлекателен. Если написать хранимые программы, в которых применяются параметры серверных типов данных, спецификацию модуля менять не придется, и вы всегда сможете использовать самые прогрессивные возможности языка.
Однако не следует исключать вероятность того, что в какой-то ситуации вы не в состоянии будете изменить спецификацию (список параметров) хранимого
модуля. Он может быть написан другими программистами, возможно, для другого приложения, и его нельзя будет модифицировать, не изменив само приложение. В этом случае яторой подход сложнее, но все равно применим. Например, если клиентский код, написанный для версии до OracleSi, должен вызывать процедуру с входным параметром типа TIMESTAMP, имеет смысл инкапсулировать ее в другую хранимую процедуру, принимающую значение даты. Фактически эту процедуру можно просто перегрузить. Приведем пример спецификации:
PACKAGE logger
AS
PROCEDURE note_the_time (event_t1mejn IN TIMESTAMP);
PROCEDURE note_the_time (event_timejn IN DATE);
END;~ ~~-
Тело пакета будет содержать следующий код:
PROCEDURE note the_t1me (event_time_in IN DATE) IS
BEGIN ~~"
note_the_t-in)B(IO_TIMESTAMP(event timejn));
ENO:~"
Теперь процедуру 1 ogger. note_the_ti me можно вызывать из клиентских ттро- грамм, написанных для версий до Огас1е9г, поскольку она принимает значение поддерживаемого типа данных,
ВНИМАНИЕ —
Выполняя перегрузку с целью поддержки разных типов данных, не полагайтесь на неявное преобразование типов в теле процедуры. Автор, например, пытался сделать это следующим образом:
PROCEDURE note the_time (eventJinieJn IN DATE) IS
BEGIN
note_the_fin)e(event_time_in); /* Плохая идея */
END; ~~"
В результате был получен бесконечный вложенный цикл, для выхода из которого необходимо уничтожить задачу, выполняющуюся в теневом режиме, с помощью исполняемого файла orakill.exe в Windows или команды kill -9 в Unix.
Следует отметить, что при использовании Oracle Forms 9i такой проблемы с типом данных TIMESTAMP у вас не будет, поскольку эта версия его поддерживает. Однако, как будет показано в следующем разделе, могут возникнуть неприятности с переменными серверных пакетов.
Ограничения, касающиеся удаленных вызовов в Oracle
На момент написания настоящей книги программное обеспечение Oracle не позволяло программе PL/SQL непосредственно использовать такие конструкции удаленного серяера, как переменные (включая константы), курсоры и исключения. Причем ограничение касается не только клиентского PL/SQL, вызывающего сервер базы данных, но и межсерверных яызовоя удаленных процедур.
Простым решением указанной проблемы в отношении переменных является использование программ установки и чтения, инкапсулирующих нужные данные — тем более что это вообще хорошая программистская практика.
Курсоры можно инкапсулировать в программы открытия, выборки и закрытия. Если, предположим, в спецификации серверного пакета book_ma1nt объявлен курсор book^cur, в тело пакета можно поместить такой код:
CREATE OR REPLACE PACKAGE BODY book_maint
AS
prv_book_cur_status BOOLEAN;
PROCEDURE open_book_cur IS,
BEGIN
IF NOT book_maint.book_curXISOPEN
THEN "~
OPEN book_maint.boDk_cur;
END IF; ~"
END;
FUNCTION next_book_rec
RETURN bocisXROWTYPE
IS
1_book_rec booksJROWTYPE;.
BEGIN ~
FETCH book_mai nt.book^cur INTO )_book_rec:
prv_book_cur_status 严 book_main.book_curXFOUND;
fCTJRN 1 book rec;
£也; 一 一
FUWCTIQN book_cur_1s_found
RETURN BOOLEAN- ~
IS
BEGIN
RETURN prv_book_cur_status;
END:"~ "
PROCEDURE cl ose_book_cur
IS"
BEGIN
IF book maint.book curXISOPEN
THEN ""
CLOSE book_mai nt.book_cur;
END IF: "~
END;
KND:
К сожалению, этот подход не годится для перехвата и обработки удаленных исключений, поскольку <тилы данных» исключений интерпретируются иначе, чем остальные типы данных. Поэтому вам придется выполнить процедуру RAISE^AP- PLICATION_ERROR с пользовательскими номерами исключений от -20 000 до -20 999. О том, как написать хороший пакет для перехвата и обработки исключений этого типа, рассказывалось в главе 6.
Клиентские библиотеки PL/SQL
Код PL/SQL на стороне клиента может располагаться в двух местах:
О в PL/SQL-библиотеке многократного использования;
О в модуле приложения (формы, отчета и т. п.).
Библиотека может содержать любое количество процедур, функций и пакетов; это что-то вроде «суперпакетной» структуры. Данная конструкция уникальна для клиентских средств Oracle, и ее использование требует отдельного обсуждения. Главное, что код PL/SQL может располагаться в самых разных местах (табл. 20.4), но только библиотеки содержат код PL/SQL, доступный для совместного использования несколькими клиентскими модулями.
Таблица 20.4. Библиотеки и клиентские файлы, в которых может содержаться код PL/SQL
Расширение имени файла
Описание
,PLL (PL/SQL Library)
Исходный код PL/SQL байт-код процедур, функций и пакетов
.PLX (PL/SQL Ubrary «executable»)
Байт-код соответствующих программных элементов библиотеки
,PLD (叱/SQL Ubrary Text)
Необязательная читабельная текстовая версия программных элементов библиотеки
.FMB (Forms Module Binary)
Любой иосодный код PiySQL а модуле Oracle Forms, хранящийся в двоичном файле вместе с DIANA-предстаалением и 8灯ходом
.FMX (Forms Module “executable»)
Байт-код, соответствующий коду PL/SQL в модуле Oracle Forms
.FMT (Forms module text)
Необязательная текстовая версия модуля Grade Forms; одкак。код PL/SQL представлен только в шестнадцатеричном формате
•RDF (Report Definition Rie)
Любой созданный в модуле Grade Reports исходный код PU5QL хранящийся в двоичном файле вместе с DIANA-предстаалением и байг-кодом
.REP (Report runfile)
Байт-код, соответствующий коду PLVSQL в модуле Oracle Reports
■REX (Report Text File)
Необязательная текстовая версия модуля Grade Reports, включающая читабельный код PL/SQL
.MMB (Menu Module Binary)
Любой созданный в модуле меню исходный код PLJSQL хранящийся в двоичном файле вместе с DIANA-предстаалением и байт-кодом
.MMX (Menu Module executable)
Байт-код, соответствующий коду PL/SQL в модуле меню
•MMT (Menu Module Text)
Необязательная текстовая версия модуля меню
Поскольку библиотеки PL/SQL могут кардинально улучшить архитектуру и повысить производительность клиентских приложений, следующие два раздела посвящены основным концепциям и неочевидным аспектам их использования.
Клиентские библиотеки во время проектирования
Стандартное соглашение, которым обычно руководствуются при разработке клиентского PL/SQL, предполагает наличие в модуле только специфического для него кода, и размещение остального кода, используемого более чем одним моду- лем,в библиотеке. Поместив код в библиотеку, таковую нужно «присоединить» к модулю. Эта задача вьшолняется в среде разработки с помощью программы Object Navigator. Примем библиотечный модуль и файл модуля приложения (например, .fmb) остаются автономными, и при распространении кода нужно скопи^ роватъ в дистрибутив оба эти файла.
Начинающих программистов обычно смущает отображаемый системой запрос об удалении из библиотеки информации о пути. Отвечая на него, вы можете выбрать одну из двух возможностей;
О сохранить полную информацию о пути (такую как c;\apps\forms\libs), поскольку библиотека будет помещена в тот же каталог, где она разрабатывается;
О удалить информацию о пути и предоставить клиентским средствам возможность искать библиотеку в соответствии с установками в системных переменных.
Первый вариант больше подходит для этапа проектирования, а второй, более гибкий, - для этапа выполнения приложения, хотя и требует настройки переменных пути. Во время выполнения поиск пути файла произзодится в определенном порядке.
О Сначала проверяется значение переменной, специфической для данного средства, например FORMS90_PATH, REPORTS90_PATH или GRAPH[CS60_PATH.
О Затем, если переменная не найдена, прозеряется значение переменной ORAC- LE_PATH
В среде Windows присвоить значения этим переменным можно либо с помощью панели управления, либо путем установки нужных параметров в реестр. Задать их значения в командном файле нельзя.
При выполнении разрешения внешних ссылок на программы в тестируемых автором клиентских средствах поиск производится в строгой последовательности:
О клиентские встроенные программы;
О «программные элементы», определенные в самом модуле {.fmb);
О РЬХ-фа^Ьш присоединенных библиотек PL/SQL, ггуть к которым задан в указанных выше переменных среды;
О PLL-файлы присоединенных библиотек PL/SQL (при отсутствии PLX-фай- лов);
О сервер базы данных (с использованием стандартного разрешения имени сервера).
Если на этапе проектирования программы размещены в легко доступных местах, загрузку библиотеки и присоединенного модуля (формы, отчета и т. п.) в среду разработки лучше выполнять одновременно. И не забывайте сохранить модифицированную библиотеку перед использованием в присоединенном к ней модуле новых элементов.
ПРИМЕЧАНИЕ
Если модификация библиотеки и присоединенного модуля выполняется в течение одного сеанса редактирования, то для того чтобы изменения, произведенные в библиотеке, стали доступными в модуле, необходимо сделать следующее. При условии, что во время выполнения приложения используется только РЬЬфайл, библиотеку PL/SQL нужно сохранить (File > Save). Если в том же каталоге имеется PLX-файл библиотеки, ее нужно сгенерировать повторно (File ► Administration ► Compile File кли Program > Compile).
Клиентские библиотеки PL/SQL во время выполнения
Поиск библиотеки PL/SQL на этапе выполнения осуществляется точно так же, как и на этапе разработки: если библиотека не вызывается с явным указанием пути, приложение ищет ее в каталогах, задаваемых переменными среды.
Одной из особенностей библиотек PL/SQL является способ их загрузки в память. Во время выполнения в память сначала считывается только «каталог» библиотеки. Затем, когда модуль в первый раз вызывает конкретную процедуру, функцию или пакет, загружается их код, блоками по 4 Кбайт. Напомним, что при запуске приложения PL/SQL в память помещается весь код модуля.
ПРИМЕЧАНИЕ
Поскольку библиотек! PL/SQL загружаются в память не цзазу и не целиком, а по требованию, размещение больших клиентских программ в б^бл^тегтехах, а не в итдельных файлах модулей приложения, помогает огттимизуроватъ использование памяти.
Если присоединить библиотеку PL/SQL к нескольким формам, они смогут совместно использовать информацию о состояния библиотеки, то есть оперировать переменными пакета как глобальными структурами. Для этого программе CALL_FORM нужно передать специальный аргумент:
CALL_FORM(forrmiodule_name -> 'BOOKS'. data_mode -> SHARE一LIBRARYJWHA);
Если значение параметра data_mode не будет задано, по умолчанию последний будет содержать установку NO_SHARE_LIBRARY_DATA, означающую, что каждая форма создает собственные экземпляры программ и переменных, используемых в библиотеке.
РАЗРЕШЕНИЕ ИМЕН В КЛИЕНТСКОМ PL/SQL
Перенести многократно используемый код в библиотеку PL/SQL достаточно сложно. Объясняется это, в частности, тем, что в библиотечном коде нельзя непосредственно ссылаться на объекты уровня модуля. Компиляция библиотеки должна выполняться успешно независимо от конкретных модулей приложения. Поэтому в модуль Oracle Forms можно включить такую инструкцшо:
IF :ЬсюкЫock.summary IS NULL.
THEN
:bookblock.sunnary 尸'TO BE SUPPLIED1:
END IF:
Здесь ibookblock-SunTnary — это имя поля на экране. Однако если попробовать выполнить те же действия в коде библиотеки PL/SQL, компилироваться она не будет, поскольку в ней ничего ае известно об этом поле.
Поэтому разрешение ссылок на поля нужно как-то отложить до времени выполнения.
Для решения этой проблемы Oracle предоставляет две специализированные встроенные программы: IQAME_IN, считывающую значение элемента, имя которого задается строковым аргументом, и COPY, присваивающую элементу значение. С их помощью предыдущий пример можно переписать ло^ другому:
IF hiAME_IN(':bookblock.summary'} IS NULL•
THEN ~
COPYCTO BE SUPPLIED' ,' :bookblock.sumniary,):
END IF;
Это решает проблему компиляции, но, как и в случае использования любой другой технологии динамического кодирования, создает иную проблему: обнаружить ошибку теперь можно будет лшпь во время выполнения. Компилятор не станет анализировать строку символов и пытаться определить, является ли она реальным именем элемента. Однако если код тщательно протестировать, данную проблему можно отнести к разряду незначительных.
Модели разрешений
Во всех версиях системы вплоть до Oracle8i для выполнения хранимых программ всегда требовалось разрешение владельца, или создателя. Это не имело особого значения, если код и данные приложения создавались пользователем с одной и той же учетной записью. Разрешения, задаваемые для централизованно хранящегося кода, не переносились автоматически на объекты пользователей (называемые также разрешениями вызывающего). Пользователь мог не иметь разрешения на удаление конкретной таблицы, но если для хранимого кода было задано это разрешение, таблица благополучно удалялась! Однако бывают случаи, например, при использовании в программе пакета DBMS_SQL (динамический SQL), когда из-за такого поведения Oracle могут возникнуть определенные сложности.
В Oracle 8.1 у программиста появилась возможность указывать, какие разрешения необходимы для выполнения программы (или всех программ пакета): привилегии создателя (что было единственно возможно в Oracle 8.0 и более ранних версиях) или привилегии вызывающего.
Модель разрешений владельца
Очень важно понимать особенности обеих моделей разрешения на выполнение программ, поскольку во многих приложениях PL/SQL возможно их сочетание. Перед выполнением хранимая программа должна быть откомпилирована и захш- сана в базу данных. Поэтому она всегда сохраняется в конкретной схеме или с конкретной учетной записью пользователя, даже если в ней имеются ссылки на объекты из других схем.
вила.
О Разрешение всех внешних ссылок в программе выполняется во время компиляции, с использованием непосредственно предоставленных привилегий схемы, в которой эта программа компилируется.
О Роли базы данных действительны при компиляции анонимных блоков, но полностью игнорируются при компиляции хранимых программ.
При запуске программы, откомпилированной с использованием модели разрешений создателя (по умолчанию), ее SQL-инструкции выполняются с разрешениями схемы, которой принадлежит эта программа.
Хотя на выполнение компиляции программы требуется особое разрешение, привилегию EXECUTE, позволяющую выполнять эту профамму, можно предоставить другим схемам и ролям.
На рис. 20.11 показано, как использовать модель разрешений, создателя для управления доступом к объектам базы данных. Все данные заказа хранятся в схеме OEData, а код, выполняющий ввод заказов, хранится в схеме OECode. Владельцу схемы OECode предоставлены непосредственные привилегии, необходимые для компиляции пакета Order_Mgt и позволяющие вводить и удалять заказы.
Рис. 20.11. Управление доступом к данным с помощью модели разрешений создателя
Для обеспечения возможности корректного обновления таблицы заказов необходимо запретить прямой доступ к ней (будь то через роли или привилегии) из всех схем, кроме OECode. Предположим, что из схемы Sam^Sa les нужно просмотреть все открытые заказы и закрыть те из них, которые были сделаны раньше определенной даты. Ассоциированный с этой схемой пользователь Sam не может выполнить инструкцию DELETE из процедуры Close_Old_Orders; вместо этого он должен вызвать процедуру Order_Mgt.cancel.
Преимущества модели разрешений создателя
В некоторых ситуациях вам просто не обойтись без модели разрешений создателя, обладающей в сравнении с моделью с разрешениями вызывающего рядом преимуществ.
О Разработчик имеет больше возможностей для управления доступом к структурам данных и может быть уверен, что единственным способом изменения содержимого таблицы является использование созданного им программного интерфейса (обычно пакета).
О Производительность приложения значительно выше, поскольку ядру PL/SQL не приходится во время выполнения проверять наличие разрешений на доступ к объектам или, что не менее важно, выяснять, с какой именно таблицей должна работать прохрамма (поскольку одна таблица может очень отличаться от другой таблицы accounts!).
О Не нужно беспокоиться о том, что программа может обратиться не к той таблице. При использовании разрешений создателя программный код будет обрабатывать те же структуры данных, с которыми работали бы SQL-инструкции, выполняемые из SQL*Plus (или другого приложения).
Недостатки модели разрешений создателя
Однако использование модели разрешений создателя может вызвать определенные проблемы. О них рассказывается в следующих разделах.
Куда исчезла таблица
Попытаемся разобраться, какое значение имеют перечисленные правила использования модели разрешений создателя для разработчика прохрамм PL/SQL в его повседневной работе. Разработчики нередко пишут код, который выполняет обработку таблиц и представлений из других схем, создавая для них общие синонимы, которые попросту скрывают схемы. Привилегии при этом предоставляются посредством ролей базы данных.
Эта очень распространенная модель имеет несколько нехороших особенностей. Предположим, что в сети вашей компании управление доступом к объектам реализуется посредством ролей. При работе с таблицей accounts вы можете спокойно выполнить в SQL*Plus такой запрос:
SQL> SELECT account#, паше FROM acccwnts;
Однако если эту же таблицу (и даже тот самый запрос) указать в процедуре, PL/SQL выдаст два сообщения об ошибках:
SQL> CREATE OR REPLACE PROCEDURE show accounts
IS~
BEGIN'
FOR rec IN (SELECT account*, name FROM accounts)
、5 LOOP
DBMS^anpUT.pUTjiNE (гес.падк);、
E隅 LOOP; -
END;
/
Warning: Procedure created with compllatfoi errors.
SQL> sho err■
Errors for PROCEDURE SHOW_ACCDUNTS:'
LINE/COL ERROR
4/16 PL/SQL: SOL Statement ignored
4/43PLS-00201: identifier 'ACCOUVTS' must be declared
Странно, не правда ли? Проблема заключается в том, что таблица accounts на самом деле входит в состав другой схемы; для получения доступа к данным вы использовали синонимы и роли, но Это не сработало. Так что если вы когда-нибудь столкнетесь с подобной ситуацией, просто обратитесь к администратору базы данных или владельцу объекта и попросите предоставить вам необходимые привилегии,
Как поддерживать весь этот кед
Предположим, что в каждом филиале вашей компании используется собственная схема базы данных. Такая ситуация может сложиться в том случае, если в каждом филиале производится сбор и обработка специфических, характерных только для него данных. Во всех схемах имеются собственные таблицы с одинаковой структурой, но с разными данными. (С точки зрения проектирования базы данных это неправильно, но что поделаешь — так уж получилось.) Теперь мы хотели бы так адаптировать код, чтобы сопровождение приложения требовало минимум времени и усилий. Для этого можно поместить его в одну схему и предоставить доступ к нему из объектов всех схем филиалов.
Однако при использовании модели разрешений создателя так поступить невозможно. Если поместить код в определенную схему и задать для объектов всех схем филиалов право на выполнение EXECUTE, все они смогут работать с любым набором таблиц, указанных в центральной схеме (с набором одного филиала или же с набором таблиц-макетов). Но так делать не стоит. Придется добавлять весь наш код в каждую региональную схему, как показано на рис. 20.12.
Не имеет смысла говорить, в какой кошмар превратится сопровождение и модификация такого кода. Конечно же, модель разрешений вызывающего должна обеспечить лучшее решение.
Динамический SQL и разрешения создателя
Еще одним типичным источником проблем, связанных с разрешениями создателя, является динамический SQL (описан в главе 15). Предположим, у нас имеется универсальная программа для вьшолнения DDL-инструкций:
/* Файл в web: execddl.sp */
CREATE OR REPLACE PROCEDURE execDDL (ddl_String IN VARCHAR2)
BEGIN
EXECUTE IM1EDIATE ddl_str1ng:
EXCEPTIOM~
WHEN OTHERS
THEN
DBMS OUTPUT.PJT_LINE ('Dynamic SQL Failure: ' || SQLERRM);
dbms2output.put_line (
'on statement: "' 11 ddl_string ||
RAISE;~
END;
Южный офисЮжный офисРис. 20.12. Многоразовая инсталляция кода при использовании модели разрешений саадгтеляРис. 20.12. Многоразовая инсталляция кода при использовании модели разрешений саадгтеля
Южный офис
Южный офис
Рис. 20.12. Многоразовая инсталляция кода при использовании модели разрешений саадгтеля
Рис. 20.12. Многоразовая инсталляция кода при использовании модели разрешений саадгтеля
Протестировав этот код, мы решили, что им мохут пользоваться и остальные разработчики. Откомпилировав его в схеме COMMON, где обычно хранится весь общедоступный код, мы передали разрешение на его вьшолнение пользователю с привилегией public, создали общий синоним, после чего разослали по электронной почте сообщение о новой замечательной программе.
Несколько недель спустя наверняка начнут поступать сообщения от коллег типа: <Я просил создать таблицу, и код выполнился без ошибок, но таблицы по* чему-то нет» ияи <Я просил удалить таблицу, но программа ответила, что таковой не существует». В общем идея ясяа. В аналогичной ситуации в свое время очутился и автор настоящей книги. Поначалу он засомневался в такой технологии совместного использования кода, но потом решил провести некоторые исследования.
Получив доступ к объектам схемы COMMON, автор обнаружил там все объекты, которые пользователи пытались создать с помощь его процедуры. Оказывается, если при вызове процедуры execDDL пользователь попросит создать таблицу и не укажет имя собственной схемы, процедура создаст таблицу схемы COMMON. Что, впрочем, и не удивительно.
Иными словами, вызов
SQL> EXEC execDDLC'CREATE TABLE newone(r1ghtnw DATE)')
создаст таблицу newone схемы COMMON. Вызов процедуры execDDL
SQL> EXEC execDDLC'CREATE TABLE Scott.newoneCrightnow DATE)1)
мог бы решить проблему, но он, к сожалению, завершается следующим сообщением об ошибке (если только для схемы COMMON не предоставлена привилегия CREATE ANY TABLE):
DRA-01031: Insufficient privileges -
Увы, такая хорошая процедура, а предоставить ее в распоряжение других про- траммисгов, не создав им и себе дополнительных проблем, невозможно. А как было бы здорово, если бы все могли запускать ее как свою, с собственными привилегиями (а не с привилегиями схемы COMMON), не устанавливая собственную копию кода этой процедуры. ■
Модель разрешений на выполнение программ
Для того чтобы помочь разработчикам преодолеть проблемы, связанные с использованием модели разрешений создателя, в Oracle 8.1 была введена альтернативная модель разрешений на выполнение программ, а именно модель разрешений аызывающега. Суть ее заключается в том, что разрешение всех внешних ссылок в SQL-инструкциях и программах PL/SQL выполняется в соответствии с привилегиями схемы вызывающего программу пользователя, а не владельца схемы, в которой данная программа определена.
Принципиальное отличие модели разрешений вызывающего от модели разрешений создателя иллюстрирует рис. 20.13. Вспомните описываемый выше пример (рис. 20.12), когда для получения возможности обрабатывать таблицу копии приложения пришлось поместить в схемы всех филиалов. Модель разрешений вызывающего исключает такую необходимость. Теперь весь код можно поместить в единое централизованное хранилище. И когда пользователь из Северного региона вызовет централизованно хранящуюся программу (возможно, воспользовавшись синонимом), она автоматически получит доступ к таблицам соответствующей схемы.
蛇Ж^щш|
Хранилище хода
务岁法邑霹星整强條噺唯瓢認甥及; 胃^療i^e圖%■:碱轨:!^»^硝崛/.號綴^腻大瞬.汹占话:嘛豳咄照.:痣i
Рис 20.13. Использование модели разрешений вызывающего
Как видите, идея модели разрешений вызывающего очень проста. Рассмотрим, как она реализуется программно, а затем поясним, как ее лучше использовать.
Синтаксис модели разрешении вызывающего
Синтаксис поддержки модели разрешений вызывающего очень прост. В заголовок программы, перед ключевым словом IS или AS, добавляется следующее предложение:
AUTHID CURRENT_b$ER
В качестве примера рассмотрим все ту же процедуру execDDL, но на этот раз объявленную с разрешениями вызывающего:
/* Файл в web: execddl.sp */
CREATE OR REPLACE PROCEDURE execDDL (ddl_Str1ng IN VARCHAR2)
AUTHID CURRENT_USER IS~
BEGIN
EXECUTE IMMEDIATE ddl_string;.
END;~
Предложение AUTHID CURREtn_U5ER перед ключевым словом IS указывает, что при выполнении процедуры execDDL должны использоваться привилегии вызывающего или текущего пользователя, но не привилегии ее создателя. Если не включить в программу предложение AUTHID или задать в нем параметр AUTHID DEFINER, разрешение ссылок будет выполняться в соответствии с привилегиями схемы-владельца про1раммы.
РАЗРЕШЕНИЯ ВЫЗЫВАЮЩЕГО ДЛЯ ДИНАМИЧЕСКОГО SQL
Автор написал сотни программ с использованием динамического SQL, но до появленияОгас1е 8.1 ему постоянно приходилось беспокоиться о разрешениях, функциональных возможностях и содержимом схем: в какой схеме выполняется программа? кто ее вызывает? что произойдет, если кто-нибудь ее запустит? Вечные вопросы!
Возможно, у вас появится мысль включать предложение AUTHID CURRE^T_ USER в каждую хранимую программу, в которой используется динамический SQL. Сделав так, вы сможете пребывать в полной уверенности, что в какой бы схеме не была откомпилирована программа и кто бы ее ни запустил, она всегда будет работать в текущей схеме.
Все это так, но есть одна проблема: при запуске программы с разрешения вызывающего необходимо производить ряд дополнительных проверок, что может отрицательно сказаться на производительности системы. Поэтому модель разрешении вызывающего следует использовать продуманно.
Ограничения на использование модели разрешений вызывающего
При выборе данной модели разрешений нужно помнить о существовании ряда правил ее применения и всегда их выполнять. Не следует игнорировать и установленные ограничения.
О По умолчанию для программ задается параметр AUTHID DEFINER.
О В случае применения любых SQL-ссылок на объекты базы данных модель разрешений вызывающего требует проверки во время выполнения программы привилегии, непосредственно предоставленных вызывающему.
О При использовании модели разрешении вызывающего роли действуют во время выполнения программы, если только программа с разрешениями вызывающего не была вызвана из программы с разрешениями создателя.
О Предложение AUTHID может содержаться только в заголовках отдельных программ (процедур или функций), в спецификации пакета или в спецификации объектного типа, но его нельзя задавать в отдельных программах пакета или методах объектного типа.
О Разрешение внешних ссылок с привилегиями вызывающего будет действовать для следующих видов инструкций:
инструкций языка манипулирования данными SELECT, UPDATE, INSERT и DELETE;
инструкции управления транзакциями LOCK TABLE;
инструкций управления курсорами OPEN и OPEN FOR;
инструкций динамического SQL EXECUTE I刪EDIATE и OPEN FOR USING;
SQL-инструкции, проанализированных с помощью процедуры DBMS_SQL. PARSE.
О Для разрешения всех внешних ссылок на программы PL/SQL и методы объектных типов во время компиляции всегда используются привилегии создателя.
О Привилегии вызывающего можно использовать для изменения разрешения ссылок на статические элементы данных (таблицы и представления).
Привилегии вызывающего могут использоваться для разрешения внешних ссылок на программы PL/SQL. Вот один из примеров того, как это делается:
EXECUTE IMMEDIATE 'BEGIN програннз: EW.':
В данном операторе ссылка программа будет разрешена во время выполнения программы в соответствии с привилегиями и пространством имен вызывающего. В качестве альтернативы вместо анонимного блока можно использовать SQL-оператор CALL. (Непосредственно в PL/SQL этот оператор применять нельзя, поскольку здесь он не поддерживается.)
Комбинированная модель разрешений
Как вы думаете, что произойдет, если программа с разрешениями создателя вызовет программу с разрешениями вызывающего? А если наоборот? Все очень просто.
О Если программа с разрешениями создателя вызовет протрамму с разрешениями вызывающего, при выполнении такой программы будут действовать привилегии вызывающей программы.
О Если программа с разрешениями вызывающего вызовет программу с разрешениями создателя, при выполнении такой программы будут действовать при-
,вилегии ее владельца. Когда управление будет возвращено вызывающей про- 1рамме, возобновится действие разрешений вызывающего.
Иными словами, разрешения создателя «сильнее» разрешений вызывающего.
На узле издательства O'Reilly имеется несколько файлов, которые помогут вам изучить нюансы использования модели разрешений вызывающего.
О invdefinv.sql и Invdefinv.tst. Эти два сценария демонстрируют преобладание разрешений создателя над разрешениями вызывающего.
О invdef_overtiead.tst Демонстрируются издержки использования модели разрешений вызывающего (выполнение разрешения ссылок в процессе компиляции дешевле, чем в процессе выполнения, так как требует меньше времени).
О invrole.sqL Показано, как измекение ролей может отразиться на разрешении ссылок на объекты во время выполнения программы.
О IrdynsqUsql. Описаны некоторые сложности, связанные с использованием обеих моделей с динамическим SQL.
Аппаратное обеспечение для PL/SQL: больше — лучше?
В завершение главы речь пойдет об аппаратном обеспечении сервера и его влиянии на производительность программ PL/SQL. Как правило, аппаратная часть Сервера имеет одну из следующих конфигураций:
О однопроцессорная система;
О симметричная многопроцессорная система;
О <кластерная» система.
Как и любое другое программное обеспечение, которое выполняется в симметричной многопроцессорной или кластерной системе, Oracle использует дополнительные процессоры отнюдь не автоматически. Их эффективное применение возможна лишь при условии, что программное обеспечение разработано специально для распределения вычислителькых задач между процессорами. Проп)аммисты Oracle хорошо потрудились, и теперь этот продукт с успехом использует возможности мультипроцессорной обработки.
Однако такая возможность поддерживается не всеми версиями Oracle. Для того чтобы иметь возможность применять мультипроцессорное аппаратное обеспечение, необходимо установить версию Oracle Enterprise Edition или Personal Edition. Единственным исключением является массовая загрузка данных: во всех версиях Oracle при выполнении этой операции происходит распараллеливание нагрузки между процессорами. Для использования возможностей кластерных систем нужна не только Oracle Enterprise Edition, но и дополнительная лицензия на компонент, называемый в Oracle% Real Application Clusters (RAC), а в предыдущих версиях РСУБД — Oracle Parallel Server (OPS).
Но смогут ли дополнительные затраты на аппаратное обеспечение повысить производительность PL/SQL? Чтобы ответить на данный вопрос, рассмотрим все три базовые конфигурации более подробно.
Однопроцессорная система
Если вы, занимаясь индивидуальной разработкой приложений, установили Oracle на настольном компьютере, то, вероятнее всего, при реализации всех вычислительных задач, в том числе и программ PL/SQL, будете использовать только один процессор. Ведь для работы большинства приложений объема оперативной памяти и памяти на жестких дисках однопроцессорной системы вполне достаточно. Узким местом таких систем является быстродействие центрального процессора. Способ модернизации компьютера в этом случае очевиден: установление более мощного процессора. Однако если тщательно спроектированное приложение выполняется на компьютере с достаточным объемом памяти и высоким быстродействием устройств ввода-вывода, мощная однопроцессорная система может позволить Oracle обслуживать сотни одновременно работающих пользователей (по крайней мере для некоторых приложений).
Если установить в однопроцессорный компьютер более мощный процессор, производительности PL/SQL почти наверняка повысится, но на быстродействие системы ввода-вывода такого рода модернизация не окажет существенного влияния.
Симметричные мультипроцессорные системы
Для решения более сложных задач требуются вычислительные системы, обычно включающие не менее двух процессоров. Конфигурация, при которой процессоры имеют общую память, совместно используют устройства ввода-вывода и работают под управлением единой операционной системы, называется симметричной.
Опыт показывает, что не все программы в многопроцессорных системах выполняются быстрее, чем в однопроцессорных. Например, программа PL/SQL, которая содержит SQL-инструкцию, сортирующую таблицу объемом в миллион строк, в многопроцессорной системе будет выполняться быстрее, чем в однопроцессорной. Однако приложение, выполняющее обработку единичных з^шсей, вряд ли будет быстрее функционировать в многопроцессорной системе. В общем случае системы параллельного действия больше подходят для выполнения задач, связанных с принятием решений, чем, скажем, системы интерактивной обработки транзакции.
Предположим, что вы для создания очень большого хранилища данных приобрели многопроцессорную машину с соответствующей операционной системой и лицензированной версией Oracle Enterprise Edition или Personal Edition. Теперь вам предстоит написать программу PL/SQL. Сумеете ли вы воспользоваться преимуществами многопроцессорной обработки? Возможно, но лишь при условии, что ваша программа будет соответствовать определенным требованиям. Перечислим основные из них.
О Программа должна быть реализована как функция, а не как процедура или анонимный блок.
Q Она должна вызываться из SQL-инструкции, которую оптимизатор Oracle в любом случае будет выполнять в параллельном режиме.
Q В функции не должны применяться инструкции INSERT, UPDATE или DELETE, которые модифицируют информацию в базе данных (причем это требование невозможно обойти посредством вызова другой программы).
О Функция не должна читать или модифицировать данные пакета, поскольку информация о его состоянии не может совместно использоваться несколькими параллельно выполняющимися экземплярами программы.
Итак, Oracle может выполнять копии программы PL/SQL на разных процессорах, но лишь в том случае, если это вычислительная программа, выполняемая как вспомогательный компонент SQL-инструкции. Более подробно о параллель' ном выполнении хранимых функций рассказывается в главе 16.
Кластерные системы
Кластеризация - это процесс объединения нескольких компьютеров, каждый из которых имеет собственный процессор и память, благодаря чему они могут параллельно выполнять большие и достаточно независимые задания. Ранее необязательный компонент для поддержки кластеризации назывался Oracle Parallel Server (OPS). В Oracle9i используется новая технология, названная Real Application Clusters (RAC). Обе технологии предполагают функционирование в разных кластерах взаимодействующих между собой экземпляров РСУБД, которые вместе обрабатывают запросы приложений и используют общую базу данных.
Одним из главных преимуществ RAC перед OPS является простота, с которой приложения могут использовать возможности дополнительного аппаратного обеспечения. В документации Oracle утверждается, что «приложения можно масштабировать, не меняя ни строчки кода». Однако в примечании указано, что это возможно при соблюдении двух условий:
О проектирование базы данных в обязательном порядке производится с учетом технологии RAC;
О приложения разбиваются на отдельные части, каждая из которых может выполняться на отдельном узле.
По мере увеличения размера базы данных систему можно масштабировать, добавляя в кластер новые узлы. Однако увеличение количества пользователей наверняка потребует изменения архитектуры приложения. Честно говоря, у нас не было случая проверить эти утверждения Oracle.
Еще одним несомненным преимуществом кластерных систем является их надежность. База данных будет поддерживаться в рабочем состоянии даже в том случае, если один из компьютеров кластера выйдет из строя. Некоторые из функций OPS и RAC устанавливаются автоматически, тогда как для других требуется дополнительное конфигурирование базы данных, В Oracle8i применяемый для этой цели компонент назывался Oracle Parallel Fail Safe, а в Oracle9i он был заменен компонентом Real Application Clusters Guard. Мы не станем рассказывать о данных технологиях даже несмотря на их исключительную важность, так как это выходит за рамки книги. Достаточно подробная информация о них содержится в специальной документации по Oracle.
Что вам нужно знать
Вам действительно нужно помнить все, о чем рассказывалось в данной главе? Думаем, что нет, хотя у любого администратора базы данных на этот счет имеется собственное мнение. Главная задача главы — расширить ваши представления об
Oracle и о том, что собой представляют процессы компиляции и выполнения кода. Однако наряду с изложением информации, просто расширяющей кругозор читателя, здесь освещается ряд важных тем, которые нужно усвоить как следует. Примите во внимание следующие рекомендации.
О Во избежание дополнительных затрат на перекомпиляцию код, предназначенный для многократного использования, следует реализовывать в виде хранимых программ, а не записывать файлы как анонимные блоки.
О Помимо уникальной способности пакетов PL/SQL сохранять параметры состояния в течение всего сеанса у них есть и другие достоинства. В частности, объединение кода в пакеты позволяет повысить его производительность. Поэтому наиболее интенсивно использующуюся логику приложения следует реализовывать в виде программ, входящих в состав пакетов.
О Если вы не хотите изучать структуру словаря данных Oracle, поищите подходящую утилиту для разработчиков.
О Хотя автоматическое управление зависимостями избавляет программистов от многих проблем, обновление приложений в процессе их эксплуатации следует выполнять очень осторожно.
О В Oracle реализованы сложные технологии, предназначенные для минимизации ресурсов компьютера, которые необходимы для выполнения программ PL/SQL. Однако для их поддержки иногда требуется небольшая помощь разработчиков или администратора базы данных (например, когда речь вдет о явном освобождении неиспользуемой пользователем памяти или закреплении объектов в памяти).
О Открыв явный курсор в программе PL/SQL, обязательно закройте его сразу по окончании выборки данных.
О Встроенные средства компиляции PL/SQL не всегда влияют на производительность приложений, в которых интенсивно используются S QL-инструкции, однако они значительно повышают производительность программ, выполняющих большой объем вычислений.
О К вызову удаленных пакетов предъявляются особые требования, если вы не обращаетесь к процедурам, функциям, типам и подтипам, определенным в пакете.
О В случае применения клиентских средств Oracle совместно используемый клиентский код, устанавливаемый в различных компьютерных системах, следует помещать в библиотеки PL/SQL.
Q Для повышения производительности приложений и упрощения процедуры управления привилегиями на доступ, к объектам базы данных лучше использовать модель разрешений создателя. Однако не стоит отказываться и от модели разрешений вызывающего, которая может помочь при решении ряда проблем (скажем, связанных с управлением программами, которые посредством динамического SQL создают и удаляют объекты базы данных.)
О Повысить производительность PL/SQL можно путем применения более быстрых процессоров, установления памяти большего объема и более быстродействующей системы ввода-вывода. Для решения сложных задач целесообразно использовать системы параллельного действия, однако обойтись без мощных процессоров все равно не удастся
)1 Объектно
/ I ориентированные возможности PL/SQL
А Обзор объектных возможностей Grade
А Пример объектного приложения
А Объектные представления
А Сопровождение объектных типов и объектных представлений
А О целесообразности применения объектно-ориенлированного подхода
PL/SQL. всегда был языком, поддерживающим традиционные процедурные стили программирования, в частности структурное проектирование и функциональную декомпозидию. Пакеты PL/SQL позволяют использовать также объектноориентированный подход, применяя в работе с реляционными таблицами принципы абстракции и инкапсуляции.
В более новых версиях Oracle введена непосредственная поддержка объектно- ориеншрованного программирования (ООП). Программистам стали доступны богатая и сложная система типов, их иерархия, а также возможность вэаимоподста- новки.
Хотя тема объектно-ориентированного программирования в Oracle могла бы стать предметом отдельной книги, мы можем привести лишь несколько примеров программного кода, демонстрирующих наиболее значительные аспекты объектноориентированного программирования на PL/SQL:
О создание и использование объектных типов;
О наследование и взаимозаменяемость;
О эволюция типов;
Q излечение данных на основе REF-ссылок;
О объектные представления.
В этой главе приведены синтаксические диаграммы S QL-инструкций, рассмотрены принципы администрирования баз данных (импорт и экспорт объектных
данных), а также вопросы, касающиеся хранения данных на диске. Начнем главу с краткого исторического экскурса.
Обзор объектных возможностей Oracle
Впервые появившиеся в 1997 году как дополнение к Огас1е8 (так называемой объектно-реляционной базе данных) объектные возможности позволили разра^ ботчикам расширить набор встроенных типов данных Oracle, включив в него абстрактные типы данных. Помимо них в Огас1е8 были введены определяемые программистом коллекции (о которых рассказано в главе 11), оказавшиеся очень полезными не только потому, что разработчики давно уже искали способы хранения массивов в базах данных, но еще и потому, что PL/SQL предоставил новый способ запрашивания информации из коллекций как из таблиц. Объектная модель Oracle обеспечивает много интересных возможностей, в частности доступ к данным с помощью указателей, но она не поддерживает ни наследования, ни .динамического полиморфизма, и поэтому объектно-ориентированные средства
Огас1е8 вряд ли найдут сторонников среда приверженцев настоящего ООП. Сложность и низкая производительность объектных функций также не способствуют их успеху.
В Oracle8i была введена поддержка хранимых процедур Jav& которые позволили работать с РСУБД при помощи не столь специализированного языка, как PL/SQL, и стали для приверженцев ООП аргументом в пользу разработки хранимых процедур. Oracle предоставила способ преобразования объектных типов, определенных на сервере, в Java-классы, тем самым обеспечив им переход границы Java-база данных Версия Огас1е8: вышла в период наивысшего интереса к языку Java, и поэтому немногие заметили, что объектные функции Oracle мало изменились —разве что Oracle начала потихоньку интегрировать их с базовым сервером. Это означает, что для использования объектных функций больше не требуется дополнительная лицензия. В то время один из представителей Oracle сказал о будущем ООП на языке PL/SQL: «Если вам требуется настоящее объектно-ориентированное программирование, пользуйтесь Java».
Однако в Oracle9i встроенная поддержка объектов была значительно расширена, так что пуристы ООП могут уже начинать к ней присматриваться. Введена поддержка наследования и полиморфизма в базах данных, PL/SQL оснащен новыми объектными средствами. Имеет ли смысл расширять объектную модель системы на структуру базы данных? Следует ли переписать существующие приложения клиентского и промежуточного уровней? Как показано в табл. 21」,Oracle осуществила огромный прорыв в ООП, и переход на эту технологию может показаться очень заманчивой идеей. Наряду с поддерживаемыми элементами (отмечены звездочкой) в таблице перечислены возможности, которые пока не реализованы, но были бы очень желательны.
Если вы не имеете большого опыта объектно-ориентированного программирования, многие термины из табл. 12.1 могут быть вам не знакомы. О том, что они означают, рассказывается далее в этой главе.
Таблица 21.1. Объектно-ориентированные возможности различных версий базы данных Grade
Средства и возможности
OradeS
Oracle8l
Orade9l Release!.
Oracle9l
Release 2
Абсграктные тину данных как сущности базы данных верхнего уровня
*
*
*
Абстрактньсе типы данных как параметры PL/SQL
*
*
*
Атрибуты типа коллекции
*
*
*
*
Атрибуты wna REF для навигации по объектам внутри базы данных
*
*
*
*
Реализация логики методов в PL/SQL или С
*
*
*
*
Определяемая программистом семантика сравнения объектов
*
*
*
*
Представление реляционных данных как данных объектных типов
*
隼
*
*
Статический полиморфизм (перегрузка методов)
*
*
*
Возможность «эволюции» типа путем модификации логики существующих методов или добавления методов
»
*
*
*
Реализация логики методов в Java
*
*
«Статаческие» методы (выполняемые беэ экземпляра объапа)
*
*
*
Первичный ключ таблицы реляционной базы данных может служить идентификатором хранимого объекта, обеспечивая декларативную целостность REF-ссылок
*
*
*
Наследование атрибутов и методов от определяемого пользователем типа
*
Динамическая диспетчеризация методов
*
Супертипы, для которых не могут создаваться экземпляры, подобные «абстрактным классам» Java
*
*
Возможность эволюции тмоа путем удаления и повторного добавления метода (для изменения сигнатуры)
*
*
продолжение^
Таблица 21.1 (продолжение)
Средства и возможностиOradeS
0racle8l
0racle9l
Release 1
0racle9f
Release 2
Возможность эволюции типа путем добавления и удаления атрибутов, автоматическое распространение изменений на связанные структуры физической базы данных
*
*
«Анонимные» типы: ANYTYPE, ANYDATA, ANYDA7ASET
*
Операторы приведения базовое типа к дочернему (TREAT) и определения типа (Б OF), доступные в SQL
•
*
Операторы TREAT И IS OF в PL/SQL
*
Определяемые пользователем конструкторы
«
Личные атрибуты, переменные, константы и методы
Наследование от нескольких супертипоа
Совместное использование объектных типов или экземпляров в распределенных базах данных без обращения к объектным представлениям
Пример объектного приложения
Пример, к которому мы будем обращаться на протяжении всей главы, основан на новых возможностях, введенных в Огас1е9г. Многое, о чем говорится в этой главе, относится только к Oracle9t Release 2, поэтому если вам требуется обеспечить объектную ориентацию сервера, никакая из версий ниже Oracle9i для этой задачи не подойдет.
Иерархия типов
Идея для примера, рассмотренного в этой главе, взята из книги Learning Oracle PL/SQL Programming (издательство O'Reilly). Мы построим систему на базе Oracle, в которой с помощью объектно - ориентированного подхода реализован обыкновенный библиотечный каталог. В каталоге хранится информация о книгах, периодических изданиях (журналах и газетах) и непечатных изданиях.
Графическое представление типов верхнего уровня каталога можно увидеть на рис. 21.1. В последующем иерархия типов будет дополнена объектами, которые показаны пунктиром.
Тип
ТЬл
Периодическоеиздание
Лодггтк *№ПОР^П j 6伽10Mdl№М8*а :Лодггтк *№ПОР^П j 6伽10Mdl№М8*а :「asm「asm
Лодггтк *№ПОР^П j 6伽10Mdl№М8*а :
Лодггтк *№ПОР^П j 6伽10Mdl№М8*а :
「asm
「asm
Рии 21.1. Иерархия типов обычного библиотечного каталога
Создание базового типа
Корневой элемент (вершина иерархии) представляет общие характеристики всех подтипов. Предположим, что у книг и периодики имеются только две общие характеристики: библиотечный идентификационный номер и название. Таким образом, объектный тип для элемента каталога можно создать в SQL*Plus с помощью следующей S О,Ь-инструкции:
CREATE OR REPLACE TYPE catalogjtem_t AS OBJECT (
id INTEGER.
title VARCHAR2(4000).
NOT INSTANT1ABLE MEMBER FUNCTION ck digit_okay
RETURN BOOLEAN,-
MEM8ER FUNCTION print
RETURN VARCHAR2 > №)T 1NSTANTIABLE NOT FINAL;
Эта инструкция создает объектный тип, подобный классу Java или C++. Если провести аналогию с реляционкой моделью, объектный тип подобен типу записи с набором связанных с ним функций и процедур (в объектной терминологии методов).
Ключевые слова NOT FINAL в конце определения типа указывают, что он может служить базовым или суп^типом, от которого будут тфоизводиться другие типы, В данном случае они использованы, поскольку мы собираемся создать подтипы для книг и периодических изданий; если опустить эти ключевые слова, по умолчанию Oracle использует только слово FINAL, означающее, что создание подтипов на основе данного типа не допускается.
Обратите внимание на то, что спецификация данного типа содержит предложение NOT INSTANTIABLE, Это означает, что PL/SQL позволит объявлять переменные типа catalog_ttem_t, но им нельзя будет присваивать значения - ни явно, ни другим способом, Подобно абстрактному классу Java, данный тип предназначен исключительно для использования в качестве базовой основы для создания подтипов (для которых, конетао, можно создавать и экземпляры объектов).
Для примера и удобства отладки мы включили в определение типа метод print (кстати, это не зарезервированное слово), позволяющий описать объект одной строкой. При создании подтипа метод будет перегружен, то есть подтип будет содержать метод с тем же именем, но возвращающий атрибуты подтипа. Заметьте, что вместо того, чтобы определить этот метод как процедуру и жестко закодировать в нем вызов процедуры подобно DBMS_OUTPUT.PUT^LINE, он реализован в виде функции, вывод которой при желании можно будет переориентировать. Ничего <объектно-ориентированного> в этом решении нет — просто хорошая идея.
К тому же определен метод ck_digit_okdy, возвращающий TRUE или FALSE в зависимости от того, совпала ли контрольная цифра. Предполагается, что все объекты подтипов catalogjtem^t будут содержать идентификаторы, которые отличаются от библиотечных и включают так называемую контрольную цифру. Поскольку мы собираемся работать только с книгами и периодикой, а они обычно идентифицируются кодами ISBN и ISSN, то к этим подтипам применима концепция контрольной цифры.
Прежде чем мы перейдем к следующей части примера, обратите внимание на следующие моменты.
О Приведенная выше инструкция CREATE TYPE создает спецификацию объектного типа. Соответствующее тело типа с реализацией методов будет создано отдельно с помощью инструкции CREATE TYPE BODY.
Q Для объектных типов используется то же пространство имен, что для таблиц и программ PL/SQL верхнего уровня. Это одна из причин, по которым мы используем в именах типов префикс <_t>,
О Объектные типы всегда должны принадлежать создавшему их пользователю (схеме) Oracle, который может предоставлять на вих привилегию EXECUTE другим пользователям.
О Синонимы объектных типов поддерживаются только в Oracle9z Release 2.
О Как и обычные программы PL/SQL, объектные типы можно определять с разрешениями создателя (по умолчанию) или вызывающего (см. главу 20).
О В отличие от объектных моделей других языков, в объектной модели Oracle отсутствует корневой класс, от которого должны образовываться все остальные определяемые программистом классы, Вместо этого можно создать любое количество независимых типов данных корневого уровня, например cata- log_item_t.
О Если вы получили ошибку компиляции PLS-00103: Encountered the symbol ";" when expecting one of the following..., то, скорее всего, завершили методы точкой с запятой, в то время как в спецификации типа необходимо в качестве символа разделителя использовать запятую.
Создание подтипа
Поскольку тип catalog_item_t объявлен так, что возможность создавать его экземпляры отсутствует, нужно определить его подтипы. Начнем с подтипа для объектов-книг. Так как книга является одним из элементов каталога, в нашем примере все экземпляры подтипа book_t будут иметь следующие четыре атрибута:
О Id — наследуется от базового типа cata1og_iteni_t;
О title - также наследуется от базового типа;
О Isbn — соответствует коду ISBN книги;
О pages — количество страниц в книге.
Соответствующий программный код выглядит таким образом;
CREATE OR REPLACE TYPE book_t UNDER catalogJteuLt (
1sbn VARCHAR2(13),
pages INTEGER,
4
CONSTRUCTOR FUNCTION book_t (id IN INTEGER DEFAULT NULL.
titleIN VARCHW2 DEFAULT NULL,
1SbnIN VARCHAR2 DEFAULT NULL,.
pagesIN INTEGER DEFAULT NULL)
RETURN SELF AS RESULT.
10
OVERRIDING MEMBER FUNCTION ck_d1git_okay
RETURN BOOLEAN,"
OVERRIDING MEMBER FUNCTION print
RETURN VARCHAR2
);
Рассмотрим его более подробно.
Строка Описание
Подтип определяется с помощью ключевого слова UNDER, которое вы видите
в строке 1, В Oracle не используется предложение AS OBJECT, поскольку оно избыточно, ведь «под» (англ, under) объектным типом в иерархии может находиться только производный объектный тип
, 3Нам нужен список только тех атрибутов, которые уникальны для подтипа;
атрибуты родительского типа включаются в подтип ввггоматически. Oracle сначала создает атрибуты базового типа, а зэтем атриблы подлипа в том порядке, в какой они заданы в спецификации
4-15 Эго объявления методов, которые мы опишем в следующем разделе
Методы
В приведенном выше определении типа используются два вида методов.
О Конструктор. Функция, которая принимает значения всех атрибутов и помещает их в объект заданного типа. Объявлена в строках 5-9.
□ Методы-члены. Функция или процедура, которая выполняется в контексте экземпляра объекта, то есть имеет доступ к текущим значениям каждого атрибута. Методы-члены объявлены в строках 11, 12 и 14-15.
Наш пример демонстрирует применение определенного пользователем конструктора —элемента, введенного в Oracle気 Release 2. В предыдущих версиях Oracle поддерживались только системно-определяемые конструкторы. Создание собст- вешого конструктора типа позволяет управлять созданием экземпляра этого типа. В конструкторе можно выполнять проверку и инициализацию данных, формировать управляемые побочные эффекты. Кроме того, можно использовать несколько перегруженных версий конструктора, что дает возможность адаптироваться к разным вариантам его вызова.
Для того чтобы посмотреть методы и типы в действии, выполните следующий анонимный блок:
DECLARE
generi c_i tern catalogJteirLt;
abook book t;
BEGIN "
abook :- NEW book_t(title -> 'Out of the Silent Planet'.
1sbn -> '0-6848-238-02');
genericJ tern :- abook;
DBMS_OUfPUT.PUT_LINE('BOOK; ' || abook.printO);
DBMS_OUTPUT.PUT_LINE('ITEM: 1 |j generic_item.pr1nt());
END; ~~~
Вызовы метода print объектов abook и gener"ic_item (строки 8 и 9) возвращают следующие данные:,
BOOK: id-; titled)irt 0f the Silent Planet: lsbn-0-6848-238-02; pages-
ITEM: id-; title-Out of the Silent Planet: 1sbn-0-6848-238-D2; pages-
Строки Описание
6Конструктор «собирает» новый объект-книгу и помещает его в перененкую
abook. В данном примере аргументы передаются ему по именам. Мы задала значения только для двух аргументов, но конструктор все равно создал объект. Синтаксис любого конструкп^а имеет следующий вид:
[NEW ] имя^типа (аргументЛ аргумент^, „.);
Ключевое слово NEW, введенное в OracleSF Release 2, не обязательно, но полезно как визуальное указание на то, что данный оператор создает новый объект
Здесь проиосодит нечто очень интересное: хотя экземпляры элемента каталога
создавать не разрешается, представляющей его переменной можно присвоить экземпляр подтипа; и в ее значении будут содержаться атрибуты, уникальные для данного подтипа. ?гот факт демонстрирует важный аспект «замещаемости», поддерживаемой Oracle в PUSQL, суть которой заключается в том, что в объектной переменной может содержаться экземпляр любого подтипа типа данных этой переменной.
Разумеется, с логической точки зрения имеет смысл рассматривать книгу как элемент каталога. В компьютерном мире это соответствует расширению подтипа, содержащего специфические атрибуты, до родительского типа, то есть приведению его типа к родительскому (upcasting). Обратная операция, сужение типа или приведение его к дочернему типу (downcastlng), сложнее, но тоже возможна
Строки
Описание
瓦9
Обратите внимание, что для вызова метода pdrAQ используется классический объектный точечный синтаксис:
объект.имя.методаСаргуменг^!, аргумент一2,…)
поскольку это метод-член и его можно вызвать только для заранее объявленного экземпляра объекта. Выбор метода print для объектов каждого типа осуществляется следующим способом: всегда вызывается метод самого спеииализированного подтипа (расположенного ниже всех по иерархии), ассоциированный с текущим экземпляром объекта. Выбор метода откладывается до этапа выполнения — эта техмолошя называется динамической диспетчеризацией методов. Она очень удобна^ хотя и ухудшает производительность
Чтобы лучше понять полученные результаты, рассмотрим содержимое типа book_t. В реализации данного типа используются две важные концепции, о которых рассказывается далее.
CREATE OR REPLACE TYPE BODY book_t
AS■"
CONSTRUCTOR FUNCTION bookj (Id IN I盯EGER,
title IN VARCHAR2, 一
isbn IN VARCHAR2.
pages IN INTEGER)
RETURN SELF AS RESULT
15
BEGIN
SELF.Id :-Id:
SELF.titletitle:
SELF.Isbn :- isbn:
SELF.pages:- pages;
14 IF isbn IS NULL OR SELF.ck_digit okay
15THEN"~
16RETURN;
17ELSE
IBRAISEJ^PLICATBN一ERR0RC2000。. ’ISBN ' || isbn
19H ' has bad check digit'):
20 END IF:
21END:'
22
23OVERRIDING MEMBER FUNCTION ck_dlg1t_0kay
24RETURN BOOLEAN""
25IS
26subtotal PLS_INTEGER > 0;
27i 5bLdigits VARCHAR2(10);
28BEGIN-
29/* удаление дефисов и пробелов */
30lsbn_dig1tsREPLACE(REPLACE(SELF.1sbn, '-'). ' *);
31IF LENGTHSsbn digits)!■10
32THEN
33RETURN FALSE:
34END IF:
35
67R901234567B9012J4B6789012345 3333444444444455565 5 555566668667R901234567B9012J4B6789012345 3333444444444455565 5 5555666686FOR nthjiiglt IN 1..9
67R901234567B9012J4B6789012345 3333444444444455565 5 5555666686
67R901234567B9012J4B6789012345 3333444444444455565 5 5555666686
LOOP ~■
subtotal :- subtotal +
(11 - nth_d1g1t) * TO_NUMBER(SUBSTR11sbn_digits, nth_digit. 1));
END LOOP: --
/* контрольной цифрой может быть буква 'X'. имеюцая значение 10 */
IF UPPER(SUBSTR(isbn_digits. 10. 1})工'X' THEN subtotal :- subtotal + 10;
ELSE
subtotal :- subtotal + TO_NUMBER(SUBSTR(isbn_(11gits. 10, 1));
END IF:~~
RETURN MODCsubtotal. 11) - 0;
EXCEPTION
WHEN OTHERS■
THEN
RETURN FALSE;
END;'
OVERRIDING MEMBER FUNCTION print・
RETURN VARCHAR2
IS
BEGIN
RETURN rid-' II 1d II '; title-' Ц title
II '; Isbn-1 I; isbn И '; pages-' || pages; END;
END;
Поанализируем этот код, подробно указав какие операции в каких строках производятся. При определении конструктора объектного типа нужно следовать определенным правилам.
О Конструктор должен быть объявлен с ключевыми словами CONSTRUCTOR FUNCTION (строка 3).
О Предложение, которое определяет возвращаемое им зкачекие, должно выглядеть следующим образом: RETURN SELF AS RESULT (строка 7).
О Конструктор может присваивать значения любым атрибутам текущего объекта (строки 10-13).
О Он должен завершаться оператором RETURN или исключением (строка 16; строки 18, 19).
Обычно конструктор присваивает значения болыпинству атрибутов объекта. Как видно из строки 14, перед окончанием работы конструктор проверяет контрольную цифру. Учтите,что функция ck_d1git_okay считывает атрибут isbn текущего объекта (строка 30) еще до завершения «сборки» объекта.
Программный код в строках 17-21 следует рассматривать как предельно упрощенный пример. В реальном приложении обработка специфических для приложения исключении выполняется более сложным образом. Об этом мы рассказывали в последнем разделе главы 6.
Теперь обратимся к ключевому слову SELF, которое несколько раз встречается в коде объектного типа (не только в конструкторе, но и других методах). Для про- раммнстон Java можно сказать, что оно эквивалентно ключевому слову thi s, для программистов, работающих с другими языками, его значение можно объяснить таким образом: SELF — это ссылка на вызывающий (текущий) объект, используемая исключительно в реализации методов. Ее можно применять для ссылки иа весь объект, а с точечным синтаксисом — для ссылки на атрибут или метод объекта. Например:
IF SELF.id ...
IF SEtF.ck_digit_okay() ....
Внутри метода-члена ключевое слово SELF требуется не всегда (см. строки 62, 63), поскольку идентификаторы текущего объекта всегда видимы в методе и обычно идентифицируются именно как ссылки на его атрибуты и методы. В строках 10-13 оно было необходимо — в даяном случае имена формальных параметров конструктора совпадают с именами атрибутов и компилятор идентифицировал бы их как ссылки на параметры. Кроме того, они помогают сделать код более читабельным.
Ниже приведено еще несколько рекомендаций, касающихся применения ключевого слова SELF.
О Это ключевое слово не может использов&тъся в статических методах, поскольку у них нет <текущего объекта».
Q По умолчанию в функциях ключевое слово SELF является входной перемен* ной (IN), а в процедурах и конструкторах — входной и выходной переменной (IN OUT)..
Q Эти установки можно изменить, если задать SELF первым формальным параметром.
Продолжим рассмотрение кода.
Вычисленне контрольной цифры (строки 23-56) мы зыполняем в качестве примера, ио в нашем алгоритме новые объектно-ориентированные средства используются не в полной мере. Кроме того, не полностью реализовав обработчик исключений. Он должен обрабатывать ряд ситуаций, например получение функцией TO_NUMBER строки вместо числа.
Теперь рассмотрим второй подтип — периодические издания:
CREATE OR REPLACE ПРЕ serial_t UNDER catalogjtern_t (
issn VARCHAR2(10). ~~'
open_or_closed VARCHAf?2(l).
CONSTRUCTOR FUNCTION Serial_t (id IN INTEGER DEFAULT.NULL.
Ytk IN VARCHAR2 DEFAULT NULL. ''
issn IN VARCHAR2 DEFAULT NULL
openj)rj:】osed IN VARCHAR2 DEFAULT NULL)
RETURN SELF AS RESULT,■
OVERRIDING MEMBER FUNCTION ck/1gft~okdy
RETURN BOOLEAN,"
OVERRIDING MEMBER FUNCTION print
RETURN VARCHAR2
)NOT FINAL:
Как видите, в нем нет ничего нового. Объектный тип serial_t в нашей модели имеет собственный конструктор, свою версию функции проверки контрольной цифры и способ возврата информации о себе в ответ на вызов метода print'.
Помимо конструктора и методов-членов Oracle поддерживает еще две категории методов.
О Статические методы. К ним относятся функции и процедуры, вызываемые независимо от экземпляра объекта. Такие методы ведут себя подобно обычным процедурам н функциям PL/SQL, Пример вы найдете во врезке <Статический метод как псевдо-конструктор*.
О Методы сравнения (то есть соответствия или сортировки). Это специальные методы-члены, позволяющие указать, что должна делать система Oracle, когда ей нужно сравнить два объекта данного типа, например при проверке на эквивалентность в PL/SQL или при сортировке объектов в SQL.
На объекты распространяется правило Oracle, гласящее, что значение неини-
и для коллекции, в этом случае значения атрибутам объекта присваивать нельзя. Рассмотрим следующий пример:
DECLARE
щуЬоок book」: -■ объект объявлен, но не ииииивлиэирован
BEGIN 一
IF нуbook IS NULL ■• проверка вернет TRUE: объект равен NULL
THEN
mybook.title ;- 'Leaminfl Oracle PL/SQL'; -■ эта строка является причиной
END IF;
EXCEPTION
WHEN ACCESS.INTO NULL - ...йвннсга предапределеннаго исклочения
THEN 一 一
END;
Прежде чем присваивать атрибутам значения, вы должны инициализировать объект (создать экземпляр) одним из трех способов: с помощью метода-конструктора, путем непосредственного присваивания экземпляра другого объекта или посредством выборки из базы данных. Об этом рассказывается в следующем разделе данной главы.
Атрибут open_or_closed возвращает символ О или С. Первый символ означает, что дмпый элеыекг каталога в настоящий моыент ыодифицируется (налрнмер, библиотека еще не получила всех номе-* ров журнала), а второй — что данный элемент каталога уже сформироваи.
Исключением из правила оляются ассоциативные массгаы. Сразу после объявления они не равны NULL, а просто пусты. :
СТАТИЧЕСКИЙ МЕТОД КАК ПСЕВДО-КОНСТРУКТОР
В версиях от Огас1е82 до Oracle9i Release 1 пользовательские конструкторы не поддерживались, и их заменой были статические методы, которые возвращали объект, созданный системным конструктором. Например:
STATIC FUNCTION 用訳е (id IN INTEGER, title IN VARCHAR2. 1sbn IN VARCHAR2, pages IN INTEGER) RETURN book_t
IS~
BEGIN
IF проверка разных атрибутов...> THEN
RETURN book一tHd. title. Isbn. pages};
ELSE~
RAISE соответствующее исключен©: end if;
END:
Выделенный код — это вызов системного конструктора. Конструктор всегда имеет одинаковое с объектным типом имя. Значения атрибутов передаются ему в том же порядке, в каком они указаны при определении объектного типа. Статический метод вызывается так же, как любой другой метод объекта:
DECLARE
а»。* book_t :- bookj.makedd -> 111);
Конечно, это не единственная область применения статических методов.
Запись, извлечение и использование хранимых объектов
До сих пор мы рассматривали определение типов данных и создание экземпляров объектов в памяти, выделяемой для выполняющихся программ, Однако это только небольшой фрагмент «объектной картины» — конечно же, в Oracle существует возможность сохранять объекты в базе данных.
ОРГАНИЗАЦИЯ ЦЕПОЧКИ МЕТОДОВ
Объект, определение которого выглядит следующим образом:
CREATE OR REPLACE TYPE cha1ndefflO_t AS OWECT ( X NUMBER, у VARCHAR2(10). z DATE, MEMBER FUNCTION setx (x IN NU№ER) RETURN chalndemo t, MEMBER FUNCTION sety (y IN VARCHAR2) RETURN chalndemo t, MEMBER FUNCTION setz (z IN DATE) RETURN Chaindemo.t);"
позволяет <сцеплять> методы вместе. Например:
DECLARE
С cbalndemoj :- chaindemoJtNULL, NULL. NULL);
BEGIN"'
c :- c.setxJD.setyCfoo^.setzCsysdate); -- цепочка вызовов
用^одолжение夕
Этот оператор эквивалентен трем последовательным вызовам:
с :- c.setx(l);
с c.setyffoo'): с :- c.setz(sysdate);
Каждая функция возвращает типизированный объект, который является входным для следующей функции. Ниже описана реализация одного из трех методов (остальные похожи):
MEMBER FUNCTION setx [х IN NUMBER) RETURN chaindemo_t IS
l_self chaindBfno3 :« SELF;
BEGIN
l_self.x :- X;
RETURN
END; ~
Приведем несколько правил формирования цепочки методов.
Нельзя использовать возвращаемое функцией значение в качестве йа- раметра IN OUT следующей функции цепочки. Функции возвращают значения, доступные только для чтения.
Методы вызываются в порядке слева направо.
Каждый метод в цепочке должен возвращать объект того типа, метод которого вызывается следующим.
口 Цепочка вызовов мажет включать не более одной процедуры.
Если цепочка включает вызов процедуры, он должен быть последним (то есть крайним справа).
Создаваемый нами библиотечный каталог можно сохранить в базе данных как минимум двумя способами: в виде одной большой таблицы, которая состоит из объектов, соответствующих элементам каталога, либо в виде набора меньших таблиц, представляющих подтипы элементов каталога. Первый способ реализуется таким образом:
CREATE TABLE catalogjtems OF catalog_itCTi_t
(CONSTRAINT catalogjtems_pk PRIMARY KEY (id));
Эта инструкция предписывает Oracle создать таблицу объектов catalog_1tens, каждая строка которой является объектом типа catalog_item_t. Объектная таблица обычно содержит по одному столбцу на атрибут. Однако помните, что объектный тип catalog_itern_t не допускает создания экземпляров и каадая строка таблицы на самом деле будет объектом одного из его подтипов, например книгой или периодическим изданием. Поэтому в таблице необходимо выделить место для хранения значений атрибутов каждого из возможных подтипов. Чтобы выполнить данную операцию, в таблицу добавляются скрытые столбцы, по одному на каждый уникальный атрибут подтипа. С точки зрения прохраммирования объектов это удобно, поскольку позволяет сохранить абстракцию элементов каталога.
Предложение CONSTRAINT указывает, что столбец id является первичным ключам. Учтите, что и у объектных таблиц могут быть первичные ключи. Кроме того, по умолчанию Oracle должен создать систе&шо-генерируемый идентификатор объекта (OID),
Идентификация объектов
В реляционных базах данных каждая строка таблицы имеет уникальный идентификатор. Объектно-ориентированные системы также поддерживают уникальные идентификаторы объектов. Как правило, они присваивают объектам невидимые идентификационные номера, служащие дескрипторами. Идентификатор для любого объекта может быть создан на основе одного из следующих элементов.
Q Значение первичного ключа. Для использования данной возможности нужно добавить в конец инструкции CREATE TABLE предложение OBJECT IDENTIFIER IS PRIMARY KEY.
О Значение, генерируемое системой. В этом случае Oracle добавляет в таблицу еще один скрытый столбец с именем 5Y3_NC_0ID$ и помещает в него уникальное 16-байтовое значение типа RAW. По умолчанию идентификатор создается именно таким образом.
Не все объекты имеют идентификаторы. В частности, объекты, хранящиеся в переменных PL/SQL как и объекты в столбцах таблиц, не имеют О ID-идентификаторов. Oracle считает, что объект-столбец зависит от первичного ключа таблицы и поэтому не должен идентифицироваться независимо1.
Идентификаторы на основе первичных ключей обычно занимают меньше места, чем системно-генерируемые, но последние обладают определенными преимуществами. Более подробное обсуждение достоинств и недостатков двух указанных подходов вы найдете в документации Oracle Application Developer's Guide — Object-Relational Features. Пока же вам достаточно знать, что системно-генерируе- мые идентификаторы обладают следующими качествами.
О Прозрачность. Хотя программы могут использовать их неявно, значение такого идентификатора обычно нельзя увидеть.
О Потенциальная глобальная уникальность во всех базах данных. Пространство О ID имеет объем, достаточный для хранения 2 идентификаторов, и теоретически позволяет идентифицировать объекты в распределенных базах данных без явных ссылок на базы данных.
Q Постоянство. В данном контексте это азначает, что их нельзя обновлять. Даже после экспорта и импорта объекта его OID остается тем же, в отличие от ROWID. Для изменения OID нужно удалить объект и создать его вновь.
Функция VALUE
Операции с данными объектных таблиц потребовали некоторых изменений в синтаксисе SQL. Например, можно создать объект с помощью собственного конструктора и задать его в обычной инструкции INSERT1:
INSERT INTO catalogjtans
VALUES (NEW book_t(10003, 'Perelandra'. '0-684-82382-9,, 222»:
INSERT INTO catalog items
VALUES (NEW ser1al_t(I0004. 'Time1. 'аОДО^вХХ'. 'О1]];
ЭМУЛЯЦИЯ ГЛОБАЛЬНЫХ КОНСТАНТ С ПОМОЩЬЮ ОБЪЕКТНЫХ МЕТОДОВ
Многие профессионалы, увидев приведенный выше код, будут не согласны с жестким кодированием значений, в частности с применением параметра 'О', обозначающего <открытый>. Вместо него можно задать константу из пакета или вызвать статический метод, например ореп_с, возвращающий нужное значение. Мы предпочли бы использование статического метода, поскольку в отличие от пакетов объектные типы Oracle не поддерживают глобальных констант, переменных и вообще ничего глобального. В этом случае инструкция INSERT имела бы такой вид:
INSERT INTO catalogjtems
VALUES (NEW serlal_t(10002. 'Time'. '0040-781X'. serlal_t.open_c));
Тело соответствующего статического метода состоит из единственного оператора RETURN 'О'. Этот подход имеет очевидные преимущества — он обеспечивает согласованность кода и облегчает выполнение изменений в будущем.
Для извлечения объекта из базы данных Oracle предоставляет SQL-функцию VALUE. Она принимает единственный аргумент, в котором задается указанный в предложении FROM псевдоним таблицы, и возвращает объект того типа, на основе которого определена данная таблица. В инструкции SELECT это выглядит следующим образом:
SELECT YALUE(c)
FROM catalogjtens с;
В качестве псевдонимов таблиц лучше использовать короткие аббревиатуры, в частности с. Функция VALUE возвращает вызывающей программе последовательность битов, а не текстовое представленке значений столбцов. Однако в пакете SQL*Plus имеется встроенная возможность вывода объектов, поэтому результата приведенного выше запроса он выводит следующим образом;
VALUE(C)(ID. TITLE]
ВООК_Т(ЮООЗ, 'Perelandra'. '0-684-82382-9'. 222}
SERIAL_TC10004, 'Tire1, '0040-781X'. 'О')
В таких статических вызовах фуккциЙ лучше персдшп параметры по именам, но при вызок функций PL/SQL на 5ЦД Oracle этого не позволяет (во всякой случ&е, ОгвскШ Keleue 2),
Средства, предназначенные для работы с извлеченными из базы данных объектами, имеются и в PL/SQL. Сначала объявляется объектная переменная соответствующего типа:
DECLARE
catalog item catalog_1tem_t:
CURSOR ccur IS "
SELECT VALUES)
FROM catalogjterns c;
BEGIN
OPEN ccur;
FETCH ccur INTO catalog item:
DBMS_OUrPUr.Pirr_LINE('H3Bne4eH злеиент #' || catalogjtem.ld);
CLOSE ccur;
END;
В аргументе процедуры PUT_L1NE используется сиятаксис переменная. атрибут, а возвращает она значение атрибута catalogjtem.id:
Извлечен элемент #10003
Инструкция FETCH присваивает объект локальной переменной catalogjtem, которая объявлена как переменная базового объектного типа. Это естественно, поскольку мы не знаем заранее, объект какого подтипа будет выбран из базы данных.
Рассматриваемый нами код показывает (на примере вывода значения атрибута cdtdl og__i tern .id), что мы имеем непосредственный доступ к атрибутам базового типа.
Можно использовать также обычные атрибуты курсоров. Например, приведенный выше анонимный блок можно переписать таким образом:
DECLARE,
CURSOR ccur IS
SELECT VALUE(c) obj
FROM catalogjtems c:
arec ccuryROHTYPE;
BEGIN
OPEN ccur:
FETCH ccur INTO arec:
DBMSJJJTPUT.PJT LINECHaereneH элемент arec.obj.id);
CLOSE ccur;"
E№);
Если нам необходимо вывести все атрибуты объекта, можно использовать метод pri nt. Это вполне допустимо, поскольку он объявлен в объектном типе корневого уровня и реализован в подтипах. На этапе выполнения Oracle найдет для данного метода перегруженную версию из подтипа.
Функция VALUE поддерживает точечный синтаксис, обеспечивающий доступ к атрибутам и методам, но только к тем из них, которые определены в базовом типе. Например, инструкция
SELECT VALUE(c).1d. VALUE(c).prlntO
FROM catalog_items c:
возвращает результат
VALUECO.ID VALUECC).PRINK)
10003 1d»i0Q03: tltle^erelandra: 1sbn4)-684-B2382-9; pages*222
10004 id-10004: t1tle-T1me: 1ssrH)Q40-7BlX; open_or_closed-Open
При работе в клиентской среде, не поддерживающей объектов Oracle, можно воспользоваться именно этой функцией.
Попытайтесь прочитать только те атрибуты, которые являются уникальными для конкретного подтипа, выполнив такую инструкцию:
SELECT VALUECc).issn •• Ошибка. Непосредственный доступ к подтипу невозможен
FROM catalogjterns с:
В ответ Oracle выдает сообщение об ошибке 0RA-OO9O4: Invalid column папе (неверное имя столбца). Это означает, что объект родительского типа не предоставляет непосредственного доступа к атрибутам подтипа. Можно попробовать объявить переменную book типа book_t и присвоить ей объект данного подтипа, в надежде, что она покажет скрытые атрибуты:
book ;■ catalogjtem; /* Ошибка. Непосредственное прнсвзмвзние подтипу также не работает */
Однако на этот раз мы получим сообщение об ошибке PLS-00382: expression is 'of wrong type (неверный тип выражения). О том, как решается эта проблема, вы узнаете немного позже.
Учтите следующие особенности выполнения DML-операций в объектных реляционных таблицах.
□ Для объектной таблицы, созданной на основе объектного типа, не имеющего подтипов, возможны выборка, вставка, обновление и удаление значений всех столбцов посредством обычных SQL-инструкций. Таким образом, объектноориентированные и реляционные программы могут совместно использовать одни и те же данные.
О Традиционные DML-инструкции не имеют доступа к скрытым столбцам, представляющим атрибуты подтипов. Для работы с такими столбцами используется «объектный DML».
О Чтобы обновить весь объект в таблице базы данных из программы PL/SQL, можно использовать объектную DML-инструкцию, которая обновит все атрибуты (столбцы), включая уникальные для подтипа:
UPDATE catalogjtems с SET с ■ объектндлпеременная WHERE ...
Q Единственным известным способом обновления заданного столбца, уникального для подтипа, является обновление всего объекта. Например, чтобы установить равным 1000 значение количества страниц книги с идентификатором 10007, потребуется такая инструкция:
UPDATE catdlogjtems с
SET с - NEU book tCc.id. c.title. c.publ1cation_date, c.subject refs.
~ (SELECT TREAT(VALUECy) AS boo<t).1sbn "
FROM catalogjtems у
WHERE id 匸 10007).
1000)
WHERE id - 10007;■
Функция TREAT
Рассмотрим ситуацию, когда в объявлении типа переменной PL/SQL указано имя супертипа, а ее значение принадлежит к подтипу. Как нам получить доступ к атрибутам и методам» специфическим для этого подтипа? Предположим, что мы
хотим интерпретировать элемент каталога как книгу. Эта операция называется сужением (narrowing) или приведением базового типа к дочернему (downcasting), и компилятор воспринимает ее не всегда. Для выполнения данной операции нужно вызвать специальную функцию Oracle с именем TREAT. Это можно сделать непосредственно:
DECLARE
book book_t;
catalogjtem catalog]tern t NEW book_t{);.
BEGIN ""~~
book :- TREAT (catalogjtem AS book t):/* Работает в 91 R2 */
END;一"
или с помощью SQL (в PL/SQL OracleSi Release 1 функция TREAT явно не поддерживается):
DECLARE
book book_t:
catalogJtan catalog_1tem_t ;* bock_t(NULL, MIL, HULL, NULL):
BEGIN"""
SELECT TREAT (cataloffjtes AS book_t)
INTO book "~
FROM DUAL;
END;
Синтаксис функции TREAT выглядит следующим образом:
TREAT (зкземпляр_рбъекта AS подтип) [ . {атрибут | метов《ар「ументн,,.)}]
Здесь жземпляр_объекта — это значение конкретного супертипа в объектной иерархии, а подтип — имя подтипа в этой иерархии. Если вы попытаетесь преобразовать объект к типу, принадлежащему другой объектной иерархии, компилятор выдаст ошибку. Когда функции TREAT передан объект из ^правильной» иерархии, она возвращает либо преобразованный объект, либо NULL, но не ошибку.
Как и в случае использования функции VALUE, при обращении к функции TREAT можно применить точечный синтаксис, чтобы сразу задать атрибут или метод преобразованного объекта. Например:
DBMS_OUTPUT.PUT_LINE{TREAT (VALUES) AS serial_t),issn);
Если вам понадобится перебрать все объекты в таблице и выполнить определенные операции с учетом их типов, это можно сделать таким образом:
DECLARE
CURSOR ccur IS
SELECT VALUE[c) item FROM catalogjtems c: arec ccurZROWTYPE;
BEGIN
FOR arec IN ccur
LOOP
CASE
WHEN arec.Item IS OF (book t)
THEN
DBMS_OUTPUT.PLTT_L1 NEC'Найдена книга с ISBN-кодом '
H TREAT(arec.1tem AS book_t).isbn):
WHEN arec.item IS OF (serial t}
THEN
DBMS^QLRPUT.PUT^LINEC Найдена периодическое издание с ISSN-kordm '
|[ TREATCarec.ltem AS ser1al_t).1ssn);
ELSE
D8MS__0UTPUT.PUI^LINE('Найден неизвестный эленент каталога');
END CASE: 一-
Ж LOOP:
END:
Данный блок демонстрирует применение предиката IS OF для проверки объектного типа. Синтаксис предиката выглядит многообещающе:
объект IS OF ([ONLY] имя^типа)
Однако на самом деле возможности предиката ограничены — он работает только с объектными типами данных Oracle и не работает с базовыми, такими как NUMBER или DATE. Компилятор выдаст сообщение об ошибке, если при использовании предиката тип объекта не принадлежит той же иерархии; что и тип имя_гш.
Обратите внимание на ключевое слово ONLY. По умолчанию (то есть без ONLY) предикат возвращает TRUE, если объект относится к заданному типу или одному из его подтипов. Если ключевое слово ONLY присутствует, предикат IS OF не сравнивает подтипы и возвращает TRUE только в случае точного совпадения типов.
Предикат IS OF, как и функция TREAT, может использоваться в SQL Oracle9:, но в PL/SQL до Oracle9t Release 2 он не поддерживается. Чтобы обойти это ограни- чениег в Release 1 можно определить в дереве типов один или более дополнительных методов и, пользуясь динамической диспетчеризацией методов, выполнить нужную операцию на соответствующем уровне иерархии. На выбор подходящего метода сужения типа влияет не только номер версии, но и выполняемые в приложении операции.
Далее мы обратимся еще к одной интересной теме — возможностям, предлагаемым Oracle в случае изменения структуры приложения.
Эволюция и создание типов
По сравнению с Oracle8z в Огас1е9х достигнут отромный прогресс в области эволюции типов. Теперь Oracle позволяет вносить в объектные типы различные изменения, причем допускается это даже в случаях, когда таблицы уже созданы и заполнены объектами данных типов.
Ранее в этой главе мы определили объектный тип catalog_i tem_t на скорую руку. Однако любой библиотекарь скажет, что для каждой находящейся в библиотеке книги иди периодического издания желательно хранить в каталоге дату издания. Поэтому мы делаем следующее:
ALTER TYPE catalogjtem t
ADD ATTRIBUTE publ1cat1on_date VARCHAR2(400}■
CASCADE INCLUDING TABLE DATA;
Et voilal Система Oracle распространила это изменение на соответствующие таблицы, автоматически выполнив все необходимые модификации. Она добавила атрибут в конец списка атрибутов супертипа и один столбец после последнего
столбца супертипа в соответствующую объектную таблицу. Если теперь выполнить для тила catalogjtem_t команду DESCRI8E, которая выводит описание заданного объекта, результат будет таким:
SQL> DESC catalog-1tem_t
catalog ite(n_t Is 関丁 FINAL
catalog_^eS_t is NOT INSTANTIA8LE
Name■ Null? Type
IDNUM8ERC38)
TITLEVARCHAR2{4000)
PUBLICATIW„DATEVARCHAR2(4O0)
METHOD
MEM8ER FUNCTION CK_DIGIT_OKAY RETURNS BOOLEAN CKJJIGILOSY IS NOT INSTANTIABLE
METHOD■
MEMBER FUNCTION PRINT RETURNS VARCHAR2
Для таблицы эта команда вернет следующее:
SQL> DESC catalogjterns
NameNull? Type
IDNOT NULL NUM8ER(3B)
TITLEVARCHAR2(4000}
PUBLICATION_DATEVARCHAR2(400)
Фактически инструкция ALTER TYPE исправляет почти все, однако, увы, она не настолько умна, чтобы переписать наши методы. Особенно важны конструкторы, поскольку придется изменить их сигнатуры. Для изменения сигнатуры метода можно удалить его и добавить снова.
ПРИМЕЧАНИЕ При расширении объектного типа иногда генерируется ошибка «ORA-22337: the type of accessed object has been evolved*. После этого 8bl не сможете получить описание типа с помощью команды DESCRIBE. И перекомпиляция делу не поможет, более того, если имеются жесткие зависимое™ от этого типа, Oracle не позволит перекомпилироватъ.его спецификацию, Чтобы избавиться от данной ошибки, нужно отсоединиться от Oracle и затем подключиться снова. Б результате буферы сеанса очистятся, и с помощью команды DESCRIBE можно будет увидеть описание новой версии типа.
Была задана полная спецификация функции. Это гарантирует, что мы удаляем именно тот метод, который хотим, поскольку могут существовать его перегруженные версии. (Вообще-то, значения по умолчанию указывать здесь не обязательно.)
С оответствующая операция добавления метода выглядит так же просто (однако Oracle выполняет при этом огромную работу):
ALTER TYPE book t
ADD CONSTRUCTOR FUNCTION book_t (id INTEGER DEFAULT NULL,
title VARCHAR2 DEFAULT MULL,
publication date VARCHAR2 DEFAULT NULL,
ISbn VARCHAR2 DEFAULT NULL, pages INTEGER DEFAULT NULL) REWRN SELF AS RESULT
CASCADE;
Далее нужно аналогичным образом модифицировать тип seridl t, а затем заменить два соответствующих тела типов с помопхью инструкции CREATE OR REPLACE TYPE BODY (данные фрагменты кода в книге не приведены). Кроме того, следует просмотреть все методы на предмет необходимости изменений (например, желательно включить дату издания в метод print).
Для удаления типа (если это потребуется) применяется следующая инструкция:
DROP TYPE имя_типз [FORCE]:
Ключевым слоном FORCE нужно пользоваться осторожно. Дело в том, что все объектные типы и объектные таблицы, которые зависят от типа, удаленного с указанием этого ключевого слова, станут неприменимы ми. Если в таблицах имеются столбцы данного типа, Oracle пометит их как UNUSED и сделает недоступными. Для удаления подтипа, который используется в определениях таблиц, можно задать инструкцию DROP TYPE в такой форме:
DROP TYPE имя_подтила VALIDATE:
Ключевое слово VALIDATE предписывает Oracle просмотреть таблицы и удалить данный подтип только в случае, если они не содержат ни одного экземпляра этого подтипа. Таким образом вы сможете избежать разрушительных последствий, возникающих прн использовании ключевого слова FORCE.
И снова указатели
Среди объектно-ориентированных функций Oracle появилась возможность хранить значения типа REF. Они представляют собой объектные ссылки, то есть логические указатели на конкретную строку объектной таблицы. В ссылке хранится следующая информация:
О первичный ключ строки или системно-генерируемый идентификатор объекта;
Q уникальный идентификатор таблицы;
Q информация о физическом местоположении строки на диске в виде ее значения типа ROWID (это делается по указанию программиста).
Если содержимое REF представлено в символьном виде, вряд ли вы сможете извлечь из него полезную информацию:
SQL> SELECT REF(c) FROM catalog J tens c WHERE ROMWM - 1;
REF(C)
00DO2S02O99FC431FBE5F2O599EO34OO03BAOF1F139FC431FBE5F10599E034OO03BA0F1F130240O00COOOO
Запросы и программы могут использовать значения REF для выборки строк объектной таблицы без указания ее имени. Далее мы рассмотрим, как REF-ссылки применяются в библиотечном каталоге.
Использование REF-ссылок
Библиотечный фонд обычно классифицирован по тематике. Например, в Библиотеке Конгресса данная книга может относиться к трем категориям:
О Oracle (Computer file - компьютеры);
О PL/SQL (Computer program language — языки программирования);
О реляционные базы данных.
В нашем случае для классификации используется древовидная структура: категория Computer file является родительской для Oracle, а категория Computer program language 一 родительской для PL/SQL.
Категории и хранящиеся в библиотеке объекты связаны отношением «мно- гие-ко-многим»: книга может относиться к нескольким категориям, а к категории может принадлежать множество книг. В нашем библиотечном каталоге книги всех категорий размещены в одной таблице. При использовании реляционного подхода нужно создать промежуточную таблицу, чтобы разделить отношение «мно- гие-ко-многим> на два отношения <один-ко-многим». Однако объектно-реляционный подход предлагает другое решение.
Начнем с создания объектного типа для категории:
CREATE TYPE subject_t AS OBJECT (
name VARCHAR2C2000}.■.
broader_term ref REF subject t
В нем должно храниться имя данной категории и так называемый гиперним (Broader Term — ВТ), то есть имя вышестоящей категории. Чтобы не размещать один объект в другом, мы будем хранить только ссылку на вышестоящую категорию. Поэтому в третьей строке определения типа атрибут broader_term_ref задан как REF-ссылка на объект того же типа. Это похоже на рассматриваемую ранее таблицу ЕМР, в которой хранятся идентификаторы строк этой же таблицы, соот-- ветствующие начальникам.
Теперь создадим таблицу категорий:
CREATE TABLE subjects OF subject t
(CONSTRAINT subject_pk PRIMARY KEY Cname).
CONSTRAINT subject_self_ref FOREIGN KEY (broader_term_ref)
REFERENCES subjects!;一
Рассмотрим внешний ключ таблицы. Он указывает на таблицу с реляционным первичным ключом, но имеет тип REF, поэтому Oracle знает, что в качестве
внешнего ключа нужно использовать идентификаторы объектов. Поддержка ограничений внешнего ключа типа REF демонстрирует, что в Oracle возможна связь между объектным и реляционным мирами.
Ниже приведено несколько инструкций, которые вставляют (используя системный конструктор) данные в таблицу:
INSERT INTO subjects VALUES (subject_t('Computer file'. NULL));
INSERT INTO subjects VALUES (subject_t('Computer program language', NULL)):
INSERT INTO subjects VALUES (subject_t('Relational databases'. NULL)):
INSERT INTO subjects VALUES (subject_t{'Oracle',
(SELECT REF《5) FROM subjects s WHERE name - 'Computer file1))).
INSERT INTO subjects VALUES (subject_t('PL/SQL'.
(SELECT REF(s) FROM subjects s WHERE name - 'Computer program language')));
Теперь можно вывести содержимое таблицы subjects:'
SQL> SELECT VALUE(s) FROM subjects s;
VALUE ⑸(NAME. BROADERJERM_REF)
SUBJECT_T('Computer file*. NULL)
SUBJECTjrrComputer program language', NULL)
SUBJECTJCOracle'. OO002202O89FC431FBE6FBO599H03400D3BAOF1F139FC431FBE6690599EO3 4D003BADF1F13)
SUBJECT TCPL/SOL'. 00DD2202089FC431F8E6FC0599EO340003BAOF1F139FC431FBE6690599E03 400038/^1^3)
SUBJECTJCRelational databases1, NULL)
Полезной является способность Oracle автоматически разрешать ссылки. Например, с помощью функции DEREF можно перейти к строке, на которую указывает ссылка:
SELECT s.name. DEREF{s.broader^te^ref).name bt FROM subjects s:
Это похоже на внешнее автоматическое объединение. Иными словами, если ссылка равна NULL или недействительна, строка включается в результирующий набор, но значение целевого объекта (и столбца) будет равно NULL.
В Oracle введено сокращение для функции DEREF - точечный синтаксис, указывающий, что извлекаемый атрибут берется из целевого объекта;
SELECT s.name, s.broeder_terffl,_ref.name bt FROM subjects s:.
Оба запроса возвращают следующее:
NAMEВТ
Computer file
Con^utcr program language
口NelsComputer file
PL/SQLComputer program language
Relational databases
Учтите, что в обеих формах требуется псевдоним таблицы:
SELECT псевдоним ^таблицы. сголбец^геГ FROM ш^аблицы псевдон^_таблицы
Ссылки могут использоваться и в предложении WHERE. Вывести все подкатего' рии, относящиеся к категории Computer program language, можно с помощью сле~ дующего запроса:
SELECT VALUE(s).name FROM subjects s
WHERE s.broader_tenn_ref.name - 'Computer program language';
Хотя в нашем примере в таблице используется ссылка на эту же таблицу, на практике ссылки могут указывать на любую объектную таблицу базы данных. Чтобы продемонстрировать сказанное, вернемся к определению базового типа catalog_item_t и добавим в него атрибут, предназначенный для хранения коллекции ссылок. В результате каждый элемент каталога будет связан с набором категорий. Сначала создадим коллекцию ссылок на категории:
CREATE TYPE subject_refs_t AS TABLE OF REF subject_t;
Теперь каждый элемент каталога можно ассоциировать с любым количеством категорий:
ALTER TYPE catalog_iteni_t
ADO ATTRIBUTE subject_refs subject_refs_t
CASCADE INCLUDING TABLE DATA; ~~
He рассматривая модификацию соответствующих методов зависимых типов, перейдем к добавлению строк в каталог. Это делается с помощью следующей инструкции:-
INSERT INTO catalogjtems
VALUES (NEW booO{10007.
'Oracle PL/SQL Programming1,
'Sept 1997'.
CASKMULTISEKSELECT REF(s)
FROM subjects 5
WERE natne IN ('Oracle', 'PL/SQL', 'Rehtlonal databases1}}
AS subject rmfs_t), 'l-56592-335-9K.- 987)):.
Функции CAST и MULTISET выполняют преобразование набора ссылок в коллекцию, как рассказывалось в главе 1L
Далее следует более понятный эквивалент на PL/SQL:
DECLARE
subrefs subject refs t;
BEGIN一 -
SELECT REF(s) BULK COLLECT INTO subrsfs
FROM subjects s
WHERE name IN ('Oracle1, 'PL/SQL1, 'Relational databases1};
INSERT INTO catalog Items VALUES (NEW bock t(10007.
'Oracle PL/SQL Prograimlng'f 'Sept 1997'. subrefs, 11-56592-335-9'. 987));
END:
Мы извлекаем ссылки на три заданные категории и записываем их в объект данной книги. Рассмотрим еще один пример навигации по ссылкам:
SELECT VALUE(s).name
11 '(' II VALUE(s). broade^tern^ref. name || ')' plsql_subjects
FROM TABLE(S£LECT subject_refs
FROM catalogj terns
WHERE id-10007) s;
Код извлекает данные из таблицы subjects, при этом она даже не упоминается по имени. (Функция TA8LE преобразует коллекцию в виртуальную таблицу.) Ре・ зультаты выполнения приведенного запроса имеют следующий вид:
PLSQLJUBJECTS
Relational databases О
PL/SQL Konputer program language)
Oracle (Computer file)
К сожалению, кроме возможности выполнять автоматический переход по ссылкам в SQL, эта технология практически ничего не дает программисту. Дело в том, что ссылки строготипизированы» то есть столбец типа REF может указывать только на объект того типа, который указан в его определении. Внешние же ключи могут указывать на любые строки, для которых определен первичный ключ или уникальный индекс.
Функция REFTOHEX
Ранее в этой тавв для получения шестнадцатеричного значения типа REF мы применяли такую инструкцию:
SELECT REF(c) FROM catalogJ terns c WHERE ROWNUM 上 1;
При этом результаты запроса отображались в шестнадцатеричном виде, поскольку SQL*Plus умеет преобразовать двоичные данные типа REF в шестнадцатеричную форму при выводе на печать. Такое же преобразование можно задать явно с помощью функции REHOHEX:
SELECT REFTOHEX(REF(c)) FROM catalogjtems c WHERE ROWNUM - 1;
Когда речь вдет о запросе в SQL*Plus, ее можно не использовать. Однако если вы выполняете запрос в PL/SQL и хотите получить шестнадцатеричное значение REF, обязательно включите в него функцию REFTOHEX, чтобы явно задать преобразование результата.
К сожалению, эту функцию нельзя непосредственно вызвать из PL/SQL. Поэтому следующий код, который извлекает значение типа REF из базы данных в переменную itemref и пытается посредством функции REFTOHEX преобразовать его в шестнадцатеричное представление, работать не будет:
DECLARE
itemrmf R£F catalog_1tem_t:,
itemref as_hex VARGHAKZCIQQ):
BEGIN " ~
―Считываем значение REF из базы данных.
SELECT REFfc) IHTO itemref
FROM catalogjtems c WHERE RDWNUM - 1;
-С помощью REFTOHEX пытаенся получить
-шестнэдцатеричное представление значения REF.
^temre^as^hex :- REFTOHEXdtemref]:
-Выводим длину результата и сам результат.
DBMS_OUTPUr.Pirr_LINE(LENGTH(itemref_as_hex)}: .
DBMS~OUTPUT. PLTr21-INE{5temref_as_hex):
END:-,
Попытавшись его выполнить, вы получите такую ошибку;
ERROR at line 10:
ORA-06550: line 10, column 22:
PLS-00201: Identifier 'REFTOHEX' must be declared
ORA-0655Q: line 10, column 4:
PL/SQL: Statement ignored
Как вадите, PL/SQL не поддерживает всех функций SQL. Хотя Oracle и объявила, что PL/SQL и SQL используют общий синтаксический анализатор, поддержка функции REFTOHEX требует большего, чем синтаксический анализ. Подход, когда для преобразования REF в шестнадцатеричное значение нужно извлекать из базы данных дополнительные данные, является не эффективным, поэтому можно надеяться, что поддержка этой функции все же будет включена в PL/SQL. Поскольку Oracle добавила в PL/SQL поддержку множества других встроенных функций (например, NVL2 и TREAT), можно сказать, что в этом отношении имеются положительные тенденции.
Пакет UTL_REF
Встроенный пакет UTL_REF выполняет операцию разыменования (получения значения объекта, на который указывает данная ссылка) без явного вызова SQL Это позволяет приложению программным путем блокировать, извлекать, обновлять и удалять объекты на основе их REF-ссылок. Чтобы рассмотреть эту возможность, добавим в объектный тип subject_t еще один метод:
MEMBER FUNCTION pr1nt_bt (str IN VARCHAR2)
RETURN VARCHAR2 -
IS
bt subject」:
BEGIN "
IF SELF.broader term ref IS NULL
THEN- 一
RETURN str;
ELSE
UTL_REF.SELECT_06JECT(SELF.broader_tere_ref, bt);
RETURN bt. pri nt_bt C NVKstr, SELF. name)) |j '■ (' 11 ht.name 11 ')':
END IF; ~
END:
Эта рекурсивная процедура просматривает иерархию от текущей категории до самой верхней - родительской.
ПОДДЕРЖКА ССЫЛОК В ЯЗЫКЕ С
Хотя PL/SQL и предоставляет большие возможности для использования объектных ссылок, в Oracle Call Interface (OCI), интерфейсе языка C/C++ и даже в Рго*С они значительно шире. Помимо навигации по ссылкам, как в PL/SQL, OCI поддерживает комплексное извлечение объектов (Complex Object Retrieval, COR).
Это дает возможность за один вызов извлечь объект и все объекты, на КО' торые он ссылается. И OCI, и Pro*С поддерживают клиентское кэширование объектов, позволяющее приложению загружать объекты в память клиентской системы и манипулировать ими, как если бы они находились в базе данных (выполнять выборку, вставку, обновление и удаление). После этого за один вызов приложение может передать все изменения обратно на сервер. Помимо расширения функциональных возможностей приложения все эти функции сокращают количество обращений к серверу, повышая общую производительность клиент-серверной системы.
При использовании процедур из пакета UTL_REF задаваемый вами аргумент REF должен иметь тот же тип, что и объектный аргумент. Ниже приведен полный список процедур этого пакета.
UTL_REF.SELECT. OBJECT t объектная усылка IN, объектнзя_перененная OUT);
Находит объект, на который указывает значение аргумента объектная_ссылкд, и извлекает его копию в объектную переменную.
UTL_REF.SELECT_OBJECT_WITH_CR (объектнаяусылка IN. обьектнаяу^еременная OUT):
Подобна SELECT一OBJECT, но делает копию («снимок») объекта. Данная версия позволяет избежать ошибки ORA-4091, возможной при обновлении объектной таблицы и присваивании значения функции, использующей пакет UTL.REF для разыменования ссылки объекта из той же таблицы, которая обновляется.
UTL_REF.LOCK_OBJECT (объектная_ссылка IN);
Блокирует объект, на который указывает значение аргумента объектнвя_ссылка, но не извлекает его.
UTL_REF,LOCK_OBJECT (объектная_ссылка IN. объектная_переменная OUT):
Блокирует объект, на который указывает значение аргумента объентнаяусылка, и извлекает его копию в объектную переменную.
UTL_REF.UPDATE_OBJECT (объектная_ссь/лка IN, обьектнвя_перененнвя OUT);
Заменяет объект, на который указывает значение аргумента обьектнаяусылка, значением объектной переменной,
UTL_REF.DELETE_OBJECT (объектная_ссылка TNT):
Удаляет объект, на который указывает значение аргумента объентнаяусылка.
Объектные ссылки и иерархии типов
Все перечисленные выше подпрограммы являются процедурами, а не функциями1, а их параметры являются не полностью слаботипизироваиными. Иными словами, если ссылки соответствуют объектным переменным, то во время компиля* ции Oracle не нужно знать их точные типы данных,
1 Было бы неплохо, чтобы как минимум $ELECT_OBJECT являлась функцией.
Рассмотрим особенности объектных ссылок, которые проявляются при работе с иерархиями типов. Предположим, что в программе объявлены следующие переменные:
DECLARE^
book book-t:
Item cata1og_iteni_t:
itemref REF cataloge1teni_t;
bookref REF book」;
Вы уже видели, что если извлекаемое по ссылке значение присваивается переменной сильнотипизированного типа, то все работает четко:
SELECT REF(c) INTO itemref
FROM catalogJ terns c WHERE Id - 100D7:
Подобным образом можно извлечь объект по ссылке с помощью процедуры SELECT_OBJECT:
UTL_REF.seiect_object(itemref, item);
или:
SELECT DEREF(itemref) I仃0 Item FROM DUAL;
Однако непосредственное сужение объектного типа при выполнении данной операции не допускается:
SELECT REF(c)
INTO bookref /* Ошибка */'
FROM catalogjtems c WHERE Id - 10007:
Для сужения типа можво воспользоваться функцией TREAT» которая <умеет» работать л со ссылками:
SELECT TREAT(REFCc) AS REF book_t}
INTO bookref
FROM catalog^terns c WHERE id - 10007:
Расширить или привести объект к родительскому типу можно явно:
UTL_REF.seiect_obJect(TREAT(bookref AS ref catalogjten_t]. item); и неявно:
SELECT OEREF(bookref) INTO Item FROM DUAL;
Хотя непосредственно сузить или привести объект к дочернему типу при использовании функции DEREF нельзя:
SELECT DEREF(iteniref)
INTO book /* Ошибка */
FROM DUAL;
функция TREAT и здесь придет на помощь:
SELECT DEREF(TREAT(1temref AS REF book_t))
INTO book~
FROM catalogjtems c WHERE Id - 10007:
Неявное приведение к дочернему типу также можно выполнить с помощью процедуры из пакета UTL_REF:■
UTL_REF.seiect_object{1 temref. book);
Висячие ссылки
Ссылка REF может ни на что не указывать, тогда она называется висячей. Так может случиться, когда объект, на который указывает хранящаяся в базе данных ссылка, удален. Это можно делать, если не определено ограничение внешнего ключа, препятствующее подобному удалению.
Для поиска висячих ссылок используется оператор IS DANGLING:
SELECT VALUE(s) FROM subjects s
WHERE broader_term_ref IS DANGLING.
Далее мы познакомимся co средствами Oracle, предназначенными для работы с данными неизвестных или изменяющихся типов.
Типы данных «апу»
В главе 12 мы упоминали о том, что Oracle поддерживает несколько встроенных типов данных, универсально расширяющих модель типов. Пользуясь встроенным типом данных ANYDATA, подпрограмма PL/SQL может, например, сохранять в базе данных, считывать и обрабатывать элементы данных любого из типов SQL и вам не нужно создавать десятки ее перегруженных версий.
Для работы с произвольными типами данных Oracle предоставляет следую- пще типы и пакеты.
О Тип данных ANYDATA. Инкапсулирует любые элементы данных типов SQL в самодокументироваяные структуры данных.
О Тип данных ANYTYPE. Совместно с типом данных ANYDATA применяется для считывания описания структуры данных. Может использоваться и сам по себе для создания временных объектных типов.
О Пакет DBMS_TYPES. Пакет, состоящий только из констант, которые помогают интерпретировать типы данных, используемые в объекте ANYDATA
О Тип данных ANYDATASET. Подобен типу ANYDATA, но его содержимым является один или несколько экземпляров типа данных.
Обработка значений типа ANYDATA
Следующая программа возвращает строковую версию содержимого любой переменной. Нынешняя ее версия работает только с числами, строками, датами, объектами и ссылками REF, но ее можно расширить для поддержки практически любых типов данных.
/* Файл 8 web: prlntany.fun */
CREATE OR REPLACE FUNCTION prlntany (adata IN ANYDATA)
RETURN VARCHAR2
AS
aType ANYTYPE;
retval VARCHARZ(32767);
result_code PLS_INTEGER:
BEGIN ""''
В CASE adats.GetTypeCaType)
WHEN DBMS_nPES. TYPEC0DEJUM8ER THEN
RETURN-'NU6BER: ' || To_CHARCadata.AccessNumber);''
WHEN 口BKS—TYPES,TYPECODE_VARCHAR2 THEN
12131415161718192021222324252627282930313233343536373839404142434445464748495051525354551213141516171819202122232425262728293031323334353637383940414243444546474849505152535455RETURN 'VARCHAR2: ' || adata.AccessVarchar2;
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
WHEN DBMS_TYPES.TYPECQOE_CHAR THEN
RETURN-'CHAR: ' || RTRlMtadata.AccessCharJ;
WHEN DBMS_TrPES.TYPECODE_DATE THEN
RETURN 'DATE: ' || TO_CHAR(adata.AccessDate. 'YYYY-MM-DD hh24:nii:ss'};
WHEN DBMSJYPES.TYPECSE二OBJECT THEN
EXECUTE IMMEDIATE 'DECLARE ' ||
'myobj ' jI adata.GetTypeName || ': ' ||
'iTjyad ANYOATA :- :ad; 1 ||
'BEGIN ' И
':res ;- nyad.GetOt)ject(inyobj): ' 11
':ret myobj.prlntO l 1
'END;'
USING IN adata. OUT result_code. OUT retval:
retval :- adata.GetTypeName || 1: ' || retval;
WHEN D8MS_TYP£S.TYPECODE REF THEN
EXECUTE IWEDIATE 'DECLARE ' ||
'myref ' 11 adata.GetTypeName 11 f11
'myobj ' Ij SUBSTR(adata.GetTypeName, INSTRUdata.GetTypeName. ' ')) || ': ' ||
inyad ANYDATA :- :ad; 1 11
'BEGIN 'И•
:res nyad-GetREFCnyref); ' ||
'UTL-REF.SELECT.OBJECKmyref, myobj):' ||
':ret :- myobj.prlntO; 1 И
'END;'
USING IN adata, OUT result/o曲.OUT retval;
retval :- adata.GetTypeName || 1: ' || retvaT;
ELSE
retval := '<data of type ' 11 adata.GetTypeName ||‘>;
END CASE;
RETURN retval:'
EXCEPTION
WHEN OTHERS
THEN
IF INSTR(SQLERRM, 'component "PRINT'1 must be declared') > 0
THEN
RETURN adata. GetTypeNaine 11 ': <no printt) function〉’:
ELSE'
RETURN 'Error: ' || SQLERRM;
END IF;
END;
Рассмотрим код данной программы более подробно.
Строка
Описание
1
Функция получает единственный аргумент типа ANYDATA. Для ее вызова нужно преобразовать переменную к этому типу. Например:
DBMS_OUTPUT,PUT_LrNE(printany(ANYDATA.ConvertNumber(3.14159)));
5
На случай, если понадобится временная переменная для хранения результата, резервируем для нее 32 Кбайт. Поскольку для больших переменных типа VARCHAR2 PL/SQL выделяет память динамически, лишнюю память нам не приходится задействовать
продолжение^
Строю
Описание
6
Значение переменной result_code (см. строки 25 и 38) не используется в операциях, которьсе выполняются в примере, но его требует API ANYDATA
8
Тип данных ANYDATA включает метод Getiype, возвращаю山мй код соответствующего типа данных. Его спецификация следующая: MEMBER FUNCTION ANYDATA.GetType (OUT NOCOPY ANYTYPE) RETURN Ko/L_TMnaJnteger
Для использования данного метода необходимо объявить переменную типа ANYTYPE, в значении которой Oracle хранит детальную информацию сб инкапсулированном типе
11, 13, 15, 17, 27
12, 14, 16
В этих выражениях заданы константы из встроенного пакета DBMS_JYPES
В этих операторах используются функции-члены ANYDATA.ConvertNNN, введенные в Orade9l ReteBse2. В Release 1 можно было использовать процедуры-члены GetNNN, выполняющие похожие операции, однако им требовалась временная локальная переменная
18-25
Чтобы вывести информацию об объекте без многочисленных обращений к словарю данных, динамичесхий анонимный блок создает объект нужного типа и вызывает его метод-член prlntQ
28-ЗВ
Задача данного кода — найти объект по указателю и вернуть его содержимое, Он будет работать, только если у объекта имеется метод prints
49-51
Если попытаться вывести информацию об объекте, у которого нет метода print, генерируется ошибка времени выполнения. В этой части кода мы ее перехватываем и обрабатываем
Далее мы рассмотрим простой пример вызова этой функции:
DECLARE/
achar CHAR(20) :- 'flxed-length string':
a book book_t :- NEW book_t(id -> 12345. title -> 'гцу book', pages -> 100);
sref REF serial」;
asub subject_t :- subject_t('7he World'. NULL): BEGIN
DBMS_OUTPUT.PUT_LINE(printany(ANYDATA.ConvertNumber(3.141592654))):
DBMS_OUTPUT.PUT_LIME(printany(ANYDATA.ConvertChar(achar)));
DeMSZ0UTPUT.PUTj^E(pr1ntany(ANY0ATA.CanvErtObject(abook))):
OBMS_OUTPUT.PUT_LINE(pr1ntan>CANYDATA.ConvertOt>ject(asub)));
SELECT TREAT(REF(c) AS REF ser1al_t) INTO sref
FROM catalog items c WHERE title - 'Time';
DBMS OUTPUT.PlK_LINE(printany(ANYDATA.ConvertRef(sref))):
END:
Код выводит следующее:
NUMBER: 3,141592654
CHAR: fixed-length string
SCOTT.BOOK_T: Id-12345; tltle-щу book; pub!1cat1on_date-; 1sbn-: pages-100
SCOTT,SUBJECT^?: <no prlntO function*
REF SCOTT.SERTaL_T: Id-10004: title-Time; publication.date-; 1ssn-0040-781X;'
open^or^losed-Open
Как видите, тип данных ANYDATA не так *。6叫 как иерархии наследования, поскольку требует явного преобразования данных. Однако он позволяет создавать
столбцы таблиц и атрибуты объектов, в которых можно хранить данные практически любого типа.
Создание временного типа данных
PL/SQL не поддерживает определения новых объектных типов в разделе объявлений программы, но с помощью встроеявых типов данных ANY можно создавать «временные» типы, существующие только в период выполнения программы. Значения типа, созданного с помощью ANYTYPE, можно передавать в качестве параметра. Кроме того, можно создавать его экземпляры как значения типа ANYDATA, Рассмотрим следующий код:
CREATE OR REPLACE FUNCTION create a type
RETURN ANYTYPE
AS
туацу ANYDATA:
retype ANEPE;
BEGIN
/* Соз%нне анонимного врененкогс типа с ивумя атрибутами: number, date */
ANYTYPE.BeginCreateCtypecade -> DBMS_TYPES.TYPECODE_OBJECT. atype -> гпузде);
n^type.AddAttrCtypecade -> D0MS_TYPES.TYPECODE_NUM8ER. aname -> * just__a_nunber',
prec -> 38. scale -> 0. len -> NULL, csld -> NULL, csfrm -> NULL):
nytype.AddAttr(typecode 2 DBMS__TYPES.TYPECQDE_DATE. aname -> 1 Just_a_date', prec -> 5, scale -> 5, 1en -> NULL, csid ■> NULL csfrm -> NULL): ~
niytype.EndCreate:
RETURN nytype;
END:
Как видите, процесс проходит в три основных этапа.
Создание типа начинается вызовом статической процедуры BeginCreate. Она возвращает инициализированный объект ANHVPE.
С помощью процедуры-члена AddAttг по одному добавляются атрибуты.
Вызывается процедура-член EndCreate.
Аналогичным образом перед использованием типа присваиваются значения его атрибутам:
DECLARE
Itype ANEPE :- create a type:
l_any ANYDATA;
BEGIN-
ANYDATA.BeglnCreate(dt^pe -> Itype. adats 2 l_any):
1 any.SetNirbertnuni ~ 12345);-
fany-SetDatetdat ■> SYSDATE):
any.EndCreate;
END"•
Если структура данных заранее не известна, информацию о ней можно получить с помощью методов ANYTYPE (в частности, GetAttrElemlnfo) в сочетании с методами ANYDATA.Get. (Пример вы найдете в файле anyObJect.sql на web-узле издательства O'Reilly)
Все делаем сами
Некоторые приверженцы объектно-ориентированного программирования считают, что каждый объектный тип должен быть самодостаточным. Если объект предназначен для хранения в базе данных, необходимо знать, как его сохранить и какие у него должны быть методы для обновления! удаления и выборки, то есть тип следовало бы дополнить такими методами:
ALTER TYPE cata1ogjtem_t
ADD MEMBER PROCEDURE remove
CASCADE:
CREATE OR REPLACE TYPE BODY catalog_1tem_t
AS"
MEMBER PROCEDURE remove
IS
BEGIN
DELETE catalogjtems
WHERE id - SELF.id:
SELF ;- NULL;
END;
END:
(Oracle не предлагает для объектов Методы-деструкторы.) Определив метод на уровне супертипа, мн автоматически включили его и во все подтипы. В данном случае предполагается, что все соответствующие объекты находятся в одной таблице, однако в некоторых приложениях может потребоваться дополнительная логика для поиска объектов. (Кроме того, работающая в реальных условиях версия метода может включать код для выполнения дополнительных операций, та・ ких как удаление зависимых объектов и/или архивирование данных перед окончательным удалением объекта.)
Если предположить, что перед записью временного объекта на диск приложение должно его модифицировать в памяти, можно объединить вставку и обновление в одном методе, который мы назовем save.
ALTER ТУРЕ catalog_1ten_t
ADD MEMBER PROCEDURE~save
CASCADE;,
CREATE OR REPLACE ТУРЕ BODY catalog_1tem_t
AS~
吃MBER PROCEDURE save
IS
BEGIN
UPDATE catalogjtems c SET c “ SELF
WHERE Id - SELF.id;
IF SQLXRQWCOUWT - 0
THEN
INSERT INTO catalogjterns VALUES (SELF);
END IF;
END:
Эта процедура заменяет значения всех столбцов, даже если они не изменились. В результате подобной операции могут вызываться «липшие» триггеры, что приведет к ненужным операциям ввода-вывода. К сожалению, это один из недостатков объектного подхода. При более внимательном программировании можно было бы избежать модификации неизменившихся значении столбцов супертипа, но столбцы подтипа ни в какой разновидности инструкции UPDATE в отдельности недоступны.
Из всех операций над объектными таблицами труднее всего инкапсулировать выборку, что связано с разнообразием возможных условий в предложении WHERE и большим количеством форм данных результата. Как следствие, спецификация критерия запроса может оказаться очень сложной. Каждый, кому приходилось разрабатывать формы для выполнения пользовательских запросов, подтвердит это. Что касается результатов запроса, то и они могут быть представлены в одном из следующих видов:
О коллекция объектов;
О коллекция ссылок REF;■
О результирующий набор с конвейерной: организацией;
О курсорная переменная (сильно- или слаботипизированного типа).
На выбор представления могут повлиять требования к приложению и необходимая для него программная среда. Рассмотрим пример, в котором используется курсорная переменная:
ALTER TYPE catalogjtem^t
ADD MEMBER FUNCTION retrieve_matching
RETURN SYS_REFCURSOR ~
CASCADE; ~
В данном случае применяется встроенный тип SYS_REFCURSOR — слаботипизи- рованный тип курсора, который Oracle предоставляет просто для удобства. Идея заключается в следующем: вызывающее приложение создает экземпляр типа са- talog_item_t» содержащий образец искомых данных, вызывает его функцию ret- rieve_matching и затем использует возвращаемую ею курсорную переменную как указатель на результирующий набор строк. Ниже приведен код выполнения запроса с помощью функции retrieve_matching:
DECLARE
catalogjtem catalogjtem_t:
l_refcur SYS_REFCURSOR;-
12sample_object book_t :- NEW book_t(title -> 'OracleX'):
BEGIN -一一
l_refcur :- l_sample_object.retrleve_match1ngC):
LOOP-
FETCH l_refcur IMTD catalogjtem:
EXIT WHEN l_refcurXNOTFOUNO~
DBM$_OUTPUT.PUT_LINE(catalog_item.print):
END LOOP:~
CLOSE l_rcfcur;
E也 -
При реализации данного подхода необходимо решить, должен ли запрос извлекать все соответствующие элементы каталога или только элементы типа, к ко、 торому относится и экземпляр объекта. В первом случае соответствующая реализация будет такой:
CREATE OR REPLACE TYPE BODY catalog 1tem_t AS
MEMBER FUNCTION rBtrieve jratchlnfi
RETURN SYS REFCDRSOR
IS
re化"SYS REFCURSOR:
BEGIN ".
IF SELF IS OF {book t)
THEN
OPEN 1 re化ur FOR
SELECT VALUE(C)
FROM catalogjterns c
WHERE (SELF.Id IS NULL OR Id - SELF.fd)
AND (SELF.title (S NULL OR title LIKE SELF,title || 'X')
AND (SELF.publlcationJate IS NULL
OR publ1cation_date - SELF,publicat1on_date)
W€ (TREAT(SELF AS book_t).1sbn IS NULL -
OR TREATCVALUE(c) AS book t).1sbn ■ TREATCSELF AS book J). Isbn) AND (TREAT4ELF AS book_t) .pages IS NULL
OR TREAT(VALUE(C) AS book t).pages -
TREATtSELF AS book_t).pages):
ELSIF SELF IS OF (serial t)
THEN-
OPEN 1 refcur FOR
SELECT VALUES)
FROM catalogjtems c
WHERE (SELF.Id IS NULL OR Id - SELF Jd)
AND (SELF.title IS NULL OR title LIKE SELF.title || T)
AWD (SELF.publlcationJate IS NULL
OR publ1cation_date - SELF.publication date;
ANO (TREATCSELF AS serial_t).issn IS NULL ~
OR TREATtVALUE(c) AS"serial_t).issn -
TREATCSELF AS SErial_t).1ssn)
AND [TREATtSELF AS serial J) .apen^or_closed IS NULL OR TREAT(VALUE(c> AS serial^) -ope^or^closed - TREATCSELf AS serial tj.open or_cTosed);
END IF;"""
RETURN l_refcur:
END:~
Это не самое лучшее решение поскольку оно требует модификации метода retr1eve_match-l ng супертипа при добавлении каждого нового подтипа. Можно было бы подменять дянный метод в подтипах, но когда потребуется результирующий набор, включающий книги и периодику, мы не сможем вызвать метод супертипа. Получится странная ситуация, когда запрос для книги возвращает иформацию о периодике или наоборот.
Конечно, можно модифицировать супертип, разрешив создание его экземпляров, но лучше сделать функцию retrieve_matching статическим методом. Попробуйте выполнить это упражнение самостоятельно.
Сравнение объектов
До сих пор в наших примерах использовались объектные таблицы, то есть таблицы, в которых каждая строка представляет собой один объект. Такие объекты создаются с помощью инструкции CREATE TABLE.. .0F( и для работы с ними Oracle обеспечивает специальные возможности, в частности переходы по ссылкам и интерпретацию целого объекта (а не отдельных его столбцов) как единицы ввода-вывода.
Объектный тип может исполъзозатъся также в качестве типа отдельного столбца таблицы, Предположим, что мы хотим завести журнал изменений для таблицы catalog_1 terns и фиксировать в нем все операции вставки, обновления и удаления строк этой таблицы3.
CREATE TABLE catalog history AS (
id INTEGER NOT NULL PRIMARY KEY,
action CHAR⑴ NOT NULL,
act1on_tiffle TIMESTAMP NOT NULL DEFAULT TIMESTAMP, oldjtem catalogjtem_t.
new J tan catalogjteni_t);
Когда вы начнете заполнять таблицу объектами, у вас возникнет вопрос, ка* кой метод использует Oracle при сортировке или индексировании таблицы по одному из столбцов типа catalog^tem_t. Существуют четыре метода сравнения для объектов.
О Сравнение на уровне атрибутов. При сортировке, создании индексов и сравнении задаются соответствующие атрибуты.
О Стандартный способ SQL. SQL Oracle умеет выполнять простейшее сравнение на эквивалентность. Два объекта считаются эквивалентными, если они определены на основе одного и того же типа, а все их соответствующие атрибуты равны. Это сравнение может быть выполнено, когда объекты имеют только скалярные атрибуты (не коллекции и не LOB) и их объектный тип не содержит метод МАР или ORDER.
О Мегод-щген МАР. Можно создать специальную функцию-метод, возвращающую не значение объекта, а соответствующее ему значение одного из типов данных, которые Oracle умеет сравнивать, Этот метод работает только при отсутствии Метода ORDER.
О Метод-член ORDER, В данном методе используется функция, которая сравнивает два объекта и возвращает флаг, указывающий их относительный порядок. Этот метод работает только при отсутствии метода МАР,
Казалось бы, проще всего заполнять таблицу-журнал с помощью триггеров DML. Однако яиутрк триггера недоступны атрибуты подтипов нижнего уровня. Поэтому в нем можно фиксировать только изменения общих атрибутов (атрибутов супертипа).
Стандартному методу сравнения SQL редка находится применение, и поэтому мы не будем его рассматривать. О других, более полезных способах сравнения объектов рассказывается в следующем разделе.
Сравнение на уровне атрибутов
Сравнение на уровне атрибутов мажет быть не совсем тем, что требуется, на зато оно выполняется очень проста, как в PL/SQL, так и SQL (если талька вы не забудете задать псевдоним таблицы в SQL-инструкции). Oracle позволяет задавать атрибуты с памагцью точечного синтаксиса:
SELECT * FROM catalog history с
WHERE c.oldjtan.id > 10000
ORDER BY NVL(TREAT(c.old^itern as book_t).lsbn. TREAT(c.old_item AS serial_t).issn)
Создание индекса на основе атрибутов выполняется так же проста:
CREATE INDEX catalog_history_old_1d_1dx ON catalog_history c (c.old_1tem.1d);
Метод MAP
Методы MAP и ORDER позволяют выполнять следующие инструкции:
SELECT * FROM catalog_history
ORDER 8Y oldjtern;
IF oldjtern > neW—ltem
THEN 二 ~
Сначала рассмотрим метод МАР. Его простейший вариант добавляется в catalogs tem_t таким образом:
ALTER ТУРЕ catalog_item_t
ADD MAP ME^ER FUNCTION map1t 旺TURN NUMBER
CASCADE;
CREATE OR REPLACE TfPE BODY catakgjtem t
AS~
MAP MEMBER FUNCTION mapit RETURN NUMBER
IS
BEGIN
RHURN id:
END:
END:'
Если предположить, что сортировка по идентификаторам имеет смысл, та теперь можно сортировать и сравнивать элементы каталога, причем Oracle будет вызывать новый метод автоматически. Функция не обязательна должна быть так проста; например, она мажет возвращать скалярное значение, вычисляемое на основе всех атрибутов типа, объединенных способом, который имеет для библиотекарей определенный смысл.
У метода МАР есть один побочный эффект: сравнение объектов на эквивалентность может выполняться неподходящим способом. При наличии такого метода два объекта считаются эквивалентными, если равны возвращаемые ими значения. Когда вам нужна сравнивать объекты, па очереди анализируя их атрибуты, создайте собственный метод (не МАР) или же используйте метод ORDER.
Метод ORDER
Альтернативой методу MAP является функция-член ORDER, которая сравнивает два объекта: SELF и еще один объект того же типа, заданный в качестве ее аргумента. Эта функция должна возвращать целочисленное значение, которое в зависимости от порядка двух объектов может быть положительным, нулевым или отрицательным (табл. 21.2).
Таблица 21.2. Поведение функции-члена ORDER
Порядок элементовСоответствующм значаще функции ORDER.
^LF < Объею^в.аргумекте Любое отрицательное число (обычно -1)
SELF = Объею_в_аргументеО
SELF > ОбъеЕ_в一аргументеЛюбое полажительмое число (обычно 1)
Результат сравнения не определен NULL
Рассмотрим пример кода, в котором используется метод ORDER.
123456789123456789ALTER TYPE catalogjtem_t
123456789
123456789
DROP MAP MEMBER FUNCTION niaplt RETURN NUMBER
CASCADE:
ALTER TYPE catalog_itsn_t
ADD ORDER MEMBER FUNCTION arderlt (abj2 IN catalog item t) RETURN INTEGER”-
CASCADE;
CREATE OR REPLACE TYPE BODY catalog Item t
AS一 -
ORDER MEMBER FUNCTION orderit (obj2 IN catalog 1tan_t)
RETURN I仃EGER--
self—gt_。2 CONSTANT PISJNTEGER :- 1:
eq CONSTANT PLS_INTEGER~ = 0;
O2_gt_se1f СОРТАМ? PLSJNTEGER :- -1:
1_n»tching_count NUMBER:
BEGIN -
CASE
WHEN obj2 IS OF (book』)AND SELF IS DF (serial_U THEN
RETURN o2^gt_self;~-
WHEN obj2 IS^OF (serialj) AND SELF IS OF (book_t) THEN
RETURN self gt_o2: -~
ELSE▼—
IF obj2.title - SELF.title
AND obj2.publication_date - SELF.publ1cation_date
THENt ~~
IF obj2.subject refs IS NOT NULL
AND $ELF.subject_refs IS NOT NULL.
AND obj2.subject_refs.COUNT - SELF.subject^refs,COUNT
THEN~一
SELECT COUNT(*) INTO l_matching count FROM (SELECT *-'
О Триггеры INSTEAD OF. С помощью триггера INSTEAD OF можно указать Oracle, как именно можно выполнять вставку, обновление и удаление данных в представлениях.
С точки зрения объектной теории, объектные представления имеют один несущественный недостаток — они не обеспечивают инкапсуляции. Поскольку приложение применяет инструкции INSERT, UPDATE и DELETE непосредственно к содержимому таблиц реляционных баз данных, ни о какой инкапсуляции речь не идет. Тогда как объектно-ориентированная архитектура приложения обычно не допускает непосредственного доступа к данным. Однако поскольку Oracle не поддерживает ни личных атрибутов, ни личных методов, ее объектные средства не обеспечивают полной инкапсуляции.
Если расположить объектные представления «поверх» существующей системы, новые приложения смогут пользоваться их преимуществами, а старые будут работать, как и прежде. Применение объектных представлений представлено на рис. 21.2.
В следующих разделах обсуждаются особенности объектных представлений, в том числе различия между объектными представлениями н объектными таблицами, которые имеют особую важность и представляют интерес для программистов PL/SQL
Существующие реляционные системы
В качестве второго примера этой главы рассмотрим применение объектных представлений в приложении базы данных компании, занимающейся web-дизайном.
Реляционное приложение компании используется для работы с информацией об изображениях (GIF, JPEG и т. д.), которые размещаются на разрабатываемых ими web-узлах. Эти изображения находятся в соответствующих файлах, но информация о них содержится в таблицах базы данных. Для того чтобы художникам легче было искать нужные изображения, с каждым из них связано одно или несколько ключевых слов. Данные об изображениях и ключевые слова, хранятся в виде двух таблиц, связанных отношением <главная-подчиненная>.
В базе данных имеется таблица поставщиков изображений:
CREATE TABLE suppliers (
id INTEGER NOT NULL PRIMARY Ш.
name VARCHAR2C400) MOT NULL
Ниже приведена таблица метаданных изображений:
CREATE TABLE Images (
imagjid INTEGER NOT NULL PRIMARY KEY.
file_name VARCHAR2C512) NOT NULL,
fllejype VARCHAR2C12) NOT NULL.
suppTlerJd I叶EGER REFERENCES suppliers C1d).
supplier r1ghts_descriptor VARCHAR2(256).
bytes INTEGER ~
);
Однако не все изображения поступают от поставщиков. Если значение идентификатора поставщика (supplIeMd) равно NULL, значит, изображение создано сотрудниками компании.
В базе данных имеется еще едва таблица, которая содержит ключевые слова, связашше с изображениями:
CREATE TABLE keywords (
imagejd IAEGER NOT NULL REFERENCES Images (image id),
keyword VARCHAR2(45) NOT NULL.-
CONSTRAINT keywords_pk PRIMLY KEY (Imagejd. keyword)
Предположим, что в эти таблицы помещены такие данные:
INSERT INTO suppliers VALUES (101. 'Joe"s Graphics');
INSERT Ш0 suppliers VALUES (102, "mage Bar and Grill'):
INSERT INTO images VALUES (100001. 7f11es/web/60s/sirrlley_face.png', 'image/png'.
'fair use', 813);一
INSERT Ш0 images VALUES (100002 . 7files/web/60s/peace_symbol .gif'. 'image/gif', 101. 'fair use', 972):-
INSERT INTO Images VALUES (100003, 'files/web/OOs/towers.jpg'. 'Image/jpeg'. NULL.
NULL 21W;
'SIXTIES');'HAPPY FACE'):’SIXTIES'):'PEACE S^BOL'); 'DERRY RUBIN'):'SIXTIES');'HAPPY FACE'):’SIXTIES'):'PEACE S^BOL'); 'DERRY RUBIN'):INSERT INTO keywords VALUES (100001.
'SIXTIES');
'HAPPY FACE'):
’SIXTIES'):
'PEACE S^BOL'); 'DERRY RUBIN'):
'SIXTIES');
'HAPPY FACE'):
’SIXTIES'):
'PEACE S^BOL'); 'DERRY RUBIN'):
INSERT INTO keywords VALUES (100001. INSERT INTO keywords VALUES (100002, INSERT IMTO keywords VALUES (100002. INSERT IMTO keywords VALUES tlOQOD2.
35 3637383940414243444546474849505135 36373839404142434445464748495051
35 36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
35 36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
FROM TABLEfSELECT CAST(SELF.subject_refs AS subjecVefs_t) FROM dual]
INTERSECT
SELECT *
FROM TABLECSELECT CAST(obj2.subject_refs AS subject_refs_t) FROM dual)):~
IF 1_match1ng_count - SELF.subject_refs,COUMT then" ~~
RETURN eq:
END IF;
END IF;
END IF;
RETURN NULL:
END CASE;
END;
ЕЮ:
Ниже приведен ряд замечаний относительно данного кода.
Строки
ОПИСВН1М
21-24
26-46
Определено, что при сортировке книги будут размещаться перед периодикой
Проверка на эквивалентность. Поскольку Oracle не знает, как сравнивать коллекции, мы воспользуемся способностью Oracle выполнять выборку из коллекции, как из таблицы> Проверив, содержит ли реляционное пересечение двух коллекций ожидаемое количество элементов, можно определить, имеет ли каждый элемеит первой коллекции эквивалент во второй коллекции (предполагается, что таково определение эквивалентности}
Этот метод не в полной мере отвечает всем требованиям, поскольку не проверяет атрибуты, специфические для подтипа, на более длинный код привести было невозможно, поскольку объем книги ограничен.
Рекомендации по проведению сравнения
В заключение приведем несколько правил и рекомендаций, касающихся методов сравнения объектов.
О Методы МАР и ORDER не могут сосуществовать в одном объектном типе, поэтому используйте один из них.
О При сортировке или сравнении большого количества объектов, как это бывает в S QL-инструкциях, Oracle рекомендует применять метод МАР. Дело в том, что внутренняя оптимизация сокращает количество его вызовов, тогда как метод ORDER должен обязательно вызываться для каждого сравнения.
О Oracle игнорирует имена методов, поэтому можете называть их как угодно.
О Подтипы могут включать метод МАР, но только если он есть и у супертипа.
О Подтипы не могут включать метод ORDER, реализовать его можно в супертипе.
Объектные представления
Объектные расширения Oracle предлагают программистам PL/SQL богатые возможности для разработки*новых систем, однако маловероятно, что ради их применения вы захотите полностью перепроектировать старые системы. Для того чтобы существующие приложения могли использовать преимущества новых объект* ных функций, в Oracle реализованы объектные представления. Они имеют ряд важных достоинств, которые описаны ниже.
О Объектное представление удаленных данных. В Oracle9i еще не поддерживаются объектные таблицы и физические REF-ссылкя в распределенных базах данных, но можно создавать объектные представления и виртуальные REF-ссылки, представляющие удаленные данные реляционных баз как объекты.
О Виртуальная денормализация. В реляционной базе данных или даже объектно-реляционной базе данных обычно реализованы однонаправленные отношения. Например) книга относится к определенному количеству категорий. Объектные представления позволяют установить обратное соответствие, в частности объект-категория может включать коллекцию REF-ссылок, указываю* Шик на все книги эгой категории.
О Эффективность доступа к объектам. В приложениях Oracle Call Interface (OCI) могут использоваться программные конструкции, обеспечивающие удобное извлечение, кэширование и обновление объектных данных. Сокращая количество обращений приложения к серверу базы данных, они повышают производительность приложения и, кроме того, делают код более лаконичным.
О Большая гибкость в отношении изменения объектной модели. Хотя в Огас- le9t имеются прекрасные средства для реализации эволюции типов, добавление и удаление атрибутов объектов по-прежнему сопряжено с перемещением таблицы по диску» чего администраторы баз данных очень не любят. Перекомпиляция объектных представлений не имеет таких последствий.
В то же время у объектных представлений есть определенные недостатки.
□ Производительность. Объектные представления обрабатываются не так быстро, как этого хотелось бы.
О Виртуальные ссылки. Виртуальные ссылки не хранятся в базе данных, а создаются в ходе вьшолнения программы. Это может вызвать определенные проблемы, если однажды вы вдруг захотите преобразовать объектные представления в объектные таблицы:
Зато в Oracle появились дополнительные средства, расширяющие возможности любых представлений, включая и объектные. Отметим две из них — коллекции и триггеры, в объявлении которых содержится предложение INSTEAD OF (далее триггеры INSTEAD OF).
О Коллекции. Если взять две реляционные таблицы, связанные отношением глав- ная'Подчиненная, можно создать для них представление, возвращающее детализирующие строки подчиненной таблицы в виде одного нескалярного атрибута (коллекции) строки главной таблицы.
В следующих разделах описываются объектные представления, определенные на основе этих данных:
О первое представление будет определено на основе типа изображения, включающего в качестве атрибута коллекцию ключевых слов;
О второе представление является подпредставлением, то есть мы определим его на основе подтипа из иерархии объектных типов. Оно будет включать характеристики изображений от поставщиков;
О последнее представление включает ключевые слова и соответствующие ссылки на изображения (оно реализует обратное отношение).
Объектное представление с атрибутом-коллекцией
Перед созданием объектного типа для первого представления нужно определить тип коллекции для хранения ключевых слов. В данном случае используем вложенную таблицу, поскольку порядок ключевых слов не важен и ограничения на их максимальное количество не существует.
CREATE TYPE keyword_tab_t AS TABLE OF VARCHAR2(45);
Объект для хранения изображения определить очень просто:
CREATE ПРЕ Image」AS OBJECT (
Imagejd IMTEGER, image file BFILE. fllejype VARCHAR2(12),■
bytes INTEGER, keywords keyword_tab_t
Если файлы изображений и сервер базы данных располагаются на одном компьютере, то вместо имени файла можно использовать тип данных BFILE. Нужно создать директорию», то есть используемый только в Oracle псевдоним директории операционной системы, в которой содержатся изображения. Зададим корневую директорию, поскольку в столбце filename указаны полные пути к файлам. CREATE DIRECTORY rootdir AS '/';
Мы пока не определили связь между реляционными таблицами и объектным типом. Это две совершенно независимые сущности, и наложение определения
CREATE VIEW Images v
OF image t
陷 WITH OSJECT IDENTIFIER (imagejd)
SELECT 5.1таде_1<1. BFILENAMEC'rootdir1. 1 .file^name).
.Mle_type7 i.bytes.~
CAST (MULTISET CSELECT keyword
FROM keywords k
WHERE k.imagejd - l.lmagjid)
AS keywordj;ab_t)
FROM images I;
Два компонента данной инструкции характерны только для объектных представлений.
О OF 1mage_t. Означает, что будут возвращены объекты типа 1mage_t.
О WITH OBJECT IDENTIFIER (Imagjld). Для того чтобы возвращаемые в представлении данные были похожи на настоящие экземпляры объектов, им нужен объектный вдентификатор. Определив первичный ключ как основу для виртуального OID, можно пользоваться преимуществами REF-навигации по объектам представления.
Кроме того, список выборки объектного представления должен соответство' вать (по количеству и типу данных атрибутов) атрибутам объектного типа.
Итак, объектное представление мы создали, но что же с ним можно делать? Прежде всего, из него можно извлекать данные, как из объектной таблицы. Например, в SQL*Plus запрос
SOL> SELECT iinage_1d, keywords FROM Images^v:
вернет следующие данные:
IMAGEJD KEYWORDS
100003 KEYWORD_TAB_T(}
1QQ0Q1 KEYWORD JABJC HAPPY FACE'. SIXTIES')
100002 KEYUORDZtASJC JERRY RUBIN'. 'PEACE SYMBOL', 'SIXTIES')
Чтобы все это еще более походило на объекты, можно добавить в определение
типа методы, в частности метод prlntO:
ALTER TYPE 1mage_t
ADD MEMBER FUNCTION print RETURN VARCHAR2
CASCADE;
CREATE OR REPLACE TYPE BODY Image』
MEMBER FUNCTION print
RETURN VARCHAR2■
IS
filename images.fl 1 e_nameJI;TYPE;
dirname VARCHAR2(3Q)7
keywordjlst VARCHAR2(32767)i
BEGIN
DBMS_LOfi.FILEGETI4AME(SELF. 1mage_fllef dimame. filename):
IF SELF.keywords IS NOT NULL
THEN
FOR key_elt IN 1..SELF.keywords.COUNT
LOOP ~
keywordjlst :- keywordjist 11 '. ' || SELF, keywords (key elt);
END LOOP:"-
END IF:
RETURN 'Id-' [| SELF.ImageJd || '; File-' || filename
'; keywords-' || SUBSTRCkeywardjist. 3);
NULL ИЛИ НЕ NULL?
Вы уже знаете, что коллекция, равная NULL, и инициализированная коллекция, не содержащая ни одного элемента, это не одно и то же. У изображения 100003 нет ключевых слов, но объектное представление по ошибке возвращает пустую инициализированную коллекцию. Чтобы получить вместо нее значение NULL, для проверки количества ключевых слов можно использовать функцию DECODE:
CREATE OR REPLACE VIEW 1mages_v
OF 1mage_t
WITH OBJECT IDENTIFIER (imagjid)
AS
SELECT l.lmagejd, BFILENAME('ROOTD1R\ 1.filename).
1.f11e_type. i-bytes.
DECODE((SELECT COUNT(*)
FROM keywords k2
WHERE k2.lmage_1d - 1.1mage_1d).
0. NULL. ~~
CAST (MULTISET (SELECT keyword
FROM keywords k
WHERE k.lmagejd - 1.1mage__1d)
AS keyword_tab_t))
FROM images i;
Если ключевых слов нет, возвращается значение NULL, в противном случае —выражение CAST(MULTISET(...)). Для этого представления инструкция
SELECT WHERE 1mageJd-100003
должна вернуть следующие данные:
IMAGE」。KEYWORDS
100003
Однако вы можете посчитать, что талая концептуальная чистота не стоит того, чтобы выполнять дополнительный ввод-вывод (или писать такую сложную инструкцию SELECT).
Описанный выше пример показывает, как составить <плоский» список ключевых слов, содержащихся в виртуальной коллекции.
Кроме того, работая с объектными представленими, можно применять следующие средства:
О Виртуальные объектные ссылки. Виртуальные объектные ссылки (REF) — это указатели на виртуальные объекты (см. ниже раздел «Различие между объектными представлениями и объектными таблицами»).
Q Триггеры INSTEAD OF. Триггеры данного типа позволяют непосредственно манипулировать содержимым представления (см. ниже раздел «Триггеры INSTEAD OF»).
Объектные подпредставления
Если в нашем примере одни изображения должны интерпретироваться не так, как другие, можно создать для них подтип. Выделим в подтип изображения, у которых есть поставщики. В нем будет содержаться ссылка на объект поставщика, определенный следующим образом:
CREATE TYPE 5叩plier t AS OBJECT (
id INTEGER,
name VARCHAR2(400)
Соответствующее объектное представление будет иметь такой вид:
CREATE VIEW suppl 1ers_v
OF suppl1er_t
WITH OMECT~IDENTIFIER (id)
AS
SELECT Id. name
FROM suppliers;
Теперь нужно изменить или удалить и повторно создать базовый тип, но уже с предложением NOT FINAL;
ALTER TYPE bage__t NOT FINAL CASCADE;
Затем можно создать для него подтип:
CREATE TYPE supplied_iniages_t UNDER 1mage_t (
supplier_ref REF supplier_tt
suppl1er_r1ghts_descriptor VARCHAR2(256)
После этой подготовительной работы создается представление на основе подтипа, и в его определение включается предложение UNDER:
CREATE VIEW supplied_1mages_v
OF suppl 1ed_iwges_t
UNDCT Iruges v
AS亠
SELECT i.imagejd, BFILENAME('ROOTDIR', 1 .file_name),
i.file type, 1.bytes.
CAST (MULTISET (SELECT keyword
FROM keywords k
WHERE k.image_id = i.imagjld)
AS keyword_tab_t).~
MAKE_REF( su而 1 ers_v. suppll erjd),
suppl1er_rights^descrlptor
FROM images i
WHERE suppllerjd IS NOT NULL:
Oracle не позволяет при формировании подпредставления запрашивать данные из суперпредставления, поэтому в рассматриваемом случае обращение происходит непосредственно к базовой таблице, а предложение WHERE ограничивает набор извлекаемых строк. Обратите внимание на то, что в подпредставлениях не используется предложение WITH OBJECT IDENTIFIER, поскольку они наследуют идентификаторы объектов от суперпредставления.
В этом запросе используется новая функция MAKE_REF, предназначенная для вычисления ссылки на виртуальный объект. Он в данном случае представляет поставщика, возвращаемого представлением suppliers^. Синтаксис функции МА- <E_REF выглядит следующим образом:
FUNCTION MAKE_REF (представление, список^значений) RETURN Ссылка;
Здесь представление — это объектное представление, на которое должна указывать ссылка, заданная в значении аргумента ссылка, а список_значений - это разделенный запятыми список значений столбцов, типы данных которых должны в точности соответствовать типам данных OID-атрибутов представления, указанного в значении аргумента представление.
Учтите, что функция MAKE.REF не извлекает идентификатор из представления, ока лншъ использует встроенный алгоритм Oracle для формирования REF-значения. Однако, как и REF-ссылку виртуальные ссылки могут не указывать на реальные объекты.
Продемонстрируем неожиданный результат — хотя мы не изменяли супера представление, изображения от поставщиков теперь выводятся им дважды (то есть дублируются):
SQL* SELECT COUNTt*), Imagejd TOH 1mages_v GROUP BY Imagejd;
CQUNTC*) IMAGE_ID
2100001
2 1QQQQ2
110D0Q3.
Дело в том, что Oracle возвращает логическое объединение (UNION ALL) двух запросов: к суперпредставлению и подпредставлению. В этом есть определенный смысл, ведь изображение от поставщика все же является изображением. Для удаления дубликатов добавьте в родительское представление предложение WHERE, которое исключает строки, возвращаемые подпредставлением:
CREATE OR REPLACE VIEW 1mage$_v AS
WHERE suppllerjd IS NULL;
Объектное представление, реализующее обратное отношение
С целью демонстрации виртуальной денормализации можно создать объектный тип для ключевых слов и соответствующее представление, связывающее ключевые слова с изображениями, которые они описывают:
CREATE TYPE inage_refs_t А$ TABLE OF REF Imagjt;
CREATE TYPE keyword_t AS OBJECT (
keyword VARCHAR2(45).
1mage_「ef§ image_refs_t);
Определение представления имеет следующий вид:
CREATE OR REPLACE VIEW keywords_v
OF ksyword_t■'
WITH OBJECT IDENTIFIER (keyword)
AS'
SELECT keyword, CAST(MULT1SET(SELECT MAKE_REF(1niages_v. imagejd)
FROM keywords
WHERE keyword - main.keyword)■
AS 1mage_refs t)
FROM (SELECT DISTINCT keyword"FROM keywords) main;
Запросы к данному представлению не будут производиться очень быстро — ведь при этом нужно выполнять операцию SELECT DISTIHCT, так как в базе данных нет справочной таблицы ключевых слов. Даже без использования объектных функций с точки зрения времени выполнения эти запросы были бы дорогостоящими.
Вы справедливо заметите* что функция MAKE_REF здесь не обязательна; объектные ссылки можно извлекать с помощью вложенного запроса к представлению 1mages_vraHe к таблице ключевых слов, В большинстве случаев функция MAKE_REF выполняется быстрее поиска в объектном представлении. Кроме того, возможность произвести такой поиск может у вас вообще отсутствовать.
Как бы там ни былог но на этом этале можно выполнять такие лаконичные запросы:
SQL> SELECT DEREF(VALUE(1)).pr1nt()
FROK keywords v y, TABLE(v.1mage_refs) 1
WHERE keyword - 'SIXTIES1; "
DEREF(VALUE(L)).PRINT().
Id-100001; F1le-/files/web/60s/sn-lley_face.gif; keywords-HAPPY FACE. SIXTIB
Id-100002: Fl1e-/f11es/web/60s/peace_symbol.g1 f; keywords-DERRY RUBIN. PEACE SYMBOL,
SIXTIES-
Иными словами, теперь можно вывести все изображения, помеченные ключевым словом SIXTIES, вместе со всеми их ключевыми словами и атрибутами!-
Триггеры INSTEAD OF
Поскольку триггеры INSTEAD OF описывались в главе 18, в этом разделе принцип их действия не рассматривается. Мн обсудим только их применение для обновления объектных представлений. Если вы намерены освоить объектный подход, вас может заинтересовать, не являются ли триггеры INSTEAD OF просто реляционным дополнением, позволяющим приложениям выполнять DML-операции. Рассмотрев артументы *за» и «против», вы сможете решить, какой подход лучше выбрать для своего приложения.
Аргументы «против»
Для инкапсуляции DML-операций гораздо лучше триггеров INSTEAD OF подходят пакеты и объектные методы PL/SQL. Логику такого триггера легко перенести в альтернативную конструкцию PL/SQL, имеющую более универсальное применение. Иными словами, если в качестве средств выполнения DML в системе используются пакеты и методы, а механизм этот давно стандартизирован и налажен, триггеры в него могут совершенно не вписываться и лишь усложнят вашу работу.
О Дублирование ОШ в нескольких представлениях. Если объектное представление создано на основе объектной таблицы или представления и О ID определяется с помощью ключевого слова DEFAULT, представление содержит идентификаторы объектов, соответствующие идентификаторам базовой структуры. Скорее всего, вам подойдет второй способ, поскольку отдельные представления -это, по сути, сохраненные результаты запросов.
Хранение в базе данных REF-ссылок
Если вы разрабатываете приложение с «физическими» объектными таблицами| REF-ссылки на объекты могут храниться в других таблицах. Ведь REF - это двоичное значение, используемое Oracle в качестве указателя на объект.
Однако если попытаться сохранить в реальной таблице виртуальную ссылку (то есть ссылку на строку объектного представления), Oracle сгенерирует ошибку. Поскольку виртуальная ссылка зависит от значений столбцов, вместо нее нужно созфанять данные значения. Это скорее неудобство, чем серьезное ограничение, но все же жалъ( что нельзя совмещать объектные таблицы с объектными представлениями или преобразовывать объектные представления в объектные таблицы. Было бы хорошо иметь возможность создать объектную таблицу:
CREATE TABLE images2 OF Image』
NESTED TABLE keywords STORE 解 keyword_tab;
и затем заполнить ее данными из представления:
INSERT INTO Images^ /* недопустимо, поскольку 1mages_v содержит REF */
SELECT VALUEd) FROM images_v 1:~
Но, увыг Oracle при этом генерирует ошибку ORA-22979: cannot INSERT object view REF or user-defined REF.
Ссылки на неуникальные OID
В каином случае мы не считаем, что при работе с объектными таблицами возможно существование ссылки на неуникальный OID. Вряд ли кто-нибудь зиает, что произойдет в результате создания ссылки на объект объектного представления, содержащего несколько таких объектов с одинаковыми OID. Поэтому лучше не создавать представлений с дублирующимися OID.
Наши эксперименты показали, что вызов функции OEREF дли такой виртуальной ссылки возвращает объект, вероятно, первый найденный Oracle. В предыдущих версиях системы данная операция возвращала NULL Поэтому не стоит рассчитывать, что Oracle будет вести себя именно так, как вы предполагаете.
Сопровождение объектных типов и объектных представлений
Проработав некоторое время с объектными типами, вы освоите разные способы получения информации о создаваемых типах и представлениях. Когда информации, которую предоставляет команда SQL*Plus DE立RI8E, станет недостаточно, можно обратиться с запросами непосредственно к словарю данных Oracle.
В словаре определяемый пользователем тип (объект или коллекция) называется просто TYPE. Определение и тело любого объектного типа, а также спецификация и тело любого пакета находятся в представлении USER_SOURCE, DBA^SOURCE или ALL-SOURCE В табл. 21.4 приведены запросы, которые будут вам полезны.
Таблица 21.4. Запросы к словарю данных для объектных типов
Требуемая информация
Запрос, позволяющий получить ответ
Какие тупы объектов и коллекции созданы пользователем?
SELECT * FROM user_types; SELECT * FROM user objects WHERE objecLtype = TYPE';
Какие объектные иерархии созданы пользователем?
SELECT RPAD(' ; 3*(LEVEL-1)) || type_name
FROM user_types
WHERE typecode = 'OBJECT
CONNECT BY PRIOR typejame = supertype_name;
Какие атрибуты имеет тип foo?
SELECT * FROM user.typejttra WHERE type^rwme ■ rFOO';
Какие методы имеет тип too?
SELECT * FROM user_type_methods WHERE type_rarne = TOO';
Каковы параметры методов шпв ft»?
SELECT * FROM user_method_pBrams WHERE type_name = TOO';
Какой тип данных возвращает метод bar типа fbo?
SELECT* FROM user_method_results
WHERE type_name = rFOO' AND methodjwne ■ 'BARr;
Каков исходный код типа too, в том числе все инструкции ALTER?
SELECT text FROM use resource
WHERE name = TOO'
AND type = TYPE /* WM TYPE BODY* »/ ORDER BY line;
Какие объектные таблицы имеют тип foo?
SELECT t3ble_name FROM user_object_taNes WHERE table_type = 'FOO';
Какие столбцы содержит объеЕая таблица fdo_tab, в том числе скрытые?
SELECT column_name, data_type, hldden_ralumn, vlrtual_column
FROM user_tab_cols
WHERE t3bie_name - fFOO_TABf;
Какие столбцы имеют кп foo?
SELECT t3bte_namer column_name FROM user_t3b_columns
WHERE data_type = ГКЮ';
Какие объекты базы данных зависят от типа foo?
SELECT name, type
FROM user_dependendes
WHERE referenced_name = 'FOO';
Какие объектные представления созданы и с какими OID?
SELECT ylewjame, vlew_type, old_text FROM user_views
WHERE type一text IS NOT NULL;
продолжение^
Более того, даже Oracle предупреждает, что триггерами не стоит злоупотреблять, поскольку они могут создавать сложные взаимозависимости. Если триггер INSTEAD OF вьшолняет DML-инструкцию для таблиц, у которых имеются другие триггеры, выполняющие DML-инструкции для других таблиц с триггерами, то как это все отладить?
Аргументы «за»
Значительную часть логики, обычно реализуемой в пакете или теле метода, можно перенести в триггер INSTEAD OF. В сочетании с продуманным набором привилегий это позволит защитить данные лучше, чем с помощью методов и пакетов.
Если вы пользуетесь таким клиентским средством, как Oracle Foims, то при создании в форме «блока* на основе представления, а не таблицы, триггеры позволяют применять гораздо больше встроенных возможностей этого продукта.
При использовании OCI, когда объектное представление не подлежит модификации и нужно быстро отправлять кэшируемые данные объектного представления обратно на сервер, триггеры INSTEAD OF просто необходимы.
Главный вопрос
Куда следует помещать инструкции, выполняющие вставку, обновление и удаление данных, в особенности при использовании объектных представлений? Если вы хотите локализовать эти операции на сервере; то у вас имеются как минимум три варианта: пакеты PL/SQL, объектные методы и триггеры INSTEAD OF.
В табл. 21.3 приведены сравнительные характеристики этих вариантов. Заметьте, что мы рассматриваем их исключительно с точки зрения размещения DML- операций над объектными представлениями.
Таблица 21.3. Сравнитель№1е характерисшки технологий инкапсуляции DML-операциЙ над объектными представлениями
Характеристика
Пакет PL/SQL
Объектный метод
Триггер INSTEAD OF
Соответствие
□бъекгно-ориентмро- ванному подходу
Потенциально очень хорошее
Прекрасное
Потенциально очень хорошее
Возможность модификации при изменении схемы
Имеется, код легко модифицировать и независимо перекомпилировать
Имеется (в Orade9l)
Имеется
Степень риска неожиданных последствий
Низкая
Низкая
Высокая; триггеры могут иметь скрытые зависимости
Взаимодействие со стандартными функциями клиентских средств (в частности, Grade Developer)
Программист должен добавить код для всех клиентских транзакционных триггеров
Программист должен добавить код для всех клиентск/х транзакционных триггеров
Хороню реализовано для типов верхнего уровня (однако хе существует серверного триггера INSTEAD OF LOCK}
Возможность включаться и отключаться по желанию
Отсутствует
Отсутствует
Имеется (путем отключения и включения триггера)
Как видите, явного победителя здесь нет. В каждом варианте имеются пре- имущества, которые в конкретном приложении могут сыграть решающую роль.
Важной особенностью использования триггеров INSTEAD OF в иерархии представлений является необходимость создания отдельного триггера для каждого уровня иерархии. При выполнении DML-инструкции применительно к подоред- ставлению запускается триггер подпредставления, а применительно к суперпредставлению —триггер суперпредставления.
Триггеры INSTEAD OF могут использоваться в сочетании с пакетами PL/SQL и/или объектными методами для обеспечения уровней инкапсуляции. Например:
CREATE DR REPLACE TRIGGER images v_insert
INSTEAD OF INSERT DM images_v
FOR EACH ROW BEGIN
/* Вызов процедуры из пакета для выпопенния вставки */
managMmage.create_one(:NEW.1mage_1db :NEW.file_type.
:NEW.file_name, :NEW.bytes. :NEW.keywords):;
END;~
В идеале разработчики должны заранее выбирать архитектуру и конструкторские подходы, а не напичкивать приложение всевозможными функциями Oracle. Используйте любую функцию лишь при условии, что она действительно полезна в приложении и согласуется с принятыми общими концепциями разработки. Мы полностью поддерживаем мнение Oracle, что к триггерам должен применяться взвешенный подход.
Различие между объектными представлениями и объектными таблицами
Помимо очевидных различий между объектным представлением и объектной таблицей программисты PL/SQL должны понимать и более <тонкие» различия. Основными из них являются:
О уникальность OID-идентификаторов;
О возможность хранения в базе данных REF-ссылок;
О наличие REF-ссылки на неуникальные ОШ-идентификаторы.
Далее мы рассмотрим каждое из перечисленных различий.
Уникальность OID-идентификаторов
Объектная таблица всегда содержит уникальный идентификатор объекта (системно -генерируемый или производный от первичного ключа). Можно, хотя это и плохая практика, создать объектную таблицу с дублирующимися строками, но экземпляры объектов в таблице все равно будут иметь уникальные идентификаторы. Данную операцию можно выполнить двумя способами.
Q Дублирование ОШ в одном представлении. Объектное представление может содержать несколько экземпляров объектов (строк) с одним и тем же OID. Вы уже видели пример суперпредставления с дубликатами объектов.
Таблица 21.4 (продолжение)
Требуемая информация
ЗапроСг позволяющий получить ответ
Какова иерархия созданных “ пользователем представлений?
(Требует временной таблицы, поскольку в подзапросе нельзя использовать предложение CONNECT BY)
CREATH TABLE uvtemp AS
SELECT v,vlew_nanief v.vlew_type,
v.5Up^vlew」iame, vl.view_type supervlew_type
FROM user„views v, user_views vl
WHERE v.supervlew_name = vl.v1ew_name (+);
SELECT RPADC ', 3*(LEVEL-1)) || vlew_name
II 'С II view_type || ')1
FROM uvtemp
CONNECT BY PRIOR vtewjype = supervtew_type;
DROP TABLE uvtemp;
На основе какого запроса определено представление foo_v?
SET LONG 1СХЮ - или больше SELECT text FROM user^views WHERE vtewLname = 'FOO_V;
Какие столбцы содержит представление fbo_v?
SELECT column^ame, data_type_mod, data_type FROM user_tab_columns
WHERE table_name - 'FOO一M;
Учтите, что объектные таблицы невидимы для представления USER TABLES. Их список можно получить из представлений USER_OBJECT_TABLES и USER_ALL_TABLES.
Привилегии
Существует целый ряд системных привилегий, ассоциированных с объектными типами.
О CREATE [ANY] TYPE. Создание, изменение или удаление объектных типов и кода типов. ANY означает «в любой схеме».
О CREATE [ANY] VIEW. Создание и удаление представлений, в том числе и объектных. ANY означает «в любой схеме*.
О ALTER ANY TYPE. Выполнение инструкции ALTER TYPE в любой схеме.
О EXECUTE ANY TYPE. Использование объектного типа из любой схемы для создания экземпляров, выполнения методов, ссылок и гюлучения объектов по ссылкам.
О UNDER ANY TYPE. Создание подтипа в одной схеме, наследующего тип из другой схемы,
О UNDER ANY VIEW. Создание подпредставления в одной схеме, наследующего представление из другой схемы.
На уровне объекта для объектных типов используются три вида привилегии — EXECUTE, UNDER и DEBUG. Кроме того, важно понимать, как применяются к объектным таблицам и представлениям традиционные DML-привилегии,
Привилегия EXECUTE
Если вы хотите разрешить пользователю Joe использовать ваши типы в его программах и таблицах PL/SQL, предоставьте ему привилегию EXECUTE:
GRANT EXECUTE on catalog_1tem_t TO joe:
В том случае, если пользователь Joe имеет привилегию, необходимую для соз^ дания синонимов^ и работает с версией Oracle9i Release 2 или выше, он может создать синоним:
CREATE SYNONYM catalog_4em_t FOR scott.catalog_1tem_t;
и использовать его следующим образом:
CREATE TABLE catalogjtems OF catalog_item_t:
или же так:
DECLARE
anjteni catalog_item_t:
Кроме того, пользователь Joe имеет возможность применять уточненные ссылки на тип scott. с a t а 1 og_11 em_t.
Если вы ссылаетесь на объектный тип в хранимой программе и предоставили другому пользователю или роли привилегию EXECUTE на эту программу, такая же привилегия на тип не обязательна, даже когда программа определена как выполняющаяся с привилегиями вызывающего (см. главу 20). В том случае, когда у пользователя есть DML-привилегия на представление, имеющее для данной DML- операции триггер INSTEAD OF, который ссылается на объектный тип, пользователю не нужна явная привилегия EXECUTE на этот тип, поскольку триггеры всегда выполняются с привилегиями создателя. Однако привилегия ЕХЕС1ЯЕ ва объектный тип необходима пользователям, выполняющим анонимные блоки, в которых он используется.
Привилегия UNDER
Привилегия UNDER дает право иа создание подтипа. Пр ед оставляется она следующим образом:
GRA盯 UNDER ON image/ TO scott;
Для того чтобы владелец схемы мог создать подтип, супертип должен быть определен с привилегиями вызывающего (AUTHID CURRENT_USER).
Кроме того, привилегия UNDER дает право на создание подпредставления:
GRA盯 UNDER ON images_v TO scott;
Привилегия DEBUG
Если пользователь с помощью отладчика PL/SQL анализирует код, в котором применяется созданный вами тип, можно предоставить ему привилегию DEBUG:
GRANT DEBUG ON 1mage_t TO joe;
Это даст возможность пользователю исследовать имеющиеся внутри типа переменные и устанавливать точки останова внутри методов.
Привилегия DEBUG применима и к объектным представлениям — для них она позволяет отлаживать исходный код триггеров INSTEAD OF.
Привилегии DML
Для объектных таблиц имеют значение традиционные привилегии SELECT, INSERT, UDPATE и DELETE. Пользователь, который имеет на такую таблицу только привилегию SELECT, может извлекать из нее столбцы базового типа, лежащего в основе определения таблицы, но не может извлекать объекты. Это означает, что операции
VALUE, TREAT. REF и DEREF для него недоступны. Остальные DML-привилегии, такие как INSERT, UPDATE и DELETE, также применимы только к реляционной интерпретации таблицы.
Тот, кто получает тхривилегию, может не иметь права на использование конструктора или других методов объектного типа, если владелец объектного типа не предоставил ему на этот тип привилегию EXECUTE. Кроме того, ему будут не видны столбцы, определенные в подтипах.
О целесообразности применения объектно-ориентированного подхода
Автор должен признаться, что начал программировать задолго до того, как при разработке приложений стали применяться объектные подходы.
С годами стало ясно, что ни один стиль программирования не имеет монополии на такие фундаментальные и важные для нас моменты, как соответствие требованиям, эффективность и производительность, удобство разработки и надежность системы. Помнится множество восторгов, преувеличенных ожиданий, мод* ных тенденций, горячего энтузиазма (к чему автор сам был причастен) — всего этого в свое время было вдоволь и в отношении объектно-ориентированного программирования. Нельзя сказать, что данная технология не помогает решать определенные проблемы, но это не волшебная палочка, как думают многие.
Возьмем, к примеру^ принцип объектной декомпозиции, используемый при разработке объектных иерархий. При внимательном выполнении моделирования объектов реального мира получаются удобные и понятные программные компоненты, из которых легко собирать даже крупномасштабные системы. Звучит многообещающе, не так ли? Однако существует множество способов декомпозиции объектов реального мира* и эти объекты редко можно представить в виде простой иерархии. Например, декомпозицию нашего библиотечного каталога можно выполнить на основе... ну, скажем, носителей (печатные материалы, аудиозаписи, цифровой формат и т. д.). Хотя Oracle предоставляет прекрасные возможности для эволюции типов, серьезные изменения в иерархии типов ведут к таким тяжелым последствиям, что никто и никогда не станет их вносить. И не программные средства тому виной.
Нельзя сказать, что объединение программкой логики (методов) и данных (атрибутов) в абстрактных типах дает какие-либо явные преимущества. Пока никто еще убедительно не доказал, что это лучше, чем хранить структуры данных (логическую и физическую структуру таблиц) отдельно от процессов (процедур! функций и пакетов). И многие признают, что структуры данных реальных компаний меняются гораздо реже, чем алгоритмы работы с этими данными. Даже в отношении ООП является несомненным тот факт, что изменчивые элементы системы должны быть отделены от более стабильных.
Рекомендуем, в частности, обратиться к материалам Object Oriented Proffvmming Oversold!, представленным по адресу ht^://www.geodtles.cOfri/tablizer/Dopbad.htni.
Последнее обстоятельство особенно интересно. Ярые приверженцы объектного подхода, которые настаивают на объединении данных и действий, в то же время подчеркивают важность подхода <модель-предсгавление-управление», <отделяющего бизнес-логику от данных». Вам не кажется, что это совершенно противоположные утверждения?
Многие сторонники ООП главным преимуществом данного подхода считают возможность многократного использования программного обеспечения. Это го・ верилось столько раз, что должно было бы уже стать правдой! К сожалению, мало кто потрудился привести убедительные доказательства, так как не существует четко определенного понятия «многократного использования». Даже приверженцы объектного подхода признают многократное использование возможным в первую очередь для компонентов выстих уровней, поскольку объекты обычно предназначаются для узкого применения. Исходя из личного опыта можем сказать: возможность повторного использования объектного кода ничуть не выше, чем у обычных хорошо спроектированных подпрограмм.
Безусловно, можно применить объектный подход в PL/SQL и разработать ком- • поненты для многократного использования. Например, Дон Бейлз, имеющий огромный опыт объектно-ориентированного программирования, в течение десяти лет использовал пакеты PL/SQL как «типы». Он утверждает, что может взять целый пакет (и все сопутствующие таблицы) и перенести его в новый проект без единого изменения. Недостающим элементом большинства объектных подходов он считал точную модель человека, выполняющего приложения (пользователя), моделируемого Доном в виде объекта, поведение которого реализовано в реальной протрамме.
Независимо от метода разработки важнейшими со ставляющими ее успеха остаются опыт в решении подобных задач, возможность нанять высококвалифицированных специалистов и наличие достаточного времени для проектирования. А уж выбор подхода, объектного или нет важен только в том отношении, что он должен быть сделан заранее, с тем чтобы предотвратить стихийное и неупорядоченное развитие системы.
В завершение будет приведено несколько рекомендаций, касающихся применения объектных функций Oracle.
О Если вы используете Oracle Call Interface (ОС【),то возможность выполнить клиентское кэширование и комплексную выборку объектов могут склонить вас к интенсивному использованию объектных средств Oracle. Автор не работает с ОС1и поэтому не может поделиться своим опытом в этом отношении.
О В случае, когда на вашем предприятии уже используется объектно-ориентированное программирование, объектные средства Oracle могут облегчить интеграцию технологий баз данных в имеющиеся системы.
О Если вы не намерены пользоваться объектным подходом, не отвергайте вместе с ним и коллекции. Помните, что они обладают гпирочайгаими возможностями и что для их применения не требуются ни объектные типы, ни объект- ныв представления.
Q Если вы никогда раньше не практиковали объектно-ориентированного программирования, новые объектные возможности Oracle могут показаться вам
Использование Java в базах данных Oracle и программирование на этом языке -обпшрнейшие темы, подробное освещение которых выходит за рамки нашей книги. Приступая же к работе над настоящей главой, авторы поставили перед собой две основные задачи:
О предоставить читателю информацию, необходимую для загрузки классов Java в базу даниых Oracle и управления новыми объектами базы данных, а также для их объявления с целью дальнейшего использования в PL/SQL;
О изложить основные требования и рекомендации по разработке классов Java, достаточные для создания собственных классов, без которых невозможен доступ к функциональным возможностям, предоставляемым Java.
Для того чтобы вызвать методы классов Java из Oracle, необходимо выполнить действия, перечисленные ниже.
Создать элементы кода Java в Oracle JDeveloper или любой другой интегрированной среде разработки приложений Java. (Более того, это можно сделать в любом текстовом редакторе!)
Загрузить классы Java в Oracle с помощью утилиты командной строки loadjava или инструкции CREATE JAVA.
Предоставить доступ к методам классов Java в PL/SQL путем написания на PL/SQL программ-оболочек для Java-кода.
Назначить необходимые атрибуты доступа программам-оболочкам PL/SQL и использующимся в них Java-классам.
В результате полученные прохраммы PL/SQL можно будет вызывать из самых разных сред (рис. 22.1).
Для работы с Java программистам предоставляется ряд средств, перечисленных в табл. 22.1.
Таблица 22.1. Используемые в Oracle компоненты и команды для работы с Java
Название
Описание
Aurora JVM
Виртуальная машина Java, реализованная в составе сервера базы данных Grade .
toadjava
Утилита операционной системы с интерфейсом командной строки, загружающая элемента кеда Java (классы, файлы Jar и т. п.) в базу данных Oracle
drupjava
■ Утилита операционной системы с интерфейсом командной строки, удаляющая элементы кода Java (классы, файлы .jar и т. п.) из базы данных Oracle
CREATE JAVA DROP JAVA ALTER JAVA
OBMS-JAVA .
Новые DDL-инструкции, выполняющие те же функции, что и утилиты loadjava, dropjava
Встроенный пакет, содержащий набор утилит для установки параметров виртуальной машины Java
^publisher
Утилита, используемая для построения Java-классов на основе объектных типов и ЛЕР, определенных в базе данных Oracle
Подготовка к использованию Java в Oracle
Прежде чем вызывать методы Java из программ PL/SQL, нужно выполнить действия, перечисленные ниже:
О установить пакет Java Development Kit (JDK™); предварительно ознакомившись с последней информацией о поддержке версий, которая содержится в документации по Oracle;
О определить классы и элементы кода Java, а затем откомпилировать их, создав файлы .class и .jar;
О задать привилегии для схемы Oracle.
Установка Java
Вы можете установить пакет Java Development Kit (JDK) версии 1.1,5 или выше, загрузив его с web-узла JavaSoft:
http://www. JavaSoft .com/products/1ndex. №ml
Начиная c Oracle8i этот пакет входит и в состав Oracle. Обязательно задайте переменную окружения CLASSPATH, и компилятор Java (javac) сможет найти ссылки
сложными. Однако советуем потратить время на их освоение. В частности, попробуйте создать объектные представления для существующих систем.
О Не стоит отвергать объектные типы и представления из-за их низкой производительности. Oracle постоянно работает над ее повышением. Кроме того, если вы проведете тестирование, может оказаться, что ООП вполне удовлетворяет требованиям приложения.
О Оказывается, что объектные типы можно используются и в некоторых встроенных функциях Oracle, напрямую не связанных с объектно-ориентированным программированием, в частности в Oracle9i Advanced Queuing и Oracle Spatial. Для использования этих функций нужно иметь хотя бы общее представление об объектных типах, даже если вы сами создавать их не собираетесь.
О О Взаимодействие Java // и PL/SQL
A Oracle и Java
А Использование Java в Oracle
А Утилиты loadjava и dropjava
А Управление объектами Java в базе данных
► Пакет DBMS JAVA
А Как еделать методы Java доступными в PL/SQL
В этой главе рассматривается новая замечательная возможность Oracle, которая была введена в Огас1е8г, а именно вызов из PL/SQL хранимых Java-процедур (Java Stored Prose dure, JSP). Как вы наверняка знаете, Java - исключительно мощный и во многих отношениях более надежный язык, чем PL/SQL. Он включает сотни классов с четкими и простыми API, используемыми для самых разнообразных целей.-
Oracle и Java
Начиная с Огас1е8г в состав РСУБД включается продукт под названием J Server, состоящий из следующих компонентов:,
Q виртуальная машина Java для Oracle (Java Virtual Machine, JVM), называемая Aurora, а также соответствующие ей среда выполнения и библиотеки классов Java;
О средства интеграции Java с PL/SQL и РСУБД Oracle;
О брокер объектных запросов (Object Request Broker, или Aurora/ORB) и технология Enterprise JavaBeans (EJB);
О встроенный компилятор JServer Accelerator.
Виртуальная машина Java Aurora вызывает методы Java, которые также называются хранимыми Java-процедурами (Java Stored Procedure, JSP)» и классы так, как если бы они хранились в базе данных.
на ваши классы и на классы Oracle. Дополнительная информация о переменной CLASSPATH содержится в документации по языку Java, которую можно получить, обратившись по адресу http://java.sun.coni.
Проектирование и компиляция кода Java
Для многих программистов, не обладающих навыками работы с объектно-ориентированными языками, переход к Java может оказаться далеко не простым делом. Поэтому очень уместными здесь будут следующие выводы, основанные на личном опыте автора настоящей главы:
О освоение базового синтаксиса языка, достаточного для создания простых классов Java, требует сравнительно немного времени;
О использование Java в PL/SQL не сопряжено с преодолением каких-то особых трудностей;
О создание объектно-ориентированных приложений на базе Java требует от разработчика PL/SQL полного переосмысления привычных подходов.
Ознакомившись с этой главой, вы поймете, как вызываются методы Java из PL/SQL, и научитесь создавать простейшие классы Java.
Определение привилегий, необходимых для разработки и выполнения Java-кода
Поскольку функции системы безопасности, связанные с разработкой и выполнением Java-кода, в Огас1е8: и Oracle9t реализованы по-разному, мы рассмотрим каждую из версий.
Защита данных в OracleSi
В РСУБД Oracle8i определены две роли, предоставляющие привилегии на выполнение разных видов операций:
О JAVAUSERPRIV — привилегии на выполнение относительно небольшого количества операций, в том числе иа просмотр свойств;
О JAVASYSPRIV — привилегии на выполнение большей части операций, включая обновление пакетов, защищенных JVM.
Эти роли назначаются таким же образом, как любые другие роли базы данных. Для того чтобы пользователю Scott получить, скажем, разрешение на любые действий относительно Java, ему нужно подключиться к Oracle с учетной записью SYSDBA и выполнить такую инструкцию:
GRANT JAVASYSPRIV ТО 5С0П:
При необходимости предоставить этому же пользователю ограниченные права на работу в Java, следует ввести инструкцию
GRANT JAVAUSERPRIV ТО SCOTT:
Например, чтобы получить возможность создать файл с помощью Java, нужно быть членом роли JAVASYSPRIV, а для чтения или записи содержимого этого файла достаточно быть членом роли JAVAUSERPRIV. За дополнительными сведениями по
вопросу управления разрешениями обращайтесь к документации Oracle, где, в частности, приведена таблица методов Java и указано, какие привилегии для вызова каждого из них необходимо иметь.
В процессе инициализации JVM Aurora устанавливает экземпляр менеджера защиты Java (Java Security Manager) - java .lang. SecurityManager. Каждый пользователь Oracle имеет динамический идентификатор, определяющий права, или привилегии, владельца сеанса при доступе к методам Java из PL/SQL.
Если пользователь, не имеющий достаточных полномочий, пытается выполнить запрещенную операцию, JVM инициирует исключение java.lang.Securi- tyExceptlon. Вот что увидит пользователь в таком случае в SQL*Plus;
ORA-29532: Java call terminated by uncaught Java exception: Java.lang.SecurityException
При вызове методов Java в базе данных возможно возникновение различных проблем, связанных с системой безопасности, особенно при взаимодействии с серверной файловой системой или другими ресурсами операционной системы. Контролируя операции ввода-выводат Oracle следует определенным правилам.
О Если пользователю, имеющему персональный идентификатор прав доступа, были предоставлены привилегии JAVASYSPRIV, Security Manager разрешает выполнить операцию.
О Если же такому пользователю были предоставлены привилегии JAVAUSERPRIV, Security Manager проверяет допустимость операции в соответствии с теми правилами, которые действуют в отношении пакета PL/SQL inL_FILE. Иными словами, файл должен находиться в каталоге, определяемом параметром UTL_FI- LE_DIR, который задается в инициализационном файле базы данных.
Защита данных в Oracle9i
Система защиты, реализованная в Oracle9z для JVM, основана на модели защиты Java 2, согласно которой привилегии предоставляются каждому из существующих классов. Подробный анализ этой сложной системы выходит за рамки данной книги. Однако мы приведем несколько примеров, чтобы вы могли составить представление о программном коде, который можно писать в Oracle9i для предоставления и отмены привилегий на выполнение определенных действий над объектами базы данных.
В общем случае для предоставления привилегий используется процедура DBMS_ JAVA.GRANT-PERMISSION. Пример ее вызова для предоставления схеме 8АТСН разрешения на чтение и запись файла, lastorder, log выглядит следующим образом:
CALL DBMS JAVA.GRAMT_PERHISSION(
'BATCH7,
'Java.1o.F1lePerniission,, '/apps/OE/lastorder.log',
'read.write'):
Приведем последовательность операторов, которая сначала предоставляет разрешения на доступ к файлам в каталоге, а затем ограничивает их возможностью выполнения операций над заданным файлам:
CONNECT 0E_adm1n/0E_admin
REM Grant permission to all users (PUBLIC) to be
REM able to read and write all files in /tmp.、
CALL DBMS JAVA.GRANT_PERMISSION( 'PUBLIC , 'JavaJoJllePemlsslon'.
'/tmp/*1, 'read-wrlte' >:
REM Limit permission to all users (PUBLIC) from reading REM or writing the password file in /tmp.
CALL DBMS JAVA.GRANT_PERMISSION('PU9LIC, 'Java.Io.FIlePermlsslon'.
7tmp/password'.
'read.write'):
REM By providing a more specific rule that overrides the REM limitation OE admin can read and write Лпф/password, CALL DfiMSJAVA.GfWT.PERMISSIONC'OE^admln',
'JflveJa.FIlePermlsslon1,
7tmp/password'.
'read.wrlte'):
COMMIT;
Примеры использования Java
Прежде чем мы приступим к изучению особенностей системы защиты, рассмотрим последовательность действий, которые следует выполнить для получения доступа к Java из PL/SQL Низке будет рассказано о нескольких интересных приемах, применяемых для реализации этой задачи.
Предположим, что при выполнении кода PL/SQL требуется удалить файл операционной системы. Раньше это можно было сделать следующим образом: О в Oracle 7.3 и более поздних версиях в канал базы данных направлялось специальное сообщение, прочитав которое программа-слушатель, написанная на языке С, выполняла указанную задачу;
О в Oracle 8.0 появилась возможность объявлять библиотеку, содержащую ссылку на DLL или общедоступную библиотеку языка С( а затем из кода PL/SQL вызывать хранящуюся в этой библиотеке программу, которая и производила удаление файла.
Способ с использованием канала довольно удобен, но изяществом он, как вы понимаете, не отличается. Более удачным является решение, основанное на вызове внешней процедуры, но если вы не знаете языка С, реализовать его не так просто. Наилучшим оказался способ, в основу которого положен принцип использования классов Java. Для его выполнения необходимо знать язык Java, но не в таком объеме, какой требуется для написания эквивалентного кода на С. Java реализуется с набором готовых (базовых) классов, предоставляющих широкий набор функций, включая и файловый ввод-вывод.
Вот какие действия необходимо выполнить для удаления файла операционной системы в данном случае:
Идентифицировать используемые функциональные элементы Java.
Самостоятельно создать класс и таким образом обеспечить доступ из PL/SQL к функциональным элементам Java.
Откомпилировать класс и загрузить его в базу данных.
Написать программу PL/SQL для вызова методов созданного класса.
Удалить требуемые файлы, вызвав соответствующие методы класса из PL/S QL.
ПРИМЕЧАНИЕ
Oracle9l Release 2 содержит ховырвхную версию встроенного пакета UTL^FILE, позволяющую удалять файлы с помощью программы UTL_HL£. FREMOVE.
Выбор функциональных элементов
Пакет java.lo содержит класс File, включающий набор методов для получения информации о файлах и каталогах. Причем он не только позволяет получать указанную информацию, но и удалять, а также переименовывать файлы, создавать каталоги и т. д.
Приведем часть интерфейса, предоставляемого классом File:
public class java.1о.File (
public boolean deleteC); public boolean nkdlr ():
}
Иными словами, для удаления файла нужно вызвать функцию типа Boolean. Если файл будет удален, эта функция возвратит значение TRUE, а в противном случае — значение FALSE.
Создание пользовательского класса Java
Вы спрашиваете, для чего создается собственный Java-класс поверх класса Fl 1 е и почему нельзя вызвать нужную функцию прямо из оболочки PL/SQL? На то есть две причины.
О Во-первых, методы класса Java всегда (за исключением статических методов) выполняются для конкретного объекта, являющегося экземпляром этого класса. В PL/SQL создавать экземпляры классов Java нельзя, поэтому невозможно и вызывать такие методы.
О Во-вторых, хотя и Java и PL/SQL включают типы данных Boolean (a Java даже предлагает примитив Boolean и класс Boolean), эти типы не соответствуют друг другу. Поэтому значение типа Bool еап Java нельзя непосредственно присвоить переменной Boolean PL/SQL, Следовательно, нам придется разработать собственный класс, который:
О создаст экземпляр класса File;
О выполнит для этого класса метод delete;
О возвратит значение, которое PL/SQL сможет корректно интерпретировать.
Ниже приведен пример очень простого класса, позволяющего использовать метод File.delete:
/* Файл в web; JDelete.java */
Import java.1о.File;
public class J Delete {
public static 1nt delete (String flleName) {
File tnyFile - new File (fileName);
boolean retval - tnyFile.deleteO;
If (retval) return 1: else return 0:
Все этапы выполнения кода отражены на рис. 22.2. Принципы, положенные в основу его реализации, очень просты. Метод JDelete.delete создает объект File для указанного имени файла. Объявление этого метода статическим избавит нас от необходимости создавать экземпляр класса J Del ete, что принципиально важно для вызова метода JDelete.delete из PL/SQL. Статический метод ассоциирован с классом^ а не с конкретным объектом данного класса.
Рис. 22«2< Класс Java, используемый для удаления файла
Все очень просто! Правда, на рисунке не отражены усилия, затраченные автором на отладку кода. Вы сумеете их оценить, когда попытаетесь сделать что-нибудь подобное.
Этот класс демонстрирует множество различий между Java и PL/SQL, а именно: О блоки, циклы и условные операторы в Java ограничиваются не ключевыми словами BEGIN и END, а фигурными скобками (начало блока отмечает открывающая фигурная скобка, а конец — закрывающая);
О код Java чувствителен к регистру клавиатуры, поэтому ключевое слово if в нем — это вовсе не то же самое, что IF;
О оператор присваивания обозначается обычным знаком равенства (=), а не составным символом (:==), как в PL/SQL;
О в вызове метода без аргументов (например, метода delete класса File) после его имени должны стоять открывающая и закрывающая скобки. Иначе компилятор Java попытается интерпретировать метод как член класса или как структуру данных.
Компиляция и загрузка класса в Oracle
Теперь, когда код класса готов, наступило время компиляции. Открываем в Windows сеанс MS-DOS, переходим в каталог d:\Java (или в любой другой, где установлен пакет Sun JDK) и компилируем класс;
D:\Java* Javac delete.Java
Вообще-то, не лишним было бы протестировать его до того, как помещать в Oracle и вызывать из PL/SQL. Разработку и тестирование удобнее выполнять пошагово, тем более что Java предоставляет для этого эффективное средство — метод main. Если класс содержит метод (процедуру) типа void с именем main и заданным набором параметров, можно вызвать указанный класс — и код будет вы- полнен,
ПРИМЕЧАНИЕ Код метода main может служить одним из примеров того, как Java интерпретирует определенные элементы со стандартной сигнатурой. Еще одним таким примером является метод toString. Если класс содержит данный метод, он автоматически вызывается для вывода пользовательского описания объекта. Эго особенно полезно в тех случаях, когда объект состоит из множества элементов, имеющих смысл лишь при определенном представлении или требующих форматирования, без которого они не читабельны.
Добавим в класс JDel ete простой метод mai п (его код выделен жирным шрифтом):
public class JDelete {
public static int delete
public static void nain (String args[]) {
System.out.printin ( delete (args[o]> )
}
Метод main вызывает метод delete, передавая ему в качестве аргумента первое из переданных классу значений, и выводит на экран результат. Перекомпилируем класс и запустим его (все операции выполняются б окне MS-DOS):
D:\JavaA javac JDelete.Java
D:\Java> java JDelete c:\tenp\te employee.pks
D:\Java> java JOelete c:\temp\te enployee.pks
0~
Обратите внимание: при первом запуске метод возвращает единицу (TRUE), свидетельствующую о том, что файл удален. А когда мы запускаем его повторно,
он возвращает в качестве значения нуль, потому как удалять больше нечего. Все очень просто.
ПРИМЕЧАНИЕ -
Еще одним примером превсхэсодства Java над PL/SQL является тот факт, ига для вывода строки в PU5QL нужно ввести с клавиатуры команду длиной в 20 символов (DBMS_OUTPUT.PUT_LINE), тогда как в Java для этой цели достаточно 18 символов (System.Qirt.prfntin). А кроме того, если при объявлении класса ввести строку private static final Print Stream о = System.out;, то для вывода данных из него достаточно будет команды o.prlntin — всего 9 символов!
Теперь, когда класс откомпилирован и проверен и мы точно знаем, что метод delete работает, с помощью команды loadjava класс можно загрузить в схему SCOTT базы данных Oracle:
D:\Java> loadjava -user scott/tiger -ocifl -resolve JDelete.class
Можно даже проверить, успешно ли загружен класс. Для этого нужно обратиться к представлению USER_0BJECT5 из словаря данных, воспользовавшись специальной утилитой (мы познакомим вас с ней чуть позже):
SQL> exec nyjava.showobjects
Object Nd昵
Object Type
Status
Timestanp
Hello
JAVA CLAS'S
VALID
1999-05-19:16:42 ~
JOelete
JAVA CLASS
VALID
1999-06-07:13:20
JFile2
JAVA CLASS
VALID
1999-05-26:17:07
JFi1e3
JAVA CLASS
VALID
1999-05-27:12:53
На этом работа с Java завершается, и мы возвращаемся в уютный мир PL/SQL.
Создание оболочки PL/SQL
Чтобы облегчить пользователям процесс удаления файлов из PL/SQL, попытаемся создать PL/SQL-оболочку для метода Java, которая внешне очень похожа на обычную функцию PL/SQL (ко на самом деле она является переходником между PL/SQL и Java):
/* Файл a web: fDelete.sf */
CREATE OR REPLACE FUNCTION Welete (
file IN VARCHAR2) RETURN NUMBER.
AS LANGUAGE JAVA
NAME 'JDelete.delete (
java.lang.String) return int';
Реализация функции fDelete представляет собой единственную строку, описывающую вызов метода Java. Список ее параметров должен соответствовать списку параметров метода, но вместо параметров должны указываться полные имена типов данных. В нашем случае это означает, что нельзя просто написать String, а. нужно указать полное имя пакета, содержащего класс String. Обратите внимание на предложение RETURN. Заданное в нем имя int идентифицирует не класс, а примитивный тип данных, так что это полная спецификация.
Удаление файлов из PL/SQL
Итак, мы должны скомпилировать и вызвать функцию:
SQL> Pfdelete.sf '
Function created.
Input truncated to 12 characters
SQL> exec DBMS_OVTPUT,Pirr_LINE (
fdelete('c:\temp\te_employee.pkb'})
~
SQL* exec DBHS_OUTPirr.PUT_LINE (
fdelete('c:\temp\te_erap1oyee.pkb'))
0"'
Теперь на основе этой функции можно создавать всевозможные утилиты, например процедуру) удаляющую все файлы, которые указаны во вложенной таблице, или процедуру, удаляющую из заданного каталога все файлы, которые удовлетворяют заданному условию фильтрации (например, *.tmp).
На практике вы, конечно же, создали бы пакет и поместили бы в него все эти функции и процедуры. Мы это тоже сделаем, но немного позже. А сейчас даэайте рассмотрим каждое из только что выполненных действий подробнее.
Утилита loadjava
Утилита loadjava 亠 это программа операционной системы с интерфейсом командной строки, загружающая в базу дакныхЗауа-файлы^ При первом запуске в схеме она создает несколько дополнительных элементов, необходимых для нормальной работы Java.
О CREATE$JAVA$LOB$TABLE — таблица, которая создается в каждой схеме и содержит элементы кода Java. При загрузке с помощью утилиты loadjava каждого нового класса в эту таблицу добавляется новая строка.
О JAVA$CLASS$MD5$TABLE— хэш-таблица, которая также называется дайджест-таб- лицей и используется для отслеживания процесса загрузки элементов Java в указанную схему базы данных.
О LQADLOBS — пакет, устанавливаемый в каждой схеме и используемый для загрузки элементов кода Java в базу данных в виде больших объектов (LOB).
Утилита loadjava с помощью пакета LOADLO6S помещает Java-файлы в BLOB-сгол- бец таблицы базы данных CREATE$JAVA$L08$TABLE. Кроме того, она проверяет хэШ- значение таблицы JAVA$CLASS$M05$TABLE. MD5, с тем чтобы определить, загружен ли указанный класс в базу данных и был ли он изменен. Данный прием позволяет
избежать повторной загрузки и перекомпиляции зависимых классов. Затем вызывается новая DDL-HHcrpyraiHH, CREATE JAVA, которая загружает класс Java из 0LO0-столбца таблицы CREATE$JAVA$LO0$TABLE в РСУБД. Загрузка производится только в том случае, если выполняется одно из следующих условий:
О класс загружается впервые;
О класс изменился;
Q задана опция -force.
Процесс загрузки элементов Java в Oracle продемонстрирован на рис. 22.3.
Рис. 2Z3. Загрузка элементов Java в Grade
Утилита loadjava имеет следующий синтаксис:
loadjava {-user | -и} апя_попьзова теля/пароль [&бззд_Дднных']
[-Ш^драметра [-имл_ параметр» ...] имя^Илд [и«я_фзйла]...
Здесь иня_лараметра — это одно из множества значений, часть которых перечислена ниже:■
I debug j {deflner | d)
I (encoding | e) имяухемы_1<0дировкя
I {force I f)
I {grant I g} [иня^опьзователя | имя_роли}[.{ иия^льзоватепя | и«яj»zw }]/..
I (℃1S I 0}■■
|. {resolve | r)..
I {resolver S} имя_схены
I {synonym I s}
I {tbn [ t}
I {verbose | v}
I (nousage}
I {noverify))
В командной строке в любом порядке можно ввести имена файлов исходного кода Java, класса и ресурса, имена входных файлов SQU (файлов .sqlj)f несжатых файлов .jar и архивов .zip.
Например, следующая команда загружает в схему SCOTT класс JFi 1е:
loadjavd -user scott/tiger -ocIB -resolve JFile.class
Эту команду можно вызвать из окна MS-DOS в системах, функционирующих на платформе Windows, или из командной строки в сеансе Unix. Ее можно выполнить и из SQL*Plus:
host loadjava -user scott/tiger -oc18 -resolve JF1le.class
Для того чтобы упростить загрузку ресурсов Java в Oracle, мы создали для Windows файл с именем lj.bat, содержащий несколько команд:
javac XI.java
loadjava -user X2 -oc18 -resolve XI.java
С его помощью компиляция и загрузка Java-класса выполняется за один шаг:
D:\Java> 1 j scatt/tiger JFi'le
Приведем несколько особенностей применения утилиты loadjava, о которых полезно знать.
О Для вывода справки используется следующая команда:
load java {-help | -h)
О Элементы в списке параметров или файлов должны быть разделены только пробелами:
-force, -resolve, -thin// Нет
-force -resolve -thin// Да
О Элементы в списке пользователей или ролей должны быть разделены только запятыми:
SCOTT. PAYROLL, 8LAKE// Нет
SCOTT,PAYROLL,SLAKE// Да
Параметры командной строки данной утилиты перечислены в табл. 22.2 с указанием выполняемых ими функций.
Таблица 22-2, Параметры командная строки утилиты loadjava
Параметры
Описание
-debug
Генерирует отладочную информацию. Этот параметр эквивалентен параметру Javac-д
-definer
Указывает, что методы загружаемых классов будут вызываться с привилешями создателя, а н€ вызывающего. (По умолчанию методы выполняются с привилегиями вызывающего,) Создающие классы пользователи могут иметь разные привилегии, а приложение может использовать очень много класс。% поэтому следует убедиться, что методы класса выполняются только с необходимыми привилегиями
-encoding
Устанавливает (или переустанавливает) параметр -encoding в таблице базы данных JAVA$OPTIONS, присваивая ему заданное значение, которым должно быть имя стандартной схемы кодировки JDK (по умолчанию — latinl). Это значение используется компилятором, так что кодировка загружаемого исходного файла должна ему соответствовать. Дополнительная информация по данному вопросу приведена далее, в разделе «Функция GET_8MpiLER_0PTION и процедуры SET. и RESET一8MpiLEIVOPTON: чтение и установка параметров компилятора»
продолжение^
Таблица 22 (прадолженив)
ПараметрыОписание
-forceВьзываег принудительную загрузку файлов класса Java независимо от
тога, были ли они загружены до этого. По умолчанию уже загруженные файлы класса повторно не загружаются. Учтите, что нельзя вызвать принудительную загрузку файла класса, если уже загружен файл исходного кода (сначала нужно удалить из схемы объект, представляющий иосодный код)
-grantПредоставляет перечисленным пользователям или ролям привилегии
EXECJTt на загруженные классы. (Эта привилегия необходима для непосредсгвенного вызова методов класса.) Данный параметр куммулятивен. Указанные пользователи и роли добаЕшякпся а список имеющих привилегию EXECUTE.
Для отмены прав нужна либо удалить и повторно загрузить объект схемы без параметра -grant, либо воспользоваться SQL-инструкцией REVOKE. Для предоставления прав на объект из схемы другого пользователя нужно иметь привилешю CREATE PROCEDURE УИТН GRANT
-od8Заставляет утилиту loadjava взаимодействовать с базом данных через
драйвер ОС! JDBC. Этот параметр, используемый по умолчанию, и парамеггр -thin являются взаимоисключающими. Для вызова loadjava . на клиентском компьютере, где Oracle не устанснлена, используйте параметр -thin
•resolveПосле загрузки и компиляции всех заданных в командной строке файлов
выполняет разрешение всех имеющихся в классах внешних ссылок. Если этот параметр не задан, файлы загружаются, но не компилируются, а разрешение ссылок не производится до вьоова файла во время выполнения.
Этот параметр позволяет откомпилировать ранее загруженные классы и ссылаться на них. Параметр -force при этом задавать не следует, поскольку указанные операции выполняются независимо, после загрузки
■resolverСвязывает новые объекты схемы со спецификацией пользовательской
системы разрешения имен. Значения этого параметра и используемого по умолчанию параметра -oraderesolver являются взаимоисключающими
■schemaСвязывает схему с новыми объектами Java. Если этот параметр не задан,
используется схема подключения. Для загрузки объектов в схему другого пользователя нужно иметь привилегии CREATE ANY PROCEDURE
-synonymСоздает общедоступный синоним загружаемого класса, который делает
его доступным для всех пользователей, находящихся за пределами схемы, в которую он загружен. Для того чтобы задать этот параметр, нужно иметь привилегию CREATE PUBLIC SYNONYM.
Если вы задали этот параметр для исходных файлов, он действует и для компилируемых ка их основе классов
"tWnЗаставляет утилиту loadjava взаимодействовать с базой данных через
тонкий драйвер JDBC. Этот параметр и используемый по умолчанию параметр -oci8 являются взаимоисключающими. При вызове утилиты на клиентском компьютерег на котором Oracle не установлена, используйте параметр -thin*
werboseВключает режим verbose, в котором выводятся сообщения о выполнении
叩。иесса ■
Как вы уже поняли, сфера использования утилиты loadjava довольно обширна. В частности, утилита может загружать отдельные файлы или сжатые группы элементов, хранящиеся в виде .jar- и ,zip-файлов. Более полное ее описание приведено в документации по Oracle.
Утилита dropjava
Утилита dropjava выполняет действия, противоположные производимым утилитой loadjava. Она преобразует заданные имена файлов в имена объектов схемы, удаляет эти объекты схемы, а в завершение удаляет соответствующие им строки дайджест-таблицы. При удалении класса другие классы, непосредственно или косвенно от него зависящие, становятся недействительными. Удаление исходного кода приводит к удалению созданного на его основе класса.
Приведем синтаксис утилиты dropjava:
drop java {-user | -u} имя_роль зов а теля / пароль [^бгззданных] [~иня_параметрд [-川内力 периметра] .,.] имя_(райла [яяя_0айлэ]...
Здесь имя_ параметра — это одно из множества значений^ часть которых перечислена ниже:
{ {ос18 I 0}
I {schema I s) ш схеяы
I {thin I t} ~
I {verbose | v)
-I {help}
I {encoding}
I {synonyn})
В командной строке можно ввести, причем в любом порядке, имена файлов исходного кода Java, классов и ресурсов, имена входных файлов SQU, несжатых .]аг-фаилов и .zip-архивов.
Краткое описание параметров командной строки утилиты dropjava приведено в табл. 22.3.
Таблица 22.3. Параметры командной строки утилиты dropjava
Параметр
Описание
ес!8
Заставляет утилиту взаимодействовать с базой данных через драйвер ОО JDBC. Значения этого параметра, используемого по умолчанию, и параметра -thin являются взаимоисключающими
•schema
Удаляет объекты из указанной схемы. Если этот параметр не задан, используется схема подключения.
Для удаления объектов из схемы другого пользователя нужно иметь привилегию CREATE ANY PROCEDURE
-thin
Заставляет утилиту dropjava взаимодействовать с базой данных посредством драйвера JDBC. Этот параметр и используемый по умолчанию параметр 代8 являются взаимоисключающими
■verbose
Включает режим verbose, в котором выводятся сообщения о ходе процесса выполнения утилиты
Управление объектами Java в базе данных
В этом разделе подробно рассматриваются вопросы, связанные с использованием Java-объектов в базе данных, а точнее, с их хранением и управлением ими.
Пространство имен Java в Oracle
Oracle хранит каждый класс Java как объект схемы. Имя этого объекта является производным от полного имени класса (но не совпадает с ним). Например, полное имя класса OracleSimpl eChecker должно быть таким:
oracle.sqlj. checker. Adel eSi mpl eChecker
В базе данных же имя соответствующего объекта схемы будет таким:
oracle/sqlj/checker/OracleSimpleChecker
Как видите, при сохранении объекта в СУБД Oracle точки в его имени заменяются символами косой черты.
Длина имени любого объекта в Oracle, будь то имя таблицы базы данных или имя класса Java, не может превышать 30 символов. В Java такого ограничения не существует, а значит, вы можете использовать гораздо более длинные имена. Oracle позволяет загрузить в базу данных класс Java с именем длиной до 4000 символов. Но в том случае, если имя элемента Java содержит более 30 символов, Oracle автоматически генерирует для него допустимый псевдоним.
Однако не волнуйтесь — использовать псевдоним вам никогда не придется. Работая с программами, вы будете иметь дело с реальным именем элемента Java, a Oracle, когда потребуется, автоматически выполнит нужную подстановку.
Просмотр загруженных элементов Java
После загрузки в базу данных исходного кода Java, класса и ресурсов информация о них становится доступной через несколько представлении словаря данных, перечисленных в табл, 22.4.
Таблица 22.4. Информация о классах е словаре данных
Представление
Олисаниа
IfSEfCOBJECTS ALMJBJECTS DB/LOBJECTS
Содержит информацию, извлекаемую из определений объектов тапа JAVA SOURCE, JAVA CLASS И JAVA RESOURCE
USEI<_ERRORS all_errors OBA_ERRORS
Содержит информацию об ошибках, происшедших при компиляции объектов
U5E«_SOURCE
Содержит исходный код Java, но только в том случае, если для создания объекта схемы вы использовали команду CREATE JAVA SOURCE
Информацию из этих представлений можно извлекать с помощью SQL-запросов, а для отображения ее в более удобном виде можно написать специальные
программы. Рассмотрим в качестве примера запрос, возвращающий список всех объектов схемы, относящихся к Java:
/* Файл в web; showjava.sql и myjava.pkg */
COLUMN object_name FORMAT A3G
SELECT object二 name. object_type. status. timestamp
FROM user_objects
WHERE (object_nanie NOT LIKE 'SYSJ'
AND object_nane NOT LIKE TREATEJX'
AW object_name NOT LIKE 'JAVA$2'
AND objecCname NOT LIKE 'LOADLO0X') -
Al€ object_type LIKE 'JAVA 21
ORDER BY object_type, object_name;
Условия отбора объектов базы данных, которые Oracle использует для управления объектами Java, задаются в предложении WHERE. Вот пример данных, возвращаемых приведенным сценарием:
SQL> exec inyDava. showobjects
OBJECT_№ME
口BJECT一TYPE
STATUS
TIMESTAMP
Hello
JAVA CLASS
VALID
1999-05-19:16:42:27
JFile2
JAVA CLASS
VALID
1999-05-26:17:07:11
JFile3
JAVA CLASS
VALID
1999-05-27:12;53:46
plsolutions/java/putLn
JAVA SOURCE
VALID
1999-05-19:16:30:29
В файле myJava.pkg на web-узле издательства O'Reilly содержится версия этого же запроса, реализованная в виде пакетной процедуры и позволяющая просматривать информацию об объектах Java с помощью такого вызова:
SQL> exec myJava.showobjects
Следующий вызов позволяет получить список всех элементов Java, имена которых начинаются с ОЕ:
SQL> exec rnyJava.showobjects ('OEX1)
В столбце object__name представления USER_OBJECTS содержатся полные имена объектов схемы Java, длина которых не превышает 30 символов и которые не включают никаких символов из набора Unicode. Для остальных объектов в этом столбце указаны короткие псевдонимы. Чтобы преобразовать псевдоним в полное имя, можно вызвать функцию LONGNAME из пакета DBMS JAVA, о котором рассказывается в следующем разделе.
Пакет DBMS^JAVA
Встроенный пакет DBMS_JAVA обеспечивает доступ к параметрам виртуальной машины Aurora Java Virtual Machine и даже позволяет нх модифицировать. Здесь содержится большое количество программ, многие из которых предназначены для использования только в среде Oracle. Представленные в пакете функции и процедуры очень полезны для программистов, причем большую их часть можно вызывать и из SQL-инструкций. Краткое описание наиболее интересных прои>амм
приведено в табл. 22.5. Как упоминалось ранее в этой главе, в пакете DBMS_JAVA, кроме прочего, имеются программы для управления защитой и разрешениями.
Таблица 22.5. Основные программы пакета D8M5JAVA
Имя программы
Описание
функция LONGNAME
Возвращает полное (длинное) имя объекта Java, соответствующее указанному короткому имени Oracle
ф/нкция GET_COMPILER^OPTION
Возвращает значение параметра компилятора Java из таблицы параметров
Процедура SET^COMPILEILOPnON
Устанавливает значение параметра компилятора в таблице параметров Java; если таблицы еще нет, создает ее
Процедура RESETLCOMPlLfrcOPTION
Переустанавливает значение параметра компилятора в таблице параметров Java
Процедура SET_OUTPUT
Перенаправляет выходные данные Java в текстовый буфер DBMS_OUTPUT
Процедура EXPORT_SOURCE
Экспортирует объект схемы, представляющий исходный код Java, в больцюй объект (LOB)
Процедура EXPORLRESOURCE
Экспортирует объект схемы, представляющий ресурс Java, в большой объект
Процедура EXPORT_CLASS
Экспортирует объект схемы, представляющий класс Java, в Большой объект
Эти программы достаточно подробно рассматриваются в следующих разделах
Функция LONGNAME: преобразование длинных имен Java
Длина имен классов и методов Java может превышать 30 символов — максимальный размер идентификаторов SQL. В таких случаях Oracle создает для Java-элемента уникальное краткое имя и использует его в SQL и PL/SQL
Для получения длинного имени объекта, соответствующего заданному его короткому имени, используется функция
FUNCTION DBMSJAVA.LONGNAME (shortname VARCHAR2) RETURN VARCHAR2
Следующий запрос выводит длинные имена всех определенных в текущей схеме классов Java, у которых длинные н короткие имена не совпадают:
/* Файл в web: longname.sql */
SELECT objectjiare shortname.
DBMS_JAVA.L0NGNW4E (object_name) longnare
FROM USER 二 OBJECTS-
WHERE object_type - 'JAVA CLASS'
AND object^name !- D^S_JAVA.LONGNAME (object_name):
Приведенный выше запрос реализован и в пакете my Java (в файле myjava.pkg). Предположим, что мы объявили класс с таким именем:.
public class DropAnyQbjectldentifiedByTypeAndName {
Для Oracle это имя слишком длинное, и с помощью следующего вызова можно убедиться, что РСУБД определила для него короткое имя длиной 30 символов:
SQL> exec nyjava.show!angnames
Short Name | Long Name '
Short: /247421b0_DropAny0bjectIdentif
Long: OropAnyObjectldentlfledByTypeAndName
Функция GET_ COMPILER_OPTION и процедуры SET_ и RESET_COMPILER_OPTION: чтение и установка параметров компилятора
Параметры компилятора для классов Java устанавливаются в таблице базы данных JAVA$OPTIONS, называемой далее просто таблицей параметров. Их можно выборочно переустанавливать с помощью параметров утилиты loadjava. Каждая строка в таблице параметров содержит имена объектов схемы, к которым относится соответствующий параметр. Добавив в таблицу несколько строк с одним и тем же именем параметра, можно задать различные значения этого параметра для разных объектов схемы.
Компилятор обращается к таблице параметров только в том случае, если интересующий его параметр не задан в командной строке утилиты loadjava. Если же параметр не задан ни в командной строке, ни в таблице, компилятор использует его значение, задаваемое по умолчанию. (Подробную информацию о параметрах компилятора вы найдете в документации по Oracle SQJJ Developer^ Guide and Reference} Ниже приведены значения по умолчанию двух параметров компилятора: encoding - lat5nl online - true 〃 относится только к исходным файлам SQLJ
Для считывания и установки значений параметров в таблице используются следующие функции и процедуры пакета DBMS_JAVA:
FUNCTION DBMS JAVA.CET_COMPILER^OPTION (
объект VARCHAR2, ш_пэранетра VARCHAR2)
PROCEDURE DBMS JAVA. SET_COMPILER_OPT[ON (
объект VARCHAR2. имя^паранетра VARCHAR2. value VARCHAR2)
PROCEDURE DBMS JAVA,RESET_C0MPILER._QPTION (
обьект VARCHAR2, аня_пардметрд VARCHAR2)
Параметр объект задает имя пакета Java, полное имя класса или пустую строку. Компилятор ищет в таблице параметров строку, в которой значение параметра объект в наибольшей степени соответствует полному имени объекта схемы. Если н качестве значения параметра задана пустая строка, поиск распространяется на все объекты схемы.
Параметр 力Ах снрннетрн определяет, какой параметр устанавливается. Первоначально схема не содержит таблицы параметров. Ее можно создать с помощью процедуры DBMS_JAVA. SET_COMPILER_OPTION, которая к тому же присваивает значение
заданному параметру. Параметры должны быть заключены в одинарные кавычки, как это сделано в нашем примере:
SQl> DBMS_JAVA.SEI_COMP1LER_OPTION ('X.sqlj'. 'online', 'false'):
Процедура SET_OUTPUT: управление выводом из Java
При выполнении Java-кода в базе данных Oracle классы System.out и System.err выводят данные в текущие файлы трассировки. Это не очень удобно, если вам нужно просто протестировать код. В таких случаях с помощью процедуры DBMS_ JAVA.SET_OUTPUT можно перенаправить выходные данные в текстовый, буфер пакета DBMS_OUTPUT, затем они автоматически выводятся в SQL*Plus. Синтаксис указанной процедуры таков:
PROCEDURE D8MS_JAVA.SET_0UTPUT (рдзмер_буферд NUMBER);
Ниже приведен пример ее вызова в программе:
/* Файл з web: ssoo.sql */
SET SERVEROUTPUT ON SIZE 1000000
EXEC DBMSJAVA.SET_OUTPUT C10000D0);
Документация по совместному использованию команды SERVEROUTPUT и процедуры DBMS__JAVA. SET_OUTPUT слишком скудна. Поэтому автору пришлось самостоятельно провести тщательное тестирование, в результате которого были сделаны следующие выводы.
О Минимальный размер буфера (он используется по умолчанию) составляет всего 2000 байт, максимальный — 1 000 000 байт. При задании большего значения ошибки не возникает, но само это значение игнорируется.
О Значение размера буфера, устанавливаемое командой SET SERVEROUTPUT, имеет больший приоритет по сравнению со значением, которое задается с помощью процедуры DBMS_JAVA. SET_OUTPUT.
О Если при выводе данных с помощью процедур пакета DBMS_OUTPUT объем данных превышает размер буфера, отображается сообщение об ошибке:
ORA-10027: buffer overflow, limit of rtnn bytes
О Такая ошибка не возникает, если операторы вывода заданы в коде Java. В этом случае выходной текст просто усекается до размера буфера, и выполнение кода продолжается.
Как и в случае использования пакета DBMS^OUTPUT, вы не увидите выходных данных процедур и функций Java до тех пор, пока не будет завершена работа хранимой процедуры, из которой они были вызваны.
Процедуры EXPORT_SOURCE, EXPORT_RESOURCE и EXPORT_CLASS: экспорт объектов схемы
Пакет DBMS^JAVA включает следующий набор процедур для экспорта исходного кода, ресурсов и классов:
PROCEDURE DeMS_JAVA.EXPORT_SOURCE (
имя VARCHAR2,
[blob BL06 I dob CLOB ]
PROCEDURE DBMSJAVA.EXPORTJOURCE ( имя VARCHAR2. cxewd VARCHAR2. [blob BLOB I c?ob CLOB ]
);
PROCEDURE DBMS_JAVA .EXPORT.旺SCWCE ( имя VARCHAR2.
[blob BL06 I dob CLOfi ] ):
PROCEDURE DBMSJAVA. EXPQRT__RESOURCE ( имя VARCHAR2.~
схема VARCHAR2,
[Mob BLOB I dob CLOB ]
PROCEDURE DBMSJAVA.EXPORT.CLASS (
имя VARCHAR2,.
blob BLOB.
PROCEDURE D0MS_JAVA.EXPORT_CLASS (
ИНЯ VARCHARZ."
cxettd VARCHAR2..
blob BLOB ):
Во всех приведенных процедурах ямя - это имя экспортируемого Java-объекта схемы (если оно не задано, используется текущая схема), a blob | clob— большой объект, в который записывается указанный Java-объект схемы.
Класс нельзя экспортировать в объект типа CLOB — для этого пригоден только большой объект BLOB. Исходный код хранится в формате UTF8, и в этом же формате записывается в BLOB.
Рассмотрев следующую процедуру, вы поймете, как с помощью программ экспорта получить исходный код Java-объектов схемы:
尸 Файл a web: showjava.sp */
CREATE OR REPLACE PROCEDURE show java_source C
NAMEIN VARCHAR2. ~"
SCHEMAINVARCHAR2 ;= NULL
)
一 Выводит исходный код Java (прототип). Автор: Vadim Loevskl
IS
bCLOB:
vVARCHAR2CZOOO):
iINTEGER;
object^not^avallable EXCEPTION;
PRAGMA EXCEPTION INIT (object not available. -29532);
BEGIN~
/* Помещаем исходный код Java в CLOB. */
DBMS_LDB.createtemporary (b, FALSE );
dbmsJava.export_source (NAME. NVL (SCHEMA. USER). b);
/* Сохраняем большой обьект в переменной типа VARCHAR2 и выводим ее. */
1 产 1000;
DBMS_LOB.READ (b. 1, 1. v); pl
EXCEPTION
WHEN object_not_avaiTable
TWEN
IF (SQLERRM) LIKE '^no SUChXobjecf
THEN
08MS_0UTPUT.putj 1ne ('Java object cannot be found.'):
END【Fl-
ЕЮ:
Теперь с помощью инструкции CREATE JAVA можно создать Java-объект, представляющий исходный код:
CREATE OR REPLACE JAVA SOURCE NAMED “Hello"
AS■
public class Hello {
public static String helloO { return "Hello Oracle World*;}
Затем вы сможете вывести этот исходный код с помощью следующего вызова (если активизирован вывод в буфер пакета DBMS_OUTPUT):
5QL> exec show java source ('Hello')
public class Hello {
public static String hello( ) { return uHello Oracle World”;}
):.
Как сделать методы Java доступными в PL/SQL
Создав классы Java и загрузив их в РСУБД Oracle, вы можете вызывать из PL/SQL (и SQL) их методы, сделав таковые доступными с помощью оболочки PL/SQL
Спецификация вызова
Оболочки PL/SQL нужны только для тех методов Java, которые вы хотите сделать доступными через интерфейс PL/SQL. Методы Java могут вызывать другие методы Java непосредственно, без использования объектов-оболочек. Для того чтобы сделать метод Java доступным в Oracle, нужно написать спецификацию вызова -заголовок программы PL/SQL (функции или процедуры), тело которой представляет собой вызов метода Java с помощью предложения LANGUAGE JAVA. Такое предложение будет содержать полное имя метода, типы параметров и тип
возвращаемого значения. Спецификацию вызова можно объявить как отдельную функцию или процедуру, как программу в пакете или метод объектного типа:
CREATE [OR REPLACE] --только для отдельной программы «Стандартный заголовок функции или процедуры PL/SQL> (IS I AS) LANGUAGE JAVA
NAME ' полное_имя_метода (nonHoejfM^_rnna_java [. nonnoe__^_rnnd_jdva]...)
[return nonHoe_HHf_rana_javd']';
Здесь полиое_имя_типа_да ^ — это строка символов вида java, la ng. String.
Строка, заданная в предложении NAME, уникально идентифицирует метод Java, для которого создается данная оболочка. Параметры метода в его спецификации, сопоставляемые только по позиции, должны в точности соответствовать параметрам функции или процедуры PL/SQL, являющейся его оболочкой. Если метод не имеет параметров, включите пустой список параметров в его спецификацию, а не в заголовок процедуры или функции PL/SQL. Проиллюстрируем сказанное несколькими примерами.
Q Независимая функция, вызывающая метод:
CREATE OR REPLACE FUNCTION fDelete (
file IN VARCHAR2)
RETURN NUMBER
AS LANGUAGE JAVA
NAME 'JDelete.delete (
java.lang.String) return inf;
О Пакетная процедура, вызывающая метод, которому передается объект
CREATE OR REPLACE PACKAGE nat health care
IS~
ROCEDURE consolidate insurer (ins Insurer)
AS LANGUAGE JAVA ~
NAME 1NHC_consol1dati on.processtoracle.sql.STRUCT)1;
ENO natJiealth—Care:
О Метод объектного типа с предложением LANGUAGE:.
CREATE TYPE pet_ AS OBJECT (
name VARCHAR2(100)..
date_of_birth DATE.
MEMBER FUNCTION age (
namejn IN VARCHAR2) RETURN DATE
AS LANGUAGE JAVA
NAME 'petlrvfo.age (Java.lang.String) return java.sql.Timestamp'
О Независимая процедура с выходным параметром:
CREATE OR REPLACE PROCEDURE read out_nie (
filenameIN VARCHAR2, ~ -
filejineOUT VARCHAR2
AS LANGUAGE DAVA
NAME 'utils.ReadFI1e.readfJava.lang.Strlng
.Java.lang.Strlng[])';
Некоторые правила написания оболочек для Java-методов
Приведем несколько правил, которых следует придерживаться при написании оболочек для методов Java.
О Спецификация вызова метода в PL/SQL и указанный в ней метод Java должны принадлежать к одной схеме,
О Спецификация вызова делает доступной для Oracle точку входа метода Java верхнего уровня. Поэтому только общедоступные статические методы можно публиковать как независимые процедуры, а методы-члены класса публикуются как методы-члены объектного типа SQL.
О В списке параметров программы PL/SQL, служащей оболочкой для вызова метода Java, нельзя задавать значения параметров по умолчанию.
О В объектно-ориентированных языках программирования, если метод должен изменить значения атрибутов объекта, этот объект не передается ему в качестве параметра. Метод вызывается как член данного объекта. Если параметр метода, вызываемого из SQL или PL/SQL, необходимо изменить, в спецификации вызова для указанного параметра следует определить режим OUT или IN OUT, Соответствующий ему параметр Java должен быть одноэлементным массивом.
ПРИМЕЧАНИЕ
В качестве параметра можно использовалъ любой объект Java соответствующего тмпа, если речь идет □ параметрах, для которых задан режим IN OUT, или объект любого совместного типа данных, если это допускается типом данных Java. Новое значение параметра будет возвращено вызывающему коду. Например, выходной параметр типа NUMBER в Спецификации вызова можно поставить в соответствие Java-параметру, объявленному как float口 р, и затем присвоить навое значение элементу р[0]. Функция, в которой объявлены параметры с режимом использования OLTT или IN OUT, не может быть вызвана из DML-инструкции.
Сопоставление типов данных
Ранее в этой главе приводился очень простой пример оболочки PL/SQL — функция fDelete, передающая методу Java значение VARCHAR2 как параметр типа java, lang. String. Метод возвращал целочисленное значение, которое передавалось функции PL/SQL посредством предложения RETURN NUMBER. Это пример сопоставления типов данных, то есть установки соответствия между типами данных Java и PL/SQL
В спецификации вызова параметры PL/SQL и Java, как и возвращаемое функцией значение, сопоставляются в соответствии с их позициями, их типы данных должны быть совместимыми. О том, как соотносятся типы данных PL/SQL и Java, можно судить по табл. 22.6. Взаимопреобразование соответствующих друг другу типов данных Oracle выполняет автоматически.
Вы сможете убедиться, что Oracle автоматически преобразует только типы данных SQL. Произвести автоматическое преобразование специфических типов данных PL/SQL таких как BINARY_INTEGER, PLS_INTEGER, BOOLEAN и ассоциативные массивы, в соответствующие типы данных Java невозможно. Преобразование этих типов выполняется вручную. Далее, в разделе -«Другие примеры*, приводятся
ссылки на код, выполняющий нестандартное преобразование типов. Еще более подробные примеры с использованием JDBC имеются в документации по Oracle.
Таблица 22.6. Соответствие между типами данных SQL и Java
Тип данных SQL
Класс Java
CHAR, NCHAR, LONG, VARCHAR2, NVARCHAR2
oracle.sql.CHAR
java.Iang.String
java.sql.Date
java.sql.Time
java.sql.Tlmestamp
java.lang.Byte
java.Iang.Short
java.Iang.Integer
javaJang.Long
java Jang.Float java.fang.Double %va.mdth・®gDedmal byte, short, lnt( long, float; double
DATE
oracle.sql.DATE java.sql.Date java.sql.Time java.sql.Tirnestamp javaJang.SWng
NUMBER
oracle.sql.NUMBER java.lang. Byte java.lang.Short java.Iang.Integer java.Iang.Long java.lang.Fioat Java.Iang.Double java.math.BigDecimal
byte, short, int, long, float, double
RAW, LONG RAW
□racCe.Sql.RAW,
byte[]
ROWID
□rade.sql.CMAR orade.sql.ROWID java.Iang.String
BHLE
BLOB
Oracle.sql.BFILE
orade.sql.BLOB
oracle.jdbc2.Blob
CLOB,NaOB
oracle.sql.CLOB
orade.jdbc2.Qob
OBJECT
orade.sql.STRUCT oracle.SqijData oracle.jdbc2.Struct
REF
0racle.5ql.REF
oracle,jdbc2.Ref
TABLE, VARRAY
oracle.sql.ARRAY
oracle.jdbc2 Array
Любой из перечисленных oracle.sqt.CustomDatum
выше типов SQL
oracle.sqJ.Datum
Вызов метода Java в SQL
Из DML-инструкций можно вызвать пользовательские функции, и в том числе функции-оболочки методов Java. К этим методам предъявляются определенные требования.
О Метод, вызываемый из инструкции SELECT или распараллеливаемой инструкции INSERT, UPDATE либо DELETE, не должен модифицировать таблицы базы дан* ных.
О Метод, вызываемый из инструкции INSERT, UPDATE или DELETE, не должен выполнять запросы к таблицам базы данных или модифицировать таблицы, изменяемые этой инструкцией.
О Метод, вызываемый из инструкции SELECT, INSERT, UPDATE или DELETE, не должен выполнять SQL-инструкции управления транзакциями (например, инструк- пию COMMIT), управления сеансом (SET ROLE) или управления системой (ALTER SYSTEM). Кроме того, он не должен выполнять DDL-инструкции, так как они автоматически производят фиксацию транзакции. Данные ограничения отсутствуют, если метод выполняется из блока PL/SQL с автономной транзакцией. Цель ввода перечисленных ограничений 一 предотвращение побочных эффектов, которые могут помешать корректному выполнению SQL-инструкции. Попытка сделать это путем вызова метода, который не удовлетворяет хотя бы одному из требований, приведет к сообщению об ошибке во время выполнения уже при синтаксическом анализе SQL-инструкции.
Методы Java можно вызывать из PL/SQL посредством команды CALL и встроенного динамического SQL, что продемонстрировано на примере фрагмента кода, представленного ниже (реализация процедуры dropany приведена в следующем разделе):
DECLARE
Тр varchar2(30):-'TABLE':
Nm varchar2(30):-'inytable';
BEGIN
EXECUTE IWEDIATE 'CALL dropany(:tp. :nm)' USING tp. nm;
END:
Обработка исключений с помощью Java
Механизм обработки исключений в Java очень похож на подобный механизм, используемый в PL/SQL. Исключение сначала инициируется, а затем перехватывается для обработки.
Тем не менее механизм обработки исключений Java гораздо более совершенен. В нем имеется базовый класс, названный Exception. Все исключения являются объектами этого или производных от него классов. Исключения можно переда- ватз в качестве параметров и манипулировать ими так же, как объектами любого другого класса.
Исключение, генерируемое при выполнении SQL-инструкции хранимым методом Java, является объектом подкласса java .sql .SQLException. Этот подкласс имеет два метода, которые возвращают код ошибки Oracle и сообщение об ошибке: getErrorCodeO и getMessageO.
Если исключение, которое возникает при выполнении хранимой процедуры Java, вызванной из SQL или PL/SQL, не обрабатывается JVM, оно передается вызывающей программе. Ей передаются, в частности, все неперехваченные исключения, в том числе и такие, которые не относятся к SQL. Рассмотрим несколько способов обработки исключений.
Предположим, что для удаления объектов из базы данных посредством JDBC был создан специальный класс (это пример из документации Oracle):
/* Файл в web: dropany.java */
Import java.sql import java.io,*; Import oracle.jdbc.driver.*;
public class DropAny {
public static void object (String object_t>pe. String object_nare)
throws SQLExceptlon {
〃 Подклочение к Oracle через драйвер JD8C
Connection conn - new OracleDri ver().defaultConnectlon();
// Формирование $О1-инс1рукц^
String sql = "DROP ' + object_type + " " + object_name;
try {■
Statement stmt - conn.createStatementC): stmt.executellpdatefsql):■
stmt.closeO:
catch (SQLExceptlon e) {System.err.printlnCe.getNessageC)):)
}
)
ПРИМЕЧАНИЕ
• Конечно, не имеет смысла производрпь удаление объектов базы данных через JDBC, поскольку это гораздо проще сделать с помощью PL/SQL. Однако реализация подобной функции в Java делает ее доступной другим Java-программам.
Созданный класс перехватывает любое исключение SQLException и выводит информацию о нем с помощью следующего кода:
} catch (SQLException е) {System.err.println(e.getMessage(}):}
Загрузим его в базу данных Oracle с помощью утилиты loadjava и создадим для него PL/SQL-оболочку:
CREATE OR REPLACE PROCEDURE dropany (
tp IN VARCHAR2.
nm IN VARCHAR2
)'
AS LANGUAGE JAVA
NAME 'DropAny.object ( java.lang.String, java.lang.String)':
При попытке удалить несуществующий объект мы получим один из двух результатов:
SOL> CONNECT scott/tlger Connected.
SOL> SET SERVEROUTPUT ON.
SQL* BEGIN dr叩any ('TABLE', 'blip'): END;■
/
PL/SOL procedure successfully completed.
SQL> CALL OBMSJAVA.SET_OUTPUT (lOOOOOO);
Call completed.、
SQL> BEGIN dropariy ('TABLE', 'blip'); ENO:
0RA-00942: table or view does not exist■
Результатов же работы метода System, err. printl n вы не увидите, пока не будет вызвана процедура DBMS_JAVA.SET_OUTPUT. Однако в любом случае в вызывающем блоке исключение не инициируется, поскольку оно было перехвачено Java. После второго вызова функции dropany сообщение об ошибке, полученное классом Java с помощью метода getMessageO, попало в выходной текстовый буфер Oracle и было выведено на экран в SQL*Plus.
Если строки метода DropAny.object будут закомментированы, мы получим совсем иной результат:
SQL> BEGIN
dropany ('TABLE'.(Ы1р');
EXCEPTION
WHEN OTHERS
THEN■
DBMS_OUTPUT.PUT_LINE (SQLCODE);.
DBMS_OUTPUT.PUT_LINE (SQLERRM):
END;
Java.sql.SQLException; ORA-00942: table or view does not ex1st
at oracle.jdbc.kprb.KprbDBAccess.check_error(KprbDBAccess.Java)
at oracle.jdbc.kprb.KprbDBAccess.parseExecuteFetch(KprbOBAccess.Java)
at oracle.jdbc.driver.OracleStatement.doExecuteOtherCOracleStatement.Java)
at oracle.jdbc.driver.OracleStatement.doExecuteWithBatchCOracleStatement.Java)
at oracle.jdbc.driver.OracleStatement.doExecuteCOracleStatement.Java)
at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeouKOracleStatewit.Java)
at oracle.jdbc.driver.OracleStatement. executel1pdatefOracleStatement.java)
at DropAny.object(DropAny.Java:14)
-29532
ORA-29532: Dava call tennlnated by uncaught Dava exception: java.sql.SQLException: ORA-00942: table or view does not exist
Этот код требует пояснения. Результаты, выведенные между строкой
java.sql.SQLException: 0RA-0O942: table or view does not exist
и строкой
-29532
представляют собой содержимое стека ошибок, сгенерированных Java и направляемых на стандартный вывод независимо от используемого способа обработки
этих ошибок в PL/SQL. Иными словами, даже если бы блок обработки исключений в вызывающей программе PL/SQL был таким:
EXCEPTION WHEN OTHERS THEN NULL;
то на экран все равно выводился бы тот же текст, и лишь после этого продолжалось бы выполнение внешнего блока (при наличии такового). Обратите внимание: код ошибки Oracle равен не 0RA-OO942)a 0RA-29532, что свидетельствует лишь о появлении ошибки Java (какая это ошибка, пока что неизвестно). А для того чтобы перехватить ошибку и обработать ее должным образом, придется написать специальную утилиту.
Похоже, что утилита SQLERRM возвращает ошибку в такой форме:
ORA-29532: Java call ...: java.sql .SQLExceptlon: ORA-WW ...'
Поэтому в данной строке сначала ищется элемент java.sql .SQLException, а затем с помощью функции SUBSTR устанавливается номер исходной ошибки Oracle. На узле O'Reilly, в файле getErrorlnfo.sp, содержится действующая по этому принципу программа, которая возвращает код текущей ошибки и соответствующее сообщение.
В следующем разделе для расширения функций класса JDelete будет разработан класс JFile, предназначенный для выполнения из PL/SQL различных файловых операций. Мы подробно расскажем о процессе написания классов Java и соответствующих им программ-оболочек PL/SQL, которые служат для манипулирования объектами Oracle.
Новые возможности файлового ввода-вывода
Встроенный пакет Oracle UTL_FILE примечателен, скорее, не присутствующими, а отсутствующими возможностями. С помощью содержащихся в нем программ можно лишь последовательно считывать н записывать содержимое файла. Но удалять и копировать файлы, изменять привилегии, считывать содержимое каталога, задавать пути, производить какие-либо еще действия невозможно. Зато в Java имеется множество классов для выполнения указанных и ряда других операций с файлами. Вы уже немного знакомы с классом File и знаете, как просто с его помощью добавить в PL/SQL функцию удаления файла.
Теперь давайте создадим новый класс с именем JFIle, который позволит разработчикам PL/SQL получить ответы на следующие вопросы.
О Можно ли считывать данные из указанного файла? Существует ли этот файл? Чем - файлом или каталогом — является указанный элемент?
□ Сколько байтов содержит файл? Каков его родительский каталог?
□ Каковы имена всех файлов в каталоге, соответствующих заданному условию отбора?
Кроме того( с его помощью можно будет выполнять такие действия:
создавать каталоги;
переименовывать файлы;
изменять расширения файлов.
Мы не станем описывать все методы класса JFile и соответствующий пакет; в нем много повторений и большинство представленных там методов Java похожи на функцию deleteO, упоминавшуюся в начале главы. Нам больше хотелось бы сконцентрировать внимание на технологических моментах, связанных с разными элементами класса и пакета. Полный код класса JFile вы найдете в файлах, хранящихся на web-узле издательства O'Reilly.
О JFile.java — класс Java, собирающий информацию о файлах операционной системы и возвращающий ее через API, доступный из PL/SQL.
О xfi 1 е. pkg — пакет PL/SQL, служащий оболочкой для класса JF11 е. Его название расшифровывается как «eXtra stuff for FILEs» (дополнительные функции для работы с файлами).
ПРИМЕЧАНИЕ В Oracle9i Release 2 включена расширенная версия пакета UTL_FILE, который/ помимо всего прочего, позволяет удалять файлы, для чего в нем используется процедура UTL_FILE.FREMOVE. Кроме того, данный пакет поддерживает копирование файлов и их переименование.
Усовершенствование метода delete
Презвде чем приступить к разработке новых методов, проверим, насколько оптимальным является уже написанный нами код. Следует признать, что метод JDele- te.deleteO и функция del ete_f 11 е далеки от совершенства. Приведем код метода:
public static int delete (String fileName) <
File myFile = new File (fl 1 eName);
bool вал retval 匸 myFile.delete。; if (retval) return 1: else return 0;
}
CREATE OR REPLACE FUNCTION fDelete (
file IN VARCHAR2) RETURN NUMBER■
AS LANGUAGE JAVA
NAME 'JDelete.delete (
java.lang.String)
return int';
Проблема заключается прежде всего в том, что здесь используется числовое представление логических значений TRUE/FAL5E. В результате функцию приходится вызывать примерно так:
IF fdelete ('c:\temp\teiiv.sql') - 1 THEN ...
Это, конечно же, никуда не годится. И не только потому, что код «некрасив* и содержит жестко закодированное значение, но еще и потому, что при вызове метода fdelete нужно указывать, как будут представлены возвращаемые классом Java значения TRUE и FALSE.
Гораздо целесообразнее было бы определить функцию удаления файла с таким заголовком:
FUNCTION fDelete (
file IN VARCHAR2] RETURN BOOLEAN;
Поэтому давайте подумаем, можно ли предоставить пользователям пакета xfile более понятный и удобный интерфейс. Начнем мы с переименования класса JDe- 1 ete в класс JF:】е. Затем добавим методы, инкапсулирующие значения TRUE/FALSE, и будем вызывать их изо всех методов, возвращающих логические значения, включая метод delete。:
/* Фдйл в web: JFile.java */ import java.io.file:
public class JFYle {
public static 1nt tVal ( ) { return 1; };
public static int fVal C ) { return 0; };
public static int delete (String fileNmme}(
File ruyFile - new F11e (fileName);
boolean rctval - myFile.deleteC );
if (retval) return tValC;else return 丽{);
}
Теперь обратимся к пакету PL/SQL. Вот первая версия его спецификации:
/* Файл в web: xfile.pkg */'
CREATE OR REPLACE PACKAGE xfile IS
FUNCTION delete (file IN VARCHAR2)
RETURN BOOLEAN: END xfile;
Это объявление функции типа BOOLEAN, но как ее реализовать? Перед нами стоят две задачи.
, Скрыть тот факт, что значения TRUE и FALSE представлены в виде чисел.
. Избежать жесткого кодирования в пакете значений 1 и 0.
Для ик решения следует определить в пакете две глобальные переменные:
/* Файл в web: xfile.pkg */ CREATE OR REPLACE PACKAGE xfile IS
g_true INTEGER;
g_false INTEGER:
В конец пакета можно поместить инициализациоиный раздел, в котором глобальным переменным с помощью функций tVal и fVal присваиваются начальные значения. Выполнение данной операции в инициализационном разделе позволит избежать лишних вызовов методов Java и связанных с этим затрат:
8EGIN
g_true :- tval:
g_fa〕se fval;
END ^file;
Теперь вернемся в раздел объявлений пакета и определим две личные функции, единственным назначением которых будет предоставление доступа к методам JFIlu, инкапсулирующим значения 1 и 0:
FUNCTION tvdl RETURN NUMBER*
AS LANGUAGE JAVA
NAME 'JFile.tVal 0 return inf:
FUNCTION fvdl RETURN NUMBER
AS LANGUAGE JAVA
NAME 'JFHe.fVal () return inf;
Итак, жесткого кодирования значений TRUE/FALSE в пакете больше не будет. Для того чтобы спецификация пакета содержала настоящую логическую функ- циют создадим личную функцию-оболочку для метода JFile.deleteO, возвращающую число:
FUNCTION idelete (FILE IN VARCHAR2).
RETURN NUMBER
AS
UNGUAGE JAVA■
NAME 'JFile.delete (java.lang.String) return inf;
Теперь наша общедоступная функция delete может вызывать функцию ide1 ete и преобразовывать возвращаемое ею целочисленное значение в тип данных BOOLEAN, сравнивая его со значением глобальной переменной:
FUNCTION delete {file IN VARCHAR2)
RETURN BOOLEAN
AS
BEGIN
RETURN idelete (file) = g_true:
EXCEPTION~
WHEN OTHERS
THEN
RETURN FALSE :
END;
Вот так логическое значение Java преобразуется в логическое значение PL/SQL Вы еще не раз встретитесь с подобным преобразованием в теле пакета xfile.
Чтение содержимого каталога
Одной из интереснейших функций класса JFi 1 е является dirContents, возвращающая список файлов заданного каталога. Функция выполняет свою задачу с помощью метода Flle.HstO. Если строка, указанная при создании объекта File, была именем каталога, этот метод возвращает строковый массив имен файлов, обнаруженных в каталоге. Посмотрим, как можно сделать эту информацию доступной в PL/SQL.
Для начала напишем метод dirContents, возвращающий значение типа String:
/* Файп 8 web: JFIle.java */
public static String dirContents (String dir) {
File щуО1г - new File 【dir);
Strlngt] filesLIst - inyDlr.listO ;
String contents « new StringO:.
for (int T ■ Q; i v filesLIst.length; 1++)
contents - contents + listDelimiter + filesLlst[1J: // Utrecht 4/2002 return contents;
}
Данный метод создает объектную переменную myDir класса File, а также присваивает результат, возвращаемый методом myDir.listO, массиву filesList, который. состоит из элементов типа String. После этого в цикле for все имена файлов объединяются в одну строку, разделенную символами, которые были заданы переменной 11 stDeli mi ter. И, наконец, метод возвращает новую строку вызывающему коду.
В PL/SQL мы создадим оболочку, вызывающую данный метод:
FUNCTION dirContents (dir IN VARCHAR2.曲】im IN VARCHAR2)
RETURN VWCHAR2
AS
LANGUAGE JAVA
NAME 'JFlle.dlrContents (Java.lang.String, java.lang.String)
return java.lang.String';
Но что нам делать с полученной строкой? Напишем для функции-оболочки еще одну функцию, возвращающую информацию в более удобном для разработчика виде. Хорошо бы предоставить пользователям пакета xfiie возможность работать с именами файлов как со списком строк или с вложенной таблицей (поскольку в таком, более структурированном, виде их легче просматривать и обрабатывать). Для этого определим следующий табличный тип:.
CREATE TYPE fne_list_t IS TABLE OF VARCHAR2(2000):
Теперь осталось объявить процедуру, возвращающую список файлов заданного каталога в виде вложенной таблицы типа file_11st_t- Обратите внимание на вызов функции-оболочки di rContents и ссылку на переменную g_listdelim, содержащую разделитель, который был получен от JFile тем же способом, что и числовые значения для TRUE и FALSE:
PROCEDURE getOirContents (
dlr IN VARCHAR2.
files IN OUT filejist t)
IS-
filejist VARCHAR2C32767):
next_de!1m PLS_INTEGER:
startj)os PLS INTEGER 1:
BEGIN~
files.DELETE:
file_list :- dirContents (dir):
LOOP
next_dellm :-■
[NSTR (file list, gjistdellm. startjjos):
EXIT WHEN next delltn - 0:
files.EXTEND: ~
files(files.LAST)尸
SUBSTR (filejist,
start_pos,
next—dalim - startjws):
startjxis :口 next_delim + 1;
END LOOP;
ENO;-
Ну а дальше вы уже сами может экспериментировать с PL/SQL и Java, основываясь на описанных принципах их взаимодействия. В пакете xfile вы найдете следующие процедуры, использующие функцию getDi rContents:
О GetDi rContents — дает возможность пользователю указать типы отбираемых файлов, например *.tmp или %,tmp, и получить их список (символ подчеркивания, согласно стандарту SQL интерпретируется как один произвольный символ в проверяемой строке);
О s?iowDi rContents — выводит список файлов из указанного каталога, соответствующих заданному фильтру;
О chgext — изменяет расширение имени заданного файла.
Кроме того, вы найдете в пакете xfile точки входа пакета UTL_FILE, такие как FOPEN и PUT_LINE. Мы добавили их для того, чтобы пакет UTL FILE не использовался ни для каких других операций, кроме объявления дескрипторов файлов (UTL_FI- LE.FILE.TYPE) и ссылок на декларированные в нем исключения.
Другие примеры использования Java
На web-узле издательства O'Reilly представлены три интересных класса, в которых Java используется для расширения возможностей PL/SQL, а также для выполнения более сложного сопоставления типов.
О utlzip.sql — этот класс Java и соответствующий ему пакет делают доступными для PL/SQL функции сжатия файлов. Они также затружают класс в базу данных с помощью инструкции CREATE OR REPLACE JAVA, не прибегая к утилите loadjava. Вот заголовок инструкции, создающей класс Java:
CREATE OR REPLACE ANO RESOLVE JAVA SOURCE NAMED "UlLZip" AS
import java.util.zip.*:
Import java.lo.*:
public class Litlzip
{ public static void compressfilefstring infilename, string outfllename)
А это оболочка для вызова метода класса:
CREATE OR REPLACE PACKAGE utlzip
IS
PROCEDURE compres5Me Cp in file IN VARCHAR2. p_out_*le IN VARCHAR2)
AS'~
LANGUAGE JAVA
NAWE 'UTLZip.campf'essFileC Java. lang. String,Java. lang. String)':
END:
Q DeleteFile. java и deletefile.sql — код этого класса Java демонстрирует один из способов передачи коллекции (вложенной таблицы или массива VARRAY) в массив Java. Из заданного каталога удаляются все файлы, которые были изменены после определенной даты. Для этого в PL/SQL создается вложенная
таблица объектов, содержимое которой затем передается в Java с помощью класса oracle.sql .ARRAY:
CREATE TYPE filejetai Is AS OBJECT ( dirnarne VARCHAR2 (30). de^etedate DATE)
CREATE TYPE filjtdble AS TABLE OF file_deta11s:
CREATE OR REPLACE PACKAGE delete files
IS一
FUNCTION fdelete (tbl IN file table) RER^N NU圏ER
AS-
LANGUAGE JAVA
NAME 'DeleteFile. delete foracle.sql.ARRAY) return inf:
END delete_.files:
Приведем начальные строки метода Java. Обратите внимание, что код извлекает результирующий набор данных из массива и сканирует его. Полная реализация этого метода и подробные комментарии имеются в файле DeleteRle.java. public class DeleteFile {
public static int delete(oracle.sql.ARRAY tbl) throws SOLException f try (
// Считываем содержимое таблицы/varray как резултирующий набор
Resultset rs - tbl .getResultSetO;
for (int ndx - 0: ndx < tbl .lengthO: ndx++) { rs.nextf):
// Извлекаем индекс и элемент кассива. int aryndx - (int)rs.getlnt(i): STRUCT obj - {STRUCT)r$.getObject⑵;
О utl cmd. sql — этотДауа-класс м соответствующий ему пакет позволяют без труда выполнять из PL/SQL команды, операционной систевш.
23 Внешние процедуры
А Концепция внешних процедур
А Конфигурирование Oracle Net
> Создание библиотеки Oracle
> Вызов внешней функции
А Инициирование исключений из программ на С
> Агенты, отличные от используемых по умолчанию
А Сопровождение внешних процедур
Во времена, когда программисты работали с Oracle?, они часто задавали вопрос: «Какие средства можно вызывать из Oracle?» Как правило, под средствами подразумевалась система электронкой почты, команды операционной системы или функции, реализованные в других языках программирования. И хотя после включения в состав Oracle пакета UTL_SMTP проблема электронной почты потеряла свою актуальность, методов выполнения других внешних по отношению к Oracle операций по-прежнему существует не очень много. Для реализации внешних операций можно сделать следующее:
О написать программу в виде хранимой процедуры Java и затем вызывать ее из кода PL/SQL;
Q использовать таблицу базы данных или очередь как буфер для записи запросов и создать отдельный процесс для их считывания и обработки;
О создать канал базы данных и написать демон, отвечающий на входящие запросы;
О написать программу на языке С и вызывать ее как внешнюю процедуру.
Кратко рассмотрим каждый из этих методов. Код Java подходит в том случае, если его быстродействия достаточно для вашего приложения. Интересна техно* логия, базирующаяся на использовании очереди» но даже применение обыкновенных таблиц требует запуска двух сеансов Oracle: одного для записи запросов в очередь, а другого — для считывания. Более того, для каждого сеанса выделяется отдельное пространство транзакций, что может создать дополнительные сложности при работе приложения- Применение канала базы данных связано не только с проблемой двух сеансов, но и с упаковкой и распаковкой данных, передаваемых
по каналу. Кроме того, необходимость одновременной обработки множества запросов с использованием любого из этих подходов может потребовать от вас создания собственного процесса прослушивания соединения — листенера (Estener)- и системы диспетчеризации процессов.
Учитывая все это, мы остановились на четвертом методе. Применение внешних процедур позволяет выполнять в PL/SQL практически любые задачи, реализуемые средствами других языков программирования, и в то же время избежать проблем, характерных для остальных упомянутых подходов. У программистов PL/SQL обычно возникает множество вопросов относительно внешних процедур: как они работают, насколько защищены, каким образом проектировать их самостоятельно, каковы их достоинства и недостатки. В настоящей главе мы не только попытаемся дать ответы на все эти вопросы, но и приведем примеры, демонстрирующие большинство возможностей внешних процедур.
Концепция внешних процедур
Для того чтобы внешнюю программу можно было вызвать из среды Oracle, ее необходимо реализовать в виде библиотеки, доступной для совместного использования. Речь идет о библиотеках динамической компоновки (DLL), используемых Microsoft^ и общих библиотеках системы UNIX, хранящихся в файлах с расширением .so (shared object). Теоретически внешнюю процедуру можно написать на любом языке, но компилятор н компоновщик должны сгенерировать библиотеку, которую можно вызывать из кода, написанного на языке С. Чтобы обеспечить возможность вызова внешней программы из PL/SQL, для нее нужно написать специальную оболочку PL/SQL, называемую спецификацией вызова. Если внешняя программа возвращает значение, ей ставится в соответствие функция PL/SQL, а если она ничего не возвращает — процедура PL/SQL.
Пример выполнения команды операционной системы
В качестве первого примера применения внешних процедур рассмотрим вызов из программы PL/SQL команды операционной системы. Конечно, у вас немедленно возникнет масса вопросов, касающихся обеспечения безопасности, но их мы пока рассматривать не будем.
Используем очень простую написанную на С функцию extprocshO, которая принимает строку символов и передает ее функции system для выполнения в качестве команды:
1nt extprocshfchar *cmd)
return system(cmd): }
Она возвращает код результата, полученный от функции system, которая обын* но храяится в С-библиотеке времени выполнения ПЬс (для систем UNIX) ои msvcrt.dll (для систем Microsoft).
Сохранив функцию в файле extprocsh.c, при помощи компилятора GNU С можно сгенерировать файл совместно используемой библиотеки:
дес -с extprocsh.c
дсс -shared -о extprocsh.so extprocsh.o --Соглашение об именах в болыкинстве систем UNIX дСс -shared -о extprocsh.dll extprocsh.o --Соглашение об икенах в системах Microsoft Windows
В результате выполнения данных команд генерируются объектный файл extprocsh.o и файл совместно используемой библиотеки extprocsh.so или extprocsh.dll. Библиотечный файл требуется поместить в каталог, из которого система Oracle могла бы его извлечь1. Если переменная окружения ORACLE一НОМЕ установлена, можно просто скопировать файл из каталога, где он был откомпилирован, выполнив соответствующую команду:
ср extprocsh.so JORACLE_HOME/1ib/Unix
copy extprocsh.dll XORACLE_HOME^\bin\ -- Microsoft
Далее нужно создать в Oracle «библиотеку», указывающую на DLL. Если администратор базы данных предоставил вам привилегию CREATE LIBRARY, вы сможете выполнить приведенные ниже команды:
CREATE OR REPLACE LIBRARY extprocshelljib
AS 'ZuOl/app/oracle/9.2/11b/extproesh.so': -- UNIX
CREATE OR REPLACE LIBRARY extprocshelljib、
AS 'c:\oracle\ora92\bln/extprocsh.dTl1;-- Microsoft
Теперь у нас имеется библиотека extprocshelljib. Термин «библиотека* в данном случае применять не совсем корректно, потому что в действительности речь идет о псевдониме полного имени файла операционной системы, который добавлен в пространство имен Oracle.
Приступим к написанию PL/SQL-спецификапии вызова внешней процедуры:
CREATE OR REPUTE FUNCTION shell(emd IN VARCHAR2)
RETURN PLS INTEGER
AS
LANGUAGE C
LIBRARY extprocshelljib
NAME "extproesh" -
PARAMETERS (emd STRING. RETURN IMT);
Эта спецификация ставит в соответствие параметрам функции PL/SQL параметры функции С, позволяя обращаться к функции shell О из любого места кода, откуда может быть вызвана функция PL/SQL (SQL* Plus, Perl, Pro* С и т. п.). С точки зрения программирования приложений вызов внешней процедуры не отличается от вызова обычной процедуры PL/SQL.
Если администратор базы данных установил параметры системного окружения для поддержки внешних процедур (см. раздел «Настройка параметров конфигурации листенера* далее в этой главе), вызвать такую процедуру очень просто. Достаточно такого блока кода:
DECLARE
result PLS_INTEGER:
Усовершенствованная система защиты Oracle Net в OracleSi Release 2 позволяет по умолчанию ^а,- нить файлы библиотек только в указанных каталогах. Но, к сожалению, администратор базы данных не позволит поместить пользовательские файлы в каталоги Oracle.
BEGIN
result :■ shel1 с'номанла_рпердЦионноИ_систены');
ENO;~
ИЛИ:.
SX> VAR res NUMBER
SX> CALL shell('коидндд_рпераиионноИ_системы'3 INTO :res;
В большинстве случаев команда операционной системы выводит результаты на стандартное устройство stdout или stderr, однако вы можете (если позволяют привилегии) перенаправить вывод в файл. Так, в приведенном ниже примере со- д^жимое каталога не выводится на экран, а записывается в файл:
result:- shel 1('Is / > /trnp/extprcxL.out'):-- UNIX
result:= shelK'cnid /с “dir c:\ > c:\temp\extprcx:.out"*): -- Microsoft
Выполнение этих команд операционной системы возможно при наличии таких же прав, как у листенера Oracle Net, запустившего процесс extproc. Мы думаем, что администратор базы данных (или администратор, отвечающий за безопасность) вряд ли захочет изменить эту ситуацию.
Механизм вызова внешних процедур
Рассмотрим, что происходит в результате вызова внепшей процедуры. Для этого вернемся к примеру предыдущего раздела, при выполнении которого используется стандартный «агент» внешней процедуры.
Когда исполняющее ядро PL/SQL в результате анализа откомпилированного кода обнаружит, что программа реализована вне Oracle, оно будет искать сервис TNS с именем Е XTPROC_CONN ЕСТ I ON__DAT А. Он должен быть описан в одном из конфи- хурационных файлов Oracle Net (например, в файле tnsnames.ora). Как показано на рис, 23.1, листенер Oracle Net реагирует на запрос запуском для сеанса особого тцзоцесса, называемого extproc, которому он передает путь к файлу DLL, имя функции и ее аргументы. Этот процесс динамически загружает совместно используемую библиотеку, передавая ей нужные аргументы, принимает выходные данные и возвращает их вызывающему коду. При этом для конкретного сеанса Oracle выполняется только один процесс extproc — он стартует при первом вызове внешней процедуры и завершается по окончавии сеанса. Для каждой вызываемой внешней процедуры процесс extproc загружает соответствующую совместно используемую библиотеку (если это еще не сделано).
С целью повышения эффективности использования внешних процедур в Oracle применяются перечисленные ниже специальные технологии и средства.
Q Совместно используемая DLL. Внешняя С-программа должна быть реализо* вана в виде совместно используемой библиотеки динамической компоновки, а не статически скомпонованного модуля. Конечно, если компоновка откладывается до этапа выполнения, то это требует определенных затрат. Однако тем самым обеспечивается более эффективное использование памяти — за счет того, что одна библиотека может применяться в нескольких сеансах. Операционная система позволяет нескольким процессам совместно использовать некоторые страницы памяти библиотеки. Еше одним преимуществом динамически компонуемых модулей является то, что создавать и обновлять их легче,
чем статические программы. Кроме того, совместно используемая библиотека может содержать несколько подпротрамм (отсюда и термин «библиотека»), что также снижает издержки, поскольку требуется динамически загружать меньшее количество файлов.
О Отдельное пространство памяти. Внешние процедуры Oracle выполняются в области памяти, независимой от процессов ядра базы данных. Поэтому, если внешняя процедура завершается аварийно, это не оказывает никакого влияния на память ядра — процесс extproc просто возвращает код ошибки ядру PL/SQL, а оно передает сообщение о ней приложению. Хотя возможность написания внешней процедуры, которая может вызвать сбой работы сервера Oracle, существует, создать ее не легче, чем процедуру PL/SQL.
О Полная поддержка транзакций. Внешние процедуры полностью поддерживают транзакции. Это означает, что они могут быть задействованы в текущей транзакции. Получив от PL/SQL информацию «контекста», процедура может возвращать в базу данных записи, выполнять SQL- или PL/SQL-запросы и инициировать исключения. Все это требует низкоуровневого программирования с использованием интерфейса уровня вызовов РСУБД (Oracle Call Interface, OCI), но главное, что такая возможность существует.
Рис. 23.1. Вызов внешней процедурь|
Особенности выполнения внешних процедур в Oracle
Хотя внешние процедуры обеспечивают массу преимуществ, их использование сопровождается ростом издержек. Объясняется это тем, что выполнение внешних процедур в Oracle требует интенсивного взаимодействия процессов. Последнее вызвано необходимостью разграничения пространств памяти сервера базы данных и процедуры. Проблемы могут возникнуть также из-за того, что каждому
сеансу, вызывающему внешнюю процедуру, в дополнение к вторичному Ф。% q- му процессу, применяемому для подключения к серверу базы данных,曰已。6*口 м собственный процесс extproc. Все это приводит к дололнителъным заграт^^ *_ сурсов, а возможность совместного использования процесса extproc нескол^,. Р[ сеансами, к сожалению, отсутствует. w
Конфигурирование Oracle Net
Рассмотрим, как настраиваются параметры конфигурации, отвечающие за Bg некие внешних процедур и повышающие эффективность системы защиты. "
Настройка параметров конфигурации листенера
Соединение между PL/SQL и совместно используемыми библиотеками обеспечивается средствами компоненты Oracle Net. Хотя стандартная конфигур^д Oracle8i и более поздних версий предусматривает определенную поддержку внеиг них процедур, вы, скорее всего, не захотите ограничиться предлагаемыми по умод^ чанию возможностями до тех пор, пока разработчики Oracle не усовершенствует систему защиты.
На момент написания этой книги в Oracle по-прежнему недостаточно учить1- вался тот факт, что выполнение внешних процедур является слабым местом сиС" темы защиты базы данных. В частности, удаленный несанкционированный 口олн зователь может подключиться к базе данных через порт TCP/IP листенера (обычН° это порт 1521) и без аутентификации запустить процесс extproc. Чтобы избежав этой проблемы, советуем запускать листенеры Oracle за брандмауэром и не о'" тавлять доступным порт листенера, предназначенный для соединения с Интерн°~ том или другой незащищенной сетью.
Для настройки параметров листеаера требуется модифицировать файлы ^咤 mes.ora и listener.ora (вручную или с помощью Oracle Net Manager). Прмведе _ пример простого файла listener.ora, в котором параметры листернеров, предМазн: ченных для внешней процедуры и для базы данных, устанавливаются отде;льН ' LISTENER -
(ADDRESS = (PROTOCOL = TCP)(HOST = *w?一xobd)(PORT - 1521))
EXTPROC_LISTENER -
(ADDRESS = (PROTOCOL = I PC) (KEY = ключ_процецуры_ех1рг'ос))
SIO_LIST_LISTENER 工
(SIDJESC =
(GLOBAL_DBNAME - г/юбальное_иня)
(ORACLE_HOME - каталогJ)reele)
(SID_N前E R SID) -
(Е№*5-нЕХТРРОС_О115-ОМУ:слисок-0ай/?О5_сойлесгяо_^с/7о/?ь5уе№Х_обьекго8и)
(PROGRAM - extproc)
Назначение параметров, использованных в этом файле, описано ниже.
О ключ_процедуры_ех1ргос - короткий идентификатор, используемый Oracle Net для того, чтобы отличить данный листенер от других листенеров, участвующих в межпроцессорном взаимодействии (InterProcess Communication, IPC). Это имя можно выбирать произвольным образом, поскольку программы никогда его «не увидят*. По умолчанию при первой инсталляции Oracle Net на конкретной машине используется имя EXTPROCO. В списках ADDRESS файлов tnsna- mes.ora и Hstener.ora этот идентификатор должен быть одинаковым.
□ пня_хоста — имя или IP-адрес машины. Данный параметр не применяется к внешним процедурам, которые выполняют прослушивание только через IPC.
О кдТдлог_0гзс1е — полный путь к каталогу, в котором установлена система Oracle (ORACLE_HOME), предположим /uDl/app/oracle/product/9.2 (в UNIX) или же C:\ORACLE\ora92 (в Microsoft Windows). Обратите внимание, что имя каталога не заключено в кавычки и не завершается косой чертой.
О SID_внешкаИ—ПроиеАуры — это произвольный уникальный идентификатор листе- нера внешней процедуры. В стандартной конфигурации Oracle для него используется значение PLSExtProc.
О глобальное_ш — полностью определенное имя базы данных. Этот элемент не относится к внешним процедурам.
□ ENVS-"EXTPROC_DLLS-ONLY:список_фаИлов_совмесгно_используемых_объектов”
В Огас1е9г Release 2 предложение ENVS устанавливает переменные окружения для листенера. В нашем примере переменной EXTPROC_DLLS присваивается значение, обеспечивающее максимальный уровень защиты, — ключевое слово ONLY. Оно разрешает выполнение только тех совместно используемых библиотек, которые перечислены в списке.
Ниже приведен пример установки переменных среды для системы Solaris:
(EWVS=" EXTPROC__DLLS-ONLY:/uOl/арр/огас! е/admin/lxal/11 b/extprocsh. so: /uOI/арр/ oracle/admin/local/lib/RawdataToPrinter.so")
А вот пример для системы Windows ХР:
CENVS=HEXTPROC_DLLSONLY:c:\oracle\admin\lcx:al\lib\extprxsh.dll :c:\oracle\admin\ local\I1b\RawDataToPrlnter.dll")
Заметьте, что двоеточие в данном случае применяется не только в качестве разделителя элементов списка, но и для того, чтобы отделить букву, обозначающую диск (как это принято в MS-DOS). Хотя в этом списке названы всего две библиотеки, вы, конечно же, можете задавать любое их количество.
Если при работе с Oracle% Release 2 вы не зададите список значений в предложении ENVS, Oracle позволит вызывать только те библиотеки, которые располагаются в одном, специально предназначенном для них каталоге Oracle. Для системы Windows это будет каталог bin, а для системы UNIX — каталог lib. Когда в значении переменной EXTPROC_DLLS не указано ключевое слово ONLY, но задан список
файлов библиотек, помимо указанных библиотек для выполнения будут доступны и те, что хранятся в подкаталогах каталога bin или ПЬ.
Ну а если вы решите пренебрег безопасностью, можете задать ключевое слово ANY, что позволит вызывать любые совместно используемые библиотеки, доступные пользователю операционной системы, выполняющему листенер внешней процедуры.
Уровень защиты, установленный в данной конфигурации
Заданные нами параметры конфигурации с точки зрения обеспечения безопасности дают следующие преимущества:
О системный администратор может запускать листенер внешней процедуры от имени пользователя с учетной записью, предоставляющей ограниченные права. По умолчанию листенер выполняется от имени учетной записи, запустившей сервер Oracle;'
О функции листенера внешней процедуры ограничены приемом 1РС-запросов от локальной машины (возможность получения межсетевых запросов, поступающих по протоколу TCP/IP, отсутствует).
Настройку параметров листенера можно продолжить, добавив в файл tnsna- mes.ora для базы данных, из которой производится вызов, следующую запись:
EHPROC_CONNECTION_DATA 工
(DESCRIPTION ■
(ADDRESS - (PROTOCOL - IPC)(KEY - ключ_пpoцeдypы_extproc'))
(CONNECT_DATA - (SID - SID_npoueayptjyitproc) (PRESENTATION - RO))
)--
Большая часть этих установок вам уже знакома из приведенного выше описания конфигурации листенера. Обратите внимание на то, что значения ключ_проце- nypbiyxtproc и 510__процедуры_ех1ргос в файле настройки листенера должны соответствовать значениям в данном файле. Заданное нами значение параметра REPRESENTATION предназначено для повышения производительности, оно указывает серверу, который поддерживает прослушивание по нескольким протоколам, что клиент будет взаимодействовать с ним по протоколу RemoteOps (RO).
Внимательно проверьте права, предоставленные учетной записи дополнительного листенера, особенно права на модификацию файлов операционной системы или файлов пользователя с учетной записью oracle. Кроме того, задав значения переменной окружения TNS_AOMIN в UNIX (в Microsoft она устанавливается в реестре), файлы tnsnames.ora и listener.ora листенера внешней процедуры можно поместить в отдельный каталог.
ПРИМЕЧАНИЕ
Профессионалы должны следить за информацией о проблемах защиты Oracle, публикуемой на странице http://otn.oracle.com/deploy/security/alerts.htm. Упомянутая в разделе «Настройка параметров конфигурации листенера» проблема, связанная с внешними процедурами, описана на этой странице под номером 29, но рекомендуем вам просмотреть весь список, чтобы заранее принять все возможные меры защиты и установить необходимые «заплаты».
Настройка указанных выше конфигурационных файлов и создание дополнительной учетной записи пользователя операционной системы может показаться дополнительной нагрузкой, однако вопрос защиты настолько важен, что вам придется это делать.
Создание библиотеки Oracle
Инструкция CREATE LIBRARY определяет в словаре данных Oracle псевдоним файла внешней совместно используемой библиотеки, что позволяет исполняющему ядру PL/SQL найти эту библиотеку во время вызова. Создавать библиотеки разрешено только администраторам и пользователям с правами CREATE LIBRARY или CREATE ANY LIBRARY.
В общем случае инструкция CREATE LIBRARY имеет такой вид:
CREATE [ OR REPLACE ] LIBRARY иня_риблиотеки
AS_
'путьj<_^aAny' [ AGE盯'С8язь_бдзыуанных_агента'];
Здесь и«я_библиотеяи — допустимый идентификатор PL/SQ.L. Это имя будет применяться в теле внешних процедур, которым нужно вызвать совместно используемый объектный файл (DLL). Имя библиотеки не может совпадать с именем таблицы, объекта PL/SQL верхнего уровня или другим именем в основном пространстве имен.
Параметр 〃уть_к_фаилу — полный путь к совместно используемому объектному файлу (DLL). В Oracle9i появилась возможность применять в параметре путь_ *_侬府” переменные окружения, В частности, если переменная устанавливается до запуска листенера, ее можно указать в инструкции CREATE LIBRARY. Например: CREATE LIBRARY extprocshellJib AS '${ORACLE_HOME}/1ib/extprocsh.so': --UNIX
CREATE LIBRARY extprocshelljib AS 'XORACLE_HOMEX\bin\extprocsh.dll1: --Microsoft
Это удобно с точки зрения переносимости сценария, хотя, возможно, не очень хорошо с точки зрения безопасности.
Имеется и другой способ обеспечить возможность применения переменных при создании библиотек, а именно путем добавления их в список значении параметра ENVS листенера внешней процедуры. Так, в системе UNIX, для того чтобы задействовать переменную MYLI8S, можно использовать такую строку:
(ENVS="EXTPROC_DLLS=ANY.MYLIBS=/usr/local/extproclib")
Обратите внимание, что значение переменной окружения отделено от пути к библиотекам символом запятой. Теперь библиотеку можно создать с помощью следующей инструкции:
CREATE LIBRARY extprocshelljib AS '${MYLIBS}/extprocsh.so'; --UNIX
Практика показала, что помещать переменную в список значений параметра ENVS и ссылаться на нее при создании библиотеки можно только в системе UNIX (в Microsoft Windows ХР это не работает).
Возможно, вы захотите установить в системе UNIX еще одну переменную — ID_LIBRARY__PATH или ее эквивалент, чтобы предоставить внешней процедуре путь
для поиска совместно используемых библиотек, который отличается ат применяемого по умолчанию.
Параметр AGEMT 'связь^дзыу!днных_агентд' в инструкции CREATE LIBRARY - это необязательный параметр, задающий соединение с базой данных (в версии Oracle9i и выше), к которой имеет доступ владелец библиотеки. Это соединение должно быть ассоциировано с именем сервиса дая внешней процедуры. Предложение AGENT позволяет внешней процедуре выполняться на другом сервере базы данных, хотя физически она может располагаться на том же компьютере.
Пользуясь инструкцией CREATE LIBRARY, имейте в виду следующее:
О инструкция должна выполняться администратором базы данных либо пользователем с правами CREATE LIBRARY или CREATE ANY LIBRARY;
О как большинство других объектов базы данных, библиотека принадлежит конкретному пользователю (схеме) Oracle, который автоматически получает право на ее выполнение и может предоставлять и отменять такое право для других пользователей;
О пользователи, получившие на библиотеку право EXECUTE, могут ссылаться на нее в спецификациях вызовов, применяя синтаксис владелец. библиотека, или создавать и использовать для нее синонимы;
О выполняя инструкцию CREATE LIBRARY, Oracle не проверяет, суидествует ли указанный в ней файл. Такая проверка не производится и позднее, когда объявляется внешняя процедура для функции этой библиотеки. Поэтому, если путь содержит ошибку, вы узнаете об этом только при первом выполнении функции. Для каждой совместно используемой библиотеки необходимо создать одну библиотеку Oracle. Совместно используемая библиотека может включать множество функций С, и обращения к ней могут содержать несколько спецификаций вызова.
Ниже подробно описан процесс создания подпрограммы PL/SQL, которая будет служить оболочкой библиотечной функции, вызываемой из Oracle.
Вызов внешней функции
Вызов функции может быть задан в процедуре или функции PL/SQL верхнего уровня, процедуре или функции пакета либо в методе объекта. Более того, спецификация вызова может определяться как в спецификации, так и в теле пакета, или же в спецификации либо в теле объектного типа. Приведем несколько примеров.
CREATE FUNCTION имя Аргументы) RETURN тип^возврдтаепых__данных
AS спецификация^вызова;一~
Вы, вероятно, узнали в первом примере функцию shell(), описанную ранее в этой главе. Можно создать и процедуру:
CREATE PROCEDURE w
AS 5耿啾耽d“frW_8M3Sd;
В данном случае соответствующая функция С должна быть объявлена как void.
Далее представлена пакетная функция, для которой не требуется создавать тело пакета:
CREATE PACKAGE ^я_пдкета
AS
FUNCTION иня RETURN гил_аозвр»емыхтдаяных
AS спеинфиндции^зызова;
END;"
Но учтите, что при модификации пакета спецификацию вызова придется перекомпилировать. В зависимости от объема выполненных изменений, можно значительно уменьшить количество объектов, подлежащих перекомпиляции, переместив спецификацию вызова в тело пакета:
CREATE PACKAGE **月 пакета
AS-
PROCEDURE m;
END:
CREATE PACKAGE BODY имя пакета
AS~
PROCEDURE и«я;
IS спецафикацияуызова ;
END;~
Применяемые внутри пакета личные или не являющиеся общедоступными программы также могут быть реализованы как внешние процедуры. В методе объектного типа спецификация вызова определяется так же, как в пакете, то есть ее можно поместить либо в спецификацию, либо в тело объектного типа.
Спецификация вызова
Спецификация вызова задается в предложении AS LANGUAGE1 и выглядит следующим образом;
AS LANGUAGE С
LIBRARY ими_библиотеки
[NAME имя^внешнеИ^нкции]
[WITH CONTEXT ] ~
[AGENT IN (иия_форндльного_рарамеrp5)]
[PARAMETERS (список_знешйих_пдраметров)]
Назначение элементов данной спецификации описано ниже.
О AS LANGUAGE С - альтернатива параметру AS LANGUAGE JAVA, который был рассмотрен в главе 22.
Q LIBRARY имя_библиотеки - имя библиотеки, определенное с помощью инструкции CREATE LIBRARY, на которую у вас имеется право на выполнение.
Q NAME ^я_внешней^нк^ — имя функции в том виде, как оно задано в С-биб- лиотеке. Но если имя представлено символами нижнего или обоих (верхнего и нижнего) регистров, оно должно быть заключено в двойные кавычки. Если
' В Oracle 8.0 этого предложения не было, вместо него использовалась не поддерживаемая теперь форма AS EXTERNAL.
данный параметр опущен, в этом случае имя внешней процедуры должно совпадать с именем, указанным для соответствующего ей модуля PL/SQL (по умолчанию представлено символами верхнего регистра).
О WITH CONTEXT - наличие этого предложения указывает, что PL/SQL должен передавать вызываемой программе указатель на «контекст», а вызываемая программа должна принять этот указатель в качестве параметра типа OCIExtProC' Context* (определен в заголовочном С-файле ociextp.h). «Контекст» представляет собой структуру данных, которая содержит разнообразную информацию, специфическую для Oracle.
О AGENT IN (имя^х)рмзльного_пдрдметра) — это предложение, подобно предложению AGENT в определении библиотеки, задает другой процесс агента. Однако оно позволяет отложить выбор агента до этапа выполнения. Имя агента сначала передается в качестве параметра функции-оболочке PL/SQL, а из нее 一 во внешнюю функцию. При выполнении этой функции переданное ей имя агента заменяется именем, заданным для библиотеки (если оно было задано). Имя соответствующего формального параметра функции PL/SQL указывается в предложении AGENT IN.
О PARAMETERS (список_внешних_параметров) — в этом предложении определяются тн- пы данных параметров, которыми обмениваются PL/SQL и С.
О список_внешних_параметров — это список элементов, разделенных запятыми. Позиции параметров в списке и в объявлении функции С должны совпадать. Данный список представляет собой самую сложную часть спецификации, требующую подробного разъяснения.
Список внешних параметров
Выполняя обмен данными между PL/SQL и С, необходимо учитывать следующие особенности. Во-первых, PL/SQL имеет собственные типы данных, лишь частично совпадающие с типами данных С. Поскольку переменные PL/SQL могут принимать значение NULL, то булевы (логические) переменные в этом языке могут иметь три значения. Для переменных языка С это не применимо. Во-вторых, библиотека С может «не знать», набор символов какого языка используется для представления буквенно-цифровых значений. Кроме этого, необходимо выяснить, каким методом должен быть передан аргумент С-функции: по значению или по ссылке (то есть в виде указателя).
Вернемся к приведенному в начале главы примеру, где используется функция shell О. Для нее не предусмотрен вызов с аргументом NULL, который может быть задан вместо существующей команды. Поэтому результатом вызова функции shell (NULL) будет ошибка времени выполнения 0RA-O14O5: fetched column value 1s NULL. Возможно, существуют приложения, для которых такого сообщения достаточно, но мы потребуем, чтобы внешняя процедура гоже возвращала NULL в ответ на входное значение NULL.
Чтобы в языке С можно было корректно распознать значение NULL, во внешнюю программу из PL/SQL требуется передать дополнительный параметр, называемый переменной-индикатором. Аналогичным образом, для того чтобы программа С могла возвратить Oracle значение NULL, она также должна передать
в PL/SQL дополнительный параметр-индикатор. И если со стороны PL/SQL эти значения устанавливает и интерпретирует РСУБД Oracle, то функция С должна получать и устанавливать их явно.
Вот как изменится спецификация функции shell в PL/SQL после реализации поддержки значения NULL;
CREATE OR REPLACE FUNCTION shelKcmd IN VARCHAR2)
RETURN PLS_INTEGER
AS~
LANGUAGE C
LIBRARY extprtx:shell_lib
NAME "extprocsh"
PARAMETERS {cmd STRING, cmd INDICATOR, RETURN INDICATOR. RETURN IND;
Формальные параметры, заданные для функции PL/SQL, могут располагаться в любом месте списка PARAMETERS. Однако сам этот список должен в точности (то есть по позиции и типу данных) совпадать со списком параметров функции С. Возвращаемое значение, идентифицируемое в этом списке ключевым словом RETURN, должно стоять последним.
Если вы согласны на порядок сопоставления по умолчанию, возвращаемый параметр можно и не задавать, но индикатор должен присутствовать обязательно:
CREATE OR REPLACE FUNCTION shell(cmd IN VARCHAR2)
RETURN PLS^INTEGER
AS一
LANGUAGE C
LIBRARY extprocshell_lib
NAME "extprocsh"
PARAMETERS (and STRING, cmd INDICATOft, RETURN INDICATOR):
Следует отметить, что хотя мы и внесли в спецификацию функции некоторые изменения, они не видны коду, вызывающему функцию shell (* — для него количество и типы данных параметров остались прежними.
Теперь рассмотрим новую версию программы на языке С, в которую добавлены два параметра, по одному для каждого индикатора:
1 #include <oci extp.h>
int extprocsh(char *cmd. short cmdlnd, short *械12)
if (cmdlnd — OCI IND_NOTNULL)
*retlnd = (short)OCI_IND_NOTNULL;'
return system(cmd);
} else
{
П*retlnd - (short)OCI_IND_NULL;
return 0:
)
M }
Ниже кратко описаны особенности этой программы.
Строка
Описание
1
Данный подклюмаемый файл (include file) в аперационньк системах Microsoft располагается в подкаталоге %ORACLE_HOME%\ociYinclude, а на большинстве и^1Х-машин он хранится в каталоге $ORACLE_HOME/rdbms/demo (в вашей системе он может оказаться и в другом месте)
3
Обратите внимание на то, что индикатор параметра cmd имеет тип short, а индикатор возвращаемого значения — short*. Эта соответствует соглашению о передаче параметров, согласно которому входные параметры из PL/SQL в С передаются по значению, а выходные параметры и возвращаемое значение из С в PUSQL — по ссылке
5,7
Переменная-индикатор может иметь значение либо OQ_IND_NULL, либо OCLIND一NOTNULL Эго специальные значения, которые определены с помощью директив #define в подключаемом файле Oracle. Как видите, индикатору возвращаемого значения явно присваивается одно из двух значений
11,12
Если индикатор во^аращаемого значения указывает, что функция вернула NULL, возвращаемое значение 0 просто игнорируется
Далее показано несколько простых команд, которые выполняют компиляцию и компоновку приведенной ранее программы. Для UNIX (как и раньше с помощью компилятора GNU С):
дсс -с -ISfORACLE^HOMEJ/rdbms/defflo -I$ORACLE_HOME/rdbms/pubHc extprocsh.c
дсс -shared -о extprocsh.so extprocsh.o
А вот эквивалентные команды для Windows (с использованием Cygwin-ком- пилятора дсс):
дсс -I/cygdrive/c/oracle/ora92/oci/include/ -с extprocsh.c
дсс -shared -о extprocsh.dll extprocsh.o
Теперь вы готовы перейти к более сложным вопросам, касающимся соответствия параметров.
Сопоставление параметров
В предыдущем разделе говорилось, что при обмене данными между PL/SQL и С каждый тип данных PL/SQL ставится в соответствие внешнему типу данных, идентифицируемому ключевым словом PL/SQL, которое, в свою очередь, соответствует определенному набору типов С:
Типы данных PL/SQL о Внешние типы данных с Типы данных С
Внешние типы данных идентифицируются в предложении PARAMETERS с помощью ключевых слов PL/SQL, которые в ряде случаев (но не всегда) совпадают с названиями типов данных С. Например, при передаче переменной PL/SQL типа PLS_IbTTEGER ей назначается внешний тип INT, соответствующий типу данных iM языка С. Однако типу данных Oracle VARCHAR2 соответствует внешний тип данных STRING, обычно сопоставляемый с типом char* языка С.
Все возможные варианты преобразования типов данных, которые поддержИ- вает Oracle-интерфейс, обеспечивающий взаимодействие между PL/SQL и С> 口修 речислены в табл. 23.1. Обратите внимание, что допустимость преобразований
зависит и от типов данных, и от режима передачи формального параметра PL/SQL (это показывает предыдущий пример). В тех случаях, когда возможно сопоставление с несколькими типами данных, используемый по умолчанию тип выделен жирным шрифтом.
Таблица 23.1. Допустимые сопоставления типов данных PL/SQL и С
Тип данных параиегра PL/SQL
Ключевое слово
PL/SQL, идентифицирующее внешний тип
Типы данных С для параметров PL/SQL, которые используются
в режиме IN и дляв режиме IN OUT, О1ГГ
возвращаемогои для параметров,
функциейобозначенных как
значенияBY REFERENCE
Семейство длинных целых: BINAR<_INTEGER, BOOLEAN, PLS」NTEGER
INT, UNSIGNED 1NT, CHAR, UNSIGNED CHAR, SHORT, UNSIGNED SHORT, LONG, UNSIGNED LONG, SB1, UB2, 5B2, UB2, SB4, UB4, SEE_T
Ent, unsigned Int, char, unsigned char, short, unsigned short, long, unsigned long, sbl, ub2, sb2, ub2^ sb4, ub4, size_t
To же, что слева, но используются указатели (например, по умолчанию применяется ие Int, a Int*)
Семейство коротких целых: NATURAL, NATURALN, POSITIVE, POSinVEN, SIGNTYPE
To же, что и выше, с той разницейг что по умолчанию используется UNSIGNED Ж
To же, что и выше, с тай разницей, что по умолчанию используется unsigned int
То же, что выше, стой разницей, что по умолчанию используется unsigned Int*
Семейство символьных типов: VARCHAR2, CHAR, NCHAR, LONG, NVARCHAR2, VARCHAR, CHARACTER, ROWID
STRING, 0OSTRING
char*, odstring
char*, oclstrlng
NUMBER
OCINUMBER
OCINumber*
oaNumber*
DOUBLE PRECISION
DOUBLE
double*
double*
FLOAT, REAL
FLOAT
float
float*
RAW, LONG RAW
RAW, OQRAW
unsigned char*, OQRaw*
unsigned char*, □CIRaw*
DATE
OCIDATE
OCIDate*
OCIDate*
Семейство ПМ5АМР: TIMESTAMP, TIMESTAMP WITH TIME ZONE, TIMESTAMP WITH LOCAL TIME ZONE
ОСЮАТЕПМЕ
OCIDatetime*
OCIDatetime*
INTERVAL DAY TO SECOND, INTERVAL YEAR TO MONTH
OCIINTERVAL
OCIInterval*
CXHInterval*
BFILE, BLOB, CLOB
OCILOBLOCATOR
OCILOBLOCATOR*
OCILO8LOCATOR*
Типы данных С для параиетроа PL/SQL,. которые используются
Тмп данныхКлючевое слово
параметраPL/SQL, мдянтифици-
PL/SQLрующее внешний тип
в режиме ZN и для возвращаемого функцией значения
в режиме IN OUT, OUl и для параметров, обозначенных как BY REFERENCE
Дескриптор типа,TDO
определяемого пользователем (коллекции или объекта)
ООТуре*
ОСГТуре*'
Значение определяемой OCICOLL пользователем коллекции
OCIColl** ООАггау»* OOTable»*
OCICofl**, OCIArray**, ОСГГаЫе**
ЗначениеDVOID
определяемого пользователем объекта
dvold*
dvoid* для типов final; dvcld** для типов не-final
В некоторых простых случаях, когда передаются только числовые аргумент^ и допускается применять типы, назначаемые по умолчанию, предложение PARA METERS можно вообще не задавать. Однако при передаче индикаторов или другие свойств данных его применение необходимо.
Каждый элемент дополнительной информации, которая используется при об' мене с внешней функцией, передается посредством отдельного параметра и зада' ется как в предложении PAIW1E7ERS, так и в спецификации функции на языке С
Синтаксис предложения PARAMETERS
В предложении PARAMETERS задается список, который может содержать следующий разделенные запятыми элементы:
О имя параметра, за которым следует идентификатор внешнего типа данных;
О ключевое слово RETURN и связанный с ним идентификатор внешнего типа;
О ^свойство» параметра PL/SQL или возвращаемого значения, например индикатор NULL или целое число, соответствующее длине параметра;
О ключевое слово CONTEXT, задающее позицию, в которой передается указатели контекста;
Q ключевое слово SELF (в случае применения внешней процедуры для метода^ члена объектного типа).
Если в спецификации вызова нет ключевого слова CONTEXT, то она выглядит следующим образом:
{川卄算 I RETURN I SELF } [ своИство ][ BY REFERENCE ] [ якейиий_гил_даяных ]
При наличии предложения WITH CONTEXT соответствующий элемент в списке параметров задается одним словом:
CONTEXT
Кроме того, при использовании предложения WITH CONTEXT ключевое слово CONTEXT принято располагать в списке параметров первым, поскольку именно там оно должно находиться в случае, когда сопоставление остальных параметров производится по умолчанию.
Элементы списка параметров имеют следующее назначение.
О имя I RETURN I SELF 一 имя параметра из списка формальных параметров модуля PL/SQL, ключевое слово RETURN или SELF (в случае использования метода-члена объектного типа). Имена параметров PL/SQL и параметров в программе на языке С не обязательно должны быть одинаковыми, однако порядок параметров должен совпадать.
О свойство — одно из следующих ключевых слов: INDICATOR, INDICATOR STRUCT, LENGTH, MAXLEN, TOO, CHARSETID или CHARSETFORM (они будут описаны в следующем разделе).
О BY REFERENCE — указывает, что передача параметров производится по ссылке, то есть модуль в совместно используемой библиотеке получает не значение параметра, а указатель на это значение. Установка 8Y REFERENCE важна только для скалярных параметров, которые используются в режиме IN и не являются строками, то есть принадлежат к типу BINARY_INTEGER, PLS_INTEGER, FLOAT, DOUBLE PRECISION или REAL. Данные других типов (применяемые в режиме IN OUT или OUT, а также параметры IN типа STRING) зсегда передаются по ссылке, и в соответствующем прототипе С для них должен быть задан указатель.
О flнешний_гип^днных - ключевое слово из второго столбца табл. 23.1, которое задает внешний тип данных. Если его не указать, будет использоваться внешний тип данных по умолчанию, приведенный в этой таблице.
Свойства параметров
В этом разделе описаны различные свойства, которые могут задаваться в предложении PARAMETERS.
Свойство INDICATOR
Это свойство представляет собой флаг, указывающий на то, имеет ли параметр значение NULL Допустимыми для данного свойства являются следующие характеристики.
О Внешние типы, которые используются при объявлении переменной-индикатора —short (по умолчанию), 1nt и long.
О Типы PL/SQJL 一все скалярные типы данных. При передаче переменной-индикатора для составного типа данных, такого как пользовательский объект или коллекция, применяется свойство INDICATOR STRUCT.
О Режимы использования параметров PL/SQL — IN, IN OUT, OUT и RETURN.
О Методы передачи параметров внешней программе — по значению для параметров IN (если не задано BY REFERENCE) и по ссылке для параметров IN OUT, OUT и RETURN. -
Таким образом, свойство INDICATOR можно задавать для параметров в любом режиме, включая RETURN. Если индикатор не указан, PL/SQL считает, что пнеш- няя программа никогда не возвращает значение NULL (так бывает не во всех случаях; см. врезку ^Индикация без индикаторов»).
Если внешней процедуре передать переменную в режиме IN с индикатором. Oracle присвоит значение индикатора автоматически, но если возвращаемый модулем параметр имеет режим RETURN или OUT, то значение индикатора следует установить в коде С.
Для параметра IN соответствующий параметр-индикатор в функции С можно объявить таким образом;
short plndicatorFoo
а для параметра IN OUT это можно сделать так;
short *p!ndicatorFoo
В теле функции С следует использовать определенные с помощью директивы #define константы OSI_INOmNOTNULL и OSI_IND_NULL, которые будут доступны, если посредством директивы #i nd ude включить в программу файл oci.h. Oracle определяет их так:
typedef sb2 OCIInd;
Refine OSI_INO_NQTNULL (OCIInd)O/* not NULL */
#define OSIJND~NOTNULL (OCIInd)(-l) /* NULL */
ИНДИКАЦИЯ БЕЗ ИНДИКАТОРОВ
Что произойдет, если не задать переменную-иадикатор для строки и вернуть пустую строку С? Рассмотрим простую программу:
void mynulKchar *outbuff)
(
outbuff[0] 口 ЛО';
}
Спецификация вызова этой программы имеет следующий вид:
CREATE OR REPLACE PROCEIXJRE myng (res QUT VARCHAR2)
AS
LANGUAGE C
LIBRARY nynul 111b
NAME "mynuir;
/
При вызове такой внешней процедуры PL/SQL интерпрепфует значение параметра как NULL Оказывается, внешний тип данных STRING обладает некоторыми особенностями: для того чтобы вернуть Oracle значение NULL достаточно передать строку длиной 2 байта, в которой первый байт равен \0. Однако учтите, что это работает только при отсутствии свойства LENGTH.
Но мы все же рекомендуем не лениться и использовать явный индикатор.
Свойство LENGTH
Это свойство представляет собой целочисленное значение, которое задает коли- [ество символов в символьном параметре. Допустимыми для данного свойства шляются следующие характеристики.
Внешние типы, которые используются при объявлении переменной свойства 一 int (по умолчанию), short, unsigned short, unsigned Int, long, unsigned long.
Типы PL/SQL 一 VARCHAR2, CHAR, RAW, LONG RAW.
Э Режимы использования параметров PL/SQL 一 IN, IN OUT, OUT и RETURN.
Э Методы передачи параметров внешней программе - по значению для параметров IN (если не указано BY REFERENCE) и по ссылке для параметров IN OUT, OUT и RETURN.
Свойство LENGTH обязательно для параметров типа RAW и LONG RAW, а для других символьных типов данных поддерживается просто из соображений удобства про- ^аммирования на С. При передаче значения типа RAW из PL/SQL в С значение свойства LENGTH присваивается автоматически. Однако для того чтобы программа на языке С возвращала в PL/SQL значение типа RAW, необходимо явно установить в ней значение этого сзойстза.
Для параметра IN переменную свойства LENGTH в программе С можно объявить следующим образом:
int pLenFoo
а для параметра IN OUT это можно сделать так:
int *pLenFoo
Свойство MAXLEN
Данное свойство 一 это целочисленное значение, которое задает максимальное количество знаков символьного параметра. Допустимыми для данного свойства являются следующие характеристики.
Q Внешние типы, используемые при объявлении переменной свойства - 1nt (во умолчанию), short, unsigned short, unsigned int, long и unsigned long.
о Типы PL/SQX - VARCHAR2, CHAR, RAW и LONG RAW;
Q Режимы использования параметров PL/SQL — IN OUT, OUT и RETURN;
Q Методы передачи параметров внешней программе — только по ссылке.
Свойство MAXLEN применимо только к параметрам, имеющим режим IN OUT или OUT. Если попытаться задать его для параметра IN, Oracle выдаст сообщение об ошибке компиляции PLS-00250: Incorrect usage of MAXLEN in parameters clause. В от- ли^ие от параметров со свойством LENGTH, данные, для которых задается свойство MAXLEN, всегда передаются по ссылке.
В программе на С переменная для данного свойства может быть объявлена следующим образом:
int *pMaxLenFoo
Свойства CHARSEHD и CHARSETFORM
Эти два свойства представляют собой флаги, которые применяются для передачи информации о наборе символов языка. Допустимыми для данных свойств являются следующие характеристики.
О Внешние типы, используемые при объявлении переменной свойства — unsigned int (по умолчанию), unsigned short и unsigned long.
О Типы PL/SQL - VARCHAR2, CHAR и CLOB.
О Режимы использования параметров PL/SQL — IN, IN OUT, OUT и RETURN.
О Методы передачи параметров внепшей программе — только по ссылке.
Свойства CHAR5ETID и CHARSETFORM позволяют передать в программу С идентификатор и информацию о наборе символов языка, который применяется для кодировки данных, передаваемых внешней процедуре. Они необходимы в том случае, если набор символов не является используемым по умолчанию. Эти значения доступны только для чтения, а модифицировать их в программе нельзя. Ниже приведен пример предложения PARAMETERS с информацией о наборе символов:
PARAMETERS (CONTEXT, cmd STRING, cmd INDICATOR, and CHARSETID, and CHARSETFORM):
Oracle устанавливает дополнительные значения автоматически, основываясь на наборе символов, который используется для представления значения аргумента cmd. Дополнительную информацию о поддержке языков в программах С можно найти в разделе документации Oracle, посвященном описанию интерфейса уровня вызовов (OCI).
Инициирование исключений из программ на языке С
Программа shel 1(), приведенная в качестве примера, является типичной для языка С — эта функция возвращает код состояния, значение которого проверяется в вызывающей программе для определения того, успешно ли завершилась заданная операция. Не лучше ли (с точки зрения PL/SQL) реализовать такую программу в виде процедуры, инициирующей исключение при возникновении какой-либо ошибки? Чтобы ответить на этот вопрос, рассмотрим, как реализовать процедуру RAISE APPLICATION ERROR с использованием интерфейса уровня вызовов РСУБД Oracle.""
Для выполнения этой задачи помимо замены функции процедурой нужно сделать следующее:
О передать контекст;
О указать номер ошибки из диапазона 20001-20999 и записать соответствующее ссюбщение;
Q добавить вызов служебной программы OCI, инициирующей исключение.
В спецификацию вызова необходимо внести следующие изменения:
/* Файл в web: extprocsh.sql */
CREATE OR REPLACE PROCEDIME shellCcmd IN VARCHAR2) AS
LANGUAGE C
LIBRARY extproc$hell_lib
NAME "extprocsh"
■ WITH CO灯EXT
PARAMETERS (CONTEXT, cmd STRING, cmd INDICATOR):
Кроме того, из процедуры удалены возвращаемый параметр и соответствующий индикатор, поскольку они больше не нужны. В приведенном ниже коде продемонстрировано, как можно получить указатель контекста и инициировать с его помощью исключение:
/* Файл в web: extprocsh.c */
1include <ociextp.h>
2#include <errno.h>
3
void extprocsh(OCIExtProcContext *ctx, char *cmd. short cmdlnd)
{
int excNum - 20001;
char excMsg[512]:
size t excMsgLen;
一
If (cmdlnd " OCI_IND_NIJLL)
return;
12
13if (system(cmd) !- 0)
14{
15 sprintffexcMsg, "Error Xi during system call: X,*s". errno. 475,
16strerror(errno));
17excMsgLen - C$-ize_t)strlen(excMsg};
18~
19If (OCrExtProcRalseExcpWithMsgfctx, excNum, (text *)excMsg. excMsgLen)
20 .OCIEXTPROC_SUCCESS)
21return:
22}
23
24 }
Особенности данного кода кратко описаны в следующей таблице.
Строка
Описание
4
6
Первый из формальных параметров — указатель контекста
В знестве номера ошибки можно использовать любое значение из определенного в Oracle диапазона номеров пользовательских ошибок (хотя 8 обгцем случае не рекомендуется кодировать эти значения жестко)
7
8
Максимальная олиш пользовательского сообщения об ошибке равна 512 байт
Переменная для хранения длины текста сообщения об ошибке (ее значение применяется при формировании вызова интерфейса OCI, инициирующего исключение)
•” irib^p・,・jzww«ri・i0 “b!U IIUTDEnn .9 I l|^^i ^»> .. . . — -——-—^ 一
Строка
Описание
10, и
Это уже знакомый нам код обработки входного значения NULL,Если функции передается значение NULL, ничего делать не нужно
13
Когда возвращаемое функцией system。значение равно нулю, это значит, что команда выполнена правильно. Ненулевое значение соответствует ошибке или предупреждению об ошибке. Более сложная программа могла бы обработать разные предупреждения в зависммостм от возвращенного функцией system。 значения
15,16
19, 20
Подготовка переменных, в которых задаются сообщение об ошибке и его длина
Функция интерфейса уровня вызовов инициирует пользовательское исключение; в ней применяется указатель контекста
Сначала откомпилируем данный код для UNIX. Это делается так:
/* Файл в web: bu11d_extprocsh.sh */
gcc -с -1 ${№ACLE_HOME}/rdbrns/demo -I$(ORACLE_HOME)/rdbms/publ1c extprocsh.c gcc -shared -o extprocsh.so extprocsh.o
В Microsoft Windows нужно явно указать файл .def, чтобы идентифицировать точку входа:
/* Файл в web: build_extprocsh.bat */
ECHO LIBRARY extprocsh.dll > extprocsh.def
ECHQ EXPORTS » extprocsh.def
ECHO extprocsh » extprocsh.def
gcc-IXORACLE_HOME?\oc1\include extprocsh.c■
gcc -shared ~o extprocsh.dll extprocsh.def extprocsh.o °
XORACLE_HOMEX\oc1\И b\ms vc\oci.11b
Теперь выполним простое тестирование:
SQL:* CALL she!U'garbage'):
CALL shell('garbage')
ERROR at line 1:
0RA-2DOO1: Error 2 during system call: No such file or directory
Программа работает отлично. Сообщение No such file or directory об отсутствии файла или каталога с заданным именем генерируется стандартной С-фунК- цией strerrorO. Тестирование было выполнено в UNIX, в Microsoft Windows эта фушсция не возвращает ничего полезного, и сообщение об ошибке всегда оДно и то ^е: ORA-20001: Error 0 during system call.
Существуют программы, специально предназначенные для вызова внешних процедур. Их полный список приведен ниже.
Q 口CIExlProcAllocCdllMBmory — выделяет память, которую Oracle автоматически освобождает при возврате управления PL/SQL;
О OClExtProcRaiseExcp — инициирует предопределенное исключение по номеру ошибки Oracle;
Q OCIExtProcRaiseExcpWithMsg - инициирует пользовательское исключение, в том числе пользовательский номер ошибки (как в приведенном выше примере);
О OCIExtProcGetEnv — позволяет внешней процедуре производить обратные вызовы интерфейса уровня вызовов, обращаясь х базе данных для выполнения запросов SQL или кода PL/SQL.
Все эти программы требуют наличия указателя контекста. Подробная информация о них и примеры использования содержатся в документации Oracle ЛррЙ- cation Developer Guide 一 fundomentals.
Агенты, отличные от используемых по умолчанию
В Oracle9i появилась возможность запускать агентов внешних процедур через соединения базы данных с другими серверами. При этом следует иметь в виду, что отдельный процесс запускается даже при отсутствии подключения к другим серверам во время вьшолнения внешней процедуры с помощью агента, отличного от используемого по умолчанию. Подход с применением других серверов может быть удобен в том случае, когда ваша внешняя программа не отличается устойчивостью. Загрузка программы посредством другого агента означает, что аварийное завершение ее процесса extprac никак не отразится на других внешних процедурах, выполняющихся в данйом сеансе.
Ниже приведен пример простой конфигурации, позволяющей агенту выполняться в той же базе данных, но внутри отдельного процесса extproc. Для ее создания нужно внести в файл transnames.ога дополнительную запись:
agent! *
(DESCRIPTION -
(ADDRESS - (PROTOCOL - I PC) (КЕУ*ооч_лроуедуру extproc))
(CONNECT_DATA - (SID - PLSExtProc))" 亠
)
В данном случае параметр ключ_叩оиедурн fxtproc может иметь го же экачениС) что и в записи EXTPROC_CONNECTION_DATA.
Для нового агента нужна новая связь базы данных:
SOL> СЯЕАЯ DATABASE LINK igentlllnk'
CONNECT TO username IDERTIFIED BY password
USING 1agentl
Теперь этот агент может быть задан в инструкции CREATE LIBRARY:
CREATE OR REPLACE LIBRARY extprocshel1_11b_w1th_agent
AS 'cAoracle\admin\local\11b\extprocshTdlI1
AGENT 'agentl1;
В любом вызове, который содержит обращение к создаваемой библиотеке, будут выполняться аутентификация и подключение через соединение агента agentl с порождением отдельного процесса extproc. В данном случае, как и для агента по умолчанию, при вызове процедуры несколькими пользователями Oracle порождает отдельный процесс для каждого из них. Пулинг процесса extprac невозможен.
Oracle предоставляет также возможность передать имя агента внешней процедуре в качестве параметра. Для этого нужно задать предложения AGENT IN в спецификации вызова. Ниже показано} как модифицировать предыдущий пример, чтобы включить в него явное определение агента (изменения выделены жирным шрифтом):
CREATE OR REPLACE PROCEDURE shell2(name of agent IN VARCHAR2, cM IN VARCHAR2}. AS
LANGUAGE C
LI8RARY extprocshelljlb
NAME "ejctprocshZ"-
AGENT IN Cname_of agent)
WITH CONTEXT -"
PARAMETERS (CONTEXT, ngof/gertt STONG. cmd STOMS. cmd INDICATOR):
Обратите внимание, что имя агента указывается в списке параметров. Синтаксис требует, чтобы каждому формальному параметру соответствовал элемент списка PARAMETERS, поэтому нам придется модифицировать и внешнюю библиотеку С. В данном случае добавим в библиотеку вторую точку входа extproc$h2:
void extprocsh(OCIExtProcContext *ctx. char %gent, char *cmd, short cmdlnd)
('
extprocshCctx. cmd. cmdlnd);
}
Этот код просто игнорирует полученное имя агента, зато теперь процедуру shell2 можно вызвать таким образом:
CALL ('agentl1, 'whatever');
Если вам необходимо, чтобы хранимая программа вызывала внешнюю процедуру на удаленной машине, это можно обеспечить одним из трех способов. Во-первых, реализовать на локальной машине внешнюю процедуру-переходник, которая будет выполнять вызов удаленной процедуры средствами С. Во-вторых, реализовать хранимую программу PL/SQL на удаленной машине как внешнюю процедуру и вызывать ее с локальной машины, используя соединение с базой данных. А третья возможность - подключиться прямо к удаленному агенту из локальной хранимой процедуры PL/SQL (эта технология, судя по всему, не поддерживается Oracle официально). Можно настроить параметры листенера внешней процедуры так, чтобы он принимал запросы на установление соединения по сети (то есть по протоколу TCP, а не IPC), но попытки авторов подключиться к удаленному агенту не имели успеха.
ОТЛАДКА ВНЕШНИХ ПРОЦЕДУР
Чтобы помочь разработчикам в отладке внешних процедур, Oracle предоставляет сценарий dbextp.sql, который находится в каталоге plsql/demo. В результате работы данного сценария создается пакет DEBUG.EKTPROC и связанная с ним библиотека debug_extproc_l 1 brary. С помощью этого пакета можно подключить отладчик GNU (gdb 4.18) к выполняющемуся процессу и отлаживать внешние процедуры. Далее рассказано, как авторы произвели эти действия сначала в системе Solaris, а затем — в Windows ХР-
В первую очередь мы откомпилировали файл совместно используемой библиотеки, указав опцию компилятора ( д), необходимую для включения в результирующий файл символьной информации для отладчика, а затем запустили сценарий dbextp.sql и б новом сеансе SQL*Plus выполнили такую команду:
5QL> EXEC OEBUG_EXTPROC.startup_extproc_agent
В результате активизировался процесс extproc, идентификатор (PID) которого был получен с помощью команды ps -ef. После запуска отладчика мы произвели попытку подключиться к выполняющемуся процессу:
gdb $ORACLE__HOME/b1n/extproc p1d
Однако при этом была получена ошибка типа <pennission denied^. Поскольку мы уже имели разрешение на чтение и выполнение программного файла extproc, то решили обойти эту ошибку, загрузившись с учетной записью oracle (похоже, это наилучший способ), а затем задали точку останова в строке pextproc согласно инструкциям, содержащимся б файле dbextp.sql.
Далее, переключившись в SQL*Plus> мы вызвали процедуру:
5QL> CALL shell(NULL);
и сразу после запуска процедуры extproc прервали ее выполнение б точке останова. Для того чтобы отладчик прочитал символы в нашей только что загружена ой библиотеке, была выполнена команда gdb "share", а затем мы смогли установить точку останова во внешней процедуре extprocshO, после чего все работало нормально.
Подобная отладка возможна и в системах Windows, однако проводить ее не так просто. Ниже перечислены основные изменения, которые необходимо сделать б процедуре отладки.
С помощью панели управления Windows нужно модифицировать сервис листенера внешней процедуры, чтобы он выполнялся с правами собственной учетной записи пользователя, а не «Local Systems.
Поскольку отладчик не может найти строку pextproc и команда gdb "share" к Microsoft Windows не применима, в программе С в точке останова требуется добавить строку кода DebugBreak, а затем перекомпилировать программу. Кроме того, придется включить в нее директиву include <windows.h>.
Вместо команды ps -ef для получения PID процесса extproc следует использовать программу Microsoft tasklist.exe.
Для платформы UNIX тесты проведены в операционной системе Solaris 2.6, а для платформы Microsoft — в Windows ХР с использованием отладчика Cygwin GDB 5.0 (имеет и консольную, и GUI-версии).
Сопровождение внешних процедур
В этом разделе приведен ряд сведений, которые могут быть полезны при созда- ниит отладке и сопровождении внешних процедур.
Удаление библиотек
Синтаксис команды, применяемой для удаления библиотеки, прост:
DROP LIBRARY нмя^иблиотекн;
Выполняющий ее пользователь Oracle должен иметь право DROP LIBRARY или DROP ANY LIBRARY.
Перед удалением библиотеки Oracle не проверяет взаимосвязи между объектами. Это удобно, если вам нужно изменить имя или местоположение совместно используемого объектного файла, на который указывает библиотека. В таком случае ее можно просто удалить и создать заново, причем все зависящие от нее программы будут работать по-прежнему. (Пожалуй, было бы неплохо иметь в своем распоряжении команду CROP LIBRARY FORCE, но ее нет.)
Прежде чем полностью удалить библиотеку, желательно запросить информацию из представления DEPENDENCIES, чтобы узнать, не зависят ли от нее какие-нибудь модули PL/SQL.
Словарь данных
В словаре данных появилось несколько новых элементов^ облегчающих управление внешними процедурами. Приведенная ниже табл. 23.2 содержит перечень USER_-BepcHH таблиц словаря, в дополнение к которым имеются соответствующие DBA_- и ALL_-BepcHH.
Таблица 23.2. Представления словаря Данных, предназначенные для работы с внешними процедурами
Требуемая информация
Представление, позволяющее получить ответ
Пример
Какие библиотеки мною созданы?
USERJJBRARIES
SELECT * FROM userjibraries;
В каких программах PL/SQL спецификации вызова содержат ссылку на библиотеку fbo?
USER.DEPENDENCIES
SELECT * FROM user_dependendes WHERE refe reiced_n a me = 'FOO';
Советы по использованию внешних процедур
Применяя внешние процедуры в PL/SQL, важно соблюдать ряд правил и следовать указаниям, приведенным ниже.
О Если PL/SQL требует соблюдения правил использования параметров, заданных режимами IN, IN OUT и OUT, го язык С за этим не следит. Несоблюдение правил использования параметров во время компиляции программой С не выявляется, поэтому вы можете не узнать о нарушении до тех пор, пока не запустите
программу. Правила эти вам известны: не присваивать значений параметрам IN, не читать значений параметров OUT, всегда присваивать значения параметрам IN OUT и OUT, а также обязательно возвращать значение соответствующего типа данных..
О Значения модифицируемых свойств INDICATOR и LENGTH всегда передаются по ссылке, а соответствующие им параметры — в режиме IN OUT, OUT или RETURN. Значения немодифицируемых свойств INDICATOR и LENGTH всегда передаются по значению, если ключевые слова BYREFERENCE не указаны явно. В последнем случае, несмотря на то что свойства INDICATOR и LENGTH передаются по ссылке, они доступны только для чтения.
О Теоретически в процессе.обмена паяными между PL/SQL и С может быть передано до 128 параметров, однако если какой-нибудь из них имеет тип float или double, допустимое количество параметров уменьшается (насколько — зависит от операционной системы),
О Поскольку в будущих версиях extproc может стать многопотоковым процессом, а операционная система не спрашивает разрешения на выгрузку из памяти совместно используемых библиотек, следует избегать использования во внешнем коде внешних статических переменных.
О Внешняя процедура не может выполнять команды DDL, запускать сеанс или управлять транзакциями с помощью инструкций COMMIT и ROLLBACK. (Полный список неподдерживаемых функций OCI вы найдете в документации Oracle PL/SQf. User's Guide and Reference}
Алфавитный указатель
$, элемент форматирования, 250
%BULK_EXCEPTIONS, атрибут явного курсора, 488
%FOUND, атрибут
явного курсора, 486, 506
%ISOPEN, атрибут явного курсора, 488, 506
%NOTFOUND, атрибут. 141
%NOTFOUND, атрибут
явного курсора, 487, 506
%ROWCOUNT, атрибут
явного курсора, 487, 506
%ROWTYPE, атрибут, 141, 183, 185, 336
%TYPE, атрибут, 183
,(запятая), элемент форматирования, 251
,тип данных, 412
.(точка), элемент форматирования, 251
0, элемент форматирования, 250
9, элемент форматирования, 250
А
АМ, элемент формгшфозания даты-времени, 284
ABS, функция, 264
ACOS, функция, 265
AD, элемент форматирования даты-времени, 282
AD* элемент форматирования даты-времени, 282
ADD_MONTHS, функция, 322
AGENT IN, предложение, 903, 914
AGENT, предложение, 901
ALL_SOURCE, представление, 851
ALTER ANY TYPE, привилегия, 852
ALTER SESSION, инструкция, 200
ALTER SESSION, оператор, 281
ALTER SYSTEM, инструкция, 200
AM( элемент форматирования
даты-времени, 284
ANY, ключевое слово, 613
ANYDATA, тип данных, 1S0, 439, 828
ANYDATASET, тип данных, 180, 439, 82g
ANYTYPE, тип данных, 180, 439, 828
AS LANGUAGE С)предложение, 902
набор символов, 197
функция, 215
ASCIISTR, функция, 215-
ASIN, функция, 265
ATAN, функция, 265・
ATAN2, функция, 265
AUTHID CURRENT一USER,
параметр, 778
предложение, 793
AUTHID, предложение, 623
AUTONOMOUS_TRANSACTION, директива компилятора, 106,469
В
В, элемент форматирования, 250
ВС, элемент форматирования даты-времени, 2S2
В.С., элемент форматирования даты-времени, 282
BEGIN, ключевое слово, 90
BETWEEN, оператор, 115
BFILE, тип данных, 178, 411, 422§
BINARYJNTEGER. тип данных, 243. 24
BITAND, функция, 265
BLOB, тип данных, 178, 411
BOOLEAN, тип данных, 178, 403
BULK COLLECT
предложение, 450, 484, 510
BY REFERENCE, предложение, 908
BYTE, спецификатор, 201, 205
38 значащих цифр, а плавающая десятичная запятая необходима для более точного представления присваиваемых переменной значений.
При объявлении переменной типа NUMBER можно задать для ее значения количество значащих цифр и количество цифр после запятой:
NUMBER (儿幻
Такое объявление определяет число с плавающей запятой, где А — общее количество значащих цифр в числе. С этим параметром непосредственно связан объем памяти, выделяемый Oracle для значения переменной типа NUMBER. Параметр В определяет количество цифр справа или слева после десятичной запятой и влияет на результат округления числа. Оба параметра должны быть литеральными целочисленными значениями; ни переменные, ни константы в объявлениях использовать нельзя. Допустимое значение параметра А находится в диапазоне от 1 до 38, а параметра В — от -84 до 127. При объявлении чисел с фиксированной запятой параметр В обычно меньше А. Например, переменную для хранения денежных сумм можно объявить как NUMBER0.2). Способ интерпретации этого объявления показан на рис. 9.1.
S ID_LI ST_EXTPROC_LISTENER =