Oracle Database 12c. Programowanie w języku PL/SQL 9788324699506, 9788324699230, 8324699236, 8324699503

253 45 12MB

Polish Pages 928 Year 2015

Report DMCA / Copyright

DOWNLOAD FILE

Polecaj historie

Oracle Database 12c. Programowanie w języku PL/SQL
 9788324699506, 9788324699230, 8324699236, 8324699503

Table of contents :
Spis treści
O autorze
O współautorze
O redaktorze technicznym
Podziękowania
Wprowadzenie
Zarys książki
Część I: Podstawy języka PL/SQL
Część II: Programowanie w języku PL/SQL
Część III: Dodatki i słowniczek
Leksykon
Leksykon języka SQL
Programy składowane w języku PL/SQL
Inne konwencje
Model danych i kod źródłowy
Część I. Podstawy języka PL/SQL
Rozdział 1. Przegląd programowania w języku Oracle PL/SQL
Tło historyczne języka PL/SQL
Architektura używana przy programowaniu baz Oracle
Baza danych
Język PL/SQL
Architektura przetwarzania instrukcji w Oracle
Model dwuwarstwowy
Model n-warstwowy
Podsumowanie
Test wiedzy
Rozdział 2. Nowe funkcje
Nowe funkcje SQL-a
Tworzenie obiektów LIBRARY za pomocą katalogu wirtualnego
Definiowanie tabel z obsługą wymiaru VT
Wzbogacona składnia instrukcji LEFT OUTER JOIN w bazach Oracle
Domyślne wartości kolumn oparte na sekwencjach
Wartości domyślne przy bezpośrednim wstawianiu null
Kolumny IDENTITY
Większa pojemność typów łańcuchowych i typu RAW
Przekazywanie wartości z instrukcji SQL-a do zewnętrznych programów
Określanie liczby wyników i zwracanych przedziałów wyników w natywnym SQL-u
Sterownik bazy Oracle dla aplikacji dla baz MySQL
Instrukcje CROSS APPLY, OUTER APPLY i LATERAL w SQL-u
Widoki z ustawieniem BEQUEATH CURRENT_USER
Nowe funkcje języka PL/SQL
Zapisywanie wyników funkcji z uprawnieniami jednostki wywołującej
Tworzenie białych list jednostek wywołujących w programach języka PL/SQL
Natywna obsługa klienckich interfejsów API w zakresie typów języka PL/SQL
Nowy pakiet utl_call_stack języka PL/SQL
Nowa procedura expand_sql_text w pakiecie dbms_utility
Nowy formalny schemat procedury parse w pakiecie dbms_sql
Funkcje języka PL/SQL w klauzuli WITH SQL-a
Stosowanie w SQL-u typów danych specyficznych dla języka PL/SQL
Niejawne wiązanie parametrów REF CURSOR
Skrypty pomocnicze
Podsumowanie
Test wiedzy
Rozdział 3. Podstawy języka PL/SQL
Struktura bloków
Sekcja wykonawcza
Podstawowa struktura bloku
Sekcja deklaracji
Sekcja obsługi wyjątków
Działanie zmiennych w blokach
Bloki anonimowe
Zagnieżdżone bloki anonimowe
Lokalne bloki nazwane
Składowane bloki nazwane
Podstawowe skalarne i złożone typy danych
Skalarne typy danych
Kotwiczenie atrybutów i tabel
Złożone typy danych
Struktury sterujące
Struktury warunkowe
Struktury iteracyjne
Wyjątki
Wyjątki zdefiniowane przez użytkownika
Dynamiczne wyjątki zdefiniowane przez użytkownika
Operacje masowe
Funkcje, procedury i pakiety
Funkcje
Procedury
Pakiety
Zasięg transakcji
Pojedynczy zasięg transakcji
Wiele zasięgów transakcji
Wyzwalacze bazodanowe
Podsumowanie
Test wiedzy
Rozdział 4. Podstawowe elementy języka
Jednostki leksykalne
Ograniczniki
Identyfikatory
Literały
Komentarze
Zmienne i typy danych
Typy zmiennych
Skalarne typy danych
Duże obiekty (typy LOB)
Złożone typy danych
Systemowe kursory referencyjne
Podsumowanie
Test wiedzy
Rozdział 5. Struktury sterujące
Instrukcje warunkowe
Instrukcje IF
Instrukcje CASE
Instrukcje kompilacji warunkowej
Instrukcje iteracyjne
Pętle proste
Pętle FOR
Pętle WHILE
Kursory
Kursory niejawne
Kursory jawne
Instrukcje masowe
Instrukcje BULK COLLECT INTO
Instrukcje FORALL
Pomocnicze skrypty
Podsumowanie
Test wiedzy
Rozdział 6. Kolekcje
Wprowadzenie do kolekcji
Typy obiektowe: tablice VARRAY i tabele zagnieżdżone
Tablice VARRAY
Tabele zagnieżdżone
Tablice asocjacyjne
Definiowanie i stosowanie tablic asocjacyjnych
API Collection
Metoda COUNT
Metoda DELETE
Metoda EXISTS
Metoda EXTEND
Metoda FIRST
Metoda LAST
Metoda LIMIT
Metoda NEXT
Metoda PRIOR
Metoda TRIM
Pomocnicze skrypty
Podsumowanie
Test wiedzy
Rozdział 7. Obsługa błędów
Typy i zasięg wyjątków
Błędy kompilacji
Błędy czasu wykonania
Wbudowane funkcje do zarządzania wyjątkami
Wyjątki zdefiniowane przez użytkownika
Deklarowanie wyjątków zdefiniowanych przez użytkownika
Dynamiczne wyjątki zdefiniowane przez użytkownika
Funkcje do zarządzania stosem błędów
Pomocnicze skrypty
Podsumowanie
Test wiedzy
Część II. Programowanie w języku PL/SQL
Rozdział 8. Funkcje i procedury
Architektura funkcji i procedur
Zasięg transakcji
Wywoływanie podprogramów
Notacja oparta na pozycji
Notacja oparta na nazwie
Notacja mieszana
Notacja z pominięciem
Notacja w wywołaniach w języku SQL
Funkcje
Wybór rodzaju funkcji
Opcje używane przy tworzeniu funkcji
Funkcje o parametrach przekazywanych przez wartość
Funkcje o parametrach przekazywanych przez referencję
Procedury
Procedury o parametrach przekazywanych przez wartość
Procedury o parametrach przekazywanych przez referencję
Pomocnicze skrypty
Podsumowanie
Test wiedzy
Rozdział 9. Pakiety
Architektura pakietu
Specyfikacja pakietu
Elementy prototypu
Dyrektywa prekompilatora SERIALLY_REUSABLE
Zmienne
Typy danych
Komponenty — funkcje i procedury
Ciało pakietu
Elementy prototypu
Zmienne
Typy
Komponenty — funkcje i procedury
Uprawnienia jednostki definiującej i jednostki wywołującej
Zarządzanie pakietami w katalogu bazy danych
Wyszukiwanie, walidacja i opisywanie pakietów
Sprawdzanie zależności
Metody sprawdzania poprawności — znaczniki czasu i sygnatury
Podsumowanie
Test wiedzy
Rozdział 10. Duże obiekty
Praca z wewnętrznie składowanymi dużymi obiektami
Przypisywanie do dużych obiektów danych o wielkości poniżej 32 kilobajtów
Przypisywanie do dużych obiektów danych o wielkości powyżej 32 kilobajtów
Wczytywanie plików do wewnętrznie przechowywanych kolumn
Wczytywanie lokalnych plików do kolumn typu CLOB lub NCLOB
Wczytywanie plików lokalnych do kolumn typu BLOB
Używanie dużych obiektów za pomocą stron WWW
Praca z plikami binarnymi (typ BFILE)
Tworzenie i używanie katalogów wirtualnych
Wczytywanie ścieżek kanonicznych i nazw plików
Pakiet DBMS_LOB
Stałe pakietu
Wyjątki pakietu
Metody do otwierania i zamykania
Metody do manipulowania dużymi obiektami
Metody do introspekcji
Metody do obsługi obiektów typu BFILE
Metody do obsługi tymczasowych dużych obiektów
Metody do obsługi bezpiecznych odnośników
Skrypty pomocnicze
Skrypt przekształcający dane typu LONG na wartości typu CLOB
Zarządzanie dużymi obiektami w systemie plików
Zarządzanie obiektami typów CLOB i BLOB z poziomu stron WWW
Zarządzanie obiektami typu BFILE z poziomu stron WWW
Podsumowanie
Test wiedzy
Rozdział 11. Typy obiektowe
Wprowadzenie do obiektów
Deklarowanie typów obiektowych
Implementacja ciała typów obiektowych
Tworzenie białych list dla typów obiektowych
Gettery i settery
Statyczne metody składowe
Porównywanie obiektów
Dziedziczenie i polimorfizm
Deklarowanie klas pochodnych
Implementowanie klas pochodnych
Ewolucja typu
Kolekcje obiektów
Deklarowanie kolekcji obiektów
Implementowanie kolekcji obiektów
Skrypty pomocnicze
Podsumowanie
Test wiedzy
Rozdział 12. Wyzwalacze
Wprowadzenie do wyzwalaczy
Architektura wyzwalaczy w bazie danych
Wyzwalacze DDL
Funkcje-atrybuty zdarzeń
Tworzenie wyzwalaczy DDL
Wyzwalacze DML
Wyzwalacze z poziomu instrukcji
Wyzwalacze z poziomu wierszy
Wyzwalacze złożone
Wyzwalacze zastępujące
Wyzwalacze systemowe (bazy danych)
Ograniczenia związane z wyzwalaczami
Maksymalny rozmiar wyzwalaczy
Instrukcje języka SQL
Typy danych LONG i LONG RAW
Tabele mutujące
Wyzwalacze systemowe
Skrypty pomocnicze
Podsumowanie
Test wiedzy
Rozdział 13. Dynamiczny SQL
Architektura dynamicznego SQL-a
Wbudowany dynamiczny język SQL (NDS)
Instrukcje dynamiczne
Instrukcje dynamiczne z danymi wejściowymi
Instrukcje dynamiczne z danymi wejściowymi i wyjściowymi
Instrukcje dynamiczne o nieznanej liczbie danych wejściowych
Pakiet DBMS_SQL
Instrukcje dynamiczne
Instrukcje dynamiczne o zmiennych wejściowych
Instrukcje dynamiczne o zmiennej liczbie danych wejściowych i stałej liczbie danych wyjściowych
Dynamiczne instrukcje o zmiennej liczbie danych wejściowych i wyjściowych
Definicja pakietu DBMS_SQL
Skrypty pomocnicze
Podsumowanie
Test wiedzy
Część III. Dodatki i słowniczek
Dodatek A. Wprowadzenie do bazy Oracle
Architektura bazy danych Oracle
Uruchamianie i zatrzymywanie serwera bazy Oracle Database 12c
Operacje w systemach Unix i Linux
Operacje w systemie Microsoft Windows
Uruchamianie i zatrzymywanie odbiornika Oracle
Architektura MVCC
Transakcje na danych
Kontrola blokowania i izolacji w instrukcjach DML
Uprawnienia jednostki definiującej i uprawnienia jednostki wywołującej
Uprawnienia jednostki definiującej
Uprawnienia jednostki wywołującej
Interaktywne i wsadowe przetwarzanie instrukcji SQL-a
Interfejs SQL*Plus uruchamiany z wiersza poleceń
Narzędzie Oracle SQL Developer
Administrowanie bazą danych
Dodawanie kont użytkowników
Stosowanie ograniczeń w bazach danych
Wzmacnianie zabezpieczeń
Zarządzanie danymi
Dostrajanie SQL-a
Instrukcja EXPLAIN PLAN
Pakiet DBMS_XPLAN
Śledzenie instrukcji SQL-a
Instrukcje związane z sesją śledzenia
Przekształcanie nieprzetworzonych plików śladu na czytelny format
Podsumowanie
Dodatek B. Wprowadzenie do języka SQL
Typy danych środowiska SQL*Plus w Oracle
Język definicji danych (DDL)
Instrukcja CREATE
Instrukcja ALTER
Instrukcja RENAME
Instrukcja DROP
Instrukcja TRUNCATE
Instrukcja COMMENT
Instrukcje DML
Transakcje zgodne z modelem ACID
Instrukcja INSERT
Instrukcja UPDATE
Instrukcja DELETE
Instrukcja MERGE
Język kontroli transakcji (TCL)
Zapytania — instrukcje SELECT
Zapytania zwracające kolumny lub wyniki z kolumn
Zapytania agregujące
Zapytania selektywnie zwracające kolumny lub wyniki
Wyniki złączeń
Złączenia dotyczące wierszy
Złączenia łączące kolekcje
Podsumowanie
Dodatek C. Funkcje wbudowane języka SQL
Funkcje znakowe
Funkcja ASCII
Funkcja ASCIISTR
Funkcja CHR
Funkcja CONCAT
Funkcja INITCAP
Funkcja INSTR
Funkcja LENGTH
Funkcja LOWER
Funkcja LPAD
Funkcja LTRIM
Funkcja REPLACE
Funkcja REVERSE
Funkcja RPAD
Funkcja RTRIM
Funkcja UPPER
Funkcje do konwersji typów danych
Funkcja CAST
Funkcja CONVERT
Funkcja TO_CHAR
Funkcja TO_CLOB
Funkcja TO_DATE
Funkcja TO_LOB
Funkcja TO_NCHAR
Funkcja TO_NCLOB
Funkcja TO_NUMBER
Funkcje do konwersji dat i czasu
Funkcja ADD_MONTHS
Funkcja CURRENT_DATE
Funkcja CURRENT_TIMESTAMP
Funkcja DBTIMEZONE
Funkcja EXTRACT
Funkcja FROM_TZ
Funkcja LAST_DAY
Funkcja LOCALTIMESTAMP
Funkcja MONTHS_BETWEEN
Funkcja NEW_TIME
Funkcja ROUND
Funkcja SYSDATE
Funkcja SYSTIMESTAMP
Funkcja TO_CHAR(data)
Funkcja TO_DSINTERVAL
Funkcja TO_TIMESTAMP
Funkcja TO_TIMESTAMP_TZ
Funkcja TO_YMINTERVAL
Funkcja TRUNC(data)
Funkcja TZ_OFFSET
Funkcje do zarządzania kolekcjami
Funkcja CARDINALITY
Funkcja COLLECT
Funkcja POWERMULTISET
Funkcja POWERMULTISET_BY_CARDINALITY
Funkcja SET
Operatory zbiorów działające dla kolekcji
Operator CARDINALITY
Operator EMPTY
Operator MULTISET
Operator MULTISET EXCEPT
Operator MULTISET INTERSECT
Operator MULTISET UNION
Operator SET
Operator SUBMULTISET OF
Funkcje liczbowe
Funkcja CEIL
Funkcja FLOOR
Funkcja MOD
Funkcja POWER
Funkcja REMAINDER
Funkcja ROUND
Funkcje do zarządzania błędami
Funkcja SQLCODE
Funkcja SQLERRM
Funkcje różne
Funkcja BFILENAME
Funkcja COALESCE
Funkcja DECODE
Funkcja DUMP
Funkcja EMPTY_BLOB
Funkcja EMPTY_CLOB
Funkcja GREATEST
Funkcja LEAST
Funkcja NANVL
Funkcja NULLIF
Funkcja NVL
Funkcja SYS_CONTEXT
Funkcja TABLE
Funkcja TREAT
Funkcja USERENV
Funkcja VSIZE
Podsumowanie
Dodatek D. Wbudowane pakiety i typy języka PL/SQL
Nowe pakiety w wersjach Oracle Database 11g i Oracle Database 12c
Przykłady zastosowania pakietów
Przykład ilustrujący pakiet DBMS_APPLICATION_INFO
DBMS_COMPARISON
DBMS_CRYPTO
DBMS_FGA
Przypadek użycia — narzędzie do analizy zapytań
Podsumowanie
Dodatek E. Wprowadzenie do wyrażeń regularnych
Wprowadzenie do wyrażeń regularnych
Klasy znaków
Klasy porządkowania
Metaznaki
Metasekwencje
Literały
Stosowanie wyrażeń regularnych
Funkcja REGEXP_COUNT
Funkcja REGEXP_INSTR
Funkcja REGEXP_LIKE
Funkcja REGEXP_REPLACE
Funkcja REGEXP_SUBSTR
Podsumowanie
Dodatek F. Wprowadzenie do opakowywania kodu w języku PL/SQL
Ograniczenia w opakowywaniu kodu w języku PL/SQL
Ograniczenia związane z narzędziem wrap języka PL/SQL
Ograniczenia funkcji DBMS_DDL.WRAP
Stosowanie narzędzia wrap
Opakowywanie kodu za pomocą pakietu DBMS_DDL
Funkcja WRAP
Procedura CREATE_WRAPPED
Podsumowanie
Dodatek G. Wprowadzenie do hierarchicznego programu profilującego języka PL/SQL
Konfigurowanie schematu
Zbieranie danych
Odczytywanie danych wyjściowych programu profilującego
Odczyt surowych danych wyjściowych
Definiowanie tabel na potrzeby programu profilującego języka PL/SQL
Zapytania o przetworzone dane
Używanie narzędzia plshprof
Podsumowanie
Dodatek H. Słowa zarezerwowane i kluczowe języka PL/SQL
Podsumowanie
Dodatek I. Odpowiedzi do testów wiedzy
Rozdział 1.
Rozdział 2.
Rozdział 3.
Rozdział 4.
Rozdział 5.
Rozdział 6.
Rozdział 7.
Rozdział 8.
Rozdział 9.
Rozdział 10.
Rozdział 11.
Rozdział 12.
Rozdział 13.
Słowniczek
Skorowidz

Citation preview

Tytuł oryginału: Oracle Database 12c PL/SQL Programming Tłumaczenie: Tomasz Walczak ISBN: 978-83-246-9950-6 Original edition copyright © 2014 by McGraw-Hill Education (Publisher). All rights reserved. Polish edition copyright © 2015 by HELION S.A. All rights reserved. All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from the Publisher. Wszelkie prawa zastrzeżone. Nieautoryzowane rozpowszechnianie całości lub fragmentu niniejszej publikacji w jakiejkolwiek postaci jest zabronione. Wykonywanie kopii metodą kserograficzną, fotograficzną, a także kopiowanie książki na nośniku filmowym, magnetycznym lub innym powoduje naruszenie praw autorskich niniejszej publikacji. Wszystkie znaki występujące w tekście są zastrzeżonymi znakami firmowymi bądź towarowymi ich właścicieli. Autor oraz Wydawnictwo HELION dołożyli wszelkich starań, by zawarte w tej książce informacje były kompletne i rzetelne. Nie biorą jednak żadnej odpowiedzialności ani za ich wykorzystanie, ani za związane z tym ewentualne naruszenie praw patentowych lub autorskich. Autor oraz Wydawnictwo HELION nie ponoszą również żadnej odpowiedzialności za ewentualne szkody wynikłe z wykorzystania informacji zawartych w książce. Wydawnictwo HELION ul. Kościuszki 1c, 44-100 GLIWICE tel. 32 231 22 19, 32 230 98 63 e-mail: [email protected] WWW: http://helion.pl (księgarnia internetowa, katalog książek) Drogi Czytelniku! Jeżeli chcesz ocenić tę książkę, zajrzyj pod adres http://helion.pl/user/opinie/or12ps_ebook Możesz tam wpisać swoje uwagi, spostrzeżenia, recenzję. Pliki z przykładami omawianymi w książce można znaleźć pod adresem: ftp://ftp.helion.pl/przyklady/or12ps.zip   

Poleć książkę na Facebook.com Kup w wersji papierowej Oceń książkę

 

Księgarnia internetowa Lubię to! » Nasza społeczność

Dla Lizy, mojej odwiecznej towarzyszki, inspiracji, żony i najlepszej przyjaciółki, oraz dla naszych wspaniałych, zesłanych przez niebo dzieci — Sary, Josepha, Elizy, Iana, Ariel, Callie, Nathana, Spencera i Christianne. Dziękuję Wam za nieustanne wsparcie, cierpliwość i poświęcenie, które umożliwiły mi napisanie jeszcze jednej książki.

Spis treści O autorze ....................................................................................................................... 15 Podziękowania ............................................................................................................... 17 Wprowadzenie ............................................................................................................... 19 CZĘŚĆ I

Podstawy języka PL/SQL 1

Przegląd programowania w języku Oracle PL/SQL ....................................................... 33 Tło historyczne języka PL/SQL .................................................................................................33 Architektura używana przy programowaniu baz Oracle ...........................................................35 Baza danych ...................................................................................................................36 Język PL/SQL ...................................................................................................................37 Architektura przetwarzania instrukcji w Oracle ................................................................40 Model dwuwarstwowy ....................................................................................................41 Model n-warstwowy ........................................................................................................41 Podsumowanie .......................................................................................................................43 Test wiedzy .............................................................................................................................43

2

Nowe funkcje ................................................................................................................. 45 Nowe funkcje SQL-a ...............................................................................................................45 Tworzenie obiektów LIBRARY za pomocą katalogu wirtualnego ......................................46 Definiowanie tabel z obsługą wymiaru VT .......................................................................46 Wzbogacona składnia instrukcji LEFT OUTER JOIN w bazach Oracle ..............................47 Domyślne wartości kolumn oparte na sekwencjach .........................................................47 Wartości domyślne przy bezpośrednim wstawianiu null ..................................................49 Kolumny IDENTITY .........................................................................................................49 Większa pojemność typów łańcuchowych i typu RAW ....................................................50 Przekazywanie wartości z instrukcji SQL-a do zewnętrznych programów .........................50 Określanie liczby wyników i zwracanych przedziałów wyników w natywnym SQL-u .......52 Sterownik bazy Oracle dla aplikacji dla baz MySQL ........................................................55 Instrukcje CROSS APPLY, OUTER APPLY i LATERAL w SQL-u .........................................55 Widoki z ustawieniem BEQUEATH CURRENT_USER ......................................................56 Nowe funkcje języka PL/SQL ..................................................................................................57 Zapisywanie wyników funkcji z uprawnieniami jednostki wywołującej ............................57 Tworzenie białych list jednostek wywołujących w programach języka PL/SQL .................57 Natywna obsługa klienckich interfejsów API w zakresie typów języka PL/SQL .................58 Nowy pakiet utl_call_stack języka PL/SQL .......................................................................59 Nowa procedura expand_sql_text w pakiecie dbms_utility ..............................................59 Nowy formalny schemat procedury parse w pakiecie dbms_sql .......................................60 Funkcje języka PL/SQL w klauzuli WITH SQL-a ..............................................................60

6

Programowanie w języku PL/SQL Stosowanie w SQL-u typów danych specyficznych dla języka PL/SQL ............................. 61 Niejawne wiązanie parametrów REF CURSOR ................................................................ 63 Skrypty pomocnicze ............................................................................................................... 64 Podsumowanie ....................................................................................................................... 64 Test wiedzy ............................................................................................................................. 64

3

Podstawy języka PL/SQL ................................................................................................. 67 Struktura bloków .................................................................................................................... 67 Sekcja wykonawcza ........................................................................................................ 68 Podstawowa struktura bloku ........................................................................................... 68 Sekcja deklaracji ............................................................................................................. 71 Sekcja obsługi wyjątków ................................................................................................. 71 Działanie zmiennych w blokach .............................................................................................. 72 Bloki anonimowe ............................................................................................................ 72 Zagnieżdżone bloki anonimowe ..................................................................................... 76 Lokalne bloki nazwane ................................................................................................... 78 Składowane bloki nazwane ............................................................................................. 80 Podstawowe skalarne i złożone typy danych ........................................................................... 82 Skalarne typy danych ...................................................................................................... 82 Kotwiczenie atrybutów i tabel ......................................................................................... 84 Złożone typy danych ...................................................................................................... 86 Struktury sterujące .................................................................................................................. 97 Struktury warunkowe ...................................................................................................... 97 Struktury iteracyjne ......................................................................................................... 99 Wyjątki ................................................................................................................................. 106 Wyjątki zdefiniowane przez użytkownika ..................................................................... 107 Dynamiczne wyjątki zdefiniowane przez użytkownika .................................................. 108 Operacje masowe ................................................................................................................. 108 Funkcje, procedury i pakiety ................................................................................................. 110 Funkcje ......................................................................................................................... 110 Procedury ..................................................................................................................... 112 Pakiety ......................................................................................................................... 113 Zasięg transakcji .................................................................................................................... 117 Pojedynczy zasięg transakcji ......................................................................................... 117 Wiele zasięgów transakcji ............................................................................................. 118 Wyzwalacze bazodanowe ..................................................................................................... 119 Podsumowanie ..................................................................................................................... 120 Test wiedzy ........................................................................................................................... 120

4

Podstawowe elementy języka ....................................................................................... 123 Jednostki leksykalne .............................................................................................................. 123 Ograniczniki ................................................................................................................. 123 Identyfikatory ............................................................................................................... 127 Literały ......................................................................................................................... 129 Komentarze .................................................................................................................. 130 Zmienne i typy danych ......................................................................................................... 131 Typy zmiennych ........................................................................................................... 132 Skalarne typy danych .................................................................................................... 134 Duże obiekty (typy LOB) .............................................................................................. 147 Złożone typy danych .................................................................................................... 149 Systemowe kursory referencyjne ................................................................................... 152 Podsumowanie ..................................................................................................................... 154 Test wiedzy ........................................................................................................................... 154

Spis treści

5

7

Struktury sterujące ....................................................................................................... 157 Instrukcje warunkowe ...........................................................................................................157 Instrukcje IF ..................................................................................................................163 Instrukcje CASE .............................................................................................................166 Instrukcje kompilacji warunkowej .................................................................................168 Instrukcje iteracyjne ..............................................................................................................171 Pętle proste ...................................................................................................................171 Pętle FOR .....................................................................................................................177 Pętle WHILE .................................................................................................................178 Kursory .................................................................................................................................181 Kursory niejawne ..........................................................................................................182 Kursory jawne ...............................................................................................................186 Instrukcje masowe .................................................................................................................196 Instrukcje BULK COLLECT INTO ..................................................................................196 Instrukcje FORALL ........................................................................................................200 Pomocnicze skrypty ..............................................................................................................204 Podsumowanie .....................................................................................................................204 Test wiedzy ...........................................................................................................................205

6

Kolekcje ........................................................................................................................ 207 Wprowadzenie do kolekcji ....................................................................................................207 Typy obiektowe: tablice VARRAY i tabele zagnieżdżone ........................................................210 Tablice VARRAY ............................................................................................................210 Tabele zagnieżdżone .....................................................................................................213 Tablice asocjacyjne ...............................................................................................................225 Definiowanie i stosowanie tablic asocjacyjnych .............................................................226 API Collection .......................................................................................................................231 Metoda COUNT ...........................................................................................................233 Metoda DELETE ............................................................................................................234 Metoda EXISTS ..............................................................................................................235 Metoda EXTEND ...........................................................................................................236 Metoda FIRST ...............................................................................................................236 Metoda LAST ................................................................................................................237 Metoda LIMIT ...............................................................................................................237 Metoda NEXT ...............................................................................................................238 Metoda PRIOR ..............................................................................................................238 Metoda TRIM ................................................................................................................239 Pomocnicze skrypty ..............................................................................................................240 Podsumowanie .....................................................................................................................240 Test wiedzy ...........................................................................................................................240

7

Obsługa błędów ........................................................................................................... 243 Typy i zasięg wyjątków ..........................................................................................................243 Błędy kompilacji ...........................................................................................................245 Błędy czasu wykonania .................................................................................................247 Wbudowane funkcje do zarządzania wyjątkami ....................................................................252 Wyjątki zdefiniowane przez użytkownika ..............................................................................253 Deklarowanie wyjątków zdefiniowanych przez użytkownika .........................................253 Dynamiczne wyjątki zdefiniowane przez użytkownika ..................................................256 Funkcje do zarządzania stosem błędów .................................................................................258 Pomocnicze skrypty ..............................................................................................................263 Podsumowanie .....................................................................................................................263 Test wiedzy ...........................................................................................................................263

8

Programowanie w języku PL/SQL

CZĘŚĆ II

Programowanie w języku PL/SQL 8

Funkcje i procedury ...................................................................................................... 267 Architektura funkcji i procedur .............................................................................................. 268 Zasięg transakcji .................................................................................................................... 273 Wywoływanie podprogramów ...................................................................................... 275 Notacja oparta na pozycji ............................................................................................. 275 Notacja oparta na nazwie ............................................................................................. 276 Notacja mieszana ......................................................................................................... 276 Notacja z pominięciem ................................................................................................. 276 Notacja w wywołaniach w języku SQL .......................................................................... 276 Funkcje ................................................................................................................................. 277 Wybór rodzaju funkcji .................................................................................................. 278 Opcje używane przy tworzeniu funkcji ......................................................................... 279 Funkcje o parametrach przekazywanych przez wartość ................................................. 290 Funkcje o parametrach przekazywanych przez referencję ............................................. 301 Procedury ............................................................................................................................. 303 Procedury o parametrach przekazywanych przez wartość ............................................. 304 Procedury o parametrach przekazywanych przez referencję ......................................... 307 Pomocnicze skrypty .............................................................................................................. 309 Podsumowanie ..................................................................................................................... 309 Test wiedzy ........................................................................................................................... 309

9

Pakiety .......................................................................................................................... 311 Architektura pakietu ............................................................................................................. 311 Specyfikacja pakietu ............................................................................................................. 316 Elementy prototypu ...................................................................................................... 317 Dyrektywa prekompilatora SERIALLY_REUSABLE .......................................................... 319 Zmienne ....................................................................................................................... 320 Typy danych ................................................................................................................. 322 Komponenty — funkcje i procedury ............................................................................. 324 Ciało pakietu ........................................................................................................................ 325 Elementy prototypu ...................................................................................................... 325 Zmienne ....................................................................................................................... 327 Typy ............................................................................................................................. 329 Komponenty — funkcje i procedury ............................................................................. 330 Uprawnienia jednostki definiującej i jednostki wywołującej .................................................. 332 Zarządzanie pakietami w katalogu bazy danych .................................................................... 335 Wyszukiwanie, walidacja i opisywanie pakietów ........................................................... 336 Sprawdzanie zależności ................................................................................................ 337 Metody sprawdzania poprawności — znaczniki czasu i sygnatury ................................. 338 Podsumowanie ..................................................................................................................... 339 Test wiedzy ........................................................................................................................... 339

10 Duże obiekty ................................................................................................................ 341 Praca z wewnętrznie składowanymi dużymi obiektami ......................................................... 342 Przypisywanie do dużych obiektów danych o wielkości poniżej 32 kilobajtów .............. 342 Przypisywanie do dużych obiektów danych o wielkości powyżej 32 kilobajtów ............ 344 Wczytywanie plików do wewnętrznie przechowywanych kolumn ......................................... 352 Wczytywanie lokalnych plików do kolumn typu CLOB lub NCLOB .............................. 352 Wczytywanie plików lokalnych do kolumn typu BLOB ................................................. 355 Używanie dużych obiektów za pomocą stron WWW .................................................... 357

Spis treści

9

Praca z plikami binarnymi (typ BFILE) ....................................................................................363 Tworzenie i używanie katalogów wirtualnych ................................................................363 Wczytywanie ścieżek kanonicznych i nazw plików ........................................................368 Pakiet DBMS_LOB ................................................................................................................374 Stałe pakietu .................................................................................................................375 Wyjątki pakietu .............................................................................................................376 Metody do otwierania i zamykania ................................................................................376 Metody do manipulowania dużymi obiektami ...............................................................377 Metody do introspekcji .................................................................................................382 Metody do obsługi obiektów typu BFILE ........................................................................385 Metody do obsługi tymczasowych dużych obiektów ......................................................387 Metody do obsługi bezpiecznych odnośników ..............................................................388 Skrypty pomocnicze ..............................................................................................................390 Skrypt przekształcający dane typu LONG na wartości typu CLOB .................................390 Zarządzanie dużymi obiektami w systemie plików .........................................................391 Zarządzanie obiektami typów CLOB i BLOB z poziomu stron WWW ...........................391 Zarządzanie obiektami typu BFILE z poziomu stron WWW ...........................................391 Podsumowanie .....................................................................................................................391 Test wiedzy ...........................................................................................................................391

11 Typy obiektowe ............................................................................................................ 393 Wprowadzenie do obiektów .................................................................................................395 Deklarowanie typów obiektowych ................................................................................396 Implementacja ciała typów obiektowych .......................................................................398 Tworzenie białych list dla typów obiektowych ...............................................................402 Gettery i settery .............................................................................................................403 Statyczne metody składowe ..........................................................................................405 Porównywanie obiektów ...............................................................................................406 Dziedziczenie i polimorfizm ..................................................................................................412 Deklarowanie klas pochodnych .....................................................................................414 Implementowanie klas pochodnych ..............................................................................415 Ewolucja typu ...............................................................................................................418 Kolekcje obiektów .................................................................................................................419 Deklarowanie kolekcji obiektów ...................................................................................419 Implementowanie kolekcji obiektów .............................................................................420 Skrypty pomocnicze ..............................................................................................................422 Podsumowanie .....................................................................................................................423 Test wiedzy ...........................................................................................................................423

12 Wyzwalacze .................................................................................................................. 425 Wprowadzenie do wyzwalaczy .............................................................................................425 Architektura wyzwalaczy w bazie danych ..............................................................................428 Wyzwalacze DDL .................................................................................................................431 Funkcje-atrybuty zdarzeń ..............................................................................................433 Tworzenie wyzwalaczy DDL .........................................................................................442 Wyzwalacze DML .................................................................................................................444 Wyzwalacze z poziomu instrukcji ..................................................................................445 Wyzwalacze z poziomu wierszy ....................................................................................447 Wyzwalacze złożone .............................................................................................................453 Wyzwalacze zastępujące .......................................................................................................457 Wyzwalacze systemowe (bazy danych) ..................................................................................461 Ograniczenia związane z wyzwalaczami ................................................................................462 Maksymalny rozmiar wyzwalaczy ..................................................................................462 Instrukcje języka SQL ....................................................................................................463

10

Programowanie w języku PL/SQL Typy danych LONG i LONG RAW ................................................................................ 463 Tabele mutujące ........................................................................................................... 463 Wyzwalacze systemowe ................................................................................................ 464 Skrypty pomocnicze ............................................................................................................. 465 Podsumowanie ..................................................................................................................... 465 Test wiedzy ........................................................................................................................... 465

13 Dynamiczny SQL .......................................................................................................... 467 Architektura dynamicznego SQL-a ........................................................................................ 468 Wbudowany dynamiczny język SQL (NDS) ........................................................................... 468 Instrukcje dynamiczne .................................................................................................. 469 Instrukcje dynamiczne z danymi wejściowymi .............................................................. 471 Instrukcje dynamiczne z danymi wejściowymi i wyjściowymi ........................................ 473 Instrukcje dynamiczne o nieznanej liczbie danych wejściowych .................................... 477 Pakiet DBMS_SQL ................................................................................................................ 478 Instrukcje dynamiczne .................................................................................................. 479 Instrukcje dynamiczne o zmiennych wejściowych ......................................................... 482 Instrukcje dynamiczne o zmiennej liczbie danych wejściowych i stałej liczbie danych wyjściowych .................................................................................................. 484 Dynamiczne instrukcje o zmiennej liczbie danych wejściowych i wyjściowych .............. 487 Definicja pakietu DBMS_SQL ....................................................................................... 492 Skrypty pomocnicze ............................................................................................................. 503 Podsumowanie ..................................................................................................................... 503 Test wiedzy ........................................................................................................................... 503

CZĘŚĆ III

Dodatki i słowniczek A

Wprowadzenie do bazy Oracle .................................................................................... 507 Architektura bazy danych Oracle .......................................................................................... 508 Uruchamianie i zatrzymywanie serwera bazy Oracle Database 12c ....................................... 513 Operacje w systemach Unix i Linux .............................................................................. 514 Operacje w systemie Microsoft Windows ...................................................................... 518 Uruchamianie i zatrzymywanie odbiornika Oracle ................................................................ 519 Architektura MVCC ............................................................................................................... 523 Transakcje na danych ................................................................................................... 524 Kontrola blokowania i izolacji w instrukcjach DML ....................................................... 527 Uprawnienia jednostki definiującej i uprawnienia jednostki wywołującej .............................. 528 Uprawnienia jednostki definiującej ............................................................................... 528 Uprawnienia jednostki wywołującej .............................................................................. 529 Interaktywne i wsadowe przetwarzanie instrukcji SQL-a ....................................................... 530 Interfejs SQL*Plus uruchamiany z wiersza poleceń ....................................................... 530 Narzędzie Oracle SQL Developer ................................................................................. 547 Administrowanie bazą danych .............................................................................................. 553 Dodawanie kont użytkowników .................................................................................... 553 Stosowanie ograniczeń w bazach danych ...................................................................... 560 Wzmacnianie zabezpieczeń .......................................................................................... 569 Zarządzanie danymi ..................................................................................................... 577 Dostrajanie SQL-a ................................................................................................................. 580 Instrukcja EXPLAIN PLAN ............................................................................................. 581 Pakiet DBMS_XPLAN .................................................................................................... 582

Spis treści

11

Śledzenie instrukcji SQL-a .....................................................................................................585 Instrukcje związane z sesją śledzenia .............................................................................586 Przekształcanie nieprzetworzonych plików śladu na czytelny format ..............................588 Podsumowanie .....................................................................................................................589

B

Wprowadzenie do języka SQL ..................................................................................... 591 Typy danych środowiska SQL*Plus w Oracle .........................................................................594 Język definicji danych (DDL) .................................................................................................597 Instrukcja CREATE .........................................................................................................598 Instrukcja ALTER ...........................................................................................................654 Instrukcja RENAME .......................................................................................................669 Instrukcja DROP ...........................................................................................................670 Instrukcja TRUNCATE ...................................................................................................672 Instrukcja COMMENT ...................................................................................................672 Instrukcje DML .....................................................................................................................672 Transakcje zgodne z modelem ACID .............................................................................673 Instrukcja INSERT ..........................................................................................................676 Instrukcja UPDATE ........................................................................................................688 Instrukcja DELETE .........................................................................................................699 Instrukcja MERGE ..........................................................................................................703 Język kontroli transakcji (TCL) ................................................................................................709 Zapytania — instrukcje SELECT .............................................................................................711 Zapytania zwracające kolumny lub wyniki z kolumn .....................................................712 Zapytania agregujące ....................................................................................................725 Zapytania selektywnie zwracające kolumny lub wyniki ..................................................729 Wyniki złączeń ......................................................................................................................736 Złączenia dotyczące wierszy .........................................................................................738 Złączenia łączące kolekcje ............................................................................................746 Podsumowanie .....................................................................................................................749

C

Funkcje wbudowane języka SQL ................................................................................. 751 Funkcje znakowe ..................................................................................................................751 Funkcja ASCII ................................................................................................................751 Funkcja ASCIISTR ..........................................................................................................752 Funkcja CHR .................................................................................................................752 Funkcja CONCAT .........................................................................................................753 Funkcja INITCAP ...........................................................................................................753 Funkcja INSTR ..............................................................................................................753 Funkcja LENGTH ..........................................................................................................754 Funkcja LOWER ............................................................................................................754 Funkcja LPAD ...............................................................................................................755 Funkcja LTRIM ..............................................................................................................755 Funkcja REPLACE ..........................................................................................................756 Funkcja REVERSE ..........................................................................................................756 Funkcja RPAD ...............................................................................................................756 Funkcja RTRIM .............................................................................................................757 Funkcja UPPER .............................................................................................................757 Funkcje do konwersji typów danych ......................................................................................758 Funkcja CAST ................................................................................................................758 Funkcja CONVERT ........................................................................................................760 Funkcja TO_CHAR ........................................................................................................760 Funkcja TO_CLOB ........................................................................................................762

12

Programowanie w języku PL/SQL Funkcja TO_DATE ........................................................................................................ 762 Funkcja TO_LOB .......................................................................................................... 763 Funkcja TO_NCHAR .................................................................................................... 764 Funkcja TO_NCLOB ..................................................................................................... 764 Funkcja TO_NUMBER .................................................................................................. 764 Funkcje do konwersji dat i czasu ........................................................................................... 765 Funkcja ADD_MONTHS .............................................................................................. 765 Funkcja CURRENT_DATE ............................................................................................. 765 Funkcja CURRENT_TIMESTAMP .................................................................................. 766 Funkcja DBTIMEZONE ................................................................................................. 766 Funkcja EXTRACT ......................................................................................................... 766 Funkcja FROM_TZ ....................................................................................................... 767 Funkcja LAST_DAY ....................................................................................................... 767 Funkcja LOCALTIMESTAMP ......................................................................................... 767 Funkcja MONTHS_BETWEEN ...................................................................................... 768 Funkcja NEW_TIME ...................................................................................................... 768 Funkcja ROUND .......................................................................................................... 769 Funkcja SYSDATE ......................................................................................................... 769 Funkcja SYSTIMESTAMP ............................................................................................... 769 Funkcja TO_CHAR(data) ............................................................................................... 770 Funkcja TO_DSINTERVAL ............................................................................................ 771 Funkcja TO_TIMESTAMP ............................................................................................. 771 Funkcja TO_TIMESTAMP_TZ ....................................................................................... 771 Funkcja TO_YMINTERVAL ............................................................................................ 772 Funkcja TRUNC(data) ................................................................................................... 772 Funkcja TZ_OFFSET ..................................................................................................... 772 Funkcje do zarządzania kolekcjami ....................................................................................... 773 Funkcja CARDINALITY ................................................................................................. 773 Funkcja COLLECT ........................................................................................................ 773 Funkcja POWERMULTISET ........................................................................................... 776 Funkcja POWERMULTISET_BY_CARDINALITY ............................................................. 777 Funkcja SET .................................................................................................................. 777 Operatory zbiorów działające dla kolekcji ............................................................................. 777 Operator CARDINALITY ............................................................................................... 779 Operator EMPTY .......................................................................................................... 779 Operator MULTISET ..................................................................................................... 779 Operator MULTISET EXCEPT ........................................................................................ 780 Operator MULTISET INTERSECT .................................................................................. 780 Operator MULTISET UNION ........................................................................................ 781 Operator SET ................................................................................................................ 781 Operator SUBMULTISET OF ......................................................................................... 782 Funkcje liczbowe .................................................................................................................. 783 Funkcja CEIL ................................................................................................................. 783 Funkcja FLOOR ............................................................................................................ 783 Funkcja MOD ............................................................................................................... 783 Funkcja POWER ........................................................................................................... 785 Funkcja REMAINDER .................................................................................................... 785 Funkcja ROUND .......................................................................................................... 786 Funkcje do zarządzania błędami ........................................................................................... 786 Funkcja SQLCODE ....................................................................................................... 787 Funkcja SQLERRM ........................................................................................................ 787 Funkcje różne ....................................................................................................................... 789 Funkcja BFILENAME ..................................................................................................... 789 Funkcja COALESCE ...................................................................................................... 791

Spis treści

13

Funkcja DECODE .........................................................................................................791 Funkcja DUMP .............................................................................................................792 Funkcja EMPTY_BLOB ..................................................................................................792 Funkcja EMPTY_CLOB ..................................................................................................795 Funkcja GREATEST .......................................................................................................795 Funkcja LEAST ..............................................................................................................797 Funkcja NANVL ............................................................................................................798 Funkcja NULLIF ............................................................................................................798 Funkcja NVL .................................................................................................................799 Funkcja SYS_CONTEXT .................................................................................................799 Funkcja TABLE ..............................................................................................................802 Funkcja TREAT ..............................................................................................................804 Funkcja USERENV .........................................................................................................805 Funkcja VSIZE ...............................................................................................................806 Podsumowanie .....................................................................................................................806

D

Wbudowane pakiety i typy języka PL/SQL ................................................................... 807 Nowe pakiety w wersjach Oracle Database 11g i Oracle Database 12c .................................807 Przykłady zastosowania pakietów ..........................................................................................814 Przykład ilustrujący pakiet DBMS_APPLICATION_INFO ...............................................814 DBMS_COMPARISON ..................................................................................................818 DBMS_CRYPTO ............................................................................................................823 DBMS_FGA ..................................................................................................................825 Przypadek użycia — narzędzie do analizy zapytań ........................................................826 Podsumowanie .....................................................................................................................831

E

Wprowadzenie do wyrażeń regularnych ..................................................................... 833 Wprowadzenie do wyrażeń regularnych ................................................................................833 Klasy znaków ................................................................................................................833 Klasy porządkowania .....................................................................................................836 Metaznaki .....................................................................................................................836 Metasekwencje .............................................................................................................838 Literały ..........................................................................................................................838 Stosowanie wyrażeń regularnych ...........................................................................................839 Funkcja REGEXP_COUNT .............................................................................................839 Funkcja REGEXP_INSTR ................................................................................................842 Funkcja REGEXP_LIKE ...................................................................................................843 Funkcja REGEXP_REPLACE ...........................................................................................844 Funkcja REGEXP_SUBSTR .............................................................................................846 Podsumowanie .....................................................................................................................847

F

Wprowadzenie do opakowywania kodu w języku PL/SQL .......................................... 849 Ograniczenia w opakowywaniu kodu w języku PL/SQL .........................................................850 Ograniczenia związane z narzędziem wrap języka PL/SQL ............................................850 Ograniczenia funkcji DBMS_DDL.WRAP ......................................................................850 Stosowanie narzędzia wrap ...................................................................................................850 Opakowywanie kodu za pomocą pakietu DBMS_DDL ..........................................................851 Funkcja WRAP ..............................................................................................................851 Procedura CREATE_WRAPPED .....................................................................................855 Podsumowanie .....................................................................................................................856

G

Wprowadzenie do hierarchicznego programu profilującego języka PL/SQL .............. 857 Konfigurowanie schematu .....................................................................................................857 Zbieranie danych ..................................................................................................................859

14

Programowanie w języku PL/SQL Odczytywanie danych wyjściowych programu profilującego ................................................. 861 Odczyt surowych danych wyjściowych ......................................................................... 862 Definiowanie tabel na potrzeby programu profilującego języka PL/SQL ........................ 863 Zapytania o przetworzone dane .................................................................................... 863 Używanie narzędzia plshprof ................................................................................................ 865 Podsumowanie ..................................................................................................................... 867

H

Słowa zarezerwowane i kluczowe języka PL/SQL ........................................................ 869 Podsumowanie ..................................................................................................................... 875

I

Odpowiedzi do testów wiedzy ..................................................................................... 877 Rozdział 1. ............................................................................................................................ 877 Rozdział 2. ............................................................................................................................ 879 Rozdział 3. ............................................................................................................................ 881 Rozdział 4. ............................................................................................................................ 883 Rozdział 5. ............................................................................................................................ 885 Rozdział 6. ............................................................................................................................ 887 Rozdział 7. ............................................................................................................................ 889 Rozdział 8. ............................................................................................................................ 891 Rozdział 9. ............................................................................................................................ 893 Rozdział 10. .......................................................................................................................... 895 Rozdział 11. .......................................................................................................................... 897 Rozdział 12. .......................................................................................................................... 899 Rozdział 13. .......................................................................................................................... 901

Słowniczek .................................................................................................................... 905 Skorowidz ..................................................................................................................... 917

O autorze Michael McLaughlin jest profesorem komputerowych technologii informacyjnych na uczelni BYU-Idaho w college’u biznesu i komunikacji. Jest również założycielem firmy McLaughlin Software, LLC i aktywnie działa w grupie użytkowników produktów Oracle w Utah. Ponadto napisał osiem innych książek wydanych przez wydawnictwo Oracle Press, w tym: Oracle Database 11g. Programowanie w języku PL/SQL, Oracle Database. Tworzenie aplikacji internetowych w AJAX i PHP, Oracle Database 10g Express Edition. Tworzenie aplikacji internetowych w PHP. Michael używa języka PL/SQL od czasu, gdy był on tylko dodatkiem do Oracle 6. Oprócz tego Michael programuje w innych językach, takich jak C, C++, Java, Perl, PHP i Python. Michael przez osiem lat pracował w korporacji Oracle, gdzie zajmował się konsultacjami, rozwojem oprogramowania i pomocą techniczną. W tym czasie kierował pracami nad inżynierią wydania bezpośredniej aktualizacji systemu CRM w produktach Oracle Applications 11i (11.5.8 i 11.5.9), a także kierował testami zgodności języka PL/SQL z przyszłymi wersjami w produktach Oracle Applications 11i i Oracle Database 9i. Jest też twórcą architektury transakcyjnej ATOMS (patenty amerykańskie numer 7206805 i 7290056). Prawa do tych patentów przysługują korporacji Oracle. Przed objęciem posady w korporacji Oracle Michael pracował jako programista, analityk systemowy i biznesowy oraz architekt baz danych od czasu udostępnienia wersji 6. produktów firmy Oracle. Michael mieszka we wschodniej części stanu Idaho dwie godziny jazdy od parków Caribou-Terghee National Forest, Grand Teton National Park i Yellowstone National Park. Lubi wraz z żoną i dziećmi (sześcioro z dziewięciorga wciąż mieszka razem z nim) spędzać czas na świeżym powietrzu.

O współautorze John Harper pracuje obecnie dla kościoła Church of Jesus Christ of Letter-day Saints (LDS) jako główny inżynier baz danych. Uwielbia zajmować się tam magazynami danych i inteligencją biznesową oraz współpracować z inżynierami baz danych. Jego mentorami są: Michael McLaughlin, Robert Freeman, Danette McGilvary i wiele innych osób, które poświęciły dużo czasu na to, aby stać się ekspertami w swoich dziedzinach. John czuje wobec nich szacunek, a także jest zainspirowany ich umiejętnościami oraz uważa, że ma szczęście, że się z nimi zetknął. Niedawno John miał okazję ściśle współpracować z czołowymi specjalistami z obszaru bezpieczeństwa baz danych. Ma nadzieję, że uda mu się przygotować serię tekstów poświęconych takim produktom firmy Oracle jak Oracle Audit Vault and Database Firewall i Oracle Data Redaction. John interesuje się japońskimi sztukami walki. Jako nastolatek i młody mężczyzna ćwiczył jujitsu, karate, judo i aikido. Uwielbia aikido i chciałby kiedyś uczyć tej sztuki walki. Chciałby też poznać kyudo, jeśli znajdzie na to czas. John mieszka razem z żoną (są małżeństwem od ponad 23 lat) w hrabstwie Northern Utah w stanie Utah. Wychowują adoptowaną córkę, którą uwielbiają i rozpieszczają. John pracuje z bazami danych od 14 lat i specjalizuje się w zarządzaniu, architekturze, programowaniu i bezpieczeństwie baz Oracle oraz zapewnianiu jakości przechowywanych w nich informacji.

16

Programowanie w języku PL/SQL

O redaktorze technicznym Joseph McLaughlin jest programistą aplikacji sieciowych na urządzenia iPhone pisanych w języku Ruby. Pracuje w firmie Deseret Book w Salt Lake City w stanie Utah. Ma duże doświadczenie w tworzeniu zaplecza baz danych Oracle, MySQL i PostgreSQL. Jego ulubione języki programowania to Objective-C i Ruby. Joseph niedawno ukończył uczelnię BYU-Idaho z dyplomem komputerowych technologii informacyjnych. Gdy był studentem i niezależnym konsultantem, zaprojektował, napisał i wdrożył cztery aplikacje mobilne na urządzenia iPhone i iPod Touch. Oprócz programowania Joseph lubi grę w koszykówkę i oglądanie zwycięstw drużyny Boston Red Sox — zwłaszcza w finale rozgrywek.

Podziękowania Gorąco dziękuję Paulowi Carlstroemowi, Amandzie Russell, Harry’emu Popli i całemu zespołowi z działu produkcji, dzięki któremu powstała ta książka. W dziale produkcji, pracując na zapleczu, kryją się anonimowi herosi i nieznane bohaterki. Te osoby odpowiadają za skład i korektę oraz za doprowadzenie do wydania książki. Choć nie znam nazwisk wszystkich tych pracowników, zasługują oni na podziękowania za ich staranną pracę. Specjalne podziękowania należą się Billowi McManusowi, adiustatorowi. Wykonał on świetną pracę, aby książka była spójna, dobrze napisana i odpowiednio uporządkowana. Dziękuję też za moralne i praktyczne wsparcie Paulowi Carlstroemowi i Amandzie Russell (gdy projekt trwał już ponad rok), ponieważ to dzięki nim udało mi się zakończyć tę książkę. Dziękuję Sheili Cepero, która zarządza programem Oracle Publishers, za pomoc w testach beta bazy Oracle Database 12c. Podziękowania należą się również Lynn Snyder, odpowiadającej za program Oracle Database 12c. Dziękuję Johnowi Harperowi, który napisał fragmenty dodatku A i dodatek D. John był też drugim redaktorem technicznym pozostałych części książki, a jego staranność z pewnością przyczyniła się do wysokiej jakości tej pozycji. Składam podziękowania Pablo Ribaldiemu za jego wkład w przygotowanie dodatku A (poświęconego zarządzaniu danymi). Pablo jest menedżerem zarządzania informacjami i kierował w kościele LDS zespołem Information Communication Services, który otrzymał nagrodę Data Governance Best Practice Award przyznawaną przez organizację DebTech International LLC. Dziękuję też licznym studentom i prowadzącym ćwiczenia wykładowcom, którzy zainteresowali się tym projektem. Są to między innymi: Craig Christensen, Jeremy Heiner, Matthew Mason, Alan Pynes i Jordan Smith. Dziękuję też Kentowi Jacksonowi za przeczytanie fragmentów niniejszej książki i zaproponowanie poprawek, a także Steve’owi Rigby’emu, dyrektorowi instytutu, za wsparcie w trakcie prac nad tym projektem.

18

Programowanie w języku PL/SQL

Wprowadzenie Z tej książki dowiesz się, jak stosować język PL/SQL. Pozycja ta jest pełna przykładów i technik, które pomogą Ci pisać solidne aplikacje oparte na bazach danych. W dodatku A opisane są podstawy potrzebne administratorom i programistom baz danych, na przykład: uruchamianie i zatrzymywanie baz i odbiorników Oracle, korzystanie z działającego w wierszu poleceń interfejsu (ang. Command Line Interface — CLI) SQL*Plus, stosowanie bezpłatnego i działającego w różnych systemach interfejsu graficznego (ang. Graphical User Interface — GUI) SQL Developer, a także techniki optymalizacji kodu w SQL-u. Z dodatków B, C i D dowiesz się, jak pisać kod w SQL-u, a także jak stosować wbudowane funkcje SQL-a i pakietu języka PL/SQL. Pozostałe dodatki dotyczą: stosowania wyrażeń regularnych, zaciemniania kodu w języku PL/SQL przez jego opakowywanie, korzystania z hierarchicznego programu profilującego dla języka PL/SQL, a także słów zarezerwowanych i słów kluczowych. Autorzy zwykle piszą wprowadzenie albo na końcu, albo na początku pracy. Tu, inaczej niż w poprzednich wydaniach, zacząłem od szkicu wprowadzenia. Pomogło mi to zachować pewność, że pracuję zgodnie z planem. W podziękowaniu wspomniałem, że osoby z działu produkcji także pomagały mi w porządkowaniu tekstu, a ich talent był niezbędny do wydania książki wysokiej jakości. Oto zawartość wprowadzenia:  Podrozdział „Zarys książki” obejmuje krótkie streszczenie każdego rozdziału. Warto się z nim zapoznać, aby na ogólnym poziomie poznać strukturę książki.  Podrozdział „Leksykon” zawiera omówienie konwencji nazywania zmiennych z tej książki oraz zalecanych technik, które pozwalają zaoszczędzić czas w trakcie debugowania kodu.  Podrozdział „Model danych i kod źródłowy” obejmuje prosty opis przykładów. Dowiesz się stąd, gdzie znaleźć kod, który generuje przykładową bazę danych wypożyczalni filmów.

Zarys książki Ta książka składa się z trzech części: „Podstawy języka PL/SQL”, „Programowanie w języku PL/SQL” i „Dodatki i słowniczek”. W dwóch pierwszych częściach każdy duży fragment rozdziału kończy się sekcją przeglądową, w której wymienione są najważniejsze zagadnienia z danego fragmentu. Ponadto rozdziały z tych części obejmują test wiedzy, składający się z dziesięciu pytań typu tak-nie i pięciu pytań wielokrotnego wyboru. Ten test pomoże Ci się upewnić, że opanowałeś materiał opisany w poszczególnych rozdziałach. Odpowiedzi na pytania znajdziesz w dodatku I. Trzecia część, „Dodatki i słowniczek”, zawiera wprowadzenie do bazy Oracle Database 12c, języka SQL, wbudowanych funkcji SQL-a, wbudowanych pakietów języka PL/SQL, wyrażeń regularnych, opakowywania kodu w języku PL/SQL, programu profilującego dla języka PL/SQL, słów zarezerwowanych i słów kluczowych. Jak już wspomniano, dodatek I obejmuje odpowiedzi na pytania z testów wiedzy. Słowniczek znajduje się po ostatnim dodatku.

Część I: Podstawy języka PL/SQL 

Rozdział 1., „Przegląd programowania w języku Oracle PL/SQL”, obejmuje omówienie historii języka PL/SQL, a także architektury związanej z programowaniem baz Oracle. Z fragmentu poświęconego historii dowiesz się, że SQL to główny interfejs, a PL/SQL rozbudowuje możliwości

20

Programowanie w języku PL/SQL













SQL-a za pomocą wbudowanego imperatywnego języka programowania, umożliwia implementowanie mechanizmów obiektowo-relacyjnych, a także pozwala administratorom i programistom na wykorzystanie możliwości bazy Oracle Database 12c. Fragmenty dotyczące architektury związanej z programowaniem baz Oracle opisują działanie SQL-a w trybie interaktywnym i trybie wywołań w wierszu poleceń, a także funkcjonowanie dwu- i n-warstwowych modeli w bazie Oracle Database 12c. Rozdział 2., „Nowe funkcje”, zawiera wprowadzenie do nowych funkcji języków SQL i PL/SQL w bazie Oracle Database 12c. Zakładam tu, że znasz już funkcje bazy Oracle Database 11g. Nowe funkcje SQL-a dotyczą kolumn niewidocznych, kolumn identyfikacyjnych, większej długości typu danych VARCHAR2 i rozbudowanego złączenia zewnętrznego. Nowe funkcje języka PL/SQL to kolumny niewidoczne i identyfikacyjne (ang. identity), tworzenie białych list jednostek wywołujących kod w języku PL/SQL, nowe mechanizmy zarządzania stosem błędów, osadzanie funkcji w klauzuli WITH SQL-a i stosowanie lokalnych typów danych języka PL/SQL w osadzanych instrukcjach SQL-a. Rozdział 3., „Podstawy języka PL/SQL”, zawiera wyjaśnienia i przykłady dotyczące podstawowych funkcji języka PL/SQL. Znajdziesz tu omówienie bloków języka PL/SQL, działania zmiennych w takich blokach, podstawowych skalarnych i złożonych typów danych, struktur sterujących, wyjątków, operacji masowych, funkcji, procedur, pakietów, zasięgów transakcji i wyzwalaczy baz danych. W tym rozdziale przedstawione są przykłady ilustrujące wszystkie podstawowe elementy składni języka PL/SQL. Jest to najlepszy punkt wyjścia dla osób, które potrzebują ogólnego przeglądu lub wprowadzenia do podstaw pisania programów w tym języku. Rozdział 4., „Podstawowe elementy języka”, opisuje jednostki leksykalne (ograniczniki, identyfikatory, literały i komentarze), zmienne i typy danych. W tym rozdziale poznasz podstawowe cegiełki jednostek programów pisanych w języku PL/SQL. Dowiesz się też, jakie typy danych są dostępne i jak deklarować zmienne tych typów. W dalszych rozdziałach zakładam, że znasz te typy danych i wiesz, jak deklarować je w anonimowych i nazwanych blokach języka PL/SQL. Dlatego jest to ważny rozdział, który należy przeczytać przed przejściem do głównych mechanizmów tego języka. Rozdział 5., „Struktury sterujące”, dotyczy instrukcji warunkowych, instrukcji iteracyjnych, kursorów i operacji masowych. W tym rozdziale znajdziesz kompletne omówienie instrukcji IF oraz pętli. Firma Oracle zaimplementowała instrukcje IF i CASE na potrzeby warunkowego wykonywania kodu oraz proste pętle FOR i WHILE do zarządzania iteracjami. W kontekście pętli opisano warunki i wartownika, a także zabezpieczenia związane z dynamicznymi wartościami wartownika. W rozdziale omówiono też zarządzanie kursorami w pętlach i instrukcje DML służące do przetwarzania masowego. Rozdział 6., „Kolekcje”, wyjaśnia, jak pracować z tablicami VARRAY i zwykłymi w SQL-u, a także z tablicami asocjacyjnymi języka PL/SQL (nazywanymi wcześniej tabelami języka PL/SQL lub tabelami indeksowanymi). Z omówienia tablic VARRAY i tabel dowiesz się, jak korzystać z typów ADT (ang. attribute data types) i typów UDT (ang. user-defined types). Przedstawiono tu także różnice w stosowaniu zmiennych typów ADT i UDT. Ponadto znajdziesz tu omówienie pracy z tablicami asocjacyjnymi (są one dostępne tylko w języku PL/SQL) zawierającymi wartości skalarne lub złożone (rekordy albo obiekty). Oprócz tego przedstawiono tu funkcje i procedury z interfejsu Oracle Collection API, a także przykłady zastosowania tych konstrukcji. Rozdział 7., „Obsługa błędów”, wyjaśnia stosowanie wyjątków w języku PL/SQL. Opisane są tu: typ i zasięg wyjątków, wbudowane funkcje do zarządzania wyjątkami, wyjątki definiowane przez użytkownika i funkcje związane ze stosem wyjątków. Z tego rozdziału dowiesz się, jak wykrywać i rozwiązywać typowe problemy, które mogą wystąpić w trakcie pisania programów w języku PL/SQL. Ponadto opisano tu, jak pisać metody obsługi wyjątków, zarządzające nieoczekiwanymi wyjątkami czasu wykonania. Zobaczysz również, jak zarządzać stosem wyjątków.

Wprowadzenie

21

Część II: Programowanie w języku PL/SQL 











Rozdział 8., „Funkcje i procedury”, wyjaśnia architekturę funkcji i procedur języka PL/SQL, zasięg transakcji, opcje i implementacje funkcji oraz implementacje procedur. Fragment poświęcony architekturze dotyczy działania funkcji i procedur z parametrami przekazywanymi przez wartość i przez referencję. Dowiesz się, jak w bazie Oracle Database 12c utworzyć białą listę z niezależnymi funkcjami i procedurami. Omówiono tu także używaną w SQL-u notację opartą na pozycji, na nazwach, mieszaną i wyłączającą. Ponadto znajdziesz tu opis różnych sposobów definiowania funkcji (deterministycznych, dostosowanych do przetwarzania równoległego, potokowych i zapisujących wynik w pamięci podręcznej). Przedstawiono tu też pisanie funkcji rekurencyjnych i autonomicznych. Rozdział 9., „Pakiety”, przedstawia, jak należy pracować z pakietami. Opisano tu architekturę, specyfikacje i zawartość pakietów. Znajdziesz tu też porównanie uprawnień jednostek definiujących i wywołujących kod, a także omówienie zarządzania stanem i poprawnością specyfikacji i zawartości pakietów za pomocą katalogu bazy danych. Rozdział zawiera również opis tworzenia namiastek z deklaracjami uprzedzającymi (ang. forward-referencing stub) oraz przeciążania funkcji i procedur. Dowiesz się też, jak tworzyć białe listy ze specyfikacjami pakietów. Rozdział 10., „Duże obiekty”, wyjaśnia korzystanie z wewnętrznie zarządzanych typów danych BLOB, CLOB i NCLOB oraz zewnętrznie zarządzanego typu danych BFILE. Z tego rozdziału dowiesz się, jak tworzyć i stosować wewnętrznie zarządzane duże obiekty znakowe i binarne, a także jak pracować z zewnętrznie zarządzanymi plikami binarnymi. Rozdział 11., „Typy obiektowe”, opisuje pracę z typami obiektowymi. Dzięki lekturze nauczysz się deklarować, implementować i dodawać do białej listy typy obiektowe, a także implementować gettery, settery i funkcje do porównywania obiektów. Po omówieniu podstaw przedstawione są dziedziczenie i polimorfizm oraz implementowanie kolekcji typów obiektowych. Rozdział 12., „Wyzwalacze”, zawiera wprowadzenie do wyzwalaczy bazy danych. Dowiesz się z niego, jak działają różne rodzaje wyzwalaczy (DDL, DML, złożone, zastępujące, a także związane ze zdarzeniami systemu i bazy danych) i jak je implementować. Opisano tu także ograniczenia wyzwalaczy. Rozdział 13., „Dynamiczny SQL”, opisuje podstawy instrukcji dynamicznego SQL-a. Przedstawione są tu język NDS (ang. Native Dynamic SQL) i pakiet dbms_sql. Znajdziesz tu omówienie wszystkich czterech rodzajów instrukcji dynamicznego SQL-a: statyczne instrukcje DDL i DML, dynamiczne instrukcje DML, dynamiczne instrukcje SELECT ze statycznymi listami SELECT i dynamiczne instrukcje SELECT z dynamicznymi listami SELECT.

Część III: Dodatki i słowniczek 





Dodatek A, „Wprowadzenie do bazy Oracle”, wyjaśnia architekturę bazy Oracle Database 12c, uruchamianie i zatrzymywanie serwera i odbiornika tej bazy, mechanizm kontroli współbieżności, uprawnienia jednostek definiujących i wywołujących, przetwarzanie interaktywne i wsadowe w SQL-u, administrowanie bazą danych, a także optymalizowanie i monitorowanie kodu w SQL-u. Dodatek B, „Wprowadzenie do języka SQL”, opisuje korzystanie z języka SQL w bazie Oracle Database 12c. Omówiono tu typy danych SQL-a, instrukcje DDL, DML i TCL, instrukcje SELECT, a także operatory zbiorów dla kolekcji. Z tego dodatku dowiesz się też, jak przekształcać instrukcje zagnieżdżone na zwykłe i jak pracować z trwałymi typami obiektowymi. Dodatek C, „Funkcje wbudowane języka SQL”, zawiera kompletny przykładowy kod ilustrujący stosowanie w bazie Oracle Database 12c najważniejszych funkcji wbudowanych języka SQL. Opisano tu funkcje znakowe, funkcje do konwersji typów danych, funkcje związane z datą i czasem, funkcje do zarządzania kolekcjami, operatory zbiorów dla kolekcji, funkcje liczbowe, funkcje do obsługi błędów i funkcje różne.

22

Programowanie w języku PL/SQL 

  

  

Dodatek D, „Wbudowane pakiety i typy języka PL/SQL”, opisuje korzystanie z języka PL/SQL w bazie Oracle Database 12c. Znajdziesz tu wprowadzenie do nowych wbudowanych pakietów tego języka i przykłady ilustrujące stosowanie najważniejszych pakietów. Dodatek E, „Wprowadzenie do wyrażeń regularnych”, opisuje stosowanie wyrażeń regularnych w językach SQL i PL/SQL. Dodatek F, „Wprowadzenie do opakowywania kodu w języku PL/SQL”, przedstawia stosowanie procedur create_wrapped i wrap z pakietu dbms_ddl. Dodatek G, „Wprowadzenie do hierarchicznego programu profilującego języka PL/SQL”, opisuje korzystanie z takiego programu. Dowiesz się tu, jak skonfigurować schemat, zbierać dane związane z profilowaniem, zrozumieć dane wyjściowe z programu profilującego, a także jak korzystać z uruchamianego z poziomu wiersza poleceń narzędzia plshprof. Dodatek H, „Słowa zarezerwowane i kluczowe języka PL/SQL”, zawiera listę słów zarezerwowanych i słów kluczowych używanych w bazie Oracle Database 12c. Dodatek I, „Odpowiedzi do testów wiedzy”, zawiera odpowiedzi do wszystkich testów wiedzy z poszczególnych rozdziałów. Słowniczek obejmuje definicje najważniejszych pojęć stosowanych w tej książce.

Leksykon Programy można pisać na wiele sposobów, a aplikacje rozwijane w odmiennych językach zwykle różnią się od siebie. To samo dotyczy języków SQL i PL/SQL. Są to różne języki i wymagają stosowania odmiennych podejść. W trzech dalszych punktach opisane są: leksykon języka SQL, platformy składowane języka PL/SQL i inne konwencje związane ze składnią.

Leksykon języka SQL Zachęcam do pisania instrukcji SQL-a w taki sposób, aby słowa kluczowe były wyrównane do lewej strony. To oznacza, że przecinki w instrukcjach SELECT oraz logiczne AND [NOT] lub OR [NOT] w klauzulach WHERE powinny znajdować się po lewej stronie, ponieważ pozwala to szybko zauważyć błędy w kodzie. To zalecenie łatwo jest zastosować, jednak sugestie dotyczące składni złączeń są bardziej skomplikowane, ponieważ złączenia mogą być oparte na standardzie ANSI SQL-89 lub ANSI SQL-92. Ten pierwszy umożliwia porządkowanie tabel w postaci list rozdzielonych przecinkami, natomiast drugi wymaga określenia typu złączenia za pomocą słów kluczowych. Oto moje sugestie na temat składni złączeń:  Zawsze stosuj dla tabel aliasy, ponieważ gwarantuje to, że nie wystąpi błąd wieloznaczności nazw kolumn, jeśli instrukcja SELECT może zwrócić więcej niż jedną kolumnę o tej samej nazwie. Może się to zdarzyć przy złączaniu tabel zawierających kolumny o identycznych nazwach. Aliasy warto też stosować w zapytaniach dotyczących jednej tabeli, ponieważ później za pomocą złączenia może zostać dodana inna tabela. W dodatku B opisano instrukcję SELECT i składnię zgodną z tym zaleceniem.  Przy stosowaniu standardu ANSI SQL-89 i tabel rozdzielonych przecinkami każdą tabelę warto zapisać w odrębnym wierszu i podać kolumny (na przykład na liście instrukcji SELECT) po lewej stronie. Pozwala to szybko przeczytać kod programu. Nie dotyczy to wielotabelowych instrukcji UPDATE i DELETE przedstawionych w dodatku B. Odpowiednie przykłady znajdziesz w rozdziałach książki.  Przy stosowaniu standardu ANSI SQL-92 warunki złączenia podaj w klauzuli FROM za pomocą podklauzuli ON lub USING. Dla większości programistów w takiej sytuacji sprawdzają się dwa podejścia. Jeśli złączenie jest małe (obejmuje dwie lub maksymalnie trzy tabele), należy umieścić podklauzulę ON lub USING po złączeniu w tym samym wierszu. W dużych złączeniach (trzy tabele lub większa ich liczba) podklauzulę lepiej jest umieścić w wierszu pod instrukcją złączenia. Gdy złączenie obejmuje wiele kolumn, warto logiczne instrukcje AND [NOT] lub OR [NOT]

Wprowadzenie



23

wyrównać do lewej strony, co poprawi czytelność kodu. To samo zalecenie przedstawiłem dla klauzuli WHERE na początku punktu. Zwykle to podejście dobrze się sprawdza. Standard ANSI SQL-92 pozwala stosować opisowe słowa kluczowe lub jedynie wymagane słowa. Choć większość programistów lubi pisać jak najkrótszy kod, ostatecznie trafia on do personelu pomocniczego, a przejrzystość kodu pomaga uniknąć bezsensownych raportów o błędach. Dlatego staraj się stosować instrukcję INNER JOIN zamiast JOIN, LEFT OUTER JOIN lub RIGHT OUTER JOIN zamiast LEFT JOIN i RIGHT JOIN, a także FULL OUTER JOIN zamiast FULL JOIN. W książce stosuję skróconą składnię tylko z powodu ograniczonej szerokości stron, przez co wiersze z kodem mogą mieć maksymalnie 70 znaków (dłuższe wiersze wymagałyby mniejszej czcionki, przez co kod stałby się mniej czytelny).

Po tym wstępie pozwól, że podzielę się z Tobą moimi doświadczeniami związanymi z nieprzestrzeganiem porad z zakresu składni. Te porady otrzymałem od instruktora z jednostki Santa Teresa Lab (obecnie Silicon Valley Lab) firmy IBM, który w 1985 roku uczył mnie pisania kodu w SQL-u (a dokładnie — w języku SQL/DS.; ang. Structured Query Language/Data System). Ta osoba zaleciła mi umieszczanie przecinków po lewej stronie, abym zaoszczędził sobie wielu godzin poszukiwania brakujących przecinków. Zignorowałem tę radę i przez kilka miesięcy zapisywałem przecinki po prawej stronie, na końcu wiersza. Dopiero potem zdałem sobie sprawę, że instruktor miał rację. Często powtarzał mi wtedy tę maksymę: „Dobre programowanie jest efektem stosowania prostych zasad”. Uwaga na temat narzędzi Ta książka dotyczy głównie pisania kodu w SQL-u za pomocą wiersza poleceń, ponieważ tak wygląda praca w programach pisanych w językach C++, C#, Java lub PHP. Jednak narzędzia CASE (ang. Computer-Aided Software Engineering) są naprawdę wygodne. Pomagają poznać składnię i możliwości języka, pod warunkiem jednak, że programista nie stosuje ich w celu ukrycia swej niewiedzy. Najlepszymi programistami nie są użytkownicy biznesowi, którzy są bardzo elokwentni, poprawnie stosują modne powiedzonka i potrafią się zareklamować. Najlepsi pracownicy to ci, którzy uczą się radzić sobie z problemami biznesowymi dzięki zrozumieniu tego, jaka technologia pozwoli opracować najlepsze rozwiązanie. Osoby z wysokimi umiejętnościami z zakresu inżynierii nie trafią do tego ekskluzywnego klubu, jeśli będą korzystać tylko z mechanizmów dostępnych w ramach narzędzi CASE. Wynika to z tego, że takie narzędzia zwykle pozwalają rozwiązywać tylko ogólne problemy za pomocą interfejsu opartego na przeciąganiu. Osoby promujące rozwiązania typu NoSQL to zwykle ci, którzy nie wiedzą, jak stosować bazy danych ani w jaki sposób bazy pomagają realizować codzienne ważne transakcyjne potrzeby firmy. Korzystaj więc z narzędzi do nauki, ale nie uzależniaj się od nich. Zawsze zastanawiaj się, dlaczego dane rozwiązanie działa i jak można je usprawnić. Wtedy odkryjesz, że narzędzia CASE są pomagającym wykonywać zadania błogosławieństwem, a nie przekleństwem potencjalnie ograniczającym rozwój kariery (co przytrafiło się w ostatnich latach wielu programistom).

Obecnie na uczelni podkreślam tę regułę w każdym semestrze. Niektórzy studenci akceptują ją i stosują, natomiast inni tego nie robią. Ci ostatni mają w trakcie kursu problemy ze składnią, ponieważ ciągle próbują znaleźć brakujące przecinki lub komponenty w instrukcjach SQL-a. SQL nie jest łatwy w nauce, ponieważ wymaga tworzenia map danych, a nie wszyscy programiści potrafią szybko nabyć tę umiejętność. Dostrzeżenie zależności między danymi w relacyjnej bazie nieraz wymaga sporo czasu. Wraz z nabywaniem doświadczenia to zadanie staje się łatwiejsze, pod warunkiem jednak, że programista pisze przejrzyste instrukcje, konsekwentnie stosuje to samo podejście i posługuje się przenośną składnią SQL-a.

Programy składowane w języku PL/SQL PL/SQL to kompletny język programowania. Umożliwia pisanie przechowywanych w bazie programów, które zarządzają zestawami instrukcji SQL-a składającymi się na kompletne transakcje. Konwencje tworzenia nazw zmiennych w niektórych firmach są źródłem kontrowersji, ponieważ wielu programistów uważa, że zmienne powinny mieć znaczące nazwy. Argumentem przeciw stosowaniu

24

Programowanie w języku PL/SQL

konwencji (na przykład określonych przedrostków) jest to, że zmniejszają one czytelność kodu. Problem wynika ze sprzeczności pomysłów. Obie strony mają swoje racje i zawsze można wymyślić sytuacje, w których jedno lub drugie podejście będzie lepsze. Moim zdaniem najważniejsze jest znalezienie równowagi między regułami zwiększającymi stabilność firmy lub korporacji i tworzeniem zmiennych o znaczących nazwach. W tej książce staram się konsekwentnie stosować przedrostki. W niektórych miejscach zdecydowałem się na używanie semantycznie znaczących nazw zmiennych (na przykład w sesjach Oracle lub dla zmiennej wiązanej :whom w rozdziale 2.). Moim zdaniem stosowanie przedrostków zwiększa czytelność kodu. Zachęcam do używania przedrostków wymienionych w tabeli W.1. Tabela W.1. Przedrostki nazw zmiennych w języku PL/SQL Przedrostek

Przykład

Opis

cv

cv_input_var

Reprezentuje zmienne będące parametrami kursorów. Są to przekazywane przez wartość parametry wejściowe kursorów w programach składowanych języka PL/SQL.

lv

lv_target_var

Reprezentuje zmienne lokalne zdefiniowane w programach składowanych języka PL/SQL.

pv

pv_exchange_var

Reprezentuje parametry funkcji i procedur składowanych języka PL/SQL. Nie są to wyłącznie parametry wejściowe, ponieważ PL/SQL obsługuje w funkcjach i procedurach parametry wejściowe i wyjściowe.

sv

sv_global_var

Reprezentuje zmienne sesji. Działają one jak zmienne globalne w trakcie połączenia klienta z bazą danych. Oracle umożliwia współużytkowanie wartości tych zmiennych między anonimowymi blokami. Wymaga to podania dwukropka przed nazwą zmiennej (:sv_global_var) w bloku. Inna nazwa takich zmiennych to zmienne wiązane.

Niektóre zmienne zaawansowanych typów danych (zmienne złożone) wymagają zarówno przedrostków, jak i przyrostków. Przyrostek określa typ zmiennej złożonej. Ten wymóg jest specyficzny dla baz danych Oracle. W tabeli W.2 znajdziesz listę zalecanych przeze mnie przyrostków (rozpoczynających się od podkreślenia) dla złożonych typów danych z baz Oracle. W tej tabeli podane są zarówno długie, jak i krótkie wersje takich przyrostków. Stosowanie przyrostków dla złożonych typów danych jest ogólnie przyjętą praktyką, ponieważ są to typy użytkownika. Nie jest to jednak reguła ani wymóg języka PL/SQL. PL/SQL to język ze ścisłą kontrolą typów. Używane są w nim bloki deklaracji, wykonywanego kodu i wyjątków. W programach z blokami stosuje się słowa kluczowe rozpoczynające i kończące jednostki programu (inaczej jest na przykład w językach C++, C#, Java i PHP, gdzie używane są nawiasy klamrowe). W bibliotekach GeSHi (ang. Generic Syntax Highlighter) związane z blokami słowa kluczowe języka PL/SQL zaczynają się wielkimi literami. W tej książce stosuję to samo podejście.

Inne konwencje Czasem bloki kodu wymagają zwiększenia przejrzystości. W przykładowym kodzie w językach PL/SQL i SQL z baz Oracle znajdują się numery wierszy, ponieważ są one wyświetlane w środowisku SQL*Plus. Konwencje stosowane w tekście tej książki obejmują wyróżnianie, stosowanie kursywy i rozdzielanie. Ich opis znajdziesz w tabeli W.3. Mam nadzieję, że dzięki tym konwencjom lektura książki będzie łatwiejsza. Ponadto na kartach znajdziesz wyróżnione szarym kolorem ramki.

Model danych i kod źródłowy Model danych reprezentuje małą wypożyczalnię filmów. Kod źródłowy potrzebny do utworzenia i zapełnienia modelu danych w bazie Oracle znajdziesz w witrynie wydawnictwa, które wydało tę książkę (http://www.OraclePressBooks.com; spolszczoną wersję znajdziesz w witrynie wydawnictwa Helion — http://helion.pl).

Wprowadzenie

25

Tabela W.2. Przyrostki nazw zmiennych w języku PL/SQL Przyrostek Pełna postać Skrócona postać

Opis

_ATABLE _AARRAY

_ATAB _AA

Przyrostki _ATABLE, _AARRAY, _ATAB i _AA służą do opisywania tablic asocjacyjnych w języku PL/SQL. Ja najchętniej stosuję przyrostki _ATABLE i _ATAB, ponieważ pozostałe są mniej intuicyjne i wymagają dodania dokumentacji w kodzie.

_CURSOR

_CUR _C

Przyrostki _CURSOR, _CUR i _C służą do opisywania zmiennych opartych na kursorze, zdefiniowanych w lokalnym bloku deklaracji lub w specyfikacji pakietu w języku PL/SQL. Ja najchętniej używam przyrostków _CURSOR i _C.

_EXCEPTION

_EXCEPT _EX _E

Przyrostki _EXCEPTION, _EXCEPT, _EX i _E służą do opisywania wyjątków zdefiniowanych przez użytkownika w języku PL/SQL. Ja najchętniej używam przyrostków _EXCEPTION i _E.

_OBJECT

_OBJ _O

Przyrostki _OBJECT, _OBJ i _O służą do opisywania typów zdefiniowanych przez użytkownika w językach SQL i PL/SQL. Typy obiektowe mogą działać jak typy rekordowe z języka PL/SQL. Są to zdefiniowane przez użytkownika typy SQL-a z poziomu schematu; nie są to typy używane tylko w języku PL/SQL. Typami obiektowymi mogą być też typy, na podstawie których tworzy się obiekty. Są to na przykład klasy języków C++, C# i Java. Ja najchętniej używam przyrostków _OBJECT i _O.

_NTABLE _TABLE

_NTAB _TAB

Przyrostki _NTABLE, _TABLE, _NTAB i _TAB służą do opisywania tablic. Jest to typ kolekcji używany w językach SQL i PL/SQL. Działa podobnie jak listy, ponieważ nie ma górnego limitu liczby przechowywanych elementów. Ja najchętniej używam przyrostków _TABLE i _TAB, ponieważ tablica jest kolekcją i najbardziej przypomina listy z innych języków programowania.

_RECORD

_REC _R

Przyrostki _RECORD, _REC i _R służą do opisywania typów zdefiniowanych przez użytkownika w języku PL/SQL. Są one implementacją rekordu stosowaną w języku PL/SQL. Można ich używać w kolekcjach tego języka, ale już nie w kolekcjach SQL-a. Ja najchętniej używam przyrostków _RECORD i _R, ponieważ są w pełni opisowe lub naprawdę krótkie, jednak wielu programistów stosuje przyrostek _REC.

_TYPE

_T

Przyrostki _TYPE i _T służą do opisywania typów zdefiniowanych przez użytkownika, na przykład podtypów standardowych skalarnych typów danych opisanych w rozdziale 4. Ja stosują obie wersje, jednak w repozytoriach kodu częściej pojawia się przyrostek _TYPE.

_VARRAY

_VARR _VA

Przyrostki _VARRAY, _VARR i _VA służą do opisywania tablic VARRAY (zapamiętuję ten typ danych baz Oracle jako virtual array, czyli tablica wirtualna). VARRAY to kolekcja przypominająca standardowe tablice z innych języków programowania, ponieważ ma określoną maksymalną długość i zawsze musi mieć indeks o kolejnych wartościach. Za pomocą tego typu można definiować kolekcje w językach SQL i PL/SQL. Ja najchętniej używam przyrostków _VARRAY i _VA, ponieważ _VARR zanadto przypomina skrót opisujący zmienne.

Rysunek W.1 ilustruje podstawowe tabele używane w przykładowych programach. Jedna z tabel modelu może wymagać pewnych wyjaśnień. Jest to tabela common_lookup. Jak ilustruje rysunek W.2, jest to tabela tabel. Naturalnym kluczem jest zestaw atrybutów (kolumn), które jednoznacznie identyfikują wiersze. Klucz obejmuje nazwy tabel i kolumn oraz typ danych. Typy są zapisywane za pomocą wielkich liter, a poszczególne słowa są połączone podkreśleniem. Dzięki temu kierowanie zapytań o te zbiory jest łatwiejsze. Kolumna common_lookup_meaning zawiera informacje, które można udostępnić użytkownikom wybierającym opcje z list rozwijanych.

26

Programowanie w języku PL/SQL

Tabela W.3. Konwencje stosowane w tekście Konwencja

Znaczenie

Pogrubienie

Wyróżnienie konkretnych wierszy kodu w przykładowych programach.

Kursywa

Wyróżnienie nowych słów i zagadnień.

Stała szerokość

Wszystkie bloki kodu są zapisane czcionką o stałej szerokości.

WIELKIE LITERY, STAŁA SZEROKOŚĆ

Określa słowa kluczowe używane w językach SQL i PL/SQL oraz nazwy funkcji wbudowanych SQL-a.

MAŁE LITERY, STAŁA SZEROKOŚĆ

Określa nazwy tabel, widoków, kolumn, funkcji, procedur, pakietów i typów zdefiniowanych przez użytkownika.

[]

Określa opcjonalne elementy składni i występuje w prototypach.

{}

Służy do grupowania opcji rozdzielanych symbolem potoku (|).

|

Oznacza logiczny operator OR na listach opcji.



Informuje, że dany fragment powtarza się lub został pominięty w celu skrócenia kodu.

Klucz główny tabeli common_lookup to kolumna z kluczem sztucznym common_lookup_id (zastosowano tu praktykę łączenia nazwy tabeli z przyrostkiem id, używanym dla nazw kolumn z kluczem głównym). Kopie wartości z tej kolumny są przechowywane w określonych kolumnach poszczególnych tabel (na przykład w kolumnie item_type tabeli item). Przy takim podejściu można zmienić wyświetlaną wartość XBOX na Xbox w jednym miejscu, a wszystkie moduły kodu i wartości w tabeli nie będą wymagały zmian. Jest to cenna technika tworzenia modelu danych, ponieważ pozwala uniknąć umieszczania komponentów takich jak płeć, kolor skóry lub odpowiedzi typu tak-nie w formularzach sieciowych (jako zagnieżdżonych opcji), a także zmniejsza koszt zarządzania aplikacją po jej udostępnieniu. Zobacz teraz, jak wykorzystać tabelę common_lookup w aplikacji sieciowej. Dane są przechowywane w złączeniu dwóch tabel, member i contact. Przy wewnętrznym wyszukiwaniu nazwisko klienta (klucz naturalny) z tabeli contact używane jest do znalezienia informacji o koncie w tabeli member. SELECT , , , , , , , FROM ON WHERE AND AND

m.account_number m.member_type -- Klucz obcy z tabeli common_lookup. m.credit_card_number m.credit_card_type -- Klucz obcy z tabeli common_lookup. c.first_name c.middle_name c.last_name c.contact_type -- Klucz obcy z tabeli common_lookup. member m INNER JOIN contact c m.member_id = c.member_id c.first_name = 'Harry' c.middle_name = 'James' c.last_name = 'Potter';

To zapytanie zwraca pokazane poniżej dane, jeśli uruchomisz je za pomocą przykładowego kodu z pakietu dbms_sql z metody 4. z rozdziału 13. Ten kod wyświetla nazwy kolumn po lewej stronie, a ich wartości — po prawej. Zauważ, że kolumny member_type, credit_card_type i contact_type zawierają wartości klucza obcego ustalone na podstawie kolumny z kluczem sztucznym common_lookup_id.

Te wartości można wykorzystać do połączenia danych w złączeniu lub za pomocą wywołań funkcji używających tabeli common_lookup. Ta tabela zawiera wartości często wyświetlane w formularzach aplikacji.

Wprowadzenie

27

Rysunek W.1. Schemat encji-relacji bazy wypożyczalni filmów

Poniższe złączenie łączy wszystkie trzy klucze obce z trzema różnymi wierszami tabeli common_ lookup. SELECT , , , , ,

m.account_number cl1.common_lookup_meaning -- Wyświetlanie danych zrozumiałych dla użytkownika. m.credit_card_number cl2.common_lookup_meaning -- Wyświetlanie danych zrozumiałych dla użytkownika. c.first_name c.middle_name

28

Programowanie w języku PL/SQL

Rysunek W.2. Tabela common_lookup (tabela tabel) , , FROM ON ON ON ON WHERE AND AND

c.last_name cl3.common_lookup_meaning -- Wyświetlanie danych zrozumiałych dla użytkownika. member m INNER JOIN contact c m.member_id = c.member_id JOIN common_lookup cl1 cl1.common_lookup_id = m.member_type JOIN common_lookup cl2 cl2.common_lookup_id = m.credit_card_type JOIN common_lookup cl3 cl3.common_lookup_id = c.contact_type c.first_name = 'Harry' c.middle_name = 'James' c.last_name = 'Potter';

To złączenie pozwala uzyskać następujące zrozumiałe informacje biznesowe:

Dane zwrócone z tych zapytań są symetryczne. Oznacza to, że wszystkie kolumny zwracają tę samą liczbę wierszy. Wyniki z pierwszego zapytania to dane biznesowe z trzech operacji wyszukiwania i obejmują wartości wybrane wcześniej przez użytkownika biznesowego. Jednak te wyniki nie są odpowiednie do wyświetlania w formularzu sieciowym, który ma umożliwiać modyfikację wartości (na przykład typu użytkownika, karty kredytowej lub kontaktu). Zamiast nich potrzebna jest obecnie ustawiona wartość, a także lista innych dostępnych wartości, które może wybrać użytkownik pracujący z formularzem aplikacji (rysunek W.3). Zapytania tego nie umożliwiają, ponieważ zbiory wyników obejmują symetryczne dane, takie jak w ostatnim zapytaniu. Aby pobrać wartość bieżącą i inne dostępne wartości, należy przekazać w wywołaniu funkcji parametr w postaci klucza obcego. W tym przykładzie trzeba też podać nazwę tabeli, nazwę kolumny i bieżącą wartość. W formularzu na stronie HTML funkcja zwraca zbiór znaczników option, które należy umieścić w znaczniku select. Obecnie ustawiona wartość powinna być widoczna w wybranym

Wprowadzenie

29

Rysunek W.3. Pola wyboru w formularzu aplikacji sieciowej

znaczniku option, a pozostałe dostępne wartości mają znajdować się w niewybranych znacznikach tego rodzaju. To podejście zwraca asymetryczny zbiór wyników, taki jak przedstawiony poniżej:

Zastosowanie tego podejścia do często używanych wartości pozwala na łatwiejsze korzystanie z modułów wielokrotnego użytku w kodzie aplikacji. Oczywiście najlepszym rozwiązaniem jest zastosowanie dostępnej w języku PL/SQL funkcji zapisywania wyników w pamięci podręcznej w aplikacji opartej na bazie Oracle Database 12c.

30

Programowanie w języku PL/SQL

CZĘŚĆ

I

Podstawy języka PL/SQL

32

Część I

Podstawy języka PL/SQL

Rozdział 1. Przegląd programowania w języku Oracle PL/SQL

33

ROZDZIAŁ

1

Przegląd programowania w języku Oracle PL/SQL

Ten rozdział to wprowadzenie do programowania w języku Oracle PL/SQL. Zrozumienie kwestii związanych z tym, jak, co i dlaczego działa w danym języku programowania, zapewnia solidne podstawy do nauki o tym, jak wydajnie korzystać z niego do rozwiązywania problemów. W tym rozdziale omówiono następujące zagadnienia:  tło historyczne języka PL/SQL,  architektura programowania baz Oracle. Przykłady w tym rozdziale są prezentowane za pomocą programu SQL*Plus, ponieważ jest to najprostsze narzędzie używane przy programowaniu baz Oracle. Choć takie narzędzia programistyczne jak Toad firmy Dell i Oracle SQL Developer mają wiele zalet, nie są też pozbawione wad. Ich wartość polega na upraszczaniu zadań i udostępnianiu metadanych, które standardowo mogą być ukryte przez miesiące lub lata. Wady są mniej zauważalne. Te narzędzia umożliwiają rozwiązywanie problemów bez konieczności zrozumienia samego problemu czy rozwiązania. Czasem prowadzi do to wyboru sugerowanego rozwiązania, które okazuje się nieoptymalne lub błędne. Ponadto poleganie na narzędziach utrudnia proces nauki. Choć program SQL*Plus to także narzędzie, jest on podstawą, na której bazują wszystkie środowiska IDE (ang. integrated development environment). Dobre zrozumienie podstaw baz Oracle i programu SQL*Plus pozwoli Ci wydajniej korzystać z narzędzi środowisk IDE.

Tło historyczne języka PL/SQL Jest to krótka wersja historii powstania firmy Oracle Corporation w jej obecnej postaci. W latach 70. Larry Ellison zauważył, że tworzenie systemów zarządzania relacyjnymi bazami danych (ang. relational database management system — RDBMS) może okazać się sukcesem biznesowym. Dlatego wraz z kilkoma znajomymi założył w 1977 roku firmę Software Development Laboratories (SDL). Po kilku latach zmienił jej nazwę na Relational Software, Inc. (RSI), później na Oracle Systems Corporation, a ostatecznie na Oracle Corporation. W wyniku własnego rozwoju i przejęć licznych firm przez ostatnie 35 lat firma Oracle, jak się ją zwykle nazywa, przejęła większość rynku systemów RDBMS. Systemy RDBMS to skomplikowane zagadnienie. Mają one: (a) obejmować informacje o tym, jak dane są przechowywane i ustrukturyzowane, (b) przechowywać dane i (c) zapewniać dostęp do struktury i danych oraz umożliwiać zarządzanie nimi za pomocą standardowego języka. Tym językiem jest SQL (ang. Structured Query Language). Firma Oracle wyszła poza pierwotną specyfikację SQL-a i opracowała własny dialekt tego języka — PL/SQL (ang. Procedural Language/Structured Query Language). Choć wiele nowych funkcji tego języka pojawiło się w standardzie ANSI 92 SQL, niektóre pozostają zastrzeżone dla baz Oracle. Dzięki nim firma Oracle zachowuje przewagę konkurencyjną. W odróżnieniu od innych organizacji Oracle

Część l

34

Podstawy języka PL/SQL

nie zadowala się pozycją lidera. Dba o zachowanie swojego statusu i przewagi konkurencyjnej dzięki ciągłym innowacjom. Ponadto wyznacza standardy branżowe w obszarze relacyjnych i obiektowo-re­ lacyjnych baz danych. Firma Gracle opracowała język PL/SQL pod koniec lat 80., ponieważ dostrzegła potrzebę utworze­ nia proceduralnego rozszerzenia języka SQL. PL/SQL był i jest innowacyjnym językiem imperatywnym z obsługą programowania sterowanego zdarzeniami i obiektowego. Prawdopodobnie najważniejszym aspektem tego języka jest możliwość wywoływania w nim instrukcji w SQL-u i w drugą stronę. Użyt­ kownicy często rezygnują ze stosowania języka PL/SQL, ponieważ chcą zachować niezależność od ba­ zy danych, co oznacza, że wolą tworzyć rozwiązania w SQL-u, które łatwiej jest wykorzystać w innych systemach. Choć producenci konkurencyjnych baz dodali do nich procedury składowane, nie zapew­ niają one takich samych możliwości jak PL/SQL. Jedynym wyjątkiem jest firma IBM, która zaimple­ mentowała język PL/SQL w bardzo podobny sposób jak w bazach Gracle. Na nieszczęście dla IBM-u wbudowane mechanizmy języków SQL i PL/SQL w bazach Gracle i zastrzeżone rozszerzenia SQL-a sprawiają, że Gracle nadal jest liderem w rozwoju technologii w branży systemów RDBMS. Pod koniec lat 90. firma Gracle dostrzegła potrzebę dodania obiektowo-relacyjnego rozszerzenia języka SQL. Dlatego wprowadziła typy obiektowe w bazie Gracle 8i odpowiednio zmodyfikowała ser­ wer baz Gracle. W ten sposób przeszła od systemów zarządzania relacyjnymi bazami danych do syste­ mów zarządzania relacyjno-obiektowymi bazami danych (ang. object-relati01�..database management obiektowych w ba­ system GRDBMS). Firma Gracle konsekwentnie usprawniała działani � zach Gracle 8i, 9i, lOg, llg i 12c. PL/SQL to naturalna droga do two · s sowania typów obiek­ ��j ch do baz Gracle. towych. Ten język umożliwił dodanie i rozwój technologii obiekto

-



��

��! '�

���

Pojęcia model obiektowo-relacyjny używa się wym

Uwaga



relacyjny, przy czym firma Oracle preferuje pierwsL

7

�� "1tta

zwą rozszerzony model określeń.

Firma Gracle w 1998zwróciła ponadto uwagę na ż e implementacji maszyny JVM (ang. ka maszyna. Firma Gracle usprawniała w bazie. W bazie Gracle 9i poja� jej implementacje w bazach lOg, llg i 12c. Interf� j � PL/SQL zapewniają dostęp do wewnętrz­ nych bibliotek Javy, które działają w maszynie JV�kże do zewnętrznych bibliotek w językach wy­ woływanych w języku C. Pełne możliwości sy GRDBMS można uzyskać dzięki cennemu połą­ . Co więcej, PL/SQL umożliwił powstanie modelu czeniu języka PL/SQL i wewnętrznej maszy�J obiektowo-relacyjnego znanego z baz GracłU Rysunek 1.1 przedstawia oś czasl14..._ tr�jącą ewolucję języka PL/SQL w bazach Gracle. Co cieka­ we, firma Gracle w ciągu 28lat ro tego języka wprowadziła w nim 12 istotnych aktualizacji. tał już prawie zapomniany, a Ada w przeciągu ponad 30 lat została Warto zauważyć, że język Pasca . Jedynym oprócz PL/SQL językiem, którego twórcy w równie duzaktualizowana tylko cztero żym stopniu inwestują w d anie funkcji, jest Java, obecnie także zarządzana przez firmę Gracle. Na podstawie wielu la świadczenia w korzystaniu z baz Gracle i innych baz stwierdzam, że firma Gracle podjęła właściwą decyzję, dodając do swoich baz język PL/SQL. Jest on niezwykle cennym i dającym duże możliwości narzędziem, które pozwala wykorzystać możliwości serwera baz danych. Korzystanie z serwera baz Gracle Database 12c jest bardzo istotne w kontekście tworzenia dynamicz­ nych i skutecznych aplikacji bazodanowych.

fava Virtual Machine)





)M

� �

Powtórzenie materiału W tym podrozdziale opisano następujące szczegóły dotyczące tła historycznego baz Oracle: •

Firma Oracle powstała z organizacji Relational Software, Inc. (RSI), która z kolei narodziła się w wy­ niku przekształcenia z Software Development Laboratories (SDL).



Język SQL jest interfejsem silnika bazodanowego Oracle Database 12c, a dodane przez firmę Oracle rozszerzenia zapewniają jej przewagę konkurencyjną.



Język PL/SQL rozszerza możliwości języka SQL i umożliwił ewolucję technologii obiektowo-relacyjnych.



PL/SQL zapewnia dostęp do wewnętrznych bibliotek Javy.



PL/SQL umożliwia tworzenie obiektowo-relacyjnych baz Oracle.



PL/SQL umożliwia programistom wykorzystanie możliwości serwera baz Oracle Database 12c.

Rozdział 1. Przegląd programowania w języku Orade PL/SQL

Rysunek 1.1. Oś czasu języka

PL/�

0

�ana przy programowaniu bazOrade�

Architektura

35

.

Architektura baz danych ma wiele poziomów. Głównym elementem baz Oracle Database 12c jest SQL. Ten język jest interfejsem dla silnika baz Oracle Database 12c (przypomina kierownicę, hamulce i de­ skę rozdzielczą samochodu). Odpowiednikiem silnika pojazdu jest oprogramowanie serwera. Obejmuje " " ono "silnik , który zapisuje i przetwarza dane, "skrzynię biegów zarządzającą transakcjami i rozbu­ dowany "licznik kilometrów", rejestrujący w plikach operacje systemu. Oprogramowanie serwera to także programy pomocnicze, które zapewniają integralność systemu. Są one odpowiednikiem opon, elementów karoserii, pianki w fotelach i zderzaków. Więcej informacji o architekturze baz Oracle Data­ base 12c znajdziesz w dodatku A. Dobry mechanik musi znać wszystkie części samochodu. Podobnie administrator bazy danych musi znać liczne komponenty związane z systemem bazodanowym Oracle. Administrator jest głów­ nym mechanikiem, który odpowiada za silnik, a czasem pomaga mu w tym programista bazy. Przez większość czasu programista zajmuje się kierowaniem samochodem, co oznacza, że koncentruje się głównie na danych i pracy z SQL-em. Podobnie jak mechanik konserwuje i ustawia silnik samochodu, aby zoptymalizować jego wydaj­ ność i zwiększyć trwałość, administrator bazy zajmuje się licznymi szczegółami silnika bazodanowe­ go w celu jak najlepszego wykorzystania możliwości baz Oracle. Wiele z tych szczegółów nie dotyczy programistów. Wynika to z tego, że programiści koncentrują się na interakcji z danymi, podobnie jak

36

Część l

Podstawy języka PL/SQL

kierowca samochodu sportowego zgłasza problemy z działaniem pojazdu, gdy te wystąpią. Choć pro­ gramiści zwracają uwagę na wydajność, często proszą o rozwiązanie problemu administratorów. Pro­ gramiści, którzy tego nie robią, często łączą role administratora i programisty. Pozwala to im zdobyć nowe umiejętności diagnostyczne. Programiści, którzy często wykonują zadania administratora, cza­ sem są nazywani administratorami aplikacji. Kierowanie samochodem wymaga umiejętności w posługiwaniu się kierownicą, pedałem gazu i ha­ " mulcami. Sterowanie "pojazdem Oracle wymaga wiedzy z zakresu instrukcji SQL-a. Niektóre z nich przypominają fabrykę i służą do tworzenia egzemplarzy bazy danych (samochodów). Inne pozwalają konserwować, reperować i ustawiać bazę. Jeszcze inne służą do interakcji z danymi- wstawiania, ak­ tualizowania, usuwania i pobierania danych w bazach. Instrukcje SQL-a przeznaczone do interakcji z danymi czasem są nazywane operacjami CRUD (ang. create, read, update i delete, czyli tworzenie, wczytywanie, aktualizowanie i usuwanie). Więcej informacji na ten temat znajdziesz w dodatku B. Programiści kierujący pojazdem Oracle często pracują nad małymi i średnimi projektami oraz mają dostęp tylko do niezbędnych tabel, widoków i programów składowanych potrzebnych w aplikacji biznesowej. Programiści aplikacji korzystają więc tylko z małego podzbioru interfejsu SQL-a, podob­ nie jak kierowcy prawdziwych samochodów koncentrują się na kierownicy, pedale gazu, hamulcach i wskaźniku ilości paliwa. Z dalszych punktów dowiesz się, jak administratorzy za pomocą języ a� '"QL zarządzają silni­ kiem i ustawiają go, a także jak programiści mogą wykorzystać ten języ o optymalizowania wy­ dajności aplikacji. Szczegóły zarządzania silnikiem i ustawiania g ie y , jednak ta książka ma ązywania problemów z zaprzede wszystkim pokazać, jak wykorzystać języki SQL i PL/SQL d kresu programowania aplikacji bazodanowych.



��

�� {��



Uwa,�a

���

W dodatku A opisano środowisko baz danyc , i h

ponenty, a także podstawowe interfejsy- interfejs CLI (ang. Command Line ce, czyli interfejs uruchamiany w wierszu poleceń) SQL *Pius i interfejs GUl�. raphical User lnterface, czyli graficzny interfejs użytkownika) Oracle SQL Deve \..!:lodatek B jest poświęcony implementacji SQL-a używanej w bazach Oracle. Jest to� ardziej kompletna implementacja w branży.





� �

Zanim wyjaśnię, jak kierować "pojazde ', chcę pokrótce przedstawić napędzający go silnik. Jeśli dopiero poznajesz bazy Oracle, powini zacząć od zrozumienia terminologii. Ponadto ten sam p�wala nimi kierować. Kod w SQL-u jest też uruchamiany język SQL, który służy do tworzenia przez dostępne w bazach Oracle kr

� � �

� � � �:�;:��

��

a d n skła awu plików i procesów oraz jednego katalogu. Bazę można utwo­ rzyć za pomocą różnych narzędzi, takich jak Oracle Database Configuration Assistant (we wszystkich systemach operacyjnych można go wywołać za pomocą instrukcji dbca). Database Configuration Assistant to jeden z programów instalowanych w warstwie serwera w trakcie instalowania produktów firmy Oracle. Te programy razem noszą nazwę systemu zarządzania relacyjną bazą danych. Database Configuration Assistant to kreator upraszczający tworzenie baz Oracle. Gdy tworzysz bazę za pomocą kreatora, generuje on wszystkie potrzebne pliki i procesy oraz ka­ talog bazy danych. Taki katalog to zbiór tabel, w którym znajdują się wszystkie informacje o struktu­ rach i algorytmach danej bazy. Prawdopodobnie słyszałeś już o katalogu (lub słowniku) metadanych, czyli o danych na temat danych. Metadane to zestaw tabel definiujących, co można w bazie zapisywać, czym można manipulować i co jest w niej dostępne. System RDBMS tworzy bazy podobnie jak fabryka produkuje samochody. Oracle Database 12c potrafi utworzyć więcej niż jeden egzemplarz bazy na serwerze, pod warunkiem że dany serwer ma wystarczającą ilość pamięci roboczej i miejsca na dysku. Dzięki używanej w Oracle Database 12c architekturze wieloorganizacyjnej można też tworzyć bazy kontenerowe (ang. eontainer database- CDB) i bazy PDB (ang. pluggable database). Najprostszym odpowiednikiem systemu RDBMS jest edytor tekstu, na przykład Microsoft Word, Corel WordPerfect czy Apple Pages. Po zainstalowaniu takiego edytora staje się on fabryką do tworze­ nia dokumentów. Takie programy można też nazwać systemami zarządzania dokumentami, jednak

Rozdział 1. Przegląd programowania w języku Orade PL/SQL

37

ich możliwości są nieco ograniczone. Zapewniają one interfejs użytkownika umożliwiający tworzenie i edytowanie dokumentów. Ten interfejs jest jak kierownica, pedał gazu, hamulce i deska rozdzielcza służące do kierowania samochodem (przy czym deska rozdzielcza oznaczałaby, że potrzebny jest gra­ ficzny interfejs użytkownika). Gracle także udostępnia interfejs użytkownika - SQL *Plus. Pierwotnie jego nazwa brzmiała AFI (ang. Advanced Friendly Interface, czyli zaawansowany przyjazny interfejs), czego odzwierciedleniem jest nazwa domyślnego bufora tymczasowych plików (afiedt.buj). Jestem doświadczonym użytkowni­ kiem i uważam, że to narzędzie nie jest ani zaawansowane (według obecnych standardów), ani specjal­ nie przyjazne. Takie wrażenie można odnieść, jeśli nie używało się wcześniej uruchamianych w wierszu poleceń interfejsów baz MySQL i SQL Server. Po ich wypróbowaniu większość programistów (w tym i ja) stwierdza, że SQL *Plus jest- przez porównanie - zarówno zaawansowany, jak i przyjazny. Narzędzie działające w wierszu poleceń to podstawowy interfejs użytkownika, jednak większość osób korzysta z interfejsów graficznych, takich jak Toad firmy Dell (drogie) lub Gracle SQL Developer (bezpłatne). Z dodatku A dowiesz się, jak zainstalować i skonfigurować narzędzia SQL *Plus i SQL Developer, a także jak z nich korzystać. Po zrozumieniu podstaw z zakresu działania połączeń (co także jest omówione w tym dodatku) używanie tych narzędzi nie sprawia trudności. Powinieneś nauczyć si' korzystać z przynajmniej jednego z tych narz,dzi, aby wydajniej posł si' bazami Oracle.



Architektura wieloorganizacyjna

��

WOracle Database 12c wprowadzono architekturę wieloorganiz ·n a�. muftitenant architecture), która przypomina kompleks mieszkalny dla egzemplarzy bazy Or Ie. oć takie kompleksy mogą być zlokalizowane w jednym budynku lub w ich grupie, zwykl są zane z jednego miejsca. Bardzo duże kompleksy mieszkalne mogą mieć scentralizowane · owne i biura lokalne w poszczególnych budynkach. Architektura wieloorganizacyjna stosowana przez nie różni się znacznie od dużych, obejcentralizowanym biurem. Baza CDB to wła­ mujących wiele budynków kompleksów mieszkalny śnie takie biuro, a bazy PDB to poszczególne b J Iokalnymi biurami. Baza CDB, przypominająca scentralizo ne ·ur w kompleksie mieszkalnym, przechowuje główne schematy i Każda baza PDB u yt ownika ADMIN, który posiada uprawnienia do danej bazy (podobnie jak schemat 1e CDB). W bazie PDB znajduje się też schemat przeznaczony dla tej konkretnej bazy. maty i lokalnego użytkownika ADMIN z bazy PDB przypominają lokalnych zar óv-.budynków w bardzo dużym kompleksie mieszkalnym. Kon­ figurowanie baz PDB opisano ku A.

� \..,;

� � �

sys system.

sy� � �

��

sys system

sysdba system



Wiersz poleceń to w · ędzie w trakcie pisania kodu produkcyjnego. Możliwe musi być wie­ lokrotne uruchamian· ·ego kodu. Oznacza to, że możliwe ma być wywoływanie już wcześniej uruchomionych instru:Kc ·. Aby spełnić ten warunek, należy połączyć zestaw powiązanych poleceń w językach SQL i PL/SQL, które wcześniej wprowadzono w trybie interaktywnym w konsoli, i zapisać je w pliku. Jest to plik skryptu. Może on na przykład usuwać tabelę przed próbą jej ponownego utwo­ rzenia. Skasowanie tabeli przed jej odtworzeniem pozwala uniknąć błędu ORA-00955, który informuje, że programista próbuje ponownie wykorzystać nazwę użytą już w katalogu danych. Skrypt można uruchomić w wierszu poleceń lub w innym skrypcie. Dlatego w punkcie "Model dwuwarstwowy" w dalszej części rozdziału opisuję, jak posługiwać się wierszem poleceń.

��

Język PL/SQL Język PL/SQL to solidne narzędzie oferujące wiele możliwości. Programista może napisać kod raz i umieścić go w bazie blisko danych. Język PL/SQL pozwala uprościć rozwój aplikacji, zoptymalizować ich działanie, a także usprawnić wykorzystanie zasobów komputera. PL/SQL nie zastępuję SQL-a, który jest opartym na zbiorach językiem deklaratywnym, umożliwiającym interakcję z danymi i bazą. PL/SQL to rozbudowany język imperatywny z mechanizmami opartymi na zdarzeniach i obiektowymi. W języku PL/SQL, podobnie jak w SQL-u, wielkość znaków nie ma znaczenia. Doprowadziło to do powstania wielu dobrych praktyk w obszarze formatowania. Zamiast przytaczać tu argumenty dowo­ dzące wyższości poszczególnych rozwiązań, zachęcamy do stosowania stylu zgodnego ze standardami

Część l

38

Podstawy języka PL/SQL

danej firmy. W kodzie PLISQL przedstawionym w tej książce instrukcje są zapisywane przy użyciu dużych

znaków, a nazwy zmiennych i kolumn oraz wywołania programów składowanych - znaków małych. W języku PL/SQL wykorzystano wiele elementów języka Ada, w tym: programowanie struktural­ ne, statyczną typizację danych, modularność, zarządzanie wyjątkami i przetwarzanie równoległe. Język Ada, rozwijany przez Ministerstwo Obrony Stanów Zjednoczonych, zaprojektowano do obsługi wbu­ dowanych systemów wojskowych działających w czasie rzeczywistym i o wysokim poziomie bezpie­ czeństwa. Takie systemy funkcjonują na przykład w samolotach i rakietach. Składnia języka Ada­ między innymi operatory przypisania i porównania oraz ograniczniki w postaci apostrofów - opiera się w dużym stopniu na języku Pascal.

Czy programowanie w języku PL/SQL to czarna magia? Język PL/SQL 1. 0 nie był niczym więcej jak narzędziem do tworzenia raportów. Obecnie sama in­ strukcja CASE języka SQL zapewnia większość funkcji dostępnych w tamtej wersji. W połowie lat 90. programiści opisywali języki z rodziny PL/SQL 2. x jako czarną magię. Wtedy to określenie było uza­ sadnione. Istniało niewiele materiałów poświęconych temu językowi, a dostępność przykładowego kodu w sieci WWW była ograniczona, ponieważ sieć nie była wtedy jeszcze tak rozbudowana, jak obecnie. ,, iezależny od baz Także i dziś niektóre osoby uważają PL/SQL za czarną magię i często wol danych kod w języku Java i innych językach programowania. Politycznie po a e jest zachęcanie do unikania rozwiązań opartych na języku PL /SQL i to mimo ich zalet. Dla�o j Oracle PL/SQL wciąż � jest uważany przez liczne osoby za skomplikowany, skoro istnieje ta I CREATE OR REPLACE LIBRARY demo IS 2 '//.'; 3 /

Za pomocą nowej składni obiekt LIBRARY można utworzyć na podstawie katalogu wirtualnego: SQL> CREATE OR REPLACE LIBRARY demo IS '' IN 2 nazwa_katalogu_wirtualnego;

Drugi argument to katalog wirtualny. Tworzenie takich katalogów opisano w podrozdziale „Katalogi wirtualne” w dodatku B. W Oracle Database 12c proces tworzenia katalogów tego rodzaju się nie zmienił.

Definiowanie tabel z obsługą wymiaru VT W Oracle Database 12c dostępna jest obecnie obsługa wymiaru VT (ang. valid time). Ten wymiar różni się od wymiaru czasu transakcji (ang. transaction time — TT). VT odpowiada dacie zdarzenia biznesowego, na przykład zatrudnienia, promocji lub zwolnienia pracownika. TT odpowiada fizycznemu momentowi wstawienia lub zaktualizowania wiersza. W Oracle Database 11g pojawiły się archiwa Flashback Data Archive, w których używano czasu TT. Technologia Flashback pozwala przyjrzeć się danym archiwalnym w celu przeanalizowania trendów w zapytaniach, różnic w raportach lub historii transakcji. Są to wymiary retrospektywne, ponieważ dane są w nich dzielone na podstawie czasu operacji. W Oracle Database 12c wprowadzono obsługę wymiaru VT w wyniku sformalizowania w definicjach tabel dwóch podejść. Jedno polega na definiowaniu okresów przez jawne przypisanie ich do kolumn. Drugie definiuje okresy w kolumnach niejawnie. Nowa składnia SQL-a związana z wymiarem VT to PERIOD FOR. Jej zastosowanie zobaczysz w przykładowych instrukcjach CREATE TABLE w dalszych punktach. Warto zauważyć, że to wymiar VT, a nie TT steruje operacjami retrospektywnymi. Za pomocą wymiaru VT można zarządzać procesem ILM (ang. Information Lifecycle Management).

Tabele z jawnie dodanymi kolumnami VT Przyjrzyj się teraz przykładowej tabeli rental. Obejmuje ona kolumny check_out_date i return_date. W wersjach starszych niż Oracle Database 12c za zarządzanie tymi kolumnami odpowiadał interfejs API aplikacji. Zawierają one ważną logikę biznesową opisującą, jak wypożyczalnia filmów wideo, na przykład Redbox, nalicza płatności. Wymiar VT pozwala teraz jawnie zidentyfikować takie kolumny:

Rozdział 2. Nowe funkcje SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

47

CREATE TABLE rental ( rental_id NUMBER GENERATED ALWAYS AS IDENTITY , customer_id NUMBER CONSTRAINT nn_rental_01 NOT NULL , check_out_date DATE CONSTRAINT nn_rental_02 NOT NULL , return_date DATE , created_by NUMBER CONSTRAINT nn_rental_03 NOT NULL , creation_date DATE CONSTRAINT nn_rental_04 NOT NULL , last_updated_by NUMBER CONSTRAINT nn_rental_05 NOT NULL , last_update_date DATE CONSTRAINT nn_rental_06 NOT NULL , PERIOD FOR rental_event (check_out_date, return_date) , CONSTRAINT pk_rental PRIMARY KEY(rental_id) , CONSTRAINT fk_rental_01 FOREIGN KEY(customer_id) REFERENCES contact(contact_id) , CONSTRAINT fk_rental_02 FOREIGN KEY(created_by) REFERENCES system_user(system_user_id) , CONSTRAINT fk_rental_03 FOREIGN KEY(last_updated_by) REFERENCES system_user(system_user_id));

Wiersze 4. i 5. to kolumny VT z logiką biznesową. W wierszu 10. do okresu związanego z regułą biznesową jawnie przypisywany jest identyfikator. Pozwala to na wywoływanie retrospektywnych zapytań dotyczących danego okresu. Oto przykładowe zapytanie z wykorzystaniem wymiaru VT: SQL> SELECT * 2 rental AS OF PERIOD FOR rental_event 3 TO_TIMESTAMP('04-AUG-2013 12:00:00 AM');

Ponadto jeśli używasz pakietu dbms_flashback_archive, możesz zastosować człon AS OF do przedziałów VT. Zachęcam do korzystania z podejścia z jawnym wskazywaniem kolumn VT.

Tabele z niejawnie definiowanymi kolumnami z czasem VT Gdy Oracle wprowadza nowe funkcje, zawsze udostępnia różne możliwości. Dotyczy to także kolumn z czasem VT. Możesz utworzyć tabelę z niejawnie definiowanymi kolumnami z czasem VT. W tym celu usuń referencje do tych kolumn. W kodzie przykładowej tabeli rental oznacza to zmianę wiersza 10. w następujący sposób: 10 , PERIOD FOR rental_event

W tym wierszu pominięto kolumny z instrukcji CREATE TABLE.

Wzbogacona składnia instrukcji LEFT OUTER JOIN w bazach Oracle Oracle Database 12c obsługuje obecnie polecenie LEFT OUTER JOIN umożliwiające podanie po lewej stronie złączenia dwóch i więcej tabel. Wcześniej można było w tym miejscu zastosować tylko jedną tabelę. W bazie Oracle Database 11g próby podania dwóch tabel powodowały błąd ORA-01417. Oto zalety tej nowej funkcji:  Podawanie tabel po lewej stronie umożliwia zmianę ich kolejności, co z kolei pozwala na generowanie lepszych planów wykonania instrukcji.  Obsługa wielu widoków upraszcza pracę programistów piszących operacje OUTER JOIN. Wadą rozbudowanej instrukcji LEFT OUTER JOIN z baz Oracle jest brak przenośności. Korzystne jest natomiast to, że można tworzyć wydajniejsze operacje OUTER JOIN.

Domyślne wartości kolumn oparte na sekwencjach Oracle Database 12c umożliwia jawne wiązanie sekwencji z tabelami. Dostępne są dwie techniki. Jedna polega na utworzeniu sekwencji i jawnym powiązaniu jej z kolumną tabeli. Druga wymaga wykorzystania kolumn identyfikacyjnych (z modyfikatorem IDENTITY; jest to następna nowa funkcja). W pierwszym z tych podejść można wykorzystać domyślne wartości z niezależnych sekwencji.

48

Część I

Podstawy języka PL/SQL

Przyjrzyj się przykładowej tabeli customer. Dla uproszczenia ma ona tylko dwie kolumny. Pierwsza to kolumna z kluczem sztucznym zawierająca kolejne wartości. Te wartości nie są powiązane z danymi z żadnej tabeli i powinny być powiązane w sposób „jeden do jednego” z kluczem naturalnym tabeli. Klucz naturalny to jedna lub — zazwyczaj — więcej kolumn, które niepowtarzalnie identyfikują każdy wiersz tabeli. W tym przykładzie kluczem naturalnym jest kolumna customer_name. W praktyce jest mało prawdopodobne, by taka kolumna była dobrym kluczem naturalnym, jednak takie założenie pozwala uprościć przykład i skoncentrować się na domyślnych wartościach kolumny. Przed utworzeniem tabeli należy przygotować sekwencję. Jest to odejście od techniki stosowanej w przeszłości w bazach Oracle, jednak tu omawiane są rozwiązania z nowego wspaniałego świata baz Oracle Database 12c. Utwórz więc sekwencję rozpoczynającą się od wartości 1: SQL> CREATE SEQUENCE customer_s;

Tę sekwencję należy utworzyć na początku, ponieważ jest ona używana w czasie tworzenia tabeli o domyślnych wartościach kolumny. SQL> CREATE TABLE customer 2 ( customer_id NUMBER DEFAULT customer_s.nextval 3 , customer_name VARCHAR2(20));

Ponieważ ten przykład ma ilustrować zarządzanie wartościami klucza głównego i klucza obcego w ramach transakcji, trzeba też utworzyć następną sekwencję i tabelę. Tu będą to sekwencja preference_s i tabela preference. Zamiast rozdzielać obie instrukcje, poniżej przedstawiono je jedna pod drugą: SQL> SQL> 2 3 4

CREATE SEQUENCE preference_s; CREATE TABLE preference ( preference_id NUMBER DEFAULT preference_s.nextval , customer_id NUMBER DEFAULT customer_s.currval , preference_name VARCHAR2(20));

Wartości sekwencji podanej po słowie DEFAULT pozwalają pominąć wyzwalacze ON INSERT. Dzięki nim można też zrezygnować z bezpośredniego stosowania pseudokolumn .nextval i .currval w instrukcjach INSERT związanych z sekwencją. Trzeba jednak pamiętać, że zależność między instrukcjami .nextval i .currval się nie zmieniła. Trzeba wywołać .nextval dla sekwencji przed wywołaniem dla niej .currval w danej sesji. Z powodu zależności między dwoma wspomnianymi pseudokolumnami sekwencji zalecam ostrożność przy stosowaniu tej techniki.

Teraz można wstawiać wiersze do obu tabel za pomocą zmodyfikowanej sygnatury (ang. override signature). Takie sygnatury to listy wszystkich wymaganych i używanych opcjonalnych kolumn, których wartości programista chce wstawić do tabeli. Przy wstawianiu danych do dwóch przykładowych tabel trzeba się upewnić, że wartości kolumn customer_id są ze sobą zgodne. Dzięki temu możliwe są złączenia równościowe między tabelami customer i preference. SQL> INSERT INTO customer (customer_name) VALUES ('Pan Nowak'); SQL> INSERT INTO preference (preference_name) VALUES ('Jasne mocne');

Oba wiersze są więc wstawiane bez bezpośrednio podanych wartości klucza sztucznego. Zobaczmy, czy baza Oracle Database 12c poprawnie radzi sobie z taką sytuacją. Proste zapytanie ze złączeniem: SQL> SELECT 2 FROM

* customer c INNER JOIN preference p USING(customer_id);

powinno dać następujący wynik: CUSTOMER_ID CUSTOMER_NAME PREFERENCE_ID PREFERENCE_NAME ----------- ------------- ------------- -------------------1 Pan Nowak 1 Jasne mocne

Wyniki są dowodem na to, że to podejście działa. Z dalszego punktu „Kolumny IDENTITY” dowiesz się, jak używać takich kolumn.

Rozdział 2. Nowe funkcje

49

Wartości domyślne przy bezpośrednim wstawianiu null Baza Oracle Database od dawna umożliwia podawanie wartości domyślnych dla dowolnej kolumny. Taką wartość można było jednak zmienić w wyniku bezpośredniego podania wartości null w instrukcji INSERT. W bazie Oracle Database 12c taką bezpośrednio podaną wartość null można zastępować wartością domyślną. SQL> CREATE TABLE essay 2 ( essay_name VARCHAR2(30) DEFAULT essay_s.nextval 3 , essayist VARCHAR2(30) 4 , published DATE DEFAULT TRUNC(SYSDATE) 5 , text CLOB 6 , CONSTRAINT essay_pk 7 PRIMARY KEY (essay_name, essayist, published));

Wiersz 4. gwarantuje, że próby pominięcia danych w kolumnie published spowodują wstawienie bieżącej daty. W dodatku C (poświęconym funkcjom wbudowanym SQL-a) wyjaśniono, że funkcja TRUNC usuwa godziny i minuty z typów danych obejmujących datę i czas. Wszystkie typy danych DATE w bazach Oracle obejmują datę i czas. Poniższa instrukcja INSERT wstawia wiersz do tabeli essay. Ta instrukcja działa tak samo w wersjach 11g i 12c bazy Oracle Database — wstawia bieżącą datę (bez godzin, minut i sekund) do kolumny published. Dzieje się tak, ponieważ kolumna published nie znajduje się na liście kolumn w zmodyfikowanej sygnaturze. INSERT INTO essay ( essay_name , essayist , text ) VALUES ('Dlaczego chcę zostać Supermanem?' ,'21-SEP-2011' ,'W pewnym okresie swojego życia każdy chciałby stać się kimś innym...');

Jeśli do zmodyfikowanej sygnatury dodasz kolumnę published, będziesz mógł bezpośrednio wstawić wartość null. Powoduje ona zastąpienie standardowej wartości domyślnej. W wersjach starszych niż Oracle Database 12c nie można było zapobiec takiemu zastępowaniu wartości. W Oracle Database 12c dostępna jest instrukcja ON NULL, która pozwala uniemożliwić bezpośrednie wstawianie wartości null do kolumny. Aby uzyskać ten efekt, należy wprowadzić następującą zmianę w wierszu 4. wcześniejszej instrukcji CREATE TABLE: 4 , published DATE DEFAULT ON NULL TRUNC(SYSDATE)

Instrukcja ON NULL powoduje, że w bazie Oracle Database 12c w kolumnie published nie można wstawić wartości null.

Kolumny IDENTITY Społeczność związana z bazami danych (czyli konkurencja) wskazywała na słabość baz Oracle, ponieważ nie obejmowały one kolumn IDENTITY (kolumn identyfikacyjnych). Takie kolumny obsługują automatyczne numerowanie wierszy i zwykle pełnią funkcję klucza sztucznego, tworzonego na podstawie sekwencji kolejnych wartości. W bazie Oracle Database 12c dostępny jest operator tożsamościowy. Jeszcze lepszą wiadomością jest to, że ta wersja bazy umożliwia generowanie wartości identyfikacyjnych. Podstawowa kolumna IDENTITY zwykle ma nazwę id, a bazy Oracle obsługują tę konwencję (chyba że programista zmieni nazwę takiej kolumny). W nazwach kolumn IDENTITY lepiej stosować przyrostek _id zamiast id.

50

Część I

Podstawy języka PL/SQL

Poniższy kod tworzy tabelę o dwóch kolumnach — identyfikacyjnej i tekstowej: SQL> CREATE TABLE identity 2 ( id NUMBER GENERATED ALWAYS AS IDENTITY 3 , text VARCHAR2(10));

Ta przykładowa tabela pozwala pominąć kolumnę id w instrukcjach INSERT. Gdyby tabela obejmowała tylko jedną kolumnę, podanie wartości kolumny id byłoby konieczne. Łatwiej napisać zmodyfikowaną sygnaturę, która jest jednym z przykładów stosowania notacji opartej na nazwach. W takich sygnaturach należy podać listę kolumn między nazwą tabeli a klauzulą VALUES lub podzapytaniem. Przykładowa tabela identity jest bardzo prosta, ponieważ domyślne ustawienie kolumny identyfikacyjnej to ALWAYS, co oznacza, że nie można ręcznie wprowadzać wartości kolumny id. Dlatego jeśli tabela nie obejmuje innych kolumn, nie można w niej wstawiać wierszy. Wstawianie wierszy do tabeli z kolumną identyfikacyjną jest możliwe tylko wtedy, gdy dana tabela ma więcej niż jedną kolumnę (tak jak w przykładzie). Poprawny sposób wywoływania instrukcji INSERT polega na pominięciu kolumny id na liście kolumn: SQL> INSERT INTO identity (text) VALUES ('Jeden');

Dlaczego firma Oracle wybrała ustawienie ALWAYS jako domyślne? W dokumentacji baz Oracle nie jest to wyjaśnione, jednak podejrzewam, że chodziło o następującą kwestię: jeśli programista użyje opcji BY DEFAULT i poda liczbę wyższą niż obecnie wygenerowana wartość sekwencji, może zduplikować wartość kolumny bez klucza głównego lub ograniczenia niepowtarzalności i spowodować nieudane wstawianie, jeśli tabela ma klucz główny lub ograniczenie niepowtarzalności. W dodatku B znajduje się punkt „Kolumny IDENTITY”, w którym opisano, jak korzystać z takich kolumn. Tu warto wspomnieć, że należy zapoznać się z klauzulą RETURNING INTO instrukcji INSERT, ponieważ sekwencja wartości dla kolumn IDENTITY jest generowana przez system i trudno dostępna. Szczegółowe informacje znajdziesz w ramce „Wiązanie kolumn IDENTITY z sekwencjami” w dodatku B. Kolumny IDENTITY zmieniają sposób pracy w bazach Oracle — przynajmniej wtedy, gdy nie musisz zapewniać obsługi starszego kodu, na przykład opartego na pakiecie Oracle E-Business Suite. Podejście zastosowane w bazie Oracle Database 12c sprawia, że można zaprzestać korzystania z pseudokolumn sequence.nextval i sequence.currval. Dzięki temu można zarządzać wartościami sztucznych kluczy głównych i zewnętrznych w zasięgu transakcji. Kolumny IDENTITY wymagają stosowania klauzuli RETURNING INTO w instrukcjach INSERT. Pozwala to zapisać w zmiennej lokalnej ostatnią wartość sekwencji z instrukcji INSERT. Następnie można ponownie wykorzystać tę zmienną i przypisać ją jako klucz obcy do zależnej tabeli. Oczywiście oparte jest to na założeniu, że wstawianie wartości do tabel odbywa się w jednostce transakcyjnej w bloku języka PL/SQL.

Większa pojemność typów łańcuchowych i typu RAW Maksymalną pojemność typów VARCHAR2, NVARCHAR2 i RAW można teraz skonfigurować w SQL-u. Aby pozostawić limit 4000 bajtów, ustaw parametr max_string_size na wartość STANDARD. Inna możliwość to ustawienie tego parametru na wartość EXTENDED. Wtedy maksymalna pojemność tych typów to 32 767 bajtów. Zaleta większego limitu pojemności tych typów powinna być oczywista dla programistów używających wcześniej bazy Oracle Database 11g. W owej wersji w języku PL/SQL typy VARCHAR2, NVARCHAR2 i RAW miały pojemność 32 767 bajtów, jednak tak dużych wartości nie można było zapisać w kolumnach tych samych typów. Obecnie jest to możliwe.

Przekazywanie wartości z instrukcji SQL-a do zewnętrznych programów W wersjach starszych niż Oracle Database 12c wyniki instrukcji SELECT trzeba było zapisywać za pomocą typu danych języka SQL lub PL/SQL. To wymagało dodatkowych kroków przy zagnieżdżaniu

Rozdział 2. Nowe funkcje

51

zapytań w programach w języku PL/SQL. Aby w programach zewnętrznych uzyskać dostęp do wyników, trzeba było stosować pasujące skalarne lub złożone typy danych. Typami złożonymi były zwykle tabele SQL-a, systemowe kursory referencyjne języka PL/SQL lub zbiory wyników z potokowych funkcji SQL-a. W Oracle Database 12c dostępna jest nowa procedura return_result z pakietu dbms_sql. Z tego punktu dowiesz się, jak korzystać z tej procedury i z tego pakietu. Nowy mechanizm działa podobnie jak technologia Shared Source Common Language Infrastructure (CLI) Microsoftu. Według artykułu „The Microsoft Shared Source CLI Implementation” Davida Stutza (ten tekst został opublikowany w marcu 2002 roku w sieci MSDN) „Microsoft opracował technologię Shared Source CLI, aby naukowcy, studenci, profesorowie i inni zainteresowani programiści mogli poznawać zaawansowaną infrastrukturę języka komputerowego, uczyć jej innych i eksperymentować z nią”. Z tego samego artykułu wynika, że Microsoft udostępnia licencję na implementację technologii Shared Source CLI każdemu, kto obieca, że będzie modyfikował kod CLI tylko na potrzeby użytku niekomercyjnego. Jednak w 2009 roku Microsoft dodał język C# i technologię CLI do listy specyfikacji objętych programem Community Promise. To powinno oznaczać (choć przyznaję, że nie jestem prawnikiem), iż każdy może bezpiecznie implementować te technologie bez obaw o pozew patentowy ze strony Microsoftu. W Wikipedii, pod poniższym adresem, znajduje się ciekawy artykuł na temat CLI: http://en.wikipedia.org/wiki/Common_Language_Infrastructure Ponieważ w dokumentacji baz Oracle nie ma żadnych informacji o kwestiach licencyjnych, wygląda na to, że firma Oracle musi ufać zapewnieniom Microsoftu z programu Community Promise lub w inny sposób rozwiązała kwestie korzystania z CLI. W CLI parametry funkcji podaje się tak: CREATE FUNCTION mydb.getConquistador (@nationality AS VARCHAR(30)) RETURNS TABLE RETURN SELECT * FROM mydb.conquistador WHERE nationality = @nationality;

Funkcja zgodna z Shared Source CLI przekazuje referencję do zbioru wyników jako wartość zwracaną funkcji. W bazach Oracle stosuje się inne podejście. Tu używane są procedury get_next_result i return_result z pakietu dbms_sql. Przyjmują one parametry przekazywane przez referencję. Specyfikacje tych procedur znajdziesz w tabeli 2.1. Tabela 2.1. Procedury z pakietu dbms_sql przekazujące niejawnie zbiory wyników Procedura

Opis

get_next_result

Procedura get_next_result przyjmuje dwa parametry. Pierwszy to parametr typu IN przekazywany przez wartość. Jest nim referencja do kursora dbms_sql. Drugi to przeciążony parametr typu OUT przekazywany przez referencję. Przyjmuje albo jeden systemowy kursor referencyjny języka PL/SQL, albo referencję do takiego kursora. Programista nie może bezpośrednio używać parametru rc w trybie OUT. Oto prototypy tej procedury: GET_NEXT_RESULT( c IN INTEGER, rc OUT SYS_REFCURSOR) GET_NEXT_RESULT( c IN INTEGER, rc OUT INTEGER)

return_result

Procedura return_result przyjmuje dwa parametry. Pierwszy to przeciążony parametr typu IN OUT przekazywany przez referencję. Parametr rc to albo jeden systemowy kursor referencyjny języka PL/SQL, kolekcja takich kursorów, referencja do takiego kursora, albo kolekcja referencji do takich kursorów. Drugi to przekazywany przez wartość parametr logiczny o wartości domyślnej TRUE. Oto prototypy tej procedury: RETURN_RESULT( rc IN OUT to_client RETURN_RESULT( rc IN OUT to_client

SYS_REFCURSOR, IN BOOLEAN [DEFAULT TRUE] ) INTEGER, IN BOOLEAN [DEFAULT TRUE] )

Poniżej przedstawiony jest program w postaci bloku anonimowego, ilustrujący zwracanie wyniku z kursora niejawnego:

52 SQL> SQL> SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15

Część I

Podstawy języka PL/SQL

COLUMN item_title FORMAT A30 COLUMN item_subtitle FORMAT A40 DECLARE /* Deklarowanie kursora. */ lv_cursor SYS_REFCURSOR; BEGIN /* Otwieranie statycznego kursora. */ OPEN lv_cursor FOR SELECT i.item_title , i.item_subtitle FROM item i WHERE REGEXP_LIKE(i.item_title,'^Star.*'); /* Wywołanie procedury dbms_sql.return_result. */ dbms_sql.return_result(lv_cursor); END; /

W wierszu 3. zadeklarowany jest systemowy kursor referencyjny języka PL/SQL. W wierszach od 6. do 10. tworzone jest statyczne zapytanie powiązane z tym kursorem W wierszu 13. ten lokalny kursor jest zwracany do klienta. Przedstawiony blok anonimowy wyświetla poniższe informacje, ponieważ wyniki z kursora są niejawnie przekazywane przez referencję z powrotem do zasięgu wywołania: ITEM_TITLE -----------------------------Star Wars I Star Wars II Star Wars III

ITEM_SUBTITLE ---------------------Phantom Menace Attack of the Clones Revenge of the Sith

Gdy anonimowy blok obejmuje dwa systemowe kursory referencyjne (lub większą ich liczbę) i dwa wywołania procedury return_result z pakietu dbms_sql (lub większą ich liczbę), zwracane są przynajmniej dwa zbiory wyników. Procedura get_next_result zwraca jeden zbiór wyników. Pojawiły się też nowe funkcje bibliotek zewnętrznych przeznaczone do pracy z niejawnymi zbiorami wyników. Na przykład w bibliotece OCI8 2.0 pojawiła się funkcja oci_get_implicit_resultset(). Można z niej korzystać razem z wszystkimi funkcjami z rodziny oci_fetch_*. Dzięki temu pojawiają się ciekawe alternatywy do stosowania systemowych kursorów referencyjnych oraz potokowych i obiektowych funkcji tabelowych. Od wersji Oracle Database 10g można tworzyć obiektowe funkcje tabelowe oraz używać funkcji TABLE do zwracania kolekcji elementów typów skalarnych i złożonych z baz Oracle jako relacyjnych zbiorów wyników.

Określanie liczby wyników i zwracanych przedziałów wyników w natywnym SQL-u W wersjach starszych od Oracle Database 12c można było tylko ograniczyć liczbę zwracanych wierszy za pomocą operacji ROWNUM. Nowe klauzule FETCH FIRST i OFFSET dają większe możliwości. W Oracle Database 12c dostępny jest rozbudowany zbiór opcji do pobierania pierwszych n wierszy. Aby ograniczyć liczbę wyników zapytania do jednego wiersza, zastosuj następujący kod: SQL> SELECT i.item_title 2 FROM item i 3 FETCH FIRST 1 ROWS ONLY;

Wiersz 3. pokazuje, jak za pomocą klauzuli FETCH FIRST pobrać tylko jeden wiersz. Najzabawniejsze jest to, że trzeba tu zastosować słowa kluczowe w liczbie mnogiej — ROWS ONLY (czyli tylko wiersze). Łatwo można się domyślić, że w celu zwrócenia pierwszych pięciu wierszy należy zmodyfikować wiersz 3. w następujący sposób: 3 FETCH FIRST 5 ROWS ONLY;

Załóżmy, że nie wiesz, ile wierszy zwróci zapytanie, a nie chcesz ograniczać liczby zwracanych wyników do 20, 50, 100 lub 500 (są to najczęściej stosowane wartości). Oracle udostępnia składnię, która

Rozdział 2. Nowe funkcje

53

pozwala pobrać określoną część wszystkich wierszy. W tym celu należy dodać do klauzuli FETCH FIRST słowo kluczowe PERCENT, tak jak w poniższej wersji wiersza 3.: 3 FETCH FIRST 20 PERCENT ROWS ONLY;

Baza Oracle Database 12c umożliwia też pominięcie zbioru wierszy przed wczytaniem ich określonej liczby. W ten sposób pobieranych jest n wierszy od określonego miejsca zbioru wyników. Potrzebną składnię przedstawia zmodyfikowana wersja wiersza 3.: 3 OFFSET 20 ROWS FETCH FIRST 20 ROWS ONLY;

Minimalna wartość klauzuli OFFSET to 0. Warto o tym pamiętać przy podawaniu parametrów dla takich zapytań. Do zastosowania parametrów w takiej instrukcji można wykorzystać zmienne wiązane: 3 OFFSET :bv_offset ROWS FETCH FIRST :bv_rows ROWS ONLY;

Jeśli chcesz zastosować parametry w zapytaniu pobierającym określoną liczbę wierszy, zawsze powinieneś używać klauzuli OFFSET, ponieważ pozwala napisać jedną instrukcję mającą dwie funkcje. Jedna polega na odczycie danych od początku zbioru rekordów (wtedy klauzula OFFSET ma wartość 0). Druga pozwala wczytać dane od dowolnego miejsca innego niż początek zbioru. Dane są wczytywane do końca tylko wtedy, gdy wartość :bv_rows jest większa od liczby pozostałych rekordów. Klauzule FETCH FIRST i OFFSET są też dostępne w blokach PL/SQL. Te klauzule można stosować w instrukcjach SELECT INTO i jako definicje kursorów statycznych. W anonimowych blokach PL/SQL można też korzystać ze zmiennych wiązanych. Poniższy fragment pokazuje, jak zastosować zapytanie SELECT INTO. SQL> 2 3 4 5 6 7 8 9 10 11 12

DECLARE /* Deklarowanie zmiennej lokalnej. */ lv_item_title VARCHAR2(60); BEGIN /* Pobieranie danych do zmiennej lokalnej. */ SELECT i.item_title INTO lv_item_title FROM item i FETCH FIRST 1 ROWS ONLY; dbms_output.put_line('['||lv_item_title||']'); END; /

Wiersz 9. pobiera tylko pierwszy wiersz z zapytania. W wierszu 9. można też zastosować klauzulę OFFSET: 9

OFFSET 5 ROWS FETCH FIRST 1 ROWS ONLY;

Jak wspomniano, w anonimowych blokach PL/SQL można stosować zmienne wiązane. Jeśli wartość zmiennej :bv_size to 1, można wywołać następujący kod: 9 OFFSET :bv_offset ROWS FETCH FIRST :bv_size ROWS ONLY;

Zmienna :bv_size musi mieć wartość 1, ponieważ instrukcja SELECT INTO może zwrócić tylko jeden wiersz. Dlatego przy większej wartości wystąpi wyjątek ORA-01422, informujący, że zapytanie zwróciło zbyt wiele wierszy. Za pomocą dynamicznego zapytania umieszczonego w zewnętrznym programie możesz wyeliminować ryzyko zwrócenia zbyt wielu wierszy. W tym celu należy wykorzystać biblioteki ODBC lub JDBC. Poniższy fragment ilustruje technikę dynamicznego pobierania n wierszy w języku PHP: 15 16 17 18 19 20 21 22 23

// Deklaracja instrukcji SQL-a. $sql = "SELECT i.item_title " . "FROM item i " . "OFFSET :bv_offset ROWS FETCH FIRST :bv_rows ROWS ONLY"; // Przygotowanie instrukcji i powiązanie dwóch łańcuchów znaków. $stmt = oci_parse($c,$sql); // Powiązanie zmiennych lokalnych z instrukcją języka PHP.

54 24 25 26 27 28

Część I

Podstawy języka PL/SQL

oci_bind_by_name($stmt, ":bv_offset", $offset); oci_bind_by_name($stmt, ":bv_rows", $rows); // Wykonanie instrukcji języka PL/SQL. if (oci_execute($stmt)) {

Następny przykład pokazuje, jak pobrać n wierszy ze środka zbioru w statycznym kursorze: SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

DECLARE /* Deklaracja zmiennej lokalnej. */ lv_item_title VARCHAR2(60); /* Deklaracja statycznego kursora. */ CURSOR c IS SELECT i.item_title FROM item i OFFSET 10 ROWS FETCH FIRST 1 ROWS ONLY; BEGIN /* Otwieranie, pobieranie, wyświetlanie i zamykanie kursora. */ OPEN c; FETCH c INTO lv_item_title; dbms_output.put_line('['||lv_item_title||']'); CLOSE c; END; /

W wierszu 8. użyty jest literał do ustawienia wartości klauzuli OFFSET i liczby zwracanych wierszy. Nie można podstawiać zmiennych za literały (a przynajmniej nie w produkcyjnej wersji bazy Oracle Database 12c Release 1). Oto próba użycia kursora dynamicznego: SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14

DECLARE /* Deklaracja zmiennej lokalnej. */ lv_item_title VARCHAR2(60); /* Deklaracja statycznego kursora. */ CURSOR c ( cv_offset NUMBER , cv_size NUMBER ) IS SELECT i.item_title FROM item i OFFSET cv_offset ROWS FETCH FIRST cv_size ROWS ONLY; BEGIN NULL; END; /

W wierszu 10. ustawiane są ograniczenia zapytania zwracającego wybraną liczbę wierszy. Używane są do tego parametry kursora: cv_offset i cv_size. Wiersz 12. zapobiega błędowi parsowania. W bloku wykonania musi znajdować się instrukcja; w przeciwnym razie parsowanie zakończy się niepowodzeniem. Ten blok jednak i tak powoduje błąd parsowania. Występuje wyjątek i odłączenie programu od aktywnej sesji. Oto wyświetlany stos błędu: ERROR: ORA-03114: not connected to ORACLE DECLARE * ERROR at line 1: ORA-03113: end-of-file on communication channel Process ID: 4148 Session ID: 31 Serial number: 3187

Jest to nieprzechwycony wyjątek. Takie wyjątki zwykle są szybko eliminowane. Z powodu takiego, a nie innego typu błędu podejrzewam, że Oracle w przyszłości rozwiąże ten problem. Możliwe też, że ten błąd zostanie udokumentowany jako ograniczenie. Do czasu opublikowania tej książki kwestia powinna zostać rozwiązana.

Rozdział 2. Nowe funkcje

55

Sterownik bazy Oracle dla aplikacji dla baz MySQL Baza Oracle Database 12c udostępnia sterownik dla aplikacji dla baz MySQL. Zastępuje on bibliotekę kliencką MySQL 5.5. Dzięki niemu można korzystać z aplikacji i narzędzi opartych na językach, które wykorzystują interfejs API w języku C bazy MySQL. Są to na przykład języki: PHP, Ruby, Perl i Python. Zaletą tego podejścia jest to, że użytkownicy mogą korzystać z aplikacji dla baz MySQL zarówno w takich bazach, jak i w bazach Oracle. Zwiększa to przenośność rozwiązań opartych na wymienionych językach skryptowych.

Instrukcje CROSS APPLY, OUTER APPLY i LATERAL w SQL-u Instrukcja APPLY SQL-a umożliwia wywoływanie funkcji tabelowych (zwracających kolekcje, których można używać jak tabel) dla każdego wiersza zwróconego przez zewnętrzne wyrażenie tabelowe zapytania. Złączenie traktuje wtedy funkcję tabelową jako prawy operand, a zewnętrzne wyrażenie tabelowe jako lewy operand. Złączenie analizuje każdy wiersz z prawej strony dla każdego wiersza z lewej strony i łączy wyniki w ostateczny zbiór wyników. Istnieją dwie odmiany tej operacji. Instrukcja CROSS APPLY to wersja złączenia wewnętrznego. Zwraca wiersze z tabeli (lub ze zbioru tabel) podanej po lewej stronie instrukcji CROSS APPLY pasujące do klauzuli WHERE zapisanej wewnątrzwierszowo po stronie prawej. Poniżej pokazano przykładowy kod ze złączeniem CROSS APPLY: SQL> SELECT i.item_title 2 FROM item i CROSS APPLY 3 (SELECT * 4 FROM rental_item ri 5 WHERE i.item_id = ri.item_id 6 OFFSET 0 ROWS FETCH FIRST 1 ROWS ONLY);

Instrukcja OUTER APPLY to odmiana złączenia lewostronnego. Tworzy ona złączenie zewnętrzne między tabelą lub zbiorem złączanych tabel a zapisanym wewnątrzwierszowo widokiem. Ten widok musi obejmować klauzulę WHERE, która określa relację między zbiorem wyników z lewej strony a podanym po prawej widokiem. Zwracane są wszystkie wiersze z tabeli podanej po lewej stronie złączenia i pasujące wyniki z kolekcji lub wartości null. Oto przykładowy kod, w którym zastosowano złączenie OUTER APPLY: SQL> SELECT i.item_title 2 FROM item i OUTER APPLY 3 (SELECT * 4 FROM rental_item ri 5 WHERE i.item_id = ri.item_id 6 OFFSET 0 ROWS FETCH FIRST 1 ROWS ONLY);

Klauzula LATERAL służy do podawania podzapytań w lateralnych widokach wewnątrzwierszowych. W klauzuli FROM zapytania można podać tabele, z którymi korelowany jest dany lateralny widok wewnątrzwierszowy. Przy stosowaniu takich widoków obowiązują pewne ograniczenia:  Nie można stosować klauzul PIVOT i UNPIVOT ani klauzul z referencjami tabel.  Nie można stosować lewostronnej korelacji, gdy lateralny widok wewnątrzwierszowy zawiera klauzulę PARTITION w zapytaniu i pojawia się po prawej stronie złączenia.  W widoku lateralnym nie można stosować lewostronnej korelacji dla pierwszej tabeli ze złączenia prawostronnego lub pełnego złączenia zewnętrznego. Klauzula LATERAL jest częścią standardu ANSI SQL i stanowi rozszerzenie składni widoków wewnątrzwierszowych baz Oracle. Choć poniższe zapytanie można łatwo przekształcić na złączenie INNER JOIN, ta wersja pozwala zademonstrować ograniczenia wyeliminowane dzięki klauzuli LATERAL z bazy Oracle Database 12c. SQL> SELECT * 2 FROM contact c CROSS JOIN 3 (SELECT *

56 4 5

Część I FROM WHERE

Podstawy języka PL/SQL member m c.member_id = m.member_id);

To zapytanie próbuje zapisać widok wewnątrzwierszowy obejmujący skorelowane podzapytanie. W efekcie generowany jest następujący komunikat o błędzie: WHERE c.member_id = m.member_id) * ERROR at line 5: ORA-00904: "C"."MEMBER_ID": invalid identifier

Ten błąd oznacza, że nie można znaleźć aliasu c tabeli contact. Widok wewnątrzwierszowy nie potrafi znaleźć tego aliasu, ponieważ staje się on dostępny dopiero po zakończeniu parsowania klauzuli FROM. To dlatego Oracle zgłasza błąd nieprawidłowego identyfikatora (szczegółowe omówienie identyfikatorów znajdziesz w rozdziale 4.). Błąd tego samego rodzaju występuje w złączeniach CROSS APPLY i OUTER APPLY. Klauzula LATERAL umożliwia korzystanie w widoku wewnątrzwierszowym z tabel podanych po lewej stronie instrukcji CROSS JOIN. Jest tak, ponieważ cały kod do słowa kluczowego LATERAL jest parsowany osobno. Rozdzielenie parsowania na dwa etapy pozwala widokowi wewnątrzwierszowemu umieszczonemu z prawej strony słowa kluczowego LATERAL na poprawne zinterpretowanie identyfikatora. To oznacza, że teraz w widokach wewnątrzwierszowych można stosować korelacje: SQL> SELECT * 2 FROM contact c CROSS JOIN 3 LATERAL(SELECT * 4 FROM member m 5 WHERE c.member_id = m.member_id);

Słowo kluczowe LATERAL z wiersza 3. pozwala podzapytaniu znaleźć tabele podane po lewej stronie instrukcji CROSS JOIN. Operacje są tu wykonywane od lewej do prawej, dlatego ta technika nie zadziała, jeśli niezrozumiały identyfikator znajduje się po prawej stronie.

Widoki z ustawieniem BEQUEATH CURRENT_USER W wersjach starszych niż Oracle Database 12c widoki zawsze działały z uprawnieniami jednostki definiującej. Takie uprawnienia są stosowane domyślnie dla funkcji, procedur, pakietów i typów. Przy definiowaniu jednostek programów składowanych można zastosować klauzulę AUTHID DEFINER, przy czym nie jest to konieczne, ponieważ to ustawienie jest domyślne. W Oracle Database 12c pojawiła się możliwość ustawiania uprawnień widoków. Domyślne ustawienie to BEQUEATH DEFINER, które działa jak opcja AUTHID DEFINER stosowana do jednostek programów składowanych. Aby zmienić domyślne uprawnienia, należy zastosować ustawienie BEQUEATH CURRENT_USER. Powtórzenie materiału W tym podrozdziale przedstawiono następujące informacje na temat nowych funkcji SQL-a dostępnych w bazie Oracle Database 12c:  W Oracle Database 12c oprócz wskazywania zmiennych środowiskowych w instrukcjach LIBRARY można też stosować katalogi wirtualne.  Oracle Database 12c umożliwia bezpośrednie i pośrednie definiowanie wymiarów VT, co zwiększa możliwości administratorów w zakresie analiz retrospektywnych.  W Oracle Database 12c wzbogacono możliwości złączeń LEFT OUTER JOIN. Obecnie mogą one obejmować wiele tabel po lewej stronie.  W Oracle Database 12c wprowadzono instrukcje CROSS APPLY, OUTER APPLY i LATERAL, przeznaczone do pracy z zagnieżdżonymi tabelami.  Oracle Database 12c udostępnia domyślne kolumny przechowujące wartości pseudokolumn .nextval i .currval dla nazwanych sekwencji.

Rozdział 2. Nowe funkcje

57

W Oracle Database 12c wprowadzono kolumny IDENTITY, zawierające automatycznie zwiększane sekwencje liczb. Takie kolumny pełnią funkcję klucza sztucznego.  W Oracle Database 12c do wartości domyślnych można dodać klauzulę ON NULL, co uniemożliwia bezpośrednie ustawianie wartości null przy wstawianiu lub aktualizowaniu wierszy tabeli.  Oracle Database 12c pozwala ustawić parametr w celu zwiększenia pojemności typów VARCHAR2, NVARCHAR2 i RAW do 32 767 bajtów, co odpowiada ich pojemności w języku PL/SQL.  Oracle Database 12c udostępnia słowo kluczowe BEQUEATH, służące do ustawiania dla widoków uprawnień jednostki definiującej lub wywołującej. 

Nowe funkcje języka PL/SQL W Oracle Database 12c do języka PL/SQL dodano liczne nowe funkcje:  Można zapisywać w pamięci podręcznej wyniki funkcji z uprawnieniami jednostki wywołującej.  Ważne usprawnienie pozwala tworzyć białe listy jednostek wywołujących dla funkcji składowanych, procedur, pakietów i typów.  Dostępna jest natywna obsługa wiązania pakietów PL/SQL i typów logicznych jako parametrów. Pojawiła się też natywna obsługa klienckich interfejsów API w zakresie typów danych języka PL/SQL.  Dodany został pakiet utl_call_stack.  W pakiecie dbms_utility znalazła się nowa procedura expand_sql_text.  Procedura parse ma nowy formalny schemat określania niepełnych nazw obiektów.  W klauzuli WITH SQL-a można stosować funkcje języka PL/SQL.  Można definiować lokalne typy języka PL/SQL i stosować je w zagnieżdżonych instrukcjach SQL-a.  Dostawca ODP.NET (ang. Oracle Data Provider for .NET) może teraz wiązać parametry typu REF CURSOR z procedurami składowanymi. W dalszych punktach znajdziesz omówienie wszystkich tych nowych funkcji języka PL/SQL.

Zapisywanie wyników funkcji z uprawnieniami jednostki wywołującej Oracle Database 12c umożliwia zapisanie w pamięci podręcznej wyników funkcji z uprawnieniami jednostki wywołującej. Do zapisywanych wyników dołączana jest tożsamość bieżącego użytkownika. To pozwala zapisać różne wyniki dla jednego programu z uprawnieniami jednostki wywołującej. To oznacza, że można zapisywać w pamięci podręcznej wyniki deterministycznych funkcji z uprawnieniami jednostki wywołującej, czyli funkcji korzystających z wartości CURRENT_USER. Wprowadzenie funkcji z uprawnieniami jednostki wywołującej zmienia sposób rozwiązywania problemów. W środowisku rozproszonym (na przykład z bazami PDB) takie funkcje pozwalają uzyskać wyższą przepustowość.

Tworzenie białych list jednostek wywołujących w programach języka PL/SQL Oracle Database 12c umożliwia przechowywanie białych list użytkowników, którzy mogą wywoływać funkcje, procedury, pakiety i typy obiektowe. Umieszczenie użytkownika na białej liście umożliwia mu wywoływanie procedur składowanych. Jest to uzupełnienie innych zabezpieczeń. Użytkownik, który ma uprawnienia do wykonywania procedury składowanej ze schematu, musi też znajdować się na liście uprawnionych użytkowników.

58

Część I

Podstawy języka PL/SQL

W dokumentacji bazy Oracle Database 12c przedstawiono nowy sposób opisywania procedur składowanych. Ogólne określenie rodzaj_jednostki oznacza tu funkcje, procedury, pakiety i typy obiektowe. Klauzula ACCESSIBLE BY służy do określania białych list dla tworzonych lub zastępowanych programów. W dokumentacji bazy Oracle przedstawiony jest następujący prototyp: [ACCESSIBLE BY (rodzaj_jednostki [schema.]nazwa_jednostki [, rodzaj_jednostki [schema.]nazwa_jednostki] [,... ]])]

Jest to bezpośredni i krótki zapis, jednak bardziej zrozumiały może okazać się rozszerzony prototyp, ponieważ słowo kluczowe zastępujące określenie rodzaj_jednostki poprzedza w nim nazwę programu składowanego: [ACCESSIBLE BY ( [{FUNCTION | PROCEDURE | PACKAGE | TYPE}] [schema.]nazwa_jednostki) [,[{FUNCTION | PROCEDURE | PACKAGE | TYPE}] [schema.]nazwa_jednostki)] [,... ]]]

Poniższy krótki przykład ilustruje, jak napisać funkcję dodającą jednostki do białej listy. Ta funkcja umieszcza na białej liście funkcję, procedurę, pakiet i typ, co pozwala przedstawić kompletny opis tego mechanizmu. SQL> 2 3 4 5 6 7 8 9 10 11 12 13

CREATE OR REPLACE FUNCTION library ( pv_message VARCHAR2 ) RETURN VARCHAR2 ACCESSIBLE BY ( FUNCTION video.gateway , PROCEDURE video.backdoor , PACKAGE video.api , TYPE video.hobbit ) IS lv_message VARCHAR2(20) := 'Witaj, '; BEGIN lv_message := lv_message || pv_message || '!'; RETURN lv_message; END; /

W wierszach od 3. do 7. zadeklarowana jest lista uprawnionych jednostek wywołujących. Każda z nich może z powodzeniem wywołać funkcję biblioteczną, natomiast nie mogą tego zrobić żadne inne funkcje, procedury, pakiety ani typy. Próba utworzenia nowej funkcji wywołującej funkcję z ustawioną białą listą, na przykład: SQL> 2 3 4 5 6

CREATE OR REPLACE FUNCTION black_knight ( pv_message VARCHAR2 ) RETURN VARCHAR2 IS BEGIN RETURN library(pv_message); END; /

spowoduje błąd kompilacji. Można wtedy wyświetlić informacje o błędzie: SQL> show errors Errors for FUNCTION BLACK_KNIGHT: LINE/COL ERROR -------- ---------------------------------------------------------4/3 PL/SQL: Statement ignored 4/10 PLS-00904: insufficient privilege to access object LIBRARY

Tworzenie białych list jednostek wywołujących to przydatne i od dawna oczekiwane usprawnienie, niedostępne na razie w żadnej innej bazie danych.

Natywna obsługa klienckich interfejsów API w zakresie typów języka PL/SQL Ta funkcja umożliwia klienckim interfejsom API baz Oracle opisywanie i wiązanie typów z pakietów języka PL/SQL i typów logicznych. Używane są do tego interfejsy API OCI i JDBC. Do wiązania i wykonywania funkcji oraz procedur języka PL/SQL można też używać aplikacji opartych na języku C.

Rozdział 2. Nowe funkcje

59

Nowy pakiet utl_call_stack języka PL/SQL W Oracle Database 12c pojawił się pakiet utl_call_stack. Udostępnia on liczne funkcje usprawniające obsługę stosu błędów. Stos błędów to sekwencja zgłoszonych wyjątków przekazanych w górę łańcucha wywołań. Zawartość tego pakietu i sposób korzystania z niego opisano w rozdziale 6.

Nowa procedura expand_sql_text w pakiecie dbms_utility W Oracle Database 12c w pakiecie dbms_utility dostępna jest nowa procedura expand_sql_text. Ta procedura umożliwia przekształcenie widoku zależnego od innych widoków w jedno zapytanie. Jest to bardzo przydatne, gdy chcesz zobaczyć kompletny obraz działania kodu. Procedurę expand_sql_text można wykorzystać do ustalenia, jak widoki oparte na innych widokach są powiązane z tabelami. Jest to najprostsze rozwiązanie oprócz ręcznego refaktoryzowania kodu widok po widoku. Problem z procedurą expand_sql_text w Oracle polega na tym, że przyjmuje ona obiekt CLOB i zwraca taki obiekt, natomiast widoki są przechowywane w kolumnach typu LONG. Przekształcenie typu LONG na CLOB nie jest łatwe. Dlatego napisałem funkcję, która zrobi to za Ciebie. Tę funkcję, long_to_clob, znajdziesz w rozdziale 10. Nawet z funkcją long_to_clob używanie procedury expand_sql_text wymaga kilku kroków, co ilustruje poniższa funkcja: SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32

CREATE OR REPLACE FUNCTION expand_view ( pv_view_name VARCHAR2 ) RETURN CLOB IS /* Deklaracje kontenerów na widoki. */ lv_input_view CLOB; lv_output_view CLOB; /* Deklaracja docelowej zmiennej (z powodu ograniczeń instrukcji SELECT INTO). */ lv_long_view LONG; /* Deklaracja dynamicznego kursora. */ CURSOR c (cv_view_name VARCHAR2) IS SELECT text FROM user_views WHERE view_name = cv_view_name; BEGIN /* Otwieranie, pobieranie i zamykanie kursora w celu pobrania tekstu widoku. */ OPEN c(pv_view_name); FETCH c INTO lv_long_view; CLOSE c; /* Przekształcanie typu LONG na CLOB. */ lv_input_view := long_to_clob(pv_view_name, LENGTH(lv_long_view)); /* Przesyłanie tekstu widoku i pobieranie kompletnego tekstu. */ dbms_utility.expand_sql_text(lv_input_view, lv_output_view); /* Zwracanie wyjściowej wartości CLOB. */ RETURN lv_output_view; END; /

Choć nie lubię stosować typu danych LONG (jest to prawdziwy „dinozaur” w bazie Oracle), jest on potrzebny do zilustrowania omawianej tu ciekawej funkcji. W wierszu 9. znajduje się deklaracja zmiennej lv_long_view typu LONG. Choć używanie kursora z parametrami jest tu pewną przesadą, warto konsekwentnie stosować się do dobrych praktyk. Nie można wykorzystać tu instrukcji SELECT INTO, ponieważ nie współdziała ona z typem LONG. Klauzula FETCH INTO pozwala na zastosowanie typu LONG, dlatego wykorzystano ją w przypisaniu w wierszu 20.

60

Część I

Podstawy języka PL/SQL

Dalej znajduje się wywołanie funkcji long_to_clob z parametrem pv_view_name i długością kolumny tekstowej widoku. Ten kod zgłasza dwa zapytania do katalogu (ponieważ funkcja long_to_clob ponawia takie zapytanie), jest to jednak konieczne, aby uniknąć przepisywania danych znak po znaku z typu LONG do typu CLOB. Oracle nie udostępnia wielu możliwości w zakresie pracy z typem LONG. Na przykład funkcja to_clob nie przyjmuje parametrów typu LONG. Pełne omówienie funkcji long_to_clob znajdziesz w rozdziale 10. Tu wystarczy zauważyć, że wykorzystuje ona pakiety dbms_sql i dbms_lob do przekształcenia danych typu LONG na typ CLOB. Więcej informacji o pakiecie dbms_sql zawiera rozdział 13. W rozdziale 10. opisano pakiet dbms_lob i pracę z dużymi obiektami. Wiersz 27. to wywołanie procedury expand_sql_text, a w wierszu 30. zwracany jest wyjściowy obiekt CLOB z procedury expand_sql_text. Ostatecznie funkcja zwraca obiekt CLOB z kompletnym zapytaniem opartym na tabelach. Po uzyskaniu tego zapytania można przeanalizować jego wydajność.

Nowy formalny schemat procedury parse w pakiecie dbms_sql Pakiet dbms_sql obejmuje nowy formalny schemat procedury parse. Ta procedura obecnie interpretuje niepełne nazwy obiektów. Dzięki temu jednostka programu z uprawnieniami jednostki definiującej może sterować określaniem nazw w uruchamianych dynamicznych instrukcjach SQL-a. Za pomocą procedury dbms_sql.parse możesz na przykład wywołać polecenie DROP TABLE w procedurze składowanej.

Funkcje języka PL/SQL w klauzuli WITH SQL-a W Oracle Database 12c można wywoływać funkcje języka PL/SQL w klauzuli WITH. Jedyny problem związany jest z uruchamianiem takich funkcji, ponieważ obejmują średniki. Załóżmy, że chcesz wywołać instrukcję w środowisku SQL*Plus. Najpierw trzeba wyłączyć domyślny symbol kończący SQL-a (średnik — ;). Służy do tego następujące polecenie tego środowiska: SET SQLTERMINATOR OFF

Następnie możesz utworzyć funkcję lokalną w instrukcji WITH: SQL> COLUMN person FORMAT A18 SQL> WITH 2 FUNCTION glue 3 ( pv_first_name VARCHAR2 4 , pv_last_name VARCHAR2) RETURN VARCHAR2 IS 5 lv_full_name VARCHAR2(100); 6 BEGIN 7 lv_full_name := pv_first_name || ' ' || pv_last_name; 8 RETURN lv_full_name; 9 END; 10 SELECT glue(a.first_name,a.last_name) AS person 11 FROM actor a 12 /

Funkcja z wierszy od 2. do 9. złącza dwa łańcuchy znaków i wstawia między nie jedną spację. Średniki są traktowane w zapytaniu jak zwykłe znaki, ponieważ wyłączono domyślny symbol kończący SQL-a. Warto też zauważyć, że w środowisku SQL*Plus instrukcje SQL-a są uruchamiane za pomocą ukośnika i że kompletna instrukcja nie ma kończącego średnika w wierszu 11. W tym prostym przykładzie tabela actor zawiera nazwiska dwóch aktorów (z filmu Iron Man), dlatego zapytanie zwraca następujące dane: PERSON -----------------Robert Downey Gwyneth Paltrow

Rozdział 2. Nowe funkcje

61

Gdy otworzysz takie zapytanie w narzędziu Oracle SQL Developer lub podobnym, mogą wystąpić pewne problemy z parsowaniem przy uruchamianiu zapytania. Najłatwiejsze rozwiązanie polega na umieszczeniu zapytania w widoku, ponieważ widok eliminuje konieczność zmiany ustawienia SQLTERMINATOR w czasie wykonywania programu. Poniższy kod tworzy widok z funkcją języka PL/SQL zagnieżdżoną w instrukcji WITH: SQL> CREATE OR REPLACE VIEW actor_v AS 2 WITH 3 FUNCTION glue 4 ( pv_first_name VARCHAR2 5 , pv_last_name VARCHAR2) RETURN VARCHAR2 IS 6 BEGIN 7 RETURN pv_first_name || ' ' || pv_last_name; 8 END; 9 SELECT glue(a.first_name,a.last_name) AS person 10 FROM actor a 11 /

Widok to nic innego jak składowane zapytanie. Widok actor_v pozwala skrócić funkcję glue o dwa wiersze. Można pominąć deklarację lv_full_name i zastąpić przypisywanie złączanych wartości do zmiennej instrukcją, która bezpośrednio je zwraca (wiersz 7.). Jeśli zechcesz uruchomić zwykłe polecenia SQL-a, z domyślnym średnikiem, ponownie włącz domyślny symbol kończący SQL-a: SET SQLTERMINATOR ON

Oczywistą zaletą klauzuli WITH jest to, że jest uruchamiana raz, po czym można ją wielokrotnie wykorzystać w zasięgu zapytania. Można też zagnieździć funkcje o zasięgu lokalnym ograniczonym do jednego zapytania. Po co stosować klauzulę WITH, skoro można wykorzystać globalną tabelę tymczasową? Tom Kyte odpowiedział na to pytanie w dziale Ask Tom (http://asktom.oracle.com), gdzie napisał, że optymalizator potrafi scalić klauzulę WITH z resztą instrukcji, natomiast w przypadku globalnej tabeli tymczasowej jest to niemożliwe.

Stosowanie w SQL-u typów danych specyficznych dla języka PL/SQL Możliwość przekazywania specyficznych dla baz Oracle typów danych z języka PL/SQL to cenna funkcja bazy Oracle Database 12c. Aby to zadziałało, potrzebna jest pewna sztuczka — należy zadeklarować zmienną lokalną w programie składowanym, a następnie wykorzystać tę zmienną w zagnieżdżonej instrukcji SQL-a. Przyjrzyj się działaniu tej techniki na przykładzie kolekcji języka PL/SQL oraz nazwanego i anonimowego bloku tego języka. Zademonstrowanie tego podejścia wymaga pięciu kroków. W szóstym kroku zobaczysz, w jakiej sytuacji pojawiają się problemy, i jednocześnie zrozumiesz, dlaczego w Oracle Database 12c potokowe funkcje tabelowe nadal są potrzebne. Pierwszy krok to utworzenie pozbawionego ciała pakietu type_defs. Specyfikacja pakietu pozbawionego ciała obejmuje tylko definicje typu i kursora. Takie pakiety stosuje się w celu współużytkowania typów i kursorów między różnymi jednostkami programu. W poniższej specyfikacji pakietu tworzona jest tylko tablica asocjacyjna specyficzna dla języka PL/SQL (jest to kolekcja z rzadkimi indeksami): SQL> CREATE OR REPLACE PACKAGE type_defs IS 2 TYPE plsql_table IS TABLE OF VARCHAR2(20) 3 INDEX BY BINARY_INTEGER; 4 END type_defs; 5 /

W drugim kroku tworzona jest tabela honeymooner, z kolumną identyfikacyjną i kolumną person. Kolumna person zawiera dane typu używanego we wspomnianej wcześniej tablicy asocjacyjnej. Dane z tej tabeli posłużą do zapełnienia tablicy asocjacyjnej z języka PL/SQL.

62

Część I

Podstawy języka PL/SQL

Oto definicja tej tabeli: SQL> CREATE TABLE honeymooner 2 ( honeymooner_id NUMBER GENERATED ALWAYS AS IDENTITY 3 , person VARCHAR2(20));

Trzeci krok wymaga wstawienia do tabeli honeymooner czterech wierszy: SQL> SQL> SQL> SQL>

INSERT INSERT INSERT INSERT

INTO INTO INTO INTO

honeymooner honeymooner honeymooner honeymooner

(person) (person) (person) (person)

VALUES VALUES VALUES VALUES

('Ralph Kramden'); ('Alice Kramden'); ('Edward Norton'); ('Thelma Norton');

Trzy pierwsze kroki tworzą pozbawiony ciała pakiet plsql_table i tabelę honeymooner oraz zapełniają tę tabelę czterema wierszami. Czwarty krok wymaga utworzenia funkcji implicit_convert, która wczytuje cztery dodane wcześniej wiersze z tabeli i zapisuje je w tablicy asocjacyjnej języka PL/SQL, a następnie zwraca tę tablicę: SQL> 2 3 4 5 6 7 8 9 10 11 12 13

CREATE OR REPLACE FUNCTION implicit_convert RETURN type_defs.plsql_table IS lv_index NUMBER := 1; -- Zmienna licznika. lv_list TYPE_DEFS.PLSQL_TABLE; -- Zmienna kolekcji. CURSOR c IS SELECT person FROM honeymooners; BEGIN FOR i IN c LOOP lv_list(lv_index) := i.person; lv_index := lv_index + 1; END LOOP; RETURN lv_list; -- Zwracanie kolekcji języka PL/SQL o zasięgu lokalnym. END; /

W wierszu 2. jako typ zwracanej wartości ustawiona jest tablica asocjacyjna języka PL/SQL. W wierszu 4. znajduje się deklaracja zmiennej lokalnej tego samego typu. Pętla zapełnia tę tablicę, a w wierszu 11. zwracana jest zmienna lokalna zawierająca tę tablicę. W piątym kroku należy zaimplementować blok anonimowy wywołujący funkcję implicit_convert. W instrukcjach tego bloku lokalna tablica asocjacyjna języka PL/SQL jest przekazywana do instrukcji SQL-a, która wczytuje tę tablicę za pomocą funkcji TABLE. Oto ten blok anonimowy: SQL> 2 3 4 5 6 7 8 9 10

DECLARE list TYPE_DEFS.PLSQL_TABLE; BEGIN list := implicit_convert; FOR i IN (SELECT column_value FROM TABLE(list)) LOOP dbms_output.put_line(i.column_value); END LOOP; END; /

W wierszu 2. zadeklarowana jest zmienna typu plsql_table z pakietu type_defs. Wiersz 4. zawiera wywołanie funkcji implicit_convert i przypisanie zwróconej tablicy asocjacyjnej języka PL/SQL do zmiennej lokalnej. Wiersze 5. i 6. obejmują instrukcję SELECT, która wykorzystuje lokalnie zadeklarowaną zmienną języka PL/SQL w funkcji TABLE. W wersjach starszych niż Oracle Database 12c funkcja TABLE potrafiła tylko przekształcać tablice VARRAY lub tabele zagnieżdżone na zbiór wyników SQL-a. Obecnie funkcja TABLE potrafi przekształcić lokalną zmienną z tablicą asocjacyjną języka PL/SQL w zasięgu SQL-a. Jeśli umieścisz wiersz 4. (ze zmienną lokalną) w komentarzu i zastąpisz zmienną lokalną wywołaniem funkcji implicit_convert w wierszu 6., wystąpi błąd. Oto te zmiany: 4 5 6

-- list := implicit_convert; FOR i IN (SELECT column_value FROM TABLE(implicit_convert)) LOOP

Rozdział 2. Nowe funkcje

63

Te zmiany prowadzą do następującego stosu błędu: FROM TABLE(implicit_convert)) LOOP * ERROR at line 6: ORA-06550: line 6, column 28: PLS-00382: expression is of wrong type ORA-06550: line 6, column 22: PL/SQL: ORA-22905: cannot access rows from a non-nested table item ORA-06550: line 5, column 13: PL/SQL: SQL Statement ignored ORA-06550: line 7, column 26: PLS-00364: loop index variable 'I' use is invalid ORA-06550: line 7, column 5: PL/SQL: Statement ignored

Jest też dobra wiadomość — można przekształcić tablicę asocjacyjną języka PL/SQL przez opakowanie jej w potokową funkcję tabelową. Takie funkcje nadal są potrzebne w języku PL/SQL. Załóżmy, że chcesz usunąć pozbawiony ciała pakiet, w którym zdefiniowałeś tablicę asocjacyjną języka PL/SQL. Wymaga to zmodyfikowania kodu w bloku anonimowym: SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

DECLARE TYPE local_table IS TABLE OF VARCHAR2(20) INDEX BY BINARY_INTEGER; lv_index NUMBER := 1; -- Counter variable. lv_list LOCAL_TABLE; -- Local PL/SQL collection. CURSOR c IS SELECT person FROM honeymooners; BEGIN FOR i IN c LOOP lv_list(lv_index) := i.person; lv_index := lv_index + 1; END LOOP; FOR i IN (SELECT column_value FROM TABLE(lv_list)) LOOP dbms_output.put_line(i.column_value); END LOOP; END; /

Ten blok także nie zadziała, ale nie, jak miało to miejsce wcześniej, z powodu próby przetwarzania tablicy asocjacyjnej zwróconej przez funkcję języka PL/SQL (choć na podstawie stosu błędu można uznać, że w obu sytuacjach przyczyna problemów jest taka sama). Tym razem źródłem błędu jest to, że typ języka PL/SQL nie jest zdefiniowany w katalogu bazy danych i baza Oracle nie potrafi go znaleźć. To oznacza, że baza nie wie, co ma przekształcić na analogiczny typ SQL-a. Choć firma Oracle nie wyjaśnia szczegółów procesu przekształcania typów, można spróbować domyślić się, jak on przebiega. Zgaduję, że baza Oracle odwzorowuje niejawną kolekcję z języka PL/SQL na jawną tabelę zagnieżdżoną SQL-a. Jeśli na tym etapie książki ten opis wydaje Ci się zbyt techniczny, nie martw się. W rozdziale 6. znajdziesz szczegółowe omówienie złożonych typów danych (kolekcji). Oto krótkie podsumowanie: można przypisać lokalną zmienną języka PL/SQL do lokalnego kontekstu SQL-a. Jednak obecnie nie można przypisać w ten sposób nielokalnej tablicy asocjacyjnej języka PL/SQL zwróconej przez funkcję.

Niejawne wiązanie parametrów REF CURSOR Dostawca ODP.NET potrafi teraz niejawnie wiązać parametry REF CURSOR procedur składowanych. Dzieje się tak, gdy programista poda metadane w plikach konfiguracyjnych platformy .NET. Powtórzenie materiału W tym podrozdziale przedstawiono następujące zagadnienia związane z nowymi funkcjami języka PL/SQL w bazie Oracle Database 12c:

64

Część I

        

Podstawy języka PL/SQL

Oracle Database 12c umożliwia zapisywanie w pamięci podręcznej wyników funkcji o uprawnieniach jednostki wywołującej. Oracle Database 12c umożliwia tworzenie białych list z jednostkami wywołującymi funkcje składowane, procedury, pakiety i typy obiektowe. Oracle Database 12c udostępnia natywną obsługę klienckich interfejsów API w zakresie typów danych języka PL/SQL. Oracle Database 12c udostępnia nowy mechanizm zarządzania stosem błędów — pakiet utl_ call_stack. Oracle Database 12c umożliwia rozwinięcie kodu widoków zależnych od innych widoków. Służy do tego procedura expand_sql_text z pakietu dbms_utility. Pakiet dbms_sql obejmuje nowy formalny schemat, który pozwala określać niepełne nazwy obiektów. Oracle Database 12c obsługuje zagnieżdżanie funkcji języka PL/SQL w instrukcjach w klauzuli WITH SQL-a. Oracle Database 12c umożliwia stosowanie w lokalnych instrukcjach SQL-a lokalnych zmiennych o typach danych z języka PL/SQL. Oracle Database 12c obsługuje w dostawcy ODP.NET niejawne wiązanie typu danych REF CURSOR z języka PL/SQL.

Skrypty pomocnicze W tym podrozdziale opisane są powiązane z tą książką programy dostępne w witrynach wydawnictw McGraw-Hill Professional i Helion.  Plik dynamic_topnquery.php zawiera kompletny przykładowy program, którego fragmenty przedstawiono w tym rozdziale.  Plik white_list.sql obejmuje wszystkie funkcje, procedury, pakiety i typy potrzebne w pokazanych w tym rozdziale przykładach ilustrujących białe listy.  Plik expanding_view.sql zawiera funkcje potrzebne do przekształcania typu LONG na typ CLOB i wywoływania omówionej w tym rozdziale procedury dbms_utility.expand_sql_text.

Podsumowanie W tym rozdziale zapoznałeś się z nowymi funkcjami bazy Oracle Database 12c. W dalszych rozdziałach także znajdziesz informacje o różnicach między starszymi wersjami baz Oracle a bazą Oracle Database 12c.

Test wiedzy Test wiedzy to zestaw pytań typu „prawda czy fałsz” i wielokrotnego wyboru, dzięki którym sprawdzisz, jak dobrze opanowałeś materiał z poszczególnych rozdziałów. Odpowiedzi na pytania znajdziesz w dodatku I.

Prawda czy fałsz? 1. __Wymiar VT określa moment zatwierdzenia transakcji. 2. __Można zdefiniować kolumnę z wartościami domyślnymi, w której do generowania sekwen-

cji używana jest pseudokolumna .nextval. 3. __Można zdefiniować kolumnę z wartościami domyślnymi, w której do generowania sekwen-

cji używana jest pseudokolumna .currval. 4. __Pseudokolumna .currval nie jest już zależna od wcześniejszego wywołania pseudokolumny .nextval w sesji.

Rozdział 2. Nowe funkcje

65

5. __Oracle Database 12c nie umożliwia zapobiegania bezpośredniemu ustawieniu wartości null

w instrukcji INSERT, co oznacza, że można zastąpić wartość domyślną kolumny. 6. __Kolumny identyfikacyjne umożliwiają automatyczne generowanie wartości kolumny z klu-

czem sztucznym. 7. __W bazie Oracle Database 12c typy danych VARCHAR2, NVARCHAR2 i RAW zawsze mają 32 767 bajtów. 8. __Dzięki zmianom wprowadzonym w bazie Oracle Database 12c funkcja języka PL/SQL może zwrócić tablicę asocjacyjną tego języka bezpośrednio do instrukcji SQL-a. 9. __Oracle Database 12c obsługuje pobieranie n pierwszych wyników zapytania bez konieczności określania przedziału pobieranych danych. 10. __Można umieścić funkcję języka PL/SQL w klauzuli WITH zapytania i wywoływać ją w programach zewnętrznych.

Pytania wielokrotnego wyboru 11. Które z poniższych słów kluczowych można stosować przy definiowaniu widoku? Poprawnych

może być kilka odpowiedzi. A. Słowa kluczowe AUTHID DEFINER. B. Słowa kluczowe BEQUEATH INVOKER. C. Słowa kluczowe AUTHID CURRENT_USER. D. Słowa kluczowe BEQUEATH DEFINER. E. Wszystkie z powyższych. 12. Które z poniższych stwierdzeń dotyczących zapisywania w pamięci podręcznej wyników funkcji z uprawnieniami jednostki wywołującej są prawdziwe? Poprawnych może być kilka odpowiedzi. A. Dla poszczególnych jednostek wywołujących istnieją różne zbiory wyników. B. Dla każdej jednostki wywołującej istnieje ten sam zbiór wyników. C. Funkcja z uprawnieniami jednostki wywołującej, której wyniki są zapisywane w pamięci podręcznej, musi być deterministyczna. D. Funkcja z uprawnieniami jednostki wywołującej, której wyniki są zapisywane w pamięci podręcznej, może być niedeterministyczna. E. Wszystkie z powyższych. 13. Które z poniższych stwierdzeń dotyczących przekształcania tekstu rozwijanych instrukcji SQL-a z typu LONG na typ CLOB są prawdziwe w kontekście pracy z widokami CDB_, DBA_, ALL_ i USER_ VIEWS w bazie Oracle Database 12c? Poprawnych może być kilka odpowiedzi. A. Możesz używać funkcji wbudowanej to_lob do przekształcania typu LONG na typ CLOB. B. Możesz używać funkcji wbudowanej to_clob do przekształcania typu LONG na typ CLOB. C. Możesz używać pakietu dbms_sql do przekształcania typu LONG na typ VARCHAR2. D. Możesz używać funkcji wbudowanej length do określania długości danych typu LONG. E. Możesz używać pakietu dbms_lob do tworzenia tymczasowych danych typu CLOB. 14. Które z poniższych stwierdzeń dotyczących typów danych języka PL/SQL dostępnych w zagnieżdżonych instrukcjach SQL-a są prawdziwe? Poprawnych może być kilka odpowiedzi. A. Typ danych języka PL/SQL musi być zadeklarowany w pakiecie. B. Instrukcja SQL-a musi być zagnieżdżona w bloku języka PL/SQL, w którym zdefiniowany jest określony typ. C. Typ danych języka PL/SQL musi być zdefiniowany lokalnie. D. Typ danych języka PL/SQL może być zwracany przez funkcję tego języka. E. Wszystkie z powyższych.

66

Część I

Podstawy języka PL/SQL

15. Która z poniższych instrukcji umożliwia dostęp do sztucznego klucza głównego z kolumny

identyfikacyjnej w celu użycia wartości tego klucza w dalszej instrukcji INSERT jako wartości klucza obcego? A. Instrukcja RETURN INTO. B. Instrukcja RETURNING INTO. C. Instrukcja .nextval. D. Instrukcja .currval. E. Żadne z powyższych.

ROZDZIAŁ

3

Podstawy języka PL/SQL

Aby nauczyć się programować w języku PL/SQL w bazie Oracle Database 12c, najpierw musisz zrozumieć podstawowe komponenty tego języka. Przedstawia je ten rozdział. Następne rozdziały szczegółowo dowodzą, że język ten to niezawodne narzędzie o wielu możliwościach. W ramach wprowadzenia do języka PL/SQL w tym rozdziale przedstawiono i pokrótce opisano:  strukturę bloków,  zmienne w blokach,  podstawowe typy skalarne i złożone,  struktury sterujące,  wyjątki,  operacje masowe,  funkcje, procedury i pakiety,  zasięg transakcji,  wyzwalacze bazodanowe. W języku PL/SQL, podobnie jak w SQL-u, wielkość znaków nie ma znaczenia. Mimo to obowiązuje wiele konwencji związanych z pisaniem kodu w tym języku. Nie istnieje jedno standardowe podejście. Większość programistów używa wyrazów pisanych wielką literą, małą literą, tylko dużymi literami i notację wielbłądzią.

Struktura bloków Język PL/SQL rozwinięto na podstawie programowania strukturalnego, statycznej typizacji danych, modularności i zarządzania wyjątkami. Jest on wzbogaconą wersją języka Ada, będącego z kolei rozszerzeniem języka Pascal, z którego pochodzą między innymi operatory przypisania i porównania oraz ograniczanie łańcuchów znaków za pomocą apostrofów. W odróżnieniu od wielu innych współczesnych języków programowania, gdzie do wyznaczania bloków programu używane są nawiasy klamrowe ({}), w PL/SQL służą do tego słowa kluczowe. Podstawowy prototyp anonimowych i nazwanych bloków języka PL/SQL przedstawia rysunek 3.1. Bloki anonimowe mają ograniczone zastosowania i nie posiadają definicji w katalogu danych. Programy nazwane są przechowywane w katalogu bazy danych i są jednostkami wielokrotnego użytku w bazie danych.

68

Część I

Podstawy języka PL/SQL

Rysunek 3.1. Struktura bloku anonimowego

Sekcja wykonawcza Jak ilustruje to prototyp z rysunku 3.1, w bloku anonimowym języka PL/SQL niezbędna jest tylko sekcja wykonawcza. Sekcja ta rozpoczyna się od instrukcji BEGIN, a kończy w miejscu wystąpienia opcjonalnego bloku EXCEPTION lub instrukcji END. Średnik kończy anonimowy blok w języku PL/SQL, a ukośnik powoduje jego wykonanie. Konwencje używane w kodzie PL/SQL w tej książce Polecenia w kodzie PL/SQL są pisane przy użyciu wyłącznie dużych znaków, a nazwy zmiennych i kolumn oraz wywołania programów składowanych — za pomocą liter małych.

Sekcja deklaracji może zawierać definicje i deklaracje zmiennych, definicje typów języka PL/SQL zdefiniowanych przez użytkownika (ang. User Defined Types — UDT), definicje kursorów, definicje kursorów referencyjnych i definicje lokalnych funkcji oraz procedur. Sekcja wykonawcza może obejmować operacje przypisania wartości do zmiennych, inicjowania obiektów, struktury warunkowe, struktury iteracyjne, zagnieżdżone bloki anonimowe języka PL/SQL i wywołania lokalnych lub składowanych nazwanych bloków języka PL/SQL. Sekcja obsługi wyjątków to miejsce na kod do obsługi błędów, który może obejmować wszystkie elementy dozwolone w sekcji wykonawczej. Wszystkie instrukcje, niezależnie od bloku, w którym się znajdują, muszą kończyć się średnikiem.

Podstawowa struktura bloku Najprostszy blok języka PL/SQL nie wykonuje żadnych operacji. Każdy blok wykonawczy musi zawierać przynajmniej jedną instrukcję, nawet jeśli jest to tylko instrukcja NULL. Ukośnik powoduje wykonanie anonimowego bloku języka PL/SQL. Poniższy fragment ilustruje najprostszy program w formie bloku anonimowego. Nie robi on nic oprócz wykonania się bez zgłaszania błędów. SQL> BEGIN 2 NULL; 3 END; 4 /

Rozdział 3. Podstawy języka PL/SQL

69

Blok bez sekcji wykonawczej prowadzi do zgłoszenia wyjątku, ponieważ PL/SQL nie obsługuje pustych bloków. Na przykład poniższy blok anonimowy prowadzi do błędu: SQL> BEGIN 2 END; 3 /

Zgłaszany jest wtedy następujący wyjątek: END; * ERROR at line 2: ORA-06550: line 2, column 1: PLS-00103: Encountered the symbol "END" when expecting one of the following: ( begin case declare exit for goto if loop mod null pragma raise return select update while with SQL> 2 3 4

SET SERVEROUTPUT ON SIZE 1000000 BEGIN dbms_output.put_line('Witaj, świecie!'); END; /

Zmienna środowiskowa SERVEROUTPUT interfejsu SQL*Plus to bufor na dane wyjściowe, a funkcja dbms_output.put_line() wyświetla wiersz takich danych. Wszystkie deklaracje, instrukcje i bloki muszą kończyć się średnikiem. Aby uruchomić blok anonimowy, należy wywołać go w środowisku SQL*Plus. Symbol @ w tym środowisku powoduje wczytanie i wykonanie pliku skryptu. Domyślnie takie pliki mają rozszerzenie .sql, ale można je zastąpić innym. To oznacza, że można wywoływać pliki bez rozszerzenia .sql. Jeśli jest to dla Ciebie nowość, w dodatku A znajdziesz samouczek dotyczący narzędzi SQL*Plus i SQL Developer. Teraz możesz uruchomić utworzony program w bieżącym katalogu roboczym, w którym otworzyłeś środowisko SQL*Plus: @hello_world.sql

To spowoduje wyświetlenie w konsoli następującego tekstu: Witaj, świecie!

W języku PL/SQL można używać komentarzy jedno- i wielowierszowych. Dwa myślniki rozpoczynają komentarz jednowierszowy: -- To komentarz jednowierszowy.

Ograniczniki /* i */ wydzielają komentarze wielowierszowe: /* To komentarz wielowierszowy. Styl i wcięcia powinny odpowiadać standardom używanym w firmie. */

70

Część I

Podstawy języka PL/SQL

W PL/SQL są dwa rodzaje programów: w postaci bloków anonimowych oraz w formie bloków nazwanych. Oba typy obejmują sekcję (blok) deklaracji, wykonawczą i obsługi wyjątków. Bloki anonimowe służą do tworzenia skryptów wsadowych, czyli kolekcji instrukcji SQL-a i anonimowych bloków języka PL/SQL uruchamianych jako jednostka programu. Bloki nazwane to trwale przechowywane jednostki programów, działające podobnie jak biblioteki współużytkowane z innych języków programowania. Bloki anonimowe można stosować w skryptach i zagnieżdżać w innych, nazwanych jednostkach programów. Zasięg bloku anonimowego ogranicza się do kontekstu nadrzędnej jednostki programu lub skryptu. Bloków anonimowych nie można wywoływać za pomocą nazw z poziomu innych bloków, ponieważ  jak wskazuje na to określenie anonimowe  nie mają one nazw. Prawie wszystkie zmienne przekazywane są do tych bloków przez referencję. Wyjątkiem są tu zmienne podstawiane. Zwykle są to literały liczbowe lub znakowe, które nie wymagają alokacji pamięci typowej dla zmiennych. Zmienne podstawiane można przekazywać do bloków anonimowych tylko przy wywoływaniu ich w środowisku SQL*Plus. Podstawowy przebieg pracy bloków anonimowych przedstawia rysunek 3.1. Blok anonimowy ze zmienną podstawianą wygląda tak: SQL> BEGIN 2 dbms_output.put_line('['||'&input'||']'); 3 END; 4 /

Ampersand (&) to domyślna wartość zmiennej środowiskowej DEFINE w SQL*Plus. Określa, że dalszy tekst to nazwa zmiennej podstawianej (chyba że wyłączysz tę zmienną w SQL*Plus). To oznacza, że gdy uruchomisz dynamiczny blok anonimowy, SQL*Plus wyświetli następującą prośbę: Enter value for input:

Jeśli wpiszesz łańcuch „Witaj, świecie Linuksa”, blok anonimowy wyświetli wtedy następujący tekst: [Witaj, świecie Linuksa!]

W wywołaniu funkcji dbms_output.put_line symbol potoku (używany do scalania łańcuchów) łączy nawiasy kwadratowe z wprowadzonym tekstem. Zauważ, że w podawanej wartości można umieścić spację bez konieczności ujmowania tekstu w cudzysłów. To, co wpiszesz w odpowiedzi na prośbę środowiska SQL*Plus, staje się wartością zmiennej podstawianej &input. Oczekiwane jest, że zmienne podstawiane będą wartościami liczbowymi, dlatego wstawiany łańcuch znaków trzeba umieścić między apostrofami. Jeśli wpiszesz Żegnaj i zapomnisz zastosować apostrofów, wystąpi następujący wyjątek: dbms_output.put_line('['||Żegnaj||']'); * ERROR at line 2: ORA-06550: line 2, column 29: PLS-00201: identifier 'ŻEGNAJ' must be declared ORA-06550: line 2, column 3: PL/SQL: Statement ignored

Błąd PLS-00201 nie jest łatwy do zrozumienia, chyba że wiesz, czym są identyfikatory w programie. Ten błąd informuje, że łańcuch znaków ŻEGNAJ nie jest identyfikatorem. Dokładne omówienie identyfikatorów znajdziesz w rozdziale 4. Na razie wystarczy zapamiętać, że są to słowa zarezerwowane, wstępnie zdefiniowane identyfikatory, identyfikatory ujęte w cudzysłów, zmienne zdefiniowane przez użytkownika, podprogramy i typy zdefiniowane przez użytkownika. W Oracle można też stosować zmienne sesji (zmienne wiązane), przypominające zmienne podstawiane z bloków anonimowych języka PL/SQL. Zmienne sesji różnią się od zmiennych podstawianych, ponieważ działają w zasięgu kontekstu połączenia lub sesji bazy danych. Deklarowanie zmiennych wiązanych w sesji SQL*Plus przebiega tak: VARIABLE zmienna_wiązana VARCHAR2(20)

Operatorem przypisania w języku PL/SQL jest dwukropek połączony ze znakiem równości (:=). Literały znakowe w tym języku są umieszczane między apostrofami. Literały z datami, liczbami i łań-

Rozdział 3. Podstawy języka PL/SQL

71

cuchami znaków są opisane w rozdziale 4. Aby w bloku języka PL/SQL przypisać wartość do zmiennej wiązanej, umieść dwukropek przed nazwą tej zmiennej: SQL> BEGIN 2 :bind_variable := 'Witamy na Kryptonie.'; 3 dbms_output.put_line('['||:bind_variable||']'); 4 END; 5 /

Wiersz 2. przypisuje łańcuch znaków Witamy na Kryptonie. do zmiennej wiązanej z poziomu sesji, a wiersz 3. wyświetla tę wartość. Po przypisaniu wartości do zmiennej sesji :bind_variable można pobrać tę wartość, poprzedzając nazwę zmiennej dwukropkiem: SELECT :bind_variable FROM dual;

Można zadeklarować zmienną wiązaną w sesji, przypisać wartość w blokach języka PL/SQL, a następnie uzyskać dostęp do zmiennej w instrukcjach SQL-a i innych blokach języka PL/SQL. Więcej informacji na temat korzystania ze zmiennych sesji znajdziesz w punkcie „Ustawianie zmiennych sesji w języku PL/SQL” w dodatku A.

Sekcja deklaracji W bloku anonimowym opcjonalna sekcja deklaracji zaczyna się od słowa kluczowego DECLARE i kończy się słowem kluczowym BEGIN. Na początku bloku deklaracji należy podać nazwę podprogramu (na przykład funkcji lub procedury), listę formalnych parametrów, a także — w przypadku funkcji — zwracany typ. Procedury, w odróżnieniu od funkcji, nie zwracają wartości. Procedury działają podobnie jak funkcje i metody zwracające wartość void w językach C, C++, C# i Java. Więcej informacji na temat podprogramów znajdziesz w dalszej części rozdziału, w punkcie „Funkcje, procedury i pakiety”. Poniższy blok anonimowy deklaruje zmienną lokalną lv_input w sekcji deklaracji i przypisuje do tej zmiennej wartość zmiennej :bind_variable. To oznacza, że najpierw należy uruchomić wcześniejszy program, aby ustawić zmienną wiązaną, a dopiero potem można uruchomić niniejszy program. SQL> 2 3 4 5 6 7

DECLARE lv_input VARCHAR2(30); BEGIN lv_input := :bind_variable; dbms_output.put_line('['||lv_input||']'); END; /

Wiersz 2. definiuje zmienną lv_input jako łańcuch znaków o zmiennej długości. Wiersz 4. przypisuje wcześniej zainicjowaną zmienną :bind_variable do zmiennej lokalnej lv_input, a następnie wyświetla wartość tej zmiennej lokalnej w wierszu 5.

Sekcja obsługi wyjątków Ostatnia z omawianych sekcji to opcjonalna sekcja obsługi wyjątków. Ta sekcja zarządza zgłoszonymi błędami czasu wykonania, a ogólna procedura obsługi wyjątków może zarządzać dowolnymi błędami. Do przechwytywania błędów służy blok WHEN, a blok WHEN OTHERS przechwytuje dowolne błędy zgłoszone w programie. Poniższy fragment ilustruje, w jaki sposób sekcja obsługi wyjątków zarządza błędami (na przykład błędem niezadeklarowania identyfikatora): SQL> 2 3 4 5 6 7

BEGIN dbms_output.put_line('['||&input||']'); EXCEPTION WHEN OTHERS THEN dbms_output.put_line(SQLERRM); END; /

Wiersz 2. umieszcza zmienną podstawianą &input w łańcuchu znaków złączanym za pomocą symboli potoku, przy czym zmienna nie jest ujęta w apostrofy. Nawet po dodaniu sekcji obsługi wyjątków

72

Część I

Podstawy języka PL/SQL

program zgłasza błąd, ponieważ problem dotyczy tu parsowania, a sekcja obsługi wyjątków przechwytuje tylko wyjątki czasu wykonania. Jedyny sposób rozwiązania problemu to zastąpienie nazwy &input określeniem '&input' między symbolami potoku. Powtórzenie materiału W tym podrozdziale opisano następujące zagadnienia związane z blokami:  W języku PL/SQL wymagana jest jedna sekcja — wykonawcza. Inaczej niż w większości współczesnych języków programowania, gdzie stosuje się nawiasy klamrowe, w języku PL/SQL sekcja wykonawcza zaczyna się słowem kluczowym BEGIN, a kończy słowem EXCEPTION.  Są dwie sekcje opcjonalne — deklaracji i obsługi błędów.  Sekcja deklaracji rozpoczyna się od słowa kluczowego DECLARE (w bloku anonimowym) lub od sygnatury podprogramu (w bloku nazwanym; takie bloki omówiono w punkcie „Funkcje, procedury i pakiety” w dalszej części rozdziału), a kończy się słowem kluczowym BEGIN.  Sekcja obsługi wyjątków rozpoczyna się od słowa kluczowego EXCEPTION, a kończy słowem kluczowym END.  PL/SQL obsługuje komentarze jedno- i wielowierszowe.  W blokach anonimowych można stosować zmienne podstawiane. Jeśli nie są one umieszczone w apostrofach, przyjmuje się, że zawierają wartość liczbową.  W blokach anonimowych można stosować zmienne wiązane poziomu sesji. Po ich zdefiniowaniu w środowisku SQL*Plus taka zmienna ma określony typ danych (wprowadzenie do środowiska SQL*Plus znajdziesz w dodatku A).

W następnym podrozdziale opisano zasięg zmiennych i przypisywanie do nich wartości. Zobaczysz tu, jak rozwinąć przedstawiony wcześniej krótki przykład i jak zarządzać błędami czasu wykonania.

Działanie zmiennych w blokach PL/SQL obsługuje zmienne skalarne i złożone. Zmienne skalarne przechowują tylko jedną wartość, natomiast w zmiennych złożonych można zapisać więcej elementów. W tym rozdziale opisano zasięg i działanie zmiennych w blokach anonimowych, zagnieżdżonych blokach anonimowych, lokalnych blokach nazwanych i składowanych blokach nazwanych.

Bloki anonimowe Nazwy zmiennych muszą rozpoczynać się od litery i mogą składać się ze znaków alfabetu, cyfr (od 0 do 9) i symboli $, _ oraz #. Zmienne mają zasięg lokalny. Oznacza to, że są dostępne tylko w zasięgu danego bloku języka PL/SQL. Wyjątkiem od tej reguły są zagnieżdżone bloki anonimowe, które działają w obrębie bloku nadrzędnego i mają dostęp do używanych w nim zmiennych. Nie będzie to jednak możliwe, jeśli programista zadeklaruje w zagnieżdżonym bloku anonimowym nową zmienną o nazwie użytej już w bloku zewnętrznym. Dwa przykładowe bloki anonimowe są przedstawione dalej, w punkcie „Zagnieżdżone bloki anonimowe”. Ilustrują one zagadnienia dotyczące zasięgu zmiennych w programach języka PL/SQL. Zmienne skalarne i złożone Zmienne skalarne przechowują jednocześnie tylko jeden element i często zawierają wartości typów prostych (liczby, łańcuchy znaków i znaczniki czasu). Znaczniki czasu w Oracle to daty określane z precyzją do jednej tysięcznej sekundy. Można też definiować zmienne złożone (w języku angielskim stosuje się tu dwa pojęcia, compound i composite; mają one bardzo podobne znaczenie, przy czym w dokumentacji Oracle Database 12c używane jest pojęcie composite). W tej książce określenie „zmienne złożone” dotyczy tablic, struktur i obiektów. Zmienne złożone są zbudowane z typów prostych języka programowania.

Rozdział 3. Podstawy języka PL/SQL

73

Zadeklarowanie zmiennej liczbowej bez bezpośredniego przypisania do niej wartości powoduje, że wartość początkowa to null. Jest tak, ponieważ sekcja deklaracji robi dwie rzeczy:  definiuje zmienną w wyniku określenia jej nazwy i typu danych,  niejawnie przypisuje do zmiennej wartość null. Ponieważ PL/SQL to język ze ścisłą kontrolą typów, przypisuje wartość null bezpośrednio do każdej zmiennej, która nie ma określonej wartości. W tym języku trzeba zdefiniować wszystkie zmienne, co oznacza, że należy je zadeklarować przez podanie nazwy i typu oraz przypisać im wartość. Poniższy prototyp bloku anonimowego pokazuje, że wartość zmiennej można przypisać później, w sekcji wykonawczej: SQL> 2 3 4 5 6

DECLARE lv_sample NUMBER; BEGIN dbms_output.put_line('Wartość to ['||lv_sample||']'); END; /

Ten kod powoduje wyświetlenie literału znakowego bez żadnych symboli między nawiasami kwadratowymi. Zauważ, że w wierszu 4. lokalnie zadeklarowana zmienna nie musi być umieszczona między apostrofami, ponieważ ma zadeklarowany typ danych. Dane wyjściowe z tego programu to: Wartość to []

Można też zdefiniować zmienną o wartości różnej od null. W tym celu należy bezpośrednio przypisać daną wartość. W sekcji deklaracji można przypisać wartość domyślną za pomocą operatora przypisania lub słowa zarezerwowanego DEFAULT (obie te techniki działają tak samo) umieszczonego po typie danych. Inna możliwość to zadeklarowanie zmiennej o wartości null i przypisanie nowej wartości w sekcji wykonawczej. Oto prototyp: DECLARE lv_sample [CONSTANT] NUMBER [:= | DEFAULT ] 1; BEGIN ... END; /

W sekcji deklaracji nie trzeba przypisywać wartości. Zwykle ustawia się je tylko dla stałych lub zmiennych, które mają działać jak stałe. Zmienne funkcjonują jak stałe, gdy spełnione są dwa warunki:  zmienna jest zadeklarowana za pomocą statycznej wartości w sekcji deklaracji,  programista nie zmienia wartości zmiennej w sekcji wykonawczej ani w sekcji obsługi wyjątków. Przypisywanie niezmiennej (stałej) wartości do zmiennej to przypisanie statyczne. Tę technikę stosuje się rzadziej od przypisania dynamicznego, kiedy to wartości są przypisywane w czasie wykonywania programu i mogą się zmieniać. Ważna uwaga — nigdy nie powinno się przypisywać dynamicznych wartości w bloku deklaracji, ponieważ błędy związane z takim przypisaniem nie są przechwytywane w lokalnej sekcji obsługi wyjątków. Lokalna sekcja tego rodzaju obsługuje błędy czasu wykonania występujące w sekcjach wykonawczej i obsługi wyjątków; sekcja deklaracji nie jest tu uwzględniana. Przyjrzyj się teraz krótkiemu przykładowi źle zaprojektowanego przypisania dynamicznego. Używana jest tu znana już zmienna podstawiana '&input' (w apostrofach) ze środowiska SQL*Plus: SQL> 2 3 4 5 6 7 8 9

DECLARE lv_input VARCHAR2(10) := '&input'; BEGIN dbms_output.put_line('['||lv_input||']'); EXCEPTION WHEN OTHERS THEN dbms_output.put_line(SQLERRM); END; /

74

Część I

Podstawy języka PL/SQL

Gdy pojawi się prośba o wprowadzenie wartości, wpisz długi tekst, który nie zmieści się w używanym w kodzie typie łańcuchowym o zmiennej długości: Enter value for input: James Tiberius Kirk

Ten program przechodzi etap parsowania, ponieważ zmienna podstawiana jest umieszczona w apostrofach. Program wyświetla więc wprowadzoną wartość zmiennej podstawianej: old 2: lv_input VARCHAR2(10) := '&input'; new 2: lv_input VARCHAR2(10) := 'James Tiberius Kirk';

Następnie kod zgłasza w zasięgu wywołania błąd czasu wykonania: DECLARE * ERROR at line 1: ORA-06502: PL/SQL: numeric or value error: character string buffer too small ORA-06512: at line 2

Ten błąd oznacza, że sekcja obsługi wyjątków została zignorowana. Wynika to z tego, że przypisania z bloku deklaracji nie są uwzględniane przy obsłudze błędów czasu wykonania. Przeniesienie dynamicznego przypisania z wiersza 2. (z sekcji deklaracji) do wiersza 4. (do sekcji wykonawczej) pozwala umieścić przypisanie w zasięgu czasu wykonania programu. Ta prosta zmiana umożliwia przechwycenie i obsłużenie wyjątku w sekcji obsługi wyjątków. SQL> 2 3 4 5 6 7 8 9 10

DECLARE lv_input VARCHAR2(10); BEGIN lv_input := '&input'; dbms_output.put_line('['||lv_input||']'); EXCEPTION WHEN OTHERS THEN dbms_output.put_line(SQLERRM); END; /

Teraz wprowadzenie pełnego imienia i nazwiska kapitana Kirka (z fikcyjnego świata z serialu Star Trek) spowoduje zgłoszenie obsłużonego wyjątku zamiast wyświetlania pełnego stosu błędów (omówienie stosu błędów znajdziesz w rozdziale 7.). ORA-06502: PL/SQL: numeric or value error: character string buffer too small

Funkcja SQLERRM zwraca tylko błąd przypisania, a nie kompletny stos błędów, które spowodowały zwrócenie sterowania do zasięgu wywołania. Omówienie tej funkcji znajdziesz w rozdziale 7. i dodatku C. Na razie zapamiętaj tylko, żeby nigdy nie stosować dynamicznych przypisań w sekcji deklaracji. Ta reguła dotyczy także sekcji deklaracji w funkcjach i procedurach składowanych. Przypisaniami dynamicznymi w sekcjach deklaracji zarządza się trudniej, ponieważ każda jednostka wywołująca musi przewidywać skutki takich przypisań i je obsługiwać. Dynamiczne przypisywanie ma też inne cechy, które trzeba uwzględnić w programie. Załóżmy, że chcesz przypisać liczbę rzeczywistą 4,67 do zmiennej typu NUMBER: SQL> 2 3 4 5 6 7 8 9 10

DECLARE lv_input INTEGER; BEGIN lv_input := 4.67; dbms_output.put_line('['||lv_input||']'); EXCEPTION WHEN OTHERS THEN dbms_output.put_line('['||SQLERRM||']'); END; /

Ten kod nie powoduje wyjątku, choć przypisywana wartość jest odpowiednia dla typu DOUBLE, FLOAT lub NUMBER, a typ danej zmiennej (lewego operandu) to INTEGER. Ten program wyświetla tekst: Wartość to [5]

Rozdział 3. Podstawy języka PL/SQL

75

Model przypisań i język Wszystkie języki programowania umożliwiają przypisywanie wartości do zmiennych. Zwykle wartość jest przypisywana do zmiennej podanej po lewej stronie. To dlatego ciężarówka znajduje się po lewej, a towar jest ładowany po stronie prawej. Towar to wartość przypisywana do zmiennej. Proces przypisania polega na załadowaniu towaru na ciężarówkę (czyli na powiązaniu wartości ze zmienną).

Prototyp ogólnej operacji przypisania z dowolnego języka programowania wygląda tak: lewy_operand operator_przypisania prawy_operand ogranicznik_instrukcji

To powoduje przypisanie prawego operandu do lewego operandu. W języku PL/SQL ta operacja wygląda tak: lewy_operand := prawy_operand;

Lewym operandem zawsze musi być zmienna. Prawym operandem może być wartość, zmienna lub funkcja. Funkcje używane jako prawy operand muszą zwracać wartość. W języku PL/SQL wygodne jest to, że wszystkie funkcje zwracają wartość. Problem polega na tym, że w instrukcjach SQL-a można wywoływać tylko funkcje zwracające dane typów SQL-a. Funkcje zwracające dane typów z języka PL/SQL można stosować tylko w blokach tego języka. W instrukcji SELECT INTO możliwe jest przypisanie „od prawej do lewej”. Oto prototyp takiej instrukcji: SELECT [ literał | wartość_kolumny ] INTO zmienna_lokalna FROM [ nazwa_tabeli | nazwa_pseudotabeli ] WHERE instrukcje_porównania;

Poniższy fragment przypisuje literał znakowy do zmiennej lokalnej: SQL> SQL> 2 3

VARIABLE sv_reader VARCHAR2(20) SELECT 'Witaj, czytelniku.' AS "Output" INTO :sv_reader FROM dual;

Ten kod wyświetli: Output ------------Witaj, czytelniku.

Choć przypisanie „od prawej do lewej” jest niestandardowe, stanowi cenną możliwość. Tę technikę najczęściej stosuje się przy zwracaniu jednej wartości skalarnej lub zbioru kolumn w jednym wierszu z kursora SQL-a. Kursor SQL-a to struktura języka PL/SQL, która umożliwia dostęp do wyników zapytania wiersz po wierszu lub za pomocą operacji masowej.

Wynika to z tego, że Oracle Database 12c i wcześniejsze wersje tej bazy niejawnie rzutują wartości między różnymi typami danych. W przypisaniu z wiersza 4. używany jest typ danych docelowej zmiennej, podanej po lewej stronie przypisania. Ponieważ liczby całkowite nie mogą mieć części dziesiętnych, w przypisaniu wartość jest zaokrąglana. Jeśli zmodyfikujesz program i przypiszesz wartość 4,49, Oracle Database 12c zaokrągli ją w dół. Oracle 11g wykonuje wiele niejawnych operacji rzutowania. Niestety, nie są one zgodne z powszechną regułą: rzutuj niejawnie, jeśli nie występuje utrata precyzji. Oznacza to, że przypisanie liczby 4,67 do zmiennej typu całkowitoliczbowego prowadzi do utraty części 0,67 z pierwotnej wartości. Dostępne są też liczne funkcje umożliwiające jawne rzutowanie, a których stosowanie jeszcze bardziej naraża programistę na utratę precyzji liczb. Należy starannie rozważyć jawne rzutowanie zmiennych na typy o niższej precyzji. Funkcje do obsługi jawnego rzutowania zostały opisane w dodatku C.

76

Część I

Podstawy języka PL/SQL

Istnieje też szereg typów danych specyficznych dla produktu. Obsługują one różne komponenty z rodziny Oracle 12c. Typy te opisano w podręczniku Oracle Database PL/SQL Packages and Type Reference. Operator przypisania to nie jedyny operator w języku PL/SQL. W rozdziale 4. scharakteryzowano wszystkie operatory porównywania, łączenia, logiczne i matematyczne. Należy używać:  symbolu równości (=) do sprawdzania, czy wartości są takie same;  standardowych symboli większości lub mniejszości ze znakiem równości albo bez niego (>, >=, < lub 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

DECLARE -- Deklaracja zmiennej lokalnej. lv_input VARCHAR2(30) DEFAULT 'ZEWNĘTRZNY'; BEGIN -- Wyświetlanie wartości zmiennej przed blokiem wewnętrznym. dbms_output.put_line('Blok zewnętrzny ['||lv_input||']'); -- Blok zagnieżdżony. BEGIN -- Wyświetlenie wartości zmiennej przed przypisaniem. dbms_output.put_line('Blok wewnętrzny ['||lv_input||']'); -- Przypisanie nowej wartości do zmiennej. lv_input := 'WEWNĘTRZNY'; -- Wyświetlanie wartości po przypisaniu. dbms_output.put_line('Blok wewnętrzny ['||lv_input||']'); END; -- Wyświetlanie wartości po bloku zagnieżdżonym. dbms_output.put_line('Blok zewnętrzny ['||lv_input||']'); EXCEPTION WHEN OTHERS THEN dbms_output.put_line('Wyjątek ['||SQLERRM||']'); END; /

Rozdział 3. Podstawy języka PL/SQL

77

W wierszu 3. znajduje się deklaracja zmiennej lv_input o początkowej wartości ZEWNĘTRZNY. Zasięg tej zmiennej to zewnętrzny blok w bloku anonimowym, co oznacza, że do zmiennej można przypisywać nowe wartości w obu blokach — zewnętrznym i wewnętrznym. Wiersz 14. bloku wewnętrznego przypisuje do zmiennej lv_input nową wartość (WEWNĘTRZNY). Program przed przypisaniem w bloku wewnętrznym wyświetla pierwotną wartość zmiennej lv_input: Blok zewnętrzny [ZEWNĘTRZNY] Blok wewnętrzny [ZEWNĘTRZNY]

Po przypisaniu w bloku wewnętrznym wyświetlana jest zmodyfikowana wartość: Blok wewnętrzny [WEWNĘTRZNY] Blok zewnętrzny [WEWNĘTRZNY]

To ilustruje, że zagnieżdżone bloki anonimowe mają uprawnienia do odczytu i do zapisu względem zmiennych zdefiniowanych w zasięgu zewnętrznym. Zmienna lv_outer zachowuje wartość przypisaną w bloku zagnieżdżonym, ponieważ używana jest tylko jedna zmienna, a jej zasięg jest określony w bloku zewnętrznym. Istnieje pewien wyjątek od tej reguły zasięgu zmiennej w blokach anonimowych. Dotyczy on sytuacji, gdy dwie zmienne o tej samej nazwie są zdefiniowane w blokach zewnętrznym i wewnętrznym. W następnym przykładzie nazwę zmiennej lv_input zmodyfikowano na lv_outer, a ponadto utworzono nową zmienną lv_active w blokach zewnętrznym i zagnieżdżonym. Nazwa lv_active została wybrana dlatego, że w momencie uruchomienia programu powstają dwie takie zmienne. Jedna z nich jest dostępna w bloku zewnętrznym i we wszystkich blokach zagnieżdżonych, natomiast druga — tylko w bloku zagnieżdżonym, w którym jest tworzona. SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31

DECLARE -- Deklaracja zmiennej lokalnej. lv_outer VARCHAR2(30) DEFAULT 'ZEWNĘTRZNY'; lv_active VARCHAR2(30) DEFAULT 'ZEWNĘTRZNY'; BEGIN -- Wyświetlanie wartości przed blokiem wewnętrznym. dbms_output.put_line('Zewnętrzny ['||lv_outer||']['||lv_active||']'); -- Blok zagnieżdżony. DECLARE -- Deklaracja zmiennej lokalnej. lv_active VARCHAR2(30) DEFAULT 'WEWNĘTRZNY'; BEGIN -- Wyświetlanie wartości przed przypisaniem. dbms_output.put_line('Wewnętrzny ['||lv_outer||']['||lv_active||']'); -- Przypisywanie nowej wartości do zmiennej. lv_outer := 'WEWNĘTRZNY'; -- Wyświetlanie wartości po przypisaniu. dbms_output.put_line('Wewnętrzny ['||lv_outer||']['||lv_active||']'); END; -- Wyświetlanie wartości po bloku zagnieżdżonym. dbms_output.put_line('Zewnętrzny ['||lv_outer||']['||lv_active||']'); EXCEPTION WHEN OTHERS THEN dbms_output.put_line('Wyjątek '||SQLERRM||']'); END; /

Wiersze 3. i 4. to deklaracje zmiennych lv_outer i lv_active w bloku zewnętrznym. Ich domyślna wartość to ZEWNĘTRZNY. Wiersz 12. zawiera deklarację zmiennej lv_active bloku wewnętrznego. Wartość tej zmiennej to WEWNĘTRZNY. Wiersz 19. przypisuje wartość WEWNĘTRZNY do zmiennej lv_outer.

78

Część I

Podstawy języka PL/SQL

Ten program wyświetla następujące informacje: Zewnętrzny Wewnętrzny Wewnętrzny Zewnętrzny

[ZEWNĘTRZNY][ZEWNĘTRZNY] [ZEWNĘTRZNY][WEWNĘTRZNY] [WEWNĘTRZNY][WEWNĘTRZNY] [WEWNĘTRZNY][ZEWNĘTRZNY]

Ten program przed wejściem do anonimowego bloku zagnieżdżonego wyświetla początkowe wartości zmiennych lv_outer i lv_active. Następnie wyświetla wartości zmiennych po sekcji deklaracji bloku zagnieżdżonego. Zauważ, że na tym etapie modyfikowana jest tylko wartość zmiennej lv_active, ponieważ w tym zasięgu używana jest wartość z sekcji deklaracji zagnieżdżonego bloku anonimowego. Po przypisaniu wartości WEWNĘTRZNY do zmiennej lv_outer obie zmienne mają tę wartość. Po wyjściu z zagnieżdżonego bloku anonimowego zmienna lv_active ponownie przyjmuje wartość ZEWNĘTRZNY, ponieważ tu używana jest zmienna z zewnętrznego bloku anonimowego. W tym punkcie zobaczyłeś, jak działa zasięg zmiennych w zagnieżdżonych blokach anonimowych. Choć w tym programie zewnętrzną jednostką jest blok anonimowy, działanie zmiennych i dostęp do nich wyglądają tak samo, gdy zagnieżdżony blok anonimowy znajduje się w podprogramach składowanych (w funkcjach, procedurach, pakietach lub typach obiektowych).

Lokalne bloki nazwane Programista ma do wyboru dwa rodzaje bloków nazwanych — funkcje i procedury. Funkcje zwracają wartość i zwykle używa się ich jako prawego operandu przy przypisywaniu wartości do zmiennych od prawej do lewej. Procedury to funkcje, które nie zwracają wartości. Są one odpowiednikiem metod Javy zwracających wartość void. Lokalne funkcje i procedury są przydatne tylko w zasięgu jednostki programu, w której się znajdują. Takie funkcje i procedury możesz zaimplementować w sekcji deklaracji bloku anonimowego lub nazwanego. Ponadto funkcje lokalne można implementować w funkcjach i procedurach składowanych typów obiektowych. Tę technikę stosuje się przy tworzeniu ciała obiektu, co opisano w rozdziale 11. Poniższy przykładowy program zawiera procedurę lokalną w anonimowym bloku języka PL/SQL. Tu kod zagnieżdżonego bloku z poprzedniego punktu jest przekształcony na procedurę, co pozwala zrozumieć zasięg zmiennych w lokalnych procedurach. W przykładowym programie wykorzystano zmienne lv_outer i lv_active w podobny sposób jak w poprzednim punkcie. W tym miejscu nieuniknione jest więc przedwczesne omówienie podstaw funkcji i procedur, których pełny opis znajdziesz w dalszej części rozdziału. SQL> DECLARE 2 -- Deklaracja zmiennej lokalnej. 3 lv_outer VARCHAR2(30) DEFAULT 'ZEWNĘTRZNY'; 4 lv_active VARCHAR2(30) DEFAULT 'ZEWNĘTRZNY'; 5 -- Lokalna procedura bez parametrów formalnych. 6 PROCEDURE local_named IS 7 -- Deklaracja zmiennej lokalnej. 8 lv_active VARCHAR2(30) DEFAULT 'WEWNĘTRZNY'; 9 BEGIN 10 -- Wyświetlanie wartości przed przypisaniem. 11 dbms_output.put_line( 12 'Wewnętrzny ['||lv_outer||']['||lv_active||']'); 13 14 -- Przypisanie nowej wartości do zmiennej. 15 lv_local := 'WEWNĘTRZNY'; 16 17 -- Wyświetlanie wartości po przypisaniu. 18 dbms_output.put_line( 19 'Wewnętrzny ['||lv_outer||']['||lv_active||']'); 20 END local_named; 21 22 BEGIN 23 -- Wyświetlenie wartości przed blokiem wewnętrznym. 24 dbms_output.put_line(

Rozdział 3. Podstawy języka PL/SQL

79

25 'Zewnętrzny '||lv_outer||']['||lv_active||']'); 26 27 -- Wywołanie lokalnie zadeklarowanej procedury nazwanej. 28 local_named; 29 30 -- Wyświetlanie wartości po bloku zagnieżdżonym. 31 dbms_output.put_line( 32 'Zewnętrzny ['||lv_outer||']['||lv_active||']'); 33 EXCEPTION 34 WHEN OTHERS THEN 35 dbms_output.put_line('Wyjątek ['||SQLERRM||']'); 36 END; 37 /

Wiersze od 6. do 20. obejmują lokalnie zdefiniowaną procedurę local_named. Ta procedura nie ma parametrów formalnych i obejmuje kod z używanego wcześniej bloku zagnieżdżonego. Ten kod wyświetla wartości zmiennych lv_outer i lv_active przed przypisaniem nowej wartości do pierwszej z tych zmiennych i po wykonaniu tej operacji. Zauważ, że w lokalnej procedurze nie ma deklaracji zmiennej lv_outer. To oznacza, że przypisanie dotyczy zmiennej lv_outer zdefiniowanej w kontekście wywołania (czyli w zewnętrznym bloku anonimowym). Wiersz 27. wywołuje procedurę lokalną. Program po uruchomieniu zwraca następujące dane: Zewnętrzny Wewnętrzny Wewnętrzny Zewnętrzny

[ZEWNĘTRZNY][ZEWNĘTRZNY] [ZEWNĘTRZNY][WEWNĘTRZNY] [WEWNĘTRZNY][WEWNĘTRZNY] [WEWNĘTRZNY][ZEWNĘTRZNY]

Jak widać, dane wyjściowe są takie same jak po uruchomieniu bloku zagnieżdżonego z poprzedniego punktu. Dzieje się tak, ponieważ funkcje i procedury lokalne mają dostęp do zmiennych zadeklarowanych w bloku wywołującym, w którym są zdefiniowane. Różnica między procedurą lokalną a blokiem zagnieżdżonym może wydawać się niewielka, jednak procedura lokalna umożliwia wywołanie jednego kodu wielokrotnie w tym samym programie. Podejście oparte na umieszczaniu kodu w nazwanych jednostkach to modułowość. Ta technika zwykle prowadzi do zwiększenia przejrzystości kodu. Problem z zagnieżdżonymi blokami nazwanymi polega na tym, że nie są one publikowane. To oznacza, że funkcja lub procedura może wywołać inną taką jednostkę, która jeszcze nie jest zdefiniowana. Tego rodzaju problem projektowy to błąd zasięgu. Prowadzi on do wyjątku czasu kompilacji PLS-00313. Przyczyną błędów zasięgu jest głównie to, że w języku PL/SQL stosowane jest jednoprzebiegowe parsowanie. To oznacza, że kompilator jednokrotnie wczytuje kod źródłowy (od początku do końca). Dlatego identyfikatory, na przykład nazwy funkcji i procedur, trzeba zdefiniować przed ich wywołaniem; w przeciwnym razie wystąpi błąd czasu wykonania. Poniższy kod wywołuje błąd czasu kompilacji PLS-00313, ponieważ procedura jack wywołuje funkcję hector przed miejscem jej zdefiniowania: SQL> 2 3 4 5 6 7 8 9 10 11 12 13

DECLARE PROCEDURE jack IS BEGIN dbms_output.put_line(hector||', świecie!'); END jack; FUNCTION hector RETURN VARCHAR2 IS BEGIN RETURN 'Witaj'; END hector; BEGIN jack; END; /

Wiersze od 2. do 5. to definicja procedury lokalnej jack. W wierszu 4. w tej procedurze znajduje się wywołanie funkcji hector. Ta funkcja na tym etapie bloku anonimowego nie jest jeszcze zdefiniowana, dlatego zgłaszany jest błąd zasięgu:

80

Część I

Podstawy języka PL/SQL

dbms_output.put_line(hector||', świecie!'); * ERROR at line 4: ORA-06550: line 4, column 26: PLS-00313: 'B' not declared in this scope ORA-06550: line 4, column 5: PL/SQL: Statement ignored

Jak wspomniano, jest to błąd czasu kompilacji, ponieważ wszystkie bloki anonimowe są parsowane przed wykonaniem, a parsowanie to operacja z czasu kompilacji. Parsowanie to proces rozpoznawania identyfikatorów. Identyfikatory to słowa zarezerwowane, identyfikatory wbudowane, identyfikatory w cudzysłowach, zmienne zdefiniowane przez użytkownika, podprogramy lub typy zdefiniowane przez użytkownika. Nazwy bloków nazwanych to także identyfikatory. Funkcja hector nie jest rozpoznawana jako identyfikator, ponieważ PL/SQL wczytuje identyfikatory do pamięci w trakcie jednokrotnego przechodzenia przez kod od początku do końca. Ten problem można rozwiązać za pomocą referencji uprzedzających (ang. forward references). Taka referencja do funkcji lub procedury wymaga podania samej sygnatury funkcji lub procedury; implementacja nie jest wtedy konieczna. Referencja uprzedzająca przypomina interfejs z Javy. Takie prototypy są w języku PL/SQL nazywane namiastkami (ang. stub). Namiastki powodują dodanie nazwy podprogramu do przestrzeni nazw (listy identyfikatorów), dzięki czemu kompilator zaakceptuje dany identyfikator, nawet jeśli nie doszedł jeszcze do parsowania jego implementacji. W poniższym przykładzie znajdują się referencje uprzedzające dla wszystkich lokalnych funkcji i procedur. Zachęcam do tego, aby zawsze dodawać namiastki w programach, w których w zasięgu lokalnym implementowane są bloki nazwane. SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15

DECLARE PROCEDURE jack; FUNCTION hector RETURN VARCHAR2; PROCEDURE jack IS BEGIN dbms_output.put_line(b||', świecie!'); END jack; FUNCTION hector RETURN VARCHAR2 IS BEGIN RETURN 'Witaj'; END hector; BEGIN jack; END; /

Wiersze 2. i 3. to namiastki procedury jack i funkcji hector. Parsowanie zmodyfikowanego programu przebiega prawidłowo, ponieważ kompilator może poprawnie określić wszystkie symbole w trakcie jednokrotnego przejścia przez blok anonimowy. Pamiętaj, że choć zagnieżdżone bloki nazwane są bardzo przydatne, to jeśli wywołują siebie nawzajem, niezbędne jest dodanie namiastek.

Największym zagrożeniem związanym z lokalnymi nazwanymi blokami języka PL/SQL jest to, że zastępują one nazwane funkcje i procedury z poziomu schematu, choć nie powinno się tak dziać. Oto prosta ogólna reguła dotycząca tworzenia lokalnych podprogramów: podprogram może być lokalny, jeśli nie będzie potrzebny w innych modułach.

Składowane bloki nazwane Składowane bloki nazwane to podprogramy (funkcje i procedury). Często nazywa się je funkcjami lub procedurami z poziomu schematu. Funkcje składowane zwracają wartość i zwykle są używane jako prawy operand w przypisaniach wartości do zmiennej w modelu „od prawej do lewej”. Procedury składowane to funkcje, które nie zwracają wartości. Aby zdefiniować funkcję lub procedurę w bazie

Rozdział 3. Podstawy języka PL/SQL

81

danych, należy skompilować daną jednostkę jako obiekt schematu. W efekcie powstaje niezależny komponent. Procedura składowana, w odróżnieniu od lokalnej procedury z bloku anonimowego z poprzedniego punktu, ma dostęp tylko do wartości parametrów przekazanych do niej w momencie wywołania. Próba zastosowania zmiennej spoza tego zasięgu, na przykład zmiennej lv_outer, prowadzi do błędu kompilacji. Zmienną lv_outer trzeba zadeklarować wewnątrz funkcji. Ma to taki sam wpływ na zasięg co zadeklarowanie zmiennej lokalnej w bloku zagnieżdżonym. Poniższy przykład ilustruje, jak zdefiniować niezależną procedurę składowaną. Jest to utworzona na poziomie schematu wersja procedury local_named, działająca tak samo jak zagnieżdżona wersja procedury przedstawiona w poprzednim punkcie. Szczegółowe omówienie procedur składowanych znajdziesz w punkcie „Funkcje, procedury i pakiety” w dalszej części rozdziału. SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

CREATE OR REPLACE PROCEDURE local_named IS -- Deklarowanie zmiennej lokalnej. lv_active VARCHAR2(30) DEFAULT 'WEWNĘTRZNY'; lv_outer VARCHAR2(30) DEFAULT ' '; BEGIN -- Wyświetlanie wartości przed przypisaniem. dbms_output.put_line( 'Wewnętrzny ['||lv_outer||']['||lv_active||']'); -- Przypisanie nowej wartości do zmiennej. lv_outer := 'WEWNĘTRZNY'; -- Wyświetlanie wartości po przypisaniu. dbms_output.put_line( 'Wewnętrzny ['||lv_outer||']['||lv_active||']'); END local_named; /

Wiersz 4. deklaruje zmienną lv_outer jako łańcuch znaków składający się ze spacji. Wiersz 11. przypisuje do zmiennej lokalnej lv_outer nową wartość i zastępuje łańcuch spacji. Więcej o składni tworzenia i zastępowania w programach składowanych dowiesz się z punktu „Wykonywanie bloków nazwanych” z dodatku A. Zwróć uwagę na lokalną definicję zmiennej lv_outer w wierszu 4. Poniższy anonimowy blok wywołuje przedstawioną wcześniej procedurę składowaną, ponieważ nie istnieje procedura lokalna o tej samej nazwie. SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

DECLARE -- Deklaracja zmiennej lokalnej. lv_outer VARCHAR2(30) DEFAULT 'ZEWNĘTRZNY'; lv_active VARCHAR2(30) DEFAULT 'ZEWNĘTRZNY'; BEGIN -- Wyświetlanie wartości przed blokiem wewnętrznym. dbms_output.put_line('Zewnętrzny ['||lv_outer||']['||lv_active||']'); -- Wywołanie procedury nazwanej. local_named; -- Wyświetlanie wartości po bloku zagnieżdżonym. dbms_output.put_line('Zewnętrzny ['||lv_outer||']['||lv_active||']'); EXCEPTION WHEN OTHERS THEN dbms_output.put_line('Wyjątek ['||SQLERRM||']'); END; /

Wiersz 11. wywołuje procedurę składowaną local_named i wyświetla następujące dane: Zewnętrzny Wewnętrzny Wewnętrzny Zewnętrzny

[ZEWNĘTRZNY][ZEWNĘTRZNY] [ ][WEWNĘTRZNY] [WEWNĘTRZNY][WEWNĘTRZNY] [ZEWNĘTRZNY][ZEWNĘTRZNY]

82

Część I

Podstawy języka PL/SQL

Pierwszy wiersz wyświetla wartości z bloku anonimowego. Drugi wiersz wyświetla wartości zmiennej lokalnej przed przypisaniem łańcucha znaków WEWNĘTRZNY. Trzeci wiersz pokazuje wartości zmiennych po ich przypisaniu w procedurze lokalnej. W ostatnim wierszu wyświetlane są pierwotne wartości z bloku anonimowego. Dzięki dodaniu parametrów do procedury można przekazywać wartości z zewnętrznego zasięgu do zasięgu procedury. Można też przekazywać z zewnętrznego zasięgu do procedury referencje i zwracać zmodyfikowane wartości do zewnętrznego zasięgu wywołania. Jest to opisane w punkcie „Funkcje, procedury i pakiety” w dalszej części rozdziału. Wiesz już, jak przypisywać wartości do zmiennych i jak wygląda zasięg zmiennych w blokach anonimowych i nazwanych. W następnym punkcie opisano podstawowe informacje na temat skalarnych (łańcuchowych, dat i liczbowych) i złożonych typów danych. Powtórzenie materiału W tym podrozdziale omówiono następujące zagadnienia dotyczące zmiennych, przypisywania i zasięgu:  Nazwy zmiennych rozpoczynają się od litery i mogą obejmować litery, cyfry oraz symbole $, _ i #.  Zmienne są dostępne w blokach anonimowych i nazwanych, w których są zadeklarowane, a także w blokach anonimowych i nazwanych zagnieżdżonych w bloku zewnętrznym z daną zmienną.  Nazwa zmiennej jest unikatowa w ramach bloku anonimowego lub nazwanego. Deklaracja identycznej zmiennej w zagnieżdżonym bloku anonimowym lub nazwanym powoduje zastąpienie zmiennej z bloku zewnętrznego, który zawiera definicję danego bloku anonimowego lub nazwanego.  Nazwę zmiennej w podprogramie z poziomu schematu (w nazwanej funkcji lub procedurze) trzeba zdefiniować w sekcji deklaracji danego bloku nazwanego.  Podprogramy z poziomu schematu nie mają dostępu do zasięgu wywołujących je bloków, ponieważ są niezależnie zdefiniowanymi jednostkami.  W Oracle dla bloków języka PL/SQL używany jest jednoprzebiegowy proces parsowania, co oznacza, że należy stosować namiastki jako referencje uprzedzające dla lokalnych funkcji i procedur.

Podstawowe skalarne i złożone typy danych Ten podrozdział obejmuje wprowadzenie do trzech najczęściej używanych skalarnych typów danych, kotwiczenia atrybutów i tabel, a także czterech ogólnych złożonych typów danych. Popularne skalarne typy danych to typy znakowe, daty i typy liczbowe. Złożone typy danych to typy definiowane przez użytkownika w SQL-u, rekordy z języka PL/SQL, kolekcje danych typów SQL-a i kolekcje danych typów języka PL/SQL. Skalarne typy danych przechowują tylko pojedyncze elementy, natomiast złożone typy danych mogą obejmować więcej wartości (na przykład struktury lub kolekcje danych). Kompletne omówienie podstaw języka PL/SQL i skalarnych typów danych znajdziesz w rozdziale 4. W języku PL/SQL można też stosować typy danych z SQL-a, opisane w dodatku B.

W następnym punkcie przedstawione są typy znakowe, daty i typy liczbowe (są to główne skalarne typy danych), co posłuży jako podstawa do dalszego opisu złożonych typów danych. Te informacje będą potrzebne w dalszych podrozdziałach poświęconych strukturom sterującym, wyjątkom, operacjom masowym, funkcjom, procedurom, pakietom, zasięgowi transakcji i wyzwalaczom baz danych.

Skalarne typy danych Jak wcześniej wspomniano, kompletne omówienie typów danych języka PL/SQL zawiera rozdział 4. Ten punkt zawiera wprowadzenie do podstawowych skalarnych typów danych. Co programista uznaje za skalarne typy danych, zależy często od przebiegu jego kariery. W tym punkcie jest to dokładnie wyjaśnione. Ogólnie można stwierdzić, że skalarne typy danych zawierają jeden i tylko jeden element. Taki element najczęściej traktuje się jak liczbę lub znak i uznaje za wartość prostego typ danych. W tym ujęciu

Rozdział 3. Podstawy języka PL/SQL

83

łańcuch znaków to złożony typ danych, ponieważ jest tablicą znaków. Jednak w bazach danych i niektórych nowych językach programowania sytuacja wygląda inaczej. Na przykład w językach Java, C# i PL/SQL łańcuchy znaków są traktowane jak skalarne typy danych, choć mogą obejmować słowa, zdania, akapity, rozdziały, a nawet książki. W dalszych podpunktach znajdziesz wprowadzenie do trzech najbardziej podstawowych i najczęściej stosowanych zarządzanych typów danych: łańcuchów znaków, dat i liczb. Pełniejsze omówienie tego zagadnienia zawiera rozdział 4.

Łańcuchy znaków W Oracle Database 12c występują dwa główne rodzaje łańcuchów znaków: o stałej długości i o zmiennej długości. Aby utworzyć łańcuch o stałej długości, należy ustawić jego wielkość za pomocą liczby bajtów lub znaków. Przy podawaniu liczby znaków (jest to zalecane podejście) liczba bajtów jest określana na podstawie używanego zestawu znaków. Domyślny zestaw znaków jest ustawiany w trakcie tworzenia egzemplarza bazy danych (więcej o egzemplarzach baz danych dowiesz się z dodatku A). W łańcuchach znaków o stałej długości używane są typy CHAR lub NCHAR (znaki Unicode), natomiast w łańcuchach o zmiennej długości stosuje się typy VARCHAR2 (inaczej VARCHAR) lub NVARCHAR2 (znaki Unicode). Dla większości łańcuchów znaków stosuje się typy VARCHAR2 i NVARCHAR2, aby uniknąć niepotrzebnego przydzielania pamięci, co ma miejsce w łańcuchach o stałej długości. Poniższy przykładowy program ilustruje przypisywanie danych i alokację pamięci w łańcuchach znaków o stałej i zmiennej długości: SQL> 2 3 4 5 6 7 8

DECLARE lv_fixed CHAR(40) := 'Niezbyt długi tekst.'; lv_variable VARCHAR2(40) := 'Niezbyt długi tekst.'; BEGIN dbms_output.put_line('Stała długość ['||LENGTH(lv_fixed)||']'); dbms_output.put_line('Zmienna długość ['||LENGTH(lv_variable)||']'); END; /

Ten kod wyświetla informacje o zaalokowanej pamięci: Stała długość [40] Zmienna długość [20]

Łańcuchy znaków są przydatne jako proste typy danych. Zauważ jednak, że na łańcuchy o stałej długości zwykle potrzeba więcej pamięci niż na łańcuchy o zmiennej długości. Dlatego w większości sytuacji lepiej jest stosować łańcuchy o zmiennej długości — chyba że mają zajmować ponad 32 kilobajty. Bardzo długie łańcuchy znaków należy zapisywać za pomocą typu CLOB zamiast typu LONG.

Daty Daty w językach programowania są skomplikowane. Typ DATE służy do przechowywania dat, godzin i przedziałów. W Oracle używane są dwie domyślne maski daty. Obie obsługują niejawne rzutowanie na typ DATE. Jedna domyślna maska obejmuje dwucyfrowy zapis dnia, trzyliterowy zapis miesiąca i dwucyfrowy zapis roku (DD-MIE-RR). Druga maska to dwucyfrowy zapis dnia, trzyliterowy zapis miesiąca i czterocyfrowy zapis roku (DD-MIE-RRRR). Inne literały znakowe wymagają zmiany formatu maski za pomocą wbudowanej funkcji TO_DATE SQL-a. Następny przykład ilustruje przypisywanie wartości do zmiennych za pomocą niejawnego i jawnego rzutowania zgodnych i niezgodnych łańcuchów znaków. Niezgodne łańcuchy znaków wymagają zastosowania maski formatującej i wbudowanej funkcji SQL-a (więcej informacji na jej temat znajdziesz w dodatku C). SQL> DECLARE 2 lv_date_1 DATE := '28-APR-75'; 3 lv_date_2 DATE := '29-APR-1975'; 4 lv_date_3 DATE := TO_DATE('19750430','YYYYMMDD'); 5 BEGIN 6 dbms_output.put_line('Niejawne ['||lv_date_1||']');

84

Część I

7 8 9 10

Podstawy języka PL/SQL

dbms_output.put_line('Niejawne ['||lv_date_2||']'); dbms_output.put_line('Jawne ['||lv_date_3||']'); END; /

Ten kod wyświetla następujące dane: Niejawne [28-APR-75] Niejawne [29-APR-75] Niejawne [30-APR-75]

Jeśli chcesz wyświetlić rok za pomocą czterech cyfr, zastosuj wbudowaną funkcję TO_CHAR i odpowiednią maskę formatującą. Ponadto można wykonywać operacje matematyczne na datach, co omówiono w dodatku B.

Liczby Stosowanie liczb w języku PL/SQL jest proste. Liczby całkowite i zespolone przypisuje się w taki sam sposób do wszystkich typów danych z wyjątkiem nowych typów IEEE 754. Korzystanie z typów IEEE 754 opisano w rozdziale 4. Podstawowym liczbowym typem danych jest NUMBER. Można definiować zmienne tego typu bez ograniczeń lub z ograniczeniami (ustawianymi za pomocą precyzji i skali). Określona precyzja pozwala zapobiec przypisywaniu do docelowej zmiennej liczb o większej precyzji. Ograniczenie skali powoduje odcięcie części wartości dziesiętnej; pozostała część wartości zostaje przypisana do zmiennej. Możliwe jest przypisanie zmiennej typu NUMBER z ograniczeniami do zmiennej bez ograniczeń: SQL> 2 3 4 5 6 7 8

DECLARE lv_number1 NUMBER; lv_number2 NUMBER(4,2) := 99.99; BEGIN lv_number1 := lv_number2; dbms_output.put_line(lv_number1); END; /

Jeśli włączona jest zmienna środowiskowa SERVEROUTPUT, ten kod wyświetli następującą liczbę: 99.99

Wartość przypisana do zmiennej lv_number1 się nie zmienia, ponieważ nie ustawiono ograniczeń. Znacznie więcej informacji o liczbach znajdziesz w rozdziale 4.

Kotwiczenie atrybutów i tabel Oracle Database 12c i wcześniejsze wersje tej bazy obsługują kotwiczenie (wiązanie) atrybutów i tabel. Kotwiczenie atrybutów umożliwia powiązanie w programie typu danych z kolumną tabeli. Kotwiczenie tabel pozwala powiązać zmienną złożoną, na przykład typu RECORD, ze strukturą tabeli lub kursora.  Atrybut %TYPE wiąże zmienną z kolumną z tabeli lub kursora.  Atrybut %ROWTYPE wiąże zmienną złożoną ze strukturą tabeli lub kursora. W ramach ilustracji kotwiczenia atrybutów poniżej pokazano, jak zadeklarować zmienną lv_dwarf_ name powiązaną z kolumną name tabeli dwarf: 3

lv_dwarf_name dwarf.name%TYPE := 'Bofur';

Proces kotwiczenia wiąże zmienną lokalną z tabelą z poziomu schematu. Jest to wygodne, gdy zmienia się tylko pojemność łańcucha znaków o zmiennej długości lub precyzja i skala liczbowego typu danych. Może jednak prowadzić do problemów, jeśli w przyszłości zmieni się podstawowy typ danych (na przykład z liczbowego na datę lub z daty na łańcuch znaków). Atrybut %ROWTYPE daje trzy możliwości w zakresie kotwiczenia złożonych typów danych. Jedna technika pozwala ustawić strukturę rekordową z tabeli jako typ danych zmiennej. Inne metody umożliwiają ustawienie typu danych zmiennej na podstawie struktury rekordowej z kursora lub zmiennej systemowego kursora referencyjnego.

Rozdział 3. Podstawy języka PL/SQL

85

Oto przykład ilustrujący kotwiczenie tabel. Zmienną lv_dwarf_record, której typem jest struktura tabeli dwarf, można zadeklarować za pomocą poniższej instrukcji: 3

lv_dwarf_record dwarf%ROWTYPE;

Ta sama składnia pozwala powiązać zmienną lv_dwarf_record z tabelą dwarf, kursorem dwarf_ cursor lub zmienną systemowego kursora referencyjnego dwarf_cursor. Za pomocą poniższej składni można utworzyć tablicę asocjacyjną dwarf_table (opis takich tablic znajdziesz w następnym punkcie) na podstawie struktury tabeli dwarf: 3 TYPE dwarf_table IS TABLE OF DWARF%ROWTYPE 4 INDEX BY PLS_INTEGER;

Jeśli to możliwe, należy stosować atrybut %ROWTYPE i kursor lokalny. Zalecam to podejście, ponieważ modyfikacje kursora w danym bloku zwykle prowadzą też do zmian w innych komponentach programu. Wiązanie zmiennych z tabelami może prowadzić do problemów, ponieważ struktura tabeli nieraz zmienia się niezależnie od kodu programu, w którym używane są powiązane struktury. Kotwiczenie tabel prowadzi też do przełączania kontekstu w kodzie, o czym można się przekonać w trakcie analizy wydajności programu. Instrukcje dotyczące śledzenia wydajności znajdziesz w punkcie „Śledzenie wydajności w SQL-u” w dodatku A. W Oracle Database 12c w trakcie decydowania, czy kotwiczyć tabele, warto uwzględnić pewien nowy czynnik. W strukturze rekordowej dowolnego zakotwiczonego typu znajdują się tylko widoczne kolumny. To oznacza, że w celu uzyskania dostępu do widocznych i niewidocznych kolumn trzeba zakotwiczyć jawnie utworzony kursor. Wymaga to dokonania określonych wyborów na etapie projektowania programu. Kotwiczenie widocznych i niewidocznych kolumn Oracle Database 12c obsługuje widoczne i niewidoczne kolumny. Jeśli jednak użyjesz symbolu gwiazdki (*) w celu pobrania wszystkich kolumn, otrzymasz tylko widoczne kolumny. W celu uzyskania obu rodzajów kolumn trzeba wymienić je wszystkie na liście w instrukcji SELECT. Jeśli to zagadnienie jest dla Ciebie czymś nowym, zapoznaj się z punktem „Niewidoczne kolumny” w dodatku B. Najlepiej ilustruje to krótki przykład. Utwórz następującą tabelę (wykorzystane są tu nazwy z powieści Tolkiena): SQL> 2 3 4

CREATE TABLE ( dwarves_id , name , allegiance

dwarves NUMBER GENERATED AS IDENTITY VARCHAR2(20) VARCHAR2(20) INVISIBLE);

W wierszu 2. tworzona jest kolumna identyfikacyjna. Taka kolumna automatycznie zwiększa wartości klucza sztucznego opartego na niejawnie wygenerowanej sekwencji. Informacje o tym, jak najlepiej wykorzystać takie kolumny, zawiera punkt „Kolumny identyfikacyjne” w dodatku B. W wierszu 4. kolumna allegiance jest oznaczona jako niewidoczna. Dlatego nie zobaczysz tej kolumny, jeśli w instrukcji DESCRIBE lub SELECT zastosujesz gwiazdkę (*). Oto przykładowy program, w którym stosowane jest kotwiczenie tabeli z niewidoczną kolumną: SQL> 2 3 4 5 6 7 8 9 10 11 12

DECLARE /* Kotwiczenie tabeli z niewidoczną kolumną. */ dwarf dwarves%ROWTYPE; BEGIN /* Pobieranie wszystkich kolumn do zmiennej lokalnej. */ SELECT * INTO dwarf FROM dwarves FETCH FIRST 1 ROWS ONLY; /* Wyświetlanie wartości z niewidocznej kolumny. */ dbms_output.put_line( '['||dwarf.name||']['||dwarf.allegiance||']'); END; /

W tym kodzie wystąpi błąd i wyświetlony zostanie następujący komunikat (to jego skrócona postać):

86

Część I

Podstawy języka PL/SQL

'['||dwarf.name||']['||dwarf.allegiance||']'); * ERROR at line 10: ORA-06550: line 10, column 34: PLS-00302: component 'ALLEGIANCE' must be declared

Zastosowana w wierszu 6. nowa składnia do pobierania n pierwszych elementów gwarantuje, że instrukcja SELECT INTO zwróci tylko jeden wiersz. Jeśli zmodyfikujesz program i zastosujesz jawnie deklarowany kursor oraz instrukcję SELECT z listą wszystkich kolumn, kod zacznie działać prawidłowo: SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

DECLARE /* Tworzenie kursora w celu uzyskania dostępu do niewidocznej kolumny. */ CURSOR dwarf_cursor IS SELECT dwarves_id , name , allegiance FROM dwarves; /* Wiązanie zmiennej z tabelą mającą niewidoczną kolumnę. */ dwarf dwarf_cursor%ROWTYPE; BEGIN /* Pobieranie wszystkich kolumn do zmiennej lokalnej. */ SELECT dwarves_id, name, allegiance INTO dwarf FROM dwarves FETCH FIRST 1 ROWS ONLY; /* Wyświetlanie niewidocznej kolumny. */ dbms_output.put_line( '['||dwarf.name||']['||dwarf.allegiance||']'); END; /

Wiązanie struktur rekordowych z tabelami w Oracle Database 12c to doskonałe rozwiązanie, jeśli chcesz używać tylko widocznych kolumn. Jest to jednak zły pomysł, gdy potrzebne są wszystkie kolumny. Ponadto gwiazdka odpowiada obecnie jedynie liście widocznych kolumn; kolumny niewidoczne są pomijane.

Choć kotwiczenie zmiennych i typów danych do innych struktur bazy Oracle Database 12c ma zalety, wiąże się też z określonymi kosztami. W programach, w których zmienne i typy są powiązane z innymi strukturami, ma miejsce przełączanie kontekstu. Dzieje się tak, ponieważ program wczytuje wskazany typ danych, a następnie stosuje go do powiązanych zmiennych i typów danych. Przełączanie kontekstu powoduje ukryte zużycie zasobów w każdym programie, w którym stosowane jest kotwiczenie.

Złożone typy danych Złożone typy danych różnią się od typów skalarnych, ponieważ przechowują większą liczbę elementów. Takie typy mogą obejmować strukturę danych, czyli wiersz danych. Ponadto mogą przechowywać kolekcje danych. Od wersji Oracle Database 9i Release 2 dostępne są następujące rodzaje złożonych typów danych:  Zdefiniowane przez użytkownika typy z SQL-a. Mogą one przechowywać strukturę danych. Obsługiwane są dwie implementacje: sam typ obiektowy (odpowiada strukturze rekordowej z SQL-a) oraz typ obiektowy z implementacją ciała (pozwala tworzyć egzemplarze klas).  Typ rekordowy języka PL/SQL. Może przechowywać strukturę danych, podobnie jak zdefiniowane przez użytkownika typy SQL-a. Aby utworzyć taki typ, należy zakotwiczyć typ elementów do kolumn z tabel lub widoków. Można też bezpośrednio zdefiniować nowy typ. Warto rozważyć zastosowanie bezpośrednich deklaracji, ponieważ zagnieżdżanie takich typów jest problematyczne. Bezpośrednio zadeklarowany typ jest dla programistów znacznie łatwiejszy do zrozumienia niż typy zakotwiczone do tabel z zagnieżdżonymi typami danych.

Rozdział 3. Podstawy języka PL/SQL 



87

Kolekcje z SQL-a. Mogą przechowywać listę wartości dowolnego skalarnego typu danych z SQL-a. Kolekcje zmiennych skalarnych SQL-a to typy ADT (ang. Attribute Data Types) i działają inaczej niż kolekcje wartości typów zdefiniowanych przez użytkownika. Istnieją dwa rodzaje kolekcji z SQL-a. Tablice VARRAY działają jak standardowa tablica w dowolnym proceduralnym lub obiektowym języku programowania. Gdy tworzy się je jako typy zdefiniowane przez użytkownika, mają określoną liczbę elementów. Druga możliwość to tabele zagnieżdżone. Przypominają one listy ze standardowych języków programowania. W momencie definiowania nie określa się liczby ich elementów. Wielkość tabel zagnieżdżonych zmienia się w zależności od wymagań programu w czasie jego wykonania (ograniczeniem jest tu pojemność obszaru PGA). Kolekcje języka PL/SQL. Mogą przechowywać listę wartości dowolnego skalarnego typu danych lub typu rekordowego SQL-a albo listę wartości typu rekordowego języka PL/SQL. W odróżnieniu od innych kolekcji tu jako indeksu nie trzeba stosować liczb — obsługiwane są też łańcuchy znaków. Dlatego taką strukturę trafnie nazywa się tablicą asocjacyjną. Wielu doświadczonych programistów wciąż nazywa ją tablicą języka PL/SQL (ang. PL/SQL table), które to określenie używane było w dokumentacji bazy Oracle 8.

W czterech następnych podpunktach opisane są różne złożone typy danych dostępne od wersje Oracle Database 9i. Tablice asocjacyjne pojawiły się w Oracle 7, a kolekcje struktur rekordowych są dostępne od terminalowej wersji Oracle 7.3. Generyczne tabele zagnieżdżone i tablice VARRAY wprowadzono w bazie Oracle 8.

Zdefiniowane przez użytkownika typy z SQL-a Są to typy obiektowe. Podobnie jak pakiety mają specyfikację i ciało. Specyfikacja określa typ i obejmuje listę atrybutów (pól) oraz metod. Metodami mogą być funkcje i procedury statyczne oraz egzemplarza, a także wyspecjalizowane konstruktory. Konstruktor umożliwia tworzenie obiektów zgodnie z zapisaną w nim logiką. Znacznie więcej informacji o typach obiektowych znajdziesz w rozdziale 11. i dodatku B. Typy obiektowe określają wzorzec obiektów schematu i gwarantują, że dane elementy będą zaimplementowane w ciele obiektu. Możesz zdefiniować typ obiektowy jako FINAL, jednak zwykle się tego nie robi, aby umożliwić innym programistom rozszerzanie typu za pomocą typu pochodnego. Większość typów obiektowych umożliwia tworzenie egzemplarzy, ponieważ tworzenie obiektów obejmujących same metody statyczne nie ma większego sensu — do tego można wykorzystać pakiety. Poniżej znajduje się przykładowy typ obiektowy hobbit. Obejmuje on domyślny (bezparametryczny) konstruktor, przeciążony konstruktor i metody egzemplarza. SQL> 2 3 4 5 6 7 8 9 10 11

CREATE OR REPLACE TYPE hobbit IS OBJECT ( name VARCHAR2(20) , CONSTRUCTOR FUNCTION hobbit RETURN SELF AS RESULT , CONSTRUCTOR FUNCTION hobbit ( name VARCHAR2 ) RETURN SELF AS RESULT , MEMBER FUNCTION get_name RETURN VARCHAR2 , MEMBER FUNCTION set_name (name VARCHAR2) RETURN hobbit , MEMBER FUNCTION to_string RETURN VARCHAR2 ) INSTANTIABLE NOT FINAL; /

Wiersz 2. publikuje atrybut name typu obiektowego hobbit. Wiersz 3. publikuje domyślny (bezparametryczny) konstruktor. Wiersze 4. i 5. publikują przeciążony konstruktor przyjmujący jeden parametr, a w wierszach 6. i 7. opublikowane są metody egzemplarza. W programach języka PL/SQL można stosować typy obiektowe jako parametry i typy zwracanych wartości, a także jako typy kolumn i tabele obiektowe w bazach. W dodatku B dokładnie opisano kolumny i tabele obiektowe, a także obsługę modyfikowania typów w Oracle, co pozwala zmieniać obiekty za pomocą zależności. Typy obiektowe stają się przydatne po ich zaimplementowaniu. Implementacja to definicja ciała obiektu. Ważne jest, aby wszystkie parametry formalne konstruktorów pasowały do nazw i typów danych atrybutów typu obiektowego. W przeciwnym razie w czasie kompilacji wystąpi błąd PLS-00307.

88

Część I

Podstawy języka PL/SQL

Poniżej pokazana jest implementacja przedstawionego wcześniej typu obiektowego: SQL> CREATE OR REPLACE TYPE BODY hobbit IS 2 /* Konstruktor domyślny (bezparametryczny). */ 3 CONSTRUCTOR FUNCTION hobbit RETURN SELF AS RESULT IS 4 lv_hobbit HOBBIT := hobbit('Sam Gamgee'); 5 BEGIN 6 self := lv_hobbit; 7 RETURN; 8 END hobbit; 9 /* Przeciążony konstruktor. */ 10 CONSTRUCTOR FUNCTION hobbit 11 (name VARCHAR2) RETURN self AS RESULT IS 12 BEGIN 13 self.name := name; 14 RETURN; 15 END hobbit; 16 /* Getter jedynego atrybutu tego typu obiektowego. */ 17 MEMBER FUNCTION get_name RETURN VARCHAR2 IS 18 BEGIN 19 RETURN self.name; 20 END get_name; 21 /* Setter nowego egzemplarza typu obiektowego. */ 22 MEMBER FUNCTION set_name (name VARCHAR2) 23 RETURN hobbit IS 24 lv_hobbit HOBBIT; 25 BEGIN 26 lv_hobbit := hobbit(name); 27 RETURN lv_hobbit; 28 END set_name; 29 /* Wyświetlanie pozdrowienia przy użyciu atrybutu tego typu obiektowego. */ 30 MEMBER FUNCTION to_string RETURN VARCHAR2 IS 31 BEGIN 32 RETURN 'Witaj, '||self.name||'!'; 33 END to_string; 34 35 END; 36 /

W wierszach od 3. do 8. zdefiniowany jest konstruktor domyślny (bezparametryczny) typu obiektowego hobbit. Zauważ, że w wierszu 4. tworzony jest lokalny egzemplarz typu hobbit, przypisywany do zmiennej lokalnej. W wierszu 6. lokalny egzemplarz typu hobbit (zmienna lv_hobbit) zostaje przypisany do atrybutu self (jest to używany w Oracle odpowiednik atrybutu this z Javy; oznacza egzemplarz danego obiektu). To dlatego instrukcja RETURN w konstruktorach różni się od takiej instrukcji w innych funkcjach. Konstruktory zwracają egzemplarz danego obiektu, a nie zmienną lokalną lub literał. Może zauważyłeś, że w trakcie tworzenia lokalnego egzemplarza typu hobbit w konstruktorze domyślnym znajduje się wywołanie konstruktora przeciążonego, przy czym używany jest domyślny łańcuch znaków Sam Gamgee. Konstruktor przeciążony umożliwia użytkownikom określenie nazwy egzemplarza typu, podobnie jak funkcja set_name (jest to setter, czyli funkcja służąca do ustawiania wartości atrybutów egzemplarza typu), która też zwraca nowy egzemplarz typu hobbit. Typ obiektowy hobbit obejmuje też funkcję get_name. Jest to getter, czyli funkcja używana do pobierania wartości atrybutów egzemplarza. Ponadto typ hobbit udostępnia metodę to_string, która wyświetla powitanie z wartością egzemplarza typu. Teraz możesz wywołać obiekt w pokazany poniżej sposób za pomocą konstruktora domyślnego i wyświetlić powitanie skierowane do egzemplarza typu hobbit: SQL> COLUMN salutation FORMAT A20 SQL> SELECT hobbit().to_string() AS "Salutation" 2 FROM dual;

Rozdział 3. Podstawy języka PL/SQL

89

Lista instrukcji SELECT zawiera wywołanie przeciążonego konstruktora typu hobbit, a dla utworzonego egzemplarza wywoływana jest metoda to_string, która wyświetla domyślne pozdrowienie. Zastosowano tu łańcuchowe wywoływanie komponentów, możliwe dzięki zastosowaniu selektora komponentów (kropki), pozwalającego wywołać dowolną metodę egzemplarza typu obiektowego. Ten kod wyświetla następujący tekst: Salutation -------------------Witaj, Sam Gamgee!

Aby zastosować przeciążony konstruktor, podaj imię. Oto przykład: SQL> SELECT hobbit('Bilbo Baggins').to_string() AS "Salutation" 2 FROM dual;

Efekt działania tego kodu jest taki sam jak przedstawionego poniżej wywołania funkcji set_name: SQL> SELECT hobbit().set_name('Bilbo Baggins').to_string() AS "Salutation" 2 FROM dual;

W obu przypadkach kod wyświetli następujące informacje: Salutation -------------------Witaj, Bilbo Baggins!

Po zaimplementowania ciała typu obiektowego można zapisywać obiekty w tabelach lub przekazywać je między różnymi nazwanymi jednostkami programów języka PL/SQL (można też przekazać jeden obiekt do innego). Znacznie bardziej szczegółowe omówienie korzystania z obiektów w bazie Oracle znajdziesz w rozdziale 11.

Typy rekordowe w języku PL/SQL Typ rekordowy w języku PL/SQL to rekordowa struktura danych, co oznacza, że jest to wiersz obejmujący dwa lub więcej pól z danymi. Poniższy program tworzy typ rekordowy języka PL/SQL, przypisuje do niego wartości, a następnie wyświetla dane wyjściowe: SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

DECLARE -- Deklaracja lokalnej, zdefiniowanej przez użytkownika struktury rekordowej. TYPE title_record IS RECORD ( title VARCHAR2(60) , subtitle VARCHAR2(60)); -- Deklaracja zmiennej utworzonej struktury rekordowej. lv_title_record TITLE_RECORD; BEGIN -- Przypisywanie wartości do struktury rekordowej. lv_title_record.title := 'Star Trek'; lv_title_record.subtitle := 'Into Darkness'; -- Wyświetlanie elementów struktury. dbms_output.put_line('['||lv_title_record.title||']'|| '['||lv_title_record.subtitle||']'); END; /

W wierszach od 3. do 5. zdefiniowana jest struktura rekordowa języka PL/SQL, a w wierszu 8. znajduje się deklaracja zmiennej, której typem jest lokalny typ rekordowy języka PL/SQL title_record. W wierszach 11. i 12. znajdują się przypisania wartości do pól, a wiersze 14. i 15. wyświetlają zapełnione (łańcuchami znaków) pola. Struktury rekordowe są cenne w trakcie pisania rozwiązań opartych tylko na języku PL/SQL, jednak typy obiektowe są bardziej przydatne i przenośne. W strukturach języka PL/SQL wartości trzeba przypisywać na poziomie pól, ponieważ takie struktury, w odróżnieniu od typów obiektowych baz Oracle, nie obsługują konstruktorów.

90

Część I

Podstawy języka PL/SQL

Kolekcje języka SQL Kolekcje języka SQL mogą przechowywać elementy typów skalarnych i zdefiniowanych przez użytkownika typów SQL-a. W Oracle kolekcje SQL-a z elementami typów skalarnych są nazywane typami ADT (ang. Attribute Data Types). Choć występują pewne różnice w stosowaniu typów ADT i UDT w interfejsie OCI (ang. Oracle Call Interface), odmienne nazwy pozwalają przede wszystkim odróżnić kolekcje natywnych typów danych od kolekcji typów zdefiniowanych przez użytkownika. Tworzenie kolekcji obu rodzajów przebiega nieco inaczej, ponieważ kolekcje typów ADT nie wymagają tworzenia typu UDT. W dwóch dalszych podpunktach opisane jest, jak tworzyć i stosować kolekcje ADT i UDT w SQL-u. Warto przy tym pamiętać, że typów danych obu tych rodzajów można używać także w kolekcjach języka PL/SQL. Jedyna różnica między kolekcjami języków SQL i PL/SQL dotyczy tego, gdzie można je stosować i tworzyć. Kolekcje SQL-a mogą być tabelami zagnieżdżonymi (listami) wartości albo tablicami VARRAY (działają one jak tablice z tradycyjnych języków programowania). Tabele zagnieżdżone nie mają górnego ograniczenia liczby przechowywanych elementów, dlatego działają jak listy. W tablicach VARRAY w trakcie definiowania typu należy określić maksymalną liczbę elementów. Kolekcje w postaci list używane w tabelach bazy danych są nazywane tabelami zagnieżdżonymi.

Przy tworzeniu tabel zagnieżdżonych i tablic VARRAY należy wywołać nazwę typu (konstruktor domyślny) i podać listę elementów. W obu rodzajach kolekcji nowe elementy są dodawane w taki sam sposób. Jest to proces dwuetapowy, obejmujący dodanie miejsca i przypisanie wartości do lokalizacji o określonym indeksie. Tablice VARRAY można rozszerzać tylko w ramach ustawionego ograniczenia (maksymalnej liczby elementów). Próba dodania dalszych elementów spowoduje błąd wyjścia poza zakres. Kompletne omówienie kolekcji, w tym opis powiązanego z nimi interfejsu API, znajdziesz w rozdziale 6. Na razie zapamiętaj, że kolekcje to typy z modyfikatorem FINAL, co oznacza, że nie można tworzyć typów pochodnych od nich. Więcej informacji o tworzeniu typów pochodnych od typów UDT zawiera rozdział 11. Poniższe podpunkty zapewnią Ci podstawy potrzebne do zrozumienia dalszego omówienia kolekcji.

Kolekcje ADT Kolekcja ADT w SQL-u wymaga zdefiniowania kolekcji elementów podstawowego typu danych SQL-a (na przykład typu STRING). Potrzebna do tego składnia jest taka sama jak przy tworzeniu dowolnej kolekcji elementów typu obiektowego w bazie danych. W ramach wprowadzenia do kolekcji ADT najlepiej jest zaprezentować podstawowe różnice między tabelami zagnieżdżonymi a tablicami VARRAY. Dlatego w dalszych przykładach zobaczysz, jak utworzyć poszczególne rodzaje kolekcji ADT. Jeśli interesują Cię dodatkowe szczegóły, w dodatku B znajdziesz omówienie tabel zagnieżdżonych i tablic VARRAY używanych w tabelach, a także składni SQL-a służącej do tworzenia takich kolekcji. Oto składnia do tworzenia prostego typu ADT w postaci tabeli zagnieżdżonej: SQL> CREATE OR REPLACE 2 TYPE string_table IS TABLE OF VARCHAR2(30); 3 /

Instrukcja CREATE OR REPLACE w wierszu 1. to standardowe polecenie SQL-a stosowane w Oracle do tworzenia lub zastępowania obiektu o podanej nazwie i określonym typie. Wiersz 2. definiuje typ ADT — tabelę zagnieżdżoną łańcuchów znaków o zmiennej długości (nie większej niż 30 znaków). Podobny kod można umieścić w bloku języka PL/SQL. W wierszu 3. ukośnik (/) jest używany do uruchomienia instrukcji SQL-a. A oto składnia do tworzenia przykładowego typu ADT w postaci tablicy VARRAY: SQL> CREATE OR REPLACE 2 TYPE string_varray IS VARRAY(3) OF VARCHAR2(30); 3 /

Rozdział 3. Podstawy języka PL/SQL

91

Wartość podana w nawiasach określa maksymalną liczbę elementów danej tablicy VARRAY. Po utworzeniu typu na poziomie schematu można za pomocą poniższego kodu wyświetlić jego definicję: SQL> DESCRIBE collection_varray collection_varray VARRAY(3) OF VARCHAR2(30)

Następny program ilustruje, jak zadeklarować kolekcję SQL-a w bloku anonimowym: SQL> 2 3 4 5 6 7 8 9 10

DECLARE -- Deklarowanie i inicjowanie kolekcji z nazwami zbóż. lv_string_list STRING_TABLE := string_table('Kukurydza','Pszenica'); BEGIN -- Wyświetlanie pierwszego elementu tablicy. FOR i IN 1..2 LOOP dbms_output.put_line('['||i||']['||lv_string_list(i)||']'); END LOOP; END; /

W wierszu 3. zadeklarowana jest zmienna lv_string_list, której typem jest kolekcja STRING_TABLE SQL-a. W kodzie podana jest nazwa zmiennej i jej typ, a kolekcja zostaje zainicjowana dwoma wartościami. Inicjowanie wymaga podania nazwy typu i listy elementów kolekcji (tu są to elementy typu STRING). Użycie określonej listy wartości powoduje utworzenie statycznej kolekcji, jednak później można dodać do niej nowe elementy, ponieważ wielkość listy nie jest ograniczona. Często stosowaną i zalecaną konwencją jest podawanie typu danych wielkimi literami i zapisywanie wywołania konstruktora małymi literami. To podejście zastosowano w wierszu 3.

Ustawiony w pętli FOR (jest ona opisana szczegółowo w punkcie „Struktury iteracyjne” w dalszej części rozdziału) przedział powoduje wczytanie dwóch pierwszych (i jedynych) elementów kolekcji. W tej pętli przedział obejmuje elementy od 1 do 2 włącznie, a wartość indeksu (i) służy do określania miejsca, w którym pętla znajduje się w trakcie przechodzenia po kolekcji. W tym przykładowym programie można też zastosować inny typ kolekcji SQL-a, string_varray. Wymaga to wprowadzenia tylko jednej zmiany. Zmodyfikuj wiersz 3. w następujący sposób: 3

lv_string_list STRING_VARRAY := string_varray('Kukurydza','Pszenica');

W obu sytuacjach program wyświetla następujące informacje: [1][Kukurydza] [2][Pszenica]

Pamięć można przydzielać za pomocą słowa kluczowego EXTEND (jest to część opisanego w rozdziale 6. interfejsu Oracle Collections API). Po przydzieleniu pamięci można dodawać elementy do obu opisanych kolekcji ADT. Typ danych string_table nie ma ograniczenia pojemności (liczby elementów, które można dodać), natomiast do zmiennej typu string_varray można przypisać tylko trzy elementy. Następny przykład tworzy dwie kolekcje. Jedna z nich to typ ADT w postaci tabeli zagnieżdżonej przechowującej cztery łańcuchy znaków. Druga to typ ADT w postaci niezainicjowanej początkowo tablicy VARRAY przechowującej łańcuchy znaków. Program wczytuje elementy z tabeli zagnieżdżonej i próbuje dynamicznie, wiersz po wierszu, zapisać je w tablicy VARRAY. Gdy program chce zająć miejsce na czwarty element tabeli zagnieżdżonej, zgłaszany jest błąd, ponieważ ta tablica VARRAY może przechowywać tylko trzy elementy. Oto kod tego programu: SQL> DECLARE 2 -- Deklarowanie i inicjowanie kolekcji zbóż. 3 lv_string_table STRING_TABLE := 4 string_table('Kukurydza','Pszenica','Żyto','Jęczmień'); 5 lv_string_varray STRING_VARRAY := string_varray(); 6 BEGIN 7 -- Print the first item in the array.

92 8 9 10 11 12 13

Część I

Podstawy języka PL/SQL

FOR i IN 1..lv_string_table.COUNT LOOP lv_string_varray.EXTEND; lv_string_varray(i) := lv_string_table(i); END LOOP; END; /

W wierszach 3. i 4. zadeklarowana jest tabela zagnieżdżona. W wierszu 5. znajduje się deklaracja pustej tablicy VARRAY (jest to kolekcja ADT). Jeśli wywołasz kolekcję bez jej wcześniejszego zainicjowania za pomocą konstruktora jej typu, wystąpi błąd. DECLARE * ERROR at line 1: ORA-06531: Reference to uninitialized collection ORA-06512: at line 9

Ten błąd występuje nie w miejscu definicji, ale przy pierwszym wywołaniu niezainicjowanej kolekcji (w wierszu 9.). Najlepszym rozwiązaniem jest inicjowanie wszystkich kolekcji przy ich definiowaniu, ponieważ kolekcje — w odróżnieniu od innych zmiennych w bazie Oracle Database 12c — nie mają neutralnego stanu null. Wiersz 8. ustawia górny limit przedziału pętli FOR na liczbę elementów przechowywanych w zmiennej lv_string_table. W tym celu wywoływana jest funkcja COUNT. Ta funkcja jest częścią interfejsu Oracle Collections API i w tym kodzie zwraca wartość 4. Pierwsze trzy elementy kolekcji lv_string_table są poprawnie przypisywane do kolekcji lv_string_ varray, ale próba zajęcia miejsca na czwarty element prowadzi do zgłoszenia następującego wyjątku: DECLARE * ERROR at line 1: ORA-06532: Subscript outside of limit ORA-06512: at line 9

Te przykłady ilustrują podstawy stosowania kolekcji ADT w postaci tabel zagnieżdżonych i tablic VARRAY. Tabele zagnieżdżone są łatwiejsze w użyciu i w większości sytuacji okazują się lepszym roz-

wiązaniem.

Kolekcje UDT W SQL-u kolekcje elementów typów UDT wymagają zastosowania typu UDT, takiego jak przedstawiony wcześniej w tym rozdziale typ hobbit. Po zdefiniowaniu takiego podstawowego typu UDT można utworzyć kolekcję w postaci tabeli zagnieżdżonej SQL-a przechowującej elementy typu hobbit: SQL> CREATE OR REPLACE 2 TYPE hobbit_table IS TABLE OF HOBBIT; 3 /

Po utworzeniu kolekcji UDT hobbit_table pora zdefiniować korzystający z niej program. Poniższy program tworzy w sekcji deklaracji egzemplarz takiej kolekcji z dwoma elementami typu hobbit. W sekcji wykonawczej dodawane są dwa kolejne elementy tego typu. SQL> DECLARE 2 -- Declare and initialize a collection of grains. 3 lv_string_table STRING_TABLE := 4 string_table('Drogo Baggins','Frodo Baggins'); 5 lv_hobbit_table HOBBIT_TABLE := hobbit_table( 6 hobbit('Bungo Baggins') 7 , hobbit('Bilbo Baggins')); 8 BEGIN 9 -- Przypisywanie elementów z jednej kolekcji do innej. 10 FOR i IN 1..lv_string_table.COUNT LOOP 11 lv_hobbit_table.EXTEND; 12 lv_hobbit_table(lv_hobbit_table.COUNT) := 13 hobbit(lv_string_table(i)); 14 END LOOP;

Rozdział 3. Podstawy języka PL/SQL 15 16 17 18 19 20 21 22

93

-- Wyświetlanie elementów tabeli zagnieżdżonej typu hobbit_table. FOR i IN 1..lv_hobbit_table.COUNT LOOP dbms_output.put_line( lv_hobbit_table(i).to_string()); END LOOP; END; /

W tym przykładzie należy zwrócić uwagę na trzy ważne kwestie:  Deklaracja zmiennej lv_hobbit_table w wierszach od 5. do 7. obejmuje inicjowanie tabeli zagnieżdżonej typu hobbit_table rozdzielonymi przecinkami egzemplarzami typu hobbit. Wygląda to inaczej niż wymienianie wartości skalarnych (na przykład łańcuchów znaków, dat i liczb) i stanowi główną różnicę między kolekcjami ADT i UDT.  Przy przypisywaniu nowych wartości w wierszach 12. i 13. indeks jest ustawiany na przydzieloną właśnie pamięć. Przypisywany jest egzemplarz typu hobbit. Zauważ, że wartość indeksu (i) dla tabeli zagnieżdżonej lv_string_table jest podawana w zwykłych nawiasach.  Wiersz 19. wyświetla i-ty element tabeli zagnieżdżonej lv_hobbit_table. Do wyświetlania zawartości typu obiektowego używana jest natywna funkcja to_string. Ten przykład pokazuje, jak korzystać z kolekcji elementów typów UDT, jednak nie ilustruje pracy z atrybutami typów obiektowych. Funkcja zwracająca kolekcję to obiektowa funkcja tabelowa. Funkcja TABLE umożliwia dostęp do elementów kolekcji zwróconych przez obiektową funkcję tabelową. Poniższa funkcja tworzy i zwraca kolekcję elementów typu hobbit: SQL> 2 3 4 5 6 7 8 9 10 11 12

CREATE OR REPLACE FUNCTION get_hobbits RETURN HOBBIT_TABLE IS -- Deklaracja kolekcji hobbitów. lv_hobbit_table HOBBIT_TABLE := hobbit_table( hobbit('Bungo , hobbit('Bilbo , hobbit('Drogo , hobbit('Frodo BEGIN RETURN lv_hobbit_table; END; /

Baggins') Baggins') Baggins') Baggins'));

Wiersze od 4. do 8. tworzą kolekcję egzemplarzy typu hobbit. Wiersz 10. zwraca kolekcję typu hobbit_table zawierającą egzemplarze typu hobbit. Aby pobrać wartości tej kolekcji, podaj funkcję zwracającą kolekcję jako parametr funkcji TABLE: SQL> SQL> 2 3

COLUMN hobbit_name FORMAT A14 SELECT name AS hobbit_name FROM TABLE(get_hobbits()) ORDER BY 1;

Funkcja TABLE z wiersza 2. przyjmuje wynik zwrócony przez funkcję get_hobbits i przekształca listę atrybutów zagnieżdżonych obiektów typu hobbit na zwykły zbiór wyników z kolumnami. To zapytanie wyświetla następujące dane: HOBBIT_NAME -------------Bilbo Baggins Bungo Baggins Drogo Baggins Frodo Baggins

W tym podpunkcie zobaczyłeś, że zmienne złożone są bardzo przydatnym narzędziem w środowisku programowania w językach PL/SQL i SQL. Umożliwiają definiowanie skomplikowanego kodu w blokach nazwanych, które można wywoływać w programach zewnętrznych napisanych w językach C#, Java, PHP i innych. Tam, gdzie to możliwe, warto korzystać ze zmiennych złożonych.

94

Część I

Podstawy języka PL/SQL

Kolekcje języka PL/SQL Z tego podpunktu dowiesz się, jak zaimplementować czwarty i ostatni ze złożonych typów danych. To rozwiązanie jest dostępne tylko w języku PL/SQL. Jest ono stosunkowo mało elastyczne i rozszerzalne, ponieważ wymaga opakowania w funkcje potokowe (opisane w dalszej części rozdziału i w rozdziale 8.), jeśli ma być używane w SQL-u. Najlepsza technika, opisana w poprzednim podpunkcie, polega na zwracaniu kolekcji ADT lub UDT SQL-a za pomocą obiektów, co nie wymaga opakowywania.

Kolekcje ADT Poniższy przykład ilustruje, jak korzystać z kolekcji liczb. Możesz zdefiniować kolekcję elementów dowolnego standardowego lub zdefiniowanego przez użytkownika skalarnego typu danych języka PL/SQL, na przykład typu DATE, INTEGER, VARCHAR2 itd. Ten program, podobnie jak kilka przykładów z poprzedniego podpunktu („Kolekcje języka SQL”), używa pętli FOR (szczegółowo opisanej w punkcie „Struktury iteracyjne” w dalszej części rozdziału) i wyświetla elementy zainicjowanej kolekcji języka PL/SQL. SQL> 2 3 4 5 6 7 8 9 10 11 12 13

DECLARE -- Deklaracja typu danych do przechowywania kolekcji liczb. TYPE number_table IS TABLE OF NUMBER; -- Deklaracja zmiennej typu kolekcji. lv_collection NUMBER_TABLE := number_type(1,2,3); BEGIN -- Przejście w pętli po kolekcji i wyświetlanie wartości. FOR i IN 1..lv_collection.COUNT LOOP dbms_output.put_line(lv_collection(i)); END LOOP; END; /

Wiersz 3. definiuje typ danych w postaci kolekcji, a wiersz 6. zawiera deklarację zmiennej tego lokalnego typu danych i instrukcję inicjującą tę zmienną trzema elementami. Nazwa typu danych to jednocześnie nazwa konstruktora, a rozdzielone przecinkami elementy to lista wartości kolekcji. W wierszu 9. definiowany jest przedział pętli FOR (od 1 do liczby elementów ze zmiennej lv_collection, czyli do 3). Zauważ, że w pętli FOR w wierszach od 9. do 11. (tak jak w przedstawionych wcześniej przykładach) zmienna i jest iteratorem i wartością indeksu używaną do poruszania się po elementach kolekcji. Indeksy podaje się tu w nawiasach zwykłych, a nie — jak ma to miejsce w innych językach programowania — w nawiasach kwadratowych. Można też zastosować tablicę VARRAY języka PL/SQL. W tym celu wystarczy zmodyfikować deklarację w wierszu 3.: 3 TYPE number_varray IS VARRAY(3) OF NUMBER;

Reguły dotyczące wymienności tabel zagnieżdżonych i tablic VARRAY dotyczą zarówno SQL-a, jak i języka PL/SQL. Jedyna różnica między tymi językami polega na deklarowaniu tablic. W SQL-u są one obiektami schematu, w języku PL/SQL — lokalnymi typami danych.

Tablice asocjacyjne przechowujące zmienne skalarne Gdy tworzysz rozwiązanie oparte tylko na języku PL/SQL, możesz wykorzystać tablice asocjacyjne przechowujące zmienne skalarne. Są to starsze kolekcje języka PL/SQL. Tablice asocjacyjne działają tylko w języku PL/SQL. Aby dostosować je do SQL-a, trzeba zastosować potokowe funkcje tabelowe. Ponadto takich tablic nie można inicjować — wartości przypisuje się do nich jedna po drugiej. Tablice asocjacyjne mają wady i zalety. W tym wprowadzeniu omówione są zalety. Tablice asocjacyjne dobrze sprawdzają się w przechowywaniu par nazwa-wartość, gdy indeksami są łańcuchy znaków. Ponieważ takie tablice nie wymagają wywoływania konstruktora ani przydzielania fizycznej pamięci przed przypisaniem wartości, niektórzy programiści uważają, że rozwiązywanie problemów z zakresu kolekcji za pomocą tego typu danych jest wyjątkowo wygodne. Przekształć teraz przykład wykorzystujący kolekcję ADT SQL-a (z wcześniejszego punktu), w którym zgłaszany był błąd dotyczący niezainicjowanej kolekcji. Tym razem zastosuj tablicę asocjacyjną

Rozdział 3. Podstawy języka PL/SQL

95

języka PL/SQL z indeksami w postaci liczb. Aby zadeklarować taką tablicę, należy dodać klauzulę INDEX BY BINARY_INTEGER lub INDEX BY VARCHAR2 do definicji tabeli zagnieżdżonej lub tablicy VARRAY.

Poniżej przedstawiona jest prosta tablica asocjacyjna: SQL> 2 3 4 5 6 7 8 9 10 11

DECLARE -- Deklaracja typu danych w postaci kolekcji liczb. TYPE numbers IS TABLE OF NUMBER INDEX BY BINARY_INTEGER; -- Deklaracja zmiennej typu kolekcji. lv_collection NUMBERS; BEGIN -- Przypisanie wartości do kolekcji. lv_collection(0) := 1; END; /

Wiersz 3. dodaje klauzulę INDEX BY BINARY_INTEGER do definicji typu kolekcji i powoduje, że ta kolekcja jest tablicą asocjacyjną indeksowaną liczbami całkowitymi. Wiersz 9. przypisuje wartość 1 do elementu o indeksie 0. Jako wartości indeksu w takiej tablicy można użyć dowolnej liczby całkowitej. Aby utworzyć tablicę asocjacyjną z parami nazwa-wartość, zmień w przykładowym programie definicję w wierszu 3. w następujący sposób: 3

TYPE numbers IS TABLE OF NUMBER INDEX BY VARCHAR2(10);

Teraz jako indeks należy podać łańcuch znaków zamiast liczby. Przypisanie w wierszu 9. powinno obecnie wyglądać tak: 9 lv_collection('Jeden') := 1;

Te przykłady pokazują, jak zadeklarować tablicę asocjacyjną indeksowaną liczbami całkowitymi lub łańcuchami znaków. W rozdziale 6. znajdziesz przykłady pokazujące, w jakich sytuacjach w logice biznesowej warto wykorzystać takie kolekcje.

Kolekcje UDT Oprócz tworzenia kolekcji zmiennych skalarnych można też budować kolekcje dwóch rodzajów struktur: typów rekordowych języka PL/SQL i typów obiektowych SQL-a. Kolekcje typów rekordowych języka PL/SQL można stosować wyłącznie w tym języku. To oznacza, że nie da się ich wywoływać w pokazanych wcześniej zapytaniach, w których stosowano kolekcje UDT SQL-a. Ponadto występują ograniczenia dotyczące stosowania kolekcji typów obiektowych SQL-a w specyfikacjach pakietów, lokalnych blokach anonimowych i blokach nazwanych (funkcjach i procedurach) w języku PL/SQL. W następnym przykładzie kolekcja hobbit_table SQL-a jest zaimplementowana w sekcji deklaracji, dlatego należy ją traktować jak kolekcję UDT z zasięgu języka PL/SQL. Zdefiniowanie kolekcji hobbit_table w bloku anonimowym lub nazwanym powoduje, że będzie ona używana zamiast tak samo nazwanej kolekcji UDT SQL-a z poziomu schematu. Oto kod z implementacją kolekcji UDT SQL-a w anonimowym bloku języka PL/SQL: SQL> DECLARE 2 -- Deklaracja lokalnej kolekcji hobbitów. 3 TYPE hobbit_table IS TABLE OF HOBBIT; 4 5 -- Deklaracja i inicjacja kolekcji łańcuchów znaków. 6 lv_string_table STRING_TABLE := 7 string_table('Drogo Baggins','Frodo Baggins'); 8 lv_hobbit_table HOBBIT_TABLE := hobbit_table( 9 hobbit('Bungo Baggins') 10 , hobbit('Bilbo Baggins')); 11 BEGIN 12 -- Wyświetlanie pierwszego elementu tablicy. 13 FOR i IN 1..lv_string_table.COUNT LOOP 14 lv_hobbit_table.EXTEND; 15 lv_hobbit_table(lv_hobbit_table.COUNT) := 16 hobbit(lv_string_table(i)); 17 END LOOP;

96

Część I

18 19 20 21 22 23 24

Podstawy języka PL/SQL

-- Wyświetlanie elementów tablicy hobbitów. FOR i IN 1..lv_hobbit_table.COUNT LOOP dbms_output.put_line( lv_hobbit_table(i).to_string()); END LOOP; END; /

Wiersz 3. zawiera lokalną deklarację kolekcji UDT SQL-a — hobbit_table. Wszystkie dalsze referencje dotyczą tego lokalnie zdefiniowanego typu. Wiersz 16. pobiera wartość z kolekcji ADT ze zmiennej lv_string_table i używa jej jako parametru w wywołaniu konstruktora hobbit. Następnie nowy egzemplarz typu hobbit jest przypisywany jako nowy element do kolekcji lv_hobbit_table. W praktyce kolekcje UDT SQL-a definiuje się w bloku PL/SQL tylko przy przekształcaniu tablicy asocjacyjnej na kolekcję ADT lub UDT SQL-a. Dzieje się to w potokowych funkcjach tabelowych. Tę technikę stosuje się przy przekształcaniu starszych tablic asocjacyjnych zwracanych przez funkcje języka PL/SQL. Przykład takiego rozwiązania znajdziesz w punkcie „Opakowywanie starszych tablic asocjacyjnych” w rozdziale 6.

Tablice asocjacyjne przechowujące zmienne złożone Można tworzyć nie tylko tablice asocjacyjne zmiennych skalarnych, ale też tablice asocjacyjne typów rekordowych języka PL/SQL. Ilustruje to następny przykład. Takie kolekcje można stosować tylko w programach w języku PL/SQL. SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

DECLARE -- Deklaracja lokalnej zdefiniowanej przez użytkownika struktury rekordowej. TYPE dwarf_record IS RECORD ( dwarf_name VARCHAR2(20) , dwarf_home VARCHAR2(20)); -- Deklaracja lokalnej kolekcji krasnoludów. TYPE dwarf_table IS TABLE OF DWARF_RECORD INDEX BY PLS_INTEGER; -- Deklaracja zmiennej z kolekcją krasnoludów. list DWARF_TABLE; BEGIN -- Dodawanie dwóch elementów do tablicy asocjacyjnej. list(1).dwarf_name := 'Gloin'; list(1).dwarf_home := 'Durin''s Folk'; list(2).dwarf_name := 'Gimli'; list(2).dwarf_home := 'Durin''s Folk'; -- Wyświetlanie elementów tablicy. FOR i IN 1..list.COUNT LOOP dbms_output.put_line( '['||list(i).dwarf_name||']'|| '['||list(i).dwarf_home||']'); END LOOP; END; /

W wierszu 8. zadeklarowana jest tablica asocjacyjna dwarf_table, przechowująca elementy wcześniej zadeklarowanego (w wierszach od 3. do 5.) typu dwarf_record. Wiadomo, że jest to tablica asocjacyjna dostosowana wyłącznie do języka PL/SQL, ponieważ w 9. wierszu deklaracji znajduje się klauzula INDEX BY PLS_INTEGER. Jak wspomniano, tablica asocjacyjna nie wymaga wywołania konstruktora, jednak niezbędne jest bezpośrednie przypisywanie wartości do wierszy lub elementów używanego typu złożonego. Jeśli istnieje tabela dwarf zgodna z deklaracją typu dwarf_record, można powiązać zmienną lokalną z tabelą z poziomu schematu. Oto potrzebna składnia: 3 4

TYPE dwarf_table IS TABLE OF DWARF%ROWTYPE INDEX BY PLS_INTEGER;

Rozdział 3. Podstawy języka PL/SQL

97

Powiązanie używanego w tablicy asocjacyjnej typu danych z tabelą jest ryzykowne. Wynika to z tego, że trzeba pamiętać o synchronizowaniu zmian w podprogramie i tabeli. Powtórzenie materiału W tym podrozdziale omówiono następujące zagadnienia związane ze zmiennymi, przypisaniami i zasięgiem:  Zmienna skalarna przechowuje tylko jeden element, na przykład liczbę, łańcuch znaków lub datę.  Zmienna złożona przechowuje dwa lub więcej elementów, na przykład w strukturze rekordowej lub kolekcji.  Można powiązać zmienną z kolumną (za pomocą atrybutu %TYPE) lub strukturą rekordową (przy użyciu atrybutu %ROWTYPE).  Języki SQL i PL/SQL umożliwiają stosowanie tabel zagnieżdżonych i tablic VARRAY jako kolekcji zmiennych skalarnych typów danych. Są to kolekcje ADT (ang. Attribute Data Types).  Języki SQL i PL/SQL umożliwiają stosowanie tabel zagnieżdżonych i tablic VARRAY jako kolekcji zmiennych złożonych typów danych. Takie kolekcje to listy lub tablice typów UDT z poziomu schematu.  Język PL/SQL umożliwia stosowanie tablic asocjacyjnych przechowujących zmienne skalarnych typów danych (takie kolekcje to tablice asocjacyjne skalarnych typów danych).  Język PL/SQL umożliwia stosowanie tablic asocjacyjnych przechowujących zmienne złożonych typów danych (takie kolekcje to tablice asocjacyjne rekordowych typów danych).

Struktury sterujące Struktury sterujące mają dwie funkcje. Sprawdzają warunek logiczny i rozgałęziają ścieżkę wykonywania programu (są to tak zwane struktury warunkowe), a także iteracyjnie sprawdzają warunek do momentu jego spełnienia lub otrzymania instrukcji wymuszającej zakończenie działania pętli (są to struktury iteracyjne). Punkt „Struktury warunkowe” opisuje instrukcje if, elsif, else i case. Punkt „Struktury iteracyjne” jest poświęcony pętlom FOR, WHILE i pętli prostej.

Struktury warunkowe Jak już wspomniano, struktury warunkowe sprawdzają warunek logiczny i rozgałęziają ścieżkę wykonywania programu. Struktury warunkowe to instrukcje if, elsif, else i CASE.

Instrukcje if, elsif i else Instrukcje if i elsif działają na podstawie operacji logicznych. Zmienna logiczna lub wyrażenie logiczne, na przykład porównanie wartości, to jedyne kryterium w instrukcji if lub elsif. Choć wydaje się to proste, w rzeczywistości jest inaczej, ponieważ w bazach Oracle oprócz fałszu i prawdy możliwe jest też trzecie wyjście — zmienna logiczna lub wyrażenie logiczne mogą mieć wartość true, false lub null. Jest to logika trójwartościowa. Do zarządzania logiką trójwartościową można wykorzystać wbudowaną funkcję NVL. Pozwala ona dodać wewnętrzny test wartości null i zwrócić wartość przeciwną do sprawdzanego warunku logicznego. Logika trójwartościowa Logika trójwartościowa polega na tym, że jeśli warunek wymaga wartości true i jest spełniony, to wartość rzeczywiście jest równa true. Podobnie jeśli warunek wymaga wartości false i jest spełniony, to wartość jest rzeczywiście równa false. Jednak wartość przeciwna jest nieokreślona. Jeśli coś jest różne od true, nie można od razu przyjąć, że jest równe false (i vice versa). Jeżeli wartość jest różna od true, może być to false lub null. Podobnie jeśli coś jest różne od false, może być równe true lub null. Wartość null występuje na przykład wtedy, gdy zmienna jest zdefiniowana, ale nie przypisano do niej wartości, lub gdy w wyrażeniu porównywana jest pewna wartość ze zmienną równą null.

98

Część I

Podstawy języka PL/SQL

Poniższy przykład ilustruje, jak sprawdzić prawdziwość zmiennej logicznej i wyrażenia. Ostatecznie program wyświetla komunikat z informacją, że żaden z warunków nie jest spełniony. SQL> 2 3 4 5 6 7 8 9 10 11 12 13

DECLARE lv_boolean BOOLEAN; lv_number NUMBER; BEGIN IF NVL(lv_boolean,FALSE) THEN dbms_output.put_line('Wyświetlane, gdy zmienna ma wartość true.'); ELSIF NVL((lv_number < 10),FALSE) THEN dbms_output.put_line(' Wyświetlane, gdy wyrażenie ma wartość true.'); ELSE dbms_output.put_line('Wyświetlane, ponieważ zmienne mają wartość null.'); END IF; END; /

Ten kod wyświetli następujące dane: Wyświetlane, ponieważ zmienne mają wartość null.

Ten kod zawsze wyświetla tekst z bloku else, ponieważ zmienne są tylko zdefiniowane, ale nie są zadeklarowane. W języku PL/SQL niezadeklarowane zmienne zawsze mają wartość null. Wbudowana funkcja NVL umożliwia pisanie programów, w których można zagwarantować poprawne działanie. Jest to jeden z najważniejszych celów programistów. Gwarancje są możliwe, ponieważ można przekształcić naturalną logikę trójwartościową w taki sposób, aby działała jak logika dwuwartościowa. Czasem jest to niemożliwe, jednak w takich sytuacjach należy udostępnić kod dla warunku sprawdzającego wartość null.

Instrukcja CASE Instrukcja CASE wygląda bardzo podobnie do polecenia switch stosowanego w wielu językach programowania, jednak działa w inny sposób, ponieważ nie umożliwia przechodzenia do dalszych warunków. Przechodzenie do dalszych warunków (ang. fall-through) polega na znajdowaniu pierwszego spełnionego warunku i późniejszym wykonywaniu pozostałych warunków. Instrukcja CASE w języku PL/SQL działa natomiast tak jak blok if-elsif-else. Są dwa rodzaje instrukcji CASE: prosta i z wyszukiwaniem. W prostych instrukcjach CASE można stosować typy danych CHAR, NCHAR i VARCHAR2. W wersji z wyszukiwaniem używane są wyrażenia logiczne. Instrukcja CASE w języku PL/SQL różni się od swego odpowiednika z SQL-a, ponieważ kończy się słowami END CASE, a nie samym END. Nie stosuj składni SQL-a w języku PL/SQL, ponieważ wystąpi wtedy wyjątek.

Poniższy program pokazuje, jak napisać prostą instrukcję CASE. Zmienna selektora (lv_selector) to zmienna typu VARCHAR2, do której wartość jest przypisywana za pomocą zmiennej podstawianej. SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14

DECLARE lv_selector VARCHAR2(20); BEGIN lv_selector := '&input'; CASE lv_selector WHEN 'Jabłko' THEN dbms_output.put_line('Czy to pyszne, czerwone jabłko?'); WHEN 'Pomarańcza' THEN dbms_output.put_line('Czy to pomarańcza?'); ELSE dbms_output.put_line('To ['||lv_selector||']?'); END CASE; END; /

Klauzula WHEN pozwala porównać poszczególne wartości z podanym w wierszu 5. selektorem instrukcji CASE. Jeśli jedna z klauzul WHEN pasuje do selektora, program uruchamia instrukcje z danej

Rozdział 3. Podstawy języka PL/SQL

99

klauzuli, a następnie wychodzi z bloku CASE. Instrukcja break znana z języków C, C++, C# i Java jest tu stosowana domyślnie. Instrukcja CASE z wyszukiwaniem działa inaczej od jej prostej wersji, ponieważ nie ogranicza się do sprawdzania równości. Za pomocą takiej instrukcji można sprawdzić, czy liczba należy do określonego przedziału lub zbioru. Selektor w takich instrukcjach domyślnie ma wartość true i można go pominąć — chyba że chcesz sprawdzać, czy warunek ma wartość false. Aby wykrywać w klauzulach WHEN wartość false, należy dodać selektor FALSE w wierszu 2.: 2 CASE FALSE

Poniższy program sprawdza, czy warunki mają wartość true: SQL> BEGIN 2 CASE 3 WHEN (1 1) THEN 4 dbms_output.put_line('Niemożliwe!'); 5 WHEN (3 > 2) THEN 6 dbms_output.put_line('Poprawne porównanie.'); 7 ELSE 8 dbms_output.put_line('Nigdy nie dochodzi do tego miejsca.'); 9 END CASE; 10 END; 11 /

Porównanie z wiersza 5. jest prawdziwe, dlatego program wyświetla tekst: Poprawne porównanie.

Inaczej niż w instrukcjach if i elsif nie trzeba tu przekształcać naturalnej logiki trójwartościowej na logikę dwuwartościową. Jeśli warunek z klauzuli WHEN w instrukcji CASE z wyszukiwaniem nie jest spełniony, program przechodzi dalej do momentu napotkania spełnionego warunku lub instrukcji ELSE.

Struktury iteracyjne Struktury iteracyjne to bloki, które umożliwiają powtarzanie polecenia lub zbioru instrukcji. Są dwa rodzaje takich instrukcji: z warunkiem na wejściu i z warunkiem na wyjściu. Rysunek 3.2 przedstawia działanie pętli obu typów.

Rysunek 3.2. Działanie instrukcji iteracyjnych

Iterację można stosować w języku PL/SQL za pomocą trzech rodzajów pętli: for, WHILE i pętli prostej. Można używać ich z kursorem lub bez niego. Kursor to struktura języka PL/SQL umożliwiająca dostęp do wyników zapytania wiersz po wierszu lub w ramach operacji masowej.

Pętla for Pętlę FOR można zaimplementować przy użyciu przedziału lub kursora. Pętla z przedziałem przechodzi po kolejnych liczbach, przy czym trzeba określić początkową i końcową wartość. Jest to struktura

100

Część I

Podstawy języka PL/SQL

z warunkiem na wyjściu. W pętli FOR z przedziałem można poruszać się do przodu lub wstecz. Oto przykład: SQL> BEGIN 2 FOR i IN 0..9 LOOP 3 dbms_output.put_line('['||i||']['||TO_CHAR(i+1)||']'); 4 END LOOP; 5 END; 6 /

Iterator i przyjmuje wartości równe liczbom z podanego przedziału (z początkową i końcową włącznie). Typ iteratora to PLS_INTEGER. Przedstawiony program wyświetla następujące dane: [0][1] [1][2] [2][3] ... [8][9] [9][10]

Pętla FOR z przedziałem zwykle rozpoczyna się od wartości 1 i przechodzi do większych liczb, jednak jako początek przedziału można też zastosować 0. Liczbę 0 jako początek stosuje się rzadko, ponieważ tablice i kursory są numerowane od wartości 1. W tym przykładzie pokazano, jak zacząć pracę pętli od 0, jednak nie należy tego robić. Następna pętla FOR z przedziałem przechodzi od największej do najmniejszej liczby (do 1). Zauważ, że jedyną wskazówką informującą o kierunku poruszania się jest słowo zarezerwowane REVERSE. SQL> BEGIN 2 FOR i IN REVERSE 1..9 LOOP 3 dbms_output.put_line('['||i||']['||TO_CHAR(i+1)||']'); 4 END LOOP; 5 END; 6 /

Pętle FOR z kursorem pracują na zbiorach danych zwróconych przez zapytania. Oprócz stosowania niejawnego kursora dynamicznego i sparametryzowanego kursora dynamicznego można korzystać z dwóch rodzajów kursorów statycznych. Pierwszy przykład pokazuje, jak napisać kursor statyczny bez bloku deklaracji. Taki kod należy pisać tylko w ramach szybkich testów programu lub w niezależnych skryptach. SQL> BEGIN 2 FOR i IN (SELECT item_title FROM item) LOOP 3 dbms_output.put_line(i.item_title); 4 END LOOP; 5 END; 6 /

Wiersz 2. obejmuje statyczne zapytanie w nawiasach. W czasie wykonywania programu to zapytanie jest niejawnie przekształcane w kursor. Tego rodzaju niejawne kursory zawsze powinny być statycznymi zapytaniami. Zapytanie można też umieścić w formalnym kursorze, a następnie wywoływać ten kursor w sekcji wykonawczej: SQL> 2 3 4 5 6 7 8 9

DECLARE CURSOR c IS SELECT item_title FROM item; BEGIN FOR i IN c LOOP dbms_output.put_line(i.item_title); END LOOP; END; /

W tym programie w wierszach 2. i 3. zadeklarowany jest formalny kursor statyczny. Pętla for niejawnie pobiera rekordy z kursora w wierszu 5. Takie programy są bardziej czytelne niż wersja z poprzedniego przykładu. Pozwalają też łatwo wprowadzić zmiany, gdy programista zechce zastosować

Rozdział 3. Podstawy języka PL/SQL

101

kursor dynamiczny zamiast statycznego. Niezależnie od tego, czy kursor ma formalne parametry, w formalnych deklaracjach można stosować zmienne. Poniższy przykład ilustruje, jak zaimplementować kursor z formalnymi parametrami. Inna możliwość to zastąpienie w wierszu 6. parametru kursora zmienną podstawianą. SQL> 2 3 4 5 6 7 8 9 10 11 12

DECLARE lv_search_string VARCHAR2(60); CURSOR c (cv_search VARCHAR2) IS SELECT item_title FROM item WHERE REGEXP_LIKE(item_title,'^'||cv_search||'*+'); BEGIN FOR i IN c ('&input') LOOP dbms_output.put_line(i.item_title); END LOOP; END; /

Interesujące są tu wiersze 3., 6. i 8. Wiersz 3. deklaruje parametr formalny kursora dynamicznego. Wiersz 6. ilustruje, jak wykorzystać ten parametr w kursorze. W wierszu 8. parametr jest używany w wywołaniu kursora. Jako parametr stosowana jest tu zmienna podstawiana, a jej wykorzystanie sprawia, że ten blok anonimowy staje się dynamiczny. Zmienne podstawiane i parametry formalne są bardzo podobne do siebie, ponieważ stanowią miejsce na wartości podawane w momencie wywołania programu. Parametr formalny z wiersza 6. można zastąpić zmienną podstawianą, jest to jednak bardzo nieeleganckie rozwiązanie. Zgodnie z regułą dla kursorów dynamicznych zawsze należy definiować parametry formalne. To kończy podstawowe omówienie pętli for. Ciekawostką związaną z takimi pętlami jest opisana poniżej klauzula WHERE CURRENT OF.

Klauzula WHERE CURRENT OF Moim zdaniem odpowiedni opis klauzuli WHERE CURRENT OF to „dużo hałasu o nic”, ponieważ zwykle lepiej jest stosować operacje masowe. Jednak aby omówienie było kompletne, warto przedstawić kilka przykładów. Tu znajdziesz dwa przykładowe fragmenty. Pierwszy ilustruje, jak zablokować wiersz w kursorze, a następnie zaktualizować daną tabelę w pętli for: SQL> 2 3 4 5 6 7 8 9 10 11 12

DECLARE CURSOR c IS SELECT * FROM item WHERE item_id BETWEEN 1031 AND 1040 FOR UPDATE; BEGIN FOR I IN c LOOP UPDATE item SET last_updated_by = 3 WHERE CURRENT OF c; END LOOP; END; /

Wiersz 5. blokuje wiersze za pomocą klauzuli FOR UPDATE. W wierszu 9. aktualizacja jest wiązana z wierszem zwróconym przez kursor. Następny przykład pokazuje, jak korzystać z klauzuli WHERE CURRENT OF w operacjach masowych. Omówienie takich operacji znajdziesz w dalszej części rozdziału. SQL> DECLARE 2 TYPE update_record IS RECORD 3 ( last_updated_by NUMBER 4 , last_update_date DATE ); 5 TYPE update_table IS TABLE OF UPDATE_RECORD; 6 updates UPDATE_TABLE; 7 CURSOR c IS 8 SELECT last_updated_by, last_update_date

102 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

Część I

Podstawy języka PL/SQL

FROM item WHERE item_id BETWEEN 1031 AND 1040 FOR UPDATE; BEGIN OPEN c; LOOP FETCH c BULK COLLECT INTO updates LIMIT 5; EXIT WHEN updates.COUNT = 0; FORALL i IN updates.FIRST..updates.LAST UPDATE item SET last_updated_by = updates(i).last_updated_by , last_update_date = updates(i).last_update_date WHERE CURRENT OF c; END; /

Instrukcja EXIT w wierszu 16. jest uruchamiana po pobraniu wszystkich wierszy przez klauzulę BULK COLLECT. Klauzula FOR UPDATE w wierszu 11. blokuje wiersze (podobnie jak w przedstawionym wcześniej przykładzie). Klauzula WHERE CURRENT OF w wierszu 21. wiąże aktualizację z wierszami zwróconymi przez kursor zapełniony za pomocą operacji masowej. Wiesz już, jak zastosować klauzulę WHERE CURRENT OF w operacjach masowych. Możliwe, że zastanawiasz się, po co to robić. W końcu ten sam efekt można uzyskać za pomocą skorelowanej instrukcji UPDATE: SQL> 2 3 4 5 6

UPDATE item i1 SET last_updated_by = 3 , last_update_date = TRUNC(SYSDATE) WHERE EXISTS (SELECT NULL FROM item i2 WHERE item_id BETWEEN 1031 AND 1040 AND i1.ROWID = i2.ROWID);

Zgodnie z dokumentacją baz Oracle zaleca się stosowanie skorelowanych instrukcji UPDATE i DELETE zamiast klauzuli WHERE CURRENT OF. Ja także zachęcam do korzystania z natywnych rozwiązań SQL-a, jeśli jest to możliwe. Pętle FOR z przedziałami i kursorami to bardzo przydatne struktury iteracyjne. Ich zaletą jest prostota, a wadą — niejawne otwieranie i zamykanie kursorów. Takie pętle warto stosować, gdy dostęp do danych jest prosty i nie trzeba ich sprawdzać wiersz po wierszu. Gdy konieczne jest sprawdzanie wiersz po wierszu, lepiej jest zastosować pętlę WHILE lub pętlę prostą, ponieważ zapewniają one większą kontrolę.

Pętla WHILE Pętla WHILE to struktura z warunkiem na wejściu. W takich pętlach trzeba zarządzać zarówno warunkiem wejścia, jak i warunkiem wyjścia. Pętla WHILE, w odróżnieniu od pętli for, nie wymaga indeksu, ponieważ programista może kontrolować wejście i wyjście w inny sposób. Jeśli korzystasz z indeksu, możesz uzyskać jeszcze większą kontrolę za pomocą instrukcji CONTINUE (wprowadzonej w bazie Oracle Database 11g), która pozwala zakończyć dane powtórzenie i wrócić do początku pętli: SQL> DECLARE 2 lv_counter NUMBER := 1; 3 BEGIN 4 WHILE (lv_counter < 5) LOOP 5 dbms_output.put('Indeks na początku pętli ['||lv_counter||']'); 6 IF lv_counter >= 1 THEN 7 IF MOD(lv_counter,2) = 0 THEN 8 dbms_output.new_line(); 9 lv_counter := lv_counter + 1; 10 CONTINUE; 11 END IF; 12 dbms_output.put_line('['||lv_counter||']'); 13 END IF; 14 lv_counter := lv_counter + 1; 15 END LOOP;

Rozdział 3. Podstawy języka PL/SQL 16 17

103

END; /

Ten kod wyświetla następujące dane: Indeks Indeks Indeks Indeks

na na na na

początku początku początku początku

pętli pętli pętli pętli

[1][1] [2] [3][3] [4]

Tylko nieparzyste wartości licznika docierają do końca pętli, co ilustruje druga wyświetlana liczba. Jest tak, ponieważ dla liczb parzystych wyświetlany jest znak nowego wiersza, po czym instrukcja CONTINUE powoduje zwrócenie sterowania na początek pętli. Jeśli zastąpisz instrukcję CONTINUE w wierszu 10. poleceniem EXIT, kod wyjdzie z pętli, zamiast przeskakiwać do następnego powtórzenia. Podobny efekt można uzyskać za pomocą instrukcji GOTO i etykiet. Etykiety należy umieszczać w cudzysłowach francuskich (>). Etykiety były dostępne już przed wersją Oracle Database 11g i niechętnie o nich piszę, ponieważ są potrzebne tylko przy korzystaniu z instrukcji GOTO. Ogólnie używanie tych instrukcji nie jest zalecane. Jeśli jednak ich potrzebujesz, zapoznaj się z poniższym przykładem: SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

DECLARE lv_counter NUMBER := 1; BEGIN WHILE (lv_counter < 5) LOOP dbms_output.put('Indeks na początku pętli ['||lv_counter||']'); IF lv_counter >= 1 THEN IF MOD(lv_counter,2) = 0 THEN dbms_output.new_line(); GOTO skippy; END IF; dbms_output.put_line('['||lv_counter||']'); END IF; > lv_counter := lv_counter + 1; END LOOP; END; /

Instrukcja GOTO w wierszu 9. powoduje przejście do instrukcji zwiększającej wartość zmiennej sterującej (wiersz 13.). Ten kod jest nieco bardziej przejrzysty od zaprezentowanego wcześniej rozwiązania opartego na instrukcji CONTINUE. Jednak jeśli to możliwe, instrukcji GOTO należy unikać. Także polecenie CONTINUE powinno się stosować jak najrzadziej i z rozwagą. Pętla WHILE daje duże możliwości, ale jeśli stosuje się instrukcję CONTINUE, może sprawiać problemy.

Pętla prosta Pętla prosta jest taka tylko z nazwy. Programiści stosują ją, gdy chcą kontrolować wszystkie aspekty związane z dostępem do jawnego kursora. Niektóre mechanizmy kontroli są dostępne za pomocą wbudowanych atrybutów kursora:  %FOUND. Ten atrybut zwraca TRUE, jeśli instrukcja DML zmieniła wiersz.  %ISOPEN. W kursorach niejawnych atrybut ten zawsze zwraca FALSE.  %NOTFOUND. Ten atrybut zwraca TRUE, jeśli instrukcja DML nie zmieniła wiersza.  %ROWCOUNT. Ten atrybut zwraca liczbę wierszy zmienionych przez instrukcję DML lub zwróconych przez instrukcję SELECT INTO. Te atrybuty działają dla kursorów i zwykłych instrukcji SQL-a. W zwykłych instrukcjach SQL-a zamiast nazwy kursora należy dodać człon SQL. W instrukcjach SELECT INTO, INSERT, UPDATE i DELETE atrybut %FOUND zwraca true, jeśli wiersze zostały przetworzone. W przeciwnym razie to atrybut %NOTFOUND ma wartość true. Poniższy blok anonimowy używa atrybutów kursorów do zarządzania wyświetlaniem komunikatów dziennika w oknie konsoli:

104

Część I

Podstawy języka PL/SQL

SQL> BEGIN 2 UPDATE system_user 3 SET last_update_date = SYSDATE; 4 IF SQL%FOUND THEN 5 dbms_output.put_line('Zaktualizowane elementy: ['||SQL%ROWCOUNT||']'); 6 ELSE 7 dbms_output.put_line('Brak aktualizacji!'); 8 END IF; 9 END; 10 /

Instrukcja SQL%FOUND w wierszu 4. sprawdza, czy polecenie SQL-a zostało przetworzone. Jak może się domyśliłeś, człon SQL nie jest akronimem oznaczającym język Oracle PL/SQL — jest to słowo zarezerwowane dające dostęp do kursora anonimowego. Jeśli instrukcja SQL%FOUND ma wartość TRUE, wiersz 5. wyświetla liczbę wierszy zaktualizowanych w tabeli. Typowa pętla prosta otwiera kursor, pobiera z niego wiersze, przetwarza je, a następnie zamyka kursor. Poniższy program ilustruje te kroki (wykorzystano tu powiązany typ danych): SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

DECLARE lv_id item.item_id%TYPE; -- Powiązany typ danych. lv_title VARCHAR2(60); CURSOR c IS SELECT item_id, item_title FROM item; BEGIN OPEN c; LOOP FETCH c INTO lv_id, lv_title; EXIT WHEN c%NOTFOUND; dbms_output.put_line('Tytuł: ['||lv_title||']'); END LOOP; CLOSE c; END; /

Ten program definiuje zmienną lv_id. Jej typ danych jest powiązany z definicją kolumny item_id z tabeli item. To powiązanie gwarantuje, że po zmianie definicji tabeli nie trzeba będzie modyfikować programu (powiązany typ danych automatycznie dostosuje się do modyfikacji). Druga zmienna, lv_ title, ma bezpośrednio określony typ danych, dlatego zmiany w tabeli wymagają zmodyfikowania także tego typu. Pierwsza instrukcja w pętli prostej próbuje pobrać wiersz danych, a druga (w wierszu 11.) sprawdza, czy ta operacja zakończyła się powodzeniem. Jeśli nie znaleziono rekordów (dzieje się tak po wczytaniu wszystkich rekordów lub gdy nie znaleziono żadnych danych), w wierszu 11. kod wychodzi z pętli. Możesz rozbudować poprzedni przykład przez utworzenie zdefiniowanej przez użytkownika struktury rekordowej i zwracanie wiersza do jednej takiej struktury. Struktury rekordowe to zmienne złożone. W poniższym przykładzie pseudokolumna %ROWTYPE jest używana do powiązania definicji tabeli ze zmienną lokalną: SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15

DECLARE lv_item_record item%ROWTYPE; -- Powiązany typ danych. CURSOR c IS SELECT * FROM item; BEGIN OPEN c; LOOP FETCH c INTO lv_item_record; EXIT WHEN c%NOTFOUND; dbms_output.put_line('Tytuł: ['||lv_item_record.item_title||']'); END LOOP; CLOSE c; END; /

Rozdział 3. Podstawy języka PL/SQL

105

W wierszu 11. instrukcja lv_item_record.item_title zwraca wartość pola z wiersza danych. Kropka między nazwą zmiennej lokalnej a nazwą kolumny to selektor komponentu. Tę instrukcję należy czytać od lewej do prawej: wczytywane jest tu pole item_title z komponentu lv_item_record, który jest zmienną lokalną. Możesz też bezpośrednio utworzyć typ rekordowy, na przykład wtedy, gdy chcesz pobrać tylko podzbiór kolumn tabeli, a nie chcesz tworzyć widoku. Oto lokalny typ rekordowy: TYPE item_record IS RECORD ( id NUMBER , title VARCHAR2(60));

Najlepsze podejście polega na powiązaniu zmiennej lokalnej z listą pól zwróconą przez instrukcję SELECT w kursorze. Ta lista tworzy naturalną strukturę rekordową. Program można zmodyfikować

w następujący sposób: SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15

DECLARE CURSOR c IS SELECT * FROM item; lv_item_record c%ROWTYPE; BEGIN OPEN c; LOOP FETCH c INTO lv_item_record; EXIT WHEN c%NOTFOUND; dbms_output.put_line('Tytuł: ['||lv_item_record.item_title||']'); END LOOP; CLOSE c; END; /

Wiersz 5. zawiera deklarację zmiennej powiązanej z definicją kursora. Jeśli zmodyfikujesz kursor, zmienna automatycznie się do niego dostosuje. Jest to najbardziej elastyczny i powodujący najsłabszą zależność sposób wiązania zmiennych w języku PL/SQL. Warto wspomnieć, że deklarowanie zmiennej na podstawie kursora jest możliwe w bazie Oracle Database, ale już nie w bazie MySQL. Przy stosowaniu takich typów lokalnych mogą wystąpić pewne problemy, ponieważ te typy działają wyłącznie w kontekście języka PL/SQL. Lepsze podejście jest opisane w punkcie „Złożone typy danych” we wcześniejszej części rozdziału. W tym podrozdziale zobaczyłeś, jak stosować jawne i niejawne pętle. Ponadto nauczyłeś się stosować instrukcję CURSOR w sekcji wykonawczej programów w języku PL/SQL. Powtórzenie materiału W tym podrozdziale opisano następujące zagadnienia dotyczące instrukcji warunkowych i iteracyjnych:  PL/SQL obsługuje logikę trójwartościową, co oznacza, że trzeba aktywnie wykrywać możliwą wartość null.  PL/SQL udostępnia instrukcje warunkowe if, elsif i else.  PL/SQL obsługuje instrukcje CASE (wersje prostą i z wyszukiwaniem). Działają one podobnie jak bloki if, elsif i else, ponieważ — w odróżnieniu od języków C, C++, C# i Java — nie obsługują przechodzenia do dalszych warunków.  PL/SQL obsługuje pętlę FOR, która umożliwia poruszanie się naprzód i wstecz po zbiorach danych.  PL/SQL udostępnia pętlę WHILE, umożliwiającą wykonywanie kodu na podstawie warunku.  PL/SQL udostępnia pętlę prostą. Zapewnia ona największą kontrolę nad powtórzeniami i umożliwia rozgałęzianie wykonania na podstawie wartości zmiennej warunkowej.  Instrukcja EXIT umożliwia wyjście z pętli lub bloku.  Klauzule FOR UPDATE i WHERE CURRENT OF synchronizują operacje na zablokowanych wierszach, jednak prawie zawsze lepiej korzystać ze skorelowanej instrukcji UPDATE.

106

Część I

Podstawy języka PL/SQL

PL/SQL udostępnia instrukcję CONTINUE, która umożliwia przeskakiwanie do następnego powtórzenia pętli, oraz instrukcję GOTO i etykiety umożliwiające rozgałęzianie wykonania.  PL/SQL obsługuje cztery atrybuty kursorów: %FOUND, %NOTFOUND, %ISOPEN i %ROWCOUNT. 

Wyjątki PL/SQL udostępnia opcjonalną sekcję obsługi wyjątków, o czym wspomniano we wcześniejszej części rozdziału. Ta sekcja zarządza wyjątkami, które wystąpiły w trakcie działania sekcji wykonawczej. Błędy z bloku deklaracji są zgłaszane do zasięgu wywołania i tam obsługiwane. Oracle udostępnia dwie wbudowane funkcje do zarządzania wyjątkami:  SQLCODE zwraca liczbę ujemną, która odpowiada wbudowanemu błędowi Oracle. Wyjątkiem jest jeden przypadek specjalny — błąd NO_DATA_FOUND powiązany z dodatnią liczbą 100.  SQLERRM to funkcja przeciążona i działa w następujący sposób: jeśli nie otrzyma numeru, zwraca kod zdefiniowanego błędu i powiązany z nim komunikat. Jeśli otrzyma liczbę dodatnią lub liczbę ujemną, która nie odpowiada wbudowanemu wyjątkowi Oracle, zwraca argument w postaci liczby ujemnej i komunikat wyjątku spoza Oracle. Jeśli otrzyma liczbę ujemną, która odpowiada wbudowanemu wyjątkowi Oracle, zwraca argument w postaci liczby ujemnej i komunikat wbudowanego wyjątku Oracle. Najprostsza sekcja obsługi wyjątków obejmuje słowo kluczowe OTHERS z baz Oracle i przechwytuje wszystkie wyjątki zgłaszane w sekcji wykonawczej: SQL> 2 3 4 5 6 7 8 9 10

DECLARE lv_letter VARCHAR2(1); lv_phrase VARCHAR2(2) := 'AB'; BEGIN lv_letter := lv_phrase; EXCEPTION WHEN OTHERS THEN dbms_output.put_line('Błąd: '||CHR(10)||SQLERRM); END; /

Przypisanie w wierszu 5. dwuznakowego łańcucha do jednoznakowego powoduje zgłoszenie wyjątku. Jest on przechwytywany w sekcji obsługi wyjątków i wyświetlany w oknie konsoli: Error: ORA-06502: PL/SQL: numeric or value error: character string buffer too small

Oracle udostępnia w pakiecie STANDARD zestaw wbudowanych wyjątków. Listę tych wyjątków wraz z opisami znajdziesz w tabeli 7.2 w rozdziale 7. Nazwy błędów standardowych można wstawić zamiast słowa kluczowego OTHERS. W wierszu 7. można na przykład zastosować słowo kluczowe VALUE_ERROR: 7

WHEN VALUE_ERROR THEN

To pozwala przechwycić błędy ORA-06502, ale już nie wyjątki innego rodzaju. To oznacza, że potrzebne będą dwie instrukcje obsługi błędów — jedna dla konkretnego błędu numeric or value error i druga (uniwersalna) dla pozostałych wyjątków. Oto nowa wersja sekcji obsługi wyjątków: 6 EXCEPTION 7 WHEN VALUE_ERROR THEN -- Obsługuje konkretny błąd. 8 dbms_output.put_line(Błąd: '||CHR(10)||SQLERRM); 9 WHEN OTHERS THEN -- Obsługuje wszystkie błędy. 10 dbms_output.put_line('Błąd: '||CHR(10)||SQLERRM); 11 END; 12 /

Wielu programistów stosuje słowo kluczowe OTHERS do przechwytywania wszystkich błędów, jednak dobrą praktyką jest stosowanie specyficznych instrukcji obsługi wyjątków. Instrukcje obsługi konkretnych wyjątków zawsze należy umieszczać przed instrukcją ze słowem kluczowym OTHERS.

Rozdział 3. Podstawy języka PL/SQL

107

PL/SQL umożliwia też tworzenie wyjątków zdefiniowanych przez użytkownika i wyjątków dynamicznych. Z dwóch następnych punktów dowiesz się, jak to robić.

Wyjątki zdefiniowane przez użytkownika Wyjątki zdefiniowane przez użytkownika można deklarować na dwa sposoby: za pomocą samej zmiennej EXCEPTION lub przy użyciu zmiennej typu EXCEPTION i dyrektywy kompilatora EXCEPTION_INIT. Sama zmienna typu EXCEPTION umożliwia przechwytywanie wyjątków zdefiniowanych przez użytkownika za pomocą sekcji obsługi wyjątków ze słowem OTHERS i instrukcji IF. Instrukcja IF sprawdza numer wyjątku zdefiniowanego przez użytkownika (jest to numer 1). Połączenie zmiennej typu EXCEPTION i dyrektywy kompilatora EXCEPTION_INIT umożliwia tworzenie niestandardowych sekcji obsługi wyjątków. Jak wkrótce zobaczysz, ta dyrektywa wiąże nazwę sekcji obsługi wyjątków ze znanym kodem błędu z Oracle. SQL> 2 3 4 5 6 7 8 9 10 11 12

DECLARE lv_error EXCEPTION; BEGIN RAISE lv_error; dbms_output.put_line('Kod nie dociera do tego miejsca.'); EXCEPTION WHEN OTHERS THEN IF SQLCODE = 1 THEN dbms_output.put_line('Błąd ['||SQLERRM||']'); END IF; END; /

Ten program deklaruje zdefiniowany przez użytkownika wyjątek lv_error w wierszu 2. i zgłasza go w wierszu 4. Ogólna sekcja obsługi wyjątków OTHERS przechwytuje ten wyjątek w wierszu 7., a instrukcja IF w wierszu 8. sprawdza, czy jest to wyjątek zdefiniowany przez użytkownika. Ten program zgłasza wyjątek i wyświetla następujące dane: Błąd [User-Defined Exception]

Dwuetapowa deklaracja pozwala zadeklarować wyjątek i powiązać go z liczbą. Pierwszy krok polega na zadeklarowaniu zmiennej. Drugi wiąże zmienną z numerem za pomocą instrukcji prekompilatora PRAGMA EXCEPTION_INIT. SQL> 2 3 4 5 6 7 8 9 10 11 12 13

DECLARE lv_sys_context VARCHAR2(20); lv_error EXCEPTION; PRAGMA EXCEPTION_INIT(lv_error,-2003); BEGIN lv_sys_context := SYS_CONTEXT('USERENV','PROXY_PUSHER'); RAISE lv_error; dbms_output.put_line('Kod nie dojdzie do tego miejsca.'); EXCEPTION WHEN lv_error THEN dbms_output.put_line('Błąd ['||SQLERRM||']'); END; /

Wiersz 3. deklaruje lokalną zmienną typu EXCEPTION, a wiersz 4. wiąże z tym zdefiniowanym przez użytkownika wyjątkiem kod błędu Oracle. Wiersz 6. zgłasza błąd, ponieważ przekazuje niepoprawny łańcuch znaków PROXY_PUSHER jako parametr funkcji SYS_CONTEXT. Wiersz 10. to sekcja obsługi wyjątku zdefiniowanego przez użytkownika. Przechwytuje ona zgłoszony wyjątek. Tu obsługiwany jest tylko wyjątek ORA-02003, ponieważ w bloku nie ma sekcji OTHERS. Ten program testowy zgłasza wyjątek i wyświetla następujące dane: Błąd [ORA-02003: invalid USERENV parameter]

ORA-02003 to rzeczywisty kod błędu z pakietu SYS.STANDARD. Kompletną listę standardowych błędów znajdziesz w specyfikacji tego pakietu.

108

Część I

Podstawy języka PL/SQL

Dynamiczne wyjątki zdefiniowane przez użytkownika Dynamiczne wyjątki zdefiniowane przez użytkownika pozwalają zgłaszać niestandardowe wyjątki o numerze z przedziału od –20 000 do –20 999. W języku PL/SQL umożliwia to funkcja RAISE_APPLICATION_ERROR. Oto jej prototyp: RAISE_APPLICATION_ERROR(error_number, error_message[, keep_errors])

Poniższy program ilustruje, jak zgłaszać dynamiczne wyjątki zdefiniowane przez użytkownika: SQL> 2 3 4 5 6 7 8 9 10

DECLARE lv_error EXCEPTION; PRAGMA EXCEPTION_INIT(lv_error,-20001); BEGIN RAISE_APPLICATION_ERROR(-20001,'Mało oryginalny komunikat.'); EXCEPTION WHEN lv_error THEN dbms_output.put_line('['||SQLERRM||']'); END; /

Wiersz 2. deklaruje zmienną typu EXCEPTION, a wiersz 3. wiąże błąd z wartością z odpowiedniego przedziału. Wiersz 5. zgłasza wyjątek, a wiersz 7. go przechwytuje. W każdej aplikacji bazodanowej występują błędy krytyczne i niekrytyczne. Błędy krytyczne powinny prowadzić do przekazania aplikacji i klientowi komunikatu o niepowodzeniu, natomiast błędy niekrytyczne należy rejestrować w celu ich późniejszego rozwiązania przez personel techniczny. Najlepszym miejscem na kod związany z błędami niekrytycznymi są wyzwalacze bazy danych.

W Oracle Database 12c w pakiecie DBMS_UTILITY dostępna jest funkcja FORMAT_ERROR_BACKTRACE, przeznaczona do zarządzania stosem błędów. Obsługa błędów jest oczywiście ważna, a ten podrozdział nie wyczerpuje tematu zarządzania nimi w sekcjach obsługi wyjątków. Więcej informacji na temat obsługi wyjątków w języku PL/SQL znajdziesz w rozdziale 7. Powtórzenie materiału W tym podrozdziale omówiono następujące zagadnienia dotyczące obsługi wyjątków:  Oracle Database 12c udostępnia dwie funkcje wbudowane: SQLCODE i SQLERRM.  Oracle udostępnia pakiet STANDARD z wyjątkami wbudowanymi i ogólną sekcję obsługi wyjątków — OTHERS.  PL/SQL obsługuje wyjątki zdefiniowane przez użytkownika. Ich numer błędu to 1, chyba że ustawisz inną wartość za pomocą dyrektywy prekompilatora PRAGMA EXCEPTION_INIT.  PL/SQL obsługuje dynamiczne wyjątki zdefiniowane przez użytkownika. Zgłasza się je za pomocą funkcji RAISE_APPLICATION_ERRORS, w której można zdefiniować komunikat o błędzie.

Operacje masowe W Oracle Database 10g i nowszych wersjach (czyli we wszystkich wersjach obsługiwanych w czasie, gdy powstawała ta książka) dostępne jest przetwarzanie masowe. Różni się ono od mechanizmów opisanych we wcześniejszej części rozdziału, jednak ma podobny charakter. Tam, gdzie to możliwe, należy stosować operacje masowe przy przetwarzaniu wsadowym i przy przetwarzaniu dużych ilości danych. Poniższy program ilustruje, jak pobrać grupę wierszy do tablicy. Umożliwia to klauzula BULK COLLECT. Limit jest tu ustawiony na 20 wierszy ze względu na przykładowe dane. W praktyce pobierane mogą być setki, a nawet tysiące rekordów jednocześnie, zalecam jednak ograniczanie tej liczby do 250 – 500 wierszy. SQL> DECLARE 2 TYPE title_record IS RECORD 3 ( title VARCHAR2(60)

Rozdział 3. Podstawy języka PL/SQL 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

109

, subtitle VARCHAR2(60)); TYPE title_collection IS TABLE OF TITLE_RECORD; lv_title_collection TITLE_COLLECTION; CURSOR c IS SELECT item_title, item_subtitle FROM item; BEGIN OPEN c; LOOP FETCH c BULK COLLECT INTO lv_title_collection LIMIT 20; EXIT WHEN lv_title_collection.COUNT = 0; FOR i IN 1..lv_title_collection.COUNT LOOP dbms_output.put_line('['||lv_title_collection(i).title||']'); END LOOP; END LOOP; CLOSE c; END; /

Ten program jest bardziej skomplikowany od wcześniejszych przykładów i wykorzystano w nim kolekcję typu TABLE. Po przygotowaniu struktury rekordowej tworzony jest lokalny typ danych kolekcji. Dalej tworzona jest zmienna tego typu. Wiersz 13. to operacja masowa pobierająca kolekcję struktur rekordowych do jednej zmiennej. Oparta na przedziale pętla FOR z wierszy od 15. do 17. wczytuje tę kolekcję i wyświetla dla każdego rekordu wartość z tylko jednej kolumny. Po pobraniu danych można je wstawić do docelowych tabel lub zaktualizować je w tej samej jednostce odpowiedzialnej za przetwarzanie masowe. Można do tego wykorzystać instrukcję FORALL. Pozwala ona wykonać następującą aktualizację masową: SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

DECLARE TYPE title_record IS RECORD ( id NUMBER , title VARCHAR2(60) , subtitle VARCHAR2(60)); TYPE title_collection IS TABLE OF TITLE_RECORD; lv_title_collection TITLE_COLLECTION; CURSOR c IS SELECT item_id, item_title, item_subtitle FROM item; BEGIN OPEN c; LOOP FETCH c BULK COLLECT INTO lv_title_collection LIMIT 20; EXIT WHEN lv_title_collection.COUNT = 0; FORALL i IN lv_title_collection.FIRST..lv_title_collection.LAST UPDATE item_temp SET item_title = lv_title_collection(i).title , item_subtitle = lv_title_collection(i).subtitle WHERE item_id = lv_title_collection(i).id; END LOOP; END; /

Instrukcja FORALL z wierszy od 16. do 20. aktualizuje po 20 wierszy jednocześnie. Równie dobrze ta liczba może być większa. Przetwarzanie masowe zmniejsza liczbę koniecznych operacji przełączania kontekstu w bazie danych i poprawia wydajność w trakcie przetwarzania transakcji w aplikacjach sieciowych. Powtórzenie materiału

W tym podrozdziale opisano następujące zagadnienia z obszaru operacji masowych:  Klauzula BULK COLLECT INTO umożliwia masowe pobieranie danych. Dostępna jest też klauzula LIMIT, która pozwala ustawić maksymalną liczbę wierszy przetwarzanych w operacjach masowych.  Instrukcja FORALL umożliwia przetwarzanie grupy wierszy i zarządzanie przypisywaniem wartości do kolekcji.

110

Część I

Podstawy języka PL/SQL

Funkcje, procedury i pakiety Składowane jednostki kodu PL/SQL to zwykle funkcje, procedury, pakiety i wyzwalacze. Można też zapisywać typy obiektowe, to zagadnienie jednak zostało opisane w rozdziale 11. Baza danych Oracle przechowuje listę niepowtarzalnych nazw obiektów składowanych, obejmującą nazwy tabel, widoków, sekwencji, programów składowanych i typów. Ta lista to przestrzeń nazw. Zawiera ona nazwy funkcji, procedur, pakietów i typów obiektowych. Odrębna przestrzeń nazw przechowuje nazwy wyzwalaczy. Funkcje, procedury i pakiety składowane umożliwiają ukrywanie szczegółów implementacji jednostek programu. Pozwalają także schować kod w warstwie serwera, co zabezpiecza go przed ciekawskimi programistami. Funkcje i procedury można na podstawie listy parametrów formalnych podzielić na dwa rodzaje podprogramów. Pierwszy rodzaj to jednostki z parametrami przekazywanymi przez wartość. W tej grupie wszystkie parametry działają w trybie IN. Drugi rodzaj to podprogramy z parametrami przekazywanymi przez referencję. Tu przynajmniej jeden parametr działa w trybie IN OUT lub OUT. Funkcja lub procedura z parametrami przekazywanymi przez wartość przyjmuje dane wejściowe (parametry formalne) i zwraca dane wyjściowe. Wartości parametrów formalnych są przekazywane do funkcji, a do zasięgu wywołania zwracane są zupełnie inne dane. Przypomina to mieszanie składników w misce w celu uzyskania ciasta na placek. Po uzyskaniu ciasta nie da się odzyskać poszczególnych składników. Ciasto jest jak wartość zwracana przez funkcję. Funkcja lub procedura z parametrami przekazywanymi przez referencję przyjmuje dane wejściowe w postaci referencji do istniejących zmiennych lub wartości. Wartość zmiennych działających w trybie IN OUT lub OUT może się zmieniać w podprogramie. Funkcje i procedury z parametrami przekazywanymi przez referencję są ściślej powiązane z jednostką wywołującą niż funkcje i procedury z parametrami przekazywanymi przez wartość. Jest tak, ponieważ przechowują referencje do parametrów formalnych zadeklarowanych w zewnętrznym zasięgu wywołania. W Oracle Database 12c można utworzyć białą listę i określić, które podprogramy mogą wywoływać poszczególne funkcje, procedury i pakiety. Służy do tego klauzula ACCESSIBLE BY, przyjmująca funkcje, procedury, pakiety i typy obiektowe.

Funkcje Funkcje składowane są wygodne w stosowaniu, ponieważ można je wywoływać bezpośrednio w instrukcjach w języku SQL i w programach w języku PL/SQL. Wszystkie funkcje składowane muszą zwracać wartości. Z tego powodu można ich używać jako prawych operandów. Funkcje są definiowane w lokalnych blokach deklaracji lub w bazie danych, a ponadto często stanowią elementy pakietów składowanych. Prototyp funkcji składowanej wygląda następująco: CREATE OR REPLACE [{EDITIONABLE | NONEDITIONABLE}] [schema.] FUNCTION nazwa_funkcji ( parametr [IN][OUT] [NOCOPY] {typ_danych_sql | typ_danych_plsql} [, parametr [IN][OUT] [NOCOPY] {typ_danych_sql | typ_danych_plsql}] [, ... ] ) RETURN { typ_danych_sql | typ_danych_plsql } [ACCESSIBLE BY ( [{FUNCTION | PROCEDURE | PACKAGE | TYPE}] [schema.]nazwa_jednostki) [,[{FUNCTION | PROCEDURE | PACKAGE | TYPE}] [schema.]nazwa_jednostki)] [,... ]]]) [ AUTHID [ DEFINER | CURRENT_USER ]] [ DETERMINISTIC | PARALLEL_ENABLED ] [ PIPELINED ] [ RESULT_CACHE [ RELIES ON nazwa_tabeli ]] IS deklaracje BEGIN

Rozdział 3. Podstawy języka PL/SQL

111

wykonywane_instrukcje RETURN zmienna; [EXCEPTION] obsługa_wyjątków END [nazwa_funkcji]; /

W przypisaniach w języku PL/SQL funkcji można używać jako prawych operandów. Ponadto funkcje, które zwracają wartości o typach danych z języka SQL, można wywoływać bezpośrednio w instrukcjach w tym języku. Procedur nie można ani używać jako prawych operandów, ani wywoływać w instrukcjach w języku SQL. W Oracle Database 12c za pomocą nowej klauzuli ACCESSIBLE BY da się określić, które jednostki programu mogą wywoływać daną funkcję. Za pomocą wartości AUTHID można zdefiniować funkcję z uprawnieniami dostępu do bieżącego schematu lub do wywołującego schematu. Domyślne ustawienie DEFINER powoduje uruchomienie programu w tym samym schemacie, w którym zdefiniowana jest funkcja. Ustawienie CURRENT_USER sprawia, że program jest uruchamiany w schemacie wywołującym. Za pomocą poniższego prototypu można wywołać zapisaną w pseudotabeli DUAL funkcję, która zwraca wartość o typie danych z języka SQL: SELECT nazwa_funkcji[(parametr [, ...])] FROM dual;

Obecnie w instrukcjach w języku SQL nie trzeba przekazywać argumentów przy użyciu notacji opartej na pozycji. Równie dobrze można użyć notacji opartej na nazwie, stosowanej w języku PL/SQL. W punkcie „Wywoływanie podprogramów” rozdziale 8. opisano działanie notacji opartej na nazwie, opartej na pozycji i mieszanej. Poniższy kod to prototyp wywołania tej samej funkcji języka PL/SQL z pseudotabeli DUAL: SELECT nazwa_funkcji[(parametr_formalny => argument)] FROM dual;

Wywołania z użyciem notacji opartej na pozycji są najbardziej przydatne, jeśli część parametrów ma wartości domyślne. Nie ma sensu podawanie tylko niektórych parametrów, jeśli całe wywołanie będzie z tego powodu niepoprawne. Dlatego notacja oparta na pozycji najlepiej nadaje się do wywoływania funkcji i procedur z parametrami opcjonalnymi. Aby przypisać wartość zwracaną przez funkcję do zmiennej powiązanej, można użyć instrukcji CALL. Jej prototyp wygląda następująco: SQL> CALL nazwa_funkcji[(parametr [, ...])] 2 INTO powiązana_zmienna_sesji;

Poniższy kod to prosty program, który łączy dwa łańcuchy znaków w jeden: SQL> 2 3 4 5 6 7

CREATE OR REPLACE FUNCTION join_strings ( string1 VARCHAR2 , string2 VARCHAR2 ) RETURN VARCHAR2 IS BEGIN RETURN string1 ||' '|| string2||'.'; END; /

Funkcję tę można wywołać w kodzie SQL: SELECT join_string('Witaj,','świecie') FROM dual;

Można też zdefiniować powiązaną zmienną sesji, a następnie użyć instrukcji CALL, aby przypisać wartość do tej zmiennej: SQL> VARIABLE session_var VARCHAR2(30) SQL> CALL join_strings('Witaj,','świecie') INTO :session_var;

Przy używaniu funkcji składowanych w instrukcji CALL należy zastosować klauzulę INTO. W wywołaniach procedur składowanych klauzula ta nie jest potrzebna. Następnie można pobrać zmienną powiązaną z pseudotabeli DUAL: SQL> SELECT :session_var FROM dual;

112

Część I

Podstawy języka PL/SQL

Da to następujący efekt: Witaj, świecie.

Funkcje dają wielkie możliwości twórcom aplikacji bazodanowych. Funkcje można wywoływać zarówno w instrukcjach w języku SQL, jak i w blokach języka PL/SQL.

Procedury Jak wcześniej wspomniano, procedur nie można używać jako prawych operandów ani wywoływać w instrukcjach w języku SQL. Dane można przekazywać do procedur składowanych języka PL/SQL i pobierać je z nich przy użyciu listy parametrów formalnych. Podobnie jak w funkcjach składowanych, w sekcji deklaracji procedur można definiować programy lokalne w postaci bloków nazwanych. Prototyp procedury składowanej wygląda następująco: CREATE OR REPLACE [{EDITIONABLE | NONEDITIONABLE}] [schema.] PROCEDURE nazwa_procedury ( parametr [IN][OUT] [NOCOPY] {typ_danych_sql | typ_danych_plsql} [, parametr [IN][OUT] [NOCOPY] {typ_danych_sql | typ_danych_plsql}] [, ... ] ) [ACCESSIBLE BY ( [{FUNCTION | PROCEDURE | PACKAGE | TYPE}] [schema.]nazwa_jednostki) [,[{FUNCTION | PROCEDURE | PACKAGE | TYPE}] [schema.]nazwa_jednostki)] [,... ]]]) [ AUTHID DEFINER | CURRENT_USER ] IS deklaracje BEGIN wykonywane_instrukcje [EXCEPTION] obsługa_wyjątków END [nazwa_funkcji]; /

Procedury mogą mieć parametry formalne, ale nie jest to wymagane. W procedurach składowanych parametry formalne można przekazywać zarówno przez wartość, jak i przez referencję. Zmienne przekazywane przez referencję mają tryb IN i OUT. Jeśli programista nie określi trybu parametru, to — podobnie jak w funkcjach — będzie on przekazywany przez wartość. Nowa klauzula ACCESSIBLE BY jest dostępna także w procedurach. Za pomocą klauzuli AUTHID można definiować procedury z uprawnieniami dostępu do tego samego schematu lub do schematu wywołującego. W procedurach klauzula AUTHID działa tak samo jak w funkcjach. Poniższy fragment przedstawia procedurę składowaną, która umieszcza podany łańcuch znaków w nawiasach kwadratowych: SQL> 2 3 4 5 6

CREATE OR REPLACE PROCEDURE format_string ( string_in IN OUT VARCHAR2 ) IS BEGIN string_in := '['||string_in||']'; END; /

Można też użyć instrukcji CALL do wywołania procedury i przekazania zmiennych do niej oraz pobrania ich. Tak jak wcześniejszy fragment ilustrujący wywoływanie funkcji, ten kod pokazuje, jak użyć instrukcji CALL i zmiennej powiązanej: SQL> VARIABLE session_var VARCHAR2(30) SQL> CALL join_strings('Witaj,','świecie') INTO :session_var; SQL> CALL format_string(:session_var);

Warto zauważyć, że w drugiej instrukcji CALL, przy wywołaniu procedury składowanej, nie użyto klauzuli INTO do przekazania i pobrania zmiennej. Składnia jest tu inna niż przy stosowaniu funkcji składowanych. Do wywoływania procedur składowanych można też używać instrukcji EXECUTE. Działa ona dokładnie tak samo, jak instrukcja CALL: SQL> EXECUTE format_string(:session_var);

Rozdział 3. Podstawy języka PL/SQL

113

Następnie można pobrać zmienną powiązaną z pseudotabeli DUAL: SQL> SELECT :session_var FROM dual;

Tym razem efekt będzie następujący: [Witaj, świecie.]

Procedury umożliwiają przekazywanie parametrów formalnych zarówno przez wartość, jak i przez referencję. W rozdziale 8. pokazano, że procedury składowane umożliwiają wymianę danych z aplikacjami zewnętrznymi.

Pakiety Tworzenie pakietu rozpoczyna się od zaplanowania, jakie współużytkowane typy danych i kursory należy udostępnić razem z funkcjami i procedurami. Współużytkowane typy danych umożliwiają wymianę informacji za pomocą specyfikacji typów danych skalarnych, rekordowych i kolekcji, które mogą być potrzebne w pakiecie. Współużytkowane kursory pozwalają wielokrotnie wykorzystać zapytanie, a także umożliwiają wydajniejsze zaprojektowanie go i umieszczenie w jednym miejscu — w specyfikacji pakietu. Gdy udostępniasz pakiety ze współużytkowanymi kursorami, musisz zagwarantować integralność takich kursorów za pomocą poniższej dyrektywy kompilatora: PRAGMA SERIALLY_REUSABLE;

Jeśli o tym zapomnisz, współużytkowany kursor może zostać wczytany przez jeden program, począwszy od pierwszego wiersza, a przez inny program — począwszy od jednego z wierszy między pierwszym a ostatnim. Może to prowadzić do niespójnego odczytu danych ze współużytkowanych kursorów. Jest to najgorszy rodzaj błędów, jakie mogą pojawić się w kodzie w języku PL/SQL. Oto prosta reguła: jeśli stosujesz kursory współużytkowane, pakiet musi nadawać się do seryjnego uruchamiania (musi udostępniać świeże dane każdej jednostce, która go wywołuje). Pakiety obejmujące kursory współużytkowane trzeba definiować w bazie jako jednostki nadające się do seryjnego uruchamiania.

Zmienne i kursory można deklarować w pakietach w dokładnie taki sam sposób jak w innych blokach języka PL/SQL. Funkcje i procedury są jak obiekty z poziomu schematu, choć z jednym wyjątkiem — nie można stosować poleceń DDL do poszczególnych funkcji i procedur. Polecenia DDL dotyczą specyfikacji lub ciała pakietu. Ponadto wszystkie definicje funkcji i procedur ze specyfikacji pakietu muszą być zaimplementowane w odpowiedni sposób w ciele. Procedury muszą mieć takie same nazwy i listy parametrów, a funkcje muszą mieć te same nazwy i typy zwracanych wartości. Oto prototyp specyfikacji pakietu: CREATE [OR REPLACE] [{EDITIONABLE | NONEDITIONABLE}] [schema.] nazwa_pakietu [ACCESSIBLE BY ( [{FUNCTION | PROCEDURE | PACKAGE | TYPE}] [schema.]nazwa_jednostki) [,[{FUNCTION | PROCEDURE | PACKAGE | TYPE}] [schema.]nazwa_jednostki)] [,... ]]]) {IS | AS} [TYPE nazwa_typu IS {RECORD (lista_kolumn) | VARRAY(n) | TABLE [INDEX BY typ_danych]}] [nazwa_zmiennej typ_danych {DEFAULT | :=} value; [ ...]] [CURSOR nazwa_kursora (parametr typ_danych [, parametr typ_danych [, ...]) IS SELECT instrukcja; [ ...]] [TYPE kursor_ref IS REF CURSOR [ RETURN {wiersz_katalogu | wiersz_kursora | struktura_rekordowa}] [ ...]] [wyjątek_użytkownika EXCEPTION; [ ...]] [PRAGMA SERIALLY_REUSABLE;] [FUNCTION publiczny_prototyp;] [ ...] [PROCEURE publiczny_prototyp;] [ ...] END [nazwa_pakietu]; /

114

Część I

Podstawy języka PL/SQL

Nowa klauzula ACCESSIBLE BY pozwala określić, które funkcje, procedury, pakiety i typy obiektowe mogą wywoływać dany pakiet. W ten sposób można utworzyć białą listę jednostek uprawnionych do wywoływania struktur, typów, funkcji i procedur pakietu. Możesz utworzyć specyfikację pakietu obejmującą tylko typy danych, zmienne, kursory i wyjątki. Możesz też dodać funkcje i procedury. Jeśli specyfikacja pakietu nie obejmuje funkcji ani procedur, nie trzeba definiować ciała pakietu, ponieważ nie ma czego w nim umieszczać. Pakiet bez implementacji to pakiet bez ciała. W ciele pakietu trzeba umieścić implementację każdej funkcji i procedury zdefiniowanej w specyfikacji pakietu. Typy danych obsługiwane w pakietach to typy skalarne i typy złożone języka PL/SQL. Oznacza to, że w pakietach nie można definiować typów obiektowych. Jeśli spróbujesz umieścić typ obiektowy w specyfikacji lub ciele pakietu, wystąpi następujący błąd kompilacji: PLS-00540: object not supported in this context.

W pakietach nie można implementować zdefiniowanych przez użytkownika typów obiektowych.

Przykładowy pakiet overloading ilustruje, jak zdefiniować pakiet do seryjnego uruchamiania. Wymaga to zastosowania dyrektywy kompilatora SERIALLY_REUSABLE w specyfikacji i ciele pakietu. Pakiet do seryjnego uruchamiania gwarantuje, że wszystkie jednostki wywołujące funkcje pakietu otrzymają nowy egzemplarz współużytkowanych kursorów. Wadą funkcji do seryjnego uruchamiania jest to, że nie można ich wywoływać w instrukcjach SELECT. W pakiecie overloading pokazano też, jak definiować funkcje przeciążone. Ten pakiet obejmuje dwie wersje funkcji salutation z poziomu pakietu. Przyjmują one dwa lub trzy parametry. Zauważ, że w specyfikacji pakietu znajdują się tylko definicje różnych wersji tej funkcji. SQL> CREATE OR REPLACE PACKAGE overloading IS 2 3 -- Wymusza tworzenie nowych egzemplarzy kursora współużytkowanego. 4 PRAGMA SERIALLY_REUSABLE; 5 6 -- Domyślna wersja funkcji salutation. 7 FUNCTION salutation 8 ( pv_long_phrase VARCHAR2 DEFAULT 'Witaj' 9 , pv_name VARCHAR2 ) RETURN VARCHAR2; 10 11 -- Przeciążona wersja funkcji salutation. 12 FUNCTION salutation 13 ( pv_long_phrase VARCHAR2 DEFAULT 'Witaj' 14 , pv_name VARCHAR2 15 , pv_language VARCHAR2 ) RETURN VARCHAR2; 16 END; 17 /

Wiersz 4. zawiera dyrektywę prekompilatora, która sprawia, że pakiet nadaje się do seryjnego uruchamiania. Wiersze 8. i 13. tworzą parametry o wartości domyślnej. Ta sama wartość domyślna musi występować w ciele pakietu. Jedyną różnicą między definicjami w specyfikacji i ciele pakietu może być zastąpienie słowa kluczowego DEFAULT dwukropkiem i znakiem równości (:=). Po przygotowaniu specyfikacji pakietu z funkcjami lub procedurami należy utworzyć jego ciało. Poniższy kod to ciało pakietu z kursorem współużytkowanym i dwoma funkcjami przeciążonymi. Obie funkcje korzystają z tego kursora. Ponieważ ten kursor jest zadeklarowany w ciele pakietu (a nie w jego specyfikacji), mogą z niego korzystać tylko te funkcje. Ten przykład wymaga następującej tabeli: SQL> 2 3 4

CREATE TABLE salutation_translation ( short_salutation VARCHAR2(4) , long_salutation VARCHAR2(12) , phrase_language VARCHAR2(12));

Do tej tabeli wstaw następujące wartości:

Rozdział 3. Podstawy języka PL/SQL SQL> SQL> SQL> SQL>

INSERT INSERT INSERT INSERT

INTO INTO INTO INTO

salutation_translation salutation_translation salutation_translation salutation_translation

VALUES VALUES VALUES VALUES

115

('Hej','WITAJ','POLISH'); ('Pa','ŻEGNAJ','POLISH'); ('Ciao','SALUTE','ITALIAN'); ('Ciao','ADDIO','ITALIAN');

Prototyp ciała pakietu różni się od prototypu specyfikacji, ponieważ można tu implementować funkcje i procedury lokalne. Takie jednostki można wywoływać tylko w publicznych funkcjach i procedurach zdefiniowanych w specyfikacji pakietu. Oto prototyp ciała pakietu: CREATE [OR REPLACE] ciało_pakietu BODY {IS | AS} [TYPE nazwa_typu IS {RECORD (lista_kolumn) | VARRAY(n) | TABLE [INDEX BY typ_danych]}] [nazwa_zmiennej typ_danych {DEFAULT | :=} wartość; [ ...]] [CURSOR nazwa_kursora (parameter typ_danych [, parametr typ_danych [, ...]) IS SELECT instrukcja; [ ...]] [TYPE kursor_ref IS REF CURSOR [ RETURN {wiersz_katalogu | wiersz_kursora | struktura_rekordowa}] [ ...]] [PRAGMA SERIALLY_REUSABLE;] [FUNCTION implementacja_lokalna;] [ ...] [PROCEURE implementacja_lokalna;] [ ...] [FUNCTION ciało_funkcji_publicznej;] [ ...] [PROCEDURE ciało_procedury_publicznej;] [ ...] END [nazwa_pakietu]; /

Oto kod opisanego wcześniej ciała pakietu: SQL> CREATE OR REPLACE PACKAGE BODY overloading IS 2 3 -- Wymusza zwracanie nowych egzemplarzy kursora współużytkowanego. 4 PRAGMA SERIALLY_REUSABLE; 5 -- Kursor współużytkowany. 6 CURSOR c 7 ( cv_long_phrase VARCHAR2 8 , cv_language VARCHAR2 ) IS 9 SELECT short_salutation 10 , long_salutation 11 FROM salutation_translation 12 WHERE long_salutation = UPPER(cv_long_phrase) 13 AND phrase_language = UPPER(cv_language); 14 15 -- Deklaracja domyślnej wersji funkcji salutation. 16 FUNCTION salutation 17 ( pv_long_phrase VARCHAR2 DEFAULT 'Witaj' 18 , pv_name VARCHAR2 ) RETURN VARCHAR2 IS 19 20 -- Zmienne lokalne. 21 lv_short_salutation VARCHAR2(4) := ''; 22 lv_language VARCHAR2(10) DEFAULT 'POLISH'; 23 24 BEGIN 25 -- Wczytywanie współużytkowanego kursora i zwracanie złączonego wyniku. 26 FOR i IN c(pv_long_phrase, lv_language) LOOP 27 lv_short_salutation := i.short_salutation; 28 END LOOP; 29 RETURN lv_short_salutation || ', ' || pv_name || '!'; 30 END; 31 32 -- Przeciążona wersja funkcji salutation. 33 FUNCTION salutation 34 ( pv_long_phrase VARCHAR2 DEFAULT 'Witaj' 35 , pv_name VARCHAR2 36 , pv_language VARCHAR2) RETURN VARCHAR2 IS 37 38 -- Zmienna lokalna.

116 39 40 41 42 43 44 45 46 47 48 49

Część I

Podstawy języka PL/SQL

lv_short_salutation VARCHAR2(4) := ''; BEGIN -- Wczytywanie współużytkowanego kursora i zwracanie złączonego wyniku. FOR i IN c(pv_long_phrase, pv_language) LOOP lv_short_salutation := i.short_salutation; END LOOP; RETURN lv_short_salutation || ', ' || pv_name || '!'; END; END; /

Ten kod możesz przetestować w bloku języka PL/SQL lub za pomocą instrukcji CALL w SQL*Plus. Aby zastosować instrukcję CALL, należy utworzyć zmienną w zasięgu środowiska SQL*Plus, co opisano w punkcie „Interfejs CLI SQL*Plus” w dodatku A. Poniżej pokazano, jak zadeklarować zmienną i wywołać funkcję, której wyniki są zapisywane w zmiennej wiązanej :message: SQL> VARIABLE message VARCHAR2(30) SQL> CALL overloading.salutation('Witaj','Ringo') INTO :message;

Gdy teraz pobierzesz wynik, zobaczysz tekst Hej, Ringo!. Następnie możesz wywołać przeciążoną wersję funkcji salutation o trzech parametrach: SQL> CALL overloading.salutation('Addio','Lennon','Italian') 2 INTO :message;

Gdy wywołasz następujące zapytanie: SQL> SELECT :message AS "Pożegnanie" FROM dual;

otrzymasz poniższe dane: Pożegnanie --------------Ciao, Lennon!

Pakiet do seryjnego uruchamiania jest niedostępny w kontekście instrukcji SELECT. Na przykład poniższe zapytanie: SQL> SELECT overloading.salutation('Addio','Lennon','Italian') AS "Komunikat" 2 FROM dual;

powoduje zgłoszenie błędu: SELECT overloading.salutation('Addio','Lennon','Italian') AS "Komunikat" * ERROR at line 1: ORA-06534: Cannot access Serially Reusable package "STUDENT.OVERLOADING" ORA-06512: at line 1

W zapytaniach można stosować funkcje z pakietów, które nie są przeznaczone do seryjnego uruchamiania. Większość komercyjnych pakietów spełnia ten warunek. Jedyna sytuacja, gdy trzeba zdefiniować pakiet do seryjnego uruchamiania, związana jest z używaniem w nim kursora współużytkowanego. Przeniesienie kursora do każdej funkcji sprawia, że pakiet nie musi się nadawać do seryjnego uruchamiania. Pakiety są bardzo przydatne do łączenia powiązanego ze sobą kodu w moduły. Jest to technika, którą zwykle warto stosować w projektach aplikacji. Teraz już wiesz, jak implementować pakiety. Powtórzenie materiału W tym podrozdziale omówiono następujące zagadnienia dotyczące pakietów:  Funkcje to lokalne i niezależne bloki nazwane, które zwracają wartość i mogą być używane jako prawy operand w przypisaniach.  Procedury to lokalne i niezależne bloki nazwane, które nie zwracają wartości. To sprawia, że procedury muszą być wywoływane w blokach anonimowych, funkcjach lub procedurach.

Rozdział 3. Podstawy języka PL/SQL

117

Funkcje i procedury są jednostkami z przekazywaniem przez wartość, jeśli wszystkie parametry działają w domyślnym trybie IN.  Funkcje i procedury są jednostkami z przekazywaniem przez referencję, jeżeli przynajmniej jeden z parametrów formalnych działa w trybie IN OUT lub OUT.  Pakiety obejmują powiązane ze sobą funkcje, procedury i typy danych. Można w nich tworzyć także przeciążone funkcje i procedury.  Pakiety bez ciała zawierają definicje typów danych i kursorów współużytkowanych. 

Zasięg transakcji Zasięg transakcji odpowiada wątkowi wykonania, czyli procesowi. Nawiązanie połączenia z bazą danych Oracle 12c powoduje otwarcie sesji. Następnie można w niej ustawić zmienne środowiskowe, między innymi zmienną SERVEROUTPUT, która umożliwia wyświetlanie informacji w programach PL/SQL. Do momentu zatwierdzenia operacji wykonanych w sesji są one widoczne tylko dla użytkownika. Po ich zatwierdzeniu będą dostępne także w innych sesjach. W czasie sesji można uruchomić wiele programów PL/SQL. Są one wykonywane sekwencyjnie, jeden po drugim. Pierwszy program może zmienić dane lub środowisko przed uruchomieniem drugiego i tak dalej. Dzieje się tak, ponieważ sesja to główna transakcja. Wszystkie operacje zależą od poprzednich. Można zatwierdzić i utrwalić zmiany albo odrzucić i anulować wybrane lub wszystkie modyfikacje. Jednostki programów języka PL/SQL zapewniają transakcje zgodne z modelem ACID (ang. atomic, consistent, isolated i durable) dla wielu tabel. W punkcie „Technologia MVVC” w dodatku A wyjaśniono, że wszystkie instrukcje INSERT, UPDATE, MERGE i DELETE są zgodne z tym modelem. Oznacza to, że są atomowe, spójne, izolowane i trwałe. Gwarantuje to technologia MVVC stosowana w produktach firmy Oracle. Więcej na ten temat dowiesz się z dodatku A. Do kontrolowania sesji służą trzy polecenia języka kontroli transakcji (ang. Transaction Control Language — TCL):  Instrukcja COMMIT, która zatwierdza wszystkie zmiany wprowadzone za pomocą instrukcji DML od początku sesji lub ostatniego wywołania polecenia ROLLBACK.  Instrukcja SAVEPOINT, która dzieli dwie epoki. Epoka jest wyznaczana przez transakcje przeprowadzone między dwoma określonymi momentami. Instrukcja ta dzieli przedział czasu na dwie epoki.  Instrukcja ROLLBACK, która anuluje wszystkie zmiany wprowadzone w ostatniej epoce, od nazwanego punktu zapisu lub od początku sesji środowiska SQL*Plus. Opisane polecenia umożliwiają kontrolowanie operacji wykonywanych w sesji i programach. Początek sesji to jednocześnie początek epoki, ponieważ niejawnie wywoływane jest wtedy polecenie SAVEPOINT. Z kolei zakończenie sesji kończy epokę i wiąże się z niejawnym wywołaniem polecenia COMMIT. Zarządzanie zasięgiem transakcji odbywa się inaczej w ramach jednego zasięgu i ich grupy. Jeśli funkcja lub procedura to autonomiczny program składowany, potrzebnych jest wiele zasięgów transakcji.

Pojedynczy zasięg transakcji Tradycyjny problem biznesowy wiąże się z zagwarantowaniem sekwencyjnego wykonania kilku instrukcji DML. Rozwiązanie polega na tym, że albo wszystkie instrukcje zakończą się powodzeniem, albo cała transakcja zakończy się porażką. Częściowy sukces nie jest dopuszczalny. Polecenia TCL umożliwiają zapewnienie sekwencyjnego wykonania operacji w pojedynczym zasięgu transakcji. W poniższym programie użyto poleceń TCL do zagwarantowania sukcesu lub porażki wykonania dwóch instrukcji INSERT:

118

Część I

Podstawy języka PL/SQL

SQL> BEGIN 2 -- Utworzenie punktu zapisu. 3 SAVEPOINT all_or_nothing; 4 5 -- Pierwsza instrukcja INSERT. 6 INSERT INTO member 7 VALUES 8 ( member_s1.nextval, -- Sztuczny klucz główny. ... 16 , SYSDATE); 17 18 -- Druga instrukcja INSERT. 19 INSERT INTO contact 20 VALUES 21 ( contact_s1.nextval -- Sztuczny klucz główny. 22 , member_s1.currval -- Sztuczny klucz obcy. ... 30 , SYSDATE); 31 -- Zatwierdzanie rekordów. 32 COMMIT; 33 34 EXCEPTION 35 -- Anulowanie operacji do punktu zapisu i zgłoszenie o wyjątku. 36 WHEN others THEN 37 ROLLBACK TO new_member; 38 dbsm_output.put_line(SQLERRM); 39 END; 40 /

Gdy nie uda się wykonać jednej z instrukcji INSERT z wiersza 6. lub 19., cała transakcja kończy się niepowodzeniem, ponieważ działa ona na zasadzie „wszystko albo nic”. Instrukcja COMMIT z wiersza 32. jest uruchamiana tylko wtedy, gdy obie instrukcje INSERT zakończą się powodzeniem. Błąd prowadzi do zgłoszenia wyjątku, a wykonane wcześniej operacje są wtedy wycofywane za pomocą instrukcji ROLLBACK z wiersza 37.

Wiele zasięgów transakcji Niektóre operacje biznesowe wymagają niezależnego działania programów. Takie programy są uruchamiane w odrębnych zasięgach transakcji. Autonomiczne jednostki programów są wtedy uruchamiane w nowym zasięgu transakcji. Aby utworzyć program autonomiczny, należy użyć instrukcji AUTONOMOUS_TRANSACTION prekompilatora. Ta instrukcja to dyrektywa (oznaczana słowem kluczowym PRAGMA), która określa pewne działania, między innymi stosowanie niezależnych zasięgów transakcji. Jako podprogramów autonomicznych można używać tylko następujących jednostek:  bloków anonimowych najwyższego poziomu (niezagnieżdżonych),  lokalnych i samodzielnych podprogramów pakietów (czyli funkcji i procedur),  metod typów obiektowych języka SQL,  wyzwalaczy bazodanowych. Początkowy zasięg transakcji to tak zwany program główny. Z tego poziomu wywoływane są programy autonomiczne, które mają niezależne zasięgi transakcji. Jeśli po wywołaniu programu autonomicznego wystąpi błąd w programie głównym, można wycofać tylko te zmiany, które wprowadzono w zasięgu transakcji głównej. Operacje w zasięgu transakcji autonomicznej mogą zakończyć się sukcesem lub porażką niezależnie od działania programu głównego. Jednak w programie tym może wystąpić błąd, jeśli na poziomie transakcji autonomicznej zostanie zgłoszony wyjątek. Programy o wielu zasięgach transakcji są skomplikowane. Należy zastanowić się, czy korzyści stosowania wielu zasięgów przeważą koszty zarządzania nimi.

Rozdział 3. Podstawy języka PL/SQL

119

Powtórzenie materiału W tym podrozdziale opisano następujące zagadnienia dotyczące zasięgu transakcji:  Bazy Oracle zawsze działają w trybie obsługi transakcji. Różnią się pod tym względem od innych baz, na przykład MySQL.  Zasięg transakcji gwarantuje, że w trakcie wstawiania danych, ich aktualizowania lub usuwania w kilku tabelach albo wszystkie operacje zostaną wykonane, albo nie zostaną wprowadzone żadne zmiany.  Przed próbą wstawienia danych, ich zaktualizowania lub usunięcia w kilku tabelach zawsze należy utworzyć punkt zapisu (za pomocą instrukcji SAVEPOINT).  Zawsze należy wycofywać zmiany, jeśli w jednej części wieloczęściowej transakcji nie można zagwarantować zgodności z modelem ACID. Pełny opis zgodności z modelem ACID zawarto w dodatku A.

Wyzwalacze bazodanowe Wyzwalacze bazodanowe to wyspecjalizowane programy składowane wywoływane w wyniku zdarzeń zachodzących w bazie danych. Są uruchamiane między wywołaniem polecenia (INSERT, UPDATE, MERGE lub DELETE) a zatwierdzeniem zmian związanych z daną instrukcją DML języka SQL. Wyzwalacze oparte są na blokach anonimowych i zapisuje się je w kolumnach typu LONG. Mechanizmy przekazywania zmiennych między instrukcjami DML SQL-a a blokami anonimowymi są skomplikowane. Ich omówienie znajdziesz w rozdziale 12. Instrukcja SQL-a, po której następuje polecenie COMMIT, to protokół 2PC (ang. two-phase commit, czyli protokół dwuetapowego zatwierdzania). W transakcjach zgodnych z modelem ACID protokół 2PC służy do zarządzania jedną instrukcją SQL-a lub ich grupą. W tym podejściu instrukcja DML (INSERT, UPDATE, MERGE lub DELETE) rozpoczyna proces i wprowadza zmiany. Te instrukcje DML mogą też pełnić funkcję zdarzeń uruchamiających wyzwalacze bazodanowe przypisane do operacji wprowadzających zmiany w bazie. Ponieważ wyzwalacze działają między pierwszym a drugim etapem protokołu 2PC, nie można w nich używać instrukcji TCL, czyli poleceń SAVEPOINT, ROLLBACK i COMMIT. W rodzinie produktów Oracle 12c można używać czterech rodzajów wyzwalaczy. Są to:  Wyzwalacze DDL. Te wyzwalacze są uruchamiane przy wywołaniu polecenia CREATE, ALTER, RENAME lub DROP na obiektach ze schematu bazy danych. Są przydatne do wykrywania złych rozwiązań programistycznych, na przykład tworzenia (CREATE) i usuwania (DROP) tabel tymczasowych zamiast wydajnego używania kolekcji bazy danych Oracle w pamięci. Tabele tymczasowe mogą prowadzić do fragmentacji dysku i obniżenia wydajności bazy danych.  Wyzwalacze DML (poziomu wierszy). Uruchomienie tych wyzwalaczy jest wynikiem wstawiania (INSERT), aktualizowania (UPDATE) lub usuwania (DELETE) danych w tabeli. Można ich używać do kontroli, sprawdzania, zapisywania i zastępowania wartości przed ich zmodyfikowaniem. Wyzwalaczy tego typu często używa się do automatycznego numerowania kluczy głównych.  Wyzwalacze zastępujące. Wyzwalacze tego rodzaju umożliwiają zatrzymanie działania instrukcji DML i przekierowanie jej. Wyzwalacze zastępujące są często używane przy zapisie danych do widoków, które uniemożliwiają zapis bezpośredni, ponieważ nie można ich aktualizować. W wyzwalaczach zastępujących można uwzględnić reguły biznesowe i bezpośrednio wstawiać, aktualizować i usuwać wiersze w odpowiednich tabelach związanych z widokami umożliwiającymi aktualizację.  Wyzwalacze systemowe (poziomu zdarzeń bazodanowych). Te wyzwalacze są uruchamiane przy wykonywaniu przez system określonych operacji na bazie danych. Są to na przykład wyzwalacze uruchamiane przez zdarzenia związane z logowaniem i wylogowywaniem, opisane w rozdziale 12. Wyzwalacze tego typu umożliwiają śledzenie zdarzeń systemowych i wiązanie ich z użytkownikami.

120

Część I

Podstawy języka PL/SQL

Wszystkie cztery typy wyzwalaczy opisano w rozdziale 12. W tym podrozdziale ramka z powtórzeniem materiału nie jest potrzebna, ponieważ zawierałaby te same informacje co powyższa lista.

Podsumowanie Niniejszy rozdział zawiera omówienie podstawowych elementów języka PL/SQL i szybko uczy, jak zacząć go stosować. Ten przegląd powinien też wzbudzić apetyt na dalsze informacje.

Test wiedzy Test wiedzy to zestaw pytań typu „prawda czy fałsz” i wielokrotnego wyboru, dzięki którym sprawdzisz, jak dobrze opanowałeś materiał z poszczególnych rozdziałów. Odpowiedzi na pytania znajdziesz w dodatku I.

Prawda czy fałsz? 1. __Podstawowy blok w języku PL/SQL zawsze musi zawierać przynajmniej instrukcję null, aby

można go było skompilować. 2. __Instrukcja ELSEIF umożliwia rozgałęzianie wykonania programu w instrukcjach IF. 3. __W bloku DECLARE umieszcza się wszystkie zmienne i kursory oraz implementacje lokalnych funkcji i procedur. 4. __W bloku EXCEPTION należy umieścić kod obsługi błędów zgłaszanych w sekcji deklaracji danej jednostki anonimowej lub nazwanej. 5. __Dwukropek i znak równości (:=) to jedyny operator przypisania stosowany w języku PL/SQL. 6. __Aby uniknąć błędu braku deklaracji procedury lub funkcji w danym zasięgu, należy utworzyć referencje uprzedzające w postaci namiastek lokalnych funkcji i procedur. 7. __Oracle obsługuje dwie wersje instrukcji CASE: prostą i z wyszukiwaniem. 8. __Oracle umożliwia stosowanie kolekcji języków SQL i PL/SQL jako typów danych parametrów i zwracanych wartości. 9. __Pakiety umożliwiają definiowanie przeciążonych funkcji i procedur. 10. __Wyzwalacze bazodanowe są uruchamiane między uruchomieniem instrukcji DML a poleceniem COMMIT.

Pytania wielokrotnego wyboru 11. Które tryby parametrów są obsługiwane w języku Oracle PL/SQL? Poprawnych może być kilka

odpowiedzi. A. IN. B. INOUT. C. OUT. D. IN OUT. E. Wszystkie z powyższych. 12. Które z poniższych pętli są dostępne w języku PL/SQL? Poprawnych może być kilka odpowiedzi. A. Pętla prosta. B. Pętla FOR. C. Pętla WHILE. D. Pętla UNTIL. E. Wszystkie z powyższych.

Rozdział 3. Podstawy języka PL/SQL

121

13. Z którymi z poniższych typów danych współdziała prosta instrukcja CASE? Poprawnych może

być kilka odpowiedzi. A. TEXT. B. VARCHAR2. C. NCHAR. D. CHAR. E. DATE. 14. Które z poniższych nie jest słowem kluczowym języka PL/SQL? A. RECORD. B. REVERSE. C. CURSOR. D. LIMIT. E. STRUCTURE. 15. Które z poniższych słów nie jest atrybutem kursora? A. %FOUND. B. %ISOPEN. C. %TYPE. D. %NOTFOUND. E. %ROWCOUNT.

122

Część I

Podstawy języka PL/SQL

ROZDZIAŁ

4

Podstawowe elementy języka Ten rozdział jest oparty na omówieniu architektury języka PL/SQL z rozdziału 1. i opisie podstaw tego języka z rozdziału 3. Przedstawiono tu zmienne skalarne i złożone, a także przypisywanie wartości do zmiennych tych rodzajów. Rozdział składa się z dwóch podrozdziałów:  Jednostki leksykalne.  Zmienne i typy danych. Jednostki leksykalne to cegiełki umożliwiające budowanie programów. W dwóch wspomnianych podrozdziałach opisano podstawy języka.

Jednostki leksykalne Jednostki leksykalne to podstawowe cegiełki języków programowania. To z nich składają się programy w języku PL/SQL. Jednostki leksykalne tworzy się przez łączenie odpowiednich znaków i symboli. Tymi jednostkami mogą być ograniczniki, identyfikatory, literały i komentarze. Ograniczniki pełnią funkcję zaprawy; są to elementy semantyczne, takie jak operatory i ograniczniki literałów. Identyfikatory bardziej przypominają cegły i obejmują słowa zarezerwowane i kluczowe, a także nazwy podprogramów i zmiennych. Literały są wygodnym narzędziem do dodawania do programów stałych łańcuchowych i liczbowych. Komentarze nie są ani zaprawą, ani cegłami, są jednak ważne, ponieważ pomagają autorom kodu (a także osobom pracującym nad nim w przyszłości) stwierdzić, co się w nim dzieje.

Ograniczniki Ograniczniki leksykalne to symbole lub ich kombinacje. W językach programowania pełnią funkcje ograniczników oraz innych elementów języka. Są używane między innymi w operacjach przypisywania, asocjacji, łączenia i porównywania, a także w działaniach matematycznych i instrukcjach sterujących. Najczęściej stosowany ogranicznik jest przeznaczony do ograniczania łańcuchów znaków. W języku PL/SQL służą do tego apostrofy (' '). Tabela 4.1 zawiera opis wszystkich ograniczników i przykłady użycia ich w języku PL/SQL. W niektórych fragmentach kodu zastosowano techniki i zagadnienia opisane bardziej szczegółowo w dalszej części książki.

124

Część I

Podstawy języka PL/SQL

Tabela 4.1. Ograniczniki w języku PL/SQL Symbol

Funkcja

Opis

:=

Przypisywanie

Operator przypisania to połączenie dwukropka i znaku równości. Jest to jedyny operator przypisania w języku PL/SQL. Prawy operand jest przypisywany do lewego: a := b + c;

Powyższa instrukcja dodaje wartości zmiennych b i c, a następnie przypisuje wynik do zmiennej a. Dodawanie ma miejsce przed przypisaniem z uwagi na pierwszeństwo operatorów, które zostało opisane w dalszej części rozdziału. :

Asocjacja

Wskaźnik zmiennej języka bazowego poprzedza nazwę identyfikatora i określa, że jest on zmienną poziomu sesji. Zmienne te są także nazywane zmiennymi powiązanymi. Można je definiować w środowisku SQL*Plus. Zmienne tego rodzaju mogą mieć typ CHAR, CLOB, NCHAR, NCLOB, NUMBER, NVARCHAR2, REFCURSOR i VARCHAR2. Poniższy prototyp ilustruje definiowanie zmiennych sesji: VARIABLE nazwa_zmiennej typ_danych

Następna instrukcja pokazuje, jak użyć tego prototypu do utworzenia na poziomie sesji łańcucha znaków o zmiennej długości: SQL> VARIABLE my_string VARCHAR2(30)

Następnie można przypisać do tej zmiennej wartość, używając programu w postaci anonimowego bloku języka PL/SQL: BEGIN :my_string := 'Literał znakowy.'; END; /

Efekt tego przypisania można pobrać z pseudotabeli DUAL: SELECT :my_string FROM dual;

Można też powtórnie wykorzystać tę zmienną w innym bloku języka PL/SQL, ponieważ jej zasięg rozciąga się na całą sesję. Dlatego następny blok anonimowy w skrypcie może wyświetlić wartość tej zmiennej: BEGIN dbms_output.put_line(:my_string); END; /

Jest to wygodny sposób przekazywania zmiennych między wieloma instrukcjami i blokami języka PL/SQL w jednym skrypcie. &

Asocjacja

%

Asocjacja

Wskaźnik podstawiania umożliwia przekazywanie argumentów do anonimowych bloków języka PL/SQL. Nigdy nie należy przypisywać wartości do zmiennych podstawianych w bloku deklaracji, ponieważ w sekcji obsługi wyjątków nie można przechwycić zgłoszonych tu wyjątków związanych z błędami przypisywania. Takie przypisania zawsze powinny znajdować się w sekcji wykonawczej. Poniższa instrukcja przedstawia przypisanie łańcuchowej zmiennej podstawianej do zmiennej lokalnej w sekcji wykonawczej: a := '&string_in';

Wskaźnik atrybutu pozwala wskazać atrybut kolumny, wiersza lub kursora z katalogu bazy danych. Powiązanie zmiennej z obiektem z katalogu, na przykład z tabelą lub kolumną, powoduje zakotwiczenie typu danych zmiennej. Kotwiczenie (wiązanie) typów danych opisano w rozdziale 3., natomiast z punktu „Kursory” z rozdziału 5. dowiesz się, jak za pomocą tego operatora powiązać zmienne z elementami z katalogu bazy danych. Punkt „Systemowe kursory referencyjne” z niniejszego rozdziału opisuje, jak tworzyć systemowe kursory referencyjne o ściśle określonym typie w wyniku powiązania ich z tabelami.

Rozdział 4. Podstawowe elementy języka

125

Tabela 4.1. Ograniczniki w języku PL/SQL — ciąg dalszy Symbol

Funkcja

Opis

=>

Asocjacja

Operator asocjacji składa się ze znaku równości i symbolu większości. Jest używany w notacji opartej na nazwie w wywołaniach funkcji i procedur. Stosowanie operatora asocjacji opisano w rozdziale 8.

.

Asocjacja

Kropka to selektor komponentów. Łączy on różne nazwy, na przykład schematu i tabeli, pakietu i funkcji lub obiektu i metody składowej. Selektorów komponentów można też używać do łączenia kursorów i ich atrybutów (kolumn). Poniższe prototypy ilustrują, jak korzystać z tego selektora: nazwa_schematu.nazwa_tabeli nazwa_pakietu.nazwa_funkcji nazwa_obiektu.nazwa_metody_składowej nazwa_kursora.atrybut_kursora nazwa_obiektu.nazwa_obiektu_zagnieżdżonego.atrybut_obiektu

Tego rodzaju wywołania są używane w dalszych rozdziałach książki. @

Asocjacja

Wskaźnik dostępu zdalnego umożliwia dostęp do zdalnej bazy danych przy użyciu powiązań z bazami dany.

||

Łączenie

Operator łączenia składa się z dwóch pionowych linii i służy do scalania łańcuchów znaków:

=

Porównywanie

Symbol równości to operator porównywania. Sprawdza, czy wartości są sobie równe, i niejawnie dokonuje konwersji typów, jeśli jest to możliwe. Schemat obrazujący konwersje niejawne znajduje się na rysunku 4.2. PL/SQL to język z silną kontrolą typów, dlatego nie ma w nim operatora tożsamości. Operacje porównywania w PL/SQL-u są odpowiednikami porównań tożsamościowych, ponieważ można używać w nich tylko wartości o podobnym typie.

-

Porównywanie

Symbol minus to operator negacji. Zmienia wartość liczby z dodatniej na ujemną i w drugą stronę.

!= ^=

Porównywanie

Są trzy operatory nierówności. Wszystkie działają tak samo. Można wybrać dowolny, który odpowiada standardom używanym w firmie.

>

Porównywanie

Symbol większości to operator sprawdzania nierówności. Określa, czy lewy operand jest większy od prawego.


=

Porównywanie

Połączenia symboli większości i równości to operator sprawdzania nierówności. Określa, czy lewy operand jest większy od prawego lub mu równy.

Ogranicznik

W języku PL/SQL francuskie cudzysłowy otwierające i zamykające to ograniczniki otwierające i zamykające etykiet. Etykietą może być dowolny prawidłowy identyfikator. Programiści używający Perla lub PHP powinni zwrócić uwagę na to, że symbol ten działa inaczej niż w konstrukcji „here document”.

--

Ogranicznik

Dwa przyległe myślniki składają się na operator komentarza. Tekst na prawo od tego operatora jest traktowany jak komentarz i nie zostanie przetworzony jako część programu PL/SQL. Przykładowy komentarz jednowierszowy wygląda następująco:

/* */

Ogranicznik

-- To komentarz jednowierszowy.

Te kombinacje symboli to ograniczniki otwierający i zamykający komentarza wielowierszowego. Sekwencja /* informuje parser o tym, że powinien potraktować wszystko do ogranicznika zamykającego jak komentarz. Kombinacja */ informuje parser o zakończeniu tekstu komentarza. Wszystko po tym ograniczniku jest przetwarzane jako kod. Poniżej przedstawiono przykładowy komentarz wielowierszowy: /* To pierwszy wiersz. To drugi wiersz. */

Jest wiele metod stosowania komentarzy wielowierszowych. Należy wybrać jedną z nich, która odpowiada standardom firmy, a następnie konsekwentnie jej używać. "

Ogranicznik

Cudzysłów to ogranicznik identyfikatorów. Umożliwia dostęp do tabel z katalogu bazy danych utworzonych z uwzględnieniem wielkości znaków. Jest niezbędny, jeśli w nazwach obiektów z katalogu bazy danych istotna jest wielkość znaków. Ograniczniki te są dostępne od bazy danych Oracle 10g. Aby utworzyć tabelę lub kolumnę o nazwie, w której ważna jest wielkość znaków, należy użyć omawianych ograniczników identyfikatorów w następujący sposób: CREATE TABLE "Demo" ("Demo_ID" NUMBER , demo_value VARCHAR2(10));

Można wstawić wiersz do takiej tabeli, używając poniższej składni z wykorzystaniem cudzysłowów: INSERT INTO "Demo1" VALUES (1, 'TYLKO jeden wiersz.');

W języku PL/SQL, podobnie jak w SQL-u, przy wyszukiwaniu obiektów w katalogu bazy danych trzeba używać cudzysłowów: BEGIN FOR i IN (SELECT "Demo_ID", demo_value FROM "Demo") LOOP dbms_output.put_line(i."Demo_ID"); dbms_output.put_line(i.demo_value); END LOOP; END; /

Rozdział 4. Podstawowe elementy języka

127

Tabela 4.1. Ograniczniki w języku PL/SQL — ciąg dalszy Symbol

Funkcja

Opis

"

Ogranicznik

Cudzysłowów należy używać przy identyfikatorach w zagnieżdżonym kodzie SQL, ale też przy nazwach kolumn. Jest to widoczne w pierwszym wierszu wyświetlającym dane, w którym po indeksie pętli (i) występuje selektor komponentów (.), a następnie identyfikator w cudzysłowach ("Demo_ID"). Warto zauważyć, że cudzysłowy nie są potrzebne do uzyskania dostępu do kolumn, w których nazwach wielkość znaków jest nieistotna. Jeśli programista nie obejmie cudzysłowami nazwy kolumny (identyfikatora), w której wielkość znaków ma znaczenie, program zgłosi błąd PLS-00302. Jest to informacja o braku deklaracji podanego identyfikatora. Cudzysłowy służą też do tworzenia nazw zawierających symbole zarezerwowane (można na przykład utworzyć identyfikator "X+Y").

+

Obliczenia matematyczne

Operator dodawania dodaje do siebie prawy i lewy operand, a następnie zwraca wynik.

/

Obliczenia matematyczne

Operator dzielenia dzieli lewy operand przez prawy, a następnie zwraca wynik.

**

Obliczenia matematyczne

Operator potęgowania podnosi lewy operand do potęgi określonej przez operand prawy. W PL/SQL operator ten ma najwyższy priorytet, dlatego wykładniki ułamkowe trzeba umieszczać w nawiasach (nazywanych tu ogranicznikami wyrażeń lub list), aby zapewnić poprawną kolejność wykonywania operacji. Bez nawiasów lewy operand zostanie podniesiony do potęgi określonej w liczniku, a następnie program podzieli wynik tej operacji przez wartość mianownika wykładnika ułamkowego. Użycie poniższej składni powoduje podniesienie liczby 3 do potęgi 3 i przypisanie zmiennej a wyniku równego 27: a := 3**3;

Ta instrukcja podnosi liczbę 8 do potęgi 1/3 i przypisuje wynik, 2, do zmiennej a: a := 8**(1/3);

Nawiasy gwarantują, że najpierw zostanie wykonane dzielenie. Bez pogrupowania działań przy użyciu nawiasów potęgowanie ma pierwszeństwo przed innymi operacjami matematycznymi. Warto zauważyć, że potęgowanie należy do obliczeń naukowych, dlatego należy stosować typy danych ze standardu IEEE-754. *

Obliczenia matematyczne

Operator mnożenia mnoży lewy operand przez prawy i zwraca wynik.

-

Obliczenia matematyczne

Operator odejmowania odejmuje prawy operand od lewego i zwraca wynik.

;

Instrukcje

Średnik to zakończenie instrukcji. Musi zamykać wszystkie instrukcje i bloki języka PL/SQL. W Oracle Database 12c wprowadzono możliwość pisania funkcji w klauzuli WITH zapytania, co wymaga wyłączenia średnika (;) w czasie wykonywania zapytania z zagnieżdżoną funkcją. Więcej informacji o tej technice znajdziesz w rozdziale 2. i w punkcie „Widoki wewnątrzwierszowe” w dodatku B.

Identyfikatory Identyfikatory to słowa. Mogą to być słowa zarezerwowane, kluczowe, identyfikatory wbudowane, identyfikatory w cudzysłowach, zmienne zdefiniowane przez użytkownika, podprogramy lub typy zdefiniowane przez użytkownika. Lista słów zarezerwowanych i kluczowych zmienia się w poszczególnych wersjach baz Oracle. Nie istnieje źródło z kompletną listą takich słów. Listę słów zarezerwowanych i kluczowych zawiera dodatek H, funkcji wbudowanych SQL-a — dodatek C, a funkcji wbudowanych języka PL/SQL — dodatek D.

128

Część I

Podstawy języka PL/SQL

Słowa zarezerwowane i kluczowe Słowa zarezerwowane i kluczowe to jednostki leksykalne stanowiące podstawowe narzędzie do tworzenia programów. Na przykład słowo zarezerwowane NOT pozwala zanegować wartość w operacjach porównywania, a NULL reprezentuje pustą wartość lub instrukcję. Słów tych nie można używać do definiowania nazw programów lub typów danych.

Identyfikatory wbudowane W Oracle 12c (i w kilku innych nowych wersjach) znajduje się pakiet STANDARD. Jest on dostępny globalnie z uwagi na publiczne przyznanie uprawnień do niego. W pakiecie STANDARD zdefiniowane są funkcje wbudowane, opisane w dodatku C. Pakiet ten zawiera też definicje standardowych typów danych i błędów. Należy zachować ostrożność, aby nie zastąpić identyfikatorów wbudowanych przez utworzenie jednostek o takich samych nazwach. Może tak się zdarzyć, kiedy programista zdefiniuje zmienną o nazwie identycznej z nazwą komponentu z pakietu STANDARD. W taki sam sposób można utworzyć w zagnieżdżonym bloku języka PL/SQL identyfikator zastępujący zmienną z bloku zewnętrznego.

Identyfikatory w cudzysłowach Od bazy danych Oracle 11g można stosować ograniczniki identyfikatorów w cudzysłowach do tworzenia identyfikatorów, których nie można użyć w inny sposób z uwagi na powtórne wykorzystanie danego symbolu. Identyfikatory w cudzysłowach mogą zawierać dowolne znaki drukowane, włącznie z odstępami. Nie można jednak umieścić w takim identyfikatorze samych cudzysłowów. Maksymalna długość identyfikatorów w cudzysłowach to 30 znaków. Takich identyfikatorów można też użyć, aby wykorzystać w nazwach słowa zarezerwowane i kluczowe. Jest to możliwe, ale twórcy Oracle odradzają używanie tego rozwiązania. Na przykład poniższy program tworzy identyfikator w cudzysłowach "End", który wykorzystuje słowo zarezerwowane o nieistotnej wielkości znaków: SQL> 2 3 4 5 6

DECLARE "End" NUMBER := 1; BEGIN dbms_output.put_line('Identyfikator w cudzysłowach End ['||"End"||'|'); END; /

Warto zapamiętać, że ta technika jest dostępna, ale należy jej unikać.

Zmienne, podprogramy i typy danych zdefiniowane przez użytkownika Definiowanie komponentów programu wiąże się z tworzeniem identyfikatorów. Użytkownik może zdefiniować typ danych w SQL-u (jako typ poziomu schematu) lub w języku PL/SQL. Identyfikatory zdefiniowane przez użytkownika nie mogą mieć więcej niż 30 znaków, muszą rozpoczynać się od litery i mogą obejmować symbole $, # i _. Nie można umieszczać w nich znaków przestankowych, odstępów ani myślników. Identyfikatory zdefiniowane w bloku anonimowym są dostępne tylko w nim i w blokach zagnieżdżonych. Identyfikatory zdefiniowane w funkcjach i procedurach są dostępne zgodnie z ich zasięgiem. W lokalnych funkcjach i procedurach dostępne są identyfikatory z zasięgu wywołania, natomiast są one niedostępne w funkcjach i procedurach z poziomu schematu. W specyfikacji pakietu można zdefiniować typy danych poziomu pakietu, które są dostępne w określonym schemacie. Te typy udostępnia się też innym schematom, przyznając uprawnienia EXECUTE. W ciele pakietu można zdefiniować lokalny typ danych, który jest dostępny tylko w funkcjach i procedurach zdefiniowanych w tym ciele. Aby wskazać typ danych z poziomu pakietu, należy połączyć nazwę pakietu i typu danych przy użyciu selektora komponentu. Pełne omówienie pakietów znalazło się w rozdziale 9., a wprowadzenie do nich zawiera rozdział 2.

Rozdział 4. Podstawowe elementy języka

129

Literały Literały to bezpośrednio podane znaki, łańcuchy znaków, liczby lub wartości logiczne. Wartości literałów nie są reprezentowane za pomocą identyfikatorów. Literały znakowe mogą także służyć do reprezentowania literałów z datą i czasem.

Literały znakowe Aby zdefiniować literał znakowy, należy umieścić dowolny znak wewnątrz pary apostrofów. W wartościach literałów wielkość znaków ma znaczenie, choć w samym języku PL/SQL jest ona nieistotna. Odzwierciedla to działanie SQL-a i znaków oraz łańcuchów znaków składowanych w bazie danych. Najczęściej używanym typem danych literałów znakowych jest VARCHAR2. Aby przypisać literał znakowy do zmiennej, należy użyć następującej składni: a := 'a';

Literały w postaci łańcuchów znaków Literały w postaci łańcuchów znaków są definiowane w podobny sposób, jak literały znakowe, czyli za pomocą apostrofów. Literały tego rodzaju mogą zawierać dowolną liczbę znaków odpowiadającą określonemu typowi danych. Zwykle używany jest tu typ danych VARCHAR2 lub jeden z jego typów pochodnych. Przypisywanie literału w postaci łańcucha znaków do zmiennej wygląda następująco: a := 'dowolny łańcuch znaków';

W takim literale można też umieścić tekst w cudzysłowach, co ilustruje poniższy fragment: a := 'dowolny łańcuch znaków "w cudzysłowach"';

Cudzysłowy wewnątrz apostrofów są traktowane jak zwykłe znaki.

Literały liczbowe W większości języków literały liczbowe są definiowane tak samo jak liczby. Operacja przypisania literału liczbowego do zmiennej wygląda następująco: a := 2525;

Można też przypisywać do zmiennych duże liczby, używając poniższej notacji wykładniczej: n := 2525E8; -- Ta instrukcja przypisuje do zmiennej liczbę 252 500 000 000.

Przy próbie przypisania liczby przekraczającej pojemność danego typu danych zostanie zgłoszony wyjątek przepełnienia lub niedomiaru. Aby przypisać do zmiennej liczbę zmiennoprzecinkową lub o podwójnej precyzji, należy użyć następującej składni: d := 2.0d; -- Przypisanie liczby o podwójnej precyzji o wartości 2. f := 2.0d; -- Przypisanie liczby zmiennoprzecinkowej o wartości 2.

Te przypisania są możliwe tylko wtedy, gdy zmienne mają odpowiedni typ. Tu zmienna d musi mieć typ BINARY_DOUBLE, a zmienna f — BINARY_FLOAT.

Literały logiczne Literały logiczne to wartości TRUE, FALSE i NULL. Ta trzystanowa natura zmiennych logicznych sprawia, że program może nieprawidłowo oceniać warunki not true i not false, jeśli zmienna ma wartość NULL. W rozdziale 5. opisano, jak tworzyć instrukcje warunkowe dające oczekiwane wyniki. Do zadeklarowanej zmiennej typu BOOLEAN można przypisać następujące wartości: b := TRUE; -- Przypisanie stanu „prawda”. b := FALSE; -- Przypisanie stanu „fałsz”. b := NULL; -- Przypisanie pustej (domyślnej) wartości.

130

Część I

Podstawy języka PL/SQL

Dobrą praktyką jest przypisywanie początkowej wartości TRUE lub FALSE wszystkim zmiennym logicznym. Ma to na celu jawne zdefiniowanie ich wyjściowego stanu. Warto też rozważyć tworzenie kolumn o wartościach logicznych z ograniczeniem NOT NULL.

Literały z datą i czasem Literały z datą są niejawnie przekształcane z literałów z łańcuchem znaków o domyślnej masce formatu. Domyślna maska formatu dla dat ma postać DD-MIE-RR lub DD-MIE-RRRR. Część DD reprezentuje dzień zapisany za pomocą dwóch cyfr, MIE zawiera trzyliterowy skrót nazwy miesiąca, RR to dwie cyfry oznaczające rok względny, a RRRR reprezentuje rok absolutny zapisany przy użyciu czterech cyfr. Rok względny jest zawarty w okresie 50 lat wstecz i naprzód od bieżącego czasu zegara systemowego. Poniższe instrukcje przedstawiają przypisanie względnej i absolutnej daty do zadeklarowanych wcześniej zmiennych typu DATE: relative_date := '01-JUN-07'; -- Przypisanie daty 01-JUN-2007. absolute_date := '01-JUN-1907'; -- Przypisanie daty 01-JUN-1907.

Niejawne przypisywanie nie powiedzie się, jeśli programista użyje innej maski formatu, na przykład MIE-DD-RRRR. Takie literały można przypisywać jawnie, używając funkcji TO_DATE lub CAST. Tylko specyficzna dla Oracle funkcja TO_DATE umożliwia stosowanie masek formatu różnych od domyślnej. Funkcja ta ma dwie wersje: date_1 := TO_DATE('01-JUN-07'); -- Domyślna maska formatu. date_2 := TO_DATE('JUN-01-07','MON-DD-YY'); -- Niestandardowa maska formatu.

Przy użyciu funkcji CAST można zastosować jedną z opisanych wcześniej domyślnych masek formatu: date_1 := CAST('01-JUN-07' AS DATE); -- Maska formatu dla dat względnych. date_2 := CAST('01-JUN-2007' AS DATE); -- Maska formatu dla dat absolutnych.

Aby wyświetlić pełną datę, można użyć funkcji TO_CHAR(date_variable, 'MON-DD-YYYY'). Opisane operacje języka PL/SQL odzwierciedlają działanie języka SQL w Oracle.

Komentarze W języku PL/SQL można używać komentarzy jedno- i wielowierszowych. Dwa myślniki rozpoczynają komentarz jednowierszowy, a ograniczniki /* i */ wydzielają komentarze wielowierszowe. Komentarze jednowierszowe wyglądają następująco: -- To komentarz jednowierszowy.

Poniżej przedstawiono komentarz wielowierszowy: /* To komentarz wielowierszowy. Styl i wcięcia powinny odpowiadać standardom używanym w firmie. */

Zaplanowane komentarze nie sprawiają problemów, jednak oznaczanie komentarzem kodu przy testowaniu lub diagnozowaniu programów może prowadzić do błędów. Największe problemy pojawiają się po opatrzeniu komentarzem wszystkich instrukcji wykonywalnych w bloku kodu. Spowoduje to zgłoszenie wielu błędów parsowania, ponieważ każdy blok kodu musi zawierać przynajmniej jedną instrukcję, co opisano w punkcie „Struktura bloków” w rozdziale 3. Inny problem, który często pojawia się przy stosowaniu komentarzy jednowierszowych, wiąże się z umieszczaniem ich przed symbolem zakończenia instrukcji (średnikiem) lub słowem kluczowym kończącym dany blok. Przy próbie uruchomienia lub skompilowania określonej jednostki pojawi się wtedy błąd parsowania. Kompilacja programów w języku PL/SQL może oznaczać próbę uruchomienia bloku anonimowego lub utworzenia programu składowanego. W obu przypadkach następuje parsowanie programu do p-kodu języka PL/SQL. Silnik tego języka uruchamia programy mające postać p-kodu.

W tym podrozdziale omówiono prawidłowe znaki i symbole języka PL/SQL, a także opisano jednostki leksykalne, czyli ograniczniki, identyfikatory, literały i komentarze.

Rozdział 4. Podstawowe elementy języka

131

Powtórzenie materiału W tym podrozdziale omówiono następujące zagadnienia dotyczące znaków i jednostek leksykalnych:  Jednostki leksykalne to podstawowe cegiełki języka programowania. Są nimi: ograniczniki, identyfikatory, literały i komentarze.  Jednostki leksykalne tworzone są w wyniku połączenia odpowiednich znaków i symboli.  Ograniczniki to symbole (lub kombinacje symboli), które pozwalają tworzyć literały znakowe i wykonywać operacje przypisywania, wiązania, łączenia, porównywania, a także obliczenia matematyczne i instrukcje sterujące.  Pakiet STANDARD obejmuje wbudowane identyfikatory.  Można tworzyć identyfikatory zdefiniowane przez użytkownika, na przykład nazwy typów danych i zmiennych, które nie powodują konfliktu ze słowami kluczowymi lub zarezerwowanymi.  Można tworzyć identyfikatory w cudzysłowach, aby ograniczać słowa, które mogą być identyczne ze słowami kluczowymi lub zarezerwowanymi.  Można tworzyć komentarze jedno- i wielowierszowe.

Zmienne i typy danych PL/SQL to blokowy język programowania. Jednostkami programów mogą być bloki nazwane lub bez nazwy. Te ostatnie są nazywane blokami anonimowymi i to określenie jest używane w tej książce. Bloki nazwane to funkcje, procedury, pakiety z wewnętrznymi funkcjami i procedurami oraz typy obiektowe (też obejmujące funkcje i procedury). W rozdziale 11. wyjaśniono, że można tworzyć funkcje i procedury statyczne oraz egzemplarza, a także specjalne funkcje, konstruktory, służące do tworzenia egzemplarzy typów obiektowych. Styl pisania kodu w języku PL/SQL jest odmienny niż w językach C, C++ i Java. Na przykład nawiasy klamrowe nie ograniczają tu zasięgu bloków. Słowo kluczowe DECLARE rozpoczyna sekcję deklaracji w blokach anonimowych, a nagłówek, specyfikacja lub sygnatura (obejmująca nazwę, listę parametrów i typ zwracanej wartości) funkcji albo procedury rozpoczyna taką sekcję w blokach nazwanych. Słowo kluczowe BEGIN rozpoczyna sekcję wykonawczą i kończy sekcję deklaracji. Słowo kluczowe EXCEPTION to początek sekcji obsługi wyjątków. Ta sekcja kończy się słowem kluczowym END, które jednocześnie oznacza koniec danej jednostki. Choć bloki anonimowe w niektórych sytuacjach są przydatne, częściej tworzy się podprogramy wielokrotnego użytku — funkcje, procedury, pakiety i typy obiektowe. Programy w postaci bloków anonimowych są przydatne w określonych warunkach. Zwykle programiści używają ich do tworzenia skryptów, które generują dane lub wykonują inne jednorazowe operacje. Skrypty to pliki tekstowe zawierające instrukcje SQL-a i/lub bloki anonimowe języka PL/SQL, które wykonują zestaw sekwencyjnych operacji. Takie bloki są przydatne także wtedy, gdy programista chce zagnieździć daną czynność w sekcji wykonawczej innego bloku języka PL/SQL. Podstawowa struktura bloku anonimowego obejmuje sekcję wykonawczą. W takim bloku można też umieścić opcjonalne sekcje deklaracji i wyjątków. Rysunek 4.1 ilustruje prototypy bloków anonimowych (po lewej) i nazwanych (po prawej). W bloku deklaracji można zadeklarować typy danych, struktury i zmienne, a także nazwane funkcje i procedury. Deklaracja zmiennej oznacza określenie jej nazwy i typu danych. Można też zdefiniować zmienną, podając jej nazwę, typ danych i wartość. Definicja polega więc na zadeklarowaniu zmiennej i przypisaniu jej wartości. Choć można zaimplementować blok nazwany w sekcji deklaracji innego programu, takich bloków nie można implementować w żadnym innym miejscu aplikacji w języku PL/SQL. Bloki anonimowe można implementować tylko w sekcjach wykonawczej i wyjątków innych programów. Przykłady ilustrujące zasięg zmiennych w różnych blokach anonimowych i nazwanych przedstawiono w rozdziale 3. W dwóch kolejnych punktach opisano dostępne typy danych języka PL/SQL i ich podstawowe zastosowania.

132

Część I

Podstawy języka PL/SQL

Rysunek 4.1. Struktura bloków języka PL/SQL

Typy zmiennych W języku PL/SQL używane są dwa główne typy zmiennych: skalarne i złożone. Zmienne skalarne mogą zawierać tylko jeden element, na przykład znak, datę lub liczbę. Zmienne złożone składają się z typów prostych (podstawowych) danego języka programowania. W Oracle takie zmienne to rekordy (struktury), tablice, listy, kursory referencyjne i typy obiektowe. Systemowe kursory referencyjne to wyspecjalizowana, hybrydowa struktura dostępna tylko w języku PL/SQL. Działa ona jak lista typów rekordowych. Opis takich kursorów znajdziesz w punkcie „Systemowe kursory referencyjne” w dalszej części rozdziału. W języku PL/SQL można używać wszystkich typów danych z języka Oracle SQL. W PL/SQL-u wprowadzono też typy logiczne i grupę typów pochodnych od typów danych języka SQL. Te typy pochodne dziedziczą operacje typów bazowych, a ponadto obowiązują w nich często pewne ograniczenia. W nieograniczonych pochodnych typach danych (nazywanych też aliasami) działanie typów bazowych się nie zmienia. Typ bazowy można też nazwać nadtypem, ponieważ jest modelem używanym do tworzenia typów pochodnych. Nieograniczonych typów pochodnych można używać zamiast typów bazowych, podczas gdy do ograniczonych typów pochodnych można przypisywać tylko odpowiednie wartości. Typy danych można samodzielnie rozszerzać, tworząc własne typy pochodne, co ilustruje punkt „Typy CHAR i CHARACTER” w dalszej części rozdziału. W PL/SQL-u, podobnie jak w innych językach programowania, można definiować typy i deklarować zmienne. Definicja typu określa jego nazwę i sposób zarządzania obiektami tego typu w pamięci. Definicja zmiennej polega na zadeklarowaniu jej i przypisaniu do niej wartości. W momencie deklarowania zmiennej jej nazwa jest wiązana z podanym typem danych, a następnie dodawana do przestrzeni nazw programu jako identyfikator. W niektórych językach programowania deklarowane zmienne nie otrzymują wartości. PL/SQL automatycznie przypisuje większości zmiennych wartość null w momencie deklarowania. Oznacza to, że zmienne w tym języku są prawie zawsze zdefiniowane. Aby zadeklarować zmienną, należy określić jej typ lub zakotwiczyć jej typ do typu kolumny z katalogu bazy danych. Zakotwiczenie zmiennej przy użyciu atrybutu %TYPE powoduje, że program automatycznie dostosuje jej typ, jeśli zmieni się typ danych kolumny. Dlatego zmiana typu danych kolumny może prowadzić do zmiany typu danych zmiennej. Choć zmiana typu kolumny tabeli jest możliwa, gdy tabela nie zawiera danych, czasem prowadzi do problemów, jeśli w tabeli znajdują się informacje. Typ zapełnionej kolumny można zmodyfikować tylko wtedy, gdy baza Oracle potrafi zrzutować wartości wyjściowego typu danych na wartości nowego typu. Brak niejawnej konwersji typu danych oznacza, że trzeba wyeksportować wartości kolumny i ręcznie przekształcić je na wartości nowego typu. Niestety, zmiana typu kolumny i konwersja danych to dopiero początek pracy. Wynika to z tego, że program jest strukturalnie powiązany z typem danych kolumny. Choć możliwe jest, że niewielkie

Rozdział 4. Podstawowe elementy języka

133

zmiany w pojemności łańcucha znaków lub liczby nie zakłócą pracy programu, duże modyfikacje mogą doprowadzić do problemów. Także niektóre operacje logiczne, przypisania i porównania mogą zakończyć się niepowodzeniem, jeśli początkowo jako typu bazowego używano łańcuchów znaków, a następnie zmieniono go na datę. Niejawna konwersja z łańcucha znaków na datę lub w drugą stronę jest niemożliwa, dlatego taka zmiana kolumny może zakłócić porównania w warunkach w funkcjach lub procedurach. Dlatego zalecam, aby za pomocą atrybutu %TYPE wiązać typy z kolumnami tylko wtedy, gdy wiadomo, że w przyszłości typ kolumny się nie zmieni. Zmiana typu danych kolumny nie spowoduje zgłoszenia błędu, ale procedury składowane, w których używane są zmienne nieodpowiedniego typu, będą nieprawidłowe.

Za przeprowadzanie konwersji niejawnych odpowiada silnik języka PL/SQL. W PL/SQL-u, inaczej niż w niektórych innych językach programowania, możliwa jest konwersja niejawna prowadząca do utraty precyzji (szczegółów). Jeśli programista przypisze zmienną typu BINARY_FLOAT do zmiennej typu BINARY_INTEGER, wszystkie cyfry znajdujące się po przecinku zostaną niejawnie obcięte. Konwersja jawna wymaga bezpośredniego przekształcenia danych, na przykład przez wywołanie funkcji wbudowanej TO_CHAR przy wyświetlaniu znaczników czasu zapisanych w zmiennych typu DATE. Rysunek 4.2 przedstawia listę konwersji niejawnych.

Rysunek 4.2. Konwersje niejawne

Jest jeden pozorny wyjątek od reguł deklarowania zmiennych. Systemowe kursory referencyjne ze słabą kontrolą typu nie są definiowane do czasu wykonania programu. Takie kursory przyjmują przypisany im numer i strukturę rekordu przypisanego do nich wiersza. Systemowe kursory referencyjne zwracają listę struktur rekordowych, a takie struktury można przypisywać tylko do zmiennych złożonych. Ponadto można zakotwiczyć systemowy kursor referencyjny o ścisłej kontroli typów do tabeli lub widoku z katalogu. Technika ta działa podobnie jak kotwiczenie typów zmiennych do typów kolumn, co opisano w rozdziale 3. Typy danych można definiować w językach SQL i PL/SQL. Typów danych z SQL-a można używać w instrukcjach w obu tych językach, podczas gdy z typów danych z języka PL/SQL można korzystać tylko w jednostkach programów napisanych w tym języku.

134

Część I

Podstawy języka PL/SQL

Bufor języka PL/SQL i wyświetlanie informacji w oknie konsoli Jak ilustruje to rysunek 1.2, między silnikami środowiska SQL*Plus i języka PL/SQL znajduje się bufor na dane wyjściowe. Można otworzyć go w SQL*Plus, ustawiając zmienną środowiskową SERVEROUTPUT: SQL> SET SERVEROUTPUT ON SIZE 1000000

Po jej włączeniu dane przekazywane do procedur PUT_LINE i NEW_LINE pakietu DBMS_OUTPUT będą wyświetlane w środowisku SQL*Plus. Przy pierwszym uruchomieniu programu po ustawieniu tej zmiennej ilość danych wyjściowych może być większa od oczekiwanej. Jest to wynik uruchomienia używającego bufora programu w języku PL/SQL bez wcześniejszego ustawienia omawianej zmiennej środowiskowej. Ustawienia zmiennej środowiskowej w SQL*Plus są tracone przy zmianie schematu. Trzeba pamiętać wtedy o ponownym ustawieniu zmiennej SERVEROUTPUT, ponieważ w momencie zmiany schematu bufor na dane wejściowe zostaje zamknięty. Aby włączyć bufor w kodzie PL/SQL, należy użyć następującej instrukcji: dbms_output.enable(1000000);

Pierwszy zapis danych do bufora po włączeniu wspomnianej zmiennej środowiskowej spowoduje wyświetlenie całej jego zawartości w środowisku SQL*Plus. Aby usunąć starsze dane, należy wyłączyć otwarty bufor, a następnie włączyć go ponownie, wywołując kolejno dwie poniższe instrukcje: dbms_output.disable; dbms_output.enable(1000000);

Zaleca się stosowanie procedury DISABLE, ponieważ gwarantuje ona, że program nie wyświetli niepożądanych starszych danych. Dane wyjściowe w oknie konsoli można wyświetlić przy użyciu procedury PUT_LINE. Ta procedura dodaje do bufora łańcuch znaków i znak nowego wiersza. Procedura NEW_LINE pozwala przejść do nowego wiersza. Poniższe instrukcje ilustrują wyświetlanie informacji z programu PL/SQL w środowisku SQL*Plus: BEGIN dbms_output.put_line('Wiersz pierwszy.'); dbms_output.new_line; dbms_output.put_line('Wiersz drugi.'); END; /

Efekt działania tego bloku anonimowego to: Wiersz pierwszy. Wiersz drugi.

Ta technika służy do wyświetlania danych w oknie konsoli w celach diagnostycznych lub zapisywania raportów w pliku. Można także użyć polecenia SPOOL środowiska SQL*Plus, aby przesłać standardowe informacje wyjściowe jednocześnie do konsoli i pliku (podobnie działa instrukcja tee w systemie Unix). To rozwiązanie pozwala wygenerować pliki tekstowe z raportami.

Pierwszy z poniższych punktów opisuje skalarne typy danych, drugi — duże obiekty, trzeci — typy złożone, a czwarty — typy referencyjne. Zagadnienia są uporządkowane pod kątem łatwości wyszukiwania informacji. Skalarne typy danych to typy proste języka i są elementami, z których można tworzyć złożone typy danych. Następny punkt opisuje właśnie te podstawowe elementy.

Skalarne typy danych Typy proste są opisane w alfabetycznie uporządkowanych podpunktach. Każdy z nich opisuje jeden typ danych, ilustruje definiowanie i/lub deklarowanie zmiennych tego typu oraz przypisywanie do nich wartości. Rysunek 4.3 przedstawia cztery główne rodzaje zmiennych skalarnych i powiązane z nimi typy bazowe oraz pochodne. Do tworzenia zmiennych typów skalarnych w sekcji deklaracji służy poniższy prototyp: nazwa_zmiennej typ_danych [NOT NULL] [:= wartość_literału];

Rozdział 4. Podstawowe elementy języka

135

Rysunek 4.3. Skalarne typy danych

Niektóre typy danych wymagają określenia precyzji przy definiowaniu zmiennej. Precyzję określa maksymalny rozmiar danej zmiennej w bajtach lub znakach. Przy liczbowych typach danych można podać też skalę. Wyznacza ona liczbę miejsc po przecinku. Odzwierciedla to metody korzystania z tych typów danych w języku SQL.

Logiczne typy danych (BOOLEAN) Zmienne typu BOOLEAN przyjmują trzy wartości: TRUE, FALSE i NULL. Trzy stany zmiennych logicznych sprawiają, że program może nieprawidłowo oceniać warunki not true i not false, jeśli zmienna ma wartość NULL. W punkcie „Logika trójwartościowa” w rozdziale 3. i w punkcie „Instrukcje if-then-else” w rozdziale 5. opisano, jak tworzyć instrukcje warunkowe dające oczekiwane wyniki. Prototyp deklaracji typu BOOLEAN wygląda następująco: BOOLEAN [NOT NULL]

Można zdefiniować zmienne logiczne przez niejawne przypisanie im wartości null lub jawne przypisanie wartości TRUE lub FALSE. W tym celu należy użyć poniższych instrukcji w sekcji deklaracji: var1 BOOLEAN; -- Niejawne przypisanie wartości null. var2 BOOLEAN NOT NULL := TRUE; -- Jawne przypisanie wartości TRUE. var3 BOOLEAN NOT NULL := FALSE; -- Jawne przypisanie wartości FALSE.

136

Część I

Podstawy języka PL/SQL

Zawsze należy bezpośrednio inicjować zmienne logiczne w jednostkach programu. Ta praktyka pozwala uniknąć nieoczekiwanego działania programu. Użycie w deklaracji klauzuli NOT NULL gwarantuje, że zmienna logiczna nigdy nie będzie miała wartości null. Tworzenie typów pochodnych od typu BOOLEAN zwykle nie ma większego sensu, można to jednak zrobić. Służy do tego następująca składnia: SUBTYPE booked IS BOOLEAN;

Ta instrukcja tworzy typ pochodny BOOKED, który jest nieograniczonym typem pochodnym typu BOOLEAN. Może się on okazać przydatny, jeśli programista będzie potrzebował drugiej nazwy dla typu BOOLEAN, jednak zwykle tworzenie typów pochodnych od typu logicznego nie jest potrzebne. Jak opisano to w jednym z wcześniejszych podpunktów, „Literały logiczne”, w sekcji wykonawczej można przypisać literał do zmiennej logicznej w następujący sposób: var := TRUE;

W odróżnieniu od łańcuchów znaków wartości TRUE, FALSE i NULL nie są ograniczone apostrofami. Wszystkie trzy wymienione wartości to słowa zarezerwowane języka PL/SQL.

Znaki i łańcuchy znaków

Znaki i łańcuchy znaków działają podobnie jak klasa String języka Java. Łańcuchy znaków w językach C i C++ to jednowymiarowe tablice znaków. Znakowe typy danych przechowują łańcuchy znaków o stałej długości. Można określić ich rozmiar, podając maksymalną liczbę bajtów lub znaków łańcucha. Próba zapisania większej liczby bajtów lub znaków spowoduje zgłoszenie wyjątku. Poniższy program ilustruje różnice w przydziale pamięci zmiennym typu CHAR i VARCHAR2: DECLARE c CHAR(32767) := ' '; v VARCHAR2(32767) := ' '; BEGIN dbms_output.put_line('c ma długość ['||LENGTH(c)||']'); dbms_output.put_line('v ma długość ['||LENGTH(v)||']'); v := v || ' '; dbms_output.put_line('v ma długość ['||LENGTH(v)||']'); END; /

W programie tym zdefiniowano dwie zmienne, wyświetlono ich długość (zobacz opis funkcji wbudowanych języka PL/SQL w dodatku D), a następnie dołączono nowy odstęp do zmiennej typu VARCHAR2, aby przedstawić przydział pamięci. Jeśli programista włączył bufor języka SQL*Plus (ustawiając zmienną SERVEROUTPUT), program wyświetli w oknie konsoli następujące dane: c ma długość [32676] v ma długość [1] v ma długość [2]

Dane wyjściowe pokazują, że zmiennej typu CHAR system przydzielił zdefiniowaną ilość pamięci. Może ona przekraczać ilość pamięci potrzebną do zarządzania wartością zmiennej. Z kolei zmiennej typu VARCHAR2 system dynamicznie przypisuje tylko tyle pamięci, ile potrzeba do przechowywania jej wartości.

Typy CHAR i CHARACTER Typ danych CHAR to bazowy typ danych dla łańcuchów znaków o stałej długości. Można zwiększyć rozmiar zmiennej tego typu do 32767 bajtów, a ich domyślna wielkość to 1 bajt. Jeśli parametr MAX_ STRING_SIZE ma wartość STANDARD, to wielkość typu CHAR języka PL/SQL przekracza maksymalny rozmiar kolumn typu CHAR w SQL-u, czyli 4000 bajtów. Ustawienie tego parametru na wartość EXTENDED pozwala zapisać w kolumnach VARCHAR2 w SQL-u do 32 767 bajtów. Dłuższe łańcuchy znaków (powyższej 4000 bajtów) można przechowywać także w kolumnach typu CLOB i LONG. Twórcy bazy danych Oracle zalecają używanie typu CLOB, ponieważ typy LONG i LONG RAW są obsługiwane tylko ze względu na zapewnienie zgodności wstecz. Poniżej przedstawiony jest prototyp definicji typu danych CHAR: CHAR[(maksymalny_rozmiar [BYTE | CHAR])] [NOT NULL]

Rozdział 4. Podstawowe elementy języka

137

Są cztery sposoby na zadeklarowanie zmiennej typu CHAR o wartości domyślnej null: var1 var1 var1 var1

CHAR; CHAR(1); CHAR(1 BYTE); CHAR(1 CHAR);

-----

Niejawnie określony rozmiar 1 bajta. Jawnie określony rozmiar 1 bajta. Jawnie określony rozmiar 1 bajta. Jawnie określony rozmiar 1 znaku.

Przy przydziale pamięci na podstawie liczby znaków maksymalna pojemność zmiennej zmienia się w zależności od zestawu znaków używanego w bazie danych. W niektórych zestawach znaki zajmują po 2 lub 3 bajty. Trzeba podzielić wartość 32767 przez liczbę bajtów potrzebnych do zapisania znaku, co oznacza, że maksymalna pojemność zmiennej typu CHAR to 16383 znaki dla zestawów znaków dwubajtowych i 10922 znaki dla zestawów o znakach trzybajtowych. Można użyć klauzuli NOT NULL, aby zagwarantować, że zmiennej typu CHAR zostanie przypisana wartość. Zgodnie z ogólną praktyką nie należy ograniczać zmiennych typu CHAR, jeśli nie ma do tego istotnych przyczyn biznesowych. CHARACTER to typ pochodny typu CHAR. Typ danych CHARACTER ma taki sam przedział wartości, co jego typ pochodny. Jest aliasem, czyli nieograniczonym typem pochodnym. Przy przypisywaniu wartości typu CHAR i CHARACTER następuje między nimi niejawna konwersja, pod warunkiem jednak, że zmienne użyte w przypisaniu mają tę samą pojemność. Na rozmiar zmiennych typu CHARACTER wpływ mają dwa czynniki: liczba przypisywanych jednostek i ich rodzaj. Łańcucha o długości 3 znaków (pobranych z zestawu znaków) nie można zapisać w łańcuchu o 3 bajtach, a — co bardziej naturalne — łańcucha trzyznakowego nie można zapisać w dwuznakowym. Próba wykonania przypisania tego rodzaju wywoła wyjątek ORA-06502, który informuje o tym, że bufor na łańcuchy znaków zmiennej typu CHARACTER jest za mały, aby móc zapisać podaną wartość. Aby zadeklarować typ pochodny typu CHAR, należy użyć poniższego prototypu: SUBTYPE typ_pochodny IS typ_bazowy[(maksymalny_rozmiar [BYTE | CHAR])] [NOT NULL];

Poniższy kod tworzy ograniczony typ pochodny CODE i używa go: DECLARE SUBTYPE code IS CHAR(1 CHAR); c CHAR(1 CHAR) := 'A'; d CODE; BEGIN d := c; END; /

W łańcuchach i znakach nie można określać przedziałów znaków, a tylko maksymalny rozmiar typu. Inaczej odbywa się tworzenie typów pochodnych typów liczbowych, w których można ograniczyć przedziały. Globalizacja powoduje wiele problemów w obszarze stosowania łańcuchów znaków o zmiennej długości. Warto rozważyć użycie typów danych NCHAR przy zarządzaniu wielobajtowymi zestawami znaków lub zestawem Unicode.

Typy danych LONG i LONG RAW Typy danych LONG i LONG RAW są udostępniane tylko ze względu na zgodność wstecz. Zamiast typu LONG należy używać typów CLOB lub NCLOB, a zamiast LONG RAW — BLOB lub BFILE. Typ danych LONG przechowuje strumienie znaków, a LONG RAW — binarne łańcuchy znaków. W programach w języku PL/SQL typy danych LONG i LONG RAW mogą przechowywać łańcuchy znaków o zmiennej długości zajmujące do 32767 bajtów. Jest to dużo mniej niż 2 gigabajty, które można zapisać w kolumnach typów LONG i LONG RAW w bazie danych. Maksymalna pojemność zmiennych typów LONG i LONG RAW jest mniejsza niż zmiennych typów CHAR, NCHAR, VARCHAR2 i NVARCHAR2, a przy pojemności typów LOB (od 8 do 128 terabajtów) wydaje się po prostu mikroskopijna. Poniżej przedstawiono prototypy deklaracji typów LONG i LONG RAW: LONG [NOT NULL] LONG RAW [NOT NULL]

138

Część I

Podstawy języka PL/SQL

Można użyć klauzuli NOT NULL, aby zagwarantować, że do zmiennych typów LONG i LONG RAW zostanie przypisana wartość. Zgodnie z ogólną praktyką nie należy ograniczać tych typów danych, jeśli nie ma do tego istotnych powodów biznesowych. Zmienne typów LONG i LONG RAW z domyślną wartością null można zadeklarować w następujący sposób: var1 LONG; var2 LONG RAW;

-- Niejawnie określony rozmiar 0 bajtów. -- Niejawnie określony rozmiar 0 bajtów.

Poniższy kod ilustruje, jak zdefiniować zmienne tych typów i przypisać im wartości: var1 LONG := 'CAR'; var2 LONG RAW := HEXTORAW('43'||'41'||'52'); -- Słowo CAR w notacji heksadecymalnej.

Choć typ danych LONG jest łatwy w użyciu, ma niewielką pojemność w porównaniu z typami CLOB i NCLOB. Ponadto typy CHAR i VARCHAR2 przechowują o siedem bajtów więcej niż typ LONG. Należy rozważyć używanie zmiennych o typach odpowiadających typom kolumny, ponieważ dzięki temu konserwacja programu będzie łatwiejsza (i tańsza). Zalecana jest zmiana typów CHAR i LONG kolumn na typy VARCHAR2 i LOB.

Warto zauważyć, że przed przypisaniem strumienia heksadecymalnego do zmiennej typu LONG RAW trzeba przekształcić go na strumień surowych danych przy użyciu funkcji HEXTORAW. Próba przypisania nieskonwertowanego strumienia znaków spowoduje zgłoszenie wyjątku ORA-06502 informującego o błędzie konwersji danych heksadecymalnych na surowe. Trzeba też pamiętać, że język PL/SQL nie interpretuje strumieni danych w formacie LONG RAW.

Typy ROWID i UROWID Typ ROWID odpowiada pseudokolumnie ROWID dowolnej tabeli bazy danych Oracle. Można przekształcić zmienną typu ROWID na 18-znakowy łańcuch, używając funkcji ROWIDTOCHAR. Do przekształcania tych danych w drugą stronę służy funkcja CHARTOROWID. Obie te funkcje wbudowane SQL-a zostały opisane w dodatku C. Niepoprawna konwersja między łańcuchem znaków a zmienną typu ROWID wywołuje błąd SYS_INVALID_ROWID. Typ ROWID jest dostępny tylko ze względu na zachowanie zgodności wstecz. Zaleca się używanie zamiast niego typu UROWID (uniwersalnego identyfikatora wiersza).

Typ UROWID określa uniwersalny identyfikator wiersza. Typ ten współdziała z logicznymi identyfikatorami ROWID przechowywanymi w tabelach indeksowanych, czego nie umożliwia zwykły typ ROWID. Zmiennych typu UROWID należy używać w programach w języku PL/SQL we wszystkich operacjach zarządzania identyfikatorami ROWID bazy danych Oracle, a także przy obsłudze wartości typu ROWID spoza tej bazy. Prototypy deklaracji typów ROWID i UROWID wyglądają następująco: ROWID UROWID

Przy przekształcaniu wartości tych typów dobrze działa konwersja niejawna. Rzadko trzeba uciekać się do stosowania funkcji ROWIDTOCHAR i CHARTOROWID.

Typy danych VARCHAR2, STRING i VARCHAR VARCHAR2 to bazowy typ danych łańcuchów znaków o zmiennej długości. Oprócz kilku różnic ten typ działa podobnie do typu CHAR, opisanego w punkcie „Typy CHAR i CHARACTER” we wcześniejszej części rozdziału. Tu znajdziesz powtórzenie niektórych informacji (jest ono przydatne, jeśli używasz tej książki jak encyklopedii i nie zapoznałeś się jeszcze z opisem typu CHAR). Zauważ, że w typie VARCHAR2 trzeba określić fizyczny rozmiar danych, natomiast w typie CHAR i jego podtypach nie jest to konieczne. Zmienne typu VARCHAR2 mogą zawierać do 32767 znaków. Niestety, przy parametrze MAX_STRING_ SIZE ustawionym na wartość STANDARD wielkość typu VARCHAR2 języka PL/SQL może przekraczać 4000 bajtów, czyli maksymalną pojemność SQL-owych kolumn typu VARCHAR2. Ustawienie parametru MAX_ STRING_SIZE na wartość EXTENDED pozwala zapisać w kolumnach VARCHAR2 w SQL-u do 32 767 bajtów.

Rozdział 4. Podstawowe elementy języka

139

Do przechowywania łańcuchów znaków dłuższych od limitu 4000 znaków można też używać kolumn typu CLOB i LONG. Twórcy Oracle zalecają stosowanie typu CLOB, ponieważ typ LONG jest obsługiwany tylko ze względu na zachowanie zgodności ze starszymi wersjami bazy. Poniższy prototyp przedstawia deklarację typu VARCHAR2: VARCHAR2(maksymalna_wielkość [BYTE | CHAR]) [NOT NULL]

Można użyć klauzuli NOT NULL, aby się upewnić, że do zmiennej typu VARCHAR2 zostanie przypisana wartość. Zgodnie z ogólnie stosowaną praktyką nie należy ograniczać łańcuchów znaków o zmiennej długości bez istotnych przyczyn biznesowych. Warto zastanowić się nad utworzeniem typu pochodnego wymuszającego potrzebne ograniczenia. W deklaracji typów danych z rodziny VARCHAR2 trzeba określić ich fizyczną pojemność, podczas gdy w typach CHAR jest to opcjonalne. Rozmiar fizyczny tych pierwszych jest niezbędny, ponieważ baza danych potrzebuje takich informacji do przydzielenia odpowiedniej ilości pamięci zmiennym tego typu. Jeśli programista określi, że zmienna typu VARCHAR2 może mieć określoną pojemność, silnik języka PL/SQL przydzieli tylko tyle pamięci, aby móc zarządzać wartością danych. Zwykle prowadzi to do optymalizacji pracy programu. Są trzy sposoby definiowania zmiennych typu VARCHAR2 o domyślnej wartości null: var1 VARCHAR2(100); -- Jawnie określony rozmiar 100 bajtów. var2 VARCHAR2(100 BYTE); -- Jawnie określony rozmiar 100 bajtów. var3 VARCHAR2(100 CHAR); -- Jawnie określony rozmiar 100 znaków.

Jeśli programista używa przydziału pamięci na podstawie liczby znaków, maksymalny rozmiar zmiennej jest różny w zależności od zestawu znaków używanego w bazie danych. W niektórych zestawach do zapisu znaków potrzebne są 2 lub 3 bajty. Należy podzielić wartość 32767 przez liczbę bajtów potrzebnych do zapisania znaku, co oznacza, że maksymalna pojemność zmiennej typu VARCHAR2 to 16383 znaki dla zestawów znaków dwubajtowych i 10922 znaki dla znaków trzybajtowych. STRING i VARCHAR to typy pochodne typu VARCHAR2. Oba mają ten sam przedział wartości, co typ bazowy VARCHAR2. Pełnią funkcję aliasów i są nieograniczonymi typami pochodnymi. Przy przekazywaniu wartości między zmiennymi tych typów zachodzi konwersja niejawna, pod warunkiem jednak, że zmienne te mają tę samą pojemność. Na rozmiar łańcuchów znaków wpływ mają dwa czynniki: liczba przypisywanych jednostek i ich rodzaj. Łańcucha o długości 3 znaków (pobranych z zestawu znaków) nie można zapisać w łańcuchu o 3 bajtach, a — co bardziej naturalne — łańcucha trzyznakowego nie można zapisać w dwuznakowym. Próba wykonania przypisania tego rodzaju wywoła wyjątek ORA-06502, który informuje o tym, że bufor na łańcuchy znaków jest za mały, aby móc zapisać podaną wartość. Aby zadeklarować typ pochodny typu VARCHAR2, należy użyć poniższego prototypu: SUBTYPE typ_pochodny IS typ_bazowy[(maksymalny_rozmiar [BYTE | CHAR])] [NOT NULL];

Poniższy kod tworzy ograniczony typ pochodny DB_STRING: DECLARE SUBTYPE db_string IS VARCHAR2(4000 BYTE); c VARCHAR2(1 CHAR) := 'A'; d DB_STRING; BEGIN d := c; END; /

Ten fragment tworzy typ pochodny, który nie może przekroczyć fizycznego limitu pojemności kolumny typu VARCHAR2. Działa w taki sam sposób niezależnie od zestawu znaków używanego w bazie danych. Może to być bardzo przydatne, jeśli programista chce zapewnić zgodność fizycznych ograniczeń bazy danych z blokami języka PL/SQL. W łańcuchach znaków nie można określić przedziałów znaków w sposób, w jaki można podać przedział wartości w liczbowych typach pochodnych. Programista może ustawić tylko maksymalny rozmiar typu, który można zastąpić, deklarując typ pochodny o nowej maksymalnej pojemności, mniejszej lub równej 32 767 bajtom.

140

Część I

Podstawy języka PL/SQL

Globalizacja wiąże się z wieloma problemami w obszarze korzystania z łańcuchów znaków o zmiennej długości. Przy używaniu zestawów znaków wielobajtowych lub zestawu Unicode warto rozważyć zastosowanie typu danych NVARCHAR2.

Daty, godziny i interwały DATE to typ bazowy dla dat, godzin i interwałów. Rozróżnia się dwa typy pochodne służące do zarzą-

dzania interwałami i trzy do obsługi znaczników czasu. W trzech następnych podpunktach opisano odpowiednio daty, interwały i znaczniki czasu.

Typ danych DATE Typ danych DATE w bazie danych Oracle pozwala zapisać znacznik czasu z datą i godziną wykonania danej operacji. Poprawna jest dowolna data od 1 stycznia 4712 roku p.n.e. do 31 grudnia roku 9999 n.e. Najczęściej stosowany sposób zapisywania znaczników czasu polega na przypisaniu do zmiennej wyniku wywołania funkcji wbudowanej SYSDATE lub SYSTIMESTAMP. Obie zwracają kompletne daty, z wszystkimi polami zmiennej lub kolumny typu DATE. Listę pól typu danych DATE przedstawia tabela 4.2. Tabela 4.2. Lista pól typu danych DATE Nazwa pola

Przedział wartości

Prawidłowe wartości wewnętrzne

YEAR

Od –4712 do 9999 (bez roku 0).

Dowolna liczba całkowita różna od 0.

MONTH

Od 01 do 12.

Od 0 do 11.

DAY

od 01 do 31 (ograniczone według reguł kalendarza).

Dowolna liczba całkowita różna od 0.

HOUR

Od 00 do 23.

Od 0 do 23.

MINUTE

Od 00 do 59.

Od 0 do 59.

SECOND

Od 00 do 59.

Od 0 do 59,9 (po przecinku reprezentowane są części dziesiętne sekundy).

TIMEZONE_HOUR

Od –12 do 14 (przedział dostosowywany do czasu letniego).

Nie dotyczy.

TIMEZONE_MINUTE

Od 00 do 59.

Nie dotyczy.

TIMEZONE_REGION

Wartość z V$TIMEZONE_NAMES.

Nie dotyczy.

TIMEZONE_ABBR

Wartość z V$TIMEZONE_NAMES.

Nie dotyczy.

Poniższy prototyp przedstawia deklarację typu danych DATE: DATE [NOT NULL]

Można użyć klauzuli NOT NULL do zagwarantowania, że zmiennej typu DATE zostanie przypisana wartość. Jest wiele sytuacji, w których należy ograniczyć zmienne tego typu. Jeśli programista ich nie ograniczy, porównania logiczne z ich udziałem powinien przeprowadzać przy użyciu funkcji wbudowanej NVL. Poniższe instrukcje ilustrują definiowanie zmiennych typu DATE o domyślnej wartości null i o określonych wartościach: var1 var2 var3 var4

DATE; DATE := SYSDATE; DATE := SYSDATE + 1; DATE := '29-FEB-08';

-----

Niejawne przypisanie wartości null. Jawne przypisanie bieżącego znacznika czasu serwera. Jawne przypisanie znacznika czasu serwera z następnego dnia. Jawne przypisanie dnia przestępnego z roku 2008.

Funkcja TO_DATE umożliwia konwersję tekstu w formacie niezgodnym z datą na poprawne wartości typu DATE. Z kolei funkcja CAST pozwala użyć tylko domyślnej maski formatu. Domyślne maski formatu dat to DD-MIE-RR i DD-MIE-RRRR. Do pobierania daty ze znaczników czasu należy używać funkcji TRUNC(zmienna_typu_date). Może się ona przydać na przykład przy wyszukiwaniu wszystkich transakcji z określonego dnia. Domyślnie

Rozdział 4. Podstawowe elementy języka

141

funkcja wbudowana TRUNC() usuwa dokładny czas, podając datę z godziną 00, minutą 00 i sekundą 00. Poniższy program ilustruje działanie tej funkcji: DECLARE d DATE := SYSDATE; BEGIN dbms_output.put_line(TO_CHAR(TRUNC(d), 'DD-MON-YY HH24:MI:SS')); END; /

Uruchomienie tego skryptu spowoduje wyświetlenie następujących danych: 12-JUL-13 00:00:00

Nie można uzyskać tego samego efektu za pomocą wywołania ROUND(zmienna_typu_date,'klucz'). Funkcja ROUND przyjmuje jako drugi parametr słowo DAY, MONTH lub YEAR, a nie liczbę całkowitą. Ta funkcja w standardowy sposób zaokrągla wartość w dół lub w górę. Byłoby świetnie, gdyby wszystkie godziny przed południem były zaokrąglane do północy bieżącego dnia, a wszystkie godziny popołudniowe — do północy następnego dnia, jest jednak inaczej. Przy zaokrąglaniu do dnia funkcja może zaokrąglić datę do poranka dnia dzisiejszego lub poprzedniego, a przy zaokrąglaniu w górę — do poranka dnia jutrzejszego lub nawet do pojutrza. Funkcja ROUND lepiej radzi sobie z zaokrąglaniem na poziomie miesięcy. Daty z pierwszej połowy miesiąca są zaokrąglane do pierwszego dnia bieżącego miesiąca, a daty z drugiej połowy funkcja zaokrągla do pierwszego dnia następnego miesiąca. Proces przebiega podobnie na poziomie roku. Daty z pierwszej połowy roku są zaokrąglane do pierwszego dnia bieżącego roku, a daty z drugiej połowy — do pierwszego dnia następnego roku. Poniższe zapytanie ilustruje te niespójności: SQL> 2 3 4

SELECT TO_CHAR(ROUND(SYSDATE,'DAY'),'DD-MON-YYYY HH24:MI') AS Dzień , TO_CHAR(ROUND(SYSDATE,'MONTH'),'DD-MON-YYYY HH24:MI') AS Miesiąc , TO_CHAR(ROUND(SYSDATE,'YEAR'),'DD-MON-YYYY HH24:MI') AS Rok FROM dual;

To zapytanie wyświetla poniższe informacje na podstawie wartości SYSDATE odpowiadającej wieczorowi dnia 15 lipca 2013 roku: DZIEŃ MIESIĄC ROK ------------------ ------------------ ----------------14-JUL-2013 00:00 01-JUL-2013 00:00 01-JAN-2014 00:00

Zalecam, aby nie stosować funkcji ROUND do usuwania elementów dat. Lepiej korzystać z funkcji TRUNC, która działa w prosty i spójny sposób. Funkcja wbudowana EXTRACT() pozwala pobrać ze zmiennej typu DATE liczbę reprezentującą miesiąc, rok lub dzień. W dodatku C znajduje się opis dodatkowych funkcji służących do manipulowania wartościami typu DATE. Aby zadeklarować typ pochodny od typu DATE, należy użyć poniższego prototypu: SUBTYPE nazwa_typu_pochodnego IS typ_bazowy [NOT NULL];

Warto zauważyć, że — podobnie jak w znakowych typach pochodnych — nie można tu określić przedziału dat. Możliwe jest natomiast utworzenie od typu DATE typu pochodnego, który wymaga podania wartości. Używanie typu DATEN, jeśli typ DATE nie może przyjmować wartości null, odpowiada praktyce stosowania typów pochodnych NATURALN i POSITVEN.

Interwałowe typy pochodne Do zarządzania interwałami służą dwa typy pochodne od typu DATE: INTERVAL DAY TO SECOND i INTERVAL DAY TO MONTH. Ich prototypy wyglądają następująco: INTERVAL DAY[(precyzja_podstawowa)] TO SECOND[(precyzja_ułamkowa_sekund)] INTERVAL YEAR[(precyzja)] TO MONTH

Wartość domyślna precyzji podstawowej dla dni to 2, a precyzji ułamkowej dla sekund — 6. Domyślna precyzja dla lat wynosi 2. Definiowanie zmiennych typu INTERVAL DATE TO SECOND o wartościach null i zainicjowanych wygląda następująco:

142

Część I

Podstawy języka PL/SQL

var1 INTERVAL DAY TO SECOND; -- Niejawnie ustawia precyzję domyślną. var2 INTERVAL DAY(3) TO SECOND; -- Jawnie ustawia precyzję dla dni. var3 INTERVAL DAY(3) TO SECOND(9); -- Jawnie ustawia precyzję dla dni i sekund.

Poniższy prototyp przedstawia przypisywanie wartości do zmiennych typu INTERVAL DAY TO SECOND. D oznacza dzień, a HH:MI:SS odpowiednio godzinę, minutę i sekundę: nazwa_zmiennej := 'D HH:MI:SS';

Przypisanie rzeczywistej wartości do zmiennej tego typu wygląda następująco: var1 := '5 08:21:20'; -- Niejawna konwersja z łańcucha znaków.

Aby zadeklarować zmienne typu INTERVAL YEAR TO MONTH o wartości null lub zainicjowane, należy użyć instrukcji podobnych do poniższych: var1 INTERVAL YEAR TO MONTH; -- Niejawnie ustawia precyzję domyślną. var2 INTERVAL YEAR(3) TO MONTH; -- Jawnie ustawia precyzję dla roku.

Poniższy program pokazuje cztery dostępne sposoby przypisywania wartości do zmiennej var2: DECLARE var2 INTERVAL YEAR(3) TO MONTH; BEGIN -- Skrótowa forma zapisu interwału obejmującego 101 lat i 3 miesiące. var2 := '101-3'; var2 := INTERVAL '101-3' YEAR TO MONTH; var2 := INTERVAL '101' YEAR; var2 := INTERVAL '3' MONTH; END; /

Jego wykonanie spowoduje wyświetlenie następujących wartości: +101-03 +101-03 +101-00 +000-03

Tabela 4.3 przedstawia reguły wykonywania operacji arytmetycznych na zmiennych typu DATE i typach interwałowych. Standardowe operacje obejmują obliczanie interwału, na przykład przez odjęcie jednego znacznika czasu od drugiego w celu uzyskania liczby dni pomiędzy dwiema datami. Tabela 4.3. Arytmetyka na znacznikach czasu i interwałach Typ 1. operandu

Operator

Typ 2. operandu

Typ wyniku

Znacznik czasu

+

Interwał

Znacznik czasu

Znacznik czasu

-

Interwał

Znacznik czasu

Interwał

+

Znacznik czasu

Znacznik czasu

Znacznik czasu

-

Interwał

Interwał

Interwał

+

Interwał

Interwał

Interwał

-

Interwał

Interwał

Interwał

*

Liczba

Interwał

Liczba

*

Interwał

Interwał

Interwał

/

Liczba

Interwał

Interwały upraszczają zaawansowane porównania, jednak ich opanowanie wymaga pracy. Więcej informacji na temat interwałowych typów danych w językach SQL i PL/SQL zawierają podręczniki Oracle Database SQL Language Reference i Oracle Database Advanced Application Developer’s Guide.

Typy pochodne TIMESTAMP Typ pochodny TIMESTAMP umożliwia zapisywanie czasu w bardziej precyzyjny sposób niż w typie bazowym DATE. Jeśli programista użyje do zapełnienia zmiennej typu TIMESTAMP wbudowanej funkcji

Rozdział 4. Podstawowe elementy języka

143

SYSDATE, precyzja będzie taka sama. W niektórych systemach operacyjnych uzyskanie wyższej precyzji umożliwia funkcja SYSTIMESTAMP. Poniższy prototyp przedstawia deklarację typu TIMESTAMP: TIMESTAMP[(precyzja)] [NOT NULL]

Aby się upewnić, że zmienna typu TIMESTAMP będzie miała przypisaną wartość, należy użyć klauzuli NOT NULL. Jest wiele sytuacji, w których należy ograniczyć zmienne tego typu. Jeśli programista ich nie ograniczy, powinien przeprowadzać porównania logiczne z ich udziałem przy użyciu funkcji wbudowanej NVL. Można definiować zmienne typu TIMESTAMP o domyślnej wartości null i zainicjowane: var1 var2 var3 var4

TIMESTAMP; TIMESTAMP := SYSTIMESTAMP; TIMESTAMP(3); TIMESTAMP(3) := SYSTIMESTAMP;

-----

Niejawne przypisanie wartości null. Jawne przypisanie wartości. Jawne określenie precyzji przy wartości null. Jawne określenie precyzji i wartości.

Poniższy program przedstawia różnice między typami DATE i TIMESTAMP: DECLARE d DATE := SYSTIMESTAMP; t TIMESTAMP(3) := SYSTIMESTAMP; BEGIN dbms_output.put_line('DATE ['||d||']'); dbms_output.put_line('TO_CHAR ['||TO_CHAR(d,'DD-MON-YY HH24:MI:SS')||']'); dbms_output.put_line('TIMESTAMP ['||t||']'); END; /

Ten blok anonimowy wyświetli następujące informacje: DATE [31-JUL-07] TO_CHAR [31-JUL-07 21:27:36] TIMESTAMP [31-JUL-07 09.27.36.004 PM]

Dwa pozostałe typy pochodne TIMESTAMP działają w podobny sposób. Ich prototypy wyglądają następująco: TIMESTAMP[(precyzja)] WITH TIME ZONE TIMESTAMP[(precyzja)] WITH LOCAL TIME ZONE

Można zadeklarować zmienną typu TIMESTAMP WITH TIME ZONE o domyślnej wartości null lub zainicjowaną: var1 var2 var3 var4

TIMESTAMP WITH LOCAL TIME ZONE; TIMESTAMP WITH LOCAL TIME ZONE := SYSTIMESTAMP; TIMESTAMP(3) WITH LOCAL TIME ZONE; TIMESTAMP(3) WITH LOCAL TIME ZONE := SYSTIMESTAMP;

Różnica między poszczególnymi typami polega na dołączaniu danych o strefie czasowej do znaczników czasu. Typ TIMESTAMP WITH TIME ZONE zwraca standardowy czas i informacje o tym, czy w danej strefie obowiązuje czas letni. Typ TIMESTAMP WITH LOCAL TIME ZONE zwraca różnicę między czasem lokalnym a czasem GMT (ang. Greenwich Mean Time).

Znaki i łańcuchy znaków Unicode Znaki i łańcuchy znaków Unicode są używane z uwagi na globalizację, do której potrzebne jest kodowanie obsługujące wiele zestawów znaków. Baza danych Oracle udostępnia w tym celu kodowanie AL16UTF16 i UTF8. W tym pierwszym wszystkie znaki zajmują po 2 bajty, a w drugim — po 3. Typ danych NCHAR zestawu znaków Unicode to odpowiednik typu CHAR, a NVARCHAR2 tego zestawu odpowiada typowi VARCHAR2. Przy tworzeniu aplikacji obsługujących wiele zestawów znaków w jednej bazie danych należy korzystać z typów zestawu Unicode.

Typ danych NCHAR Typ NCHAR to typ bazowy łańcuchów znaków Unicode o stałej długości. Aby określić liczbę znaków, jakie można w nim zapisać, należy podzielić maksymalną pojemność tego typu (32 767 bajtów) przez

144

Część I

Podstawy języka PL/SQL

2 lub 3 (w zależności od używanego zestawu znaków). Typ NCHAR działa podobnie jak opisany we wcześniejszej części rozdziału typ CHAR. Typ NCHAR jest najbardziej przydatne do obsługi globalizacji. Należy go używać, jeśli dana baza obsługuje zestaw znaków Unicode lub planowane jest dodanie tej funkcji.

Typ danych NVARCHAR2 Typ NVARCHAR2 to bazowy typ danych dla łańcuchów znaków Unicode o zmiennej długości. Aby określić liczbę znaków, jakie można w nim zapisać, należy podzielić maksymalną pojemność tego typu (32 767 bajtów) przez 2 lub 3 (w zależności od używanego zestawu znaków). Typ NVARCHAR2 działa podobnie jak opisany we wcześniejszej części rozdziału typ VARCHAR2. Typ NVARCHAR2 jest najbardziej przydatny do obsługi globalizacji. Należy go używać, jeśli dana baza obsługuje zestaw znaków Unicode lub planowane jest dodanie tej funkcji.

Liczby Są cztery podstawowe liczbowe typy danych: BINARY_INTEGER, typy zgodne z formatem IEEE 754 (BINARY_DOUBLE i BINARY_FLOAT), NUMBER i PLS_INTEGER. Typy danych BINARY_INTEGER i PLS_INTEGER są identyczne i oba używają macierzystych bibliotek matematycznych systemu operacyjnego. W dokumentacji Oracle typ PLS_INTEGER jest używany do opisu obu tych typów. Takie samo podejście zastosowano w tej książce. Typy zgodne z formatem IEEE 754 to liczby o pojedynczej i podwójnej precyzji służące do obliczeń naukowych. Do przetwarzania zmiennych typu NUMBER używana jest niestandardowa biblioteka dostępna w bazach od wersji Oracle Database 11g. Zmienne tego typu mogą przechowywać bardzo duże liczby stało- i zmiennoprzecinkowe.

Typ danych BINARY_INTEGER Typ BINARY_INTEGER jest identyczny z typem PLS_INTEGER, przechowuje liczby całkowite z przedziału od –2147483648 do 2147483647 i zajmuje 32 bity (4 bajty). Typ ten, podobnie jak typ PLS_INTEGER, pozwala zwiększyć wydajność obliczeń na liczbach z podanego przedziału i wymaga dużo mniej miejsca w pamięci niż typ NUMBER. Jeśli operacje matematyczne wykonywane na dwóch zmiennych typu BINARY_INTEGER doprowadzą do otrzymania wartości spoza podanego przedziału, zostanie zgłoszony błąd przepełnienia (ORA-01426). Deklarację typu BINARY_INTEGER przedstawia poniższy prototyp: BINARY_INTEGER

Można zdefiniować zmienne typu BINARY_INTEGER o wartości null i zainicjowane w trakcie deklaracji. Składnia tych operacji wygląda następująco: var1 BINARY_INTEGER; var2 BINARY_INTEGER := 21;

Do obliczeń z użyciem typu BINARY_INTEGER służą macierzyste biblioteki matematyczne, dlatego instrukcja deklaracji nie musi przydzielać pamięci zmiennej do momentu przypisania do niej wartości. Poniższy prototyp ilustruje definiowanie typów pochodnych od typu BINARY_INTEGER: SUBTYPE nazwa_typu_pochodnego IS typ_bazowy [RANGE minimum..maksimum] [NOT NULL];

Istnieje kilka wbudowanych typów pochodnych od typu BINARY_INTEGER. W zmiennych typów pochodnych NATURAL i POSITIVE można zapisywać tylko dodatnie liczby całkowite. Typy NATURALN i POSITIVEN ograniczają możliwość przypisania wartości null. Próba zadeklarowania zmiennych tych typów bez zainicjowania ich doprowadzi do błędu PLS-00218. Oba wspomniane typy nakładają ograniczenie NOT NULL na bazowy typ danych. Najnowszy typ pochodny to SIMPLE_INTEGER, wprowadzony w Oracle 11g. Obcina on nadmiar i blokuje zgłaszanie wszystkich błędów związanych z przepełnieniem. Wydajność zmiennych tego typu zależy od wartości parametru PLSQL_CODE_TYPE bazy danych. Użycie tego typu poprawia szybkość działania programu, jeśli wspomniany parametr ma wartość NATIVE, ponieważ operacje arytmetyczne są wtedy wykonywane przy użyciu bibliotek systemu operacyjnego, dlatego sprawdzanie przepełnienia i wartości null jest wyłączone. Z drugiej strony przy ustawieniu parametru PLSQL_CODE_TYPE na

Rozdział 4. Podstawowe elementy języka

145

wartość INTERPRETED wydajność będzie niższa, ponieważ opcja ta uniemożliwia przeciążanie i powoduje sprawdzanie wartości null. Przeciążanie działania typów bazowych i pochodnych w pakietach PL/SQL jest zwykle niedozwolone, jednak można przeciążyć parametr formalny o tej samej nazwie lub pozycji, używając typu PLS_INTEGER lub BINARY_INTEGER w jednej sygnaturze i typu SIMPLE_INTEGER w drugiej.

Warto też wiedzieć, że przy rzutowaniu wartości typu PLS_INTEGER lub BINARY_INTEGER na zmienną typu SIMPLE_INTEGER konwersja nie zachodzi, jeśli jednak rzutowana wartość to null, program zgłosi wyjątek czasu wykonania.

Typy danych zgodne z formatem IEEE 754 Liczby o pojedynczej i podwójnej precyzji zgodne z formatem IEEE 754 służą do wykonywania obliczeń naukowych. Ich definicja i implementacja wymagają rozwiązania tradycyjnych problemów z przepełnieniem i nieskończonością. Te typy należy stosować przy rozwiązywaniu problemów naukowych, na przykład do obliczania pierwiastków trzeciego stopnia i wykonywania podobnych zadań. W środowiskach SQL i PL/SQL zdefiniowane są stałe BINARY_FLOAT_NAN i BINARY_FLOAT_INFINITY. Środowisko PL/SQL udostępnia też cztery inne stałe, opisane w tabeli 4.4. Tabela 4.4. Stałe związane z typami IEEE 754 Nazwa stałej

Środowisko

Wartość

BINARY_FLOAT_NAN

SQL i PL/SQL

Zawiera NaN, ale w porównaniach traktowana jest jak łańcuch znaków, których wielkość jest nieistotna. NaN w notacji naukowej oznacza „nie liczba” (ang. Not a Number).

BINARY_FLOAT_INFINITY

SQL i PL/SQL

Zawiera Inf, ale w porównaniach traktowana jest jak łańcuch znaków, których wielkość jest nieistotna.

BINARY_FLOAT_MIN_NORMAL

PL/SQL

Zawiera liczbę 1.17549435E-038.

BINARY_FLOAT_MAX_NORMAL

PL/SQL

Zawiera liczbę 3.40282347E+038.

BINARY_FLOAT_MIN_SUBNORMAL

PL/SQL

Zawiera liczbę 1.40129846E-045.

BINARY_FLOAT_MAX_SUBNORMAL

PL/SQL

Zawiera liczbę 1.17549421E-038.

W dokumentacji bazy danych Oracle 12c brakuje tych stałych na liście słów zarezerwowanych i kluczowych. Można je jednak wyświetlić w programie w języku PL/SQL lub kierując zapytanie do tabeli v$reserved_words.

Prototypy deklaracji typów danych zgodnych z IEEE 754 wyglądają następująco: BINARY_DOUBLE BINARY_FLOAT

Można zdefiniować zmienne tego typu o wartościach null lub zainicjować je w trakcie deklaracji. Poniżej przedstawiono składnię tych operacji: var1 var2 var3 var4

BINARY_DOUBLE; BINARY_DOUBLE := 21d; BINARY_FLOAT; BINARY_FLOAT := 21f;

Do literałów liczbowych przypisywanych do zmiennych typu BINARY_DOUBLE trzeba zawsze dodawać literę d, a w przypadku zmiennych typu BINARY_FLOAT — literę f. Baza danych Oracle 12c udostępnia przeciążone podprogramy, które pozwalają przyspieszyć wykonywanie operacji na typach zgodnych z IEEE 754. Aby zdefiniować typ pochodny od typów BINARY_DOUBLE lub BINARY_FLOAT, należy posłużyć się następującym prototypem: SUBTYPE nazwa_typu_pochodnego IS typ_bazowy [NOT NULL];

146

Część I

Podstawy języka PL/SQL

Warto zauważyć, że w odróżnieniu od innych liczbowych typów danych nie można tu ograniczyć przedziału. Jedyne ograniczenie, jakie można narzucić, to zakazanie przypisywania wartości null.

Typ danych NUMBER Typ danych NUMBER jest przetwarzany przy użyciu niestandardowej biblioteki wchodzącej w skład bazy Oracle Database 12c. Zmienne tego typu mogą przechowywać liczby z przedziału od 1.0E – 130 (1 razy 10 do ujemnej potęgi 130.) do 1.0E126 (1 razy 10 do potęgi 126.). Twórcy Oracle zalecają stosowanie typu NUMBER tylko wtedy, gdy operandy lub wyniki obliczeń znajdują się w tym przedziale. Przy używaniu tego typu nie są zgłaszane błędy NaN (nie liczba) i nieskończoności, jeśli literał lub obliczona wartość wykraczają poza podany przedział. Wystąpienie takich sytuacji prowadzi do następujących skutków:  Przypisanie literału o wartości mniejszej od minimalnej powoduje zapisanie w zmiennej typu NUMBER wartości 0.  Przypisanie literału o wartości większej od maksymalnej powoduje zgłoszenie błędu kompilacji.  Wynik obliczeń przekraczający wartość maksymalną powoduje zgłoszenie błędu kompilacji. Typ danych NUMBER obsługuje liczby stało- i zmiennoprzecinkowe. Te pierwsze można zdefiniować, podając łączną liczbę cyfr (zwaną precyzją) i liczbę cyfr po przecinku (zwaną skalą). Sam przecinek nie jest fizycznie zapisywany w zmiennej, ponieważ biblioteka wyznacza go na podstawie precyzji i skali. Do deklaracji stałoprzecinkowego typu NUMBER służy następujący prototyp: NUMBER[(precyzja, [skala])] [NOT NULL]

W deklaracjach zmiennych typu NUMBER zarówno precyzja, jak i skala to wartości opcjonalne. Domyślny rozmiar zmiennych typu NUMBER (czyli liczba cyfr lub precyzja) to 38. Można zadeklarować zmienną tego typu, podając tylko precyzję, natomiast trzeba określić precyzję, aby móc zdefiniować skalę. Można zadeklarować stałoprzecinkową zmienną typu NUMBER o wartości null lub zdefiniować ją w deklaracji. Składnia deklaracji zmiennych typu NUMBER o wartości null wygląda następująco: var1 NUMBER; var2 NUMBER(15); var3 NUMBER(15,2);

-- Liczba o 38 cyfrach i wartości null. -- Liczba o 15 cyfrach i wartości null. -- Liczba o 15 cyfrach, w tym dwóch po przecinku, i wartości null.

Poniższe instrukcje przedstawiają deklaracje zmiennych typu NUMBER zainicjowanych określonymi wartościami: var1 NUMBER := 15; -- Liczba o 38 cyfrach. var2 NUMBER(15) := 15; -- Liczba o 15 cyfrach. var3 NUMBER(15,2) := 15.22; -- Liczba o 15 cyfrach, w tym dwóch po przecinku.

Liczby stałoprzecinkowe można też deklarować przy użyciu typów pochodnych DEC, DECIMAL i NUMERIC. Do deklarowania liczb całkowitych służą typy pochodne INTEGER, INT i SMALLINT. Maksymalna precyzja wszystkich tych typów to 38 cyfr. Prototypy przedstawione poniżej służą do deklarowania zmiennoprzecinkowych podtypów typu NUMBER: DOUBLE PRECISION i FLOAT: DOUBLE PRECISION[(precyzja)] FLOAT[(precyzja)]

Definiowanie precyzji zmiennych typu DOUBLE PRECISION i FLOAT jest opcjonalne. Ograniczenie precyzji grozi utratą naturalnej precyzji typów zmiennoprzecinkowych. Oba wspomniane typy mają domyślny rozmiar (czyli liczbę cyfr lub precyzję) 126. Można zdefiniować precyzję zmiennych typu FLOAT, ale już nie skalę. Próba zdefiniowania skali któregoś z tych podtypów prowadzi do błędu PLS-00510, ponieważ nie mogą one mieć stałej liczby cyfr po przecinku. Składnia deklaracji zmiennych typu DOUBLE PRECISION i FLOAT o wartości NULL wygląda następująco: var1 var2 var3 var4

DOUBLE PRECISION; FLOAT; DOUBLE PRECISION(15); FLOAT(15);

-----

Liczba o 126 cyfrach i wartości null. Liczba o 126 cyfrach i wartości null. Liczba o 15 cyfrach i wartości null. Liczba o 15 cyfrach i wartości null.

Rozdział 4. Podstawowe elementy języka

147

Aby zadeklarować zmienne typu DOUBLE PRECISION lub FLOAT i zainicjować je określonymi wartościami, należy użyć poniższej składni: var1 var2 var3 var4

DOUBLE PRECISION := 15; -FLOAT := 15; -DOUBLE PRECISION(15) := 15; -FLOAT(15) := 15; --

Liczba o 126 cyfrach. Liczba o 126 cyfrach. Liczba o 15 cyfrach. Liczba o 15 cyfrach.

Inny typ pochodny od typu NUMBER to REAL. Służy on do przechowywania liczb zmiennoprzecinkowych, ale jego precyzja to tylko 63 cyfry, w tym 18 cyfr po przecinku.

Typ danych PLS_INTEGER

Typy danych PLS_INTEGER i BINARY_INTEGER są identyczne, a obliczenia z ich udziałem odbywają się przy użyciu arytmetyki specyficznej dla systemu operacyjnego. Zmienne tego typu mogą przechowywać liczby z przedziału od –2147483648 do 2147483647 i zajmują 32 bity (4 bajty). Typ ten wymaga dużo mniej miejsca w pamięci niż typ NUMBER. Obliczenia są też bardziej wydajne, jeśli operandy i wynik operacji matematycznych znajdują się w podanym przedziale. Warto pamiętać, że operacje dające wynik spoza przedziału powodują błąd przepełnienia ORA-01426. Błąd ten zostanie zgłoszony nawet wtedy, gdy wynik operacji zostanie przypisany do zmiennej typu NUMBER. Poniższy prototyp ilustruje deklarację typu PLS_INTEGER: PLS_INTEGER

Można zadeklarować zmienną PLS_INTEGER o wartości null lub zainicjować ją w czasie deklaracji. Składnia tych operacji wygląda następująco: var1 PLS_INTEGER; -- Wartość null nie wymaga miejsca w pamięci. var2 PLS_INTEGER := 11; -- Liczba całkowita wymaga pamięci dla każdej cyfry.

Obliczenia na zmiennych typu PLS_INTEGER odbywają się przy użyciu macierzystych bibliotek matematycznych, dlatego instrukcja deklaracji nie przydziela zmiennej pamięci, dopóki program nie przypisze do niej wartości. Można się o tym przekonać, używając funkcji wbudowanej LENGTH. Do deklarowania typów pochodnych od typu PLS_INTEGER służy poniższy prototyp: SUBTYPE nazwa_typu_pochodnego IS typ_bazowy [RANGE minimum..maksimum] [NOT NULL];

Nie należy mylić ze sobą typów PLS_INTEGER i INTEGER. Ten pierwszy korzysta z bibliotek matematycznych systemu operacyjnego, podczas gdy drugi to typ pochodny od typu bazowego NUMBER.

Funkcja wbudowana LENGTH Funkcja ta działa podobnie jak jej odpowiednik w językach C i C++. Po przypisaniu wartości do zmiennej funkcja wbudowana LENGTH zwróci liczbę znaków, a nie liczbę bajtów potrzebnych do ich zapisania (dostępne są też funkcje wbudowane LENGTHB, LENGTHC, LENGTH2 i LENGTH4). Oznacza to, że liczba PLS_INTEGER o 5 lub 6 cyfrach będzie miała długość 5 lub 6, choć w obu przypadkach jej zapisanie wymaga tylko 4 bajtów. Efekt ten związany jest z działaniem typu NUMBER, ponieważ kolumny tego typu są zapisywane jako jednowymiarowe tablice znaków języka C. Funkcja LENGTH zwraca liczbę pozycji zajmowanych przez zmienne liczbowych typów danych.

Duże obiekty (typy LOB) Duże obiekty (typy LOB) obejmują cztery typy danych: BFILE, BLOB, CLOB i NCLOB. Typ BFILE wskazuje na plik zewnętrzny, a jego rozmiar maksymalny to 4 gigabajty. Typy BLOB, CLOB i NCLOB są obsługiwane wewnętrznie, a ich maksymalna wielkość wynosi od 8 do 128 terabajtów (zależy to od wartości parametru db_block_size). Kolumny LOB zawierają lokalizator, który wskazuje na miejsce przechowywania danych. Dostęp do wartości typów LOB można uzyskać w zasięgu transakcji. Lokalizator pełni funkcję wskaźnika umożliwiającego odczyt danych i ich zapis do kolumn typów LOB. Rozdział 10. szczegółowo opisuje metody dostępu do kolumn typów LOB, korzystanie z tych typów, a także używanie pakietu wbudowanego DBMS_LOB.

148

Część I

Podstawy języka PL/SQL

Typ BFILE Typ BFILE służy tylko do odczytu. Wyjątki od tej reguły to określanie katalogu wirtualnego i nazwy pliku zewnętrznego. Wbudowana funkcja BFILENAME pozwala ustawić lokalizator używany w kolumnach typu BFILE. Przed wywołaniem tej funkcji trzeba wykonać kilka operacji wstępnych. Należy przygotować katalog fizyczny na serwerze, zapisać w nim plik, utworzyć katalog wirtualny powiązany z fizycznym, a następnie przyznać uprawnienia do odczytu plików z katalogu schematowi, który zawiera tabelę lub program składowany korzystający z określonej kolumny BFILE. Do pobierania deskryptorów (nazw kolumn), aliasów (katalogów wirtualnych powiązanych z katalogami fizycznymi) i nazw plików służy procedura FILEGETNAME z pakietu DBMS_LOB. Parametr session_ max_open_files określa maksymalną liczbę jednocześnie otwartych kolumn typu BFILE. W rozdziale 10. opisano, jak połączyć wszystkie te operacje. Znajdują się tam także programy składowane, które pozwalają uprościć proces używania typu BFILE. Poniższy prototyp przedstawia deklarację typu BFILE: BFILE

Zmienną typu BFILE można zdefiniować w tylko jeden sposób, a domyślnie zawsze zawiera ona pustą referencję: var1 BFILE;

-- Deklaracja pustej referencji typu BFILE.

Zmiennej typu BFILE nie można zdefiniować za pomocą referencji, chyba że programista napisze nakładkę na procedurę DBMS_LOB.FILEGETNAME. W rozdziale 10. przedstawiona jest taka nakładka oraz opis ograniczeń, które sprawiają, że jest ona potrzebna.

Typ BLOB Kolumny typu BLOB służą do odczytu i zapisu dużych typów binarnych. Zmiennych typu BLOB można używać w transakcjach. Takie zmienne można też przywracać. Odczyt i zapis zmiennych oraz kolumn typu BLOB jest możliwy tylko w zasięgu transakcji. Zmienne tego typu są obiektami, dlatego funkcjonują inaczej niż zmienne skalarne. Mają trzy stany: null, pusty i zapełniony (niepusty). Zmienne tego typu trzeba zainicjować przy użyciu funkcji empty_blob, która powoduje zmianę referencji null na stan pusty, lub bezpośrednio przypisać do nich wartość w notacji heksadecymalnej, aby je zapełnić. Zmienne i kolumny typu BLOB mogą przechowywać pliki binarne o maksymalnym rozmiarze od 8 do 32 terabajtów. Dostęp do kolumn tego typu można uzyskać tylko za pomocą pakietu DBMS_LOB, który służy do odczytu i zapisu wartości po ich początkowym przypisaniu. W języku PL/SQL można deklarować lokalne zmienne typu BLOB w blokach anonimowych i nazwanych. Jednak trzeba nawiązać aktywne połączenie między programem a składowaną kolumną typu BLOB, aby móc wstawić, dołączyć lub wczytać jej wartość. Należy wczytywać i zapisywać tylko fragmenty dużych wartości typu BLOB, w przeciwnym razie łatwo jest wyczerpać zasoby systemu. Poniższy prototyp przedstawia deklarację typu BLOB: BLOB

Istnieje jedna metoda deklarowania zmiennych typu BLOB zawierających domyślną referencję null: var1 BLOB;

-- Deklaracja referencji null w zmiennej typu BLOB.

Do definiowania pustych i zapełnionych zmiennych typu BLOB służą poniższe instrukcje: var1 BLOB := empty_blob(); -- Deklaracja pustej zmiennej typu BLOB. var2 BLOB := '43'||'41'||'52'; -- Deklaracja zmiennej typu BLOB o heksadecymalnej wartości CAR.

Zmienne typu BLOB są przydatne przede wszystkim do zapisywania dużych plików z obrazami i filmami oraz innych plików binarnych. Przydatność takich zmiennych w dużym stopniu zależy od dobrze napisanego interfejsu. W rozdziale 10. omówiono obsługę interakcji między kolumnami typu BLOB a zmiennymi w kodzie PL/SQL.

Typ CLOB Kolumny typu CLOB służą do odczytu i zapisu dużych typów znakowych. Zmiennych typu CLOB można używać w transakcjach oraz przywracać je. Zmienne typu CLOB mają trzy stany: null, pusty i zapełniony

Rozdział 4. Podstawowe elementy języka

149

(niepusty). Zmienne tego typu trzeba zainicjować przy użyciu funkcji empty_clob() (a nie empty_blob()), która powoduje zmianę referencji null na stan pusty. Podobnie jak w przypadku typu BLOB istnieje jedna metoda deklarowania zmiennych typu CLOB zawierających domyślną referencję null: var1 CLOB;

-- Deklaracja referencji null w zmiennej typu CLOB.

Do definiowania pustych i zapełnionych zmiennych typu CLOB służą poniższe instrukcje (podobne jak w typie BLOB): var1 CLOB := empty_clob(); -- Deklaracja pustej zmiennej typu CLOB. var2 CLOB := 'CAR'; -- Deklaracja zmiennej typu CLOB o wartości CAR.

Zmienne typu CLOB są przydatne przede wszystkim do zapisywania dużych plików tekstowych. Są to na przykład komentarze klientów pomocne przy obsłudze transakcji, zwrotach i innych operacjach. Duże fragmenty tekstu nadają się do odczytu i zapisu w małych porcjach. Przesyłanie dużych części takich plików może doprowadzić do wyczerpania zasobów systemu. W rozdziale 10. omówiono obsługę interakcji między kolumnami typu CLOB a zmiennymi w kodzie PL/SQL.

Typ NCLOB Typ NCLOB służy do odczytu i zapisu dużych typów znakowych zgodnych z Unicode. Ten typ działa podobnie jak typ CLOB. Występuje między nimi jedna różnica — w typie NCLOB przydzielana jest większa ilość pamięci, ponieważ używane są tu znaki Unicode. Wszystkie pozostałe reguły są takie same (w tym inicjowanie za pomocą funkcji empty_clob()).

Złożone typy danych Są trzy główne złożone typy danych: rekordy, obiekty i kolekcje. Kolekcje zawierają elementy typu skalarnego, rekordowego lub obiektowego. Można je tworzyć jako typy języków SQL i PL/SQL. Kolekcje z języka PL/SQL, nazywane kiedyś tablicami języka PL/SQL, od wersji Oracle Database 10g są nazywane tablicami asocjacyjnymi. Oto dostępne typy złożone:  Rekordy (nazywane też strukturami) zwykle zawierają zbiór powiązanych elementów, przypominających wiersz tabeli. Kod z deklaracją typu rekordowego jest podobny do kodu definiującego strukturę tabeli. Rekord ma strukturę zbliżoną do tabeli, ale ograniczoną do jednego wiersza.  Typ obiektowy, czasem nazywany także strukturą, zwykle obejmuje kolekcję powiązanych pól (podobnie jak wiersz tabeli). Oprócz kodu z deklaracją typu obiektowego zwykle tworzona jest też implementacja w ciele obiektu. Typ obiektowy bez ciała to odpowiednik typu rekordowego z SQL-a. To oznacza, że także definiuje strukturę podobną do pojedynczego wiersza tabeli.  Kolekcją może być tablica VARRAY lub tabela zagnieżdżona (w Oracle pojęcie tabela zagnieżdżona jest używane do odróżnienia typu danych używanego w programowaniu od fizycznych tabel) z elementami typów skalarnych lub złożonych. Jak napisano w rozdziale 3., kolekcja elementów typu skalarnego to kolekcja ADT (ang. Attribute Data Type), a kolekcja z elementami typu złożonego to kolekcja UDT (ang. User-Defined Type). W SQL-u w kolekcjach można stosować typy obiektowe, a w języku PL/SQL — typy rekordowe.  Systemowy kursor referencyjny może zwracać kolekcję opartą na jednej lub wielu kolumnach. Ten typ danych można stosować tylko w języku PL/SQL. Istnieją dwa rodzaje takich kursorów — ze słabą i ścisłą kontrolą typów. Kursor ze słabą kontrolą typów dziedziczy typ w czasie wykonywania programu, natomiast w kursorach ze ścisłą kontrolą typów są one określane na etapie kompilacji. Dobre omówienie typów złożonych zawiera rozdział 3., dlatego tu znajdziesz tylko przegląd metod przypisywania wartości do zmiennych tych typów. Prosty przykładowy kod dotyczący rekordów, obiektów i kolekcji przedstawiony jest w rozdziale 3. Po przeglądzie sposobów przypisywania wartości do rekordów, obiektów i kolekcji znajduje się omówienie systemowych kursorów referencyjnych z języka PL/SQL.

150

Część I

Podstawy języka PL/SQL

Rekordy Rekordy są niezwykle przydatne przy używaniu kursorów i innych mechanizmów dostępnych tylko w języku PL/SQL. Można zdefiniować funkcję składowaną, która zwraca wartości typu rekordowego, jednak zakres stosowania tej funkcji będzie ograniczony. W instrukcjach SQL-owych można używać funkcji składowanych tylko wtedy, gdy zwracają wartości typów z tego języka. Zamiast wartości typów rekordowych można zwracać wartości typów obiektowych języka SQL. Poniższy kod deklaruje rekord o wartościach domyślnych, inicjuje go nowymi danymi i wyświetla je: SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15

DECLARE -- Deklaracja struktury rekordowej z wartościami domyślnymi. TYPE muppet_record IS RECORD ( salutation VARCHAR(20) DEFAULT 'Pani' , name VARCHAR2(10) := 'Piggy'); -- Deklaracja zmiennej lokalnego typu rekordowego. muppet MUPPET_RECORD; BEGIN -- Przypisywanie wartości do pól rekordu. muppet.salutation := 'Pan'; muppet.name := 'Żaba Kermit'; -- Wyświetlanie rekordu jako scalonego łańcucha znaków. dbms_output.put_line(muppet.salutation||' '||muppet.name); END; /

Ten kod wyświetla: Pan Żaba Kermit

Wiersze od 3. do 5. to deklaracja lokalnego typu rekordowego. Wiersz 7. zawiera deklarację zmiennej tego typu i jednocześnie ją definiuje, ponieważ używany typ rekordowy ma wartości domyślne (Pani Piggy). Wiersze 10. i 11. pokazują, że przypisywanie wartości odbywa się pole po polu. Wyjątkiem od tej reguły jest przypisywanie zwracanego przez kursor wyniku, który pasuje do struktury rekordowej, lub przypisywanie wartości pasującej struktury. Wiersz 13. ilustruje, że do wyświetlenia zawartości rekordu potrzebne są: nazwa zmiennej, selektor komponentów i nazwa pola.

Obiekty Obiekty zwykle są przydatniejsze od rekordów. W porównaniu z typami obiektowymi typy rekordowe okazują się przestarzałe — przynajmniej moim zdaniem. Wygląda na to, że osoby z firmy Oracle myślą podobnie, ponieważ obecnie typy obiektowe są wiązane z wewnętrznymi i zewnętrznymi programami Javy. To prawda, stosowanie typów obiektowych wymaga więcej planowania i umiejętności, jednak dobry projekt ma wiele zalet. W końcu Oracle Database 12c to system zarządzania obiektowo-relacyjną bazą danych. Możesz zdefiniować funkcję składowaną zwracającą wartość typu obiektowego i korzystać z niej w językach SQL oraz PL/SQL. Poniżej pokazana jest deklaracja obiektu w SQL-u. Jest to obiekt z poziomu schematu: SQL> CREATE OR REPLACE 2 TYPE president_object IS OBJECT 3 ( salutation VARCHAR(20) 4 , name VARCHAR2(10)); 5 /

Ta instrukcja SQL-a tworzy typ obiektowy z poziomu schematu, który można wykorzystać w tabelach i własnych jednostkach programu. Prawdziwe obiekty mają też ciało, w którym znajduje się implementacja danego typu. Zauważ, że nie ma tu wartości domyślnych. Wynika to z tego, że w typach obiektowych nie można stosować takich wartości. Gdy je dodasz, wystąpi następujący błąd kompilacji: LINE/COL ERROR -------- ------------------------------------------------------0/0 PL/SQL: Compilation unit analysis terminated

Rozdział 4. Podstawowe elementy języka 2/5

151

PLS-00363: expression 'SALUTATION' cannot be used as an assignment target

Poniższy kod deklaruje zmienną typu obiektowego w SQL-u. Powstaje w ten sposób obiekt z poziomu schematu: SQL> 2 3 4 5 6 7 8

DECLARE -- Deklaracja zmiennej typu obiektowego. president PRESIDENT_OBJECT := president_object('Pan','Lincoln'); BEGIN -- Wyświetlanie danych — scalonego łańcucha znaków. dbms_output.put_line(president.salutation||' '||president.name); END; /

Nie ma tu lokalnej deklaracji typu obiektowego, ponieważ istnieje on na poziomie schematu. Wiersz 3. zawiera nowość — wywołanie wyspecjalizowanej funkcji, konstruktora. Konstruktory to wyspecjalizowane funkcje, które przyjmują listę rozdzielonych przecinkami wartości i zwracają egzemplarze typu obiektowego. Jeśli nie utworzysz egzemplarza, próba zastosowania zmiennej spowoduje w czasie wykonywania programu wyjątek niezainicjowanej zmiennej. Dlatego pamiętaj o wywoływaniu konstruktorów w celu tworzenia egzemplarzy. Przedstawiony kod wyświetla dane za pomocą techniki zastosowanej wcześniej dla typu rekordowego: Pan Lincoln

Na podstawie zaprezentowanego w rozdziale 3. wprowadzenia do funkcji można przenieść kod do funkcji, która zwraca egzemplarz typu president_object. To zadanie jest proste, przy czym należy pamiętać, aby nie przypisywać dynamicznych wartości w sekcji deklaracji. Oto potrzebny kod: SQL> 2 3 4 5 6 7 8 9 10 11 12

CREATE OR REPLACE FUNCTION get_president ( pv_salutation VARCHAR2 DEFAULT 'Pan' , pv_name VARCHAR2 ) RETURN president_object IS -- Deklaracja zmiennej typu obiektowego z poziomu schematu. president PRESIDENT_OBJECT := president_object(NULL,NULL); BEGIN -- Przypisywanie wartości do wstępnie przydzielonej pamięci. president := president_object(pv_salutation,pv_name); -- Zwracanie egzemplarza typu obiektowego. RETURN president; END; /

Wiersz 5. deklaruje zmienną lokalną za pomocą wartości null. Powoduje to przydzielenie pamięci na obiekt. Wiersz 8. przypisuje wartości do egzemplarza tego typu, a wiersz 10. zwraca ten egzemplarz. Aby pobrać egzemplarz, należy zastosować poniższe zapytanie: SQL> COLUMN president FORMAT A40 SQL> SELECT get_president(pv_name => 'Truman') AS president 2 FROM dual;

Wywołanie funkcji get_president powoduje zwrócenie „spłaszczonego” obiektu, czyli nazwy typu obiektowego i umieszczonej w nawiasach listy wartości użytych do utworzenia obiektu: PRESIDENT(SALUTATION, NAME) ----------------------------------PRESIDENT_OBJECT('Pan', 'Truman')

W punkcie „Od obiektów do tabel relacyjnych” w dodatku B opisano, jak przekształcić te dane na kolumny. Z tego krótkiego punktu dowiedziałeś się, że obiekty można stosować w języku SQL.

Kolekcje Kolekcje to tablice i listy. Tablice różnią się od list tym, że mają sekwencyjny indeks liczbowy, podczas gdy w listach używa się niesekwencyjnych indeksów liczbowych i indeksów w postaci niepowtarzalnych łańcuchów znaków. Tablice to ściśle zapełnione listy, ponieważ mają sekwencyjne indeksy

152

Część I

Podstawy języka PL/SQL

liczbowe. Choć listy czasem mają ścisłe indeksy tego typu, mogą też być zapełnione luźno. Przy luźnym zapełnieniu w sekwencji występują luki lub indeks w ogóle nie jest sekwencyjny. Baza danych Oracle udostępnia trzy typy kolekcji. Dwa z nich są dostępne zarówno w języku SQL, jak i w PL/SQL, choć zależy to od sposobu ich zdefiniowania. Są to tablice VARRAY i tabele zagnieżdżone. Trzeci typ kolekcji, tablice asocjacyjne, jest specyficzny dla języka PL/SQL. Te tablice nazywa się czasem tabelami języka PL/SQL lub tabelami indeksowanymi. Bardziej szczegółowe omówienie kolekcji znalazło się w rozdziale 3. Jeśli interesuje Cię implementowanie kolekcji, przejdź do rozdziału 6.

Systemowe kursory referencyjne Systemowe kursory referencyjne to wskaźniki na zbiory wyników w roboczych obszarach zapytań. Są to obszary w pamięci (zwane obszarami kontekstowymi) w obszarze PGA (ang. Process Global Area) bazy danych Oracle Database 12c. Roboczy obszar zapytania przechowuje informacje na temat danego zapytania. Znajdują się tam zwrócone wiersze, liczba wierszy przetworzonych przez zapytanie i wskaźnik do sparsowanego zapytania. Roboczy obszar zapytania jest niezależny od współużytkowanej puli bazy danych Oracle (zobacz dodatek A). Wszystkie kursory mają te same funkcje niezależnie od tego, czy zostaną zdefiniowane jako kursory referencyjne języka PL/SQL, czy jako zwykłe kursory. Każda instrukcja języka SQL to kursor przetwarzany i rejestrowany w obszarze kontekstowym PGA.

Kursorów referencyjnych należy używać, jeśli jeden program pobiera dane, a inny je przetwarza. Jest to szczególnie przydatne, gdy te programy są napisane w różnych językach programowania. Kursory referencyjne można implementować na dwa sposoby: ze ścisłą i słabą kontrolą typów. Kursory referencyjne są typami specyficznymi dla języka PL/SQL i można je definiować w blokach anonimowych oraz nazwanych. Najlepszym miejscem na ich zdefiniowanie jest specyfikacja pakietu, ponieważ utworzone w ten sposób kursory można współużytkować w różnych programach. Jest jeden prototyp definicji kursorów, jednak sposób jego użycia określa, czy kontrola typów w kursorze będzie ścisła, czy słaba. Prototyp ten wygląda następująco: TYPE nazwa_kursora_referencyjnego IS REF CURSOR [RETURN nazwa_obiektu_z_katalogu%ROWTYPE];

Aby utworzyć kursor referencyjny ze słabą kontrolą typów, w definicji należy pominąć typ zwracanej wartości. Z kolei typ ten należy podać przy definiowaniu kursorów referencyjnych ze ścisłą kontrolą typów. Zgodnie z praktyczną regułą kursorów referencyjnych ze ścisłą kontrolą typów należy używać wtedy, gdy potrzebne jest zakotwiczenie jego typu do typu obiektu z katalogu. Z drugiej strony kursory ze słabą kontrolą typów są doskonałym rozwiązaniem, jeśli zapytanie zwraca dane inne niż obiekty z katalogu. Ogólny kursor referencyjny ze słabą kontrolą typów jest już zdefiniowany (typ SYS_REFCURSOR) i można go używać w dowolnym miejscu środowiska programistycznego PL/SQL. Oto prototyp tworzonego zgodnie z dobrymi praktykami systemowego kursora referencyjnego ze słabą kontrolą typów: TYPE słaba_kontrola_typów IS REF CURSOR;

A to prototyp tworzonego zgodnie z dobrymi praktykami systemowego kursora referencyjnego ze ścisłą kontrolą typów: TYPE ścisła_kontrola_typów IS REF CURSOR RETURN tabela%ROWTYPE;

Zalety kursorów referencyjnych stają się bardziej widoczne, jeśli programista używa ich w składowanych jednostkach programów. Kursorów referencyjnych można ponadto używać w blokach anonimowych, a także przypisywać je do zmiennych referencyjnych środowiska SQL*Plus. Aby zdefiniować zmienną kursora referencyjnego w środowisku SQL*Plus, należy zdefiniować zmienną i wcisnąć klawisz ENTER. Aby uruchomić instrukcję w środowisku SQL*Plus, nie trzeba jej kończyć średnikiem ani ukośnikiem. Poniższe polecenie tworzy kursor referencyjny ze słabą kontrolą typów w środowisku SQL*Plus: SQL> VARIABLE sv_refcursor REFCURSOR

Rozdział 4. Podstawowe elementy języka

153

Przedstawiony poniżej program definiuje i deklaruje kursor referencyjny przed jawnym otwarciem go i przypisaniem jego wartości do zewnętrznej zmiennej z poziomu sesji: SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

DECLARE -- Deklaracja kursora referencyjnego ze słabą kontrolą typów. TYPE weakly_typed IS REF CURSOR; -- Deklaracja zmiennej lokalnej typu utworzonego wcześniej kursora. lv_refcursor WEAKLY_TYPED; BEGIN -- Otwieranie kursora referencyjnego. OPEN lv_refcursor FOR SELECT item_title , COUNT(*) FROM item HAVING (COUNT(*) > 2) GROUP BY item_title; -- Przypisanie kursora referencyjnego do zmiennej sesji środowiska SQL*Plus. :sv_refcursor := lv_refcursor; END; /

Przy użyciu poniższego zapytania można pobrać zmienną z poziomu sesji, aby sprawdzić zawartość kursora referencyjnego: SQL> SELECT 2 FROM

:sv_refcursor dual;

Jeśli Czytelnik wcześniej uruchomił generujące dane skrypty opisane we wprowadzeniu, to zapytanie zwróci następujące informacje: :SV_REFCURSOR -------------------CURSOR STATEMENT : 1 CURSOR STATEMENT : 1 ITEM_TITLE COUNT(*) ------------------------------------------------------- ---------Harry Potter and the Chamber of Secrets 3 Harry Potter: Goblet of Fire 3 Die Another Day 3 The Lord of the Rings - Two Towers 3 The Lord of the Rings - Fellowship of the Ring 3 ... 10 rows selected.

Lokalnie zdefiniowane typy kursorów referencyjnych o słabej kontroli typów można zastąpić ogólnym kursorem referencyjnym SYS_REFCURSOR. W tym celu należy opatrzyć komentarzem wiersze 2. i 3. (aby nie zmieniać dalszych numerów wierszy) oraz wprowadzić następującą poprawkę w wierszu 5.: 5

lv_refcursor SYS_REFCURSOR;

W rozdziale 8. opisano, jak używać kursorów referencyjnych w funkcjach i procedurach. Kursory referencyjne to niezwykle przydatne typy danych, kiedy trzeba przekazać do programu zewnętrznego wskaźnik do obszaru roboczego zapytania. Do przekazywania informacji do programów zewnętrznych służą biblioteki OCI8 (ang. Oracle Call Interface 8). Powtórzenie materiału W tym podrozdziale opisano następujące zagadnienia z zakresu zmiennych i typów danych:  W blokach anonimowych sekcja deklaracji rozpoczyna się od słowa kluczowego DECLARE, a w blokach nazwanych — od nagłówka funkcji lub procedury.  Słowo kluczowe BEGIN rozpoczyna sekcję wykonawczą i kończy sekcję deklaracji.

154

     

 

Część I

Podstawy języka PL/SQL

Słowo kluczowe EXCEPTION rozpoczyna sekcję wyjątków, w słowo kluczowe END kończy daną jednostkę programu. Typy danych można definiować w lokalnych blokach anonimowych i nazwanych, a także w zagnieżdżonych blokach anonimowych. W sekcji deklaracji można deklarować zmienne skalarne i złożone. Zmienne skalarne przechowują tylko jeden element (na przykład liczbę, łańcuch znaków lub datę). W języku PL/SQL można tworzyć typy pochodne od standardowych typów skalarnych. Zmienne złożone mogą przechowywać dwa i więcej elementów. Mogą być strukturą rekordową obejmującą jeden wiersz i wiele kolumn (pól), kolekcją lub kolekcją hybrydową — systemowym kursorem referencyjnym. Niektóre typy skalarne są rzutowane automatycznie, natomiast inne wymagają zastosowania przez programistę wbudowanych funkcji SQL-a. Zmienne złożone z SQL-a działają w SQL-u i w języku PL/SQL, natomiast zmienne złożone języka PL/SQL można stosować wyłącznie w tym języku.

Podsumowanie W tym rozdziale opisano: ograniczniki, definiowanie zmiennych, dostęp do nich i przypisywanie im wartości, a także pracę ze skalarnymi i złożonymi typami danych.

Test wiedzy Test wiedzy to zestaw pytań typu „prawda czy fałsz” i wielokrotnego wyboru, dzięki którym sprawdzisz, jak dobrze opanowałeś materiał z poszczególnych rozdziałów. Odpowiedzi na pytania znajdziesz w dodatku I.

Prawda czy fałsz? 1. __W bloku nazwanym sekcja deklaracji rozpoczyna się od nagłówka, specyfikacji albo sygna-

tury funkcji lub procedury. 2. __Sekcja wykonawcza może obejmować lokalny blok nazwany. 3. __Sekcja deklaracji nie może obejmować bloku anonimowego. 4. __Identyfikator to jednostka leksykalna. 5. __Połączenie dwukropka i znaku równości (:=) to jedyny operator przypisania w języku PL/SQL. 6. __Połączenie znaku równości i symbolu większości (=>) to operator asocjacji. 7. __W języku PL/SQL można tworzyć typy pochodne od standardowych typów skalarnych. 8. __Typ rekordowy jest typem języka SQL. 9. __Systemowy kursor referencyjny to typ danych obsługiwany wyłącznie w języku PL/SQL. 10. __W języku PL/SQL można stosować złożone typy danych w postaci tablic i list.

Pytania wielokrotnego wyboru 11. Jednostki leksykalne to podstawowe cegiełki języków programowania. Które z poniższych ele-

mentów są jednostkami leksykalnymi? Poprawnych może być kilka odpowiedzi. A. Ogranicznik. B. Identyfikator. C. Literał. D. Komentarz. E. Blok anonimowy.

Rozdział 4. Podstawowe elementy języka

155

12. Które z poniższych kombinacji symboli są prawidłowe w języku PL/SQL? Poprawnych może

być kilka odpowiedzi. A. Dwukropek i znak równości (:=) jako operator przypisania. B. Cudzysłów francuski (>) jako ogranicznik etykiet. C. Symbol mniejszości i symbol większości () jako operator porównywania. D. Wykrzyknik i znak równości (!=) jako operator porównywania. E. Otwierający nawias klamrowy i zamykający nawias klamrowy ({}) jako ograniczniki bloku anonimowego. 13. Które z poniższych typów danych są prawidłowymi typami skalarnymi w języku PL/SQL? Poprawnych może być kilka odpowiedzi. A. TEXT. B. VARCHAR2. C. NCHAR. D. CHAR. E. DATE. 14. Które z poniższych typów danych najlepiej nadają się do wykonywania obliczeń naukowych w języku PL/SQL? Poprawnych może być kilka odpowiedzi. A. NUMBER. B. PLS_INTEGER. C. BINARY_DOUBLE. D. BINARY_FLOAT. E. BINARY_INTEGER. 15. Z jakich powodów warto stosować systemowe kursory referencyjne? Poprawnych może być kilka odpowiedzi. A. W celu odzwierciedlenia kolekcji w postaci tabeli zagnieżdżonej. B. Gdy dane mają być pobierane w jednym programie i używane w innym. C. W celu zapisania wyników złożonego typu danych w rozwiązaniu opartym wyłącznie na języku PL/SQL. D. W celu zapisania wyników złożonego typu danych w rozwiązaniu opartym na językach SQL i PL/SQL. E. Żadne z powyższych.

156

Część I

Podstawy języka PL/SQL

ROZDZIAŁ

5

Struktury sterujące

W tym rozdziale opisano struktury sterujące języka PL/SQL. Struktury te umożliwiają dokonywanie wyborów na podstawie warunków, powtarzanie operacji i dostęp do danych. Instrukcje IF i CASE służą do rozgałęziania działania programu na podstawie warunków. Pętle służą do powtarzania operacji do momentu spełnienia określonego warunku. Kursory zapewniają dostęp do danych po jednym wierszu (lub ich zbiorze) na raz. W tym rozdziale opisano różne struktury sterujące w następującej kolejności:  Instrukcje warunkowe  Instrukcje IF  Instrukcje CASE  Instrukcje kompilacji warunkowej  Instrukcje iteracyjne  Pętle proste  Pętle FOR  Pętle WHILE  Kursory  Kursory jawne  Kursory niejawne  Instrukcje masowe  Instrukcje BULK COLLECT INTO  Instrukcje FORALL

Instrukcje warunkowe W językach programowania są trzy rodzaje instrukcji warunkowych: z pojedynczym rozgałęzieniem, z wieloma rozgałęzieniami i bez przechodzenia oraz z wieloma rozgałęzieniami i z przechodzeniem. Przechodzenie oznacza w tym kontekście przetwarzanie instrukcji dla wszystkich kolejnych warunków po znalezieniu pasującej instrukcji CASE. Instrukcje z jednym rozgałęzieniem to instrukcje if-then-else. Do tworzenia wielu rozgałęzień bez przechodzenia służy instrukcja if-then-elsif-then-else, a z przechodzeniem — instrukcja CASE. Rysunek 5.1 przedstawia logiczny przebieg operacji w instrukcjach warunkowych dwóch pierwszych rodzajów. Trzeci typ pominięto, ponieważ język PL/SQL nie obsługuje przechodzenia. Instrukcja CASE w tym języku działa podobnie jak instrukcja if-then-elsif-then-else.

158

Część I

Podstawy języka PL/SQL

Rysunek 5.1. Logiczny przebieg operacji w instrukcjach rozgałęziających W języku PL/SQL słowo zarezerwowane ELSIF zastępuje dwa odrębne słowa else if. Jest to spuścizna po językach Pascal i Ada.

Romby ze słowem „Warunek” na rysunku 5.1 to drzewa decyzyjne. Takie drzewa reprezentują rozgałęzienia kodu oparte na porównaniach. Porównania często są nazywane wyrażeniami porównania lub po prostu wyrażeniami, ponieważ zwracają wartości true lub false. A przynajmniej jest tak w modelu logiki dwuwartościowej (dwustanowej), gdzie stosowanie porównań z użyciem wartości true lub false jest proste. Większe trudności sprawiają porównania w modelu logiki trójwartościowej, w których trzeba sprawdzić wartość not true lub not false. W logice trójwartościowej wyrażenie może zwracać wartość true, false lub null. Wartość null może wystąpić, gdy porównywana wartość pochodzi z bazy danych. Wynika to z tego, że skalarne typy danych mogą mieć określoną wartość lub być równe null. Wyrażenie typu not true jest prawdziwe, jeśli sprawdzana wartość to false lub null. Z kolei wyrażenie not false jest prawdziwe, jeśli wartość to true lub… null. Jest tak, ponieważ wyrażenia not true i not false są prawdziwe, jeśli warunek nie jest spełniony, a wartość null powoduje, że warunku nie można przetworzyć. W języku PL/SQL prawidłowymi operatorami porównywania są symbole leksykalne, sekwencje symboli i identyfikatory. Tabela 4.1 zawiera listę operatorów w postaci symboli i ich definicje. W tabeli 5.1 lista ta została uzupełniona o operatory porównywania w formie identyfikatorów, którymi są tu słowa zarezerwowane i kluczowe (co opisano w rozdziale 4.). Tabela 5.1. Operatory porównywania Operator

Opis

AND

Operator AND umożliwia połączenie dwóch porównań. Powstała w ten sposób instrukcja jest prawdziwa, jeśli obie instrukcje składowe mają wartość true. Można użyć operatora AND wraz z operatorem BETWEEN, aby określić wartość maksymalną i minimalną przedziału. BEGIN IF 1 = 1 AND 2 = 2 THEN dbms_output.put_line('Prawda.'); END IF; END; /

Ten blok zwróci wartość: Prawda. BETWEEN

Operator BETWEEN pozwala sprawdzić, czy wartość zmiennej zawiera się w przedziale między dwiema wartościami tego samego typu. Jest to operator włączający, co oznacza, że zwraca true także wtedy, gdy zmienna ma wartość graniczną (taka wartość musi być różna od null). W operatorze BETWEEN mniejszą wartość trzeba podać przed większą.

Rozdział 5. Struktury sterujące

159

Tabela 5.1. Operatory porównywania — ciąg dalszy Operator

Opis

BETWEEN

BEGIN IF 1 BETWEEN 1 AND 3 THEN dbms_output.put_line('W przedziale.'); END IF; END; /

Ten blok zwróci następujące dane wyjściowe: W przedziale. IN =ANY =SOME

Operator IN służy do sprawdzania, czy zmienna ma wartość z podanego zbioru wartości rozdzielonych przecinkami. Czasem taką zmienną nazywa się wartością wyszukiwaną, ponieważ operator porównuje jedną wartość skalarną z listą wartości. Operatory =ANY i =SOME działają w taki sam sposób. Pozwalają sprawdzić, czy lewy operand znajduje się w zbiorze podanym jako prawy operand. Te operatory działają w trybie skróconego przetwarzania, co oznacza, że zaprzestają poszukiwań po znalezieniu jednej pasującej wartości. BEGIN IF 1 IN (1,2,3) THEN dbms_output.put_line('W zbiorze.'); END IF; END; /

Ten blok zwróci następujące dane wyjściowe: W zbiorze. IS EMPTY

Operator IS EMPTY pozwala ustalić, czy kolekcja typu VARRAY lub tabela zagnieżdżona są puste (w procesie konstrukcji nie podano żadnych elementów domyślnych). Oznacza to, że w obszarze SGA nie przydzielono pamięci żadnemu elementowi kolekcji. Jeśli tak jest, operator IS EMPTY zwróci true, ale jeśli choć jeden element będzie miał przydzieloną wartość, operator ten zwróci false. Jeśli programista nie zainicjuje kolekcji przy użyciu jawnej konstrukcji, program zgłosi wyjątek PLS-00306. Proces tworzenia kolekcji opisano w rozdziałach 3. i 6. Operator ten działa tylko dla kolekcji typów skalarnych języka SQL. Ten operator niejawnie wykonuje porównanie IS A SET, dlatego prawdopodobnie jest przydatniejszy od operatora IS A SET. DECLARE TYPE list IS TABLE OF INTEGER; a LIST := list(); BEGIN IF a IS EMPTY THEN dbms_output.put_line('"a" jest pusta.'); END IF; END; /

Ten blok zwróci następujące dane wyjściowe: "a" jest pusta. IS NULL

Operator IS NULL umożliwia sprawdzenie, czy zmienna ma wartość null. Funkcja wbudowana NVL pozwala przypisać zmiennej logicznej lub wyrażeniu jawnie określoną wartość true lub false. DECLARE var BOOLEAN; BEGIN IF var IS NULL THEN dbms_output.put_line('Ma wartość null.'); END IF; END; /

Ten blok zwróci następujące dane wyjściowe: Ma wartość null.

160

Część I

Podstawy języka PL/SQL

Tabela 5.1. Operatory porównywania — ciąg dalszy Operator

Opis

IS A SET

Operator IS A SET pozwala sprawdzić, czy zmienna jest tablicą VARRAY lub tabelą zagnieżdżoną (zmienna musi być wcześniej skonstruowana). Operator ten zwraca true, jeśli zmienna jest tablicą VARRAY lub tabelą zagnieżdżoną oraz została skonstruowana, czyli program utworzył egzemplarz odpowiedniej kolekcji (z elementami lub bez nich). Więcej informacji o konstruowaniu typów obiektowych zawiera rozdział 11. Operator IS A SET zwraca false, jeśli zmienna jest tablicą VARRAY lub tabelą zagnieżdżoną, ale nie została skonstruowana (nie została zainicjowana). Próba zastosowania tego operatora do tablicy asocjacyjnej doprowadzi do wyjątku PLS-00306. Ten operator działa tylko dla kolekcji przechowujących elementy typów skalarnych. Pominięcie litery "A" w nazwie operatora IS A SET spowoduje wyjątek PLS-00103 informujący o błędnym identyfikatorze. DECLARE TYPE list IS TABLE OF INTEGER; a LIST := list(); BEGIN IF a IS A SET THEN dbms_output.put_line('"a" to zbiór.'); END IF; END; /

Ten blok zwróci następujące dane wyjściowe: "a" to zbiór. LIKE

Operator LIKE pozwala sprawdzić, czy wartość zmiennej jest częścią wartości innej zmiennej. W porównaniach można używać symboli wieloznacznych: podkreślenia (_) zastępują jeden znak, a symbol % — wiele znaków. W łańcuchach znaków symbol % nie pełni funkcji wskaźnika atrybutu z języka PL/SQL. BEGIN IF 'Str%' LIKE 'String' THEN dbms_output.put_line('Dopasowano.'); END IF; END; /

Ten blok zwróci następujące dane wyjściowe: Dopasowano. MEMBER OF

MEMBER OF to logiczny operator porównywania. Służy do sprawdzania, czy element należy do określonej kolekcji. Operator ten działa tylko dla kolekcji typów skalarnych języka SQL. Zwraca true, jeśli kolekcja zawiera podany element, w przeciwnym razie zwraca false. DECLARE TYPE list IS TABLE OF NUMBER; n VARCHAR2(10) := 'Jeden’; a LIST := list('Jeden', 'Dwa', 'Trzy'); BEGIN IF n MEMBER OF a THEN dbms_output.put_line('"n" należy do zbioru.'); END IF; END; /

Jeśli lewy operand ma wartość null, operator zwróci false. Dlatego przed użyciem tego operatora trzeba zawsze sprawdzić, czy operand ma określoną wartość. Powyższy blok zwróci następujące dane wyjściowe: "n" należy do zbioru. NOT

NOT to logiczny operator negacji pozwalający sprawdzić przeciwieństwo wartości logicznej wyrażenia (pod warunkiem że nie jest to wartość null).

Rozdział 5. Struktury sterujące

161

Tabela 5.1. Operatory porównywania — ciąg dalszy Operator

Opis

NOT

BEGIN IF NOT FALSE THEN dbms_output.put_line('Prawda.'); END IF; END; /

Jeśli sprawdzane wyrażenie ma wartość null, operator NOT nie zmienia wyniku. Nie ma przeciwieństwa wartości null, a jej logiczna negacja to także null. W powyższym bloku użyto literału logicznego FALSE, a ponieważ TRUE to jedyna wartość logiczna różna od FALSE (bez uwzględniania wartości null), kod zwróci następujące dane wyjściowe: Prawda. OR

Operator OR umożliwia połączenie dwóch porównań. Powstała w ten sposób instrukcja jest prawdziwa, jeśli choć jedna instrukcja składowa ma wartość true. W języku PL/SQL używana jest ewaluacja skrócona, co oznacza, że program kończy sprawdzanie, jeśli choć jedna wartość jest prawdziwa. BEGIN IF 1 = 1 OR 1 = 2 THEN dbms_output.put_line('Prawda.'); END IF; END; /

Ponieważ jedna z instrukcji jest prawdziwa, blok zwróci następujące dane wyjściowe: Prawda. SUBMULTISET

Operator SUBMULTISET pozwala sprawdzić, czy kolekcja VARRAY lub tabela zagnieżdżona są podzbiorem kolekcji tego samego typu. Operator zwraca true, jeśli elementy z lewego zbioru znajdują się w zbiorze prawym. Warto zauważyć, że operator nie sprawdza podzbiorów właściwych, które muszą być przynajmniej o jeden element mniejsze od zbioru pełnego (tożsamego). DECLARE TYPE list IS TABLE OF INTEGER; a LIST := list(1,2,3); b LIST := list(1,2,3,4); BEGIN IF a SUBMULTISET b THEN dbms_output.put_line('Poprawny podzbiór.'); END IF; END; /

Ten blok zwróci następujące dane wyjściowe: Poprawny podzbiór.

Trzeba też poznać kolejność stosowania operatorów porównywania. Przedstawia ją tabela 5.2. Można zmienić ten porządek, umieszczając wyrażenia podrzędne w nawiasach. Język PL/SQL traktuje wszystkie wyrażenia w nawiasach jako odrębne wyniki, a następnie stosuje pozostałe operatory porównywania w wyrażeniu zgodnie ze standardową kolejnością. Porównania z jednym wyrażeniem zwracają wartości true, false i null. Wartości false i null nie są uznawane za prawdziwe przy porównywaniu z wartością true. Podobnie wartości true i null nie są oceniane jako fałszywe przy porównywaniu z wartością false. Wyrażenia zwracające null nie są ani prawdziwe, ani fałszywe. Tabela 5.3 przedstawia możliwe wyniki porównań w tablicy prawdziwości. Do opisu porównań z kilkoma wyrażeniami potrzebne są dwie dwuargumentowe tablice prawdziwości: jedna dla operatora iloczynu logicznego (AND) i druga dla operatora sumy logicznej (OR). Aby wyrażenie powstałe przez zastosowanie operatora iloczynu logicznego miało wartość true, wszystkie wyrażenia podrzędne muszą mieć taką wartość. Jeśli jedno z wyrażeń ma wartość false lub null, całe wyrażenie nie jest prawdziwe. Tabela 5.4 przedstawia wyniki dla wyrażeń X i Y o wartościach true, false i null.

162

Część I

Podstawy języka PL/SQL

Tabela 5.2. Kolejność operacji Kolejność

Operator

Definicja

1

**

Potęgowanie

2

+, -

Tożsamość i odwrotność

3

*, /

Mnożenie i dzielenie

4

+, -, ||

Dodawanie, odejmowanie i łączenie

5

==, , =, , !=, ~=, ^=, BETWEEN, IN, IS NULL, LIKE

Porównywanie

6

AND

Iloczyn logiczny

7

NOT

Negacja logiczna

8

OR

Suma logiczna

Tabela 5.3. Tablica prawdziwości dla jednej zmiennej Wartość wyrażenia X

Wyrażenie

Wynik

Wyrażenie z negacją

Wynik

TRUE

X to TRUE

TRUE

X to NOT TRUE

FALSE

FALSE

X to TRUE

FALSE

X to NOT TRUE

TRUE

NULL

X to TRUE

NULL

X to NOT TRUE

TRUE

Tabela 5.4. Tablica prawdziwości dla iloczynu logicznego; X i Y mają wartości TRUE, FALSE i NULL XiY

Y to TRUE

Y to FALSE

Y to NULL

X to TRUE

TRUE

FALSE

FALSE

X to FALSE

FALSE

FALSE

FALSE

X to NULL

FALSE

FALSE

FALSE

Także do opisu porównań z kilkoma wyrażeniami połączonymi operatorem sumy logicznej potrzebna jest dwuargumentowa tablica prawdziwości. Aby wyrażenie powstałe przez zastosowanie operatora sumy logicznej miało wartość true, jedno z wyrażeń podrzędnych musi mieć taką wartość. Z uwagi na wyrażenia o wartości null wyrażenie połączone może mieć wartość true, false lub null. Tabela 5.5 przedstawia wyniki zastosowania operatora sumy logicznej na wyrażeniach X i Y o wartościach true, false i null. Tabela 5.5. Tablica prawdziwości dla sumy logicznej; X i Y mają wartości TRUE, FALSE i NULL X lub Y

Y to TRUE

Y to FALSE

Y to NULL

X to TRUE

TRUE

TRUE

TRUE

X to FALSE

TRUE

FALSE

FALSE

X to NULL

TRUE

FALSE

FALSE

Przy obliczaniu sumy logicznej przeprowadzana jest ewaluacja skrócona (nazywana też ewaluacją McCarthy’ego — od nazwiska słynnego naukowca z dziedziny informatyki, Johna McCarthy’ego). Zaletą ewaluacji skróconej jest to, że pozwala wyeliminować konieczność sprawdzania wszystkich wartości. Wynika to z tego, że wystarczy jedna wartość true, aby całe wyrażenie miało taką wartość. Tabele 5.4 i 5.5 wyświetlają wyniki sprawdzania, czy wyrażenia są równe true. Wyniki zmieniają się, gdy wyrażenie ma sprawdzać, czy jedna lub obie wartości są równe false. Wtedy wartości true należy traktować jak false, a wartości false — jak true. Wartości null nadal są traktowane jak false. Tablice prawdziwości powinny pomóc w zaplanowaniu logiki rozgałęzień w instrukcjach IF i CASE. Te same wyniki logiczne dają operacje na trzech i więcej wyrażeniach, nie da się ich jednak opisać w tabelach dwuwymiarowych.

Rozdział 5. Struktury sterujące

163

W tym fragmencie opisano szczegóły, które pomogą w zrozumieniu podpunktów dotyczących rozgałęziania. Podpunkty te dotyczą instrukcji IF z jednym i wieloma rozgałęzieniami oraz poleceń z wieloma rozgałęzieniami opartych na instrukcjach CASE z wyszukiwaniem i prostych. Podpunkty te zostały zgrupowane w punktach poświęconych instrukcjom IF i CASE.

Instrukcje IF Instrukcje IF to bloki, które umożliwiają tworzenie pojedynczych i wielokrotnych rozgałęzień. Rozpoczynają się od identyfikatora (słowa zarezerwowanego) BEGIN, a kończą identyfikatorem END i średnikiem. Wszystkie bloki tego typu — podobnie jak bloki anonimowe i nazwane — wymagają podania przynajmniej jednej instrukcji. Instrukcje IF sprawdzają wartość warunku. Może nim być wyrażenie obejmujące porównanie lub zbiór takich wyrażeń. Wynik porównania to wartość logiczna TRUE lub FALSE. Można porównywać literały lub zmienne tego samego typu, przy czym typy zmiennych nie muszą być identyczne, jeśli zostaną niejawnie lub jawnie skonwertowane na jeden typ (tabelę niejawnych konwersji przedstawia rysunek 4.2 z rozdziału 4.). Zamiast porównywania można użyć zmiennych logicznych, a także porównywać ze sobą wyniki działania dwóch funkcji lub zmienną z wynikiem funkcji, jeśli zwraca ona zmienną logiczną. Operatory porównywania opisano w tabelach 4.1 oraz 5.1.

Instrukcje if-then-else Instrukcja if-then-else tworzy jedno rozgałęzienie. Sprawdza warunek, a jeśli jest on spełniony, uruchamia kod znajdujący się bezpośrednio pod warunkiem. Prototyp tej instrukcji wygląda następująco: IF [NOT] {wyrażenie_z_porównaniem | wartość_logiczna} [[AND | OR] {wyrażenie_z_porównaniem | wartość_logiczna}] THEN blok_dla_true; [ELSE blok_dla_false;] END IF;

Można użyć opcjonalnego logicznego operatora negacji NOT, aby sprawdzić, czy wyrażenie ma wartość false. Choć w prototypie instrukcji IF znajduje się tylko jedna klauzula [AND | OR], nie ma ograniczenia liczby sprawdzanych warunków. Blok ELSE jest opcjonalny. Instrukcje IF bez bloku ELSE wykonują kod tylko wtedy, gdy warunek jest spełniony. Najprostsza forma omawianej instrukcji to blok if-then. Poniższy kod przedstawia taką instrukcję porównującą dwa literały liczbowe: SQL> BEGIN 2 IF 1 = 1 THEN 3 dbms_output.put_line('Warunek jest spełniony.'); 4 END IF; 5 END; 6 /

Warto zauważyć, że nawiasy wokół instrukcji porównania (wiersz 2.) nie są niezbędne. Jest to udogodnienie w porównaniu z niektórymi innymi językami programowania (na przykład z językami JavaScript i PHP), w których są one wymagane. Ta sama operacja z użyciem zmiennej logicznej zamiast porównania wygląda następująco: SQL> 2 3 4 5 6 7 8

DECLARE lv_equal BOOLEAN NOT NULL := TRUE; BEGIN IF lv_equal THEN dbms_output.put_line('Warunek jest spełniony.'); END IF; END; /

W wierszu 4. wykonywane jest leniwe porównywanie, ponieważ kod ma sprawdzić, czy wartość zmiennej to true. Zdaniem niektórych programistów nie należy stosować leniwego porównywania

164

Część I

Podstawy języka PL/SQL

w języku PL/SQL, ja jednak myślę inaczej. Pełne (rozwlekłe) porównanie w wierszu 4. wyglądałoby tak jak poniżej i działałoby tak samo: 4

IF lv_equal = TRUE THEN

Przy sprawdzaniu zmiennej logicznej lub wyrażenia o wartości null taka instrukcja IF nie może być prawdziwa i zwraca wartość false. Na szczęście zmienna lv_equal w wierszu 2. jest zadeklarowana tak, że nie pozwala na ustawienie wartości null, a ponadto przypisywana jest do niej wartość true. Wywołania funkcji jako wyrażenia Do wywoływanej funkcji należy przekazać wartości lub zmienne, a zwraca ona wynik. Jeśli funkcja zwraca zmienną typu łańcucha znaków o zmiennej długości, można nazwać ją wyrażeniem łańcuchowym, ponieważ wynik jej działania to łańcuch znaków. Takiego wyniku można używać jak literału znakowego (zobacz rozdział 4.). W definicji funkcji można też określić inne skalarne typy danych zwracanych wartości, a następnie używać tej funkcji jako wyrażenia generującego wartości tego typu. Poniższy program porównuje zmienną z wyrażeniem (wywołaniem funkcji) w wierszu 16.: SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

DECLARE -- Deklaracja zmiennej lokalnej. lv_thing_one VARCHAR2(5) := 'Trzy'; -- Deklaracja funkcji lokalnej. FUNCTION ordinal (n NUMBER) RETURN VARCHAR2 IS /* Deklaracja lokalnego typu kolekcji. */ TYPE ordinal_type IS TABLE OF VARCHAR2(5); /* Deklaracja i inicjowanie zmiennej typu kolekcji. */ lv_ordinal ORDINAL_TYPE := ordinal_type('Jeden','Dwa','Trzy','Cztery'); BEGIN RETURN lv_ordinal(3); END; BEGIN IF lv_thing_one = ordinal(3) THEN dbms_output.put_line('['||ordinal(3)||']'); END IF; END; /

Porównania działają dla literałów, zmiennych i wartości wyrażeń (w tym wartości zwracanych przez wywołania funkcji). Wartość zmiennej lv_thing_one i wartość zwracana przez funkcję ordinal są takie same. Dlatego jeśli ustawiona jest zmienna SERVEROUTPUT środowiska SQL*Plus, program wyświetli poniższe dane wyjściowe: Trzy

Wartość zwracana przez funkcję to wyrażenie lub wartość czasu wykonania, którą trzeba porównać z zawartością zmiennej, literałem lub innym wywołaniem funkcji. Ponadto wartość zwracaną przez funkcję można przekazać jako parametr wywołania innej funkcji lub procedury.

Należy przewidzieć działania programu, które mogą doprowadzić do pojawienia się w instrukcji wartości null, i wszędzie tam, gdzie to możliwe, używać funkcji wbudowanej NVL, aby uniknąć nieoczekiwanych wyników. Domyślne działanie tej funkcji jest odpowiednie, jeśli program ma traktować wartości null jak wartości false. Załóżmy, że zmiennej lv_equal nie można traktować jak stałej, ponieważ program dynamicznie przypisuje do niej wartość. To oznacza, że trzeba zabezpieczyć operację porównywania IF true. W tym celu w wierszu 4. należy zastosować funkcję NVL: 4

IF NVL(lv_equal,TRUE) THEN

Funkcja NVL gwarantuje, że jeśli wartość zmiennej lv_equal jest równa null lub true, warunek będzie spełniony. Można też odwrócić pytanie i zastosować negację IF NOT false. W tym celu zastosuj w wierszu 4. następującą składnię: 4

IF NOT NVL(lv_equal,FALSE) THEN

Rozdział 5. Struktury sterujące

165

Przy pojedynczym rozgałęzieniu to rozwiązanie działa dobrze, jednak jeśli potrzebnych jest więcej gałęzi, konieczne może być umieszczenie zmiennej lv_equal w kilku miejscach, co w każdym języku programowania jest złym pomysłem. Przy stosowaniu zmiennych dynamicznych można wykorzystać lepsze rozwiązanie. Przed logiką biznesową przypisz do zmiennej lv_value wartość domyślną, ustawianą, gdy ta zmienna jest równa null. Poniższy kod ustawia wartość domyślną na true: 4 5 6

IF NOT lv_logic = TRUE AND NOT lv_logic = FALSE THEN lv_logic := TRUE; END IF;

Wiersz 4. sprawdza dwa warunki z negacją — IF NOT true i IF NOT false. Oba te warunki są jednocześnie spełnione tylko wtedy, gdy zmienna ma wartość null. Gdy tak jest, wiersz 5. przypisuje zmiennej wartość TRUE. To gwarantuje, że porównania w logice biznesowej będą działały prawidłowo. Jeśli w środowisku SQL*Plus ustawiona jest zmienna SERVEROUTPUT (instrukcje jej ustawiania znajdziesz w dodatku A), oba przytoczone wcześniej bloki anonimowe ocenią porównanie jako prawdziwe i wyświetlą następujący tekst: Warunek jest spełniony.

Dodając rozgałęzienie, można utworzyć instrukcję if-then-else: SQL> BEGIN 2 IF 1 = 2 THEN 3 dbms_output.put_line('Warunek jest spełniony.'); 4 ELSE 5 dbms_output.put_line('Warunek nie jest spełniony.'); 6 END IF; 7 END; 8 /

W tym bloku anonimowym porównanie w wierszu 2. ma wartość false, dlatego program wyświetli następujące dane wyjściowe z bloku ELSE: Warunek nie jest spełniony.

W przedstawionych programach można zastąpić literały zmiennymi lub wywołaniami funkcji zwracających pasujące typy (lub takie, które można odpowiednio skonwertować). Można także użyć jednej funkcji zwracającej wartość typu BOOLEAN.

Instrukcje if-then-elsif-then-else Instrukcja if-then-elsif-then-else pozwala utworzyć wiele rozgałęzień. Sprawdza szereg warunków, a następnie uruchamia kod znajdujący się bezpośrednio pod pierwszym spełnionym warunkiem. Po przetworzeniu tego kodu program kończy wykonywanie instrukcji i ignoruje następne spełnione warunki. Prototyp instrukcji if-then-elsif-then-else wygląda następująco: IF

[NOT] {wyrażenie_z_porównaniem | wartość_logiczna} {wyrażenie_z_porównaniem | wartość_logiczna}] ... ] THEN blok_dla_if_true; [ELSIF [NOT] {wyrażenie_z_porównaniem | wartość_logiczna} {wyrażenie_z_porównaniem | wartość_logiczna}] ... ] THEN blok_dla_elsif_true; [ELSE blok_dla_wszystkich_false;] END IF;

[[AND | OR] [[AND | OR] [[AND | OR] [[AND | OR]

Opcjonalny operator NOT służy do sprawdzania, czy wyrażenie ma wartość false. Choć w tym prototypie są tylko po dwie klauzule [AND | OR], wielokropek oznacza, że nie ma ograniczenia liczby sprawdzanych warunków. Blok ELSE jest opcjonalny. Instrukcje bez bloku ELSE wykonują kod tylko wtedy, jeśli jeden z warunków jest spełniony. Zawsze dodawaj blok ELSE — nawet wtedy, gdy zawiera on polecenie, które nie wykonuje żadnych zadań (na przykład NULL;). Dla innych programistów pracujących nad tym kodem jest to informacja, że uwzględniłeś możliwość wykonywania operacji w tym bloku.

166

Część I

Podstawy języka PL/SQL

Poniższy program zawiera instrukcję if-then-elsif-then-else, w której dwa pierwsze porównania mają wartość true, a trzecie — false: SQL> 2 3 4 5 6 7 8 9 10 11 12

DECLARE lv_equal BOOLEAN NOT NULL := TRUE; BEGIN IF 1 = 1 THEN dbms_output.put_line('Warunek pierwszy jest spełniony.'); ELSIF lv_equal THEN dbms_output.put_line('Warunek drugi jest spełniony.'); ELSIF 1 = 2 THEN dbms_output.put_line('Warunek trzeci jest spełniony.'); END IF; END; /

Ten blok anonimowy oceni pierwsze porównanie jako prawdziwe i wyświetli następujące dane wyjściowe: Warunek pierwszy jest spełniony.

Instrukcja if-then-elsif-then-else kończy działanie po znalezieniu pierwszego spełnionego warunku. To dlatego drugie porównanie dające wartość true (wiersz 6.) nie jest już przetwarzane. Domyślny blok ELSE jest uruchamiany tylko wtedy, gdy żaden z warunków nie jest spełniony.

Instrukcje CASE W języku PL/SQL są dwa rodzaje instrukcji CASE. Oba wymagają zdefiniowania selektora, czyli zmiennej, funkcji albo wyrażenia porównywanego przez instrukcję CASE z wartościami podanymi w blokach WHEN. Selektor należy umieścić bezpośrednio po słowie zarezerwowanym CASE. Jeśli programista nie poda selektora, język PL/SQL użyje wartości logicznej true. Jako selektorów można używać dowolnych typów danych języka PL/SQL oprócz typów BLOB, BFILE i typów złożonych, czyli rekordów, kolekcji, typów obiektowych i systemowych kursorów referencyjnych (zobacz rozdział 4.). Ogólny prototyp instrukcji CASE wygląda następująco: CASE [{ TRUE | FALSE | zmienna_selektora }] WHEN [ kryterium1 | wyrażenie1 ] THEN instrukcje_dla_kryterium1; [WHEN [ kryterium2 | wyrażenie2 ] THEN instrukcje_dla_kryterium2; ] [WHEN [ ... ] THEN ... ] ELSE instrukcje_bloku_else; END CASE;

Selektory w prostych instrukcjach CASE to zmienne typów innych niż typ logiczny lub funkcje zwracające wartości tego rodzaju. Selektory w instrukcjach CASE z wyszukiwaniem to zmienne logiczne lub funkcje, które zwracają wartości tego typu. Selektor domyślny to wartość logiczna true. W instrukcjach CASE z wyszukiwaniem można pominąć selektor, jeśli programista chce znaleźć wyrażenia o wartości true. Instrukcje CASE, podobnie jak instrukcje IF, mają klauzulę ELSE. W instrukcjach CASE działa ona podobnie jak w instrukcjach IF, choć różni się jedną cechą — nie można jej pominąć, ponieważ program zgłosi błąd CASE_NOT_FOUND lub PLS-06592, jeśli nie znajdzie podanego selektora. W języku PL/SQL używana jest domyślna klauzula ELSE, jeśli programista nie poda jej, a w czasie wykonywania programu nie zostanie odnaleziony pasujący blok WHEN. Instrukcje CASE to bloki. Rozpoczynają się od identyfikatora (słowa zarezerwowanego) BEGIN, a kończą identyfikatorem END i średnikiem. Wszystkie bloki tego typu — podobnie jak bloki anonimowe i nazwane — wymagają podania przynajmniej jednej instrukcji. W instrukcjach CASE trzeba użyć przynajmniej jednej instrukcji w każdym bloku WHEN i ELSE.

Rozdział 5. Struktury sterujące

167

Podobnie jak instrukcja if-then-elsif-then-else, instrukcja CASE sprawdza bloki WHEN, kolejno dopasowując je do selektora. Program uruchamia pierwszy dopasowany blok WHEN, a następnie wychodzi z instrukcji CASE. W języku PL/SQL nie jest stosowane przechodzenie do następnych bloków. Program uruchomi blok ELSE tylko wtedy, gdy nie dopasuje do selektora żadnego bloku WHEN.

Proste instrukcje CASE W prostych instrukcjach CASE selektor ma dowolny typ danych języka PL/SQL oprócz typów BLOB, BFILE i typów złożonych. Prototyp takiej instrukcji wygląda następująco: CASE zmienna_selektora WHEN kryterium1 THEN instrukcje_dla_kryterium1 [WHEN kryterium2 THEN instrukcje_dla_kryterium2 [WHEN ... THEN ... ]] ELSE instrukcje_bloku_else; END CASE;

Proste instrukcje CASE wymagają podania selektora. Można użyć o wiele więcej bloków WHEN, jednak im większa jest ich liczba, tym mniej skuteczne okazuje się rozwiązanie tego typu. Zwykle warto stosować tę instrukcję, jeśli sprawdzanych wartości jest nie więcej niż 10. Wraz ze wzrostem liczby bloków WHEN konserwacja kodu staje się coraz trudniejsza. W poniższym programie użyto selektora typu NUMBER: SQL> 2 3 4 5 6 7 8 9 10 11 12 13

DECLARE lv_selector NUMBER := 0; BEGIN CASE lv_selector WHEN 0 THEN dbms_output.put_line('Warunek 0.'); WHEN 1 THEN dbms_output.put_line('Warunek 1.'); ELSE dbms_output.put_line('Nie znaleziono.'); END CASE; END; /

Ten blok anonimowy ocenia jako prawdziwe pierwsze porównanie, ponieważ selektor lv_selector ma wartość 0. Dlatego program wyświetli: Warunek 0.

Oznacza to, że program dopasował do wartości selektora pierwszy blok WHEN. Instrukcja CASE wstrzymuje wtedy dalsze sprawdzanie i uruchamia kod w pasującym bloku WHEN, a następnie kończy działanie. Selektor może też mieć wartość innych typów języka PL/SQL, między innymi CHAR, NCHAR lub VARCHAR2.

Instrukcje CASE z wyszukiwaniem W instrukcji CASE z wyszukiwaniem selektor jest określany niejawnie, chyba że programista chce znaleźć warunek fałszywy. Selektor false trzeba podać jawnie. Czasem wartość selektora w instrukcjach CASE z wyszukiwaniem jest określana dynamicznie na podstawie logiki działania programu. Wtedy jako selektora można użyć funkcji zwracającej zmienną logiczną. Oczywiście należy wtedy zadbać o to, aby wartość selektora była różna od null. Ponieważ w instrukcjach CASE z wyszukiwaniem dozwolone są tylko selektory logiczne lub wyrażenia porównywania, należy umieścić je w wywołaniu NVL zwracającym wartość logiczną true lub false. Prototyp omawianej instrukcji wygląda następująco:

168

Część I

Podstawy języka PL/SQL

CASE [{TRUE | FALSE | zmienna_selektora}] WHEN {kryterium | wyrażenie} [{AND | OR } {kryterium | wyrażenie} [{AND | OR } ... ]] THEN instrukcje_dla_kryterium; [WHEN {kryterium | wyrażenie} [{AND | OR } {kryterium | wyrażenie} [{AND | OR } ... ]] THEN] instrukcje_dla_kryterium; [WHEN { ... } THEN ... ] ELSE instrukcje_bloku_else; END CASE;

Podobnie jak w prostych instrukcjach CASE można tu użyć o wiele więcej bloków WHEN, niż w prototypie, jednak im większa jest ich liczba, tym rozwiązanie jest mniej wydajne. Poniższa instrukcja CASE z wyszukiwaniem sprawdza wyrażenia obejmujące porównania: SQL> BEGIN 2 CASE 3 WHEN 1 = 2 THEN 4 dbms_output.put_line('Warunek [1 = 2]'); 5 WHEN 2 = 2 AND 'Something' = 'Something' THEN 6 dbms_output.put_line('Warunek [2 = 2]'); 7 ELSE 8 dbms_output.put_line('Nie znaleziono.'); 9 END CASE; 10 END; 11 /

Ten blok anonimowy ocenia pierwsze porównanie (wiersz 3.) jako fałszywe, natomiast koniunkcję (jest to wymyślna nazwa na dwa lub więcej porównań) z wiersza 5. — jako prawdziwą. Dzieje się tak, ponieważ oba porównania w koniunkcji mają wartość true, co pasuje do wartości selektora domyślnego. Dlatego program wchodzi do danego bloku i wyświetla informację z wiersza 6.: Warunek [2 = 2]

Gdyby ta instrukcja CASE szukała warunku fałszywego, selektor zostałby dopasowany do pierwszego bloku WHEN, a program poinformowałby, że 1 równa się 2. Jako selektorów można używać także wyrażeń z porównaniem.

Instrukcje kompilacji warunkowej Od czasu udostępnienia bazy danych Oracle 10g Release 2 można stosować kompilację warunkową. Umożliwia to dołączenie do programu kodu diagnostycznego lub służącego do wykonywania specjalnych zadań, a uruchamianego tylko po ustawieniu odpowiednich zmiennych z poziomu sesji. Poniższe polecenie przypisuje do zmiennej DEBUG (jest to zmienna czasu kompilacji języka PL/SQL) wartość 1: ALTER SESSION SET PLSQL_CCFLAGS = 'debug:1';

Warto zauważyć, że wielkość znaków nazwy opcji czasu kompilacji nie ma znaczenia. Takim zmiennym można też przypisać wartości true i false, aby działały jak zmienne logiczne. Jeśli programista chce ustawić więcej niż jedną opcję kompilacji warunkowej, powinien użyć następującej składni: ALTER SESSION SET PLSQL_CCFLAGS = 'nazwa1:wartość1 [, nazwa(n+1):wartość(n+1) ]';

Parametry kompilacji warunkowej są przechowywane w postaci par nazwa-wartość w parametrze PLSQL_CCFLAG bazy danych. W poniższym programie użyto słów zarezerwowanych sterujących preprocesorem, $IF, $THEN, $ELSIF, $ELSE i $END, do utworzenia bloku kompilacji warunkowej: SQL> BEGIN 2 NULL; -- Ta instrukcja jest wymagana, gdy zmienna sesji PLSQL_CCFLAGS nie jest ustawiona. 3 $IF $$DEBUG = 1 $THEN 4 dbms_output.put_line('Włączono diagnozowanie na poziomie 1.');

Rozdział 5. Struktury sterujące 5 6 7 8 9 10 11

169

$ELSIF $$DEBUG = 2 $THEN dbms_output.put_line('Włączono diagnozowanie na poziomie 2.'); $ELSE dbms_output.put_line('Poziom diagnozowania różny od 1 i 2.'); $END END; /

Wiersze 3. i 5. porównują wartość zmiennej $$DEBUG z literałem liczbowym, jednak można też zastosować zmienne dynamiczne, na przykład zmienną podstawianą &input (więcej informacji o zmiennych podstawianych znajdziesz w punkcie „Przekazywanie parametrów do skryptów środowiska SQL*Plus” w dodatku A). Gdy zmienna sesji PLSQL_CCFLAGS ma wartość 1, przedstawiony kod wyświetli następujący tekst: Włączono diagnozowanie na poziomie 1.

Bloki kompilacji warunkowej różnią się od standardowych bloków if-then-else. Najbardziej zauważalne jest to, że blok kończy się dyrektywą $END, a nie instrukcją END IF i średnikiem. Dyrektywa $END kończy instrukcje warunkowe, a instrukcja END IF zamyka blok IF. Zgodnie z regułami składni blok musi kończyć się średnikiem (symbolem zakończenia instrukcji), jednak znak ten nie jest warunkową jednostką leksykalną, a jego użycie bez poprzedzenia odpowiednią instrukcją spowoduje błąd kompilacji. Symbol $$ określa warunkową zmienną czasu kompilacji języka PL/SQL. Instrukcja ALTER SESSION służy do ustawiania zmiennych warunkowych czasu kompilacji. Ich wartości są zapisywane w zmiennej sesji PLSQL_CCFLAGS, która może przechowywać wartości wielu zmiennych. Wszystkie zmienne czasu kompilacji są stałymi do momentu zakończenia sesji lub ich zastąpienia. Aby je zastąpić, należy ponownie wywołać instrukcję ALTER SESSION. Po ponownym ustawieniu zmiennej sesji PLSQL_CCFLAGS wszystkie wcześniej podane warunkowe zmienne czasu kompilacji przestają istnieć. Zasady rządzące kompilacją warunkową są określone w parserze języka SQL. Kompilacji warunkowej nie można używać w typach obiektowych języka SQL. To ograniczenie dotyczy także tabel zagnieżdżonych i tablic VARRAY. Kompilacja warunkowa ma inną formę w funkcjach i procedurach, a także w zależności od tego, czy dany podprogram ma listę parametrów formalnych. Można podać instrukcje kompilacji warunkowej po nawiasie otwierającym listę parametrów formalnych: SQL> 2 3 4 5 6 7

CREATE OR REPLACE FUNCTION conditional_type ( magic_number $IF $$DEBUG = 1 $THEN SIMPLE_NUMBER $ELSE NUMBER $END ) RETURN NUMBER IS BEGIN RETURN magic_number; END; /

Kompilację warunkową można też umieścić po słowach kluczowych AS i IS, jeśli dana funkcja lub procedura nie mają parametrów. W funkcjach i procedurach z parametrami instrukcje kompilacji warunkowej można umieścić zarówno w liście parametrów formalnych, jak i po słowach kluczowych AS i IS. Instrukcje kompilacji warunkowej w wyzwalaczach i blokach anonimowych mogą znajdować się wyłącznie po słowie kluczowym BEGIN. Warto pamiętać, że w blokach kompilacji warunkowej nie można używać miejsc na dane (zmiennych powiązanych). Dostępne są też związane z kompilacją warunkową wbudowane dyrektywy do sprawdzania wartości:  $$PLSQL_UNIT zwraca pusty łańcuch znaków dla bloków anonimowych i zapisaną wielkimi literami nazwę dla bloków nazwanych (funkcji lub procedur).  $$PLSQL_OWNER zwraca użytkownika bazy danych, który jest właścicielem danej jednostki programu. Jest to nowa dyrektywa wbudowana, wprowadzona w bazie Oracle Database 12c.  $$PLSQL_TYPE zwraca typ danej jednostki programu. Jest to nowa dyrektywa wbudowana, wprowadzona w bazie Oracle Database 12c.  $$PLSQL_LINE zwraca liczbę całkowitą z bieżącym numerem wiersza z bloku kodu w języku PL/SQL.

170

Część I

Podstawy języka PL/SQL

Dyrektywę $$PLSQL_UNIT można przetestować w bloku anonimowym w wyniku porównania jej z pustym łańcuchem znaków lub wartością null. Poniższy fragment pokazuje, jak zastosować tę dyrektywę w bloku nazwanym: SQL> 2 3 4 5 6 7 8 9

CREATE OR REPLACE PROCEDURE running_procedure IS BEGIN -- Ilustruje stosowanie dyrektywy wbudowanej do sprawdzania wartości. IF $$PLSQL_UNIT IS NOT NULL THEN dbms_output.put_line( 'Wiersz numer ['||$$PLSQL_LINE||'] z ['||$$PLSQL_UNIT||'].'); END IF; END; /

Wiersz 4. sprawdza, czy wartość dyrektywy $$PLSQL_UNIT jest różna od null, a wiersze 5. i 6. wyświetlają wartości dyrektyw $$PLSQL_LINE i $$PLSQL_UNIT. Aby uruchomić tę procedurę, wywołaj poniższe polecenie: SQL> EXECUTE running_procedure;

Wyświetli ona następujące informacje: Wiersz numer [6] z [RUNNING_PROCEDURE].

Zauważ, że wywołanie funkcji PUT_LINE pakietu DBMS_OUTPUT rozpoczyna się w wierszu 5., jednak dyrektywa $$PLSQL_LINE zwraca numer wiersza, w którym się znajduje (choć jest parametrem wywołania funkcji rozpoczynającego się w wierszu 5.). Ostatni element kompilacji warunkowej to wbudowana dyrektywa błędu ($ERROR). Przyjmuje ona jeden łańcuch znaków o zmiennej długości (musi to być statyczny literał w postaci łańcucha znaków) i kończy się zarezerwowanym symbolem do sterowania preprocesorem, a nie średnikiem. Jeśli po łańcuchu znaków podasz średnik, program zgłosi wyjątek. Oto blok anonimowy ilustrujący stosowanie tej dyrektywy: SQL> BEGIN 2 NULL; -- Niezbędne, gdy zmienna sesji PLSQL_CCFLAGS nie jest ustawiona. 3 $ERROR 'Warunkowy błąd zdefiniowany przez użytkownika' $END 4 END; 5 /

Wiersz 3. zgłosi wyjątek niezależnie od tego, czy zmienna sesji PLSQL_CCFLAGS jest ustawiona, dlatego stosuj tę technikę ostrożnie. Kompilację warunkową najlepiej jest stosować do bloków nazwanych. Gdy w wersji produkcyjnej programu wystąpi błąd, pozwala ona włączyć debugowanie kodu bez konieczności refaktoryzowania go. Powtórzenie materiału W tym podrozdziale omówiono następujące zagadnienia związane z instrukcjami kompilacji warunkowej i nie tylko:  Język PL/SQL obsługuje instrukcje z pojedynczym rozgałęzianiem i wieloma rozgałęzieniami bez przechodzenia do dalszych warunków. W instrukcjach z wieloma rozgałęzieniami można stosować polecenia ELSIF i CASE.  W instrukcjach warunkowych można stosować iloczyn logiczny (AND) i sumę logiczną (OR).  W języku PL/SQL używana jest logika trójwartościowa z wartościami true, false i null.  Instrukcja NVL pozwala przekształcić logikę trójwartościową na logikę dwuwartościową w instrukcjach warunkowych.  Do zmiennych dynamicznych należy przypisywać wartości domyślne, jeśli wartość null może prowadzić do problemów w porównaniach.  Język PL/SQL udostępnia instrukcje CASE w wersjach prostej i z wyszukiwaniem.  W instrukcjach CASE z wyszukiwaniem używany jest selektor domyślny true, jednak można też przypisać do selektora wartość false.

Rozdział 5. Struktury sterujące

171

W ramach kompilacji warunkowej można stosować różne opcje, na przykład przedstawioną w tym rozdziale opcję $$DEBUG.  W ramach kompilacji warunkowej dostępne są dyrektywy wbudowane do sprawdzania wartości, na przykład $$PLSQL_LINE i $$PLSQL_UNIT. 

Instrukcje iteracyjne Instrukcje iteracyjne to bloki, które umożliwiają wielokrotne wykonywanie polecenia lub zbioru instrukcji. Są dwa rodzaje instrukcji iteracyjnych. W jednym z nich przed uruchomieniem powtarzanych instrukcji warunek sprawdzany jest na wejściu do pętli, w drugim — na wyjściu z niej. Wartość sterująca zakończeniem pracy pętli (niezależnie od tego, czy jest używana na wejściu, czy na wyjściu) jest czasem nazywana wartownikiem. Wartownikiem może być jedno lub kilka porównań, a także wartość (lub wartości) zmiennych logicznych albo wyrażeń zwracających wartość true lub nie true. Zauważ, że nie true to niekoniecznie false. Wynika to z tego, że w bazie używana jest logika trójwartościowa. Jeśli jest to dla Ciebie niezrozumiałe, wróć do omówienia logiki trójwartościowej z punktu „Instrukcje if-then-else” we wcześniejszej części rozdziału. W instrukcjach iteracyjnych sprawdzających warunek tylko na wyjściu blok kodu zawsze jest uruchamiany przynajmniej raz. Struktury tego typu to pętle typu powtarzaj dopóki. Rysunek 5.2 przedstawia sposób działania obu rodzajów instrukcji iteracyjnych.

Rysunek 5.2. Działanie instrukcji iteracyjnych

Język PL/SQL udostępnia pętle proste, FOR, FORALL i WHILE. Formalnie nie obsługuje pętli „powtarzaj dopóki”, można jednak zasymulować jej działanie przy użyciu pętli prostych. Pętla WHILE obsługuje tablice i listy, jednak w kontekście programowania baz danych częściej stosuje się ją razem z kursorami. Kursory to instrukcje SELECT przetwarzane wiersz po wierszu lub wsadowo. Ich opis znajduje się w pierwszym podrozdziale „Kursory” po opisie instrukcji iteracyjnych.

Pętle proste Pętle proste to struktury jawne. Rozpoczynają się słowem zarezerwowanym LOOP, a kończą słowami END LOOP. Programista musi zarządzać zarówno indeksem takich pętli, jak i warunkiem wyjścia. Zwykle pętli prostych używa się wtedy, gdy nie można zastosować łatwiejszych rozwiązań. Tym innym rozwiązaniem są zwykle popularne pętle FOR, które — za programistę — zarządzają indeksem i warunkami wyjścia. Są dwa prototypy pętli prostych. W jednej instrukcje EXIT znajdują się na początku, a w drugiej — na końcu pętli. Instrukcje EXIT są niezbędne, chyba że pętla ma działać w nieskończoność, co jest potrzebne rzadko. Kod wychodzi z pętli w wyniku testu, co — jak wyjaśniono dalej — przypomina

172

Część I

Podstawy języka PL/SQL

sprawdzanie biletów w teatrze lub na koncercie. Program sprawdza w tym celu wynik porównania, wyrażenie logiczne lub zmienną logiczną (o wartości true lub false). Pętla z warunkiem na wejściu blokuje wykonanie kodu, jeśli warunek nie jest spełniony (widz musi mieć więc ważny bilet). Aby zablokować wejście do pętli, trzeba na jej początku umieścić instrukcję warunkową. Takie instrukcje pełnią funkcję strażnika wejścia. Sprawdza on, czy wartość wartownika umożliwia wejście do pętli. Strażnik wejścia w programie robi jedną z dwóch rzeczy:  Umożliwia wielokrotne wykonywanie operacji w pętli (iterowanie) do czasu, gdy warunek na wejściu przestaje być spełniony.  Blokuje wykonanie operacji w pętli, jeśli widz nie ma ważnego biletu. Czasem programista chce uruchomić kod w pętli przynajmniej raz przed przejściem do dalszej części programu. Wtedy stosowanie warunku na wejściu to zły pomysł. W takiej sytuacji należy wykorzystać pętlę z warunkiem na wyjściu, działającą jak tradycyjna pętla „powtarzaj dopóki”. Pętla z warunkiem na wyjściu uruchamia swój kod przynajmniej raz. Przypomina to wpuszczanie do teatru wszystkich widzów (niezależnie od tego, czy mają bilet) i późniejsze wypraszanie tych, którzy nie mają biletu. Choć zastosowanie takiego podejścia w teatrze byłoby problematyczne, programy łatwo obsługują taką logikę. Pętla z warunkiem na wyjściu sprawdza wyniki porównania, wyrażenia logicznego lub zmiennej logicznej na końcu bloku z powtarzanymi instrukcjami. Jeśli warunek jest spełniony, kod można uruchomić ponownie. Sytuacja ta powtarza się do momentu, gdy warunek nie jest już spełniony. Kod sprawdzający, czy należy wejść do pętli lub wyjść z niej, to warunkowa instrukcja EXIT. Instrukcja EXIT powoduje natychmiastowe zatrzymanie wykonywania kodu i wyjście z pętli. Poniższe fragmenty przedstawiają tę technikę na podstawie działania warunków na wejściu i wyjściu pętli: Warunek na wejściu do pętli

Warunek na wyjściu z pętli

LOOP [instrukcje_zarządzania_licznikiem] IF NOT warunek_na_wejściu THEN EXIT; END IF; powtarzane_instrukcje END LOOP;

LOOP powtarzane_instrukcje [instrukcje_zarządzania_licznikiem] IF warunek_na_wyjściu THEN EXIT; END IF; END LOOP;

Instrukcja EXIT WHEN języka PL/SQL upraszcza pisanie instrukcji wyjścia. Dzięki niej nie trzeba dodawać instrukcji IF przy instrukcji EXIT. Jedną z wygodnych funkcji języka PL/SQL jest warunkowa instrukcja EXIT. Taka instrukcja pozwala skrócić blok IF do jednego wiersza kodu. Poniższe przykłady ilustrują zastosowanie tej techniki w warunkach na wejściu i wyjściu pętli: Warunek na wejściu do pętli

Warunek na wyjściu z pętli

LOOP [instrukcje_zarządzania_licznikiem;] EXIT WHEN NOT warunek_na_wejściu; powtarzane_instrukcje; END LOOP;

LOOP powtarzane_instrukcje; [instrukcje_zarządzania_licznikiem;] EXIT WHEN warunek_na_wyjściu; END LOOP;

Zwróć baczną uwagę na to, że instrukcje zarządzania licznikiem w pętlach z warunkiem na wejściu muszą znajdować się przed kodem zarządzającym wyjściem. Niektórzy programiści błędnie sądzą, że instrukcje zarządzania licznikiem należy umieszczać przed powtarzającym się kodem, ponieważ tak jest w pętlach z warunkiem wyjścia. W językach takich jak PL/SQL, obsługujących instrukcje GOTO lub CONTINUE, kod zarządzania licznikiem musi znajdować się w pętli przed kodem zarządzającym wyjściem. Dzieje się tak, ponieważ instrukcja CONTINUE zatrzymuje kod pętli i wraca do jej początku. Niepoprawnie zastosowane polecenie GOTO razem z etykietą mogą mieć ten sam efekt. Jeśli kod zarządzania licznikiem znajduje się po powtarzających się instrukcjach, zostanie pominięty przy każdym wywołaniu instrukcji CONTINUE, co może prowadzić do niepożądanego powstania pętli nieskończonej.

Rozdział 5. Struktury sterujące

173

Choć to podejście wydaje się proste, w trakcie pisania kodu łatwo jest popełnić pomyłkę. Wynika to z tego, że bazy danych działają według logiki trójwartościowej, a nie dwuwartościowej, dlatego trzeba uwzględnić także wartość null. Zwykle występuje ona w pętlach dynamicznych, a nie w statycznych. Przed wejściem do pętli dynamicznej należy zastosować logikę zabezpieczającą niezależnie od tego, czy warunek stosowany jest na wejściu, czy na wyjściu pętli.

Statyczne pętle proste Przyjrzyj się teraz blokom anonimowym, aby zobaczyć działanie pętli z warunkiem na wyjściu i z warunkiem na wejściu. Zauważ, że pętle proste języka PL/SQL można stosować zarówno w blokach anonimowych, jak i w pętlach prostych. Pętle proste można zidentyfikować na podstawie początkowych i końcowych słów zarezerwowanych — LOOP i END LOOP.

Pętle z warunkiem na wejściu Zastosowany tu warunek wejścia porównuje wartość zmiennej lv_counter i stałej liczbowej, aby sprawdzić, która z nich jest większa. Dopóki zmienna jest większa, program kontynuuje wykonywanie instrukcji z pętli. Poniższy program ilustruje najprostszą odmianę pętli z warunkiem na wejściu, ponieważ wiadomo, że oba operandy w porównaniu mają wartość różną od null. SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15

DECLARE lv_counter NUMBER := 1; BEGIN LOOP -- Zwiększanie zmiennej o jeden. lv_counter := lv_counter + 1; -- Warunek na wejściu (wartość wartownika to 3). IF NOT lv_counter < 3 THEN EXIT; END IF; -- Powtarzane instrukcje. dbms_output.put_line('Iteracja numer ['||lv_counter||']'); END LOOP; END; /

Ten kod blokuje ponowne wejście do pętli (następną iterację) po dwóch powtórzeniach pętli, dlatego wyświetla następujące dane: Iteracja numer [1] Iteracja numer [2]

Wiersz 2. to deklaracja zmiennej lv_counter o wartości początkowej 1. Większość kolekcji w bazie danych jest numerowana od 1, a nie od 0. To oznacza, że w trakcie przechodzenia po danych zwykle należy zaczynać od wartości 1. Wiersz 6. zmienia wartość licznika. Jest to pierwsza operacja, jaką należy wykonać w pętli. Wiersz 8. to warunek na wejściu. Jest to druga operacja, jaka musi znaleźć się w pętli. Warunek na wejściu blokuje wstęp wszystkim nieupoważnionym jednostkom. W tym przykładzie kod zmieniający wartość licznika nie musi znajdować się przed warunkiem, ponieważ działania pętli nie zakłócają instrukcje GOTO ani CONTINUE. Jeśli jednak w sekcji z powtarzanymi instrukcjami znajduje się polecenie CONTINUE, kod zarządzania licznikiem musi być umieszczony przed warunkiem. W pętli z warunkiem na wejściu kod zarządzania licznikiem najlepiej jest umieszczać zawsze na samym początku. W warunku na wejściu stosowana jest tu negacja, jeśli nie jest większa od 3, ponieważ jeśli ten warunek nie jest spełniony, należy wyjść z pętli. Można też napisać wersję bez negacji: 8 IF lv_counter < 3 THEN 9 NULL; -- Instrukcja, która nie wykonuje żadnych zadań. 10 ELSE 11 EXIT; 12 END IF;

174

Część I

Podstawy języka PL/SQL

Zamiast trzech wierszy kodu trzeba teraz użyć pięciu, ponieważ instrukcja EXIT znalazła się w bloku ELSE, a blok IF obejmuje polecenie NULL;, które nie wykonuje żadnych operacji. Ta zmiana sprawia też, że kod staje się mniej czytelny i wymaga za dużo pracy. Można jednak uprościć warunek na wejściu, stosując w wierszu 8. składnię EXIT WHEN z baz Oracle: 7 8

-- Warunek na wejściu z wartownikiem o wartości 3. EXIT WHEN NOT lv_counter < 3;

Instrukcja EXIT WHEN zajmuje jeden wiersz zamiast trzech potrzebnych w poleceniu if-then i pięciu używanych w bloku if-then-else. Nie trzeba wiele pracy, aby kod stał się krótszy i bardziej przejrzysty, a jednocześnie działał tak samo. Język PL/SQL, w odróżnieniu od języków C, C++, C# i Java, nie obsługuje operatorów jednoargumentowych do zarządzania licznikiem.

Omówiono tu podstawowe wartości statyczne wartownika w pętlach z warunkiem na wejściu. Pamiętaj, że taka pętla przed sprawdzeniem wartości wartownika nie wykonuje zawartego w niej kodu. Dalej opisano statyczne pętle z warunkiem na wyjściu.

Pętle z warunkiem na wyjściu Pętla z warunkiem na wyjściu umożliwia wykonanie kodu raz przed sprawdzeniem, czy należy uruchomić go ponownie. W innych językach programowania takie pętle mają postać pętli repeat-until lub do-while i stosuje się je częściej od pętli z warunkiem na wejściu. Pętle z warunkiem na wyjściu są popularne, ponieważ powtarzane w nich instrukcje zawsze wykonuje się przynajmniej raz (ale nigdy nie więcej razy, niż pozwala na to wartość wartownika), natomiast pętla z warunkiem na wejściu blokuje uruchamianie takich instrukcji, jeśli wartość wartownika nie jest zgodna z warunkiem. Mam nadzieję, że po poprzednim punkcie przekonałeś się do stosowania instrukcji EXIT WHEN. Tu jako warunek wyjścia stosowana jest tylko ta instrukcja. To oznacza, że w tym punkcie nie ma przykładów ilustrujących warunek wyjścia w postaci bloków if-then lub if-then-else. W poniższej pętli z warunkiem na wyjściu wykorzystano te same podstawowe elementy co w przedstawionej wcześniej pętli z warunkiem na wejściu: SQL> 2 3 4 5 6 7 8 9 10 11 12 13

DECLARE lv_counter NUMBER := 1; BEGIN LOOP -- Ten kod jest zawsze uruchamiany raz, a następnie liczbę powtórzeń zgodną z warunkiem. dbms_output.put_line('Iteracja numer ['||lv_counter||']'); -- Zmienna jest zwiększana o jeden przynajmniej raz. lv_counter := lv_counter + 1; -- Warunek wyjścia ze statyczną wartością wartownika równą 3. EXIT WHEN NOT lv_counter < 3; END LOOP; END; /

Podobnie jak w przykładzie z poprzedniego punktu zmienna lv_counter jest tu inicjowana przed rozpoczęciem pętli, co pozwala uniknąć problemów z wartością null. Warunek wyjścia jest ostatnią instrukcją w pętli (wiersz 10.) i blokuje nieuprawnione ponowne uruchomienie powtarzanych instrukcji, jeśli wartość zmiennej nie jest mniejsza niż wartość wartownika (3). Wartownik o wartości statycznej jest prosty i pomaga zrozumieć, jak działa wartownik. Następnym krokiem jest opanowanie wartowników o dynamicznej wartości.

Dynamiczne pętle proste Pętla prosta jest dynamiczna, jeśli w czasie kompilacji nie można zagwarantować wartości indeksu pętli ani wartownika. Kompilacja ma miejsce w momencie tworzenia lub zastępowania funkcji, procedur, specyfikacji pakietów i ciał pakietów oraz w wyniku ewolucji deklaracji i ciał typów obiektowych. Gdy wartość indeksu, wartość wartownika lub zbiór danych są generowane dynamicznie w czasie wykonywania programu, trzeba podjąć odpowiednie środki, aby zagwarantować integralność pętli.

Rozdział 5. Struktury sterujące

175

Wymaga to dodania na początku pętli zabezpieczenia, które przekształci wartości null na poprawne dane różne od null. Za pomocą procedury z poziomu schematu można zilustrować dynamiczne pętle z licznikiem rosnącym i malejącym. W pętlach z licznikiem rosnącym w trakcie przechodzenia po SQL-owej kolekcji z bazy Oracle pierwszy element ma indeks równy 1. W pętlach z licznikiem malejącym pozycja początkowa jest równa wartości zwróconej przez funkcję COUNT z interfejsu API Collection bazy Oracle wywołaną dla danej kolekcji. Omówienie tej funkcji znajdziesz w rozdziale 6. Przed utworzeniem procedury ilustrującej zabezpieczenie w pętli z warunkiem na wejściu należy utworzyć kolekcję SQL-a. Poniższy kod tworzy kolekcję ADT (listę elementów skalarnego typu danych) przechowującą łańcuchy znaków o długości 30 znaków (zobacz rozdział 3.): SQL> CREATE OR REPLACE 2 TYPE elf_table IS TABLE OF VARCHAR2(30); 3 /

Procedura ascending ma trzy parametry formalne. Parametr pv_index przyjmuje początkowy indeks pętli, parametr pv_sentinel przyjmuje wartość limitu pętli, a parametr pv_elves przyjmuje egzemplarz typu ELF_TABLE. Wszystkie te parametry w momencie wywołania procedury mogą mieć wartość null, dlatego trzeba się przed nią zabezpieczyć, aby chronić integralność pętli w procedurze i uniknąć błędów czasu wykonania. Poniższa procedura obejmuje zabezpieczenie przed pętlą oraz samą pętlę z warunkiem na wejściu: SQL> 2 3 4 5 6 7 8 9 10 11 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

CREATE OR REPLACE PROCEDURE ascending ( pv_index NUMBER , pv_sentinel NUMBER , pv_elves ELF_TABLE ) IS /* Deklaracja zmiennych lokalnych na indeks i wartownika. */ lv_counter NUMBER; lv_sentinel NUMBER; /* Deklaracja pustej listy o długości zero. */ lv_elves ELF_TABLE := elf_table(); BEGIN /* Przypisanie początkowej wartości indeksu. */ lv_counter := NVL(pv_index,1); /* Sprawdzanie, czy otrzymana lista zawiera elementy. */ IF pv_elves IS NOT EMPTY THEN /* Ustawianie wartości wartownika i przypisywanie listy do jej lokalnej kopii. */ lv_sentinel := NVL(pv_sentinel,pv_elves.COUNT); lv_elves := pv_elves; ELSE /* Ustawianie wartości wartownika. */ lv_sentinel := 1; END IF; /* Przechodzenie w pętli po elementach listy. */ LOOP /* Zwiększanie wartości licznika. */ lv_counter := lv_counter + 1; /* Warunek wyjścia. */ EXIT WHEN lv_counter > lv_sentinel; /* Powtarzane instrukcje. */ IF lv_elves.COUNT > 0 THEN dbms_output.put_line( '['||lv_counter||']['||lv_elves(lv_counter)||']'); END IF; END LOOP; END; /

176

Część I

Podstawy języka PL/SQL

Wiersz 14. zabezpiecza licznik pętli — przypisuje wartość 1, jeśli parametr wywołania jest równy null. Wiersze od 17. do 24. zabezpieczają wartość wartownika i lokalnej kolekcji. Wiersze od 35. do 38. wyświetlają elementy kolekcji (jeśli zawiera ona jakieś wartości). Wywołanie z samymi wartościami null nie wyświetla żadnych danych, jednak wywołanie z indeksem i wartownikiem równymi null oraz poprawną kolekcją, na przykład: EXECUTE ascending(null,null,elf_table('Celeborn','Galadriel','Legolas'));

wyświetla poniższe wartości, ponieważ zabezpieczenie zapewnia wartości domyślne licznika i wartownika: [1][Celeborn] [2][Galadriel] [3][Legolas]

Jest to praktyczny przykład zabezpieczania licznika, wartownika i kolekcji w standardowej pętli z warunkiem na wejściu i z rosnącymi wartościami licznika. Z powodu braku miejsca nie da się przedstawić tu kompletnych przykładów dla wszystkich możliwych sytuacji, jednak przedstawione rozwiązanie pozwala dodać zabezpieczenia także dla pętli z malejącymi wartościami licznika z warunkiem na wejściu i warunkiem na wyjściu.

Pomijanie iteracji Pomijanie iteracji w pętli jest możliwe od wielu wersji bazy Oracle. Aby uzyskać taki efekt, należało stosować instrukcję GOTO i pętlę. Choć nadal jest to możliwe, od wersji Oracle Database 11g dostępne są instrukcje CONTINUE i CONTINUE WITH. Polecenie CONTINUE różni się od GOTO, ponieważ nie kieruje programu do etykiety. Instrukcja CONTINUE zatrzymuje wykonywanie kodu w środku pętli i przechodzi do jej początku, gdzie zaczynana jest następna iteracja. Choć instrukcja CONTINUE (gdy spełnia wymagania programisty) jest wygodnym rozwiązaniem, to sprawia, że łatwo można przypadkowo utworzyć pętlę nieskończoną (zwykle jest to niepożądane). Pętlę nieskończoną czasem trudno wtedy wykryć, ponieważ kod przechodzi od początku pętli do instrukcji CONTINUE i pomija polecenia zwiększające lub zmniejszające licznik. Pominięcie takich poleceń sprawia, że kod nie może dojść do wartości wartownika, przez co powstaje pętla nieskończona. Trzeba się zabezpieczyć przed przypadkowymi pętlami nieskończonymi. W tym celu należy pamiętać o kolejności kodu zarządzania licznikiem, powtarzanych instrukcji i warunku wyjścia w pętlach z warunkiem na wyjściu. W pętlach z warunkiem na wejściu kolejność tych komponentów należy odwrócić. Poniższy blok anonimowy ilustruje, jak uniknąć pętli nieskończonej przy stosowaniu instrukcji CONTINUE w prostej pętli z warunkiem na wejściu: SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

DECLARE lv_counter NUMBER := 0; BEGIN LOOP -- Zarządzanie licznikiem. lv_counter := lv_counter + 1; -- Warunek na wejściu. EXIT WHEN lv_counter > 5; -- Powtarzane instrukcje; dla liczb parzystych wywoływane jest polecenie CONTINUE. IF MOD(lv_counter,2) = 0 THEN CONTINUE; ELSE dbms_output.put_line('Licznik ['||lv_counter||'].'); END IF; END LOOP; END; /

Ta wersja programu przed dojściem do wartości wartownika (5) wyświetla tylko nieparzyste wartości licznika. Dzieje się tak, ponieważ instrukcja CONTINUE nakazuje programowi pominąć część powtarzanych instrukcji, jeśli spełniony jest użyty warunek.

Rozdział 5. Struktury sterujące

177

MOD to funkcja wbudowana SQL-a omówiona w dodatku C.

Można uprościć ten kod. W tym celu należy zastąpić połączenie bloku IF i instrukcji CONTINUE poleceniem CONTINUE WHEN. Poniższy fragment pokazuje, jak zastąpić rozpoczynającą się w wierszu 12. instrukcję if-then-else: 11 -- Powtarzane instrukcje; dla liczb parzystych wywoływane jest polecenie CONTINUE. 12 CONTINUE WHEN MOD(lv_counter,2) = 0;

Instrukcja wyświetlająca dane wcześniej znajdowała się w bloku ELSE. Zastosowanie instrukcji CONTINUE WHEN pozwala pominąć blok IF.

Obie wersje programu po pięciu iteracjach pętli wyświetlają w konsoli następujące informacje: Licznik [1] Licznik [3] Licznik [5]

Pętla prosta jest dużo bardziej wartościowa, jeśli używa się jej razem z kursorami. Omówienie tego rozwiązania znalazło się w podrozdziale „Kursory” w dalszej części rozdziału.

Pętle FOR FOR to ulubiona pętla wielu programistów, ponieważ daje duże możliwości i jest łatwa w użyciu. Pętle

tego rodzaju zarządzają — za programistę — indeksem i warunkiem wyjścia pętli, ponieważ operacje te są określane w definicji tej instrukcji. Są dwa rodzaje pętli FOR: z indeksami w postaci przedziałów i kursorów. Opis pętli FOR z kursorami znajdziesz w dalszej części rozdziału, w kontekście omówienia kursorów.

Pętle FOR oparte na przedziale Pętle FOR oparte na przedziale to doskonałe rozwiązanie, jeśli znane są punkty początkowy i końcowy przedziału oraz można je wyrazić za pomocą liczb całkowitych. Pętli FOR można używać także do poruszania się po tablicach VARRAY, tabelach zagnieżdżonych i tablicach asocjacyjnych z indeksem liczbowym. Do poruszania się po tablicach asocjacyjnych z indeksami w postaci łańcuchów znaków lepiej jest używać pętli WHILE. Prototyp pętli FOR opartej na przedziale wygląda następująco: FOR indeks_przedziału IN [REVERSE] minimum_prze..maksimum_prze LOOP powtarzane_instrukcje END LOOP;

Za indeks_przedziału można podstawić dowolny identyfikator. Podobnie jak przy tworzeniu pętli FOR w innych językach, wielu programistów używa jako indeksu zmiennej o nazwie i. W pętlach zagnieżdżonych są to nazwy j, k, l i tak dalej. Indeksy w pętlach FOR opartych na przedziale mają typ PLS_INTEGER. Wartość początkową określa minimum przedziału (po lewej stronie dwóch kropek), a końcową — jego maksimum (po prawej stronie dwóch kropek). Jeśli zastosujesz słowo kluczowe REVERSE, pętla FOR będzie zmniejszać wartości od maksymalnej do minimalnej. Nie możesz jednak zmienić kolejności wartości przy dwóch kropkach, jeśli nie chcesz, aby program pominął wykonywanie wewnętrznego kodu pętli. Zmiana wartości zawsze wynosi 1 i nie można tego zmienić. Poniższy blok anonimowy zawiera pętlę FOR z rosnącym licznikiem: SQL> BEGIN 2 FOR i IN 1..3 LOOP 3 dbms_output.put_line('Iteracja ['||i||']'); 4 END LOOP; 5 END; 6 /

Ten kod wyświetli następujące dane wyjściowe:

178

Część I

Podstawy języka PL/SQL

Iteracja [1] Iteracja [2] Iteracja [3]

Wartość zmiennej indeksu jest wyświetlana w nawiasach kwadratowych. Warto zauważyć, że ograniczenia przedziału są włączające, a nie wyłączające. W przedziale wyłączającym wartości 1 i 3 nie zostałyby uwzględnione. Aby zastosować słowo kluczowe REVERSE, należy zmodyfikować program. Zmień wiersz 2. w następujący sposób: 2

FOR i IN REVERSE 1..3 LOOP

Jeśli zastosujesz słowo kluczowe REVERSE, program będzie zmniejszał wartości w ustawionym przedziale i wyświetli następujące informacje: Iteracja [3] Iteracja [2] Iteracja [1]

W tym kodzie nie ma instrukcji EXIT, ponieważ jest ona zbędna. Instrukcja EXIT jest niejawnie umieszczana na początku pętli. Program sprawdza, czy indeks ma wartość mniejszą od maksimum przedziału, a jeśli warunek ten nie jest spełniony, kończy działanie pętli. Jeśli zmienisz kolejność minimalnej i maksymalnej wartości przedziału w wierszu 2.: 2

FOR i IN REVERSE 3..1 LOOP

pętla zakończy pracę przed wykonaniem jakichkolwiek instrukcji, ponieważ 3 nie jest mniejsze od 1. Wypróbuj taki kod, aby zrozumieć logikę działania pętli FOR opartej na przedziałach.

Pętle WHILE Pętle WHILE to struktury jawne podobne do pętli prostych. Pętla WHILE zaczyna się od warunku na wejściu. Pętle WHILE wymagają zarządzania jednym warunkiem (zazwyczaj) lub kilkoma warunkami. WHILE to pętla z warunkiem na wejściu i można pominąć w niej indeks — na przykład wtedy, gdy w warunku używane są dane z kolekcji lub zwrócony wiersz. W pętlach WHILE sprawdzana jest wartość true, którą można ustalić na wiele przedstawionych w przykładach sposobów. Prototyp pętli WHILE wygląda następująco: WHILE { TRUE | NOT FALSE | { warunek | warunek | ... }} LOOP powtarzane_instrukcje [ instrukcje_zarządzania_licznikiem ] END LOOP;

Pętla z warunkiem na wejściu może zablokować wejście, gdy warunek nie ma wartości true, a pętla wymaga takiej wartości (wersja WHILE true). Podobnie jeśli w pętli używana jest negacja za pomocą operatora NOT (wersja WHILE NOT false), warunek blokuje wejście do pętli, jeśli ma wartość różną od false. Jeśli wygląda to znajomo, to dobrze, ponieważ podobnie działają instrukcje warunkowe i logika trójwartościowa. Niespełniony warunek może być wynikiem tego, że porównanie, wyrażenie logiczne lub wartość logiczna zwraca null. To dlatego przed wejściem do pętli WHILE trzeba dodać zabezpieczenie. Bez niego może się zdarzyć, że program nigdy nie wejdzie do pętli, ponieważ wartość null jest różna od true i false. Zabezpieczenie polega na przypisywaniu do zmiennej wartości domyślnej true lub false, gdy ta zmienna w czasie wykonywania programu jest równa null. Następny przykład przedstawia pętlę WHILE z porównaniem w warunku. W tej pętli używany jest licznik oraz wartownik w postaci literału liczbowego. Dopóki licznik ma wartość mniejszą od wartownika (w modelu z rosnącą wartością licznika), pętla wykonuje powtarzane instrukcje. W modelu z malejącą wartością licznika instrukcje są powtarzane dopóty, dopóki licznik jest większy od wartości wartownika. Poniższy kod ilustruje tradycyjną pętlę z rosnącą wartością licznika: SQL> DECLARE 2 lv_counter NUMBER := 1; 3 BEGIN 4 WHILE (lv_counter < 3) LOOP

Rozdział 5. Struktury sterujące 5 6 7 8 9

179

dbms_output.put_line('Licznik ['||lv_counter||'].'); lv_counter := lv_counter + 1; END LOOP; END; /

Wiersz 2. zawiera deklarację wartości licznika, dzięki czemu przed pętlą WHILE nie trzeba dodawać zabezpieczenia. Wiersz 4. porównuje licznik z wartownikiem, a wiersz 6. zwiększa wartość licznika. Aby uwzględnić ten sam przedział liczb w modelu z malejącą wartością licznika, należy w wierszu 2. przypisać do zmiennej lv_counter wartość 3 i w wierszu 4. zapisać warunek wyjścia w następujący sposób: 4

WHILE (lv_counter > 1) LOOP

Ten program wyświetli następujące dane wyjściowe: Licznik [1]. Licznik [2].

Pętla WHILE działa podobnie jak pętla prosta z warunkiem na wejściu. Różnica polega na tym, że nie da się zastosować ogólnego licznika na początku pętli, jeśli jego wartość początkowa jest równa pierwszej wartości indeksu. To oznacza, że stosowanie instrukcji CONTINUE i CONTINUE WHEN w pętli WHILE staje się skomplikowane, chyba że kod zarządzania licznikiem będzie pierwszą instrukcją w pętli. Oto nieelegancka wersja pętli WHILE opartej na liczniku: SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

DECLARE /* Inicjowanie początkową wartością przedziału. */ lv_counter NUMBER := 1; BEGIN WHILE (lv_counter < 6) LOOP /* True dla wszystkich liczb parzystych – wyświetlanie liczb nieparzystych. */ IF MOD(lv_counter,2) = 0 THEN /* Licznik trzeba zwiększać tutaj, aby uniknąć pętli nieskończonej w sytuacji, gdy warunek dla instrukcji CONTINUE jest spełniony. */ lv_counter := lv_counter + 1; CONTINUE; ELSE /* Zawiera wszystkie powtarzane instrukcje. */ dbms_output.put_line('Licznik ['||lv_counter||'].'); /* Zwiększanie wartości we wszystkich iteracjach, w których warunek dla instrukcji CONTINUE nie jest spełniony. */ lv_counter := lv_counter + 1; END IF; END LOOP; END; /

Kod zarządzania licznikiem znajduje się w wierszach 10. i 16. Powinien jednak występować tylko raz i to na początku pętli WHILE. Instrukcja IF powoduje rozgałęzienie programu w wierszu 7. Kod pomija numery parzyste i wyświetla numery nieparzyste. Kod zarządzania licznikiem w wierszu 10. jest wykonywany tylko dla liczb parzystych i musi być uruchamiany przed instrukcją CONTINUE (w przeciwnym razie powstanie pętla nieskończona). Blok ELSE zwiększa wartość licznika w sytuacji, gdy liczba jest nieparzysta. Występowanie dwóch instrukcji zwiększających licznik w jednej pętli sprawia, że przedstawiony program nie jest dobrym rozwiązaniem. Ponadto kod zarządzania licznikiem powinien być pierwszą instrukcją w pętli. Tu jest inaczej, ponieważ wartość indeksu rozpoczyna się od pierwszej wartości indeksu gęstego. Tego rodzaju indeksy to zwykle kolejne liczby całkowite. Można zastąpić nieelegancki kod kiepskim (nieoptymalnym) w wyniku wprowadzenia dwóch zmian. Zacznij licznik pętli od 0 lub od wartości o jeden mniejszej od początkowej wartości licznika. Ponadto umieść kod zarządzania licznikiem na początku pętli. Poniższy program obejmuje te dwie zmiany: SQL> DECLARE 2 /* Inicjowanie wartością o jeden mniejszą od początku przedziału. */ 3 lv_counter NUMBER := 0; 4 BEGIN

180 5 6 7 8 9 10 11 12 13 14 15 16 17 18

Część I

Podstawy języka PL/SQL

WHILE (lv_counter < 6) LOOP /* Należy zwiększać licznik w tym miejscu, aby uniknąć pętli nieskończonej po wywołaniu instrukcji CONTINUE. */ lv_counter := lv_counter + 1; /* Równe true dla wszystkich liczb parzystych. Wyświetlane są tylko nieparzyste. */ IF MOD(lv_counter,2) = 0 THEN CONTINUE; ELSE /* Zawiera wszystkie instrukcje związane z wyświetlaniem liczb. */ dbms_output.put_line('Licznik ['||lv_counter||'].'); END IF; END LOOP; END; /

Wiersz 3. ustawia wartość zmiennej lv_counter na 0, czyli o jeden mniejszą od pierwszej wartości licznika. Dzięki tej zmianie można umieścić kod zarządzania licznikiem na początku pętli. Takie rozwiązanie często stosuje się przy korzystaniu z kolekcji w postaci tablic VARRAY i tabel zagnieżdżonych, ponieważ ich indeksy zawsze zaczynają się od 1. Teraz kod zarządzania licznikiem znajduje się w jednym miejscu, w wierszu 8. Instrukcja CONTINUE z wiersza 12. nakazuje pętli przejście na początek i sprawdzenie następnej wartości licznika, po czym jest on natychmiast zwiększany. Problem polega na tym, że występuje tu niepotrzebna instrukcja IF. Można ją zastąpić za pomocą polecenia CONTINUE WHEN, zastosowanego w poniższej, dobrej wersji programu: SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

DECLARE /* Inicjowanie wartością o jeden mniejszą od początku przedziału. */ lv_counter NUMBER := 0; BEGIN WHILE (lv_counter < 6) LOOP /* Należy zwiększać licznik w tym miejscu, aby uniknąć pętli nieskończonej po wywołaniu instrukcji CONTINUE. */ lv_counter := lv_counter + 1; /* Wywołanie CONTINUE dla liczb parzystych.. */ CONTINUE WHEN MOD(lv_counter,2) = 0; /* Zawiera wszystkie instrukcje związane z wyświetlaniem liczb. */ dbms_output.put_line('Licznik ['||lv_counter||'].'); END LOOP; END; /

Licznik jest w wierszu 3. inicjowany wartością o jeden mniejszą od początku przedziału. Kod zarządzania licznikiem występuje w tylko jednym miejscu pętli, w wierszu 8. Instrukcja CONTINUE WHEN powoduje pomijanie wybranych iteracji pętli, a do wiersza 14. program dochodzi tylko wtedy, gdy licznik zawiera wartość nieparzystą. Wersje dobra, zła i nieelegancka przykładowego programu z pętlą WHILE wyświetlają tylko następujące liczby nieparzyste: Licznik [1]. Licznik [3]. Licznik [5].

Choć używanie w pętli WHILE instrukcji CONTINUE działa poprawnie, istnieje też inna technika, oparta na instrukcji GOTO i etykietach. Dzięki tym dwóm elementom można uniknąć implementowania i konserwowania kodu zarządzania licznikiem w dwóch miejscach. Instrukcja GOTO wymaga jednak umieszczenia licznika na końcu pętli. Następny program ilustruje zastosowanie instrukcji GOTO i etykiety w nowej wersji wcześniejszego programu. Tym razem używana jest pętla z malejącymi wartościami licznika. SQL> DECLARE 2 lv_counter NUMBER := 6; 3 BEGIN

Rozdział 5. Struktury sterujące 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

181

WHILE (lv_counter > 0) LOOP /* Warunek jest spełniony dla wszystkich liczb parzystych. */ IF MOD(lv_counter,2) = 0 THEN /* Trzeba przejść do kodu zarządzania licznikiem, aby uniknąć powstania pętli nieskończonej. */ GOTO decrement_index; ELSE /* Obejmuje wszystkie powtarzane instrukcje. */ dbms_output.put_line('Licznik ['||lv_counter||'].'); END IF; > /* Zmniejszanie wartości licznika we wszystkich iteracjach. */ lv_counter := lv_counter - 1; END LOOP; END; /

Wiersz 6. sprawdza, czy liczba jest parzysta. Jeśli ta instrukcja IF ma wartość true, sterowanie przechodzi do etykiety decrement_index. Znajduje się ona w wierszu 14., gdzie licznik jest zmniejszany. Jak widać, pętla WHILE jest przydatna, jeśli chcesz stosować warunek na wejściu. Wadą tej pętli jest to, że może utrudniać pomijanie wykonywania kodu za pomocą instrukcji CONTINUE lub CONTINUE WITH, jeśli programista dobrze nie rozumie opisanego podejścia lub samego kodu. Instrukcje CONTINUE i CONTINUE WHEN eliminują konieczność stosowania instrukcji GOTO. Należy unikać korzystania z instrukcji GOTO i etykiet. Powtórzenie materiału W tym podrozdziale omówiono następujące zagadnienia dotyczące instrukcji iteracyjnych:  Instrukcje iteracyjne to bloki umożliwiające powtarzanie pojedynczych instrukcji lub ich zbiorów.  Instrukcje iteracyjne obejmują warunek wyjścia i blok powtarzających się instrukcji.  Warunek wyjścia porównuje licznik pętli lub zmienną z wartością wartownika, aby określić, kiedy należy wyjść z pętli.  Warunki na wejściu i na wyjściu pozwalają zablokować wejście do pętli lub wyjście z niej. Te warunki można powiązać z licznikiem i wartownikiem.  Aby uniknąć błędów czasu wykonania, programy należy zabezpieczać przed dynamicznie ustawianymi wartościami zmiennych. W tym celu przed użyciem zmiennej w programie należy sprawdzić, czy jej wartość jest różna od null. Jest to konieczne, ponieważ wartość null nie działa poprawnie jako licznik (nie można jej zwiększyć) ani w porównaniach (nie można jej porównać).  Instrukcje CONTINUE, CONTINUE WHEN i GOTO pozwalają pominąć część iteracji w pętli. W pętlach z warunkiem na wejściu te instrukcje wymagają specjalnej obsługi; w przeciwnym razie może powstać pętla nieskończona.  Pętla FOR oparta na przedziale umożliwia przechodzenie po danych w kolejności rosnącej lub malejącej. Te pętle ukrywają skomplikowane mechanizmy związane z warunkiem na wejściu.  WHILE to pętla z warunkiem na wejściu. Przy jej stosowaniu trzeba dobrze zadbać o to, aby uniknąć powstania pętli nieskończonej przy pomijaniu fragmentów kodu.

Kursory Struktury kursorowe to wyniki zwracane przez instrukcję SELECT języka SQL. W języku PL/SQL można przetwarzać instrukcje SELECT wiersz po wierszu lub przy użyciu instrukcji masowych. W tym podrozdziale opisane są kursory służące do przetwarzania danych wiersz po wierszu. Są dwa rodzaje kursorów: niejawne i jawne. Aby utworzyć kursor jawny, należy zdefiniować go w sekcji deklaracji. Kursory niejawne powstają w wyniku wywołania instrukcji SELECT z klauzulami INTO lub BULK COLLECT INTO, a także przy umieszczaniu instrukcji SELECT wewnątrz pętli FOR opartych na kursorze. Także instrukcje DML w sekcjach wykonawczych i obsługi wyjątków to kursory niejawne. Te instrukcje to: INSERT, UPDATE, DELETE i MERGE.

182

Część I

Podstawy języka PL/SQL

W tym podrozdziale opisane są osobno kursory niejawne i jawne. Pierwszy punkt zawiera omówienie kursorów niejawnych, a drugi — jawnych. W ostatnim podrozdziale omówiono przetwarzanie masowe, do którego wprowadzenie znajdziesz w rozdziale 3.

Kursory niejawne Kursorem niejawnym jest każda instrukcja SQL w bloku języka PL/SQL. Po wykonaniu instrukcji DML (ang. Data Manipulation Language, czyli język manipulowania danymi) można sprawdzić, ile wierszy zostało zmienionych, używając atrybutu %ROWCOUNT. Instrukcje DML to: INSERT, UPDATE, DELETE i MERGE. Można też określić liczbę wierszy zwróconych przez instrukcję (zapytanie) SELECT (niezależnie od tego, czy wiersze są blokowane za pomocą klauzuli FOR UPDATE). Poniższy fragment ilustruje działanie atrybutu %ROWCOUNT kursorów. Użyto tu jednowierszowego kursora niejawnego i pseudotabeli DUAL: SQL> 2 3 4 5 6 7 8

DECLARE lv_number NUMBER; BEGIN SELECT 1 INTO lv_number FROM dual; dbms_output.put_line('Pobrano ['||SQL%ROWCOUNT||']'); END; /

Słowo zarezerwowane SQL przed atrybutem %ROWCOUNT (wiersz 6.) oznacza kursor niejawny. Za zarządzanie kursorami odpowiada język PL/SQL, a programista może uzyskać do nich dostęp tylko poprzez atrybuty. Dostępne atrybuty kursorów niejawnych przedstawia tabela 5.6. Tabela 5.6. Atrybuty kursorów niejawnych Atrybut

Opis

%FOUND

Ten atrybut zwraca TRUE, jeśli instrukcja DML zmieniła wiersz.

%ISOPEN

W kursorach niejawnych atrybut ten zawsze zwraca FALSE.

%NOTFOUND

Ten atrybut zwraca TRUE, jeśli instrukcja DML nie zmieniła wiersza.

%ROWCOUNT

Ten atrybut zwraca liczbę wierszy zmienionych przez instrukcję DML lub zwróconych przez instrukcję SELECT INTO.

Istnieje pięć rodzajów kursorów niejawnych. Jeden z nich to kursory używane w masowych operacjach na kolekcjach, opisane w podrozdziale „Instrukcje masowe” w dalszej części rozdziału. Cztery pozostałe typy zostały opisane w tym punkcie. Dwa pierwsze to jedno- i wielowierszowe kursory niejawne oparte na instrukcji SELECT. Dwa kolejne to statyczne i dynamiczne kursory niejawne używane w pętlach FOR.

Jednowierszowe kursory niejawne Instrukcja SELECT INTO jest obecna we wszystkich kursorach niejawnych służących do pobierania danych poza pętlami. Działa tylko wtedy, gdy instrukcja SELECT zwraca jeden wiersz. W klauzuli SELECT można wybrać kolumnę lub ich listę i przypisać kolumny wiersza do poszczególnych zmiennych lub — jako całość — do typu rekordowego. Prototyp jednowierszowego kursora niejawnego pozbawiony standardowych klauzul WHERE, HAVING, GROUP BY i ORDER BY języka SQL wygląda następująco: SELECT kolumna [, kolumna [, ... ]] INTO zmienna [, zmienna [, ... ]] FROM nazwa_tabeli;

Oba przykładowe programy przedstawione w tym punkcie używają tabeli ITEM, która zawiera dane wygenerowane przez kod, który można pobrać z witryny wydawnictwa Helion. Pierwszy program przypisuje wartości kolumn pojedynczym zmiennym skalarnym:

Rozdział 5. Struktury sterujące SQL> 2 3 4 5 6 7 8 9 10 11 12

183

DECLARE id item.item_id%TYPE; title item.item_title%TYPE; subtitle item.item_subtitle%TYPE; BEGIN SELECT item_id, item_title, item_subtitle INTO id, title, subtitle FROM item WHERE ROWNUM < 2; dbms_output.put_line('Pobrano ['||title||']'); END; /

Ten przykładowy program zakotwicza typy wszystkich zmiennych do typów kolumn tabeli (wiersze od 2. do 4.) i ogranicza zapytanie do jednego wiersza przy użyciu operatora nierówności i pseudokolumny ROWNUM języka Oracle SQL. Powyższy kod wyświetli jeden wiersz: Pobrano [Around the World in 80 Days]

Przypisywanie wartości do poszczególnych zmiennych jest bardzo pracochłonne, a także zwiększa koszty konserwacji kodu. Częściej stosowane rozwiązanie polega na przypisaniu wartości wszystkich kolumn do rekordowych typów danych. Można też bezpośrednio powiązać typ danych z definicją tabeli. To drugie podejście należy stosować tylko wtedy, gdy program ma pobierać dane z wszystkich kolumn tabeli. Niestety, tu potrzebne są tylko trzy użyte wcześniej kolumny. Oto przykład, w którym zastosowano rekordowy typ danych: SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14

DECLARE TYPE item_record IS RECORD ( id item.item_id%TYPE , title item.item_title%TYPE , subtitle item.item_subtitle%TYPE lv_record ITEM_RECORD; BEGIN SELECT item_id, item_title, item_subtitle INTO lv_record FROM item WHERE rownum < 2; dbms_output.put_line('Pobrano ['||lv_record.title||']'); END; /

Choć typy rekordowe wymagają jawnej konstrukcji, typy kolumn wchodzących w skład struktury można zakotwiczyć do typów kolumn bazy danych (wiersze od 3. do 5.). W tych wierszach tabela item jest łączona za pomocą selektora komponentów (.) z kolumnami, a te wyznaczają używane typy danych. Zmienna lv_record w wierszu 6. ma lokalny typ danych item_record. Jednowierszowe kursory niejawne pozwalają szybko tworzyć rozwiązania, jednak mają pewną wadę. Wielu programistów próbuje poradzić sobie z tym problemem, przechwytując wyjątki, jeśli kursor zwróci więcej niż jeden wiersz. Robią to, ponieważ jeśli jednowierszowy kursor niejawny zwróci więcej wierszy, zgłasza błąd ORA-01422, informujący o tym, że precyzyjnie określona operacja pobierania zwróciła za dużo wierszy. Lepsze rozwiązania polegają na wykryciu błędów przed pobraniem danych. W czasie pisania kodu trzeba rozważyć różne możliwości, a jeśli to możliwe, bezpośrednio obsługiwać błędy. Zwykle lepiej jest użyć kursorów jawnych.

Wielowierszowe kursory niejawne Wielowierszowe kursory niejawne można tworzyć na dwa sposoby:  Przy użyciu instrukcji DML w bloku języka PL/SQL. Instrukcje DML to wielowierszowe kursory niejawne, choć można je ograniczyć do jednego wiersza.  Za pomocą zapytania zagnieżdżonego w pętli FOR opartej na kursorze zamiast definiowania go w sekcji deklaracji. Te zapytania to instrukcje SELECT mające wielką zaletę — wszystkie zmienne są niejawnie udostępniane w zasięgu danej pętli FOR opartej na kursorze.

184

Część I

Podstawy języka PL/SQL

W poniższym zapytaniu użyto kursora niejawnego utworzonego za pomocą instrukcji DML: SQL> BEGIN 2 UPDATE system_user 3 SET last_update_date = SYSDATE; 4 IF SQL%FOUND THEN 5 dbms_output.put_line('Zaktualizowano ['||SQL%ROWCOUNT||']'); 6 ELSE 7 dbms_output.put_line('Brak aktualizacji.'); 8 END IF; 9 END; 10 /

Zgodnie z opisem z tabeli 5.6 atrybut %FOUND kursorów niejawnych zwraca wartość logiczną TRUE, jeśli program zaktualizował wiersze danych. Przedstawiona instrukcja powinna zaktualizować pięć wierszy, dlatego kod wyświetli wartość atrybutu SQL%ROWCOUNT: Zaktualizowano [5]

Pętle FOR oparte na kursorze Pętle FOR oparte na kursorze doskonale nadają się do obsługi zapytań kierowanych do tabel i widoków baz danych, ponieważ są proste i zarządzają za programistę wieloma czynnikami. Choć zwykle nie wiadomo, ile wierszy zwróci kursor (zapytanie), pętla FOR zarządza otwieraniem i zamykaniem kursora, pobieraniem rekordów i wychodzeniem z pętli po wczytaniu wszystkich danych. W przykładach z tego punktu używane są statyczne i dynamiczne kursory niejawne. W następnym punkcie, „Kursory jawne”, opisane jest używanie kursorów jawnych. Kursory jawne definiuje się jako formalną strukturę kursorową w sekcji deklaracji. Generalnie zaleca się stosowanie kursorów jawnych. Korzystanie z kursorów niejawnych jest uważane za uproszczone rozwiązanie, którego należy unikać w kodzie produkcyjnym. Statyczne i dynamiczne kursory niejawne to instrukcje SELECT zdefiniowane w nawiasach w pętli FOR opartej na kursorze. W odróżnieniu od kursorów jawnych nie mają formalnej listy parametrów. W dynamicznych kursorach niejawnych dostęp do danych w zasięgu lokalnym zapewniany jest w wyniku umieszczenia zmiennych lokalnych w instrukcji SELECT. Prototyp pętli FOR opartych na kursorze wygląda następująco: FOR indeks_kursora IN [nazwa_kursora[(argumenty)] | (instrukcja_select)] LOOP powtarzane_instrukcje; END LOOP;

Indeks_kursora to dowolny identyfikator. Podobnie jak przy tworzeniu pętli FOR w innych językach, wielu programistów używa jako indeksu zmiennej o nazwie i. W pętlach zagnieżdżonych są to nazwy j, k, l i tak dalej (przy czym możesz zastosować także dowolne inne nazwy). Indeks w pętlach FOR opartych na kursorze to wskaźnik na zbiór wyników w obszarze roboczym zapytania. Jak wyjaśniono w rozdziale 4., jest to obszar pamięci (nazywany obszarem kontekstowym) w obszarze PGA (ang. Process Global Area) bazy danych Oracle 12c. Obszar roboczy zapytania przechowuje informacje dotyczące zapytania. Znajdują się tu wiersze zwrócone przez zapytanie, liczba przetworzonych wierszy i wskaźnik do sparsowanego zapytania. Obszar roboczy zapytania znajduje się w puli współużytkowanej Oracle (zobacz dodatek A).

Statyczne kursory niejawne W tym punkcie opisane jest stosowanie statycznych kursorów niejawnych w pętli FOR. Kursor statyczny składa się ze słów kluczowych SQL-a, nazw tabel i kolumn oraz literałów liczbowych lub znakowych. Zamiast statycznego kursora niejawnego można wykorzystać dynamiczny kursor niejawny. Różnica między nimi polega na tym, że dynamiczny kursor niejawny obejmuje nazwy zmiennych dostępnych w zasięgu lokalnym. Aby poniższy kod zadziałał, trzeba najpierw uruchomić skrypt generujący dane, opisany we wprowadzeniu. Przedstawiony program zwraca tytuły filmów o Harrym Potterze z przykładowej bazy danych wypożyczalni wideo.

Rozdział 5. Struktury sterujące

185

SQL> BEGIN 2 FOR i IN (SELECT COUNT(*) AS on_hand 3 , item_title AS title 4 , item_rating AS rating 5 FROM item 6 WHERE item_title LIKE 'Harry Potter%' 7 AND item_rating_agency = 'MPAA' 8 GROUP BY item_title 9 , item_rating) LOOP 10 dbms_output.put( 11 '('||i.on_hand||') '||i.title||' ['||i.rating||']'); 12 END LOOP; 13 END; 14 /

Indeks kursora wskazuje wiersz, a selektor komponentu (.) łączy wskaźnik wiersza z nazwą kolumny (aliasem) określoną w kursorze niejawnym. Kod ten wyświetli następujące pozycje z bazy danych: (3) (3) (3) (2) (1)

Harry Harry Harry Harry Harry

Potter Potter Potter Potter Potter

and and and and and

the the the the the

Sorcerer's Stone [PG] Goblet of Fire [PG-13] Chamber of Secrets [PG] Prisoner of Azkaban [PG] Order of the Phoenix [PG-13]

W przedstawionym kodzie nie ma instrukcji EXIT, ponieważ nie jest potrzebna. Instrukcja ta jest niejawnie umieszczana na początku pętli (zaraz po liczniku). Licznik w pętli opartej na kursorze sprawdza, czy dostępny jest nowy wiersz. Warunek wyjścia sprawdza, czy wszystkie wiersze zostały wczytane. Jeśli nie ma już więcej wierszy, następuje wyjście z pętli.

Dynamiczne kursory niejawne Jak wcześniej wspomniano, różnica między dynamicznymi i statycznymi kursorami niejawnymi polega na tym, że w dynamicznych znajdują się zmienne o zasięgu lokalnym. Te zmienne działają jak miejsca na dane i w czasie wykonywania programu są zastępowane wartościami zmiennych lokalnych. Po wprowadzeniu niewielkich zmian można przekształcić przykład ze statycznym kursorem niejawnym na kod z dynamicznym kursorem niejawnym. Poniższy program zawiera dodatkowo sekcję deklaracji ze zmienną lokalną, a także obejmuje zmienną pełniącą funkcję miejsca na dane w instrukcji SELECT: SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

DECLARE lv_item_title VARCHAR2(60) := 'Harry Potter'; BEGIN FOR i IN (SELECT COUNT(*) AS on_hand , item_title AS title , item_rating AS rating FROM item WHERE item_title LIKE lv_item_title||'%' AND item_rating_agency = 'MPAA' GROUP BY item_title , item_rating) LOOP dbms_output.put_line( i.on_hand||' '||i.title||' Kategoria: '||i.rating); END LOOP; END; /

W wierszu 2. dodana jest lokalna zmienna lv_item_title o wartości Harry Potter. W wierszu 8. ta zmienna jest używana. Zmienna w kursorze niejawnym sprawia, że kursor jest dynamiczny, a nie statyczny (choć akurat w tym programie ta zmienna działa jak stała, ponieważ jej wartość jest ustawiona w sekcji deklaracji). Ten program oczywiście zwraca ten sam zbiór wyników co poprzednia wersja kodu.

186

Część I

Podstawy języka PL/SQL

Kursory jawne Jak opisano to we wcześniejszej części podrozdziału, aby utworzyć kursor jawny, należy zdefiniować go w sekcji deklaracji. Kursory jawne mogą być statycznymi lub dynamicznymi instrukcjami SELECT. Instrukcje statyczne tego rodzaju używają za każdym razem tego samego zapytania, choć może ono zwracać różne wyniki, które zmieniają się wraz z modyfikowaniem danych w tabelach i widokach. Dynamiczne instrukcje SELECT działają jak podprogramy z parametrami. Uruchamiają różne zapytania na podstawie parametrów otrzymanych w momencie otwarcia. Otwieranie kursorów statycznych i dynamicznych z parametrami odbywa się w odmienny sposób. Jeśli kursory tych typów nie mają parametrów, można otwierać je tak samo. Po otwarciu kursora podane argumenty są zastępowane zmiennymi lokalnymi. Programista musi otworzyć, pobrać i zamknąć kursor jawny niezależnie od tego, czy używa pętli prostej lub WHILE, czy pętli FOR opartej na kursorze. Do otwierania kursorów służy instrukcja OPEN, instrukcja FETCH pozwala pobrać rekordy z kursora, a instrukcja CLOSE zamyka kursor i zwalnia zajmowane przez niego zasoby. Te instrukcje służą do obsługi kursorów dynamicznych i statycznych zarówno wewnątrz pętli, jak i poza nimi. Pętle FOR oparte na kursorze niejawnie otwierają, pobierają i zamykają kursor za programistę. Instrukcje OPEN, FETCH i CLOSE to główny temat podpunktów „Statyczne kursory jawne” i „Dynamiczne kursory jawne”, w których przedstawiono przykłady zastosowania tych poleceń w pętlach prostych. Prototyp instrukcji OPEN wygląda następująco: OPEN nazwa_kursora [(parametr [, parametr [, ... ]])];

Instrukcja FETCH ma dwa prototypy. Jeden opisuje przypisywanie poszczególnych kolumn do zmiennych, podczas gdy drugi — wierszy do rekordu. Prototyp przypisywania pojedynczych kolumn do odpowiadających im zmiennych wygląda następująco: FETCH nazwa_kursora INTO zmienna [, zmienna [, ... ]];

Poniższy prototyp przedstawia przypisywanie wierszy do zmiennych rekordowych: FETCH nazwa_kursora INTO zmienna_rekordowa;

Prototyp instrukcji CLOSE wygląda następująco: CLOSE nazwa_kursora;

W tabeli 5.7 przedstawiono atrybuty kursorów jawnych. Atrybuty działają w ten sam sposób niezależnie od tego, czy kursor jawny jest dynamiczny, czy statyczny, natomiast inaczej niż dla kursorów niejawnych, przedstawionych w tabeli 5.6. Atrybuty kursorów jawnych zwracają różne wyniki w zależności od tego, po której instrukcji zostały wywołane: OPEN, FETCH czy CLOSE. Tabela 5.7. Atrybuty kursorów jawnych Instrukcja OPEN

1. FETCH Następne FETCH Ostatnie FETCH CLOSE

Stan

%FOUND

%NOTFOUND

%ISOPEN

%ROWCOUNT Wyjątek

Przed

Wyjątek

Wyjątek

FALSE

Po

NULL

NULL

TRUE

0

Przed

NULL

NULL

TRUE

0

Po

TRUE

FALSE

TRUE

1

Przed

TRUE

FALSE

TRUE

1

Po

TRUE

FALSE

TRUE

n + 1

Przed

TRUE

FALSE

TRUE

n + 1

Po

FALSE

TRUE

TRUE

n + 1

Przed

FALSE

TRUE

TRUE

n + 1

Wyjątek

FALSE

Wyjątek

Po

Wyjątek

Rozdział 5. Struktury sterujące

187

Atrybut %FOUND informuje, że z kursora można pobrać wiersze, a atrybut %NOTFOUND sygnalizuje, że wszystkie wiersze są już pobrane. Atrybut %ISOPEN służy do sprawdzania, czy kursor jest już otwarty. Warto go użyć przed próbą otwarcia kursora. Atrybut %ROWCOUNT zwraca, podobnie jak w kursorach niejawnych, liczbę wierszy pobranych w danym momencie. Tylko atrybutu %ISOPEN można użyć zawsze bez narażania się na wystąpienie błędu. Trzy pozostałe atrybuty zgłaszają błąd, jeśli zostaną użyte z nieotwartym kursorem. Zmiany w działaniu atrybutów przedstawiono w tabeli 5.7. W następnych podpunktach opisano kursory statyczne i dynamiczne, co pozwoliło podkreślić różnice między nimi i uporządkować przykłady. W przykładowym kodzie użyto pętli prostych, ale kursorów jawnych można też używać w pętlach WHILE i umieszczać je w pętlach FOR opartych na przedziale i kursorze.

Statyczne kursory jawne Statyczny kursor jawny to instrukcja SELECT języka SQL, która nie zmienia działania. Proces korzystania z kursorów jawnych obejmuje cztery operacje: definiowanie i otwieranie kursora, pobieranie z niego danych i zamykanie go. Poniższy program definiuje i otwiera kursor statyczny, pobiera z niego dane i zapisuje je w zmiennych skalarnych, a następnie zamyka go. SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

DECLARE lv_id item.item_id%TYPE; lv_title VARCHAR2(60); CURSOR c IS SELECT item_id , item_title FROM item; BEGIN OPEN c; LOOP FETCH c INTO lv_id , lv_title; EXIT WHEN c%NOTFOUND; dbms_output.put_line('Tytuł ['||title||']'); END LOOP; CLOSE c; END; /

W wierszu 2. w deklaracji zmiennej wykorzystano kotwiczenie do kolumny, a w wierszu 3. zadeklarowano zmienną za pomocą statycznego typu danych (odpowiada on typowi fizycznej kolumny z tabeli item). Zwykle należy stosować spójne rozwiązania, jednak ten przykład ma ilustrować obie techniki. W wierszach 12. i 13. program pobiera dwie kolumny do zmiennych. To przypisanie jest możliwe, ponieważ typy danych zmiennych lokalnych z wierszy 2. i 3. pasują do typów kolumn z listy instrukcji SELECT (wiersze 5. i 6.) z definicji kursora. Program kończy działanie po pobraniu wszystkich rekordów. Jak wyjaśniono w rozdziale 3., kursory można stosować zamiast zakotwiczania poszczególnych zmiennych do kolumn tabeli, lokalnych struktur rekordowych i tabel. Dzięki temu można zdefiniować zmienną lokalną lv_record i zakotwiczyć ją do struktury kursora, co ilustruje następny przykład: SQL> DECLARE 2 CURSOR c IS 3 SELECT item_id AS id 4 , item_title AS title 5 FROM item; 6 lv_record c%ROWTYPE; 7 BEGIN 8 OPEN c; 9 LOOP 10 FETCH c 11 INTO lv_record; 12 EXIT WHEN c%NOTFOUND;

188 13 14 15 16 17

Część I

Podstawy języka PL/SQL

dbms_output.put_line('Tytuł ['||lv_record.title||']'); END LOOP; CLOSE c; END; /

W wierszu 6. deklarowana jest zmienna lv_record, która dziedziczy typ rekordowy po liście z instrukcji SELECT kursora. Wiersz 13. za pomocą selektora komponentów wyświetla wartość pola title ze struktury zmiennej lv_record. Stosowanie instrukcji FETCH, która pobiera strukturę kursora i przypisuje ją do jednej zmiennej, to zalecane rozwiązanie. Nie należy przypisywać kolumn do zmiennych lokalnych, chyba że istnieje ku temu odpowiednia przyczyna. Jedynym takim powodem jest to, że instrukcja SELECT zwraca duży obiekt, który wymaga przetwarzania niezależnie od obsługi innych wartości. Czasem duże obiekty najlepiej jest przetwarzać za pomocą odrębnych kursorów. Obsługę struktur zagnieżdżonych (na przykład tablic VARRAY i tabel zagnieżdżonych) opisano w rozdziale 6. W języku PL/SQL jedyną alternatywą do powiązania zmiennej lv_record ze strukturą wierszy kursora jest powiązanie go z definicją tabeli, widoku lub jawnie utworzonego typu rekordowego (kompletny przykład znajdziesz w punkcie „Rekordy” w rozdziale 4.). Jeśli w sekcji deklaracji utworzysz typ rekordowy item_record, będziesz mógł zdefiniować zmienną lv_record w wierszu 9. w następujący sposób: 6

lv_record ITEM_RECORD;

Choć w języku PL/SQL jest to poprawne rozwiązanie, nie jest ono tak skuteczne jak powiązanie typu zmiennej z typem wierszy kursora. Zachęcam do tego, aby zawsze wiązać zmienne ze strukturą wierszy kursora, ponieważ dzięki temu kod jest bardziej czytelny. Nie można za pomocą instrukcji SELECT INTO zapisywać danych do zmiennej typu obiektowego. Ma to chronić przed problemami. Dlatego poniższa składnia nigdy nie zadziała: SQL> DECLARE ... 6 lv_object ITEM_OBJECT; 7 BEGIN ... 10 FETCH c 11 INTO lv_object(lv_id,lv_title); ... 16 END; 17 /

W wierszu 6. deklarowana jest zmienna lv_object typu obiektowego item_object SQL-a. Ten typ odpowiada typowi wierszy kursora. Jednak próba przypisania wartości ze struktury kursora w konstruktorze typu obiektowego spowoduje zgłoszenie następującego błędu: INTO lv_object(lv_id,lv_title); * ERROR at line 11: ORA-06550: line 11, column 11: PLS-00308: this construct is not allowed as the origin of an assignment ORA-06550: line 10, column 5: PL/SQL: SQL Statement ignored

Choć nie można przekazać zawartości wiersza kursora do konstruktora typu obiektowego, można uzyskać taki efekt za pomocą pętli FOR.

Kursor statyczny i pętla FOR Pętla FOR oparta na kursorze umożliwia bezpośrednie przypisywanie wartości do zmiennej dowolnego typu. Wymaga to zastosowania dynamicznej instrukcji SELECT. Aby utworzyć taką instrukcję, należy umieścić w niej zmienne lokalne lub parametry kursora. Statyczna pętla FOR korzysta ze statycznej instrukcji SELECT. Gdy kursor jest statyczny, nie można używać w pętli FOR parametrów, jednak można w takiej pętli przypisywać wartości z kursora statycznego. W tym celu należy wykorzystać indeks kursora z pętli.

Rozdział 5. Struktury sterujące

189

Wynika to z tego, że w pętlach FOR opartych na kursorach indeks to bezpośrednia referencja do wierszy zwracanych przez kursor (inaczej jest w pętlach FOR opartych na przedziałach, gdzie indeks kursora jest typu PLS_INTEGER). Można przypisać strukturę rekordową do zmiennej o pasującej strukturze. Podobnie można przypisać element skalarny struktury rekordowej do pasującej zmiennej skalarnej. Zmienną lv_record można zadeklarować za pomocą jawnego typu rekordowego lub powiązać przy użyciu atrybutu %ROWTYPE z lokalnym lub współużytkowanym kursorem. Poniższy fragment ilustruje przypisywanie struktury rekordowej za pomocą indeksu kursora: SQL> DECLARE ... To samo co w poprzednim przykładzie ... 7 BEGIN 8 FOR i IN c LOOP 9 lv_record := i; 10 dbms_output.put_line('Tytuł ['||lv_record.title||']'); 11 END LOOP; 12 END; 13 /

Wiersz 9. przypisuje iterator pętli FOR do zmiennej lv_record. Jak wspomniano, ta technika działa wyłącznie dla rekordowych struktur danych z języka PL/SQL. Elementy z kursora można przypisywać do zmiennych skalarnych pasujących typów lub do pól zakotwiczonych typów rekordowych. Nowa wersja wiersza 9. przypisuje element z listy SELECT do pola zmiennej lv_record (ma ona typ rekordowy zakotwiczony za pomocą atrybutu %ROWTYPE): 9

lv_record.item_title := i.item_title;

Można też przypisać kursor do konstruktora typu obiektowego z SQL-a. W tym celu należy przekazać elementy z listy instrukcji SELECT jako parametry wywołania konstruktora danego typu obiektowego. Załóżmy, że przed uruchomieniem programu zdefiniowano następujący typ obiektowy: SQL> CREATE OR REPLACE 2 TYPE item_object IS OBJECT 3 ( id NUMBER 4 , title VARCHAR2(60)); 5 /

Zamiast zakotwiczać typ zmiennej lv_record za pomocą atrybutu %ROWTYPE do struktury rekordowej kursora (wiersz 6.), można wykorzystać SQL-owy typ UDT item_object. Oto zmodyfikowana deklaracja zmiennej lv_record: 6

lv_record ITEM_OBJECT;

Następnie można przypisać zwrócony przez kursor rekord element po elemencie do konstruktora typu item_object. W tym celu zastosuj w wierszu 9. następującą składnię: 9

lv_record.item_title := item_object(i.item_id, i.item_title);

Choć pętla FOR automatyzuje wykonywanie wielu zadań, niestety nie umożliwia wykonywania operacji w zależności od tego, czy wiersze zostały zwrócone. Aby uzyskać taką kontrolę, należy wykorzystać pętlę prostą.

Warunkowe zwracanie wartości Czasem kursor nie zwraca żadnych rekordów. Gdy uruchomiony kursor niejawny lub jawny nie znajdzie żadnych danych, nie prowadzi to do zgłoszenia błędu. Jeśli chcesz otrzymywać powiadomienie, gdy kursor nie znalazł żadnych danych, musisz zastosować dodatkowy kod. W tym celu możesz użyć instrukcji IF oraz atrybutów %NOTFOUND i %ROWCOUNT kursora w pętli prostej (nie może to być pętla FOR). Poniższa pętla prosta wyświetla komunikat Brak danych, jeśli kursor nie znalazł żadnych rekordów: SQL> DECLARE 2 CURSOR c IS 3 SELECT item_id AS id 4 , item_title AS title 5 FROM item

190 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

Część I

Podstawy języka PL/SQL

WHERE item_id = -1; lv_record c%ROWTYPE; BEGIN OPEN c; LOOP FETCH c INTO lv_record; IF c%NOTFOUND THEN IF c%ROWCOUNT = 0 THEN /* Nie zwrócono wierszy. */ dbms_output.put_line('Brak danych'); ELSE /* Znaleziono przynajmniej jeden wiersz. */ dbms_output.put_line('Pobrano już wszystkie dane'); END IF; EXIT; ELSE dbms_output.put_line('Tytuł ['||lv_record.title||']'); END IF; END LOOP; END; /

W wierszu 6. znajduje się klauzula WHERE, która sprawia, że zapytanie na pewno nie zwróci żadnych wierszy. Kursor nie zwraca więc wierszy, a warunek c%NOTFOUND w wierszu 12. ma wartość true. Ponieważ ostatnia instrukcja SQL-a nie zwróciła żadnych wierszy, także warunek z atrybutem c%ROWCOUNT z wiersza 13. ma wartość true, dlatego program wyświetla komunikat Brak danych. Jeśli warunek z atrybutem c%ROWCOUNT ma wartość false, program wyświetla tekst Pobrano już wszystkie dane. W wierszu 18. program wychodzi z pętli po wyświetleniu jednego ze wspomnianych komunikatów. Niestety, za pomocą pętli FOR nie da się zbudować podobnego rozwiązania.

Dynamiczne kursory jawne Dynamiczne kursory jawne są bardzo podobne do statycznych kursorów jawnych i opierają się na instrukcji SELECT SQL-a. Jednak tu zmienne można stosować nie tylko w instrukcjach SELECT i kursorach; możliwe jest też umieszczanie lokalnych zmiennych w instrukcjach INSERT, UPDATE, DELETE i MERGE. Zmienne są tu stosowane zamiast literałów. Dynamiczne kursory jawne także są obsługiwane za pomocą czterech operacji: definiowania i otwierania kursora, pobierania z niego danych i zamykania go. W dynamicznych kursorach jawnych (podobnie jak w dynamicznych kursorach niejawnych) wykorzystuje się zmienne lokalne. Przykładowy program definiuje kursor w postaci instrukcji SELECT, która kieruje do tabeli ITEM zapytania z wartościami z określonego przedziału. Zmienne wyznaczające ten przedział są zadeklarowane jako zmienne lokalne, a program przypisuje im literały liczbowe. Nazwy tych zmiennych muszą różnić się od nazw kolumn, ponieważ w przeciwnym razie zamiast wartości zmiennych program użyje wartości kolumn. SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

DECLARE lv_lowend NUMBER := 1010; lv_highend NUMBER := 1020; CURSOR c IS SELECT item_id AS id , item_title AS title FROM item WHERE item_id BETWEEN lv_lowend AND lv_highend; lv_record c%ROWTYPE; BEGIN OPEN c; LOOP FETCH c INTO lv_record; EXIT WHEN c%NOTFOUND; dbms_output.put_line('Tytuł ['||lv_record.title||']'); END LOOP; END; /

Rozdział 5. Struktury sterujące

191

W wierszach 2. i 3. deklarowane są zmienne lv_lowend i lv_highend. W wierszu 8. te zmienne są używane jako granice w operatorze BETWEEN. Wartości zmiennych lokalnych są podstawiane w instrukcji SELECT w czasie wykonywania programu. Ta sama technika działa w pętlach FOR i WHILE. Można korzystać ze zmiennych lokalnych, jednak czasem prowadzi to do pomyłek i utrudnia konserwację kodu. Choć instrukcje INSERT, UPDATE, DELETE i MERGE wymagają zagnieżdżania zmiennych lokalnych, w instrukcjach SELECT w kursorach nie jest to konieczne. Kursory mogą przyjmować parametry formalne (podobnie jak funkcje i procedury). W kursorach z instrukcją SELECT powinno się stosować takie parametry. W następnym programie zmieniono definicję kursora i wywołanie instrukcji OPEN: SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

DECLARE lv_lowend NUMBER := 1005; lv_highend NUMBER := 1021; CURSOR c ( cv_low_id NUMBER , cv_high_id NUMBER) IS SELECT item_id AS id , item_title AS title FROM item WHERE item_id BETWEEN cv_low_id AND cv_high_id; item_record c%ROWTYPE; BEGIN OPEN c (lv_lowend, lv_highend); LOOP FETCH c INTO item_record; EXIT WHEN c%NOTFOUND; dbms_output.put_line('Tytuł ['||item_record.title||']'); END LOOP; END; /

W wierszach od 4. do 6. definiowany jest kursor z dwoma liczbowymi parametrami formalnymi: cv_low_id i cv_high_id. Są one używane jako wartości operatora BETWEEN w wierszu 10. Wiersz 13. otwiera kursor za pomocą parametrów lv_lowend i lv_highend.

Warto zauważyć, że zmienne lokalne mają określony rozmiar fizyczny, natomiast nie dotyczy to parametrów formalnych. Wynika to z tego, że rozmiar fizyczny parametrów formalnych jest określany dopiero w czasie wykonywania programu (tu dzieje się to w momencie przekazania parametrów przy otwieraniu kursora w wierszu 13.). Otwieranie kursora w pętli FOR przebiega bardzo podobnie jak w pętli prostej. Należy podać rozdzieloną przecinkami listę parametrów wywołania w nawiasach. Poniższe cztery wiersze zastępują sześć wierszy z poprzedniego przykładu: 13 FOR i IN c (lv_lowend, lv_highend) LOOP 14 item := i; 15 dbms_output.put_line('Tytuł ['||item_record.title||']'); 16 END LOOP;

Wiesz już, jak używać kursorów, ale to jeszcze nie wszystko. Można też korzystać z kursorów wewnątrz kursorów, co opisano w następnym podpunkcie.

Podkursory Podkursor to kursor zagnieżdżony. Aby utworzyć podkursor, należy zagnieździć skorelowane podzapytanie na liście instrukcji SELECT w kursorze jawnym lub dynamicznym. Skorelowane podzapytania obejmują złączenie w klauzuli WHERE, które łączy dane podzapytanie z zewnętrznym zapytaniem. Więcej informacji o podzapytaniach skorelowanych znajdziesz w punkcie „Podzapytania skorelowane” w dodatku B. Przy stosowaniu podkursora trzeba jawnie pobrać listę kolumn z instrukcji SELECT do listy zmiennych. Wyniki zwrócone przez podkursory są przypisywane do zmiennych typu REF CURSOR, używanego tylko w języku PL/SQL. Następnie trzeba pobrać wyniki w pętli zagnieżdżonej.

192

Część I

Podstawy języka PL/SQL

Możesz się zastanawiać: „Ale po co mam się tak męczyć?”. Aby zrozumieć odpowiedź, najpierw przyjrzyj się zbiorowi wyników standardowego zapytania dotyczącego trzech tabel. W tym zapytaniu używane są złączenia wewnętrzne w celu połączenia trzech tabel na podstawie kolumn z kluczami głównym i obcym (w kodzie zastosowano też formatowanie, aby dane wyjściowe były bardziej czytelne). SQL> SQL> SQL> SQL> SQL> SQL> SQL> 2 3 4 5 6 7 8 9 10

COLUMN COLUMN COLUMN COLUMN COLUMN COLUMN SELECT , , , , , FROM ON ON WHERE

first_name FORMAT A10 last_name FORMAT A10 street_address FORMAT A20 city FORMAT A8 state FORMAT A2 postal_code FORMAT A5 c.first_name c.last_name sa.street_address a.city a.state_province AS state a.postal_code contact c INNER JOIN address a c.contact_id = a.contact_id INNER JOIN street_address sa a.address_id = sa.address_id c.last_name = 'Vizquel';

To zapytanie zwraca cztery poniższe wiersze: FIRST_NAME ---------Oscar Doreen Doreen Doreen

LAST_NAME ---------Vizquel Vizquel Vizquel Vizquel

STREET_ADDRESS -------------------12 El Camino Real 12 El Camino Real 41277 Roberts Avenue Apt #14

CITY -------San Jose San Jose Fremont Fremont

ST -CA CA CA CA

ZIP ----95192 95192 94539 94539

Zwrócony zbiór jest symetryczny, co oznacza, że w każdej kolumnie znajduje się ta sama liczba wierszy. Przyjrzyj się wynikom dotyczącym Doreen Vizquel. Są trzy wiersze z takim imieniem i nazwiskiem, ponieważ tabela street_address zawiera trzy niepowtarzalne wyniki powiązane z tym samym wierszem contact. Podobnie istnieją dwa powtarzające się wiersze w kolumnach city, state i zip, ponieważ istnieją dwie unikatowe wartości street_address powiązane z jednym wierszem z tabeli address. Jest to symetryczny zbiór wyników, w którym wartości są powielane w powiązanych unikatowych wierszach. Symetryczne zbiory wyników nie są niczym dziwnym w SQL-u, jednak zwykle nie są przydatne w formularzach sieciowych i raportach. Załóżmy, że program wymaga uzyskania asymetrycznego zbioru wyników, takiego jak poniżej: Formatted Address ---------------------------------1004 Doreen Vizquel 41277 Roberts Avenue, Apt #14 Fremont, CA, 94539 12 El Camino Real San Jose, CA, 95192 1003 Oscar Vizquel 12 El Camino Real San Jose, CA, 95192

Bez stosowania zaawansowanych instrukcji do generowania raportów w środowisku SQL*Plus trudno uzyskać takie dane za pomocą instrukcji SELECT SQL-a. Jednak za pomocą podkursorów można napisać funkcję, która zwraca zbiory danych w takiej postaci. Ponieważ funkcja format_contact_address zwraca kolekcję w postaci tabeli zagnieżdżonej wartości typu CLOB, należy zdefiniować odpowiednią kolekcję SQL-a. Poniższy kod tworzy tabelę zagnieżdżoną elementów typu CLOB: SQL> CREATE OR REPLACE 2 TYPE format_address_table IS TABLE OF CLOB; 3 /

Rozdział 5. Struktury sterujące

193

Teraz można utworzyć funkcję, która używa podzapytania do sformatowania danych i zwrócenia tabeli zagnieżdżonej ze sformatowanymi adresami: SQL> CREATE OR REPLACE FUNCTION format_contact_address 2 ( pv_last_name VARCHAR2 ) RETURN FORMAT_ADDRESS_TABLE IS 3 4 /* Deklaracja kursora referencyjnego. */ 5 TYPE ref_cursor IS REF CURSOR; 6 7 /* Deklaracja kursora zagnieżdżonego. */ 8 CURSOR all_nested_results 9 ( cv_last_name VARCHAR2 ) IS 10 SELECT c.contact_id 11 , c.first_name 12 || DECODE(c.middle_name,NULL,' ',' '||c.middle_name||' ') 13 || c.last_name AS full_name 14 , CURSOR(SELECT a.city 15 , a.state_province AS state 16 , CURSOR(SELECT sa.street_address 17 FROM street_address sa 18 WHERE sa.address_id = 19 a.address_id) 20 , a.postal_code 21 FROM address a 22 WHERE a.contact_id = c.contact_id 23 ORDER BY a.start_date DESC) 24 FROM contact c 25 WHERE c.last_name = cv_last_name 26 ORDER BY c.last_name 27 , c.first_name; 28 29 /* Deklaracja licznika adresów. */ 30 lv_street_counter NUMBER := 0; 31 lv_index_counter NUMBER := 1; 32 33 /* Deklaracja dwóch kursorów referencyjnych. */ 34 lv_street_cursor REF_CURSOR; 35 lv_address_cursor REF_CURSOR; 36 37 /* Deklaracja lokalnych zmiennych skalarnych. */ 38 lv_employee_id NUMBER; 39 lv_full_name VARCHAR2(62); 40 lv_city VARCHAR2(30); 41 lv_state VARCHAR2(2); 42 lv_street_address VARCHAR2(30); 43 lv_postal_code VARCHAR2(10); 44 45 /* Deklaracja dużego łańcucha znaków na dane wyjściowe. */ 46 lv_output_message VARCHAR2(300); 47 lv_output_table FORMAT_ADDRESS_TABLE := format_address_table(); 48 49 BEGIN 50 51 /* Otwieranie kursora złożonego. */ 52 OPEN all_nested_results (pv_last_name); 53 54 /* Wczytywanie zbioru wyników z kursora. */ 55 LOOP 56 FETCH all_nested_results 57 INTO lv_employee_id 58 , lv_full_name 59 , lv_address_cursor; 60 EXIT WHEN all_nested_results%NOTFOUND; 61

194

Część I

62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124

/* Generowanie komunikatu za pomocą podstawowego kursora. */ lv_output_message := lv_employee_id||' '||lv_full_name||CHR(10);

Podstawy języka PL/SQL

/* Wczytywanie tabeli zagnieżdżonej z pierwszego poziomu. */ LOOP FETCH lv_address_cursor INTO lv_city , lv_state , lv_street_cursor , lv_postal_code; EXIT WHEN lv_address_cursor%NOTFOUND; /* Wczytywanie tabeli zagnieżdżonej z drugiego poziomu. */ LOOP FETCH lv_street_cursor INTO lv_street_address; /* Sprawdzanie, czy wczytano wszystkie rekordy podkursora. */ IF lv_street_cursor%NOTFOUND THEN /* Przechodzenie do nowego wiersza na końcu. */ IF lv_street_counter > 0 THEN lv_output_message := lv_output_message||CHR(10); lv_street_counter := 0; END IF; /* Dołączanie i wyświetlanie adresu oraz wyjście z podkursora. */ lv_output_message := lv_output_message||' ' || lv_city||', '||lv_state||', ' || lv_postal_code||CHR(10); EXIT; ELSE /* Dołączanie adresów. */ lv_street_counter := lv_street_counter + 1; IF lv_street_counter = lv_output_message := || ELSE lv_output_message := || END IF; END IF; END LOOP;

1 THEN lv_output_message||' lv_street_address;

'

lv_output_message||', ' lv_street_address;

/* Resetowanie komunikatu za pomocą podstawowego kursora. */ lv_output_message := lv_output_message||CHR(10); END LOOP; /* Dodawanie miejsca i przypisywanie elementu do kolekcji. */ lv_output_table.EXTEND; lv_output_table(lv_index_counter) := lv_output_message; lv_index_counter := lv_index_counter + 1; END LOOP; /* Zamykanie kursora. */ CLOSE all_nested_results; /* Zwracanie sformatowanego adresu. */ RETURN lv_output_table; END; /

Rozdział 5. Struktury sterujące

195

Wiersz 5. deklaruje systemowy kursor referencyjny ze słabą kontrolą typu. Ten typ jest używany dla dwóch używanych dalej podkursorów. Wiersze od 14. do 23. to podkursor zawierający następny podkursor. W wierszu 22. następuje złączenie między zewnętrznym kursorem a podkursorem z pierwszego poziomu. W wierszach 18. i 19. (tak naprawdę jest to jeden wiersz podzielony z powodu ograniczonej szerokości książki) następuje złączenie podkursorów z pierwszego i drugiego poziomu. Wiersze 34. i 35. deklarują dwie zmienne przeznaczone na podkursory. Można tu wyeliminować zdefiniowany przez użytkownika typ REF_CURSOR ze słabą kontrolą typów, ponieważ Oracle udostępnia uniwersalny typ SYS_REFCURSOR idealnie dostosowany do tej sytuacji. Aby wykorzystać ten wbudowany typ, należy zmodyfikować wiersze 34. i 35. w następujący sposób: 34 lv_street_cursor SYS_REFCURSOR; 35 lv_address_cursor SYS_REFCURSOR;

Podkursor z pierwszego poziomu jest przypisywany do systemowego kursora referencyjnego w pętli zagnieżdżonej z pierwszego poziomu (w wierszu 59.), a podkursor z drugiego poziomu zostaje przypisany w wierszu 70. Następnie można wywołać nową funkcję w funkcji TABLE: SQL> SELECT column_value AS "Formatted Address" 2 FROM TABLE(format_contact_address('Vizquel'));

Choć staram się unikać podkursorów, możesz z nich korzystać także wtedy, gdy używasz tabel zawierających tabele zagnieżdżone. Wtedy można zastosować złączenia krzyżowe zamiast złączeń wewnętrznych między kursorem i podkursorem lub podkursorem i kursorem zagnieżdżonym. Jeśli wykorzystasz tabelę employee opisaną w punkcie „Typy kolekcji zagnieżdżonych” w dodatku B, możesz zastąpić kursor z przykładu następującym kodem: 9 CURSOR all_nested_results 10 ( cv_start_id NUMBER 11 , cv_end_id NUMBER) IS 12 SELECT e.employee_id 13 , e.first_name 14 || DECODE(e.middle_name,NULL,' ',' '||e.middle_name||' ') 15 || e.last_name AS full_name 16 , CURSOR(SELECT n.city 17 , n.state 18 , CURSOR(SELECT s.column_value 19 FROM TABLE(n.street_address) s) 20 , n.postal_code 21 FROM TABLE(e.home_address) n) 22 FROM employee e 23 WHERE e.employee_id BETWEEN cv_start_id AND cv_end_id;

Wiersze 18. i 19. tworzą podkursor za pomocą zagnieżdżonego typu ADT. Wiersze od 16. do 21. tworzą podkursor na podstawie zagnieżdżonego typu UDT. Złączenia między kolekcjami a nadrzędnymi wierszami są zbędne, ponieważ baza Oracle automatycznie zarządza relacjami między takimi elementami. W tym podrozdziale opisano, jak używać jawnych i niejawnych kursorów w jednostkach programu. Dowiedziałeś się tu, że niektóre niejawne operacje są poza kontrolą programisty. Ponadto zobaczyłeś, że jawne struktury zapewniają większą kontrolę. Powtórzenie materiału W tym podrozdziale omówiono następujące zagadnienia na temat kursorów:  Kursory zwracają przetwarzane wiersz po wierszu zbiory wyników z SQL-owych instrukcji SELECT.  Kursory niejawne są tworzone dla wszystkich instrukcji DML (INSERT, UPDATE, DELETE i MERGE).  Język PL/SQL obsługuje atrybuty %FOUND, %NOTFOUND, %ISOPEN i %ROWCOUNT kursorów niejawnych.  Instrukcja SELECT INTO tworzy jednowierszowy kursor niejawny.  Instrukcje INSERT, UPDATE, DELETE i MERGE tworzą wielowierszowe kursory niejawne, choć można je ograniczyć do jednego wiersza.

196

Część I

Podstawy języka PL/SQL

Kursory mogą być statyczne lub dynamiczne. W dynamicznych kursorach niejawnych można stosować miejsca na dane w postaci referencji do zmiennych lokalnych.  Kursory z instrukcją SELECT mogą (podobnie jak funkcje i procedury) mieć parametry formalne.  W kursorach można tworzyć kursory zagnieżdżone, nazywane podkursorami. 

Instrukcje masowe Instrukcje masowe umożliwiają pobieranie, wstawianie, aktualizowanie i usuwanie dużych zbiorów danych w tabelach oraz widokach. Służy do tego polecenie BULK COLLECT, którego należy używać w instrukcjach SELECT, oraz polecenie FORALL. Tabela 5.8 opisuje dwa atrybuty kursorów masowych. Podpunkt „Instrukcja INSERT” w punkcie „Instrukcje FORALL” zawiera przykład zastosowania atrybutu %BULK_ROWCOUNT. Tabela 5.8. Atrybuty kursorów masowych Atrybut

Opis

%BULK_EXCEPTIONS(i)

Pozwala sprawdzić, czy w czasie wykonywania masowej instrukcji INSERT, UPDATE lub DELETE wystąpił błąd. Aby uzyskać dostęp do statystyk, należy użyć pętli FOR opartej na przedziale.

%BULK_ROWCOUNT(i)

Umożliwia sprawdzenie, czy element został zmieniony przez masową instrukcję INSERT, UPDATE lub DELETE. Aby uzyskać dostęp do statystyk, należy użyć pętli FOR opartej na przedziale.

W tym podrozdziale opisano, jak używać instrukcji BULK COLLECT INTO i FORALL. W pierwszym punkcie omówiono zastosowania kolekcji skalarów i rekordów oraz różnice między nimi. Punkt „Instrukcje FORALL” zawiera odrębne podpunkty przedstawiające używanie masowych instrukcji INSERT, UPDATE i DELETE. W ostatnim punkcie przedstawiono, jak używać atrybutów %BULK_ROWCOUNT(i) i %BULK_ EXCEPTIONS(i) (wprowadzenie do tego zagadnienia zawiera podpunkt „Instrukcja INSERT”).

Instrukcje BULK COLLECT INTO Instrukcja BULK COLLECT INTO pozwala pobrać kolumnę danych i wstawić ją do kolekcji elementów o typie danych z bazy Oracle. Pobierania masowego można używać w instrukcjach języka SQL oraz w instrukcjach FETCH. W tym pierwszym przypadku stosowane są kursory niejawne, podczas gdy w instrukcjach FETCH należy utworzyć kursor jawny. Jeśli programista używa pobierania masowego za pomocą kursora niejawnego, nie może ograniczyć liczby zwracanych wierszy. Instrukcja FETCH pozwala dołączyć instrukcję LIMIT i ograniczyć w ten sposób maksymalną liczbę wierszy wczytywanych z kursora. Element docelowy instrukcji opartej na kursorze niejawnym może mieć dowolny standardowy lub zdefiniowany przez użytkownika typ danych języka PL/SQL. Poniżej przedstawiono podstawowy prototyp instrukcji niejawnego pobierania masowego: SELECT kolumna [, kolumna [, ... ]] BULK COLLECT INTO kolekcja [, kolekcja [, ... ]] FROM nazwa_tabeli [WHERE instrukcje_klauzuli_where];

Pobieranie masowe w ramach instrukcji FETCH wymaga zastosowania kursora jawnego. Operacja ta ma następujący prototyp: FETCH nazwa_kursora [(parametr [, parametr [, ... ]])] BULK COLLECT INTO kolekcja [, kolekcja [, ... ]] [LIMIT liczba_zwracanych_wierszy];

Liczba kolumn zwracanych przez kursor jawny określa liczbę docelowych kolekcji elementów skalarnych lub strukturę rekordów kolekcji docelowej. Definicję liczby i typu kolumn zwracanych przez kursor zawiera instrukcja SELECT.

Rozdział 5. Struktury sterujące

197

Instrukcji BULK COLLECT INTO można użyć do wstawienia danych do wielu elementów docelowych lub do jednego takiego elementu. Wiele elementów należy podać w postaci kolekcji rozdzielonych przecinkami. Kolekcje rozdzielone w ten sposób to tak zwane kolekcje równoległe, ponieważ zwykle programista zarządza nimi równolegle. Pojedynczy element docelowy to kolekcja rekordów. W jednym wywołaniu nie można wstawić wybranych kolumn do kolekcji rekordów, a innych — do kolekcji skalarów. Próba wykonania takiej operacji wywoła błąd PLS-00494, który informuje o tym, że nie można wstawić danych do zbioru rekordów docelowych. Instrukcja BULK COLLECT INTO działa dużo szybciej niż standardowe kursory, ponieważ jest uruchamiana w jednym cyklu obejmującym parsowanie i wykonywanie instrukcji oraz pobieranie danych. Zwykłe niejawne kursorowe instrukcje INTO i kursory jawne wymagają większej liczby takich operacji. Operacje masowe lepiej się skalują wraz ze wzrostem liczby wierszy, jednak przy bardzo dużych zbiorach danych wykonywanie ich wymaga odpowiedniego skonfigurowania bazy. Podpunkty „Równoległe kolekcje docelowe” i „Docelowe kolekcje rekordów” przedstawiają przeprowadzanie operacji masowych na kolekcjach przy użyciu kursorów niejawnych. Kursory jawne opisano w ostatnim podpunkcie, „Kolekcje docelowe ograniczone instrukcją LIMIT”, przy omawianiu instrukcji LIMIT. Instrukcja ta pozwala ograniczyć liczbę masowo pobieranych elementów, można jej jednak używać tylko przy stosowaniu kursorów jawnych. W ostatnim podpunkcie, poświęconym ograniczonym kolekcjom docelowym, opisano też uwzględnianie ograniczeń bazy danych, na przykład związanych z obszarem PGA.

Równoległe kolekcje docelowe Kolekcje typów skalarnych to jedyny obsługiwany rodzaj kolekcji języka SQL. Jeśli programista chce wymieniać dane z programami zewnętrznymi lub aplikacjami sieciowymi, powinien zwracać masowo pobrane dane do zbioru kolekcji równoległych. Dane w tej formie można przekazywać do programów zewnętrznych i aplikacji sieciowych przy użyciu interfejsu OCI (ang. Oracle Call Interface). Poniższy przykładowy program używa kursora niejawnego z instrukcją BULK COLLECT INTO i wykonuje pobieranie masowe, przypisując dane do zbioru równoległych kolekcji typów skalarnych: SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

DECLARE -- Deklaracja kolekcji elementów skalarnego typu danych. TYPE title_collection IS TABLE OF VARCHAR2(60); -- Deklaracja dwóch zmiennych typu tej kolekcji. lv_title TITLE_COLLECTION; lv_subtitle TITLE_COLLECTION; BEGIN -- Wywołanie kursora niejawnego z pobieraniem masowym. SELECT item_title , item_subtitle BULK COLLECT INTO lv_title , lv_subtitle FROM item; -- Wyświetlanie zawartości zapełnionej masowo kolekcji. FOR i IN 1..lv_title.COUNT LOOP /* Wyświetlanie pierwszego elementu. */ dbms_output.put_line('Tytuł ['||lv_title(i)||']'); END LOOP; END; /

Wiersz 3. definiuje typ kolekcji obejmujący obie kolumny potrzebne w programie. W wierszach 5. i 6. zadeklarowane są zmienne typu lokalnie zdefiniowanej kolekcji. Instrukcja SELECT wierszach 11. i 12. wykonuje pobieranie masowe danych do zmiennych lv_title i lv_subtitle. Wiersz 15. to deklaracja pętli FOR. Ta pętla rozpoczyna działanie od wartości 1 (jest to początek kolekcji), a kończy na wartości równej liczbie elementów kolekcji. Powtarzana instrukcja w pętli wyświetla tylko jedną z dwóch zmiennych lokalnych. Ten program pokazuje, jak przekazać zestaw wartości do dwóch równoległych kolekcji typów skalarnych. Należy zapewnić synchronizację tych równoległych kolekcji (w przeciwnym razie mogą wystąpić błędy). Nie zachęcam do stosowania tego podejścia, ponieważ jest zbyt kosztowne w utrzymaniu

198

Część I

Podstawy języka PL/SQL

ze względu na czas i nakłady finansowe. Rozwiązanie to należy stosować tylko wtedy, gdy ze względów biznesowych niezbędne jest przenoszenie danych przy użyciu skalarnych typów języka SQL. W innych sytuacjach przy pobieraniu masowym lepiej korzystać z docelowych kolekcji rekordów.

Docelowe kolekcje rekordów Obecne ograniczenia związane z tworzeniem kolekcji w języku SQL sprawiają, że kolekcji rekordów można używać tylko w języku PL/SQL. Oznacza to, że można je stosować tylko w tych programach, które działają w całości w środowisku PL/SQL. Można jednak opakować takie kolekcje SQL-a w potokowe funkcje tabelowe (opisane w rozdziale 8.), aby przekształcić je na kolekcje typów obiektowych SQL-a. Choć można zadeklarować rekordy i kolekcje rekordów języka PL/SQL jako typy danych w pozbawionych ciała pakietach tego języka, nie można ich używać w SQL-u. Dzieje się tak, ponieważ typy danych dostępne tylko w języku PL/SQL nie mogą być parametrami funkcji i procedur wywoływanych w instrukcjach SQL-a. Takie typy danych nie mogą też być zwracane z funkcji języka PL/SQL, jeśli chcesz je wywoływać w instrukcjach SQL-a. Lepszym rozwiązaniem jest utworzenie typu rekordowego i kolekcji elementów tego typu, ponieważ wtedy można zadeklarować zmienną typu tej kolekcji. To pozwala przypisać listę instrukcji SELECT do jednej zmiennej za pomocą polecenia BULK COLLECT INTO: SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

DECLARE -- Deklaracja rekordu i kolekcji UDT. TYPE title_record IS RECORD ( title VARCHAR2(60) , subtitle VARCHAR2(60)); TYPE title_table IS TABLE OF TITLE_RECORD; -- Deklaracja zmiennej typu kolekcji. lv_fulltitle TITLE_TABLE; BEGIN SELECT item_title , item_subtitle BULK COLLECT INTO lv_fulltitle FROM item; -- Wyświetlanie jednego element struktury. FOR i IN 1..lv_fulltitle.COUNT LOOP dbms_output.put_line('Tytuł ['||lv_fulltitle(i).title||']'); END LOOP; END; /

Wiersze od 3. do 5. definiują dwuelementową strukturę rekordową. Wiersz 6. definiuje typ title_ table; jest to kolekcja struktur rekordowych title_record. Wiersz 8. deklaruje zmienną typu title_ table. Potem instrukcja SELECT w wierszu 12. przypisuje za pomocą operacji masowej dwie kolumny do kolekcji lv_fulltitle.

Kolekcje docelowe ograniczone instrukcją LIMIT Instrukcja LIMIT pozwala określić maksymalną liczbę wierszy zwracanych podczas pobierania masowego. Ograniczyć w ten sposób można tylko liczbę wierszy zwracanych przy użyciu kursorów jawnych w instrukcjach FETCH. Wada tego rozwiązania wiąże się z działaniem aplikacji interaktywnych. Takie aplikacje zwykle wymagają przesłania albo wszystkich rekordów, albo żadnych. Pobranie ich części nie jest dopuszczalne. Omawiana technika jest najbardziej przydatna w programach wsadowych, które zarządzają dużymi etapami transakcji. Tak więc instrukcja LIMIT jest przydatna przy przetwarzaniu wsadowym lub przy pobieraniu bardzo dużych zbiorów danych z kursorów, a nie w programach interaktywnych. Oto wskazówka dotycząca ustawiania wartości instrukcji LIMIT. Jeśli będzie ona zbyt wysoka, baza Oracle poświęci za dużo czasu na zarządzanie kursorem w pamięci, a za mało na wykonywanie głównego zadania. Zadbaj o odpowiednie ustawienie wartości instrukcji LIMIT. Stwierdziłem, że przydatne są wartości między 500 a 1000 wierszy. Wartości poniżej 500 nie są warte pisania dodatkowego kodu. W dwóch następnych podpunktach pokazano, jak używać instrukcji LIMIT dla kolekcji równoległych i dla kolekcji rekordów.

Rozdział 5. Struktury sterujące

199

Docelowe kolekcje równoległe Docelowe kolekcje równoległe to synchronizowane kolekcje elementów skalarnych. Kolekcje tego rodzaju mogą przechowywać elementy różnych typów, ale muszą mieć tyle samo wierszy i dopasowane wartości indeksów. We wcześniejszych przykładach stosowano pobieranie masowe za pomocą kursorów niejawnych, jednak można też wykorzystać kursory jawne. Poniższy program pokazuje, jak masowo pobierać dane po 10 wierszy jednocześnie za pomocą kursora jawnego: SQL> DECLARE ... To samo co w przedstawionym wcześniej przykładzie z kolekcjami równoległymi ... 7 -- Deklaracja kursora jawnego. 8 CURSOR c IS 9 SELECT item_title AS title 10 , item_subtitle AS subtitle 11 FROM item; 12 BEGIN 13 OPEN c; 14 LOOP 15 -- Pobieranie danych z kursora jawnego do kolekcji rekordów. 16 FETCH c 17 BULK COLLECT INTO lv_title 18 , lv_subtitle LIMIT 10; 19 EXIT WHEN lv_title.COUNT = 0; ... To samo co w poprzedniej pętli FOR wyświetlającej dane ... 25 END LOOP; 26 END; 27 /

Wiersze od 8. do 11. dodają definicję kursora jawnego. W wierszach od 16. do 18. ten kursor jest otwierany i wykonywana jest na nim operacja pobierania masowego. Z powodu dodanej klauzuli LIMIT pobieranych jest tylko po 10 wierszy. Ta klauzula sprawia, że w iteracjach pętli z otwartego kursora pobierane są dostępne wiersze w porcjach po nie więcej niż 10 wierszy. Jednak w ostatniej iteracji pętla nie powinna pobrać już żadnych danych. Zwróć uwagę na nowy warunek wyjścia w wierszu 19. Sprawdza on, czy kolekcja jest pusta. Jeśli tak jest, następuje wyjście z pętli. Użyty do tego kod odpowiada poniższej instrukcji opartej na zwykłym kursorze: EXIT WHEN c%NOTFOUND;

Choć 10 to mała liczba, technika ta ma ograniczać zajmowaną pamięć i minimalizować liczbę operacji przetwarzania, wykonywania i pobierania danych. Lepszą wartością jest 250 lub 500, ponieważ nie tworzy ona wąskiego gardła na etapie przetwarzania ani nie przeciąża zasobów obliczeniowych bazy danych.

Docelowe kolekcje rekordów Jeśli nie od razu, to zapewne w przyszłości zauważysz, że zmienne typu kolekcji rekordów zwykle są wygodniejsze w użyciu niż równoległe kolekcje wartości skalarnych. Następny program pokazuje, jak zarządzać pobieraniem masowym przy korzystaniu z kursora jawnego i zmiennej typu kolekcji rekordów. W przykładowym programie używana jest klauzula LIMIT ograniczająca liczbę wierszy masowo pobieranych do 10 w każdej iteracji pętli. Oto kod tego programu: SQL> DECLARE ... To samo co w poprzednim przykładzie pobierania masowego do kolekcji rekordów ... 9 -- Deklaracja kursora jawnego. 10 CURSOR c IS 11 SELECT item_title AS title 12 , item_subtitle AS subtitle 13 FROM item; 14 BEGIN 15 OPEN c; 16 LOOP 17 -- Pobieranie kursora jawnego do kolekcji rekordów.

200 18 19 20 25 26 27

Część I

Podstawy języka PL/SQL

FETCH c BULK COLLECT INTO lv_fulltitle LIMIT 10; EXIT WHEN lv_fulltitle.COUNT = 0; ... To samo co w poprzedniej pętli FOR wyświetlającej dane ... END LOOP; END; /

Wiersze od 10. do 13. tworzą kursor jawny, a w wierszu 19. dane są pobierane masowo do kolekcji rekordów. Klauzula LIMIT sprawia, że przy każdym przechodzeniu po zbiorze wyników z kursora przetwarzanych jest nie więcej niż 10 wierszy. Wiersz 20. sprawdza, czy przetworzono już wszystkie wiersze. Jest to jedyny moment, gdy kolekcja lv_fulltitle powinna zawierać zero elementów.

Instrukcje FORALL Pętla FORALL służy do obsługi kolekcji bazy Oracle. Umożliwia masowe wstawianie, aktualizowanie i usuwanie danych. Ten punkt opisuje używanie instrukcji FORALL i jest oparty na wprowadzeniu do kolekcji przedstawionym w rozdziale 3. Dokładne omówienie kolekcji znajdziesz w rozdziale 6. Przedstawione tu programy to rozwinięcie przykładów opisujących masowe pobieranie danych do kolekcji. Do działania kodu z tego punktu potrzebna jest tabela docelowa item_temp, której dotyczyć będą instrukcje INSERT, UPDATE i DELETE. Aby ją utworzyć, należy wykonać poniższą instrukcję: SQL> 2 3 4

CREATE TABLE item_temp ( item_id NUMBER , item_title VARCHAR2(62) , item_subtitle VARCHAR2(60));

Dalsze podpunkty pozwalają podzielić przykładowy kod i ilustrują kolejno wstawianie, aktualizowanie i usuwanie danych przy użyciu instrukcji FORALL. Po wykonaniu tych operacji można usunąć tabelę item_table z bazy.

Instrukcja INSERT Wstawianie masowe wymaga podania w klauzuli VALUES kolekcji typów skalarnych. Oznacza to, że można wykorzystać równoległe kolekcje typów skalarnych lub zastosować notację z kropką i podać pola z kolekcji rekordów. Przy próbie wstawiania danych bezpośrednio do rekordów pojawi się błąd ORA-00947, informujący o niewystarczającej liczbie elementów w tej klauzuli. Poniższy kod przedstawia masowe wstawienie danych do kolekcji typów skalarnych: SQL> DECLARE 2 -- Definicja typu rekordowego. 3 TYPE item_record IS RECORD 4 ( id NUMBER 5 , title VARCHAR2(62) 6 , subtitle VARCHAR2(60)); 7 -- Definicja kolekcji typu rekordowego. 8 TYPE item_table IS TABLE OF ITEM_RECORD; 9 -- Deklaracja zmiennej typu kolekcji. 10 lv_fulltitle ITEM_TABLE; 11 -- Deklaracja kursora jawnego. 12 CURSOR c IS 13 SELECT item_id AS id 14 , item_title AS title 15 , item_subtitle AS subtitle 16 FROM item; 17 BEGIN 18 OPEN c; 19 LOOP 20 FETCH c 21 BULK COLLECT INTO lv_fulltitle LIMIT 10; 22 EXIT WHEN lv_fulltitle.COUNT = 0; 23 FORALL i IN lv_fulltitle.FIRST..lv_fulltitle.LAST 24 INSERT INTO item_temp

Rozdział 5. Struktury sterujące 25 26 27 28 29 30 31 32 33

201

VALUES ( lv_fulltitle(i).id , lv_fulltitle(i).title , lv_fulltitle(i).subtitle ); /* Wyświetla liczbę wierszy wstawionych w poszczególnych iteracjach. */ dbms_output.put_line('Wstawiono: ['||SQL%ROWCOUNT||']'); END LOOP; END; /

Instrukcja FORALL z wiersza 23. wczytuje kolekcję lv_fulltitle, przy czym liczba wierszy jest ograniczona za pomocą klauzuli LIMIT z instrukcji BULK COLLECT. To sprawia, że przy wykonywaniu polecenia INSERT instrukcja FORALL przetwarza nie więcej niż 10 wierszy. W tym poleceniu podawany jest indeks elementu z kolekcji i selektor komponentów (.), po którym wskazywane jest pole używanego typu rekordowego. Wiersz 30. pokazuje, jak zastosować atrybut %ROWCOUNT operacji masowych w jawnej instrukcji INSERT dotyczącej tabeli item_temp. Atrybut SQL%ROWCOUNT dotyczy ostatniej wykonanej instrukcji DML. Aby istotnie przyspieszyć działanie programu, należy umieścić instrukcję COMMIT po zakończeniu pętli. W przeciwnym razie operacje będą zatwierdzane po każdym wstawieniu danych. Czasem rozmiar danych sprawia, że bardziej korzystne jest użycie COMMIT jako ostatniej instrukcji pętli. Należy sprawdzić wpływ wielkości danych na program i omówić to zagadnienie z administratorem bazy. Jeśli wartość klauzuli LIMIT jest mała (na przykład 10), zalecam, aby w produkcyjnej wersji kodu nigdy nie wywoływać instrukcji COMMIT w pętli. Jeżeli jednak ta wartość wynosi 500 – 1000, warto zatwierdzać operacje w pętli. Jeśli nie będziesz zatwierdzał w pętli operacji dotyczących zbioru rekordów, kod będzie negatywnie wpływał na działanie bazy danych, ponieważ wymusi na niej zarządzanie niepotrzebnym wycofywaniem operacji.

Instrukcja UPDATE Aktualizacja masowa wymaga podania równoległych kolekcji skalarów lub referencji do pól z kolekcji rekordów. Jak opisano to w poprzednim podpunkcie, równoległych kolekcji skalarów lub referencji do pól z kolekcji rekordów trzeba używać także w klauzuli VALUES w instrukcjach INSERT. Poniższy program używa kolekcji skalarów w operacji aktualizacji masowej: SQL> DECLARE ... To samo co w przykładzie ilustrującym wstawianie masowe ... 23 -- Aktualizacja masowa w pętli FORALL. 24 FORALL i IN lv_fulltitle.FIRST..lv_fulltitle.LAST 25 UPDATE item_temp 26 SET item_id = lv_fulltitle(i).id 27 , item_title = lv_fulltitle(i).title 28 , item_subtitle = lv_fulltitle(i).subtitle 29 WHERE item_id = lv_fulltitle(i).id 30 AND NOT (item_title = lv_fulltitle(i).title AND 31 item_subtitle = lv_fulltitle(i).subtitle); ... To samo co w przykładzie ilustrującym wstawianie masowe ... END; /

Instrukcja FORALL w wierszu 24. wczytuje kolekcję lv_fulltitle i aktualizuje wiersze, jeśli warunek z klauzuli WHERE jest spełniony. Podobnie jak przy stosowaniu instrukcji INSERT, należy się zastanowić, gdzie umieścić instrukcję COMMIT w kodzie do masowej aktualizacji rekordów.

Instrukcja DELETE W ramach pętli FORALL masowe instrukcje DELETE działają tak samo jak masowe instrukcje INSERT i UPDATE. Wcześniejsze omówienie dotyczyło głównie korzystania z równoległych kolekcji skalarnych i kolekcji typów rekordowych do masowego wstawiania i aktualizowania danych. Choć w instrukcjach INSERT i UPDATE zaleca się korzystanie z kolekcji typów rekordowych, w instrukcjach DELETE nie jest to najlepsze rozwiązanie. Tu można bezpiecznie wykorzystać jedną skalarną kolekcję kluczy sztucznych do identyfikowania wierszy w poprawnie zdefiniowanej tabeli.

202

Część I

Podstawy języka PL/SQL

Poniższy przykładowy kod używa kolekcji skalarów (liczb) w operacji usuwania masowego: SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

DECLARE -- Definicja tabeli skalarnej (przechowującej liczby). TYPE id_table IS TABLE OF NUMBER; -- Deklaracja zmiennej typu kolekcji. lv_id ID_TABLE; /* Deklaracja kursora jawnego, który zwraca wartości klucza głównego z kolekcji. */ CURSOR c IS SELECT item_id AS id FROM item; BEGIN OPEN c; LOOP FETCH c BULK COLLECT INTO lv_id LIMIT 10; EXIT WHEN lv_id.COUNT = 0; -- Instrukcja usuwania masowego w pętli FORALL. FORALL i IN lv_id.FIRST..lv_id.LAST DELETE FROM item_temp WHERE item_id = lv_id(i); /* Wyświetla liczbę wierszy usuniętych w iteracji. */ dbms_output.put_line('['||SQL%ROWCOUNT||']'); END LOOP; END; /

Wiersz 3. definiuje kolekcję skalarów id_table, a w wierszu 5. znajduje się deklaracja zmiennej lokalnej typu tej kolekcji. Wiersze od 18. do 21. ilustrują, jak napisać instrukcję usuwania masowego z wykorzystaniem kolumny klucza głównego item_id. W tym podrozdziale opisano używanie masowych operacji na kolekcjach w instrukcji FORALL. Omówione tu polecenia DML pozwalają poprawić wydajność w porównaniu z tradycyjnym wykonywaniem instrukcji na poszczególnych wierszach. Warto wykorzystać możliwości, jakie daje to rozwiązanie, aby zwiększyć przepustowość aplikacji.

Obsługa atrybutu %BULK_EXCEPTIONS Na początku podrozdziału „Instrukcje masowe” w tabeli 5.8 przedstawiono dwa atrybuty dotyczące operacji masowych: %BULK_ROWCOUNT i %BULK_EXCEPTIONS. Połączenie członu SQL (oznacza on ostatnią niejawną instrukcję DML) z atrybutem %BULK_ROWCOUNT (SQL%BULK_ROWCOUNT) pozwala pobrać liczbę wierszy wstawionych, zaktualizowanych lub usuniętych w instrukcji masowej. Atrybut %BULK_ROWCOUNT zwraca łączną lub ograniczoną limitem liczbę wierszy zmodyfikowanych w ostatniej instrukcji INSERT, UPDATE lub DELETE. Atrybut %BULK_EXCEPTIONS zwraca trzy pola: COUNT, ERROR_INDEX i ERROR_CODE. Pole ERROR_CODE zawiera dodatnią liczbę całkowitą, co oznacza, że trzeba ją pomnożyć przez –1 przed sprawdzeniem odpowiedniego komunikatu o błędzie. Aby uzyskać dostęp do pól, trzeba najpierw dodać do atrybutu %BULK_EXCEPTIONS człon SQL, a następnie podać przed nazwą pola selektor komponentów (.): SQL%BULK_EXCEPTIONS.COUNT

Model obsługi wyjątków w bazach Oracle jest podobnie prosty jak w większości języków programowania. Program po natrafieniu na błąd zgłasza wyjątek. Sekcja wyjątków przechwytuje i obsługuje zgłoszone wyjątki. Zwykle wyjątki zatrzymują wykonywanie programu. Przy przetwarzaniu masowym są dwie możliwości. Domyślne rozwiązanie polega na wykorzystaniu standardowego modelu obsługi wyjątków i zatrzymaniu pracy programu oraz zgłoszeniu jednego błędu. Drugie podejście pozwala zarejestrować błąd i kontynuować operację masową do momentu zakończenia wykonywania instrukcji INSERT, UPDATE lub DELETE. Aby zmienić domyślne rozwiązanie, należy dodać do instrukcji FORALL klauzulę SAVE EXCEPTIONS. Wyjątki związane z operacjami masowymi mają kod błędu ORA-24381. Aby zarządzać takimi wyjątkami, trzeba zdefiniować wyjątek użytkow-

Rozdział 5. Struktury sterujące

203

nika (zobacz krótki opis w rozdziale 3. i szczegółowe omówienie w rozdziale 7.), ponieważ w pakiecie standard nie ma wbudowanych wyjątków dla operacji masowych. Poniższy przykład pokazuje, jak obsługiwać wyjątki w operacjach masowych. Zmieniono tu kilka rzeczy w porównaniu z poprzednimi przykładami. Inny jest tu typ rekordowy, a ograniczenie niepowtarzalności w kolumnach item_title i item_type powoduje zgłoszenie błędów w momencie uruchomienia programu. Te błędy są ważne, ponieważ pozwalają zobaczyć, jak obsługiwać wyjątki w operacjach masowych. SQL> 2 3 4 5 6 7 8 9 10 11 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

DECLARE /* Definicja typu rekordowego. */ TYPE item_record IS RECORD ( id NUMBER , title VARCHAR2(62) , type VARCHAR2(60)); /* Definicja kolekcji elementów typu rekordowego. */ TYPE item_table IS TABLE OF ITEM_RECORD; /* Deklaracja zmiennej typu kolekcji. */ lv_fulltitle ITEM_TABLE; /* Deklaracja kursora jawnego. */ CURSOR c IS SELECT item_id AS id , item_title AS title , item_type AS type FROM item; /* Deklaracja błędu operacji masowej i powiązanie go z kodem błędu Oracle. */ bulk_error EXCEPTION; PRAGMA EXCEPTION_INIT(bulk_error, -24381); BEGIN OPEN c; LOOP FETCH c BULK COLLECT INTO lv_fulltitle LIMIT 5; EXIT WHEN lv_fulltitle.COUNT = 0; FORALL i IN lv_fulltitle.FIRST..lv_fulltitle.LAST SAVE EXCEPTIONS INSERT INTO item_temp VALUES ( lv_fulltitle(i).id , lv_fulltitle(i).title , lv_fulltitle(i).type ); /* Wyświetlanie liczby wierszy wstawionych w iteracji. */ dbms_output.put_line( '['||SQL%ROWCOUNT||'] wstawionych wierszy'); END LOOP; EXCEPTION WHEN bulk_error THEN /* Wyświetlanie liczby błędów zgłoszonych w operacji masowej. */ dbms_output.put_line( '['||SQL%ROWCOUNT||'] wstawionych wierszy'); /* Wyświetlanie poszczególnych błędów. */ FOR i IN 1..SQL%BULK_EXCEPTIONS.COUNT LOOP dbms_output.put_line('[' || SQL%BULK_EXCEPTIONS(i).ERROR_INDEX ||'] [' || SQLERRM(-1 * SQL%BULK_EXCEPTIONS(i).ERROR_CODE) ||']'); END LOOP; END; /

Wiersze 18. i 19. to deklaracja wyjątku użytkownika i dyrektywa prekompilatora, która wiąże wyjątek ORA-24381 z blokiem obsługi błędów bulk_error. W wierszu 24. limit dla operacji wstawiania masowego jest ustawiany na pięć wierszy dla każdego wywołania instrukcji z pętli FORALL. Na końcu instrukcji FORALL w wierszu 26. znajduje się klauzula SAVE EXCEPTIONS, co zapewnia, że wyjątki są rejestrowane w czasie, gdy pętla FORALL kończy wykonywanie masowej instrukcji INSERT.

204

Część I

Podstawy języka PL/SQL

W wierszu 45. wywoływana jest funkcja SQLERRM ze skomplikowanym parametrem. Parametr tego wywołania powinien być liczbą ujemną, natomiast pole error_code atrybutu %BULK_EXCEPTIONS zwraca liczbę dodatnią. Dlatego należy zastosować mnożenie przez -1. Ten program generuje następujące dane wyjściowe: [5] [5] [5] [3] [4] [5]

wstawionych wstawionych wstawionych wstawionych [ORA-00001: [ORA-00001:

wierszy wierszy wierszy wierszy unique constraint (.) violated] unique constraint (.) violated]

Blok anonimowy przetwarza pięć wierszy dla każdej masowej instrukcji INSERT. W ostatniej instrukcji z powodzeniem wstawiane są trzy wiersze, natomiast dwa wiersze są odrzucane. Naruszają one ograniczenie UNIQUE bazy danych (zobacz dodatek A). Wykonanie czwartej instrukcji INSERT powoduje zgłoszenie wyjątku ORA-24381 i przekazanie sterowania do sekcji wyjątków. W tym punkcie zobaczyłeś, jak zarządzać masowymi instrukcjami DML. Powtórzenie materiału W tym podrozdziale opisano następujące zagadnienia dotyczące instrukcji masowych:  Przy przetwarzaniu masowym można wykorzystać równoległe kolekcje skalarne lub kolekcje rekordowych typów danych.  Instrukcja BULK COLLECT umożliwia pobranie wierszy z kursora do kolekcji. Można przy tym używać pojedynczych lub równoległych kolekcji skalarnych oraz kolekcji rekordów.  PL/SQL udostępnia atrybuty operacji masowych: %BULK_EXCEPTIONS(i) i %ROWCOUNT(i).  Instrukcja FORALL umożliwia pobranie kolekcji i przekazanie jej do instrukcji INSERT, UPDATE lub DELETE.  Atrybut %ROWCOUNT działa dla wstawiania, aktualizowania i usuwania masowego, natomiast w instrukcjach MERGE nie ma pewności, które wiersze zostały wstawione lub zaktualizowane.  Kolekcje rekordów sprawdzają się najlepiej w instrukcjach FORALL z poleceniami INSERT lub UPDATE.  Pojedyncze kolekcje skalarne są najlepszym wyborem w instrukcjach FORALL z poleceniem DELETE.  Domyślnie obsługa wyjątków w bazach Oracle powoduje zatrzymanie programu i zgłoszenie jednego błędu. To rozwiązanie nie współdziała dobrze z przetwarzaniem masowym. Atrybut %BULK_ EXCEPTIONS pozwala zastąpić domyślny proces obsługi wyjątków w instrukcji FORALL, aby przechwytywać błędy dotyczące problematycznych wierszy i jednocześnie z powodzeniem przetwarzać pozostałe wiersze.

Pomocnicze skrypty Tu znajdziesz opis dostępnych w witrynie wydawnictwa Helion programów pomocniczych dotyczących tego rozdziału.  Plik conditional_logic.sql zawiera krótkie programy związane z podrozdziałem „Instrukcje warunkowe”.  Plik iterative_logic.sql zawiera krótkie programy związane z podrozdziałem „Instrukcje iteracyjne”.  Plik bulk_processing_logic.sql zawiera krótkie programy związane z podrozdziałem „Instrukcje masowe”.

Podsumowanie W tym rozdziale omówiono struktury sterujące języka PL/SQL. Programista powinien wiedzieć, jak skutecznie używać instrukcji warunkowych i iteracyjnych, a także jak tworzyć kursory i zarządzać nimi w programach w tym języku.

Rozdział 5. Struktury sterujące

205

Test wiedzy Test wiedzy to zestaw pytań typu „prawda czy fałsz” i wielokrotnego wyboru, dzięki którym sprawdzisz, jak dobrze opanowałeś materiał z poszczególnych rozdziałów. Odpowiedzi na pytania znajdziesz w dodatku I.

Prawda czy fałsz? 1. __Iloczyn logiczny pozwala ustalić, czy dwa wyrażenia (lub większa ich liczba) są jednocześnie

prawdziwe. 2. __Suma logiczna pozwala ustalić, czy jedno lub drugie wyrażenie jest w danym momencie praw-

dziwe. 3. __Przetwarzanie skrócone stosuje się przy obliczaniu sumy logicznej. 4. __W bazach danych zawsze używana jest logika dwuwartościowa. 5. __Instrukcja CASE z wyszukiwaniem pozwala korzystać z selektorów znakowych i liczbowych. 6. __W prostej instrukcji CASE można korzystać z selektorów liczbowych. 7. __W ramach kompilacji warunkowej można stosować opcje takiej kompilacji. 8. __Instrukcja CONTINUE umożliwia pominięcie części powtarzanego kodu w pętli. 9. __Instrukcja SELECT INTO to przykładowe polecenie tworzące kursor jawny. 10. __Za pomocą instrukcji FORALL można wykonywać masowe instrukcje INSERT.

Pytania wielokrotnego wyboru 11. Jaki wynik może dać instrukcja warunkowa obejmująca dwa operandy? Poprawnych może być

kilka odpowiedzi. A. True w porównaniu obejmującym same wartości różne od null. B. Różny od true w porównaniu obejmującym same wartości różne od null. C. True w porównaniu obejmującym jedną lub więcej wartości null. D. Różny od true w porównaniu obejmującym jedną lub więcej wartości null. E. True dla wartości null. 12. Które z poniższych konstrukcji są pętlami mającymi tylko warunek na wejściu? Poprawnych może być kilka odpowiedzi. A. Prosta pętla oparta na przedziale. B. Pętla FOR oparta na przedziale. C. Pętla WHILE. D. Pętla DO UNTIL. E. Pętla DO WHILE. 13. Które z poniższych pętli języka PL/SQL mają warunek wejścia i wyjścia? Poprawnych może być kilka odpowiedzi. A. Pętle FOR oparte na przedziale. B. Pętle FOR oparte na kursorze. C. Pętle proste. D. Pętle DO WHILE. E. Pętle WHILE. 14. Które z poniższych konstrukcji to pętle mające wyłącznie warunek wyjścia? Poprawnych może być kilka odpowiedzi.

206

Część I

Podstawy języka PL/SQL

A. Pętla prosta oparta na kursorze. B. Pętla prosta oparta na przedziale. C. Pętla FOR oparta na kursorze. D. Pętla WHILE. E. Pętla FOR oparta na przedziale. 15. Które z poniższych kolekcji najlepiej sprawdzają się przy masowym usuwaniu danych z dobrze

zdefiniowanej (znormalizowanej) tabeli, w której używany jest jednokolumnowy sztuczny klucz główny? Poprawnych może być kilka odpowiedzi. A. Równoległe kolekcje skalarne. B. Jedna kolekcja elementów skalarnych. C. Jedna kolekcja elementów rekordowych. D. Wszystkie z powyższych. E. Żadne z powyższych.

ROZDZIAŁ

6

Kolekcje

Kolekcje to tablice i listy — tak się je nazywa od czasu utworzenia Javy. Tablica to kolekcja o stałej liczbie elementów tego samego typu. Lista to kolekcja o dowolnej liczbie elementów tego samego typu. Listy zwykle dają większą swobodę niż tablice, ponieważ nie trzeba z góry wiedzieć, ile elementów się w nich znajdzie. Baza Oracle Database 12c udostępnia bardzo rozbudowany system kolekcji. Umożliwia on tworzenie kolekcji w językach SQL i PL/SQL. Za pomocą tego systemu można tworzyć odpowiedniki tablic, zbiorów bez powtórzeń, zbiorów z powtórzeniami, tablic z haszowaniem i tablic nienumerowanych. Ten rozdział obejmuje cztery podrozdziały. W podrozdziale „Wprowadzenie do kolekcji” opisano system kolekcji bazy Oracle Database 12c. Z podrozdziału „Typy obiektowe: tablice VARRAY i tabele zagnieżdżone” dowiesz się, jak tworzyć kolekcje w postaci tablic VARRAY i tabel zagnieżdżonych. W podrozdziale „Tablice asocjacyjne” wyjaśniono, jak korzystać ze struktur łączących klucze z wartościami. W podrozdziale „API Collection” znajdziesz omówienie interfejsu API związanego z typami kolekcji baz Oracle, a także przykłady jego stosowania.

Wprowadzenie do kolekcji Od wersji Oracle Database 8i dostępne są trzy rodzaje kolekcji. Dwa z nich to kolekcje SQL-a, a jeden jest dostępny tylko w języku PL/SQL. W Oracle tablice SQL-a mają postać tablic VARRAY, a listy SQL-a to tabele zagnieżdżone (lub po prostu tabele). Kolekcja dostępna tylko w języku PL/SQL to tablica asocjacyjna, nazywana też tabelą indeksowaną. Działa ona jak lista. Kolekcje SQL-a mają indeksy liczbowe, natomiast tablice asocjacyjne mogą mieć indeksy liczbowe i znakowe. Kolekcje SQL-a można stosować w bazach Oracle w językach SQL i PL/SQL. Kolekcje SQL-a są typami obiektowymi z poziomu schematu. Choć kolekcje SQL-a można stosować w języku PL/SQL, najlepiej korzystać z nich w SQL-u, ponieważ typy danych z SQL-a są dostępne w obu omawianych językach. Nie dotyczy to tablic asocjacyjnych; są one dostępne wyłącznie w języku PL/SQL. Oracle Database 12c umożliwia korzystanie ze złożonych typów danych języka PL/SQL w zagnieżdżonych instrukcjach SQL-a. Najpierw jednak trzeba zadeklarować lokalną zmienną typu kolekcji z języka PL/SQL w lokalnym lub zewnętrznym bloku tego języka. Następnie trzeba umieścić instrukcję SQL-a w lokalnym bloku języka PL/SQL. Kompletny przykład znajdziesz w punkcie „Stosowanie w SQL-u typów danych specyficznych dla języka PL/SQL” w rozdziale 2. Oprócz ograniczonego zasięgu tablice asocjacyjne różnią się od tablic VARRAY i tabel zagnieżdżonych w czterech innych aspektach: inicjowania, przypisywania, indeksów i bazowych typów danych. Różnice między poszczególnymi rodzajami kolekcji opisano w tabeli 6.1.

208

Część I

Podstawy języka PL/SQL

Tabela 6.1. Różnice między kolekcjami SQL-a a tablicami asocjacyjnymi Kolekcje SQL-a

Tablice asocjacyjne

Zasięg

Można definiować w SQL-u lub w języku PL/SQL.

Są definiowane tylko w języku PL/SQL.

Inicjowanie

Wymagają zainicjowania przed użyciem.

Nie wymagają inicjowania.

Przypisywanie

Pamięć jest wstępnie przydzielana przed przypisaniem wartości. Można wstępnie przydzielić pamięć i przypisać wartości dla więcej niż jednego elementu jednocześnie.

Nie trzeba przydzielać pamięci, ponieważ indeksy i wartości są ręcznie ustawiane dla poszczególnych wierszy.

Indeks

Indeksami są kolejne liczby całkowite (w tabelach zagnieżdżonych jest tak przynajmniej na początku), tak więc kolekcje SQL-a to tablice i listy gęste.

Jako indeksy można stosować liczby całkowite i łańcuchy znaków. Wartości indeksu mogą mieć dowolną kolejność, dlatego tablice asocjacyjne to listy rzadkie.

Bazowy typ danych

Można zastosować dowolny typ skalarny SQL-a lub typ obiektowy UDT.

Można zastosować dowolny typ skalarny SQL-a lub języka PL/SQL albo typ rekordowy języka PL/SQL.

Tabele języka PL/SQL W dokumentacji Oracle Database PL/SQL Language Reference 12c Release 1 napisano, że tablice asocjacyjne (ang. associative arrays) były kiedyś nazywane tabelami języka PL/SQL (ang. PL/SQL tables) lub tabelami indeksowanymi (ang. index-by tables). Dlatego aby zachować zgodność z nazwami stosowanymi przez firmę Oracle, w tej książce używane jest pojęcie tablice asocjacyjne. Jeśli jednak zaglądasz na fora poświęcone produktom tej firmy, nie zdziw się, gdy natrafisz na jedną ze starszych nazw.

W rozdziale 4. opisano trzy złożone typy danych. Dwa z nich to obiekty UDT i typy rekordowe języka PL/SQL, a trzecim są kolekcje. Najprostsze są kolekcje skalarnych typów danych (na przykład liczb, łańcuchów znaków lub dat). W takich kolekcjach dany typ skalarny jest typem bazowym. Wszystkie kolekcje to typy final, co oznacza, że nie można tworzyć typów pochodnych od nich. W dokumentacji produktów Oracle (Oracle Database PL/SQL Language Reference i Oracle Object-Relational Developer’s Guide) nie wyjaśniono przyczyn zastosowania takiego podejścia. Od wersji Oracle Database 8i dostępny jest interfejs Collection API ułatwiający pracę z kolekcjami i zarządzanie nimi. Tabela 6.2 pochodzi z dokumentacji Oracle Database PL/SQL Language Reference i ilustruje powiązania między typami złożonymi spoza języka PL/SQL a typami złożonymi z tego języka. Tabela 6.2. Typy złożone spoza języka PL/SQL i odpowiadające im typy złożone z tego języka Typ złożony spoza języka PL/SQL

Analogiczny typ złożony z języka PL/SQL

Tablica z haszowaniem

Tablica asocjacyjna

Tablica nieuporządkowana

Tablica asocjacyjna

Zbiór bez powtórzeń

Tabela zagnieżdżona

Zbiór z powtórzeniami

Tabela zagnieżdżona

Tablica

Tablica VARRAY

Rekordowy typ danych zwykle obejmuje zestaw powiązanych pól (podobnie jak wiersz tabeli). To oznacza, że struktura rekordowa przypomina strukturę tabeli, a kolekcja rekordów jest jak dostępna tylko w języku PL/SQL tabela przechowywana w pamięci. Między typami obiektowymi i rekordowymi występują zarówno drobne, jak i istotne różnice. Drobne różnice występują, gdy programista definiuje typ obiektowy SQL-a bez metod. Wtedy typ SQL-a jest podobny do typu rekordowego z języka PL/SQL. Niewielką różnicą w zakresie przypisywania danych jest to, że w typach obiektowych trzeba zastosować konstruktor (przypomina to używanie klau-

Rozdział 6. Kolekcje

209

zuli VALUES w instrukcji INSERT). Wartości w konstruktorze muszą mieć pozycje i typy danych dostosowane do listy atrybutów określonego typu obiektowego. Oracle sprawdza pozycje i typy danych atrybutów na podstawie niejawnej analizy automatycznie wygenerowanego domyślnego konstruktora (szczegółowy opis tego procesu znajdziesz w rozdziale 11.). Przed zapoznaniem się z poważniejszymi różnicami warto zrozumieć, jak i gdzie można definiować oraz stosować typy obiektowe języków SQL i PL/SQL oraz typy rekordowe języka PL/SQL. Typy obiektowe można definiować jako obiekty poziomu schematu w dowolnej bazie CDB lub PDB, a także w sekcji deklaracji języka PL/SQL. Typy rekordowe definiuje się w sekcji deklaracji języka PL/SQL. Typy obiektowe i rekordowe można stosować w blokach anonimowych i nazwanych, które mają dostęp do sekcji deklaracji zawierającej definicję danego typu. Typy obiektowe i rekordowe są dostępne lokalnie, jeśli są zdefiniowane w lokalnej sekcji deklaracji. Są też dostępne w zagnieżdżonych programach języka PL/SQL, jeśli zostały zdefiniowane w zewnętrznej sekcji deklaracji. Typy obiektowe i rekordowe są dostępne na większą skalę, jeśli zostały zdefiniowane w specyfikacji pakietu. Wtedy można z nich korzystać w dowolnym programie języka PL/SQL uruchomionym przez użytkownika z uprawnieniami do wykonywania danego pakietu. Typy obiektowe mogą być typem: kolumny tabeli, struktury w tabeli, parametru w funkcji lub procedurze oraz wartości zwracanej przez funkcję. Typy rekordowe mogą być typami parametrów programów składowanych i wartości zwracanych przez funkcje. Oczywiście w kolekcjach typów obiektowych i rekordowych obowiązują te same ograniczenia co w samych typach obiektowych i rekordowych. A oto istotne różnice między typami obiektowymi i rekordowymi:  Typy obiektowe można stosować jako typy kolumn i struktur tabel. Typy rekordowe nie mogą pełnić tych funkcji.  Typy obiektowe można stosować jako typy parametrów funkcji i procedur wywoływanych w językach SQL i PL/SQL. Typy rekordowe można stosować wyłącznie w wywołaniach w języku PL/SQL.  Kolekcje elementów typów obiektowych mogą być używane jako typ kolumny, ale już nie jako typ struktur tabeli.  Kolekcje elementów typów obiektowych można stosować w językach SQL i PL/SQL, natomiast kolekcje elementów typów rekordowych są obsługiwane tylko w języku PL/SQL. Podobieństwa i różnice między omawianymi typami bazowymi wpływają na to, jak można używać kolekcji tych typów. Rysunek 6.1 przedstawia możliwości w zakresie używania kolekcji jako parametrów i wartości zwracanych przez funkcje. Zauważ, że typy kolekcji z SQL-a można stosować w SQL-u i w języku PL/SQL z jednym wyjątkiem. Są nim tabele zagregowane, powstające w wyniku zwrócenia kolekcji SQL-a z funkcji lub zwrócenia kolekcji języka PL/SQL z potokowej funkcji tabelowej (więcej szczegółów na ten temat znajdziesz w rozdziale 8.). Tabele zagregowane są bardzo podobne do zbiorów wyników z instrukcji SELECT, dlatego na rysunku 6.1 zostały opatrzone gwiazdką.

Rysunek 6.1. Kontekst, w jakim można używać poszczególnych kolekcji

210

Część I

Podstawy języka PL/SQL

Typy obiektowe: tablice VARRAY i tabele zagnieżdżone Jak wcześniej wspomniano, kolekcje to struktury programu przechowujące zbiory podobnych elementów. Są dwa rodzaje kolekcji: tablice i listy. Tablice zwykle mają określaną w momencie definiowania wielkość fizyczną, natomiast w listach nie ma takiego limitu. W Oracle tablice mają postać tablic VARRAY, a listy — tabel zagnieżdżonych. W Oracle można definiować typy obiektowe z poziomu schematu, przechowujące kolekcje typów skalarnych lub obiektowych. Można też utworzyć kolekcję, która przechowuje typy obiektowe zawierające inne tablice VARRAY lub tabele zagnieżdżone. Niezależnie od tego, czy kolekcja zawiera elementy skalarne, czy złożone, typ tych elementów jest nazywany typem bazowym. Kolekcja SQL-a z elementami typu skalarnego to kolekcja ADT (ang. Attribute Data Type), natomiast kolekcja elementów typu obiektowego to kolekcja UDT (ang. User-Defined Type). Ważną kwestią jest przydział pamięci dla nowych elementów kolekcji SQL-a. Przydział pamięci powoduje zwiększenie wartości następnego indeksu o jeden. To sprawia, że kolekcje typu obiektowego są kolekcjami gęstymi. W następnych punktach opisano tablice VARRAY i tabele zagnieżdżone SQL-a.

Tablice VARRAY Tablice VARRAY to jednowymiarowe struktury o określonej maksymalnej liczbie elementów. Wszystkie elementy takich kolekcji są tego samego typu. Jak wspomniano, typ elementów to typ bazowy tablicy VARRAY. Oto prototyp instrukcji tworzącej tablicę VARRAY SQL-a: TYPE nazwa_typu IS {VARRAY | VARYING ARRAY}(maks_wielkość) OF typ_danych [NOT NULL];

Domyślnie nowe elementy mogą mieć wartość null. Aby uniemożliwić zapisywanie wartości null, należy w definicji kolekcji zastosować klauzulę NOT NULL, choć zalecanym rozwiązaniem jest zezwalanie na występowanie wartości null przy jednoczesnym dbaniu o to, by dodawać elementy różne od null. Blokować wartości null należy tylko wtedy, gdy program ma zgłaszać wyjątki przy próbie przypisania takiej wartości do kolekcji. Jaką nazwę należy stosować? W prototypie pokazano, że można zamiennie stosować instrukcje VARRAY i VARYING ARRAY, przy czym częściej stosowana jest wersja VARRAY. Dlatego jeśli nie stosujesz narzędzi GeSHI (ang. Generic System Highlighter), z których większość wyróżnia instrukcję VARYING ARRAY, a nie VARRAY, powinieneś używać zapisu VARRAY.

Trzyelementową tablicę VARRAY łańcuchów znaków można zdefiniować w SQL-u w następujący sposób: SQL> CREATE OR REPLACE 2 TYPE sql_varray IS VARRAY(3) OF VARCHAR2(20); 3 /

A oto inna składnia tworzenia takich tablic: SQL> CREATE OR REPLACE 2 TYPE sql_varray IS VARYING ARRAY(3) OF VARCHAR2(20); 3 /

W obu sytuacjach w wierszu 1. używana jest składnia SQL-a służąca do tworzenia lub zastępowania typów obiektowych na poziomie schematu. Wiersz 2. definiuje kolekcję w postaci tablicy VARRAY. Składnia z tego wiersza pokazuje, jak definiować takie kolekcje w sekcji deklaracji języka PL/SQL. Wiersz 3. wykonuje i kompiluje utworzony typ.

Rozdział 6. Kolekcje

211

Typy obiektowe można tworzyć i stosować w instrukcjach SQL-a bez stosowania innych komponentów. Choć poniższy przykład nie jest przydatny, ilustruje, jak to zrobić: SQL> SELECT column_value AS "Three Stooges" 2 FROM TABLE(sql_varray('Moe','Larry','Curly'));

Lista SELECT zwraca jedynie pseudokolumnę column_value, zawierającą zagregowane wyniki zwrócone przez kolekcję ADT. Alias tej pseudokolumny (Three Stooges) służy jedynie uatrakcyjnieniu danych wyjściowych. Najważniejsza przy wczytywaniu kolekcji jest funkcja TABLE, o której więcej dowiesz się z dodatku C. Przedstawione zapytanie tworzy trzyelementową kolekcję w wywołaniu funkcji TABLE w wierszu 2. Oto wyniki: Three Stooges --------------Moe Larry Curly

Można uporządkować zwracane wyniki za pomocą klauzuli ORDER BY. Należy w niej wskazać pozycję pseudokolumny column_value: 3 ORDER BY 1;

Można też zmienić domyślną kolejność sortowania, używając funkcji składowej ORDER w implementacji ciała obiektu (szczegółowy opis tego zagadnienia znajdziesz w rozdziale 11.). Jednak zmiana domyślnego sortowania w typach ADT zwykle nie jest dobrym pomysłem. Jedynym wyjątkiem jest sytuacja, gdy programista używa zaszyfrowanego skalarnego typu danych jako bazowego typu danych kolekcji. W języku PL/SQL można zdefiniować trzyelementową tablicę VARRAY łańcuchów znaków w sekcji deklaracji za pomocą poniższej składni: SQL> 2 3 4 5 6 7

DECLARE /* Definicja lokalnej kolekcji PL/SQL. */ TYPE sql_varray IS VARRAY(3) OF VARCHAR2(20); BEGIN ... END; /

Kod z sekcji wykonawczej i wyjątków wygląda tak samo niezależnie od tego, czy tablica VARRAY jest definiowana w SQL-u, czy w języku PL/SQL. Zmodyfikujmy teraz przykładowy kod, aby utworzyć i wykorzystać wartości tablicy VARRAY w kodzie w języku PL/SQL. Tym razem deklarowana jest zmienna lv_stooges. Program zapisuje w niej dwa, a nie trzy łańcuchy znaków. Oto potrzebny kod: SQL> DECLARE 2 /* Deklaracja zmiennej typu kolekcji za pomocą wywołania konstruktora. */ 3 lv_stooges SQL_VARRAY := sql_varray('Moe','Larry'); 4 BEGIN 5 /* Wyświetlanie aktualnej i maksymalnej liczby elementów. */ 6 dbms_output.put_line( 7 'Aktualna liczba: ['||lv_stooges.COUNT||'] '|| 8 'Maksymalna liczba: ['||lv_stooges.LIMIT||']'); 9 10 /* Zajmowanie pamięci i przypisywanie jej do nowego indeksu. */ 11 lv_stooges.EXTEND; 12 13 /* Wyświetlanie aktualnej i maksymalnej liczby elementów. */ 14 dbms_output.put_line( 15 'Aktualna liczba: ['||lv_stooges.COUNT||'] '|| 16 'Maksymalna liczba: ['||lv_stooges.LIMIT||']'); 17 18 /* Przypisywanie nowej wartości. */ 19 lv_collection(lv_stooges.COUNT) := 'Curly'; 20

212 21 22 23 24 25 26

Część I

Podstawy języka PL/SQL

/* Przechodzenie po kolekcji (liczba iteracji jest równa liczbie elementów). */ FOR i IN 1..lv_stooges.COUNT LOOP dbms_output.put_line(lv_stooges(i)); END LOOP; END; /

Wiersz 3. deklaruje zmienną lv_stooges utworzonego wcześniej typu kolekcji (sql_varray). Wywołanie konstruktora typu sql_varray z parametrami w postaci dwóch łańcuchów znaków prowadzi do utworzenia dwuelementowej kolekcji. Można też utworzyć pustą kolekcję, wywołując konstruktor bez parametrów. Wtedy wiersz 3. powinien wyglądać tak: 3 lv_stooges SQL_VARRAY := sql_varray();

Wszystkie tablice VARRAY i tabele zagnieżdżone przyjmują trzy stany: null, pusta i zapełniona. Kolekcja w stanie null jest niezainicjowana. Pusta kolekcja jest zainicjowana, ale nie zawiera żadnych elementów. Kolekcja zapełniona jest zainicjowana i ma przynajmniej jeden element. Do sprawdzania, czy kolekcja jest zainicjowana (należy to zrobić przed rozpoczęciem pracy z nią), służy operator porównywania IS NULL. Operator porównywania IS EMPTY pozwala wykryć puste kolekcje, a funkcja COUNT służy do określania liczby elementów w kolekcji. Ta funkcja jest częścią interfejsu Oracle Collection API i można jej używać dla wszystkich trzech rodzajów kolekcji. Ten interfejs obejmuje też funkcję LIMIT, która pozwala ustalić maksymalną liczbę elementów tablicy VARRAY. Aby zastosować funkcję COUNT lub LIMIT, należy ją poprzedzić selektorem komponentów (.). Wiersze od 6. do 8. wyświetlają wartości funkcji COUNT (2) i LIMIT (3). Te wartości wynikają z tego, że przydzielona jest pamięć na dwa z trzech możliwych elementów używanej tablicy VARRAY. Wiersz 3. rozszerza zajętą pamięć o pamięć na trzeci element, co powoduje automatyczne utworzenie nowego indeksu (3), prowadzącego do elementu null. Wiersze od 14. do 16. wyświetlają wartości funkcji COUNT i LIMIT. Tym razem obie te wartości to 3, ponieważ przydzielona jest pamięć na wszystkie trzy elementy. Wiersz 18. przypisuje wartość do nowego elementu kolekcji. Kolekcje są numerowane od indeksu 1, a funkcja COUNT zwraca indeks ostatniego elementu kolekcji, co pozwala zidentyfikować ów nowy element. Wiersz 23. wyświetla wszystkie elementy kolekcji. Zauważ, w jaki sposób są one identyfikowane. Indeks kolekcji jest umieszczany w zwykłych nawiasach. W wielu innych językach programowania do podawania indeksów używane są nawiasy kwadratowe. W Oracle zdecydowano się na nawiasy zwykłe. Jeśli uniemożliwisz stosowanie wartości null (wymaga to zmiany przedstawionej wcześniej definicji typu sql_varray), próba przypisania takiej wartości w wierszu 19. spowoduje zgłoszenie następującego wyjątku: lv_collection(lv_collection.COUNT) := NULL; * ERROR at line 18: ORA-06550: line 18, column 41: PLS-00382: expression is of wrong type ORA-06550: line 18, column 3: PL/SQL: Statement ignored

Fizyczny limit rozmiaru sprawia, że tablica VARRAY może przechowywać tylko określoną liczbę elementów. Ten limit jest często najważniejszym powodem zastosowania tablic VARRAY lub rezygnacji z nich. Programiści stosują takie tablice, gdy chcą, aby program zgłaszał błąd przy próbie przypisania wartości poza dozwolonym limitem. Jest to wygodny sposób na zgłaszanie wyjątku, gdy dane naruszają regułę „nie więcej elementów niż”. Ponadto nie trzeba wtedy pisać dodatkowego kodu do sprawdzania, czy liczba wyników nie przekracza dozwolonego limitu. Wynika to z tego, że tablice VARRAY robią to automatycznie i po przekroczeniu limitu zgłaszają błąd „poza zakresem”. Aby w przykładowym kodzie wygenerować błąd „poza zakresem”, należy wprowadzić jedną zmianę. Zmodyfikuj wywołanie konstruktora w wierszu 3. i zamiast dwóch elementów podaj trzy (ostatnim jest Shemp, trzecia z postaci z pierwszej obsady serialu The Three Stooges, a poza planem brat Curly’ego): 2

lv_collection SQL_VARRAY := sql_varray('Moe','Larry','Shemp');

Rozdział 6. Kolekcje

213

Teraz w wierszu 10. przy próbie dodania pamięci na element wykraczający poza fizyczny limit trzech elementów zostanie zgłoszony następujący wyjątek: DECLARE * ERROR at line 1: ORA-06532: Subscript outside of limit ORA-06512: at line 10

Ten sam komunikat wystąpi przy próbie dostępu do tablicy VARRAY za pomocą indeksu 0. Wynika to z tego, że tablice VARRAY zawsze są indeksowane od 1. Indeks równy 0 można zastosować w tablicach asocjacyjnych, ponieważ działają one na podstawie innych reguł. Ponieważ w tablicach VARRAY i tabelach zagnieżdżonych używana jest ta sama składnia przy korzystaniu z typów obiektowych SQL-a i typów rekordowych języka PL/SQL, to w celu uniknięcia powtórzeń przykłady dotyczące takich typów przedstawiono tylko w następnym punkcie.

Tabele zagnieżdżone Tabele zagnieżdżone to jednowymiarowe struktury, w których nie występuje ograniczenie liczby przechowywanych elementów — oczywiście z uwzględnieniem limitu zasobów dostępnych dla tabeli. Najważniejsze zasoby, które mogą ograniczać liczbę elementów, to obszar SGA (ang. System Global Area) i obszar PGA (ang. Program Global Area). Elementy w tabelach zagnieżdżonych muszą (podobnie jak w tablicach VARRAY) być tego samego typu. Typ bazowy tabel zagnieżdżonych to (też podobnie jak w tablicach VARRAY) typ danych elementów przechowywanych w takiej kolekcji. Tabele zagnieżdżone mogą zawierać elementy typów skalarnych i złożonych. Oto prototyp używany do tworzenia tabel zagnieżdżonych w SQL-u: TYPE nazwa_typu IS TABLE OF typ_danych [NOT NULL];

W tym punkcie (inaczej niż w omówieniu tablic VARRAY z poprzedniego punktu) zobaczysz, jak używać tabel zagnieżdżonych z elementami typów skalarnych i złożonych. Przyjrzysz się też kolekcjom języka PL/SQL (lokalnym i z poziomu pakietów).

Tabele zagnieżdżone elementów skalarnych W tabelach zagnieżdżonych, podobnie jak w tablicach VARRAY, można zezwolić na dodawanie wartości null lub uniemożliwić ich stosowanie. Domyślnie takie wartości są dopuszczalne. Klauzula NOT NULL pozwala zabronić ich używania. Najlepszym podejściem jest zezwalanie na występowanie wartości null i jednoczesne dbanie o dodawanie tylko elementów o wartości różnej od null (choć niektórzy programiści blokują stosowanie wartości null, aby mieć pewność, że próba jej przypisania spowoduje wyjątek). Tabelę zagnieżdżoną łańcuchów znaków można zdefiniować w SQL-u w następujący sposób: SQL> CREATE OR REPLACE 2 TYPE sql_table IS TABLE OF VARCHAR2(20); 3 /

Składnia SQL-a użyta w wierszu 1. pozwala utworzyć lub zastąpić typ obiektowy z poziomu schematu. Wiersz 2. definiuje tabelę zagnieżdżoną (składnia z tego wiersza pokazuje, jak zdefiniować taką tabelę w sekcji deklaracji języka PL/SQL). Wiersz 3. uruchamia i kompiluje ten typ danych. Podobnie jak w przykładach ilustrujących tablice VARRAY z poprzedniego punktu, tak i tu można pobrać dane z kolekcji za pomocą funkcji TABLE wywołanej w klauzuli FROM zapytania. Oto przykład ilustrujący tworzenie i pobieranie kolekcji w zapytaniu: SQL> SELECT column_value AS "Dúnedain" 2 FROM TABLE(sql_varray('Aragorn','Faramir','Boromir')) 3 ORDER BY 1;

Wywołanie funkcji TABLE w wierszu 3. przekształca tabelę zagnieżdżoną na zagregowany zbiór wyników. Wszystkie zbiory danych zwracane przez zapytania formalnie są zagregowanymi zbiorami wyników. Wartości wyników typu ADT można wyświetlać za pomocą pseudokolumny column_value.

214

Część I

Podstawy języka PL/SQL

W tym przykładzie dla tej kolumny zastosowano alias, Dúnedain, aby sformatować zbiór wyników z zapytania. Oto zwrócone dane: Dúnedain --------Aragorn Boromir Faramir

Problem z kolekcjami ADT polega na tym, że nie da się zlikwidować zagnieżdżenia, gdy są stosowane w tabeli. To oznacza, że elementy można dodawać i aktualizować wyłącznie w kodzie w języku PL/SQL. Z ramki „Używanie języka PL/SQL do aktualizowania elementów typu ADT” z punktu „Aktualizowanie tabel zagnieżdżonych” w dodatku B dowiesz się, jak aktualizować zagnieżdżone elementy ADT. Tu zobaczysz, jak za pomocą funkcji języka PL/SQL dodawać nowe elementy do kolekcji ADT. Choć można wykorzystać także blok anonimowy, moim zdaniem w instrukcji UPDATE lepiej jest zastosować referencję uprzedzającą do funkcji. Oto kod funkcji add_element: SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

CREATE OR REPLACE FUNCTION add_element ( pv_table SQL_TABLE , pv_element VARCHAR2 ) RETURN SQL_TABLE IS /* Deklaracja lokalnej tabeli zagnieżdżonej. */ lv_table SQL_TABLE := sql_table(); BEGIN /* Sprawdzanie, czy kolekcja przekazana jako parametr jest zainicjowana. */ IF pv_table.EXISTS(1) THEN -- Nieoptymalne porównanie. lv_table := pv_table; END IF; /* Sprawdzanie, czy element jest różny od null przed dodaniem go. */ IF pv_element IS NOT NULL THEN /* Zajmowanie pamięci i dodawanie elementu. */ lv_table.EXTEND; lv_table(lv_table.COUNT) := pv_element; END IF; /* Zwracanie tabeli zagnieżdżonej z nowym elementem. */ RETURN lv_table; END; /

Wiersz 2. to deklaracja formalnego parametru typu kolekcji. Wiersz 3. to deklaracja formalnej zwracanej wartości (też typu kolekcji). Kod programu wykonuje jedną z czterech operacji:  Dodanie elementu do kolekcji, w której istnieje przynajmniej jeden inny element.  Dodanie elementu do pustej kolekcji.  Rezygnacja z dodawania elementu null do zapełnionej lub pustej kolekcji.  Inicjowanie pustej kolekcji. Wiersz 6. deklaruje pustą kolekcję lv_table, ponieważ parametr pv_table może być równy null. Zainicjowanie kolekcji lv_table chroni przed błędem niezainicjowanej kolekcji. Wiersz 10. sprawdza, czy parametr pv_table zawiera przynajmniej jeden element. Używana jest do tego funkcja EXISTS z parametrem, który tu powinien być pierwszym indeksem sprawdzanej tabeli zagnieżdżonej. Program wykonuje ten test, ponieważ nie ma sensu przypisywanie pustej kolekcji do zmiennej lokalnej lv_table. Choć funkcja EXIXTS działa, jeśli zastosować parametr w postaci pierwszego indeksu, lepiej jest wykorzystać operator porównywania IS NOT EMPTY: 10

IF pv_table IS NOT EMPTY THEN -- Jest to zawsze NAJLEPSZE ROZWIĄZANIE!

Możesz się zastanawiać: „Dlaczego jest to najlepsze rozwiązanie?”. To świetne pytanie. Odpowiedź nie jest oczywista i dotyczy tylko tabel zagnieżdżonych. Choć tablice VARRAY i tabele zagnieżdżone są

Rozdział 6. Kolekcje

215

indeksowane od wartości 1 oraz są kolekcjami gęstymi, można z nich usuwać elementy. Taka operacja tworzy luki w wartościach indeksów. To dlatego sprawdzanie pierwszego elementu może nie przynieść oczekiwanych efektów. Ta operacja ustala tylko, czy obecny jest pierwszy element. W porównaniach tego rodzaju cel jest inny. Program ma zbadać, czy dana tabela zagnieżdżona jest zapełniona. To dlatego zawsze należy używać operatora porównywania IS EMPTY do sprawdzania, czy kolekcja jest zapełniona. Element do kolekcji ADT zagnieżdżonej w tabeli można dodać za pomocą wywołania funkcji add_ an_element w instrukcji UPDATE. Poniższe polecenie UPDATE pokazuje, jak dodać nowy element do kolekcji ADT zagnieżdżonej w tabeli employee (ta tabela jest zdefiniowana w punkcie „Aktualizowanie tabel zagnieżdżonych” w dodatku B). SQL> 2 3 4 5 6

UPDATE TABLE (SELECT e.home_address FROM employee e WHERE e.employee_id = 1) e SET e.street_address = add_element(e.street_address, 'Nr 622') , e.city = 'Opole' WHERE e.address_id = 1;

Wiersz 4. wywołuje funkcję add_element. Parametrami są pierwotna wartość kolumny typu ADT street_address i literał znakowy. Wiersz 4. przypisuje do wspomnianej kolumny wynik zwrócony przez funkcję. Można też napisać funkcję aktualizującą do zmieniania wartości kolumny typu ADT (przykład znajdziesz w dodatku B) i funkcję usuwającą, która kasuje elementy z takiej kolumny. Wiersz 15. sprawdza, czy wartość parametru pv_element jest różna od null. Program zajmuje dodatkową pamięć na kolekcję i przypisuje do niej wartość parametru pv_element (wiersze 17. i 18.). Funkcję add_element można przetestować za pomocą następującego zapytania: SQL> SELECT column_value AS "Dúnedain" 2 FROM TABLE(add_element(sql_table('Faramir','Boromir') 3 ,'Aragorn')) 4 ORDER BY 1;

Ten kod wyświetla uporządkowany zbiór: Dúnedain ---------Aragorn Boromir Faramir

Brakujący indeks Wszystkie kolekcje w postaci tablic VARRAY i tabel zagnieżdżonych mają indeksy liczbowe zaczynające się początkowo od wartości 1. Choć z tablicy VARRAY nie można usuwać elementów, w tabelach zagnieżdżonych jest to możliwe. W tym celu należy zastosować procedurę DELETE z interfejsu Collection API. To oznacza, że w wyniku wywołania określonych instrukcji indeks gęsty może stać się indeksem rzadkim. Usunięte elementy dla większości programistów są poważnym problemem, ponieważ do przechodzenia po kolekcjach używana jest pętla FOR. Ta pętla przechodzi do kolejnych elementów i nie może przeskakiwać pustych indeksów. Najlepszym sposobem zilustrowania łatwej i trudnej techniki poruszania się po rzadkich tabelach zagnieżdżonych (lub przynajmniej zagrożonych tym stanem) jest wykorzystanie pętli WHILE. Ją właśnie zastosowano w następnych przykładowych programach. Pierwszy przykładowy program przechodzi po kolekcji bez zwiększania indeksu o jeden w każdym kroku. Jest to łatwa technika poruszania się po kolekcji, w której w indeksach liczbowych mogą występować luki lub indeksami są łańcuchy znaków (czyli po kolekcjach przypominających listy powiązane). SQL> DECLARE 2 /* Deklaracja zmiennej o zrozumiałej nazwie (zauważ, że 3 w nazwie nie ma przedrostka lv_). */ 4 current INTEGER; 5 6 /* Deklaracja lokalnej tabeli zagnieżdżonej. */ 7 lv_table SQL_TABLE :=

216 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

Część I

Podstawy języka PL/SQL

sql_table('Aragorn','Faramir','Boromir'); BEGIN /* Usuwanie pierwszego elementu kolekcji. */ lv_table.DELETE(1); /* Ustawianie punktu początkowego. */ current := lv_table.FIRST; /* Sprawdzanie, czy bieżący indeks nie jest większy od ostatniego indeksu. */ WHILE (current 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

DECLARE /* Deklaracja zmiennej lokalnej używanej jako licznik. */ lv_counter INTEGER := 0; /* Deklaracja lokalnej tabeli zagnieżdżonej. */ lv_table SQL_TABLE := sql_table('Aragorn','Faramir','Boromir'); BEGIN /* Usuwanie pierwszego elementu tabeli zagnieżdżonej. */ lv_table.DELETE(1); /* Sprawdzanie, czy bieżący indeks nie jest większy od ostatniego. */ WHILE (lv_counter 2 3 4 5 6 7 8 9 10 11 12 13 14

DECLARE /* Definicja lokalnego typu opartego na tabeli zagnieżdżonej. */ TYPE plsql_table IS TABLE OF VARCHAR2(20); /* Deklaracja zmiennej utworzonego typu. */ lv_table PLSQL_TABLE := plsql_table('Aragorn','Faramir','Boromir'); BEGIN /* Przejście w pętli po kolekcji i wyświetlenie wyników. */ FOR i IN lv_table.FIRST..lv_table.LAST LOOP dbms_output.put_line(lv_table(i)); END LOOP; END; /

Wiersz 3. definiuje lokalną tabelę zagnieżdżoną. Wiersze 6. i 7. deklarują zmienną lokalną, która jest lokalnego typu plsql_table. Pętla FOR oparta na przedziale umożliwia poruszanie się po kolekcji i wyświetlanie wyników. Lokalny typ danych plsql_table jest dostępny tylko w zasięgu tego anonimowego bloku. Lepsze rozwiązanie związane z tabelami zagnieżdżonymi polega na umieszczeniu ich w specyfikacji pakietu. Choć na razie zapoznałeś się tylko z krótkim wprowadzeniem do pakietów w rozdziale 3., w tym przykładzie niezbędne jest zdefiniowanie pakietu. W specyfikacji pakietu można definiować typy danych dostępne tylko w języku PL/SQL. Typy danych zdefiniowane w specyfikacji pakietu są typami z poziomu pakietu. Czasem specyfikacja pakietu służy tylko do definiowania typów UDT. Wtedy nie trzeba tworzyć ciała pakietu (powstaje pakiet bez ciała). Pierwsza wersja pakietu type_library zawiera tylko definicję typu kolekcji w postaci tabeli zagnieżdżonej: SQL> CREATE OR REPLACE PACKAGE type_library IS 2 /* Definicja lokalnej kolekcji. */ 3 TYPE plsql_table IS TABLE OF VARCHAR2(20); 4 END; 5 /

W wierszu 3. widać, że definicja typu kolekcji z poziomu pakietu jest taka sama jak definicja lokalnego typu kolekcji języka PL/SQL. Następny program jest podobny do przedstawionego wcześniej programu w postaci bloku anonimowego. Różnią się one jedną rzeczą — tu nie ma definicji lokalnego typu kolekcji plsql_table. Jest tak, ponieważ zmienna lv_table ma typ z poziomu pakietu. SQL> DECLARE 2 /* Deklaracja lokalnej zmiennej typu kolekcji. */ 3 lv_table TYPE_LIBRARY.PLSQL_TABLE := 4 type_library.plsql_table('Aragorn','Faramir','Boromir'); 5 BEGIN ... 10 END; 11 /

W wierszu 3. deklarowana jest zmienna za pomocą nazwy pakietu i nazwy typu danych. Selektor komponentów (.) pozwala pobrać typ danych ze specyfikacji pakietu. Zauważ, że zarówno w typie danych, jak i w konstruktorze kolekcji trzeba podać i pakiet, i typ danych. Po przeglądzie technik związanych z kolekcjami skalarów w następnym podpunkcie omówiono pracę z kolekcjami typów złożonych.

218

Część I

Podstawy języka PL/SQL

Kolekcje typów złożonych Istnieją dwa rodzaje kolekcji typów złożonych. Są to: kolekcje typów obiektowych i kolekcje typów obiektowych przechowujących kolekcje zagnieżdżone. Kolekcje zawierające inne kolekcje nazywa się czasem wielopoziomowymi. Złożony typ danych lub typ obiektowy przechowujący tylko wartości skalarne jest symetryczny, co oznacza, że wszystkie elementy mają po jednym egzemplarzu zestawu kolumn. Typ złożony przechowujący wartości skalarne i złożone też może być symetryczny, pod warunkiem jednak, że zagnieżdżony typ złożony działa jak struktura rekordowa (jest jednym wierszem). Złożony typ danych jest asymetryczny, jeśli jeden z jego atrybutów składowych (jedno z pól) jest typu kolekcji. Z dalszych podpunktów dowiesz się, jak stosować symetryczne i asymetryczne zmienne złożone w kolekcjach. W dodatku B opisano, jak pracować ze złożonymi typami danych w tabelach. Ten dodatek ilustruje też, jak pisać zapytania bez zagnieżdżania i jak aktualizować tabele zagnieżdżone.

Symetryczne kolekcje typów złożonych Aby przyjrzeć się przykładom ilustrującym symetryczne kolekcje typów złożonych, należy zacząć od utworzenia kilku takich typów. Prosty złożony typ obiektowy ma dwie lub więcej kolumn. Poniższy typ ma tylko dwa atrybuty (dwa pola), co pomaga uprościć przykład. Pamiętaj, że typy obiektowe to typy SQL-a, a nie typy języka PL/SQL, i że trzeba definiować je jako obiekty z poziomu schematu. Poniższy kod tworzy złożony typ obiektowy prominent_object na poziomie schematu: SQL> CREATE OR REPLACE 2 TYPE prominent_object IS OBJECT 3 ( name VARCHAR2(20) 4 , age VARCHAR2(10)); 5 /

Następnie utwórzmy drugi złożony typ obiektowy, w którym używany jest przygotowany wcześniej typ prominent_object. Typ people_object obejmuje jeden egzemplarz typu prominent_object, co ilustruje poniższy przykładowy kod: SQL> CREATE OR REPLACE 2 TYPE people_object IS OBJECT 3 ( race VARCHAR2(10) 4 , exemplar PROMINENT_OBJECT); 5 /

Wiersz 4. definiuje zmienną typu prominent_object. Ostatni krok polega na utworzeniu kolekcji elementów złożonego typu obiektowego: SQL> CREATE OR REPLACE 2 TYPE people_table IS TABLE OF people_object; 3 /

Podobnie jak jest w kolekcjach w postaci tablic VARRAY i tabel zagnieżdżonych, tak i tu główna definicja znajduje się w wierszu 2. Po utworzeniu tych wszystkich typów można pobrać je za pomocą poniższej (dość skomplikowanej) składni: SQL> COLUMN EXEMPLAR FORMAT A40 SQL> SELECT * 2 FROM TABLE( 3 SELECT CAST(COLLECT( 4 people_object( 5 'Człowiek' 6 , prominent_object('Aragorn','III wiek') 7 ) 8 ) AS people_table 9 ) 10 FROM dual);

To zapytanie pokazuje, że trzeba wywołać funkcję COLLECT, aby umieścić złożony obiekt w kolekcji używanej w czasie wykonywania programu. Następnie można wywołać funkcję CAST na tej kolekcji, by zrzutować ją na znany typ kolekcji z poziomu schematu. W ostatnim kroku można wywołać

Rozdział 6. Kolekcje

219

instrukcję SELECT i pobrać dane za pomocą funkcji TABLE, żeby przekształcić je na zagregowany zbiór wyników (jest to wymyślna nazwa oznaczająca dowolny zbiór wyników z instrukcji SELECT). Ten kod wyświetla następujące informacje: RACE EXEMPLAR(NAME, AGE) ---------- ---------------------------------------Człowiek PROMINENT_OBJECT('Aragorn', 'III wiek')

Jedyny problem w przypadku tych danych wyjściowych stanowi konstruktor, który należy zastąpić wartościami kolumn z zagnieżdżonej pseudo- (na tym etapie) kolekcji. Rozwiązanie polega na usunięciu zagnieżdżenia za pomocą złączenia CROSS JOIN: SQL> SELECT o.race, n.name, n.age ... 10 FROM dual) o CROSS JOIN 11 TABLE( 12 SELECT CAST(COLLECT(exemplar) AS prominent_table) 13 FROM dual) n;

Wiersz 1. (z członem SQL) obejmuje trzykolumnową listę instrukcji SELECT. Zastosowanie gwiazdki spowodowałoby zwrócenie czterech kolumn. Oprócz trzech kolumn z listy pojawiłaby się kolumna exemplar, ponieważ złączenie krzyżowe dodaje nowe kolumny do tego samego wiersza danych. Alias o oznacza zapytanie zewnętrzne (ang. outer), a alias n reprezentuje tabelę zagnieżdżoną (ang. nested), opartą na typie obiektowym prominent_object. To złączenie zwraca kolumny zagnieżdżone dopasowane do jedynego dostępnego wiersza (kolumny są z nim łączone). Złączenie krzyżowe (iloczyn kartezjański) zwraca tyle wierszy, ile znajduje się ich w tabeli, ponieważ zawsze dopasowuje dane do nadrzędnego wiersza. Ten kod wyświetla następujące dane: RACE NAME AGE ---------- -------------------- --------Człowiek Aragorn III wiek

Funkcja TABLE zwraca zagregowany zbiór wyników zgodny z resztą zapytania. Instrukcja SELECT oraz funkcje CAST i COLLECT umożliwiają pracę z jednym elementem, a nie z prawdziwą kolekcją w postaci tabeli zagnieżdżonej. Zapytanie zwraca kolumnę exemplar, ale odfiltrowaną na podstawie kolumn z listy instrukcji SELECT (taki zabieg nazywany jest projekcją). Więcej informacji o tym podejściu znajdziesz w punkcie „Zapytania rozwijające” w dodatku B. Zapytanie jest znacznie prostsze, gdy używana jest rzeczywista kolekcja people_table. Poniższe zapytanie generuje dwuelementową kolekcję. Zastosowana składnia tworzy wielopoziomową kolekcję people_table. Oto kod bez funkcji COLLECT i CAST w pierwszej części instrukcji: SQL> SELECT o.race, n.name, n.age 2 FROM TABLE( 3 people_table( 4 people_object( 5 'Człowiek' 6 , prominent_object('Aragorn','III wiek')) 7 , people_object( 8 'Elf' 9 , prominent_object('Legolas','III wiek')) 10 )) o CROSS JOIN 11 TABLE( 12 SELECT CAST(COLLECT(exemplar) AS prominent_table) 13 FROM dual) n;

Wiersze od 3. do 9. (potrzebnych byłoby ich mniej, gdyby nie ograniczenie długości wiersza w tekście książki) tworzą składającą się z dwóch elementów złożonych kolekcję people_table. Wiersze 6. i 9. tworzą zagnieżdżony obiekt typu prominent_object dla każdego elementu kolekcji. Następnie za pomocą funkcji TABLE można pobrać zawartość tej dynamicznie utworzonej kolekcji. Ten kod wyświetla następujące dane: RACE NAME AGE ---------- -------------------- ---------

220

Część I

Człowiek Elf

Aragorn Legolas

Podstawy języka PL/SQL III wiek III wiek

Teraz można zaadaptować kod przedstawiony przy omawianiu złożonych typów obiektowych SQL-a. Poniższy przykład tworzy lokalny obiekt typu people_table oraz wczytuje dane i je wyświetla: SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

DECLARE /* Deklaracja tabeli zagnieżdżonej. */ lv_tolkien_table PEOPLE_TABLE := people_table( people_object( 'Człowiek' , prominent_object('Aragorn','III wiek')) , people_object( 'Elf' , prominent_object('Legolas','III wiek'))); BEGIN /* Dodawanie nowego rekordu do kolekcji. */ lv_tolkien_table.EXTEND; lv_tolkien_table(lv_tolkien_table.COUNT) := people_object('Krasnolud' , prominent_object('Gimli','III wiek')); /* Wczytywanie i wyświetlanie wartości z kolekcji. */ FOR i IN lv_tolkien_table.FIRST..lv_tolkien_table.LAST LOOP dbms_output.put_line( lv_tolkien_table(i).race||': '||lv_tolkien_table(i).exemplar.name); END LOOP; END; /

Początkowe wywołanie konstruktora (wiersze od 3. do 10.) jest takie samo jak w poprzednim zapytaniu. Program przydziela pamięć w wierszu 13., a następnie dodaje nowy element złożony do kolekcji (wiersze od 14. do 16.). Wiersz 21. pokazuje, jak wczytywać skalarne i złożone kolumny kolekcji. W obu sytuacjach najpierw należy określić indeks zmiennej lv_tolkien_table typu kolekcji. Aby wczytać kolumnę skalarną, należy podać nazwę atrybutu race. Bardziej skomplikowany jest dostęp do zagnieżdżonego obiektu złożonego. Po podaniu indeksu zmiennej lv_tolkien_table należy dodać nazwę atrybutu exemplar. Ten atrybut określa zagnieżdżony typ złożony i pozwala dodać kropkę (.) w celu uzyskania dostępu do dowolnego z jego atrybutów skalarnych. W tym przykładzie używany jest zagnieżdżony atrybut name. Ten kod wyświetla następujące dane: Człowiek: Aragorn Elf: Legolas Krasnolud: Gimli

Element typu obiektowego można też przenieść do struktury rekordowej lub kolekcji języka PL/SQL. Instrukcja SELECT INTO (opisana w punkcie „Jednowierszowe kursory niejawne” w rozdziale 5.) pozwala przypisać pojedynczy element typu obiektowego (lub jeden element z kolekcji takich elementów) do typu rekordowego. Instrukcja BULK COLLECT INTO (przedstawiona w punkcie „Docelowe kolekcje rekordów” w rozdziale 5.) umożliwia przypisanie kolekcji obiektów do kolekcji rekordów języka PL/SQL. Zamiast pisać dwa kompletne przykłady z typami people_object i people_table, zaprezentowany jest tylko jeden program, w którym liczba wierszy typu people_object zwróconych z kolekcji jest ograniczona: SQL> DECLARE 2 /* Deklaracja rekordu języka PL/SQL. */ 3 TYPE tolkien_record IS RECORD 4 ( race VARCHAR2(10) 5 , name VARCHAR2(20) 6 , age VARCHAR2(10)); 7 8 /* Deklaracja tabeli zagnieżdżonej rekordów. */

Rozdział 6. Kolekcje 9 10 11 12 13 14 15 16 17 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39

221

TYPE tolkien_plsql_table IS TABLE OF TOLKIEN_RECORD; /* Deklaracja zmiennych typu rekordu i kolekcji rekordów. */ lv_tolkien_record TOLKIEN_RECORD; lv_tolkien_plsql_table TOLKIEN_PLSQL_TABLE; /* Deklaracja kolekcji. */ lv_tolkien_table PEOPLE_TABLE := people_table( ... Definicja taka sama jak w poprzednim przykładzie ... BEGIN /* Niejawne podzapytanie zwracające jeden wiersz. */ SELECT o.race, n.name, n.age INTO lv_tolkien_record FROM TABLE(lv_tolkien_table) o CROSS JOIN TABLE( SELECT CAST(COLLECT(exemplar) AS prominent_table) FROM dual) n WHERE ROWNUM < 2; dbms_output.put_line( '['||lv_tolkien_record.race||'] '|| '['||lv_tolkien_record.name||'] '|| '['||lv_tolkien_record.age ||']'); END; /

Wiersze od 3. do 6. definiują strukturę tolkien_record, a wiersz 9. definiuje tabelę zagnieżdżoną elementów tego typu. Zapytanie SELECT INTO (wiersze od 26. do 32.) zwraca jeden wiersz z kolekcji lv_tolkien_table i przypisuje go do lokalnej zmiennej lv_tolkien_record. Wiersz 32. ogranicza kolekcję do jednego wiersza. Służy do tego porównanie ROWNUM. Jest ono zgodne ze starszymi wersjami bazy i w edycjach wcześniejszych niż Oracle Database 12c służyło do tworzenia zapytań pobierających n pierwszych wierszy. W Oracle Database 12c dostępna jest specjalna składnia do tworzenia takich zapytań. Dlatego w tej wersji możesz zastąpić wiersz 32. następującym kodem: 32

FETCH FIRST 1 ROWS ONLY;

Moim zdaniem składnia z bazy Oracle Database 12c jest dużo bardziej przejrzysta niż stosowane wcześniej rozwiązanie. Po przetworzeniu zapytania SELECT INTO program wyświetla wartości z rekordu: [Człowiek] [Aragorn] [III wiek]

Jeśli w wierszu 27. zastąpisz instrukcję INTO poleceniem BULK COLLECT INTO, program wykona operację masową. Operacje wykonane przez poniższą wersję wiersza 27. przenoszą wszystkie wiersze z kolekcji do zmiennej lokalnej z bloku anonimowego: 27

BULK COLLECT INTO lv_tolkien_plsql_table

Warto zauważyć, że przy pobieraniu masowym docelowa zmienna musi być tabelą zagnieżdżoną rekordów języka PL/SQL. To dlatego zmienna lv_tolkien_plsql_table to kolekcja rekordów. Niezależnie od tego, czy zmodyfikujesz wiersz 32. (za pomocą klauzuli FETCH FIRST 1 ROWS ONLY), nowa wersja programu nie wyświetla wyników. Dzieje się tak, ponieważ teraz program pobiera jeden lub więcej wierszy do kolekcji. Przy pobieraniu masowym dane przypisywane są nie do złożonej struktury rekordowej, ale to kolekcji elementów tego typu. To sprawia, że kod wyświetlający dane przestaje działać. Teraz elementy kolekcji trzeba wyświetlać jeden po drugim, dlatego należy zastosować pętlę. Najłatwiejszy sposób na wprowadzenie potrzebnej zmiany to zastosowanie pętli FOR: 33 34 35 36 37 38 39

/* Przejście w pętli po zbiorze wyników i ich wyświetlenie. */ FOR i IN 1..lv_tolkien_plsql_table.COUNT LOOP dbms_output.put_line( '['||lv_tolkien_plsql_table(i).race||'] '|| '['||lv_tolkien_plsql_table(i).name||'] '|| '['||lv_tolkien_plsql_table(i).age ||']'); END LOOP;

222

Część I

Podstawy języka PL/SQL

Zauważ, że wiersze kolekcji są wskazywane za pomocą indeksów (wiersze od 36. do 38.). Selektor komponentów (.) łączy wiersz z polem typu rekordowego. Choć wcześniej zachęcałem do przenoszenia danych do typów SQL-a, czasem trzeba przenieść dane z kolekcji obiektowej do starszych (zdaniem niektórych — przestarzałych) typów kolekcji języka PL/SQL. Teraz wiesz już, jak zrobić to wiersz po wierszu lub w ramach operacji masowej. Przypisania za pomocą instrukcji SELECT INTO i BULK COLLECT INTO to zazwyczaj najwydajniejszy sposób szybkiego przenoszenia danych z kolekcji typów obiektowych do kolekcji rekordów języka PL/SQL. Istnieje jednak także inne podejście. Wymaga ono zainicjowania pustej kolekcji, przydzielenia pamięci i przypisania elementów jeden po drugim. Jednak przypisywanie elementów jeden po drugim w pętli powinno zwrócić uwagę programisty, że tworzy wąskie gardło przeciążające procesor. Administratorzy mówiący, że nie znoszą języka PL/SQL, zwykle mają na myśli programy opracowane przez programistów, którzy nie potrafią napisać zapytań rozwijających (ang. unnesting queries), wydajniejszych od przypisywania wartości wiersz po wierszu.

Można utworzyć kolekcję bez elementów, jednak nie można utworzyć egzemplarza typu rekordowego. Jest tak, ponieważ typ rekordowy nie ma domyślnego konstruktora. Pierwsza zmiana w programie jest potrzebna w wierszu 13., gdzie tworzona jest pusta kolekcja rekordów. Wprowadź następującą poprawkę: 13

lv_tolkien_plsql_table TOLKIEN_PLSQL_TABLE := tolkien_plsql_table();

Początkujący programista może próbować przypisać wartość typu obiektowego bezpośrednio do zmiennej typu rekordowego. W tym scenariuszu trzeba zmodyfikować sekcję wykonawczą: SQL> 2 24 25 26 27 28 29 30 38 39

DECLARE ... BEGIN /* Pętla do przypisywania elementów jeden po drugim. */ FOR i IN 1..lv_tolkien_table.COUNT LOOP lv_tolkien_plsql_table.EXTEND; lv_tolkien_plsql_table(i) := lv_tolkien_table(i); END LOOP; ... Program nie dochodzi do tego miejsca, dlatego nie trzeba zapisywać tego kodu. ... END; /

Wiersz 27. zadziała, ponieważ używana jest kolekcja elementów typu obiektowego. Wiersz 28. zgłosi błąd, ponieważ nie można przypisać obiektu do struktury rekordowej języka PL/SQL. Ta próba spowoduje następujący błąd: lv_tolkien_plsql_table(i) := lv_tolkien_table(i); * ERROR at line 28: ORA-06550: line 28, column 34: PLS-00382: expression is of wrong type ORA-06550: line 28, column 5: PL/SQL: Statement ignored

Ten błąd informuje, że nie można przypisać obiektu do typu rekordowego języka PL/SQL. Te typy są niezgodne ze sobą. Można jednak przypisać wartości pól typu obiektowego do pól typu rekordowego. Sekcję wykonawczą należy więc zmodyfikować w następujący sposób: SQL> DECLARE 2 ... 24 BEGIN 25 /* Przenoszenie elementów jeden po drugim w pętli. */ 26 FOR i IN 1..lv_tolkien_table.COUNT LOOP 27 lv_tolkien_plsql_table.EXTEND; 28 lv_tolkien_plsql_table(i).race := lv_tolkien_table(i).race; 29 lv_tolkien_plsql_table(i).name := lv_tolkien_table(i).exemplar.name; 30 lv_tolkien_plsql_table(i).age := lv_tolkien_table(i).exemplar.age;

Rozdział 6. Kolekcje 31 32 33 34 35 36 37 38 39 40 41

223

END LOOP; /* Przejście w pętli po zbiorze wyników i ich wyświetlenie. */ FOR i IN 1..lv_tolkien_plsql_table.COUNT LOOP dbms_output.put_line( '['||lv_tolkien_plsql_table(i).race||'] '|| '['||lv_tolkien_plsql_table(i).name||'] '|| '['||lv_tolkien_plsql_table(i).age ||']'); END LOOP; END; /

Wiersz 28. przypisuje atrybut race. W tym miejscu należy podać indeks obiektu w kolekcji i nazwę tego atrybutu. W wierszach 29. i 30. trzeba wykorzystać atrybut exemplar, aby uzyskać dostęp do zagnieżdżonych wartości kolumn (name i age). Po przypisaniu danych do struktury rekordowej nie ma już zagnieżdżonych pól. W tym przykładzie struktura rekordowa jest prosta (nie występują w niej żadne zagnieżdżone struktury). Nie można przypisywać utworzonych obiektów do typów rekordowych języka PL/SQL.

Jak może zauważyłeś, wyświetlanie tylko jednego krasnoluda, elfa lub człowieka (z trylogii Władca Pierścieni) nie jest przydatne. Tego rodzaju informacje byłyby bardziej naturalne, gdyby zagnieżdżonym typem złożonym była kolekcja elementów złożonego typu obiektowego people_object. Taka zmiana wymaga użycia asymetrycznego złożonego typu danych, opisanego w następnym podpunkcie.

Kolekcje asymetrycznych typów złożonych Jak wcześniej wyjaśniono, asymetryczne złożone kolekcje w postaci tablic VARRAY i tabel zagnieżdżonych przechowują pola typów skalarnych i pola typów kolekcji. Pola skalarne mają po jednym wierszu, natomiast pola kolekcji — od jednego do wielu wierszy. Wykorzystajmy przykłady z poprzedniego podpunktu i dodajmy typ kolekcji prominent_table, której typem bazowym jest typ złożony prominent_object. W końcu w trylogii Władca Pierścieni występuje więcej niż jeden ważny krasnolud, elf i człowiek. Pojawia się też kilka ważnych kobiet (przy czym niestety nie dotyczy to krasnoludów), które jednak występują w kolekcjach odpowiadających im rasom. SQL> CREATE OR REPLACE 2 TYPE prominent_table IS TABLE OF prominent_object; 3 /

Po utworzeniu typu kolekcji prominent_table zdefiniujmy typy złożone people_object i people_ table. Oto składnia potrzebna do utworzenia asymetrycznego typu people_object: SQL> CREATE OR REPLACE 2 TYPE people_object IS OBJECT 3 ( race VARCHAR2(10) 4 , exemplar PROMINENT_TABLE); 5 /

W wierszu 4. zamiast typu prominent_object zastosowano kolekcję typu prominent_table. Wcześniejsza i obecna składnia typu people_table się nie zmienia, ponieważ typ bazowy działa tak samo. Typem bazowym jest teraz zmodyfikowany typ złożony people_object, jednak Oracle Database 12c obsługuje tę zmianę za pomocą ewolucji typów. W ostatnim kroku należy utworzyć kolekcję elementów asymetrycznego złożonego typu danych: SQL> CREATE OR REPLACE 2 TYPE people_table IS TABLE OF people_object; 3 /

Choć warto wiedzieć, jak wykonywać różne zadania w SQL-u (więcej informacji znajdziesz w punkcie „Zapytania rozwijające” w dodatku B), zaimplementujmy teraz nowy typ w anonimowym bloku

224

Część I

Podstawy języka PL/SQL

języka PL/SQL. Poniższy program inicjuje wielopoziomową kolekcję, a następnie przypisuje do niej nowy element. Kod jest długi, jest to jednak konieczne do kompletnego zaprezentowania używanych mechanizmów. SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35

DECLARE /* Deklaracja tabeli zagnieżdżonej. */ lv_tolkien PEOPLE_TABLE := people_table( people_object( 'Ludzie' , prominent_table( prominent_object('Aragorn','III wiek') , prominent_object('Boromir','III wiek') , prominent_object('Faramir','III wiek') , prominent_object('Eowyn','III wiek'))) , people_object( 'Elfy' , prominent_table( prominent_object('Legolas','3rd Age') , prominent_object('Arwen','3rd Age')))); BEGIN /* Dodawanie nowego rekordu do kolekcji. */ lv_tolkien.EXTEND; lv_tolkien(lv_tolkien.COUNT) := people_object('Krasnoludy' , prominent_table( prominent_object('Gimli','III wiek') , prominent_object('Gloin','III wiek'))); /* Wczytywanie i wyświetlanie wartości kolekcji. */ FOR i IN lv_tolkien.FIRST..lv_tolkien.LAST LOOP FOR j IN lv_tolkien(i).exemplar.FIRST..lv_tolkien(i).exemplar.LAST LOOP dbms_output.put_line( lv_tolkien(i).race||': '||lv_tolkien(i).exemplar(j).name); END LOOP; END LOOP; END; /

Wiersze od 3. do 16. tworzą nową wielopoziomową kolekcję. Wiersz 19. przypisuje pamięć na nowy element. Wiersze od 20. do 24. dodają do kolekcji lv_tolkien nową zmienną wielopoziomowego typu złożonego people_object. Wiersz 28. pokazuje, jak uruchomić zagnieżdżoną pętlę dla wielopoziomowej kolekcji. Pierwsza pętla FOR dotyczy zmiennej lv_tolkien, natomiast zagnieżdżona pętla FOR działa dla zagnieżdżonej zmiennej exemplar. Ponadto pętla zagnieżdżona wczytuje zagnieżdżoną kolekcję exemplar. Wiersz 30. pokazuje, że w obu kolekcjach trzeba podać indeks. Należy wskazać element z kolekcji lv_talkien, następnie element z kolekcji exemplar, a ostatecznie atrybut elementu z tej kolekcji. Ten kod wyświetla poniższe dane: Ludzie: Aragorn Ludzie: Boromir Ludzie: Faramir Ludzie: Eowyn Elfy: Legolas Elfy: Arwen Krasnoludy: Gimli Krasnoludy: Gloin

Tego rodzaju łańcuch operacji działa niezależnie od tego, ile poziomów ma dana kolekcja wielopoziomowa. Choć zastosowana tu składnia jest rozwlekła, nie istnieje inny sposób używania takich kolekcji.

Rozdział 6. Kolekcje

225

Powtórzenie materiału W tym podrozdziale opisano następujące zagadnienia dotyczące kolekcji SQL-a:  Kolekcje SQL-a definiuje się jako typy obiektowe z poziomu schematu.  Kolekcje SQL-a wymagają konstrukcji. Odbywa się ona w wyniku wywołania nazwy typu z listą argumentów pasujących do definicji atrybutów określonego typu obiektowego.  Kolekcje SQL-a, których typem bazowym jest typ skalarny, to kolekcje ADT. Kolekcje elementów typów obiektowych to kolekcje UDT.  Kolekcje SQL-a mają typ bazowy. Może nim być typ skalarny lub złożony. Jeśli bazowym typem złożonym także jest kolekcja, powstaje wielopoziomowa kolekcja SQL-a.  Kolekcje SQL-a można stosować jako parametry funkcji i procedur, a także typy wartości zwracanych przez funkcje. Dotyczy to zarówno SQL-a, jak i języka PL/SQL.  Tablice VARRAY mają zdefiniowaną stałą wielkość, natomiast tabele zagnieżdżone nie mają ograniczenia rozmiaru.  Tablice VARRAY zawsze mają sekwencyjny (gęsty) indeks.  Tabele zagnieżdżone początkowo mają sekwencyjny (gęsty) indeks, jednak możliwe jest usuwanie elementów z takich kolekcji. W efekcie powstają luki w indeksie, co prowadzi do powstawania indeksów rzadkich.

Tablice asocjacyjne Tablice asocjacyjne to jednowymiarowe struktury baz Oracle Database 12c. Można w nich używać tych samych bazowych typów danych co w kolekcjach SQL-a. Jak opisano to w ramce „Tabele języka PL/SQL”, wcześniej takie tablice nazywano tabelami języka PL/SQL. Ten podrozdział poświęcony jest jednowymiarowej strukturze w postaci tablicy asocjacyjnej. Tablice asocjacyjne to jednowymiarowe złożone typy danych. Ich typem bazowym mogą być tylko typy skalarne lub złożone. Nie można zdefiniować wielowymiarowej kolekcji tego rodzaju, ponieważ kolekcje nie mogą przechowywać wielu egzemplarzy typu bazowego w poszczególnych elementach (wierszach). Choć kolekcje nie mogą przechowywać innych złożonych typów danych, mogą w każdym wierszu zawierać inny egzemplarz kolekcji. Wtedy powstają kolekcje wielopoziomowe. Tablic asocjacyjnych nie można używać jako typu danych kolumn tabel. Są to struktury dostępne wyłącznie w kodzie programów. Tablice asocjacyjne można stosować tylko w języku PL/SQL, co oznacza, że nie można ich przekazać jako parametru w instrukcji SQL-a ani jako wartości zwracanej przez funkcję. Warto zwrócić uwagę na kilka ważnych kwestii dotyczących tablic asocjacyjnych. Te zagadnienia wpływają na zastosowany tu sposób prezentacji korzystania z takich kolekcji. Tablice asocjacyjne:  nie wymagają inicjowania ani nie mają konstruktora, a ponadto nie wymagają przydzielania pamięci przed przypisaniem wartości;  mogą mieć indeksy liczbowe (od starszych wersji aż do wersji bieżącej), a od edycji Oracle Database 12c mogą także mieć unikatowe indeksy w postaci łańcuchów znaków;  mogą mieć indeksy w postaci dowolnych liczb całkowitych (indeksem może być liczba ujemna, dodatnia lub zero);  są niejawnie przekształcane z analogicznych typów rekordowych, typów obiektowych lub struktur określonych za pomocą atrybutu %ROWTYPE;  gdy indeksy mają postać łańcuchów znaków, w bazach danych używających ustawień globalnych (na przykład parametrów NLS_COMP lub NLS_SORT) takie tablice wymagają specjalnej obsługi. Przy stosowaniu indeksów w postaci niepowtarzalnych łańcuchów znaków mogą pojawić się różnice w sortowaniu, jeśli w trakcie działania bazy danych zmieni się zestaw znaków w mechanizmie NLS (ang. National Language Support).

226

Część I

Podstawy języka PL/SQL

W następnych punktach opisano najlepsze techniki stosowania tablic asocjacyjnych w programach w języku PL/SQL.

Definiowanie i stosowanie tablic asocjacyjnych Składnia służąca do definiowania tablic asocjacyjnych w PL/SQL ma dwie wersje. Jedna z nich wygląda następująco: CREATE OR REPLACE TYPE nazwa_typu AS TABLE OF typ_elementu [ NOT NULL ] INDEX BY [ PLS_INTEGER | BINARY_INTEGER | VARCHAR2(rozmiar) ];

Zagadnienia związane z umożliwianiem lub uniemożliwianiem stosowania wartości NULL w tabelach zagnieżdżonych dotyczą także tablic asocjacyjnych. Zgodnie z zasadami należy upewnić się, że dane w tablicy nie mają wartości NULL. Można to zrobić programowo lub określając ograniczenie w definicji tablicy asocjacyjnej. Jest to decyzja, którą trzeba podjąć za każdym razem. Jako wartości indeksów w tablicach asocjacyjnych można używać liczb ujemnych, dodatnich lub zera. Zarówno PLS_INTEGER, jak i BINARY_INTEGER to typy bez ograniczeń, które w Oracle 12c są odwzorowywane na specyfikacje wywołań języków C/C++, C# i Java. Druga wersja składni do definiowania tablic asocjacyjnych w języku PL/SQL wygląda następująco: CREATE OR REPLACE TYPE nazwa_typu AS TABLE OF typ_elementu [ NOT NULL ] INDEX BY typ_klucza;

Jako typ_klucza można użyć typów danych VARCHAR2, STRING lub LONG (oprócz typów PLS_INTEGER i BINARY_INTEGER). Zarówno VARCHAR2, jak i STRING wymagają zdefiniowania rozmiaru. W przypadku typu LONG nie jest to konieczne, jednak jest on uznawany za przestarzały, dlatego należy unikać stosowania go. Jak już wspomniano, tablice asocjacyjne nie wymagają inicjowania i nie używają składni z wykorzystaniem konstruktora. Jest to istotna różnica w porównaniu z kolekcjami SQL-a — tablicami VARRAY i tabelami zagnieżdżonymi. Inna ważna różnica między tablicami asocjacyjnymi a kolekcjami SQL-a dotyczy tego, gdzie można je stosować. Tablice asocjacyjne działają tylko w kodzie w języku PL/SQL. Typem bazowym tablic asocjacyjnych może być typ obiektowy, typ rekordowy lub typ skalarny. Typy rekordowe mogą też zawierać zagnieżdżone typy obiektowe. W dalszych podpunktach wyjaśniono, jak korzystać z tablic asocjacyjnych z elementami skalarnych i złożonych typów danych.

Tablice asocjacyjne z elementami skalarnych typów danych Praca z tablicami asocjacyjnymi z elementami typów skalarnych jest łatwiejsza niż używanie takich tablic z elementami typów złożonych. Kolekcje elementów typów skalarnych to kolekcje ADT, różniące się od kolekcji elementów złożonych typów danych. Jedną z różnic jest to, że Oracle Database 12c zwraca wartości kolekcji ADT jako kolumnę column_value, natomiast wartości kolekcji o złożonym typie bazowym zwracane są za pomocą nazw pól. W tablicach asocjacyjnych można stosować indeksy liczbowe lub indeksy w postaci łańcuchów znaków. W dwóch następnych podpunktach opisano tablice asocjacyjne z indeksami tych rodzajów.

Tablice asocjacyjne z indeksami liczbowymi Na potrzeby prezentacji załóżmy, że programista pomylił tablice asocjacyjne z tablicami VARRAY lub tabelami zagnieżdżonymi SQL-a i próbuje utworzyć egzemplarz tablicy asocjacyjnej w sekcji deklaracji, co ilustruje poniższy program: SQL> DECLARE 2 /* Definicja tablicy asocjacyjnej elementów skalarnych. */ 3 TYPE suit_table IS TABLE OF VARCHAR2(7 CHAR) 4 INDEX BY BINARY_INTEGER; 5 6 /* Deklaracja i próba utworzenia obiektu. */ 7 lv_suit CARD_TABLE := suit_table('Trefl','Kier','Karo','Pik'); 8 BEGIN

Rozdział 6. Kolekcje 9 10 11

227

NULL; END; /

Definicja tablicy asocjacyjnej w wierszach 3. i 4. jest poprawna. Jednak próba przypisania wyniku wywołania konstruktora w wierszu 7. powoduje zgłoszenie pokazanego poniżej wyjątku PLS-00222, „nie istnieje w zasięgu”. Dzieje się tak, ponieważ tablice asocjacyjne nie mają konstruktora. lv_suit CARD_TABLE := suit_table('Trefl','Kier','Karo','Pik'); * ERROR at line 7: ORA-06550: line 7, column 25: PLS-00222: no function with name 'SUIT_TABLE' exists in this scope ORA-06550: line 7, column 9: PL/SQL: Item ignored

Błąd w wierszu 7. wynika z tego, że klauzula INDEX BY sprawia, iż tworzona kolekcja to tablica asocjacyjna, a nie tabela zagnieżdżona. Jak wcześniej wspomniano, nie można wywołać konstruktora tablicy asocjacyjnej, ponieważ taki konstruktor nie istnieje. Poprawny sposób przypisywania wartości do tablic asocjacyjnych polega na przypisywaniu ich jedna po drugiej w sekcji wykonawczej lub sekcji wyjątków. Każde przypisanie określa wartość i indeks elementu. Dla indeksów można stosować wartości sekwencyjne, jednak nie jest to konieczne. Zwykle używa się wartości sekwencyjnych, jednak nie ma pewności, że zachowają one tę cechę przy wczytywaniu elementów przez program. To dlatego trzeba zachować ostrożność przy wczytywaniu danych w programach. Oto zmodyfikowana wersja programu (z pominięciem identycznej sekcji deklaracji): SQL> DECLARE ... 8 BEGIN 9 /* Przypisywanie wartości do kolekcji ADT. */ 10 lv_suit(1) := 'Trefl'; 11 lv_suit(2) := 'Kier'; 12 lv_suit(3) := 'Karo'; 13 lv_suit(4) := 'Pik'; 14 15 /* Przejście w pętli po kolekcji z indeksem gęstym. */ 16 FOR i IN lv_suit.FIRST..lv_suit.LAST LOOP 17 dbms_output.put_line(lv_suit(i)); 18 END LOOP; 19 END; 20 /

Wiersze od 10. do 13. przypisują kolory kart do tablicy asocjacyjnej lv_suit. Wiersz 17. wyświetla elementy tej tablicy. Po omówieniu podstaw pora wrócić do tablic asocjacyjnych elementów typów obiektowych. Utwórz potrzebny typ obiektowy suit_object: SQL> CREATE OR REPLACE 2 TYPE suit_object IS OBJECT 3 ( suit VARCHAR2(7)); 4 /

Typ suit_object jest podobny do typu skalarnego używanego w dwóch poprzednich programach. Poniższy kod jest zbliżony do dwóch wcześniejszych przykładów, jednak ilustruje, jak utworzyć indeksowaną liczbami całkowitymi tablicę asocjacyjną elementów typu obiektowego. SQL> DECLARE 2 /* Definicja tablicy asocjacyjnej elementów typu obiektowego. */ 3 TYPE suit_table IS TABLE OF suit_object 4 INDEX BY BINARY_INTEGER; 5 6 /* Deklaracja tablicy asocjacyjnej. */ 7 lv_suit SUIT_TABLE; 8 BEGIN

228 9 10 11 12 13 14 15 16 17 18 19 20

Część I

Podstawy języka PL/SQL

/* Zapełnianie tablicy asocjacyjnej. */ lv_suit(1) := suit_object('Trefl'); lv_suit(2) := suit_object('Kier'); lv_suit(3) := suit_object('Karo'); lv_suit(4) := suit_object('Pik'); /* Wczytywanie wartości typu obiektowego. */ FOR i IN 1..lv_suit.COUNT LOOP dbms_output.put_line(lv_suit(i).suit); END LOOP; END; /

Wiersze 3. i 4. definiują lokalną tablicę asocjacyjną. Wiersz 7. to deklaracja zmiennej lv_suit typu tej tablicy. Zauważ, że w deklaracji tej zmiennej nie jest używany konstruktor. Dzieje się tak, ponieważ za pomocą konstruktora można tworzyć tylko elementy tej tablicy. Wiersze od 10. do 13. przypisują egzemplarze typu suit_object do indeksowanych liczbowo elementów tablicy asocjacyjnej. Z punktu „Tablice asocjacyjne z elementami złożonych typów danych” w dalszej części rozdziału dowiesz się, jak pracować z elementami złożonych typów danych.

Tablice asocjacyjne indeksowane łańcuchami znaków Po zapoznaniu się z różnicami między kolekcjami SQL-a a tablicami asocjacyjnymi języka PL/SQL wiesz już, że w takich tablicach nie można wywoływać (nieistniejącego) konstruktora. Wiesz już też, że indeksy tablic VARRAY i tabel zagnieżdżonych SQL-a mogą być tylko liczbami. Tablice asocjacyjne umożliwiają stosowanie indeksów w postaci łańcuchów znaków. W tym podpunkcie zobaczysz, jak je stosować. Zamiast (jak miało to miejsce w poprzednim programie) tworzyć kolekcję kart, tu kod przypisuje do każdej karty numer. Każda blotka otrzymuje swój numer, a jeśli chodzi o figury, to as to 1, walet to 11, dama to 12, a król to 13. SQL> DECLARE 2 /* Znacząca nazwa zmiennej. */ 3 current VARCHAR2(5); 4 5 /* Definicja tablicy asocjacyjnej elementów skalarnych. */ 6 TYPE card_table IS TABLE OF NUMBER 7 INDEX BY VARCHAR2(10); 8 9 /* Deklaracja i próba utworzenia obiektu. */ 10 lv_card CARD_TABLE; 11 BEGIN 12 /* Przypisywanie wartości do kolekcji ADT. */ 13 lv_card('As') := 1; 14 lv_card('Dwojka') := 2; ... 24 lv_card('Dama') := 12; 25 lv_card('Krol') := 13; 26 27 /* Ustawianie punktu początkowego. */ 28 current := lv_card.FIRST; -- Pierwszy alfabetycznie łańcuch znaków. 29 30 /* Sprawdzanie, czy obecna wartość pseudoindeksu nie jest większa od ostatniej takiej wartości. */ 31 WHILE (current CREATE OR REPLACE 2 TYPE prominent_object IS OBJECT 3 ( name VARCHAR2(20) 4 , age VARCHAR2(10)); 5 /

Choć można zastosować wiersze od 2. do 4. z tej definicji typu obiektowego SQL-a i utworzyć w sekcji deklaracji wersję typu dostępną tylko w języku PL/SQL, przypisywanie wartości do typów obiektowych i typów rekordowych przebiega inaczej. W poniższym programie zdefiniowana jest tablica asocjacyjna prominent_table, której typem bazowym jest typ obiektowy prominent_object. Kolekcja prominent_table ma zasięg lokalny ograniczony do kodu języka PL/SQL. Do każdego elementu kolekcji można tu przypisać wynik wywołania konstruktora. Jest tak, ponieważ typem bazowym tej kolekcji jest typ obiektowy. Gdyby typem bazowym był typ rekordowy, konieczne byłoby przypisywanie rekordów do rekordów lub odpowiednich wartości do poszczególnych pól rekordów kolekcji. Poniższy program ilustruje, jak przypisywać i pobierać wartości tablicy asocjacyjnej z elementami złożonego typu danych. SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

DECLARE /* Deklaracja lokalnego typu kolekcji z elementami typu złożonego SQL-a. */ TYPE prominent_table IS TABLE OF prominent_object INDEX BY PLS_INTEGER; /* Deklaracja zmiennej lokalnej typu kolekcji. */ lv_array PROMINENT_TABLE; BEGIN /* Dla pierwszego elementu indeks jest ustawiany na -100. */ lv_array(-100) := prominent_object('Bard the Bowman','III wiek'); /* Sprawdzanie, czy istnieją elementy do pobrania. */ IF lv_array.EXISTS(-100) THEN dbms_output.put_line( '['||lv_array(-100).name||']['||lv_array(-100).age||']'); END IF; END; /

230

Część I

Podstawy języka PL/SQL

Wiersz 10. przypisuje wartość do elementu o indeksie -100 z lokalnej zmiennej lv_array zadeklarowanej w wierszu 7. Choć stosowanie indeksów w postaci ujemnych liczb całkowitych to nietypowe rozwiązanie, w programie wykorzystano taki indeks, aby pokazać, że jest to możliwe. W tablicach asocjacyjnych indeksami mogą być dowolne liczby całkowite podane w dowolnej kolejności. Zauważ, że używany typ obiektowy wymaga podania atrybutów w postaci dwóch łańcuchów znaków. Są one podane w konstruktorze. Jeśli umieścisz przypisanie z wiersza 10. w komentarzu lub zmienisz wartość indeksu, referencje do elementu umieszczone w wierszu 15. będą nieprawidłowe. Stanie się tak, ponieważ wskazywana będzie wtedy nieznana wartość indeksu, co spowoduje zgłoszenie następującego wyjątku czasu wykonania: DECLARE * ERROR at line 1: ORA-01403: no data found ORA-06512: at line 12

Wiersz 15. pokazuje też, jak wskazywać wartości zagnieżdżonych atrybutów (pól) typów obiektowych. Wymaga to podania nazwy atrybutu. To oznacza, że można używać zarówno całych zbiorów atrybutów, jak i po jednym atrybucie. Zawsze sprawdzaj, czy w tablicy asocjacyjnej istnieje dany indeks. Jeśli podasz nieistniejący indeks, wystąpi wyjątek. Dzięki sprawdzaniu indeksów w programie używane będą tylko elementy o poprawnym indeksie. Wiersz 13. określa, czy istnieje element o indeksie -100. Program dochodzi do wiersza 15. tylko wtedy, jeśli dostępny jest element o podanym indeksie. Teraz zmodyfikujmy poprzedni przykład, aby zobaczyć, jak używać w tablicach asocjacyjnych typów rekordowych. Aby rozbudować przedstawiony wcześniej kod, tu zastosujmy złożony typ rekordowy obejmujący wartość skalarną i wartość typu złożonego. Oto kod programu: SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

DECLARE /* Definicja symetrycznego typu rekordowego. */ TYPE prominent_record IS RECORD ( id INTEGER , element PROMINENT_OBJECT ); /* Deklaracja lokalnego typu kolekcji elementów złożonego typu danych. */ TYPE prominent_table IS TABLE OF prominent_record INDEX BY PLS_INTEGER; /* Deklaracja zmiennej lokalnej typu kolekcji. */ lv_array PROMINENT_TABLE; BEGIN /* Indeksem pierwszego elementu jest 1. */ lv_array(1).id := 1; lv_array(1).element := prominent_object('Bilbo Baggins','III wiek'); /* Indeksem drugiego elementu jest 2. */ lv_array(2).id := 2; lv_array(2).element := prominent_object('Frodo Baggins','III wiek'); /* Sprawdzanie, czy dostępne są elementy do pobrania. */ FOR i IN 1..lv_array.COUNT LOOP IF lv_array.EXISTS(i) THEN dbms_output.put_line('['||lv_array(i).id||']'|| '['||lv_array(i).element.name||']'|| '['||lv_array(i).element.age||']'); END IF; END LOOP; END; /

Wiersze od 3. do 5. definiują złożony typ rekordowy, obejmujący wartość skalarną (liczbę całkowitą) i element złożonego typu obiektowego. Dostęp do pola id można uzyskać za pomocą nazwy zmiennej lv_array, wartości indeksu, selektora komponentów (.) i nazwy samego pola. Drugie pole

Rozdział 6. Kolekcje

231

omawianego typu rekordowego jest bardziej skomplikowane. Zawiera ono wartość typu obiektowego. Przy zagnieżdżaniu złożonych typów danych trzeba zastosować dodatkowy selektor komponentów, aby uzyskać dostęp do zagnieżdżonych pól (atrybutów). Wiersze 15. i 18. przypisują liczby całkowite do pola id rekordowego typu danych prominent_record. Wartości dwóch pól id są przypisywane bezpośrednio. Wiersze 16. i 19. przypisują egzemplarz typu obiektowego do pól element typu rekordowego. Te wartości też są przypisywane bezpośrednio, natomiast wartości atrybutów name i age zostają przypisanie pośrednio. Dzieje się tak, ponieważ używany jest do tego konstruktor typu obiektowego. Wiersze od 24. do 26. pobierają wartość pola id oraz wartości atrybutów name i age pola element. W tym podrozdziale zobaczyłeś, jak używać tablic asocjacyjnych. Dalej poznasz interfejs Oracle Collection API. Powtórzenie materiału W tym podrozdziale opisano następujące zagadnienia dotyczące tablic asocjacyjnych języka PL/SQL:  Tablice asocjacyjne to typy danych przeznaczone tylko dla języka PL/SQL.  Elementy tablic asocjacyjnych należy przypisywać pojedynczo.  Tablice asocjacyjne, których typem bazowym są typy skalarne, to kolekcje ADT.  Tablice asocjacyjne, których typem bazowym są typy złożone, to kolekcje UDT.  Do tablic asocjacyjnych, których typem bazowym jest typ rekordowy, należy przypisywać po jednym rekordzie lub po jednym elemencie rekordu. Gdy typ bazowy to typ obiektowy, do tablic trzeba przypisywać utworzone egzemplarze danego typu obiektowego.  Tablice asocjacyjne mają indeks rzadki. Indeks może tu mieć wartości liczbowe (ujemne, dodatnie i zero) lub w postaci łańcuchów znaków.

API Collection Interfejs API Collection został wprowadzony w Oracle 8i. Ten interfejs ma zapewniać łatwy dostęp do kolekcji. Działa on dla trzech rodzajów kolekcji. W tablicach VARRAY i tabelach zagnieżdżonych SQL-a indeksami są liczby całkowite. W tablicach asocjacyjnych od wersji Oracle Database 11g obsługiwane są indeksy w postaci liczb całkowitych i łańcuchów znaków. Metody API Collection nie są metodami w znaczeniu używanym w programowaniu obiektowym. Są to funkcje i procedury. Trzy z nich (EXTEND, TRIM i DELETE) to procedury, a pozostałe to funkcje. Tabela 6.3 przedstawia API Collection z Oracle 12c. Tabela 6.3. Interfejs API Collection bazy danych Oracle 12c Metoda

Opis

COUNT

Metoda COUNT zwraca liczbę elementów, którym przydzielono pamięć w tablicach VARRAY i tabelach zagnieżdżonych, lub liczbę elementów tablic asocjacyjnych. W przypadku tablic VARRAY wartość zwrócona przez COUNT może być mniejsza niż ta zwrócona przez LIMIT. Jej prototyp to: PLS_INTEGER COUNT

DELETE

Metoda DELETE umożliwia usuwanie elementów tabel zagnieżdżonych i tablic asocjacyjnych (nie działa dla tablic VARRAY). Próba usunięcia elementu z tablicy VARRAY powoduje zgłoszenie wyjątku PLS-00306. Ma dwa parametry formalne: wymagany i opcjonalny. Oba określają indeks i trzeba je podać w kolejności rosnącej. Jeśli programista poda dwa argumenty, funkcja usunie wszystkie elementy między indeksami n a m, w tym także wartości o tych indeksach. Jej prototypy to: void DELETE(n) void DELETE(n,m)

232

Część I

Podstawy języka PL/SQL

Tabela 6.3. Interfejs API Collection bazy danych Oracle 12c Metoda

Opis

EXISTS

Metoda EXISTS sprawdza, czy element o podanym indeksie znajduje się w kolekcji, czy nie. Zwraca true, jeśli znajdzie dany element, a null, jeśli zainicjowana tablica VARRAY lub tabela zagnieżdżona jest pusta. Metoda ta ma jeden wymagany parametr, który określa indeks. Jej prototyp to: boolean EXISTS(n)

EXTEND

Przydziela pamięć dla nowych elementów tablic VARRAY i tabel zagnieżdżonych. Ma dwa parametry opcjonalne. Jeśli programista nie poda żadnego argumentu, metoda domyślnie przydzieli pamięć dla jednego elementu. Pojedynczy parametr określa liczbę dodawanych elementów, jednak w tablicach VARRAY jest ona ograniczona przez wartość LIMIT. Drugi parametr to indeks. Przy dwóch parametrach pierwszy określa, dla ilu elementów należy przydzielić pamięć, podczas gdy drugi wskazuje indeks elementu, którego wartość metoda ma przypisać nowym pozycjom kolekcji. Prototypy tej metody wyglądają następująco: void EXTEND void EXTEND(n) void EXTEND(n,i)

FIRST

Metoda FIRST zwraca najniższy indeks kolekcji. Typ zwracanej wartości to PLS_INTEGER, VARCHAR2 lub LONG. Prototyp tej metody to: mixed FIRST

LAST

Metoda LAST zwraca najwyższy indeks kolekcji. Typ zwracanej wartości to PLS_INTEGER, VARCHAR2 lub LONG. Prototyp tej metody to: mixed LAST

LIMIT

Metoda LIMIT zwraca najwyższy dozwolony indeks tablic VARRAY. Zwraca wartości typu PLS_INTEGER, a współdziała tylko z tablicami VARRAY. Jej prototyp to: mixed LIMIT

NEXT(n)

Zwraca następny indeks kolekcji lub — jeśli jej działanie zakończy się niepowodzeniem — wartość false. Zwracane wartości mają typ PLS_INTEGER, VARCHAR2 lub LONG. Argumentem musi być prawidłowa wartość indeksu (podanie nieprawidłowego indeksu prowadzi do zgłoszenia wyjątku). Prototyp tej metody to: mixed NEXT(n)

PRIOR(n)

Zwraca poprzedni indeks kolekcji lub — jeśli jej działanie zakończy się niepowodzeniem — wartość false. Zwracane wartości mają typ PLS_INTEGER, VARCHAR2 lub LONG. Argumentem musi być prawidłowa wartość indeksu (podanie nieprawidłowego indeksu prowadzi do zgłoszenia wyjątku). Prototyp tej metody to: mixed PRIOR(n)

TRIM

Metoda TRIM usuwa z kolekcji indeksowane elementy. Ma jeden opcjonalny parametr. Jeśli programista nie poda argumentu, metoda usunie element o najwyższym indeksie. Podany argument określa liczbę elementów usuwanych z końca kolekcji. Prototypy tej metody wyglądają następująco: void TRIM void TRIM(n)

Zauważ, że tylko metoda EXISTS nie zgłasza wyjątku, gdy element tablicy VARRAY lub tabeli zagnieżdżonej SQL-a ma wartość null. Ta metoda zwraca wtedy true, ponieważ sprawdza, czy dla danego elementu przydzielona jest pamięć. Element skalarny może mieć wartość null lub inną, a element typu obiektowego może być równy null, być pusty lub zawierać egzemplarz tego typu.

Rozdział 6. Kolekcje

233

Poniższy kod sprawdza działanie metody EXISTS: SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14

DECLARE /* Definicja tabeli zagnieżdżonej. */ TYPE empty_table IS TABLE OF prominent_object; /* Deklaracja zmiennej typu kolekcji. */ lv_array EMPTY_TABLE := empty_table(null); BEGIN /* Sprawdzanie, czy dla elementu przydzielono pamięć. */ IF lv_array.EXISTS(1) THEN dbms_output.put_line('Prawidłowa kolekcja.'); ELSE dbms_output.put_line('Nieprawidłowa kolekcja.'); END IF; END; /

Wiersz 5. deklaruje kolekcję lv_array zawierającą jeden element o wartości null. Wiersz 8. sprawdza, czy dla pierwszego elementu tej kolekcji przydzielona jest pamięć. Ten test zwraca wartość true, a program wyświetla informację: Prawidłowa kolekcja.

To dlatego należy łączyć tę funkcję ze sprawdzaniem, czy dany element ma określoną wartość. Możesz zmodyfikować wiersz 8., aby sprawdzać przydzieloną pamięć i wartość elementu: 8

IF lv_array.EXISTS(1) AND lv_array(1) IS NOT NULL THEN

Operator iloczynu logicznego (AND) gwarantuje, że gdy funkcja EXISTS zwróci wartość false, operacja porównywania zostanie zakończona. Ten operator przeprowadza przetwarzanie skrócone, dlatego jeśli jedno z porównań ma wartość false, drzewo przetwarzania jest skracane. To sprawia, że jeśli pierwszy warunek ma wartość false, program nigdy nie dochodzi do drugiego. Rozróżnia się pięć standardowych wyjątków związanych z kolekcjami. Są one opisane w tabeli 6.4. Tabela 6.4. Wyjątki związane z kolekcjami Wyjątek związany z kolekcjami

Warunki zgłoszenia

COLLECTION_IS_NULL

Próba użycia pustej kolekcji.

NO_DATA_FOUND

Próba użycia usuniętego lub nieistniejącego indeksu tablicy asocjacyjnej.

SUBSCRIPT_BEYOND_COUNT

Próba użycia indeksu liczbowego wyższego od aktualnie najwyższego lub wskazującego usunięty element. Ten błąd dotyczy wyłącznie tablic VARRAY i tabel zagnieżdżonych.

SUBSCRIPT_OUTSIDE_LIMIT

Próba użycia indeksu liczbowego wykraczającego poza wartość zwracaną przez metodę LIMIT. Ten błąd dotyczy wyłącznie tablic VARRAY.

VALUE_ERROR

Próba użycia typu, którego nie można przekształcić na typ PLS_INTEGER.

Metody zostały omówione w porządku alfabetycznym w następnych punktach. Niektóre przykłady obejmują kilka metod API Collection. Podobnie jak w przypadku opisu rodzajów kolekcji trudno jest przedstawiać metody API Collection w izolacji. Jeśli dany przykład w pełni ilustruje kilka metod, podane są odwołania do niego.

Metoda COUNT Metoda COUNT jest tak naprawdę funkcją. Nie ma listy parametrów formalnych. Zwraca liczbę elementów tablicy. Ta liczba odpowiada numerowi końcowego elementu kolekcji, ponieważ w bazach Oracle indeksy rozpoczynają się od 1. Poniższy przykładowy program zwraca liczbę elementów kolekcji określoną za pomocą metody COUNT: SQL> DECLARE 2 /* Definicja kolekcji. */ 3 TYPE x_table IS TABLE OF INTEGER;

234 4 5 6 7 8 9 10

Część I

Podstawy języka PL/SQL

/* Deklaracja zainicjowanej kolekcji. */ lv_table NUMBER_TABLE := number_table(1,2,3,4,5); BEGIN DBMS_OUTPUT.PUT_LINE('Ile elementów? ['||lv_table.COUNT||']'); END; /

Wiersz 8. wygeneruje następujące dane wyjściowe: Ile elementów? [5]

Funkcję COUNT najlepiej stosować do sprawdzania liczby elementów przed wykonaniem operacji na kolekcji.

Metoda DELETE Metoda DELETE to w rzeczywistości przeciążona procedura. Jeśli zagadnienie przeciążania jest dla Czytelnika czymś nowym, może zajrzeć do rozdziału 9. Jedna z wersji tej metody przyjmuje jeden parametr formalny. Druga wersja przyjmuje dwa parametry formalne. Jeśli podawany jest jeden parametr, określa on indeks usuwanego elementu. Gdy podawane są dwa parametry, wyznaczają one przedział indeksów. Niższa z tych wartości określa pierwszy z usuwanych elementów, a większa — ostatni kasowany element. Aby usunąć jedną wartość za pomocą wersji z dwoma parametrami, należy dwukrotnie podać ten sam indeks. Poniższy blok anonimowy ilustruje usuwanie przedziału z kolekcji: SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

DECLARE /* Deklaracja zmiennej o znaczącej nazwie. */ current INTEGER; /* Definicja kolekcji. */ TYPE x_table IS TABLE OF VARCHAR2(6); /* Deklaracja zainicjowanej kolekcji. */ lv_table X_TABLE := xtable('Jeden','Dwa','Trzy','Cztery','Pięć'); BEGIN /* Usuwanie jednego elementu o indeksie 2. */ lv_table.DELETE(2,2); /* Usuwanie elementów z przedziału od 4. do 5. */ lv_table.DELETE(4,5); /* Ustawianie początkowego indeksu. */ current := lv_table.FIRST; /* Wczytywanie indeksów w kolejności rosnącej. */ WHILE (current 2 3 4 5 6 7 8 9 10 11 12 13 14 15

DECLARE /* Definicja tabeli zagnieżdżonej. */ TYPE x_table IS TABLE OF VARCHAR2(10); /* Deklaracja indeksu. */ lv_index NUMBER := 1; /* Deklaracja zmiennej lokalnej typu kolekcji. */ lv_table X_TABLE := x_table(); BEGIN IF lv_table.EXISTS(lv_index) AND NOT lv_table.COUNT = 0 THEN dbms_output.put_line('Lista ['||lv_table(lv_index)||']'); END IF; END; /

Wiersz 3. definiuje tabelę zagnieżdżoną. Wiersz 9. deklaruje zmienną lv_table jako zainicjowaną kolekcję. W wierszu 11. za pomocą funkcji EXISTS sprawdzany jest pierwszy element tej kolekcji. Ponieważ taki element nie istnieje, funkcja zwraca wartość null. Dalej w iloczynie logicznym (operator AND) używana jest funkcja COUNT do ustalenia, czy w kolekcji nie znajdują się żadne elementy. Kod w wierszu 11. można zmodyfikować, aby sprawdzał, czy dostępnych jest więcej niż zero elementów: 11 IF lv_table.EXISTS(lv_index) AND lv_table.COUNT > 0 THEN

Niezbędne są oba testy, aby mieć pewność, że program nie próbuje uzyskać dostępu do elementu o nieistniejącym indeksie. Jest tak, ponieważ funkcja EXISTS zwraca wartość null, jeśli nie znajdzie indeksu. Tu program nie wyświetla żadnych danych, ponieważ kolekcja jest pusta. Możesz zmodyfikować wiersz 9., aby utworzyć kolekcję zawierającą jeden element (lub większą ich liczbę): 09

lv_table X_TABLE := x_table('Dane');

Wtedy program wyświetli następujący łańcuch znaków: Lista [Dane]

Za pomocą wywołania funkcji EXISTS przed użyciem elementu kolekcji można uniknąć błędów czasu wykonania. Jest to jedyne przeznaczenie tej funkcji.

236

Część I

Podstawy języka PL/SQL

Metoda EXTEND Metoda EXTEND jest w rzeczywistości przeciążoną procedurą (podobnie jak DELETE). Jeśli przeciążanie okazuje się dla Czytelnika czymś nowym, może zajrzeć do rozdziału 9. (poświęconego pakietom). Istnieją trzy przeciążone wersje procedury EXTEND:  Jedna nie przyjmuje parametrów formalnych. Ta wersja przydziela pamięć dla jednego elementu.  Druga wersja przyjmuje jeden parametr formalny. Ta wersja przydziela pamięć dla liczby elementów określonych przez ten parametr.  Ostatnia wersja przyjmuje dwa parametry formalne. Ta wersja (podobnie jak odmiana z jednym parametrem) przydziela pamięć dla liczby elementów określonych przez pierwszy argument, a następnie kopiuje do nowych elementów zawartość elementu o indeksie podanym jako drugi argument (musi nim być poprawny indeks). Poniższy program ilustruje, jak za pomocą procedury EXTEND przydzielić pamięć. Wykorzystano tu blok anonimowy z przykładu dotyczącego procedury DELETE. SQL> DECLARE ... To samo co w przykładzie dotyczącym procedury DELETE ... 7 8 /* Deklaracja zainicjowanej kolekcji. */ 9 lv_table X_TABLE := x_table('Jeden'); 10 BEGIN 11 /* Zajmowanie pamięci i przypisywanie wartości. */ 12 lv_table.EXTEND; 13 14 /* Przypisywanie wartości do nowo dodanego elementu. */ 15 lv_table(lv_table.COUNT) := 'Dwa'; 16 ... To samo co w przykładzie dotyczącym procedury DELETE ... 27 END; 28 /

Wiersz 9. jest zmieniony w porównaniu z poprzednim przykładem i tworzy jednoelementową kolekcję. Wiersz 12. przydziela pamięć na dodatkowy element. Wiersz 15. za pomocą wywołania funkcji COUNT wskazuje ostatni dodany element. Ten program wyświetla następujące dane: Indeks [1] Wartość [Jeden] Indeks [2] Wartość [Dwa]

Metoda EXTEND jest przydatna, gdy program ma przypisać nieznaną liczbę elementów z kursora do tablicy VARRAY lub tabeli zagnieżdżonej. Dostępne są dwie główne możliwości. Jedna z nich to przydzielanie pamięci po kolei dla poszczególnych elementów (tak jak w przykładzie). Druga polega na przydzieleniu całej pamięci w jednym kroku, przy czym trzeba wtedy wiedzieć, ile elementów program dodaje do kolekcji.

Metoda FIRST Metoda FIRST to tak naprawdę funkcja. Zwraca najniższy indeks używany w kolekcji (jest to indeks jednego z elementów granicznych). Dla tablic VARRAY i tabel zagnieżdżonych ta funkcja zwraca wartość 1. Dla tablic asocjacyjnych z indeksami liczbowymi zwracany jest indeks o najmniejszej wartości. Jeśli tablica asocjacyjna ma indeksy w postaci łańcuchów znaków, zwracany jest najmniejszy taki indeks. To, który łańcuch zostanie uznany za najmniejszy, zależy od sposobu sortowania (używane są reguły sortowania alfabetycznego NLS_COMP i NLS_SORT). Choć reguły dotyczące liczb są jasne, nie dotyczy to łańcuchów znaków. Poniższy przykład tworzy tablicę asocjacyjną indeksowaną łańcuchami znaków, a następnie wyświetla pierwszą wartość: SQL> DECLARE 2 /* Definicja tablicy asocjacyjnej. */ 3 TYPE x_table IS TABLE OF INTEGER

Rozdział 6. Kolekcje 4 5 6 7 8 9 10 11 12 13 14 15 16 17

237

INDEX BY VARCHAR2(9 CHAR); /* Deklaracja zmiennej typu tablicy asocjacyjnej. */ lv_table X_TABLE; BEGIN /* Dodawanie elementów do tablicy asocjacyjnej. */ lv_table('Siedem') := 7; lv_table('Osiem') := 8; /* Wyświetlanie elementu zwróconego za pomocą najmniejszego indeksu w postaci łańcucha znaków. */ dbms_output.put_line( 'Indeks ['||lv_table.FIRST||']['||lv_table(lv_table.FIRST)||']'); END; /

Wiersze 10. i 11. przypisują do tablicy asocjacyjnej dwa elementy. Wartości są wprowadzane w kolejności od najmniejszej do największej, jednak indeksy w wyniku sortowania łańcuchów znaków są porządkowane w odwrotny sposób. Wiersze 14. i 15. (dwa wiersze są potrzebne ze względu na szerokość stron książki) wyświetlają indeks i wartość pierwszego elementu używanej tablicy asocjacyjnej: Indeks [Osiem][8]

Funkcja FIRST jest bardzo ważna przy wyszukiwaniu wartości odpowiadającej pierwszemu indeksowi dowolnej kolekcji, jednak jej znaczenie jest wyjątkowo duże, gdy chodzi o indeksy rzadkie. Za pomocą tej funkcji można jednoznacznie stwierdzić, od którego miejsca należy zacząć poruszanie się po kolekcji.

Metoda LAST Metoda LAST to tak naprawdę funkcja. Zwraca, podobnie jak funkcja FIRST, indeks granicznego elementu kolekcji; jest to najwyższy indeks używany w danej kolekcji. W tablicach VARRAY i tabelach zagnieżdżonych funkcja ta zwraca tę samą wartość co funkcja COUNT (przy czym nie zawsze jest to wartość zwrócona przez funkcję LIMIT). W tych kolekcjach funkcja LAST zwraca największą wartość, natomiast w tablicach asocjacyjnych zwraca największą liczbę lub największy łańcuch znaków. W bazach Oracle do tworzenia reguł sortowania alfabetycznego (określają one sposób sortowania łańcuchów znaków) używane są ustawienia NLS_COMP i NLS_SORT. Jeśli zastąpisz wywołanie funkcji FIRST wywołaniem LAST w wierszu 15. przykładowego programu ilustrującego działanie metody FIRST (zobacz poprzedni punkt), program wyświetli największy (alfabetycznie) indeks w postaci łańcucha znaków i odpowiadającą mu wartość: 14 15

dbms_output.put_line( 'Indeks ['||lv_table.LAST||']['||x_list(lv_table.LAST)||']');

Ten program wyświetli następujące dane: Indeks [Siedem][7]

Funkcja LAST jest bardzo ważna przy wyszukiwaniu ostatniego indeksu dowolnej kolekcji, jednak jej znaczenie jest najbardziej widoczne przy wyszukiwaniu takich indeksów w tablicach asocjacyjnych i przy stosowaniu rzadkich indeksów w postaci łańcuchów znaków.

Metoda LIMIT Metoda LIMIT to w rzeczywistości funkcja. Zwraca wartość reprezentującą najwyższy indeks używany w danej tablicy VARRAY. W pozostałych rodzajach kolekcji nie zwraca wartości. Poniższy program ilustruje zastosowanie metody LIMIT: SQL> DECLARE 2 /* Definicja tablicy VARRAY. */ 3 TYPE x_varray IS VARRAY(5) OF INTEGER; 4 5 /* Deklaracja zainicjowanej kolekcji. */ 6 lv_array X_VARRAY := x_varray(1,2,3);

238 7 8 9 10 11 12

Część I

Podstawy języka PL/SQL

BEGIN /* Wyświetlanie wartości zwracanych przez funkcje COUNT i LIMIT. */ dbms_output.put_line( 'Liczba['||lv_array.COUNT||']: Limit['||lv_array.LIMIT||']'); END; /

Wiersz 3. definiuje pięcioelementową tablicę VARRAY. Wiersz 6. tworzy trzyelementową tablicę. Wiersz 10. wyświetla rzeczywistą i maksymalną liczbę elementów tej tablicy. Oto wynik: Liczba[3]: Limit[5]

Funkcja LIMIT służy do określania maksymalnej liczby elementów tablicy VARRAY. Można ją wykorzystać jako warunek wejścia, aby uniknąć dodawania większej liczby elementów niż ustawiona maksymalna pojemność tablicy.

Metoda NEXT Metoda NEXT to tak naprawdę funkcja. Zwraca następny indeks używany w kolekcji po bieżącym. Ponieważ kolekcje w bazach Oracle są jak jednokierunkowe listy powiązane (ostatni element nie zawiera odnośnika do pierwszego), to po dotarciu do ostatniego indeksu metoda NEXT zwraca null. Choć w tablicach z indeksem gęstym można w każdym kroku zwiększać indeks o jeden, indeks rzadki tego nie umożliwia. Funkcja NEXT pozwala przechodzić między kolejnymi indeksami niezależnie od tego, czy dany indeks jest rzadki, czy gęsty. Poniższy fragment kodu z poprzedniego przykładu ilustruje, jak używać funkcji NEXT: 10 BEGIN 11 /* Ustawianie początkowego indeksu. */ 12 current := lv_table.FIRST; 13 14 /* Wczytywanie indeksów rosnąco. */ 15 WHILE (current 2 3 4 5 6 7 8 9

DECLARE lv_a NUMBER := 0; lv_b NUMBER; lv_c NUMBER; BEGIN lv_c := lv_a lv_b; dbms_output.put_line('['||lv_c||']'); END /

246

Część I

Podstawy języka PL/SQL

Środowisko wyświetli następujący komunikat o błędzie: lv_c := lv_a

lv_b; *

ERROR at line 6: ORA-06550: line 6, column 17: PLS-00103: Encountered the symbol "LV_B" when expecting one of the following: . ( * @ % & = - + ; < / > at in is mod remainder not rem or != or ~= >= 2 3 4 5 6

DECLARE lv_a NUMBER; BEGIN lv_a = 1; END; /

Wiersz 4. zawiera operator porównywania, a należało zastosować operator przypisywania. Komunikat o błędzie wskazuje operator porównywania jako źródło problemu: lv_a = 1; * ERROR at line 4: ORA-06550: line 4, column 8: PLS-00103: Encountered the symbol "=" when expecting one of the following: := . ( @ % ; The symbol ":= was inserted before "=" to continue.

Ten rodzaj komunikatów o błędzie informuje o błędnym użyciu operatora porównania. Komunikaty o błędzie tego typu są najbardziej czytelne i zrozumiałe. Mniej oczywiste są komunikaty informujące o błędach występujących w sekcji deklaracji. W poniższym programie kod w sekcji deklaracji próbuje przypisać dwuznakowy łańcuch do jednoznakowej zmiennej: SQL> 2 3 4 5 6

DECLARE lv_a CHAR := 'AB'; BEGIN dbms_output.put_line('['||lv_a||']'); END; /

Ten program spowoduje wyświetlenie następującego komunikatu o błędzie, zawierającego bardzo mało informacji, jeśli próbuje się go odczytać według opisanych wcześniej reguł: DECLARE * ERROR at line 1: ORA-06502: PL/SQL: numeric or value error: character string buffer too small ORA-06512: at line 2

Ten komunikat wskazuje na wiersz pierwszy. W odróżnieniu od poprzednich komunikatów nie informuje o problemie w kodzie poprzedzającym instrukcję DECLARE, ale o tym, że usterka znajduje się w sekcji deklaracji. Tekst po członie ERROR at line 1 opisuje problem, a na końcu wskazany jest numer wiersza, w którym wystąpił błąd.

Rozdział 7. Obsługa błędów

247

Zauważ, że ostatni wiersz komunikatu wskazuje na pierwszy błąd zapisany na stosie wyjątków. Błąd ORA-06512 w ostatnim wierszu komunikatu o błędzie wskazuje numer wiersza, w którym wystąpił problem. Kolejny wiersz na stosie opisuje ten problem, a trzeci komunikat informuje, że błąd wystąpił w sekcji deklaracji. Błąd wystąpił przy próbie przypisania literału łańcuchowego 'AB' do zmiennej o pojemności jednego znaku. Taki błąd pojawia się po parsowaniu programu, przy próbie powiązania literału znakowego lub liczbowego ze zmienną.

Błędy czasu wykonania Błędy czasu wykonania mogą znajdować się w sekcjach deklaracji, wykonawczej i obsługi wyjątków bloków języka PL/SQL. Najłatwiejsze do przechwycenia i obsłużenia okazują się błędy zgłaszane w bloku wykonawczym, ponieważ są przechwytywane najpierw przez lokalną sekcję obsługi wyjątków, a następnie przez bloki zewnętrzne. Z kolei błędy zgłaszane w sekcji obsługi wyjątków można przechwycić tylko w podobnej sekcji bloku zewnętrznego. Sekcje wyjątków obejmują bloki WHEN. Bloki te przechwytują błędy specyficzne i ogólne. Prototyp takiego bloku wygląda następująco: WHEN [wyjątek_wbudowany | wyjątek_zdefiniowany_przez_użytkownika | OTHERS] THEN [RETURN | EXIT];

Blok WHEN może przechwytywać wbudowane wyjątki Oracle, wyjątki zdefiniowane przez użytkownika, a także wszystkie wyjątki (za pomocą słowa kluczowego OTHERS). Jeśli klauzula WHEN ma przechwytywać wszystkie wyjątki, należy użyć słowa zarezerwowanego OTHERS. Blok WHEN OTHERS zawsze należy stosować jako ostatni blok obsługi wyjątków; w przeciwnym razie spowoduje pominięcie bloków obsługi specyficznych wyjątków. W tabeli 7.2 w dalszej części rozdziału znajdziesz nazwy wyjątków wbudowanych Oracle. Mają one określony numer. Informacje te są zdefiniowane w pakiecie sys.standard. Oracle definiuje te błędy we wspomnianym pakiecie w następujący sposób: 162 163

CURSOR_ALREADY_OPEN exception; pragma EXCEPTION_INIT(CURSOR_ALREADY_OPEN, '-6511');

Można też zdefiniować własne wyjątki. Jest to proces dwuetapowy. Najpierw należy utworzyć zmienną typu EXCEPTION. Następnie trzeba powiązać zdefiniowaną przez użytkownika zmienną tego typu ze specyficznym numerem błędu. Wiązanie wyjątków z numerami błędów umożliwia dyrektywa PRAGMA (jest to instrukcja prekompilatora), używana także w pakiecie sys.standard. Proces definiowania wyjątków użytkownika opisano w punkcie „Błędy zdefiniowane przez użytkownika” w dalszej części rozdziału. Oracle udostępnia też dwie wbudowane funkcje obsługi błędów, SQLCODE i SQLERRM. Upraszczają one proces wyświetlania kodów i komunikatów zgłoszonych błędów. Opis tych funkcji zawiera tabela 7.1. Następne podpunkty opisują zgłaszanie błędów w sekcjach wykonawczej i obsługi wyjątków, a następnie wyjątki w sekcji deklaracji. Ta kolejność pozwala najpierw zobaczyć, jak działają podstawowe mechanizmy, przed zapoznaniem się z sytuacjami, w których zawodzą.

Błędy w sekcjach wykonawczej i obsługi wyjątków Błędy zgłaszane w sekcji wykonawczej są przekazywane do lokalnej sekcji obsługi wyjątków, gdzie można je przechwycić i obsłużyć. W PL/SQL blok wyjątków to inna nazwa sekcji obsługi wyjątków. Jeśli lokalna sekcja obsługi wyjątków nie potrafi przechwycić wyjątku, a cały blok został wywołany w innym programie języka PL/SQL, wyjątek trafia do programu zewnętrznego. Tym programem może być środowisko SQL*Plus, instrukcja SQL-a (więcej na ten temat dowiesz się z rozdziału 8.) lub blok języka PL/SQL. Blokiem języka PL/SQL może być tu blok zewnętrzny; jest to najprostszy sposób na zilustrowanie mechanizmów obsługi wyjątków. W programach wywołujących należy stosować ogólne bloki obsługi wyjątków, aby zarządzać wyjątkami przekazanymi przez inne jednostki programu. Poniższy program pokazuje, jak w lokalnej sekcji obsługi wyjątków obsłużyć lokalnie zgłoszony wyjątek. Ta sekcja obsługuje tylko wyjątki typu

248

Część I

Podstawy języka PL/SQL

Tabela 7.1. Funkcje wbudowane do zarządzania wyjątkami w Oracle Funkcja

Wbudowane błędy Oracle

Błędy zdefiniowane przez użytkownika

SQLCODE

Zwraca liczbę ujemną, która odpowiada wbudowanemu błędowi Oracle. Wyjątkiem jest jeden przypadek specjalny — błąd NO_DATA_FOUND powiązany z dodatnią liczbą 100.

Zwraca dodatnią liczbę 1, jeśli programista nie zdefiniował dyrektywy EXCEPTION_INIT PRAGMA. Jeśli jest ona zdefiniowana, zwraca odpowiednią liczbę z przedziału od –20999 do –20001.

SQLERRM

Ta funkcja jest przeciążona i działa w następujący sposób: jeśli nie otrzyma numeru, zwraca kod zdefiniowanego błędu i powiązany z nim komunikat. Jeśli otrzyma liczbę dodatnią lub liczbę ujemną, która nie odpowiada wbudowanemu wyjątkowi Oracle, zwraca argument w postaci liczby ujemnej i komunikat wyjątku spoza Oracle. Jeśli otrzyma liczbę ujemną, która odpowiada wbudowanemu wyjątkowi Oracle, zwraca argument w postaci liczby ujemnej i komunikat wbudowanego wyjątku Oracle.

Zwraca liczbę 1 i komunikat „User-Defined Exception”, jeśli zostanie wywołana za pomocą polecenia RAISE. Jeśli programista użyje funkcji raise_application_info, zwraca liczbę z określonego przedziału i komunikat.

Wbudowane funkcje obsługi wyjątków w bazach Oracle Funkcja SQLCODE zwraca numer błędu powiązany ze zgłoszonym wyjątkiem. Funkcja SQLERRM zgłasza numer i komunikat błędu dla zgłoszonego wyjątku. Niestety, ich działanie nie jest tak proste, jak wskazują na to dwa pierwsze zdania; te funkcje w różnych scenariuszach działają w odmienny sposób. Opisano to poniżej, a także w dodatku C.

Funkcja SQLCODE

Funkcja SQLCODE zwraca jedną z trzech wartości. Dla prawie wszystkich wbudowanych wyjątków Oracle zwraca liczbę ujemną. Inaczej działa tylko dla wyjątku NO_DATA_FOUND, dla którego zwracana jest liczba dodatnia 100. Dla wyjątków zdefiniowanych przez użytkownika funkcja zwraca 1.

Funkcja SQLERRM

Ta funkcja zwraca kod błędu i pusty łańcuch znaków lub komunikat. Oto dane zwracane przez tę funkcję:  Kod i komunikat dla dowolnego nieobsłużonego kodu błędów lub nazwa wbudowanego wyjątku Oracle.  Wartość 100 i komunikat „User-Defined Exception” dla zdefiniowanego przez użytkownika wyjątku zgłoszonego za pomocą następującej instrukcji: RAISE wyjątek_zdefiniowany_przez_użytkownika; 

Wartość z przedziału od -20999 do -20001 i niestandardowy komunikat o błędzie dla zdefiniowanego przez użytkownika wyjątku zgłoszonego za pomocą następującej instrukcji: RAISE_APPLICATION_ERROR(kod_błędu, niestandardowy_komunikat);

Dyrektywa PRAGMA EXCEPTION_INIT wiąże ujemną liczbę całkowitą z komunikatem o błędzie. Wywołanie funkcji RAISE_APPLICATION_ERROR powoduje zgłoszenie takiego błędu razem z niestandardowym komunikatem. Funkcja SQLERRM zwraca wtedy kod i niestandardowy komunikat o błędzie. Należy śledzić komunikaty zdefiniowane przez użytkownika, ponieważ programiści często wielokrotnie stosują te same numery dla różnych błędów. To podejście jest mylące dla użytkowników i administratorów systemu. W takiej sytuacji trudno jest zrozumieć znaczenie poszczególnych błędów, a czasem jest to wprost niemożliwe. Jak więc zarządzać błędami? Zachęcam do utworzenia globalnej tabeli wyszukiwania i przechowywania w niej listy zdefiniowanych błędów. Programiści powinni zapisywać w tej tabeli informacje o numerach wykorzystanych dla błędów zdefiniowanych przez użytkownika. Gdy programista zechce sprawdzić, które numery błędów są zajęte, wystarczy, że zajrzy do tej tabeli. To podejście pozwala uniknąć ponownego wykorzystywania wartości błędów zdefiniowanych przez użytkownika. Dzięki temu identyfikowanie błędów i zarządzanie komunikatami zdefiniowanymi przez użytkowników staje się proste.

Rozdział 7. Obsługa błędów

249

value_error. Choć mogą być one zgłaszane w różnych sytuacjach, ten kod zgłasza błąd przy próbie umieszczenia dwuznakowego łańcucha w zmiennej o pojemności jednego znaku: SQL> 2 3 4 5 6 7 8 9 10 11

DECLARE lv_a VARCHAR2(1); lv_b VARCHAR2(2) := 'AB'; BEGIN lv_a := lv_b; EXCEPTION WHEN value_error THEN dbms_output.put_line( 'Nie można zapisać ['||lv_b||'] w jednoznakowym łańcuchu.'); END; /

Wiersz 1. deklaruje jednoznakową zmienną lv_a. Wiersz 3. deklaruje dwuznakową zmienną lv_b. Wiersz 5. próbuje przypisać zmienną dwuznakową do zmiennej jednoznakowej. Ta próba kończy się niepowodzeniem i zgłoszeniem następującego błędu: Nie można zapisać [AB] w jednoznakowym łańcuchu.

Ten przykład pokazuje, jak przechwycić i obsłużyć lokalny błąd w lokalnej sekcji obsługi wyjątków. W klauzuli WHEN używany jest tu wyjątek wbudowany value_error. Ta klauzula tworzy więc blok obsługi wyjątków value_error, dlatego przechwytuje tylko błędy ORA-06502 i zarządza nimi. Wszystkie inne wyjątki są ignorowane i przekazywane do sesji środowiska SQL*Plus. Poniższy kod wywołuje błąd NO_DATA_FOUND w bloku wewnętrznym. Ponieważ jedyny blok obsługi wyjątków przechwytuje tylko wyjątki wbudowane value_error, ten błąd nie zostaje przechwycony. Zamiast tego zostaje przekazany do bloku wywołującego: SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

DECLARE lv_a VARCHAR2(1); BEGIN DECLARE lv_b VARCHAR2(2); BEGIN SELECT 1 INTO lv_b FROM dual WHERE 1 = 2; lv_a := lv_b; EXCEPTION WHEN value_error THEN dbms_output.put_line( 'Nie można zapisać ['||b||'] w jednoznakowym łańcuchu.'); END; EXCEPTION WHEN others THEN dbms_output.put_line( 'Przechwycono w bloku zewnętrznym: ['||SQLERRM||'].); END; /

Zapytanie SELECT INTO z wierszy od 7. do 9. bez powodzenia próbuje pobrać literał liczbowy do zmiennej lokalnej. Dzieje się tak, ponieważ zastosowana klauzula WHERE zawsze zwraca false (w końcu 1 nie jest równe 2). Lokalny blok obsługi wyjątków value_error jest pomijany, a program przekazuje błąd do zasięgu wywołania (do bloku zewnętrznego). Blok obsługi wyjątków others przechwytuje zgłoszony błąd NO_DATA_FOUND. Blok obsługi wyjątków others zawsze należy umieszczać na końcu listy takich bloków, ponieważ jest ogólny i przechwytuje wszystkie wyjątki — zarówno te przewidziane, jak i nieprzewidziane. Blok obsługi wyjątków z zewnętrznego bloku przedstawionego programu wyświetla za pomocą funkcji SQLERRM następujący komunikat: Przechwycono w bloku zewnętrznym: [ORA-01403: no data found].

250

Część I

Podstawy języka PL/SQL

Można ręcznie zgłosić błąd zdefiniowany przez użytkownika bez wywoływania go w kodzie. Służy do tego instrukcja RAISE. W poniższym programie ta technika pozwala sprawdzić, co się stanie, jeśli błąd pojawi się w sekcji obsługi wyjątków. SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

DECLARE lv_a VARCHAR2(1); e EXCEPTION; BEGIN DECLARE lv_b VARCHAR2(2) := 'AB'; BEGIN RAISE e; EXCEPTION WHEN others THEN lv_a := lv_b; dbms_output.put_line('Kod nie dochodzi do tego wiersza.'); END; EXCEPTION WHEN others THEN dbms_output.put_line( 'Przechwycono w bloku zewnętrznym->'||dbms_utility.format_error_backtrace); END; /

Wiersz 8. wywołuje instrukcję RAISE, aby zgłosić błąd. To powoduje przekazanie sterowania do bloku obsługi wyjątków zaczynającego się w wierszu 9. Wiersz 11. próbuje przypisać dwuznakowy łańcuch do jednoznakowej zmiennej. Wywołany tym błąd powoduje przekazanie sterowania z bloku wewnętrznego do bloku obsługi wyjątków z bloku zewnętrznego. Zawsze umieszczaj ogólny blok obsługi wyjątków others na końcu listy takich bloków.

Blok zewnętrzny wywołuje funkcję format_error_backtrace i zwraca następujący komunikat: Przechwycono w bloku zewnętrznym->ORA-06512: at line 11

Wyjątek ORA-06512 określa numer wiersza, w którym wystąpił błąd. Ten numer jest ustalany w wyniku parsowania pliku użytego do uruchomienia programu. Aby znaleźć ten numer, uruchom plik skryptu w środowisku SQL*Plus, a następnie wpisz instrukcję list (wystarczy wpisać samą literę l). To spowoduje wyświetlenie programu z numerami wierszy wskazywanymi przez błąd ORA-06512. Jeśli program to funkcja lub procedura składowana, pakiet lub typ obiektowy, można wywołać zapytanie do widoku DBA_SOURCE, ALL_SOURCE lub USER_SOURCE. Wywołanie format_error_backtrace można zastąpić dwoma wywołaniami funkcji z nowego pakietu utl_call_stack z bazy Oracle Database 12c. Jedna z nich pobiera numer błędu, a druga — wiersz wystąpienia błędu. Opis tych funkcji znajdziesz w tabeli 7.3 w dalszej części rozdziału. Jednak żadna z tych funkcji nie zwraca członów ORA- ani : at, dzięki którym dane wyjściowe z funkcji format_error_ backtrace są czytelne. Aby wyświetlić wartość funkcji backtrace_line, należy wywołać ją w następujący sposób: utl_call_stack.backtrace_line(utl_call_stack.backtrace_depth)

Numer wiersza, w którym wystąpił błąd, pozwala uzyskać poniższe wywołanie: utl_call_stack.backtrace_line(utl_call_stack.backtrace_depth)

W punkcie „Funkcje związane ze stosem wyjątków” w dalszej części rozdziału dokładniej opisano tego rodzaju pomocnicze narzędzia do obsługi wyjątków. Pakiet utl_call_stack jest też opisany w dodatku C. W tym podpunkcie opisano podstawy zarządzania wyjątkami czasu wykonania. Warto zauważyć, że błędy zgłaszane w sekcji wykonawczej są — jeśli to możliwe — obsługiwane lokalnie. Jeżeli lokalna sekcja obsługi wyjątków nie zarządza danym błędem, jest on przekazywany do bloku zewnętrznego lub środowiska SQL*Plus. Wyjątki zgłoszone w sekcji obsługi wyjątków są przekazywane przez język PL/SQL do bloku zewnętrznego lub środowiska SQL*Plus.

Rozdział 7. Obsługa błędów

251

Błędy w sekcji deklaracji Jak pokazano we wcześniejszej części rozdziału, próba przypisania dwuznakowego literału do jednoznakowej zmiennej powoduje zgłoszenie wyjątku czasu kompilacji. Wyjątki czasu wykonania są zgłaszane w wyniku wywołania programu za pomocą nieprawidłowej wartości. Błędów czasu wykonania z sekcji deklaracji nie można przechwycić w lokalnym bloku obsługi wyjątków. Wynika to z tego, że sekcja deklaracji przekazuje błąd z powrotem do zasięgu programu wywołującego. Następny przykład to zmodyfikowana wersja kodu zaprezentowanego we wcześniejszej części rozdziału. Ten program przypisuje wartość zmiennej podstawianej do zmiennej lokalnej (korzystanie ze zmiennych podstawianych jest opisane w punkcie „Przekazywanie parametrów w trybie interaktywnym” w dodatku A). Ta operacja nie prowadzi do zgłoszenia błędu czasu kompilacji, ponieważ zmienne podstawiane do momentu uruchomienia programu nie mają określonej wielkości fizycznej. SQL> 2 3 4 5 6 7 8 9

DECLARE lv_a CHAR := '&input'; BEGIN dbms_output.put_line('['||lv_a||']'); EXCEPTION WHEN OTHERS THEN dbms_output.put_line('['||SQLERRM||']' END; /

Wiersz 2. przypisuje zmienną podstawianą do zmiennej lokalnej. Przypisanie wartości 'AB' do zmiennej podstawianej prowadzi do zgłoszenia następującego wyjątku czasu wykonania: DECLARE * ERROR at line 1: ORA-06502: PL/SQL: numeric or value error: character string buffer too small ORA-06512: at line 2

Wewnętrzny blok anonimowy zgłasza nieprzechwycony wyjątek, ponieważ sekcja obsługi wyjątków z wierszy od 5. do 7. nie potrafi zarządzać wyjątkami czasu wykonania. Poniższy program pokazuje, jak przechwytywać zgłoszony wyjątek w bloku zewnętrznym: SQL> 2 3 4 5 6 7 8 9 10 11

BEGIN DECLARE lv_a CHAR := '&input'; BEGIN dbms_output.put_line('['||lv_a||']'); END; EXCEPTION WHEN OTHERS THEN dbms_output.put_line('['||SQLERRM||']'); END; /

Aby przechwycić wyjątek zgłaszany w wierszu 3., należy umieścić pierwotny program w innym. Zewnętrzny program przechwytuje nieobsłużony wyjątek, gdy zagnieżdżony program przekaże błąd do zasięgu programu wywołującego. Podobnie działa obsługa wyjątków w programach składowanych, na przykład w funkcjach i procedurach. Podczas gdy wywołania procedur trzeba opakować, funkcje tego nie wymagają. Próba bezpośredniego wywołania funkcji w języku SQL może spowodować zgłoszenie nieobsługiwanego wyjątku. W języku SQL można wywoływać funkcje składowane, jeśli zwracają wartości o macierzystym typie danych tego języka.

W poniższej funkcji składowanej występuje opisany już problem z przypisaniem dynamicznym: SQL> CREATE OR REPLACE FUNCTION runtime_error 2 (lv_input VARCHAR2) RETURN VARCHAR2 IS 3 a VARCHAR2(1) := lv_input; 4 BEGIN

252 5 6 7 8 9 10

Część I

Podstawy języka PL/SQL

RETURN NULL; EXCEPTION WHEN others THEN dbms_output.put_line('Błąd w funkcji.'); END; /

W wierszu 2. funkcja przyjmuje jeden parametr wejściowy. W wywołaniu funkcji runtime_error należy więc przekazać dowolny poprawny łańcuch znaków typu VARCHAR2 (jeśli parametr MAX_STRING_ SIZE ma wartość EXTENDED, taki łańcuch może mieć do 32 768 bajtów). Jest tak, ponieważ parametry formalne funkcji i procedur nie mają limitu fizycznej wielkości. Formalne parametry typu VARCHAR2 dziedziczą wielkość po parametrach wywołania. W tej funkcji kod przypisuje parametr formalny do jednoznakowej zmiennej lv_input. To przypisanie powoduje zgłoszenie wyjątku czasu wykonania, jeśli dane wyjściowe są dłuższe niż jeden znak. Funkcję tę można wywołać w języku SQL na liście instrukcji SELECT: SELECT runtime_error ('AB') FROM dual;

Spowoduje to następujący nieobsłużony wyjątek: SELECT runtime_error ('AB') FROM dual; * ERROR at line 1: ORA-06502: PL/SQL: numeric or value error: character string buffer too small ORA-06512: at "PLSQL.RUNTIME_ERROR", line 3

Punkt ten miał pokazać, że operacje przypisania dynamicznego należy umieszczać w sekcji wykonawczej, ponieważ PL/SQL nie przechwytuje takich błędów w lokalnej sekcji obsługi wyjątków. Zgodnie z dobrymi praktykami programowania w języku PL/SQL należy unikać umieszczania przypisań dynamicznych w sekcji deklaracji.

Powtórzenie materiału W tym podrozdziale opisano następujące zagadnienia na temat typów i zasięgu wyjątków:  Błędy czasu kompilacji to zazwyczaj literówki. Są wykrywane w czasie parsowania programów w języku PL/SQL. Do tej kategorii należą błędy z jednostkami leksykalnymi. Mogą to być błędnie zapisane słowa lub niepoprawnie zastosowane identyfikatory (na przykład nazwy zmiennych). Błędnie zapisane lub niewłaściwie użyte identyfikatory to zwykle słowa kluczowe lub słowa zarezerwowane języków SQL i PL/SQL.  Błąd czasu kompilacji może wskazywać jedną z trzech lokalizacji: pierwszy znak następnego wiersza (jeśli błąd wystąpił w ostatnim elemencie poprzedniego wiersza), znak bezpośrednio po błędzie w danym wierszu lub początek sekcji deklaracji (gdy błąd nie jest związany z parsowaniem).  Błędy czasu wykonania są zgłaszane po etapie parsowania programu i po przypisaniu literałów do zmiennych lokalnych.  Błędy czasu wykonania występują w sekcjach: deklaracji, wykonawczej i obsługi wyjątków.  Błędy czasu wykonania zgłoszone w sekcji wykonawczej są obsługiwane w lokalnej sekcji obsługi wyjątków.  Błędy czasu wykonania zgłoszone w sekcji deklaracji lub wyjątków nie mogą zostać obsłużone w lokalnej sekcji wyjątków.

Wbudowane funkcje do zarządzania wyjątkami Oracle udostępnia zestaw wyjątków wbudowanych w pakiecie sys.standard. Są to narzędzia pomocne w czasie debugowania programów w języku Oracle PL/SQL. Większości błędów odpowiadają ujemne numery. Aby sprawdzić kod poszczególnych błędów, należy użyć funkcji wbudowanej SQLCODE. Funkcja wbudowana SQLERRM zwraca kody i komunikaty błędów. We wcześniejszym punkcie „Wbudowane

Rozdział 7. Obsługa błędów

253

funkcje obsługi wyjątków w bazach Oracle” wyjaśniono, jak działają obie te funkcje. Więcej informacji o tych funkcjach obsługi błędów znajdziesz w dodatku C. Listę wyjątków wbudowanych zawiera tabela 7.2. Te wyjątki są bardzo przydatne przy tworzeniu sekcji obsługi wyjątków. Należy ich używać, jeśli zaspokajają potrzeby związane z programem. W przeciwnym razie trzeba utworzyć własne wyjątki.

Wyjątki zdefiniowane przez użytkownika Wyjątki zdefiniowane przez użytkownika można deklarować na trzy sposoby. W tym punkcie opisano wszystkie te sposoby, a także implementowanie i zgłaszanie takich wyjątków.  Można zadeklarować zmienną typu EXCEPTION w sekcji deklaracji. Wyjątki tego typu można zgłaszać za pomocą instrukcji RAISE.  Można zadeklarować zmienną typu EXCEPTION i w sekcji deklaracji powiązać ją z określonym kodem błędu Oracle za pomocą instrukcji PRAGMA. Błędy tego rodzaju są zgłaszane, gdy kod generuje standardowe wyjątki Oracle.  Do tworzenia wyjątków dynamicznych służy funkcja raise_application_error. Ta technika nie wymaga deklarowania zmiennej typu EXCEPTION. Wspomniana funkcja umożliwia powiązanie kodu błędu zdefiniowanego przez użytkownika z komunikatem. Funkcję tę można wywoływać w sekcjach wykonawczej i obsługi wyjątków. Wywołanie tej funkcji powoduje zgłoszenie wyjątku dynamicznego. Przy zgłaszaniu wyjątków dynamicznych należy użyć liczby z przedziału od –20999 do –20000. W pakiecie Oracle E-Business Suite i innych aplikacjach niektóre liczby z podanego przedziału odpowiadają wyjątkom wbudowanym. Należy o tym pamiętać, aby uniknąć konfliktów.

W punkcie „Deklarowanie wyjątków zdefiniowanych przez użytkownika” pokazano, jak pracować z dwoma pierwszymi rodzajami wyjątków zdefiniowanych przez użytkownika. Dalszy punkt „Dynamiczne wyjątki zdefiniowane przez użytkownika” dotyczy stosowania wyjątków trzeciego rodzaju.

Deklarowanie wyjątków zdefiniowanych przez użytkownika W tym punkcie opisano, jak deklarować wyjątki i jak je zgłaszać. Dowiesz się też, jak stosować instrukcje (dyrektywy) prekompilatora pozwalające powiązać wyjątek z kodem błędu. Poznasz też dwa pierwsze rodzaje wyjątków zdefiniowanych przez użytkownika. W języku PL/SQL wyjątki można deklarować jak wszelkie inne zmienne. Po zadeklarowaniu wyjątku można go zgłosić, nie ma jednak możliwości przechwycenia go w sekcji obsługi wyjątków. Cel tworzenia wyjątku definiowanego przez użytkownika ma wpływ na sposób jego deklaracji. Poniższy program zawiera deklarację wyjątku i zgłasza go: SQL> 2 3 4 5 6 7 8 9 10 11 12 13

DECLARE e EXCEPTION; BEGIN RAISE e; dbms_output.put_line('Nie można tu dotrzeć.'); EXCEPTION WHEN OTHERS THEN /* Przechwytuje wszystkie wyjątki. */ /* Sprawdza, czy zgłoszono wyjątek zdefiniowany przez użytkownika. */ IF SQLCODE = 1 THEN dbms_output.put_line('Ten wyjątek to: ['||SQLERRM||'].'); END IF; END; /

Wiersz 2. deklaruje lokalną zmienną typu EXCEPTION. Wiersz 4. zgłasza wyjątek zdefiniowany przez użytkownika. Ponieważ nie ma tu instrukcji PRAGMA, zgłaszany jest kod błędu 1. W sekcji obsługi wyjątków używany jest ogólny blok do przechwytywania wszystkich wyjątków (wiersz 7.). Wiersz 9. pozwala wykrywać kod wyjątków zdefiniowanych przez użytkownika.

254

Część I

Podstawy języka PL/SQL

Tabela 7.2. Wyjątki wbudowane z pakietu STANDARD Wyjątek

Błąd

Przyczyna

ACCESS_INTO_NULL

ORA-06530

Próba uzyskania dostępu do niezainicjowanego obiektu.

CASE_NOT_FOUND

ORA-06592

Instrukcja CASE nie ma klauzuli ELSE, a warunek określony w czasie wykonywania programu nie pasuje do żadnej instrukcji.

COLLECTION_IS_NULL

ORA-06531

Próba dostępu do niezainicjowanej tabeli zagnieżdżonej lub tablicy VARRAY.

CURSOR_ALREADY_OPEN

ORA-06511

Próba otwarcia już otwartego kursora.

DUP_VAL_ON_INDEX

ORA-00001

Próba wstawienia powtarzającej się wartości do kolumny zawierającej niepowtarzalne indeksy.

INVALID_CURSOR

ORA-01001

Próba wykonania na kursorze niedozwolonej operacji, na przykład zamknięcia zamkniętego już kursora.

INVALID_NUMBER

ORA-01722

Próba przypisania danych innych niż liczba do zmiennej liczbowej lub zwrócenie niedodatniej liczby przez klauzulę LIMIT przy pobieraniu masowym.

LOGIN_DENIED

ORA-01017

Próba zalogowania się przy użyciu nieprawidłowej nazwy użytkownika lub błędnego hasła.

NO_DATA_FOUND

ORA-01403

Zwrócenie wartości null przez instrukcję SELECT-INTO, próba dostępu do usuniętego elementu tabeli zagnieżdżonej, próba dostępu do niezainicjowanego elementu tablicy asocjacyjnej.

NO_DATA_NEEDED

ORA-06548

Poinformowanie przez jednostkę wywołującą funkcji potokowej o tym, że nie potrzebuje dalszych wierszy.

NOT_LOGGED_ON

ORA-01012

Wywołanie przez program niepołączonej bazy danych. Zwykle przyczyną tego błędu jest zerwanie połączenia z sesją.

PROGRAM_ERROR

ORA-06501

Błąd ten pojawia się zdecydowanie za często, a jest wynikiem nieprzechwycenia błędu przez Oracle. Dzieje się tak z wieloma funkcjami obiektowymi tej bazy danych.

ROWTYPE_MISMATCH

ORA-06504

Niezgodność struktury kursora ze zmienną kursorową w kodzie PL/SQL lub argumentu kursora z parametrem formalnym.

SELF_IS_NULL

ORA-30625

Próba wywołania niestatycznej metody składowej typu obiektowego, którego egzemplarz nie został zainicjowany.

STORAGE_ERROR

ORA-06500

Wyczerpanie pamięci lub błąd obszaru SGA.

SUBSCRIPT_BEYOND_COUNT

ORA-06533

Podany indeks tabeli zagnieżdżonej lub tablicy VARRAY wykracza poza przydzieloną pamięć.

SUBSCRIPT_OUTSIDE_LIMIT

ORA-06532

Użycie niedozwolonego indeksu (niedodatniej liczby całkowitej) przy próbie dostępu do tabeli zagnieżdżonej lub tablicy VARRAY.

SYS_INVALID_ROWID

ORA-01410

Próba przekształcenia łańcucha znaków na nieprawidłową wartość typu ROWID.

TIMEOUT_ON_RESOURCE

ORA-00051

Baza danych nie może zabezpieczyć blokady zasobu.

TOO_MANY_ROWS

ORA-01422

Zwrócenie więcej niż jednego wiersza przez zapytanie SELECT INTO.

USERENV_COMMITSCN_ERROR

ORA-01725

Funkcji userenv('COMMITSCN') można użyć tylko jako wyrażenia najwyższego poziomu w klauzuli VALUES instrukcji INSERT lub jako prawego operandu w klauzuli SET instrukcji UPDATE. Jest zgłaszany, gdy numeru SCN nie można zapisać w pliku bazy danych.

VALUE_ERROR

ORA-06502

Próba przypisania jednej zmiennej do drugiej zmiennej o zbyt małej pojemności.

ZERO_DIVIDE

ORA-01476

Próba podzielenia liczby przez zero.

Rozdział 7. Obsługa błędów

255

Przedstawiony kod zgłasza wyjątek i wyświetla następujący komunikat: Ten wyjątek to: [User-Defined Exception].

Dwuetapowy proces deklaracji pozwala zadeklarować wyjątek i powiązać go z numerem. Pierwszy krok polega na zadeklarowaniu zmiennej typu EXCEPTION. Drugi na zadeklarowaniu dyrektywy PRAGMA kompilatora. Służy ona do nakazywania kompilatorowi wykonywania niestandardowych operacji. Język PL/SQL udostępnia szereg dyrektyw PRAGMA. Poniższy dyrektywa służy do wiązania wyjątku z numerem błędu. PRAGMA EXCEPTION_INIT(lokalnie_zadeklarowany_wyjątek, kod_błędu);

Należy unikać wiązania wyjątków zdefiniowanych przez użytkownika z numerami błędów, którym odpowiadają wyjątki wbudowane. Ich listę zawiera tabela 7.2.

Poniższy przykładowy program definiuje zmienną typu EXCEPTION i wiąże wyjątek z numerem błędu: SQL> 2 3 4 5 6 7 8 9 10 11

DECLARE lv_a VARCHAR2(20); invalid_userenv_parameter EXCEPTION; PRAGMA EXCEPTION_INIT(invalid_userenv_parameter,-2003); BEGIN lv_a := SYS_CONTEXT('USERENV','PROXY_PUSHER'); EXCEPTION WHEN invalid_userenv_parameter THEN dbms_output.put_line(SQLERRM); END; /

Wiersz 3. deklaruje zmienną lokalną EXCEPTION. Wiersz 4. zawiera dyrektywę kompilatora wiążącą nazwę wyjątku z numerem błędu. Wiersz 6. zgłasza kod rzeczywistego błędu, ORA-02003, ponieważ proxy_pusher nie jest poprawnym parametrem przestrzeni nazw USERENV. Wiersz 8. to wyspecjalizowany blok obsługi wyjątków, przechwytujący tylko nieprawidłowe wywołania funkcji sys_context. Nazwa INVALID_USERENV_PARAMETER odpowiada definicji błędu w ciele pakietu sys.standard. Przedstawiony kod spowoduje wyświetlenie standardowego komunikatu o błędzie Oracle: ORA-02003: invalid USERENV parameter

Poprzedni przykład dotyczy tylko wbudowanych wyjątków Oracle. Zobaczmy, co się stanie po powiązaniu kodu błędu zdefiniowanego przez użytkownika z lokalnym wyjątkiem. Warto przypomnieć, że kody błędów zdefiniowanych przez użytkownika powinny należeć do przedziału od -20999 do -20001. Poniższy kod wiąże lokalny wyjątek z kodem błędu zdefiniowanego przez użytkownika: SQL> 2 3 4 5 6 7 8 9 10

DECLARE e EXCEPTION; PRAGMA EXCEPTION_INIT(e,-20001); BEGIN RAISE e; EXCEPTION WHEN e THEN dbms_output.put_line(SQLERRM); END; /

Wiersz 3. wiąże lokalny wyjątek z poprawnym kodem błędu zdefiniowanego przez użytkownika. Wiersz 5. zgłasza ten wyjątek. Wiersz 7. przechwytuje lokalny wyjątek i wyświetla: ORA-20001:

Funkcja SQLERRM zwraca tu tylko kod błędu zdefiniowanego przez użytkownika, ponieważ z tym kodem nie jest powiązany żaden standardowy komunikat. Z następnego punktu dowiesz się, jak rozwiązać ten problem za pomocą funkcji RAISE_APPLICATION_ERROR.

256

Część I

Podstawy języka PL/SQL

Dynamiczne wyjątki zdefiniowane przez użytkownika Z tego punktu dowiesz się, jak zadeklarować wyjątek, przypisać mu numer i podać zdefiniowany przez użytkownika komunikat o błędzie. W ten sposób tworzone są wyjątki trzeciego rodzaju — dynamiczne wyjątki zdefiniowane przez użytkownika. Omówiono tu także stos błędów, czyli kolekcję kaskadowo zgłaszanych wyjątków. Funkcja RAISE_APPLICATION_ERROR umożliwia zgłoszenie wyjątku i określenie niestandardowego komunikatu o błędzie. Oto prototyp tej dynamicznej funkcji: RAISE_APPLICATION_ERROR(numer_błędu, komunikat_o_błędzie [, zapisuj_błędy])

Pierwszy parametr formalny przyjmuje numer błędu z przedziału od –20999 do –20000. Podanie innej wartości powoduje zgłoszenie błędu ORA-21000. Drugi parametr formalny zawiera komunikat o błędzie. Można przypisać do niego dowolny łańcuch, jednak powinien on być krótszy niż 68 znaków, ponieważ funkcja SQLERRM zwraca dziewięcioznakowy kod błędu, średnik i odstęp przed komunikatem o błędzie. Parametr ostatni jest opcjonalny, a jego wartość domyślna to FALSE. Jeśli programista poda opcjonalną wartość TRUE, wystąpienie danego błędu zostanie odnotowane na stosie błędów. Dynamiczną funkcję RAISE_APPLICATION_ERROR można zastosować bez deklarowania wyjątku czy używania dyrektywy kompilatora. Poniższy kod zgłasza dynamiczny wyjątek bez tworzenia lokalnego wyjątku ani stosowania dyrektywy kompilatora. SQL> 2 3 4 5 6 7

BEGIN RAISE_APPLICATION_ERROR(-20001,'Niezbyt oryginalny komunikat.'); EXCEPTION WHEN others THEN dbms_output.put_line(SQLERRM); END; /

Wiersz 2. zgłasza dynamiczny wyjątek. Wiersz 4. przechwytuje wszystkie wyjątki, ponieważ zastosowano ogólne słowo kluczowe others. Ten program wyświetli następujące informacje: ORA-20001: Niezbyt oryginalny komunikat.

Następny program łączy deklarowanie zmiennej typu EXCEPTION ze stosowaniem dyrektywy kompilatora i dynamicznym podaniem komunikatu. Kod ten pokazuje, jak te mechanizmy współdziałają w programie. Po co deklarować zmienną typu EXCEPTION i stosować dyrektywę kompilatora, skoro dynamiczne wyjątki ich nie wymagają? Ponieważ po połączeniu tych elementów z dynamicznymi wyjątkami można utworzyć niestandardowy blok obsługi wyjątków. Oto anonimowy blok z opisanymi mechanizmami: SQL> 2 3 4 5 6 7 8 9 10

DECLARE e EXCEPTION; PRAGMA EXCEPTION_INIT(e,-20001); BEGIN RAISE_APPLICATION_ERROR(-20001,'Inny mało oryginalny komunikat.'); EXCEPTION WHEN e THEN dbms_output.put_line(SQLERRM); END; /

Wiersz 2. deklaruje zmienną typu EXCEPTION. Wiersz 3. zawiera dyrektywę kompilatora z kodem błędu zdefiniowanym przez użytkownika. Wiersz 5. zgłasza wyjątek za pomocą tego kodu. Wiersz 7. przechwytuje dynamicznie zgłoszony błąd, ponieważ kod błędu zastosowany w wierszach 3. i 5. łączy zmienną typu EXCEPTION z dynamicznym wyjątkiem. Ten wyspecjalizowany blok obsługi błędów wyświetli poniższy komunikat o dynamicznie zgłoszonym błędzie: ORA-20001: Inny mało oryginalny komunikat.

W odróżnieniu od plików komunikatów dotyczących standardowych błędów Oracle ten komunikat jest dynamicznie określany w jednostce programu PL/SQL. Funkcja wbudowana SQLERRM nie

Rozdział 7. Obsługa błędów

257

wyszukuje komunikatu, ale po prostu zastępuje go literałem znakowym podanym w funkcji RAISE_ APPLICATION_ERROR.

Oracle przechowuje komunikaty o błędach dla poszczególnych języków w kilku swych katalogach głównych. Wszystkie pliki z komunikatami o błędach mają rozszerzenie .msg.

Na podstawie wcześniejszych informacji dodajmy teraz nowy kod i zmieńmy fragmenty poprzedniego przykładu, aby zobaczyć, jak za pomocą funkcji RAISE_APPLICATION_ERROR wygenerować stos błędu. Poniższy program zgłasza i przechwytuje wyjątek value_error: SQL> 2 3 4 5 6 7 8 9 10 11 12 13

DECLARE lv_a VARCHAR2(1); lv_b VARCHAR2(2) := 'AB'; BEGIN lv_a := lv_b; dbms_output.put_line('Nigdy nie dochodzi do tego miejsca.'); EXCEPTION WHEN value_error THEN RAISE_APPLICATION_ERROR(-20001,'Specyficzny komunikat.'); WHEN others THEN dbms_output.put_line(SQLERRM); END; /

Wiersz 5. zgłasza wyjątek value_error w wyniku próby przypisania dwóch znaków do jednoznakowej zmiennej. Blok obsługi wyjątków value_error zgłasza w wierszu 9. wyjątek dynamiczny, co powoduje ukrycie pierwotnego wyjątku: DECLARE * ERROR at line 1: ORA-20001: Specyficzny komunikat. ORA-06512: at line 9

Jest to domyślne działanie funkcji RAISE_APPLICATION_ERROR. Ostatnim elementem w komunikacie wyjątku jest błąd ORA-06512. Informuje on o numerze wiersza, w którym został zgłoszony wyjątek. Jest to wiersz 9., z wywołaniem funkcji zgłaszającej błąd. Aby zmienić domyślne działanie funkcji RAISE_APPLICATION_ERROR, należy zastąpić domyślną wartość trzeciego, opcjonalnego parametru. Oto zmodyfikowana wersja wiersza 11.: 11

RAISE_APPLICATION_ERROR(-20001,'Specyficzny komunikat.',TRUE);

Ta wersja wyświetla następujący stos błędów: DECLARE * ERROR at line 1: ORA-20001: Specyficzny komunikat. ORA-06512: at line 9 ORA-06502: PL/SQL: numeric or value error: character string buffer too small

W tym podejściu utracony zostaje numer wiersza, w którym wystąpił pierwszy błąd. Programista zyskuje natomiast listę wszystkich zgłoszonych wyjątków. W kodzie rzeczywistej aplikacji funkcja RAISE_APPLICATION_ERROR powinna generować informacje o błędzie zrozumiałe dla pracowników pomocy technicznej, którzy mogą następnie przystąpić do rozwiązywania problemu. W tym podrozdziale przedstawiono deklarowanie i używanie wyjątków. Wiesz już, jak powiązać wbudowane błędy Oracle i komunikaty o błędzie z wyjątkami zdefiniowanymi przez użytkownika, a także potrafisz dynamicznie podawać własne komunikaty o błędzie.

258

Część I

Podstawy języka PL/SQL

Powtórzenie materiału W tym podrozdziale opisano następujące zagadnienia dotyczące wyjątków zdefiniowanych przez użytkownika:  Wyjątki zdefiniowane przez użytkownika można zadeklarować w dowolnej sekcji deklaracji.  Można zadeklarować instrukcję prekompilatora (dyrektywę kompilatora) PRAGMA, aby powiązać kod błędu z wyjątkiem zdefiniowanym przez użytkownika.  Instrukcja RAISE umożliwia zgłaszanie wyjątków.  Instrukcja RAISE w połączeniu z kodem błędu Oracle sprawia, że funkcja SQLERRM zwraca kod błędu i komunikat.  Instrukcja RAISE w połączeniu z kodem błędu zdefiniowanym przez użytkownika sprawia, że funkcja SQLERRM zwraca tylko kod błędu. Dzieje się tak, ponieważ nie istnieje zewnętrzny plik komunikatów powiązany z takim kodem błędu.  Funkcja RAISE_APPLICATION_ERROR umożliwia określenie kodu i komunikatu dynamicznego błędu. Można przy tym pominąć lub utworzyć stos wyjątków.

Funkcje do zarządzania stosem błędów Stos wyjątków to struktura danych typu FILO (ang. First-In, Last-Out). Pierwszy zgłoszony błąd jest wyświetlany jako ostatni. Przypomina to stos wydrukowanych kartek. Pierwsza wydrukowana kartka znajduje się na spodzie stosu, a ostatnia — na jego wierzchu. PL/SQL w momencie wystąpienia błędu zgłasza wyjątek w sekcji obsługi wyjątków. Problem powoduje uruchomienie bloków obsługi wyjątków w lokalnej sekcji obsługi wyjątków. Jednostki programu przekazują wyjątki dalej, jeśli nie potrafią ich obsłużyć lokalnie. Takie przekazywanie może mieć miejsce raz, dwa razy lub kilkakrotnie. Proces ten trwa do momentu zwrócenia sterowania do najbardziej zewnętrznego bloku języka PL/SQL. W wyniku przekazywania wyjątków powstaje ich stos. Należy go przeanalizować, aby znaleźć pierwotną przyczynę problemów. Zawsze jest ona związana z pierwszym zgłoszonym błędem. Rozróżnia się dwie techniki zarządzania błędami w kodzie PL/SQL. To, którą z nich należy wybrać, zależy od wymagań związanych z kontrolą transakcji w aplikacji. Jeśli pojawi się błąd krytyczny dla logiki biznesowej aplikacji, trzeba zgłosić wyjątek. Powinno to spowodować zatrzymanie procesu biznesowego i anulowanie transakcji do stanu, w którym dane są bezpieczne i spójne. Jeśli błąd nie jest krytyczny dla logiki biznesowej, można zarejestrować go za pomocą niezależnego bloku kodu. Najlepiej wykorzystać do tego wyzwalacze bazy danych, opisane w rozdziale 12. W Oracle Database 12c wprowadzono pakiet utl_call_stack, zawierający funkcje i procedury wymienione w tabeli 7.3. Od wersji Oracle Database 10g dostępna jest funkcja format_error_backtrace z pakietu dbms_utility. Przed rozpoczęciem korzystania z pakietu utl_call_stack należy utworzyć kilka procedur składowanych. Są one bardzo krótkie i pozwalają zilustrować trzypoziomowy stos wywołań: SQL> 2 3 4 5 6 7 8

CREATE OR REPLACE PROCEDURE pear IS /* Deklaracja dwóch zmiennych. */ lv_one_character VARCHAR2(1); lv_two_character VARCHAR2(2) := 'AB'; BEGIN lv_one_character := lv_two_character; END pear; /

Pozostańmy przy prostym przykładzie przypisywania dwuznakowego łańcucha do jednoznakowej zmiennej. Procedura pear deklaruje w wierszach 3. i 4. zmienne lv_one_character i lv_two_character. Próba przypisania wartości dwuznakowej do zmiennej jednoznakowej w wierszu 6. powoduje zgłoszenie błędu w procedurze pear. Ponieważ ta procedura nie zawiera sekcji obsługi wyjątków, błąd jest przekazywany do jednostki wywołującej. Procedura orange jest jeszcze prostsza, ponieważ nie obejmuje deklaracji zmiennych:

Rozdział 7. Obsługa błędów

259

Tabela 7.3. Funkcje z pakietu utl_call_stack Funkcja pakietu

Opis

backtrace_depth

Zwraca liczbę elementów w ścieżce wstecznej (ang. backtrace). Może zwrócić wartość 1 albo większą typu PLS_INTEGER lub, gdy nie zgłoszono żadnego wyjątku, wartość 0.

backtrace_line

Zwraca numer wiersza jednostki z określonego poziomu ścieżki wstecznej. Przyjmuje jeden parametr wejściowy — wynik zwrócony przez funkcję backtrace_depth. Zwraca numer wiersza, w którym wystąpił błąd na określonym poziomie wykonania.

backtrace_unit

Zwraca nazwę jednostki z określonego poziomu ścieżki wstecznej. Przyjmuje jeden parametr — wynik zwrócony przez funkcję backtrace_depth. Zwraca nazwę modułu lub, dla bloków anonimowych, wartość null.

current_edition

Zwraca nazwę obecnej wersji jednostki podprogramu z określonego poziomu dynamicznego. Przyjmuje jeden parametr wejściowy — wynik zwrócony przez funkcję backtrace_depth. Zwraca nazwę wersji programu, jeśli w bazie stosowany jest mechanizm redefinicji opartych na wersjach.

concatenate_subprogram

Zwraca nazwę podprogramu połączoną z nazwą jednostki nadrzędnej. Przyjmuje kwalifikowaną nazwę jako parametr wejściowy i zwraca pełną kwalifikowaną nazwę podprogramu.

dynamic_depth

Zwraca liczbę podprogramów na stosie wywołań.

error_depth

Zwraca liczbę błędów na stosie błędów.

error_msg

Zwraca komunikat o błędzie z określonego poziomu stosu błędów.

error_number

Zwraca numer błędu z określonego poziomu stosu błędów. Przyjmuje jeden parametr wejściowy — wynik zwrócony przez funkcję backtrace_depth.

lexical_depth

Zwraca poziom zagnieżdżenia leksykalnego podprogramu z określonego poziomu dynamicznego. Przyjmuje jeden parametr wejściowy — wynik zwrócony przez funkcję backtrace_depth.

owner

Zwraca nazwę właściciela jednostki podprogramu z określonego poziomu dynamicznego. Przyjmuje jeden parametr wejściowy — wynik zwrócony przez funkcję backtrace_depth.

unit_line

Zwraca numer wiersza jednostki podprogramu z określonego poziomu dynamicznego. Przyjmuje jeden parametr wejściowy — wynik zwrócony przez funkcję backtrace_depth.

subprogram

Zwraca nazwę podprogramu (wraz z nazwą jednostki) z określonego poziomu dynamicznego. Przyjmuje jeden parametr wejściowy — wynik zwrócony przez funkcję backtrace_depth.

SQL> 2 3 4 5

CREATE OR REPLACE PROCEDURE orange IS BEGIN pear(); END orange; /

Wywołanie procedury pear w wierszu 3. powoduje zgłoszenie przez nią wyjątku. Nie można go obsłużyć, ponieważ procedura orange też nie ma sekcji obsługi wyjątków. Procedura apple jest podobna do procedury orange i wywołuje tę ostatnią. Procedura apple też nie ma sekcji obsługi wyjątków, co oznacza, że przekazuje wyjątek z procedury orange do zasięgu wywołania: SQL> 2 3 4 5

CREATE OR REPLACE PROCEDURE apple IS BEGIN orange(); END apple; /

260

Część I

Podstawy języka PL/SQL

W procedurze apple nie ma nic zaskakującego. Procedura apple wywołuje procedurę orange i przekazuje przechwycony wyjątek do zasięgu wywołania, ponieważ nie obejmuje sekcji obsługi wyjątków. Zasięgiem wywołania jest poniższy blok anonimowy: SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

BEGIN apple; EXCEPTION WHEN others THEN FOR i IN REVERSE 1..utl_call_stack.backtrace_depth LOOP /* Sprawdzanie błędów w bloku anonimowym. */ IF utl_call_stack.backtrace_unit(i) IS NULL THEN /* utl_call_stack nie wyświetla żadnych błędów; należy ręcznie ustawić komunikat. */ dbms_output.put_line( 'ORA-06512: w bloku anonimowym, wiersz '|| utl_call_stack.backtrace_line(i)); ELSE /* utl_call_stack nie wyświetla żadnych błędów; należy ręcznie ustawić komunikat. */ dbms_output.put_line( 'ORA-06512: w '||utl_call_stack.backtrace_unit(i)|| ', wiersz '||utl_call_stack.backtrace_line(i)); END IF; /* Ścieżka wsteczna i poziom błędu są niepowiązane. Poziom wywołania może być (i zwykle jest) wyższy niż poziom błędu. */ IF i = utl_call_stack.error_depth THEN dbms_output.put_line( 'ORA-'||LPAD(utl_call_stack.error_number(i),5,0) ||' '||utl_call_stack.error_msg(i)); END IF; END LOOP; END; /

Wiersz 2. wywołuje procedurę apple, która ostatecznie zwraca wyjątek. Przedstawiony blok anonimowy obejmuje sekcję obsługi wyjątków. Zarządza ona wyjątkami za pomocą pętli z malejącymi indeksami (wiersze od 5. do 26.). W wierszu 7. pętla sprawdza, czy funkcja backtrace_unit zwraca wartość null. Jest tak, gdy program wywołujący ma postać bloku anonimowego. Dla funkcji i procedur składowanych zwracana jest pełna nazwa. Wiersze 10. i 15. dodają standardowy kod błędu ORA-06512 przed jednostką programu, która zgłosiła wyjątek (ponieważ ten kod nie jest rejestrowany przez pakiet utl_call_stack). Na stosie błędów zarządzanym przez pakiet utl_call_stack znajduje się tylko pierwotny zgłoszony błąd. Aby się o tym przekonać, należy sprawdzić wyjątek dla określonego poziomu ścieżki wstecznej, a nie według poziomu błędów. Na przykład w tym programie poziom ścieżki wstecznej to 4, a poziom błędów — 1. To dlatego potrzebny jest wiersz 21. Wiersz 21. sprawdza, czy poziom błędów jest równy poziomowi ścieżki wstecznej. Jeśli usuniesz blok IF z wierszy 21. i 25., program nie zadziała, ponieważ w wierszu 23. zgłosi wywołanie dla nieistniejącego poziomu błędów: 23

'ORA-'||LPAD(utl_call_stack.error_number(i),5,0)

To spowoduje wystąpienie następującego wyjątku: BEGIN * ERROR at line 1: ORA-64610: bad depth indicator ORA-06512: at "SYS.UTL_CALL_STACK", line 130 ORA-06512: at line 21 ORA-06502: PL/SQL: numeric or value error: character string buffer too small

Zapamiętaj, że aby uniknąć takich problemów, zawsze należy pamiętać o różnicy między poziomem ścieżki wstecznej a poziomem błędów. Zgodnie z ogólną regułą poziom błędów jest zawsze niższy niż poziom ścieżki wstecznej. Jest to nowa funkcja, dlatego w przyszłości może zostać zmodyfikowana.

Rozdział 7. Obsługa błędów

261

Wróćmy teraz do analizy omawianego programu. Wiersz 22. uzupełnia numer błędu zerami, aby zwrócić pięciocyfrowy numer (funkcja zwraca ten numer w postaci liczby całkowitej). Wiersz 22. dodaje też człon ORA- przed uzupełnionym zerami numerem błędu oraz dołącza komunikat o błędzie po kodzie błędu. Przedstawiony program wyświetla następujące dane: ORA-06512: w VIDEO.PEAR, wiersz 6 ORA-06512: w VIDEO.ORANGE, wiersz 3 ORA-06512: w VIDEO.APPLE, wiersz 3 ORA-06512: w bloku anonimowym, wiersz 2 ORA-06502 PL/SQL: numeric or value error: character string buffer too small

Ten sam stos błędu można wyświetlić za pomocą funkcji format_error_backtrace z pakietu dbms_ utility. Wtedy wiersze od 22. do 24. należy zastąpić poniższymi wierszami 22. i 23., aby zmodyfikować cały kod związany ze śladem stosu: 22 23

dbms_output.put_line( dbms_utility.format_error_backtrace);

Jednak ślad stosu z funkcji format_error_backtrace nie jest wyświetlany poprawnie, gdy jest ona wywoływana w procedurze put_line. Wynika to z dodatkowych znaków podziału wiersza. Można uzyskać przejrzysty ślad stosu, ale wymaga to nieco pracy. W tym celu wprowadź w programie następujące zmiany: SQL> DECLARE 2 lv_length NUMBER; 3 lv_counter NUMBER := 0; 4 lv_begin NUMBER := 1; 5 lv_end NUMBER; 6 lv_index NUMBER := 0; 7 lv_trace VARCHAR2(2000); 8 BEGIN 9 apple; 10 EXCEPTION 11 WHEN others THEN 12 FOR i IN REVERSE 1..utl_call_stack.backtrace_depth LOOP 13 /* Poziomy ścieżki wstecznej i stosu błędów nie są od siebie zależne. Poziom wywołań 14 może być (i zwykle jest) wyższy od poziomu stosu błędów. */ 15 IF i = utl_call_stack.error_depth THEN 16 /* Pobieranie śladu stosu. */ 17 lv_trace := dbms_utility.format_error_backtrace; 18 19 /* Zliczanie symboli nowego wiersza (są to liczby 10 w ASCII). */ 20 lv_length := REGEXP_COUNT(lv_trace,CHR(10),1); 21 22 /* Wczytywanie stosu i usuwanie symboli nowego wiersza. */ 23 WHILE (lv_counter < lv_length) LOOP 24 /* Zwiększanie wartości licznika na początku pętli. */ 25 lv_counter := lv_counter + 1; 26 27 /* Pobieranie następnego symbolu nowego wiersza. */ 28 lv_end := REGEXP_INSTR(lv_trace,CHR(10),lv_begin,1); 29 30 /* Pobieranie pierwszego podłańcucha ze śladu stosu. */ 31 dbms_output.put_line(SUBSTR(lv_trace,lv_begin,lv_end - lv_begin)); 32 33 /* Ustawianie końca podłańcucha jako początku. */ 34 lv_begin := lv_end + 1; 35 END LOOP; 36 END IF; 37 END LOOP; 38 39 /* Wyświetlanie pierwotnego komunikatu o błędzie. */ dbms_output.put_line( 40 41 'ORA-'||LPAD(utl_call_stack.error_number(i),5,0)

262 42 43 44 45 46

Część I

END; /

Podstawy języka PL/SQL

||': '||utl_call_stack.error_msg(i)); END IF; END LOOP;

Wiersz 15. zapewnia, że przetwarzanie rozpocznie się od pierwszego elementu ze stosu błędów. Wiersz 28. zapisuje indeks symbolu nowego wiersza w łańcuchu znaków. Wiersz 31. wyświetla tylko podłańcuch ze śladu stosu. Wiersze od 40. do 42. wyświetlają pierwotny błąd. Ten kod wyświetla następujący stos błędów: ORA-06512: ORA-06512: ORA-06512: ORA-06512: ORA-06502:

at "VIDEO.PEAR", line 6 at "VIDEO.ORANGE", line 3 at "VIDEO.APPLE", line 3 at line 9 PL/SQL: numeric or value error: character string buffer too small

Te informacje są przydatne, jeśli chcesz wyświetlić ślad stosu w HTML-u. Można wprowadzić drobną zmianę, aby zastępować symbole nowego wiersza znacznikiem
HTML-a (i dodać w ten sposób znaczniki nowego wiersza dostosowane do stron WWW). Aby wprowadzić tę zmianę zgodnie z najlepszymi praktykami, należy dodać do przedstawionego wcześniej programu kilka wierszy. Poniżej zaprezentowano najważniejsze modyfikacje: SQL> DECLARE ... 8 lv_break VARCHAR2(6) := '
'; 9 BEGIN 10 apple; 11 EXCEPTION ... 31 /* Zastępowanie i pobieranie następnego podłańcucha ze śladu stosu. */ 32 lv_trace := REGEXP_REPLACE(lv_trace,CHR(10),lv_break,lv_end,1); 33 lv_end := lv_end + LENGTH(lv_break); 34 dbms_output.put_line( 35 SUBSTR(lv_trace,lv_begin,lv_end - lv_begin)); ... 42 END; 43 /

Wiersz 8. dodaje nową zmienną lokalną reprezentującą znacznik
HTML-a. Wiersz 32. zastępuje symbol nowego wiersza HTML-owym znacznikiem nowego wiersza. Wiersz 33. dodaje długość znacznika, aby ponownie ustawić punkt końcowy bieżącego podłańcucha i punkt początkowy następnego. Zmodyfikowany program wyświetla następujące dane: ORA-06512: ORA-06512: ORA-06512: ORA-06512: ORA-06502:

at "VIDEO.PEAR", line 6
at "VIDEO.ORANGE", line 3
at "VIDEO.APPLE", line 3
at line 10
PL/SQL: numeric or value error: character string buffer too small


Z tego podrozdziału dowiedziałeś się, jak używać obu dostępnych narzędzi związanych ze śladem stosu. Zobaczyłeś też, jak łączyć te narzędzia w celu uzyskania pożądanych wyników. Powtórzenie materiału W tym podrozdziale opisano następujące zagadnienia dotyczące wyjątków zdefiniowanych przez użytkownika:  Oracle udostępnia pakiet utl_call_stack przeznaczony do zarządzania stosami wyjątków.  Pakiet dbms_utility obejmuje funkcję format_error_backtrace, która generuje ślad stosu.  Pakiet utl_call_stack rejestruje stos wykonawczy niezależnie od stosu błędów.  Dane wyjściowe z funkcji format_error_backtrace można poddać parsowaniu i przekształcić je na dane wyjściowe w HTML-u.

Rozdział 7. Obsługa błędów

263

Pomocnicze skrypty Tu opisane są związane z książką programy pomocnicze dostępne w witrynie wydawnictwa Helion:  Plik exception_handling.sql zawiera krótkie programy dotyczące typów wyjątków, funkcji wbudowanych i wyjątków zdefiniowanych przez użytkownika.  Plik stack_trace_management.sql zawiera programy dotyczące zarządzania śladem stosu w sposób opisany w tym rozdziale.

Podsumowanie W tym rozdziale opisano zarządzanie błędami w języku PL/SQL. Omówiono tu różnice między błędami kompilacji i czasu wykonania, nieobsługiwane wyjątki czasu wykonania zgłaszane w sekcji deklaracji, a także obsługę błędów zgłaszanych w sekcjach wykonawczej i obsługi wyjątków.

Test wiedzy Test wiedzy to zestaw pytań typu „prawda czy fałsz” i wielokrotnego wyboru, dzięki którym sprawdzisz, jak dobrze opanowałeś materiał z poszczególnych rozdziałów. Odpowiedzi na pytania znajdziesz w dodatku I.

Prawda czy fałsz? 1. __Programowanie w języku Oracle PL/SQL wymaga zrozumienia procesu przechwytywania

i analizowania błędów czasu kompilacji i błędów czasu wykonania. 2. __Błędy czasu kompilacji mogą wystąpić przy próbie uruchomienia programu w postaci bloku

anonimowego. 3. __Błędy czasu wykonania mogą wystąpić przy próbie skompilowania procedury składowanej. 4. __Błędy czasu wykonania mogą wystąpić w wyniku wywołania procedury składowanej. 5. __Instrukcja THROW zgłasza wyjątek czasu wykonania. 6. __Można zadeklarować zdefiniowaną przez użytkownika zmienną typu EXCEPTION z kodem

błędu używanym w wyjątku wbudowanym. 7. __PRAGMA to instrukcja prekompilatora (dyrektywa kompilatora). 8. __Dyrektywa kompilatora EXCEPTION_INIT umożliwia powiązanie zdefiniowanej przez użytkownika zmiennej typu EXCEPTION z komunikatem. 9. __W wywołaniu funkcji raise_application_error można powiązać tylko kod błędu zdefiniowany przez użytkownika z niestandardowym komunikatem o błędzie. 10. __Wywołanie funkcji format_error_backtrace z pakietu utl_call_stack powoduje utworzenie śladu stosu.

Pytania wielokrotnego wyboru 11. Które z poniższych kodów błędów dotyczą wyjątków wbudowanych? Poprawnych może być

kilka odpowiedzi. A. ORA-01402. B. ORA-01722. C. ORA-06548. D. ORA-01422. E. ORA-00001. 12. Które z poniższych słów kluczowych oznaczają wyjątki wbudowane? Poprawnych może być kilka odpowiedzi.

264

Część I

Podstawy języka PL/SQL

A. CURSOR_IS_OPEN. B. INVALID_NUMBER. C. LOGIN_DENIED. D. NO_DATA_FOUND. E. VALUE_INCORRECT. 13. Które z poniższych instrukcji umożliwiają zgłoszenie wyjątku w kodzie w języku PL/SQL? Po-

prawnych może być kilka odpowiedzi. A. Instrukcja THROW e;. B. Instrukcja RAISE e;. C. Instrukcja THROW;. D. Instrukcja RAISE;. E. Wywołanie funkcji raise_application_error. 14. Które z poniższych funkcji znajdują się w pakiecie utl_call_stack? Poprawnych może być kilka odpowiedzi. A. Funkcja backtrace_error. B. Funkcja backtrace_depth. C. Funkcja error_number. D. Funkcja subprogram_name. E. Funkcja error_depth. 15. Które z poniższych funkcji wyświetlają ślad stosu dostosowany do HTML-a? Poprawnych może być kilka odpowiedzi. A. Funkcja utl_call_stack.current_edition. B. Funkcja dbms_utility.format_stack_trace. C. Funkcja dbms_utility.format_error_backtrace. D. Wszystkie z powyższych. E. Żadna z powyższych.

CZĘŚĆ

II

Programowanie w języku PL/SQL

266

Część II Programowanie w języku PL/SQL

Rozdział 8. Funkcje i procedury

267

ROZDZIAŁ

8

Funkcje i procedury

Jak wspomniano w poprzednich rozdziałach, rozróżnia się dwa rodzaje podprogramów: funkcje i procedury. Służą one do tworzenia bibliotek warstwy bazy danych w celu ukrycia mechanizmów aplikacji. Mechanizmy te są następnie umieszczane w warstwie bazy danych w celu poprawy wydajności ich działania. Rozdział ten dotyczy następujących zagadnień związanych z podprogramami:  Architektura funkcji i procedur  Zasięg transakcji  Funkcje  Opcje używane przy tworzeniu funkcji  Funkcje o parametrach przekazywanych przez wartość  Funkcje o parametrach przekazywanych przez referencję  Procedury  Procedury o parametrach przekazywanych przez wartość  Procedury o parametrach przekazywanych przez referencję Oracle 12c obsługuje podprogramy, które są przechowywane jako funkcje i procedury w bazie danych. Są to nazwane bloki języka PL/SQL. Można tworzyć je jako podprogramy niezależne lub jako elementy pakietów. Pakiety i typy obiektowe mogą zawierać zarówno funkcje, jak i procedury. W sekcji deklaracji bloków anonimowych także można definiować oba omawiane rodzaje podprogramów. Ponadto można zagnieżdżać funkcje i procedury wewnątrz innych podprogramów tego typu. Funkcje i procedury można udostępniać publicznie jako niezależne jednostki oraz w pakietach i typach obiektowych. Są wtedy nazywane funkcjami i procedurami z poziomu schematu. Takie jednostki są definiowane w specyfikacji pakietu lub typu obiektowego, a nie w ciele tych struktur. Jeśli zostaną zdefiniowane w ciele, będą funkcjonować jak podprogramy lokalne, które nie są publicznie dostępne. Także podprogramy zdefiniowane w sekcji deklaracji bloków anonimowych są lokalne. Kolekcje powiązanych funkcji i procedur można umieszczać w pakietach i typach obiektowych. Struktury te działają jak kontenery biblioteczne w bazie danych. Pakiety to podstawowe kontenery tego rodzaju, ponieważ nie trzeba tworzyć ich egzemplarzy, aby z nich korzystać, podczas gdy wymagają tego niektóre podprogramy w typach obiektowych. Pakiety umożliwiają też przeciążanie funkcji i procedur. Omówienie pakietów znalazło się w rozdziale 9. Typy obiektowe zdefiniowane przez użytkownika to typy języka SQL. W typach obiektowych funkcje i procedury można zdefiniować jako podprogramy z poziomu klasy lub egzemplarza. Funkcje i procedury z poziomu klasy są statyczne i można uzyskać dostęp do nich w taki sam sposób, jak do podprogramów zapisanych w pakietach. Jednostki z poziomu egzemplarza są dostępne tylko po utworzeniu egzemplarza danego typu obiektowego. Typy te opisano w rozdziale 11.

268

Część II Programowanie w języku PL/SQL

Poszczególne podrozdziały należy czytać po kolei, ponieważ późniejsze części opierają się na informacjach z wcześniejszych punktów. Jeśli Czytelnik chce przejść do jednego z dalszych fragmentów, szybkie przejrzenie całego rozdziału może pomóc w zrozumieniu tekstu.

Architektura funkcji i procedur Jak opisano w rozdziale 4., funkcje i procedury to bloki nazwane języka PL/SQL. Można też nazywać je podprogramami. Zamiast sekcji deklaracji znajdują się w nich nagłówki, które zawierają nazwę danej funkcji lub procedury, listę parametrów formalnych i typ wartości zwracanej przez funkcję. Parametry formalne określają zmienne, które można przesłać do podprogramu w momencie jego wywołania. W funkcjach i procedurach można używać parametrów formalnych i zmiennych lokalnych. Funkcje mogą zwracać wartości, podczas gdy procedury — nie, a przynajmniej nie są one formalnie określone, ponieważ podprogramy te zwracają wartość void. W innych językach programowania, na przykład w C, C#, Javie i C++, wartość ta jest określana jawnie. Procedury mogą jednak zwracać wartości przy użyciu przekazywane przez referencję zmiennych z listy parametrów formalnych. Lokalne funkcje i procedury nie wymagają deklaracji uprzedzających w postaci namiastek, jednak powinny je mieć. Dla funkcji i procedur składowanych listy parametrów i typy zwracanych wartości są zdefiniowane w katalogu bazy danych, jednak funkcji lokalnych to nie dotyczy. Dlatego utworzenie deklaracji uprzedzających w postaci namiastek pozwala uniknąć błędu „nie zadeklarowano w tym zasięgu” dla tworzonych procedur i funkcji. Przykład ilustrujący ten dobry zwyczaj znajdziesz w punkcie „Lokalne bloki nazwane” w rozdziale 3. W językach programowania rozróżnia się cztery ogólne rodzaje podprogramów. Te cztery typy są wyznaczane przez dwa tryby działania (ze zwracaniem wartości lub bez) i sposób przekazywania parametrów (przez wartość lub referencję). Parametry formalne należy określić w definicji podprogramu. Następnie można wywoływać podprogram przy użyciu argumentów. Parametry formalne wyznaczają dozwolone zmienne, ich pozycję i typy danych. Można określić wartość domyślną parametru, co jednocześnie sprawia, że staje się on opcjonalny. Argumenty to wartości przekazywane do podprogramów w miejscu ich wywołania. Jeśli parametr formalny ma wartość domyślną, nie trzeba podawać odpowiadającego mu argumentu. Podprogramy można wywoływać bez żadnych argumentów, jeśli wszystkie parametry formalne są opcjonalne. Podprogramy to czarne skrzynki, ponieważ ukrywają szczegóły działania, a dostępne są tylko informacje o tym, co można do nich przesłać oraz co zwracają. Różne rodzaje podprogramów opisano i zilustrowano w tabeli 8.1. „Czarna skrzynka” Pojęcia czarna skrzynka (pochodzi ono z dziedziny inżynierii) używa się w procesie weryfikacji i walidacji. Weryfikacja polega na ustaleniu, czy produkt został utworzony prawidłowo. Walidacja wymaga określenia, czy zbudowano odpowiedni produkt. Przykładowo można poddać walidacji linię produkcyjną i sprawdzić, czy wytwarza iPody, a następnie zweryfikować, czy są one zgodne z nową specyfikacją. Testy integracji służą do walidacji współdziałania komponentów. Nie można zobaczyć, jak działają mechanizmy produktu. Wiadomo jedynie, co powinien robić z danymi wejściowymi. Na przykład zadaniem funkcji może być dodanie do siebie dwóch liczb. Jeśli 1 dodać 1 równa się 2, funkcja działa zgodnie z oczekiwaniami. Na tym polegają testy czarnej skrzynki. Testy tego rodzaju to proces walidacji. Z kolei weryfikacja wymaga zajrzenia do czarnej skrzynki w celu sprawdzenia działania jej mechanizmów. Są to tak zwane testy białej skrzynki, dzięki którym można dokładnie zobaczyć, jak produkt działa. Testy jednostek pozwalają zweryfikować, czy funkcja lub procedura tworzy odpowiedni produkt, na przykład czy używa odpowiedniego wzoru do obliczenia przyszłych zasobów finansowych przy zastosowaniu procentu składanego.

Podprogramy zwracające dane wyjściowe to funkcje, a te, które nie zwracają wartości, to procedury. Funkcje zwracają dane w postaci wartości typów języka SQL lub PL/SQL. Cechy typów języka PL/SQL opisano w rozdziale 4., a dodatek B przedstawia typy języka SQL. Funkcje o parametrach przekazywanych przez wartość bywają czasem nazywane wyrażeniami, ponieważ programista określa wartości,

Rozdział 8. Funkcje i procedury

269

Tabela 8.1. Rodzaje podprogramów Opis podprogramu

Ilustracja podprogramu

Funkcje o parametrach przekazywanych przez wartość: Otrzymują kopie wartości w momencie wywołania, a po zakończeniu działania zwracają jedną zmienną wyjściową. Może to być zmienna skalarna lub złożona. Funkcje te mogą też wykonywać operacje zewnętrzne, na przykład wywoływać dotyczące bazy danych instrukcje w języku SQL DML. Funkcje o parametrach przekazywanych przez referencję: Przyjmują referencje do zmiennych w momencie wywołania. Referencje to argumenty funkcji. Podobnie jak inne funkcje zwracają jedną zmienną wyjściową, którą może być zmienna skalarna lub złożona. W odróżnieniu od wcześniej opisanych funkcji te mogą także zmieniać wartości argumentów. Po zakończeniu działania zwracają otrzymane referencje do programu wywołującego. Funkcje tego rodzaju mogą też wykonywać operacje zewnętrzne, na przykład wywoływać dotyczące bazy danych instrukcje w języku SQL DML, ale tylko w blokach języka PL/SQL. Procedury o parametrach przekazywanych przez wartość: Otrzymują kopie wartości w momencie wywołania i nie zwracają zmiennych wyjściowych. Wykonują tylko operacje wewnętrzne na zmiennych lokalnych i operacje zewnętrzne, takie jak kierowanie do bazy danych zapytań w języku SQL. Procedury o parametrach przekazywanych przez referencję: Otrzymują referencje do zmiennych w momencie wywołania i nie zwracają zmiennych wyjściowych. Podobnie jak funkcje o parametrach przekazywanych przez referencję mogą zmieniać wartość argumentów. Po zakończeniu działania zwracają do programu wywołującego referencje otrzymane jako argumenty. Procedury te mogą też wykonywać operacje zewnętrzne, na przykład kierować do bazy danych zapytania w języku SQL.

które są zwracane jako wynik. Jeśli zwracana wartość ma typ języka SQL, funkcję można wywoływać w instrukcjach tego języka. Tworzenie funkcji z parametrami przekazywanymi przez wartość jest jak pieczenie ciasta. Programista przekazuje zmienne do czarnej skrzynki, miesza je i otrzymuje wynik. Pierwotne składniki (zmienne) są wykorzystywane w procesie pieczenia ciasta. Stosowanie funkcji z parametrami przekazywanymi przez referencje przypomina szlifowanie kamieni szlachetnych. Programista umieszcza kamień w rozwiązaniu i szlifuje go. Rozwiązanie jest potem usuwane, jednak sam kamień pozostaje; jest nim zmienna przekazana przez referencję w trybie IN OUT. Można też używać zmiennej przekazywanej przez referencję w trybie OUT. Wyobraź sobie krojenie salami na plasterki. Do momentu zakończenia tego procesu nie wiadomo, ile będzie plasterków. Ich liczba to wynik zapisywany w zmiennej w trybie OUT. Rozróżnia się dwa rodzaje definicji typów danych w katalogu bazy danych. Typy danych mogą mieć postać macierzystych lub utworzonych przez użytkownika typów języka SQL lub typów PL/SQL zdefiniowanych przez użytkownika w specyfikacji pakietu.

Funkcji można używać jako prawych operandów w przypisaniach, ponieważ zwracają wartości typów zdefiniowanych w katalogu bazy danych. W blokach języka PL/SQL można używać do tego funkcji z parametrami przekazywanymi zarówno przez wartość, jak i przez referencję. W instrukcjach języka SQL można używać tych ostatnich tylko wtedy, gdy kod zarządza argumentami przed wywołaniem

270

Część II Programowanie w języku PL/SQL

danej funkcji i po nim. Można też użyć instrukcji CALL wraz z klauzulą INTO, aby pobrać zwróconą przez funkcję wartość o typie języka SQL. Z perspektywy technicznej o obsługę powiązanych zmiennych sesji języka SQL trzeba zadbać tylko przed wywołaniem w kodzie SQL funkcji o parametrach przekazywanych przez referencję.

Rysunek 8.1 ilustruje, jak w bloku języka PL/SQL przypisać wartość zwróconą przez funkcję. W instrukcjach języka SQL zwykle używane są funkcje z parametrami przekazywanymi przez wartość, ponieważ język ten nie obsługuje referencyjnych danych wyjściowych. Większość wywołań funkcji w języku SQL ma argumenty w postaci kolumn lub literałów, a zwracane wartości to skalary. Funkcje w języku SQL odpowiadają wyrażeniom z tego języka, czyli zapytaniom, które zwracają dane z tylko jednej kolumny jednego wiersza.

Rysunek 8.1. Przypisywanie wyniku funkcji

Procedur nie można używać jako prawych operandów. Zasięg wykonywania procedur musi znajdować się wewnątrz bloku języka PL/SQL, w którym zostały wywołane. Nie można ich wywoływać w instrukcjach języka SQL. Można jednak używać instrukcji CALL lub EXECUTE do uruchamiania procedur w środowisku SQL*Plus. Procedury to jednostki niezależne, podczas gdy funkcje można uruchamiać tylko w ramach przypisania i porównania lub instrukcji języka SQL. Ogólne (domyślne) funkcje i procedury działają w trybie wewnątrzwierszowym, co oznacza, że są uruchamiane w tym samym kontekście procesu, w którym działa jednostka wywołująca. Programy wewnątrzwierszowe działają w tym samym zasięgu transakcji co programy wywołujące. Program wewnątrzwierszowy nie może zatwierdzić operacji przed zatwierdzeniem instrukcji języka DML wykonanych przed wywołaniem danego programu w zasięgu transakcji. Programy autonomiczne działają w odrębnym kontekście procesu i korzystają z niezależnej kontroli transakcji. W Oracle Database 12c można utworzyć białą listę jednostek wywołujących danę funkcję lub procedurę. W tym celu należy dodać klauzulę ACCESSIBLE BY z listą funkcji, procedur, pakietów i typów obiektowych. Po utworzeniu białej listy dla funkcji lub procedury wywoływać daną jednostkę mogą tylko funkcje, procedury, pakiety i typy obiektowe wymienione na tej liście. W czarnych skrzynkach procedur i funkcji języka PL/SQL można uruchamiać instrukcje języka SQL. Tych operacji nie przedstawiono na poprzednich diagramach. Rysunek 8.2 to zmodyfikowana ilustracja funkcji o parametrach przekazywanych przez wartość, która aktualizuje bazę danych. Bardziej skomplikowane są funkcje o parametrach przekazywanych przez referencję, ponieważ skutki ich działania to dane wyjściowe, referencyjne dane wyjściowe i operacje na bazie danych. Funkcji wywołujących instrukcje INSERT, UPDATE lub DELETE zwykle nie można uruchamiać w zapytaniach, za to można je wywoływać w innych blokach języka PL/SQL. W funkcjach można umieszczać instrukcje języka SQL.

Funkcje z parametrami przekazywanymi przez wartość można wywoływać w instrukcjach SELECT tylko wtedy, gdy spełniony jest jeden z dwóch warunków. Po pierwsze, funkcja nie powinna obejmować instrukcji DML, ponieważ w zapytaniu nie można utworzyć kontekstu transakcji. Po drugie, instrukcje języka DML można stosować, gdy funkcja działa autonomicznie.

Rozdział 8. Funkcje i procedury

271

Rysunek 8.2. Funkcja o parametrach przekazywanych przez wartość z operacjami zapisu i odczytu danych z bazy

W zapytaniach nie można używać funkcji z parametrami przekazywanymi przez referencję, ponieważ nie istnieje sposób na zarządzanie parametrami w trybie IN OUT lub OUT (zobacz opis w tabeli 8.2). Programy autonomiczne działają w innym kontekście sesji niż jednostka wywołująca. To oznacza, że zagnieżdżone instrukcje INSERT, UPDATE lub DELETE nie informują bezpośrednio o sukcesie lub niepowodzeniu, chyba że w funkcji zostanie zgłoszony wyjątek. Tabela 8.2. Tryby parametrów podprogramów Tryb

Opis

IN

IN to tryb domyślny. Oznacza, że przekazywana jest kopia argumentu. Parametr formalny zdefiniowany bez określonego trybu domyślnie ma właśnie tryb IN. Oznacza to, że parametr

formalny jest przeznaczony tylko do odczytu. Takich parametrów nie można modyfikować w czasie wykonywania podprogramu. Można przypisać do takiego parametru wartość domyślną, przez co stanie się opcjonalny. W podprogramach o parametrach przekazywanych przez wartość wszystkie parametry formalne mają taki tryb. OUT

Tryb OUT powoduje przesłanie referencji. Domyślnie jej wartość początkowa to null. Parametr formalny w tym trybie jest przeznaczony tylko do zapisu. Przy tym ustawieniu zmienna nie ma określonego początkowego rozmiaru fizycznego. Jej wielkość i wartość należy podać wewnątrz podprogramu. Takim parametrom nie można przypisać wartości domyślnej i tym samym przekształcić ich na parametry opcjonalne. Próba wykonania tej operacji spowoduje błąd PLS-00230. Informuje on o tym, że zmienne o trybie OUT i IN OUT nie mogą mieć wartości domyślnej. Nie można też przekazać literału jako argumentu odpowiadającego parametrowi w trybie OUT, ponieważ uniemożliwia to przypisanie wartości do zmiennej wyjściowej. Próba przesłania literału w środowisku SQL*Plus wywoła błąd ORA-06577, a w bloku języka PL/SQL — błąd PLS-00363. Błąd w środowisku SQL*Plus informuje o tym, że parametr w trybie OUT nie jest zmienną powiązaną, czyli zmienną sesji SQL*Plus. W programie PL/SQL błąd wskazuje na to, że podane wyrażenie — a mówiąc dokładniej literał — nie może być elementem docelowym przypisania. Należy użyć trybu OUT dla jednego lub kilku parametrów formalnych, aby utworzyć podprogram o parametrach przekazywanych przez referencję przeznaczonych tylko do zapisu.

IN OUT

Tryb IN OUT wymaga przekazania referencji o określonej wartości początkowej. W tym trybie parametr formalny jest przeznaczony do odczytu i zapisu. Argumenty odpowiadające takim parametrom mają określony rozmiar fizyczny. Choć w podprogramie można zmienić zawartość zmiennej, nie można zmodyfikować ani przekroczyć przydzielonego jej rozmiaru. W trybie IN OUT obowiązują te same ograniczenia dotyczące wartości domyślnych i literałów co w trybie OUT.

Próba wywołania wewnątrz zapytania nieautonomicznej funkcji z instrukcją INSERT, UPDATE lub DELETE kończy się niepowodzeniem i zgłoszeniem błędu ORA-14551. Podobnie próba użycia funkcji z parametrami przekazywanymi przez referencję prowadzi do błędu ORA-06572, co oznacza, że funkcja ma parametry w trybie IN OUT lub OUT. Zaletą umieszczania instrukcji INSERT, UPDATE lub DELETE w autonomicznych funkcjach jest to, że można utworzyć w ten sposób funkcję z mechanizmem oczekiwania na zakończenie jej wykonywania. Taka funkcja zwraca jedną wartość po udanym zakończeniu pracy i inną w przypadku niepowodzenia. Zwykle 1 oznacza true, a 0 to false (odzwierciedla to działanie typu logicznego w SQL-u). Autonomicznej procedury z mechanizmem oczekiwania na zakończenie nie można utworzyć bez używania parametru w trybie OUT. To oznacza, że z takich procedur nie można korzystać w instrukcjach SQL-a. Funkcje i procedury z mechanizmem oczekiwania na zakończenie pozwalają przed kontynuowaniem wykonywania bieżącego programu sprawdzić, czy powstały w wyniku rozgałęzienia proces zakończył

272

Część II Programowanie w języku PL/SQL

pracę. Funkcje tego rodzaju są też nazywane funkcjami pesymistycznymi, ponieważ sprawdzają wystąpienie zdarzenia przed kontynuowaniem wykonywania kodu programu. Ogólną funkcję pesymistyczną przedstawia rysunek 8.3.

Rysunek 8.3. Funkcje pesymistyczne sprawdzają skutki wykonania instrukcji języka SQL

W języku PL/SQL rozróżnienie podprogramów na te z parametrami przekazywanymi przez wartość i referencję odbywa się na podstawie trybu ich parametrów formalnych. Język PL/SQL obsługuje trzy takie tryby: tylko do odczytu, tylko do zapisu oraz do odczytu i zapisu. Tryb domyślny to IN, który określa, że parametr formalny służy tylko do odczytu. Tryb OUT oznacza, że parametr jest przeznaczony tylko do zapisu, a tryb IN OUT wskazuje na parametry do odczytu i zapisu. Szczegółowy opis dostępnych trybów przedstawia tabela 8.2. Domyślnie programy w Oracle 12c przesyłają do wywoływanych podprogramów kopie wszystkich parametrów. Może się to wydawać dziwne, ponieważ jest sprzeczne z działaniem podprogramów o parametrach przekazywanych przez referencję. Jednak dokładnie takiego funkcjonowania można oczekiwać po podprogramach o parametrach przekazywanych przez wartość. Kiedy podprogram z powodzeniem kończy działanie, kopiuje parametry o trybie OUT i IN OUT z powrotem do zmiennych zewnętrznych. To rozwiązanie gwarantuje, że zmienne zewnętrzne nie zostaną zmodyfikowane do momentu udanego ukończenia przetwarzania przez funkcję lub procedurę. Eliminuje to możliwość zapisania częściowego zbioru wyników, jeśli działanie podprogramu zostanie zakończone przez błąd. Jeśli funkcja lub procedura zgłoszą wyjątek, można spróbować przywrócić działanie programu lub zapisać zmienne w pliku dziennika. Można zmienić domyślne rozwiązanie polegające na przekazywaniu kopii zmiennych w wywołaniach funkcji i procedur w transakcjach lokalnych. Ta inna możliwość pozwala zmniejszyć ilość używanych zasobów i powoduje przesyłanie referencji, a nie kopii danych. Domyślnego rozwiązania nie można zmienić przy wywoływaniu jednostek programu przy użyciu połączenia z bazą danych lub w wywołaniach procedur zewnętrznych. Aby zastąpić działanie domyślne, czyli kopiowanie, należy dodać wskazówkę NOCOPY. W niektórych sytuacjach wskazówka NOCOPY nie powoduje zmiany reguł kopiowania. Dzieje się tak, kiedy:  Argument to element tablicy asocjacyjnej. Wskazówka NOCOPY działa tylko przy przekazywaniu całej tablicy asocjacyjnej, a nie pojedynczych elementów.  Argument ma ograniczenie NOT NULL.  Argument jest ograniczony przy użyciu skali.  Argument jest niejawnie zdefiniowaną strukturą rekordową (programista zakotwiczył typ zmiennej przy użyciu atrybutu %ROWTYPE lub %TYPE).  Argument jest strukturą rekordową niejawnie zdefiniowaną w pętli FOR. Nie można wtedy przekazać referencji, ponieważ zasięg macierzystego indeksu jest ograniczony do pętli.  Argument wymaga niejawnego zrzutowania typu. Funkcje, procedury, pakiety i typy obiektowe można definiować na dwa sposoby:  Model oparty na uprawnieniach jednostki definiującej. Jest to domyślny model działania, gwarantujący, że program składowany korzysta z danych lokalnych z jego schematu. Atrybut AUTHID jest wtedy automatycznie ustawiany na DEFINER.

Rozdział 8. Funkcje i procedury 

273

Model oparty na uprawnieniach jednostki wywołującej. Można tak zdefiniować podprogram, aby zapisywał dane w lokalnym repozytorium bieżącego użytkownika. W tym celu atrybut AUTHID należy ustawić na CURRENT_USER. W modelu opartym na uprawnieniach jednostki wywołującej jest jedno repozytorium kodu, umożliwiające niezależnym użytkownikom korzystanie z lokalnych danych. W tym modelu trzeba przechowywać wiele egzemplarzy tabel i widoków w różnych schematach lub bazach danych. Przyznawane są wtedy uprawnienia EXECUTE do korzystania z tych innych schematów. Ten model najlepiej współdziała z przetwarzaniem rozproszonym.

Czym są dane lokalne? W Oracle dane lokalne to widoki utrwalone, synonimy, tabele i widoki. Tabele i widoki utrwalone to fizycznie przechowywane dane. Widoki to przetwarzane w czasie wykonywania programu zapytania kierowane do tabel, widoków utrwalonych i innych widoków. Synonimy danych to wskaźniki do widoków utrwalonych, synonimów, tabel i widoków. Dane można zapisywać w lokalnych widokach utrwalonych, tabelach, widokach i synonimach za pomocą podprogramów składowanych umieszczonych w tym samym schemacie. Synonimy mogą wskazywać na obiekty z tego samego lub innego schematu. Jeśli obiekt jest zdefiniowany w innym schemacie, niezbędne są uprawnienia do zapisu i odczytu tego schematu, aby można było prawidłowo przekształcić synonim na obiekt. Synonim lokalny pozwala przekształcić nazwę schematu, selektor komponentu (kropkę) i nazwę obiektu na nazwę schematu lokalnego.

W przykładowym kodzie w tym rozdziale i całej książce obowiązuje model uprawnień jednostki definiującej. Jest to najczęściej spotykane rozwiązanie. Różnice między oboma modelami są szczegółowo opisane w dodatku A. W bazie danych Oracle 11g wprowadzono zmiany w działaniu notacji opartej na nazwie i pozycji w językach SQL i PL/SQL. Obecnie obie notacje działają tak samo w obu językach. Rozwiązuje to długotrwałe rozbieżności w bazie danych. Powtórzenie materiału W tym podrozdziale opisano następujące zagadnienia dotyczące architektury funkcji i procedur:  Funkcje z parametrami przekazywanymi przez wartość to czarne skrzynki, które pobierają dane wyjściowe i zwracają nowy wynik.  Procedury z parametrami przekazywanymi przez wartość pobierają dane wyjściowe i nie zwracają wyniku.  Funkcje z parametrami przekazywanymi przez referencję to czarne skrzynki, które pobierają dane wyjściowe i obok zupełnie nowego wyniku zwracają niektóre z tych danych jako zmodyfikowane wartości zmiennych z wywołania.  Procedury z parametrami przekazywanymi przez referencję pobierają dane wyjściowe i zwracają niektóre z nich jako zmodyfikowane wartości zmiennych z wywołania.  Funkcje i procedury wewnątrzwierszowe działają w tym samym zasięgu transakcji co jednostka wywołująca.  Funkcje i procedury autonomiczne działają w innym zasięgu transakcji niż jednostka wywołująca.  Tryb IN to tryb domyślny, używany dla parametrów przekazywanych przez wartość. Tryby IN OUT i OUT dotyczą parametrów przekazywanych przez referencję.  Klauzula ACCESSIBLE BY umożliwia tworzenie białych list w funkcjach i procedurach.  Ostatecznie to, czy wskazówka NOCOPY spowoduje zastosowanie w parametrze kopii, czy referencji, zależy od działania silnika języka PL/SQL w bazie Oracle.

Zasięg transakcji Zasięg transakcji, jak wyjaśniono w punkcie „Transakcje danych” dodatku A, to wątek wykonania, czyli proces. Nawiązanie połączenia z bazą danych powoduje utworzenie sesji. Do momentu zatwierdzenia

274

Część II Programowanie w języku PL/SQL

operacji w bazie danych są one widoczne tylko w bieżącej sesji. Po zatwierdzeniu modyfikacji będą one dostępne także w innych sesjach. W trakcie sesji można uruchomić wiele programów w języku PL/SQL. Są one wykonywane sekwencyjnie, jeden po drugim. Pierwszy program może zmienić dane lub środowisko przed uruchomieniem drugiego i tak dalej. Dzieje się tak, ponieważ sesja to główna transakcja. Wszystkie operacje zależą od poprzednich. Można zatwierdzić i utrwalić zmiany albo odrzucić i anulować wybrane lub wszystkie modyfikacje. Funkcje i procedury to naturalne narzędzia do gwarantowania zgodności z modelem ACID dla dwóch lub więcej instrukcji języka DML. Zgodność z modelem ACID opisano w dodatku A. W Oracle Database 12c instrukcje INSERT, UPDATE, DELETE i MERGE działają w transakcjach zgodnych z tym modelem. Jednak czasem programista chce wykonać dwie instrukcje języka DML na tej samej bazie lub na różnych bazach. Wtedy jedynym sposobem na zapewnienie zgodności z modelem ACID jest wykorzystanie funkcji lub procedury. Grupę instrukcji języka DML należy wtedy umieścić w zasięgu jednej transakcji w jednostce programu składowanego, co przedstawia rysunek 8.4.

Rysunek 8.4. Przebieg transakcji w programie składowanym

Zapewnianie zgodności z modelem ACID dla dwóch lub więcej instrukcji języka DML uruchamianych dla jednej lub kilku tabel to główna przyczyna stosowania funkcji i procedur. Ponadto funkcje i procedury służą do izolowania logiki programu oraz dzielenia jej na moduły. Zasięg transakcji jest stosunkowo łatwy do zrozumienia, gdy używany jest jeden kontekst. Kontekst to zasięg procesu lub programu. Oracle Database 12c zarządza zasięgiem programu za pomocą pojedynczych sesji. To oznacza, że każdy program działający w jednej sesji ma kontekst operacyjny. W ramach kontekstu operacyjnego (sesji) funkcje i procedury bazy Oracle mogą wywoływać instrukcje SQL-a, a te instrukcje mogą wywoływać bloki nazwane języka PL/SQL. Te wewnętrzne wywołania w zasięgu transakcji operacyjnej powodują przełączanie kontekstu. Funkcje i procedury mogą mieć jeden z dwóch rodzajów zasięgu. Domyślnie działają w zasięgu zależnym, co oznacza, że są uruchamiane wewnątrzwierszowo, w zasięgu transakcji procesu głównego (czyli programu wywołującego). Jednak można też nakazać funkcjom i procedurom działanie we własnym zasięgu operacyjnym. W tym celu trzeba zdefiniować je jako transakcje autonomiczne. Zarządzanie transakcjami autonomicznymi jest zawsze bardziej skomplikowane, ponieważ są one uruchamiane niezależnie.

Rozdział 8. Funkcje i procedury

275

Nie pomyl wewnątrzwierszowego działania podprogramów ze stosowaną w kompilatorze sztuczką polegającą na wewnątrzwierszowym rozwijaniu kodu podprogramów. Ta sztuczka polega na tworzeniu kopii odrębnego, niezależnego programu i dodawaniu jej jako lokalnego kodu.

W transakcjach autonomicznych można zatwierdzać operacje lokalne niezależnie od programu wywołującego, pod warunkiem jednak, że nie prowadzi to do rywalizacji o zasoby w wyniku próby zmiany tych samych danych przez dwa niezależne procesy. Stosowana w bazach Oracle architektura MVCC chroni przed bezpośrednimi konfliktami. Jest to jedna z przyczyn, dla których w programach autonomicznych należy stosować własne instrukcje COMMIT. Pozwala ona utrwalić zmiany wprowadzone przez blok autonomiczny bez uwzględniania reguł obowiązujących w programie głównym. Transakcje autonomiczne to doskonałe rozwiązanie, jeśli program ma wykonywać pewne operacje niezależnie od powodzenia lub porażki innych działań. Jest to przydatne, jeśli programista chce zapisać dane przy użyciu wyzwalacza przed zgłoszeniem wyjątku, który przerwie działanie programu głównego. Jednak powoduje to także, że transakcje tego rodzaju bywają niebezpieczne. Mogą doprowadzić do przypadkowego zachowania danych w stanie, w którym nie należy ich zapisywać. Do kontroli zasięgu transakcji służą polecenia SAVEPOINT, ROLLBACK i COMMIT. Autonomiczne funkcje i procedury muszą zawierać własną instrukcję COMMIT. Jeśli w autonomicznej jednostce programu pominiesz potrzebną instrukcję COMMIT, próba kompilacji zakończy się niepowodzeniem.

Wywoływanie podprogramów W wersjach starszych od Oracle 11g programiści mogli używać notacji opartej na pozycji i nazwie do wywoływania podprogramów w jednostkach programów w języku PL/SQL, ale nie mogli używać notacji opartej na nazwie w wywołaniach funkcji w języku SQL. W Oracle 11g naprawiono tę niedoskonałość, a ponadto wprowadzono notację mieszaną. Przy stosowaniu notacji opartej na pozycji programista musi udostępnić wartość każdej zmiennej z listy parametrów formalnych. Wartości trzeba podać w odpowiedniej kolejności i muszą mieć typ pasujący do parametru. W notacji opartej na nazwie argumenty są podawane przy użyciu nazwy parametru formalnego, operatora asocjacji (=>) i wartości. Notacja oparta na nazwie umożliwia przekazywanie wartości tylko wymaganych parametrów, co oznacza, że parametry opcjonalne mogą mieć wartość domyślną. Nowa notacja mieszana związana jest z wywoływaniem podprogramów przy użyciu połączenia notacji opartej na pozycji i nazwie. Jest to bardzo wygodne, jeśli na liście parametrów najpierw określone są parametry wymagane, a następnie — opcjonalne. Programista może podać lub pominąć nazwy wymaganych parametrów, a także pominąć parametry opcjonalne, jeśli chce użyć ich wartości domyślnych. Nie rozwiązuje to jednak problemów związanych z notacją z pominięciem. Powstają one, jeśli w notacji opartej na pozycji parametry opcjonalne są wymieszane z parametrami wymaganymi lub gdy programista chce zmienić tylko niektóre parametry opcjonalne. Poniższa funkcja umożliwia wypróbowanie różnych notacji. Przyjmuje ona trzy parametry opcjonalne i zwraca sumę trzech liczb: CREATE OR REPLACE FUNCTION add_three_numbers ( a NUMBER := 0, b NUMBER := 0, c NUMBER := 0 ) RETURN NUMBER IS BEGIN RETURN a + b + c; END; /

Trzy pierwsze punkty opisują wywołania w notacji opartej na pozycji, nazwie i mieszanej. Punkt ostatni pokazuje, jak tworzyć wywołania w notacji z pominięciem.

Notacja oparta na pozycji Wywołanie funkcji przy użyciu notacji opartej na pozycji wygląda następująco: BEGIN dbms_output.put_line(add_three_numbers(3,4,5)); END; /

276

Część II Programowanie w języku PL/SQL

Notacja oparta na nazwie Poniższy kod wywołuje funkcję za pomocą notacji opartej na nazwie: BEGIN dbms_output.put_line(add_three_numbers(c => 4,b => 5,a => 3)); END; /

Notacja mieszana Podaną funkcję można też wywołać za pomocą połączenia notacji opartej na pozycji i notacji opartej na nazwie: BEGIN dbms_output.put_line(add_three_numbers(3,c => 4,b => 5)); END; /

Notacja mieszana wiąże się z pewnym ograniczeniem. Wszystkie argumenty podane w notacji opartej na pozycji muszą znajdować się na początku listy i mieć taką samą kolejność, jak w sygnaturze funkcji. Nie można podać wartości na podstawie pozycji po argumencie określonym przy użyciu nazwy bez spowodowania wyjątku.

Notacja z pominięciem Jak już wspomniano, można pominąć wybrane argumenty, jeśli parametry formalne są opcjonalne. Wszystkie parametry funkcji add_three_numbers są właśnie takie. Poniższy fragment pokazuje, jak przekazać wartość pierwszego parametru na podstawie pozycji, a trzeciego — za pomocą nazwy: BEGIN dbms_output.put_line(add_three_numbers(3,c => 4)); END; /

Jeśli programista zdecyduje się pominąć argument, będzie to odpowiadać przesłaniu wartości null. Jest to tak zwana notacja z pominięciem. Przez wiele lat twórcy Oracle zalecali umieszczanie zmiennych opcjonalnych jako ostatnich w sygnaturach funkcji i procedur, a także definiowanie parametrów opcjonalnych w kolejności od najczęściej do najrzadziej używanych. Te zalecenia mają zapobiegać błędom przy wywoływaniu podprogramów za pomocą notacji opartej na pozycji. W wywołaniach z użyciem notacji opartej na pozycji nie można całkowicie pominąć parametrów opcjonalnych, ponieważ ważny jest typ danych kolejnych parametrów. Jeśli jednak programista nie chce podawać parametrów opcjonalnych, może użyć oddzielonych przecinkami wartości null. Od wersji Oracle 11g można ponadto używać notacji mieszanej. Należy w niej podać wymagane parametry za pomocą notacji opartej na pozycji, a parametry opcjonalne — przy użyciu notacji opartej na nazwie. Pozwala to pominąć parametry opcjonalne bez konieczności podawania nazw pozostałych.

Notacja w wywołaniach w języku SQL Kiedyś programiści mieli tylko jedną możliwość — musieli podać wszystkie parametry na podstawie ich kolejności, ponieważ w języku SQL nie można było wskazywać ich za pomocą nazwy. To się zmieniło w Oracle 11g. Teraz w SQL-u można wywoływać funkcje w taki sam sposób, jak w blokach języka PL/SQL. Poniższa instrukcja przedstawia notację mieszaną w wywołaniu w języku SQL: SQL> SELECT add_three_numbers(3,c => 4, b => 5) 2 FROM dual;

Podobnie jak we wcześniejszych wersjach bazy danych, obecnie w instrukcjach w języku SQL można wywoływać tylko funkcje ze zmiennymi w trybie IN. Jeśli argumenty podawane w środowisku SQL*Plus nie są zmiennymi powiązanymi sesji, w języku SQL nie można uruchamiać funkcji, których parametry formalne to zmienne w trybie IN OUT lub OUT. Dzieje się tak, ponieważ jeśli parametr ma tryb OUT, trzeba przekazać referencję do zmiennej.

Rozdział 8. Funkcje i procedury

277

Powtórzenie materiału W tym podrozdziale opisano następujące zagadnienia dotyczące zasięgu transakcji funkcji i procedur:  Zasięg transakcji umożliwia zarządzanie wieloma instrukcjami języka DML dotyczącymi jednej tabeli lub kilku tabel przy zachowaniu gwarancji zgodności z modelem ACID. Daje to pewność, że albo wszystkie instrukcje języka DML zostaną wykonane z powodzeniem, albo wszystkie zostaną wycofane.  Domyślny zasięg transakcji odpowiada jednemu kontekstowi operacyjnemu.  Funkcje i procedury autonomiczne działają we własnym kontekście operacyjnym i aby się skompilowały, wymagają przynajmniej instrukcji COMMIT.  Oracle obsługuje następujące notacje: opartą na pozycji, opartą na nazwie, mieszaną i z pominięciem. Są one dostępne w językach SQL i PL/SQL.

Funkcje W języku PL/SQL można używać funkcji z parametrami przekazywanymi przez wartość i referencję. Oba rodzaje funkcji zwracają wartości. Dane wyjściowe mogą mieć typ danych języka SQL lub PL/SQL. W instrukcjach w języku SQL można używać tylko funkcji zwracających wartości o typie danych z tego języka. Funkcje zwracające wartości o typach danych języka PL/SQL można wywoływać tylko w blokach języka PL/SQL. Wyjątkiem od tej ogólnej reguły jest brak możliwości wywołania w zapytaniach języka SQL funkcji składowanych, które zawierają operacje DML. Próba wykonania takiej instrukcji spowoduje błąd ORA-14551, który informuje o tym, że nie można wykonywać operacji DML w zapytaniach. Można natomiast wywoływać funkcje wykonujące operacje DML w instrukcjach INSERT, UPDATE i DELETE. Funkcje mogą zawierać zagnieżdżone bloki nazwane, czyli lokalne funkcje i procedury. Bloki nazwane należy definiować w sekcji deklaracji funkcji. Ponadto można umieścić blok anonimowy w sekcji wykonawczej. Poniższy kod przedstawia prototyp funkcji: [{EDITIONABLE | NONEDITIONABLE}] FUNCTION nazwa_funkcji ( parametr [IN][OUT] [NOCOPY] typ_danych_sql | typ_danych_plsql [, parametr [IN][OUT] [NOCOPY] typ_danych_sql | typ_danych_plsql [, ... ]]) RETURN [ typ_danych_sql | typ_danych_plsql ] [ AUTHID [DEFINER | CURRENT_USER]] [ DETERMINISTIC | PARALLEL_ENABLED ] [ PIPELINED ] [ACCESSIBLE BY ( [{FUNCTION | PROCEDURE | PACKAGE | TYPE}] [schemat.]nazwa_jednostki) [,[{FUNCTION | PROCEDURE | PACKAGE | TYPE}] [schemat.]nazwa_jednostki)] [,... ]]]) [ RESULT_CACHE [RELIES ON nazwa_tabeli [, nazwa_tabeli [,...]]]] IS deklaracje BEGIN wykonywane_instrukcje RETURN zmienna; [EXCEPTION] obsługa_wyjątków END [nazwa_funkcji]; /

Aby wywołać funkcję, należy podać wymagane parametry na liście argumentów wewnątrz nawiasów otwierającego i zamykającego. W języku PL/SQL, w odróżnieniu od większości innych języków programowania, jeśli funkcja nie ma parametrów wymaganych, nawiasów nie trzeba używać. Wywołania w innych językach zwykle wymagają dodania pustych nawiasów otwierającego i zamykającego. Prototyp wywołania funkcji z argumentami w środowisku SQL*Plus wygląda następująco: CALL nazwa_funkcji(argument [, argument [, ...]]) INTO nazwa_zmiennej_docelowej;

278

Część II Programowanie w języku PL/SQL

Jeśli funkcja nie ma żadnych parametrów wymaganych, prototyp wygląda inaczej: CALL nazwa_funkcji INTO nazwa_zmiennej_docelowej;

Przypisywanie do zmiennych wartości funkcji z parametrami wymaganymi w blokach języka PL/SQL wygląda następująco: nazwa_zmiennej_docelowej := nazwa_funkcji(argument [, argument [, ...]]);

Jeśli nawiasy nie są konieczne, można je pominąć: nazwa_zmiennej_docelowej := nazwa_funkcji;

Można też zwrócić wartość funkcji jako wyrażenie i użyć jej jako argumentu innej funkcji. Przedstawia to poniższy prototyp: nazwa_funkcji_zewnętrznej(nazwa_funkcji( argument [, argument [, ...]]));

Jest kilka opcjonalnych rozwiązań, których można użyć przy tworzeniu funkcji. Można zdefiniować funkcję z uprawnieniami osoby definiującej lub z uprawnieniami osoby wywołującej. Służy do tego klauzula AUTHID. Można też zagwarantować działanie funkcji, co pozwala używać jej w instrukcjach języka SQL, indeksach opartych na funkcjach i widokach utrwalonych. Funkcje mogą ponadto zwracać tabele potokowe, a w Oracle 12c — także współużytkowane zbiory wyników z pamięci podręcznej z obszaru SGA. Jak wcześniej wspomniano, Oracle Database 12c umożliwia utworzenie białej listy jednostek wywołujących funkcje, procedury, pakiety lub typy obiektowe. Do tworzenia takich list służy klauzula ACCESSIBLE BY. Należy ją stosować, jeśli interfejs API związany z zabezpieczeniami ma kontrolować wywoływanie funkcji składowanych. W rozdziale 2. znajdziesz kompletny przykład ilustrujący tworzenie białej listy dla funkcji library. Parametry formalne mogą mieć trzy tryby:  Parametry w trybie IN są przeznaczone tylko do odczytu.  Parametry w trybie OUT są przeznaczone tylko do zapisu.  Parametry w trybie IN OUT są przeznaczone do odczytu i zapisu. Za pomocą trybu parametrów można tworzyć funkcje o parametrach przekazywanych przez wartość i funkcje o parametrach przekazywanych przez referencję. Aby utworzyć funkcję o parametrach przekazywanych przez wartość, wszystkim parametrom trzeba nadać tryb IN. Jeśli przynajmniej jeden parametr ma tryb OUT lub IN OUT, podprogram będzie funkcją o parametrach przekazywanych przez referencję. Trzy dalsze punkty opisują sposoby tworzenia funkcji. W pierwszym omówiono opcjonalne klauzule, które umożliwiają tworzenie funkcji o specyficznym przeznaczeniu. Drugi dotyczy funkcji o parametrach przekazywanych przez wartość, a trzeci — funkcji o parametrach przekazywanych przez referencję.

Wybór rodzaju funkcji Jakie są ogólne reguły dotyczące wyboru rodzaju funkcji (o parametrach przekazywanych przez wartość lub przez referencję)? Są one stosunkowo proste, o czym wkrótce się przekonasz. Funkcje o parametrach przekazywanych przez wartość należy stosować wtedy, gdy ma ona obliczać wynik na podstawie danych wejściowych. Tego rodzaje funkcje powinno się też tworzyć wtedy, gdy mają działać w instrukcjach SQL-a. Funkcja o parametrach przekazywanych przez wartość doskonale sprawdza się też dla autonomicznego zasięgu transakcji. W kontekście programowania obiektowego można powiedzieć, że funkcje tego rodzaju stosuje się wtedy, gdy chce się uzyskać najniższy możliwy poziom powiązania — powiązanie oparte na komunikatach. Gdy programy są luźno powiązane, dają programiście większą swobodę i nadają się do wielokrotnego wykorzystania w aplikacji. Programy ściśle powiązane są bezpośrednio zależne od siebie i tworzą jednostkę przetwarzania. Podobnie jak piwo korzenne i lody waniliowe są używane do przygotowania

Rozdział 8. Funkcje i procedury

279

deseru w postaci piwa korzennego z pływającymi w nim lodami, tak tego rodzaju programy są łączone ze sobą w jednostkę przetwarzania. Funkcje o parametrach przekazywanych przez referencję tworzy się wtedy, gdy działanie jednostek wywołujących i wywoływanych ma być powiązane ze sobą (jest to powiązanie oparte na danych). Ma to miejsce, gdy funkcja jest wywoływana w jednowątkowym zasięgu wykonania. W większości transakcyjnych aplikacji bazodanowych jest to zasięg domyślny. W takich ściśle powiązanych programach wartości mogą być zwracane za pomocą parametrów formalnych w trybie IN OUT lub OUT. Gdy przyjmowane parametry zawierają nieprzetworzone dane, a parametry zwracane — dane przetworzone, zwracana wartość funkcji stanowi informację o powodzeniu lub niepowodzeniu. W funkcjach języka PL/SQL, w których wartość zwracana informuje o powodzeniu lub niepowodzeniu, typem zwracanej wartości jest typ logiczny lub liczbowy. Typ logiczny stosuje się, jeśli funkcja ma działać wyłącznie w blokach języka PL/SQL. Liczby używane są wtedy, gdy funkcja może działać w zasięgu języka SQL lub PL/SQL. Funkcje o parametrach przekazywanych przez referencję są idealnym rozwiązaniem, gdy programista chce powiązać interakcję po stronie klienta z modułami działającymi po stronie serwera. Autonomiczne funkcje działają we własnym zasięgu transakcji, dlatego pracują niezależnie od zasięgu transakcji jednostki wywołującej. Jedyny sposób na ustalenie, czy działanie funkcji zakończyło się powodzeniem, to sprawdzenie stanu na podstawie zwróconej przez tę funkcję wartości. Stosowanie funkcji o parametrach przekazywanych przez referencję zwykle nie jest dobrym pomysłem, gdy programista chce powiązać dwa programy działające po stronie serwera. Gdy programy działają w tej samej warstwie i mogą być wywoływane w tym samym zasięgu sekwencyjnie wykonywanych transakcji, należy zastosować procedurę o parametrach przekazywanych przez referencję. Procedury są bardzo podobne do stosowanych w językach C, C++, C# lub Java metod zwracających wartość void, a nie wartość konkretnego typu danych.

Opcje używane przy tworzeniu funkcji Do tworzenia funkcji na potrzeby instrukcji SQL, indeksów opartych na funkcjach i widoków utrwalonych służą klauzule DETERMINISTIC i PARALLEL_ENABLED. Zastępują one starszą instrukcję prekompilatora RESTRICT_REFERENCES, która ograniczała możliwości funkcji umieszczonych w pakietach. Nowe klauzule pozwalają nakładać te same ograniczenia zarówno na funkcje z pakietów, jak i na niezależne funkcje składowane. Klauzula PIPELINED służy do tworzenia funkcji zwracających tabele potokowe. Takie tabele działają jak kursory pseudoreferencyjne i są zbudowane przy użyciu zmodyfikowanych typów kolekcji języka PL/SQL. Ta technika umożliwia używanie kolekcji struktur rekordowych języka PL/SQL bez definiowania ich jako wymagających konstrukcji typów obiektowych zdefiniowanych przez użytkownika. Można też wczytywać takie kolekcje w instrukcjach w języku SQL w podobny sposób jak widoki wewnątrzwierszowe. Funkcje zwracające tabele obiektowe umożliwiają zwracanie tablic VARRAY i tabel zagnieżdżonych bezpośrednio do dowolnej instrukcji języka DML (trzeba przy tym pamiętać o umieszczeniu wyniku w wywołaniu funkcji TABLE). Dzięki takim funkcjom można zrezygnować z funkcji zwracających tabele potokowe, potrzebnych od teraz tylko w starszym kodzie w języku PL/SQL. Funkcji zwracających tabele potokowe programiści będą prawdopodobnie używać do zwracania tablic asocjacyjnych z elementami typów skalarnych lub rekordowych. W Oracle 11g wprowadzono międzysesyjną pamięć podręczną na wyniki działania funkcji z uprawnieniami jednostki definiującej. W Oracle Database 12c w pamięci podręcznej można zapisywać także wyniki funkcji z uprawnieniami jednostki wywołującej. W tym celu trzeba dodać identyfikator bieżącego użytkownika do wyników zapisanych w takiej pamięci. Aby użyć tego mechanizmu, należy podać w definicji funkcji klauzulę RESULT_CACHE. Międzysesyjna pamięć podręczna na wyniki działania funkcji przechowuje argumenty i wyniki każdego wywołania podprogramu z taką klauzulą (a dla funkcji z uprawnieniami jednostki wywołującej — także wynik wywołania funkcji CURRENT_USER). Przy drugim wywołaniu danej funkcji z takimi samymi argumentami wynik jest pobierany z pamięci podręcznej, dzięki czemu nie trzeba ponownie uruchamiać tego samego kodu. Zwracane wyniki są zapisywane

280

Część II Programowanie w języku PL/SQL

w obszarze SGA. Gdy pojemność pamięci podręcznej wyczerpie się, system usunie z niej wynik najrzadziej używanego wywołania funkcji. Problemy ze zgodnością funkcji wstecz W bazach danych starszych od Oracle 8i (8.1.6) funkcje były podprogramami ograniczonymi. Trzeba było definiować je z gwarancją działania, czyli tak zwanym poziomem czystości. Te gwarancje ograniczały możliwości funkcji w zakresie odczytu i zapisu zmiennych pakietu oraz tabel bazy danych. Obecnie nadal można nakładać takie ograniczenia na funkcje umieszczone wewnątrz pakietów. Służą do tego opcje dyrektywy PRAGMA RESTRICT_REFERENCES opisane w poniższej tabeli. Dyrektywa PRAGMA to instrukcja dla prekompilatora. Opcja

Opis

RNDS

Opcja RNDS gwarantuje, że funkcja nie wczytuje stanu danych (ang. reads no data state). Oznacza to, że w takich funkcjach nie można umieszczać żadnych zapytań języka SQL ani wywoływać innych bloków nazwanych z takimi zapytaniami. Umieszczenie w zasięgu funkcji zapytania, które narusza to ograniczenie z dyrektywy PRAGMA, spowoduje w czasie kompilacji błąd PLS-00452.

WNDS

Opcja WNDS gwarantuje, że funkcja nie zapisuje stanu danych (ang. writes no data state). Oznacza to, że nie można w niej używać instrukcji języka SQL, które wstawiają, aktualizują lub usuwają dane, ani wywoływać innych bloków nazwanych zawierających takie polecenia. Jeśli w zasięgu funkcji znajdzie się instrukcja DML, która narusza to ograniczenie z dyrektywy PRAGMA, w czasie kompilacji wystąpi błąd PLS-00452.

RNPS

Opcja RNPS gwarantuje, że funkcja nie wczytuje stanu pakietu (ang. reads no package state). Oznacza to, że nie można w niej korzystać ze zmiennych pakietu ani wywoływać innych bloków nazwanych, które wczytują takie zmienne. Umieszczenie w zasięgu funkcji zapytania, które narusza to ograniczenie z dyrektywy PRAGMA, wywoła w czasie kompilacji błąd PLS-00452.

WNPS

Opcja WNDS gwarantuje, że funkcja nie zapisuje stanu pakietu (ang. writes no package state). Oznacza to, że nie można zmieniać wartości zmiennych pakietu ani wywoływać innych bloków nazwanych, które je zmieniają. Umieszczenie w zasięgu funkcji zapytania, które narusza to ograniczenie z dyrektywy PRAGMA, spowoduje w czasie kompilacji błąd PLS-00452.

TRUST

Opcja TRUST informuje, że funkcja ma nie sprawdzać, czy wywoływane programy są zgodne z innymi opcjami dyrektywy RESTRICT_REFERENCES. Opcja ta umożliwia stopniowe dopasowywanie kodu do nowych standardów. Z drugiej strony jej użycie grozi zmianą działania lub wydajności instrukcji SQL. Ponadto inne opcje nakładają na funkcje ograniczenia, które umożliwiają używanie tych podprogramów w indeksach opartych na funkcjach i zapytaniach równoległych.

Przy użyciu dyrektywy PRAGMA można definiować ograniczenia w specyfikacji pakietu, a nie w jego ciele. Do każdej funkcji można dołączyć tylko jedną taką dyrektywę, natomiast w instrukcji RESTRICT_ REFERENCES można użyć wielu opcji. Opcja TRUST pozwala „osłabić” instrukcję PRAGMA, jeśli programista chce umożliwić wywoływanie w funkcji ograniczonej innych, nieograniczonych funkcji. Opcja ta wyłącza sprawdzanie zgodności wywoływanych funkcji z ograniczeniami nałożonymi na wywołujące je jednostki programu, czyli z określonym poziomem czystości. W specyfikacjach starszych pakietów warto rozważyć zastąpienie ograniczających instrukcji prekompilatora klauzulą DETERMINISTIC lub PARALLEL_ENABLED. Zgodność wstecz to wartościowa cecha, jednak rzadko jest podtrzymywana w nieskończoność. Należy zastąpić starsze instrukcje prekompilatora i zdefiniować funkcje przy użyciu nowej składni. Oznacza to tworzenie funkcji z klauzulą DETERMINISTIC, jeśli są używane w indeksach opartych na funkcjach, a z klauzulą PARALLEL_ENABLED, jeśli mają być stosowane w operacjach równoległych.

Klauzula DETERMINISTIC Klauzula DETERMINISTIC pozwala zagwarantować, że funkcja dla określonych danych zawsze działa w taki sam sposób. Ten rodzaj gwarancji wymaga, aby funkcja nie pobierała danych ze źródeł zewnętrznych, na przykład z pakietów lub tabel bazy danych, ani nie zapisywała ich. W widokach utrwalonych i indeksach opartych na funkcji można używać tylko funkcji deterministycznych. Klauzulę tę warto stosować także w funkcjach zdefiniowanych przez użytkownika, które mają być używane w klauzulach

Rozdział 8. Funkcje i procedury

281

instrukcji języka SQL (na przykład w klauzulach WHERE, ORDER BY lub GROUP BY) i w metodach typów obiektowych języka SQL (na przykład w metodach MAP i ORDER). Funkcje deterministyczne przetwarzają parametry zawsze w dokładnie ten sam sposób. Oznacza to, że niezależnie od przekazanych wartości funkcja będzie działać identycznie. Działanie funkcji tego rodzaju nie powinno zależeć od zmiennych pakietu lub danych z bazy. Poniższa funkcja jest deterministyczna i oblicza poziom wyjściowy inwestycji: SQL> 2 3 4 5 6 7 8 9

CREATE OR REPLACE FUNCTION pv ( future_value NUMBER , periods NUMBER , interest NUMBER ) RETURN NUMBER DETERMINISTIC IS BEGIN RETURN future_value / ((1 + interest/100)**periods); END pv; /

Załóżmy, że użytkownik chce się dowiedzieć, ile musi zainwestować, aby przy oprocentowaniu na poziomie 6% uzyskać w ciągu 5 lat kwotę 10 000 złotych. Aby przetestować tę funkcję, należy zdefiniować zmienną powiązaną, użyć instrukcji CALL, aby umieścić odpowiednią wartość w tej zmiennej, a następnie pobrać wynik z tabeli DUAL: SQL> SQL> SQL> SQL> 2

VARIABLE result NUMBER CALL pv(10000,5,6) INTO :result; COLUMN money_today FORMAT 9,999.90 SELECT :result AS money_today FROM dual;

Widoki utrwalone W odróżnieniu od standardowych widoków bazy danych widoki utrwalone są zbiorami wyników zapisanymi w pamięci podręcznej, dlatego przechowuje się je jako tabele konkretne. Zapytania kierowane do widoków utrwalonych są przetwarzane szybciej, ponieważ program nie potrzebuje zasobów na każdorazowe dynamiczne utworzenie widoku. Wadą tej techniki jest to, że widoki utrwalone są często nieaktualne, ponieważ zapisane w nich dane mogły zmienić się od czasu zapisania widoku w pamięci podręcznej do momentu uzyskania do niego dostępu. W widokach utrwalonych można używać indeksów opartych na funkcji, jeśli funkcja z indeksu jest deterministyczna. Funkcje deterministyczne zawsze zwracają tę samą wartość, jeśli otrzymają takie same argumenty, a także gwarantują, że nie zmodyfikują zmiennych pakietu ani danych z bazy. Widoki utrwalone warto stosować wtedy, gdy dane w używanej tabeli zmieniają się rzadko, a ważny jest czas przetwarzania zapytania. Widoki tego rodzaju są przydatne na przykład przy tworzeniu tabel faktów w magazynach danych.

W wywołaniu funkcji użyto notacji opartej na pozycji, ale można też użyć notacji opartej na nazwie lub mieszanej. Powyższe instrukcje wyświetlą sformatowany wyjściowy poziom inwestycji: MONEY_TODAY ----------7,472.58

Funkcji deterministycznych można używać w widokach utrwalonych i indeksach opartych na funkcji. Obie te struktury wymagają ponownego zbudowania po zmianie wewnętrznych mechanizmów powiązanej z nimi funkcji deterministycznej.

Klauzula PARALLEL_ENABLE Klauzula PARALLEL_ENABLE umożliwia utworzenie funkcji obsługującej zapytania równoległe. Gwarancje tego typu wymagają, aby funkcja nie wczytywała danych ze źródeł zewnętrznych, takich jak pakiety i tabele baz danych, ani nie zapisywała ich. Warto też rozważyć określenie funkcji jako bezpiecznych ze względu na operacje równoległe, aby poprawić przepustowość, ale optymalizator bazy Oracle 12c może uruchomić funkcję bez tej klauzuli, jeśli uzna, że jest bezpieczna. Metody języka Java i programy zewnętrzne w języku C nigdy nie są uznawane jako bezpieczne ze względu na operacje równoległe.

282

Część II Programowanie w języku PL/SQL

Poniższej funkcji można używać w operacjach równoległych języka SQL. Łączy ona imię, nazwisko i inicjał drugiego imienia w jeden łańcuch znaków: SQL> 2 3 4 5 6 7 8 9

CREATE OR REPLACE FUNCTION merge ( last_name VARCHAR2 , first_name VARCHAR2 , middle_initial VARCHAR2 ) RETURN VARCHAR2 PARALLEL_ENABLE IS BEGIN RETURN last_name ||', '||first_name||' '||middle_initial; END; /

Funkcji tej można bezpiecznie używać w zapytaniach kierowanych do bazy danych: SQL> SELECT merge(last_name,first_name,middle_initial) AS full_name 2 FROM contact 3 ORDER BY last_name, first_name, middle_initial;

To zapytanie zależy od kodu opisanego we wprowadzeniu i zwraca następujące dane wyjściowe: FULL_NAME ----------------Sweeney, Ian M Sweeney, Irving M ...

Nie zawsze użycie wskazówki PARALLEL_ENABLE powoduje wykonanie operacji równoległych. Przy przetwarzaniu małych zbiorów danych taki sposób działania jest bardziej kosztowny. To optymalizator bazy Oracle 12c określa, czy operacje należy uruchomić w trybie równoległym, czy nie. Ponadto czasem optymalizator uruchamia funkcje równolegle nawet wtedy, gdy nie są oznaczone jako przeznaczone do takiego użytku. Podejmuje tę decyzję po sprawdzeniu, czy danej funkcji można używać w takim trybie. Dobrą praktyką programistyczną jest udostępnianie nadających się do tego funkcji na potrzeby operacji równoległych.

Klauzula PIPELINED Klauzula PIPELINED umożliwia poprawę wydajności, jeśli funkcja zwraca kolekcje, na przykład tabele zagnieżdżone lub tablice VARRAY. Zwiększenie wydajności można też zauważyć przy zwracaniu systemowych kursorów referencyjnych za pomocą tej klauzuli. Funkcje potokowe umożliwiają ponadto zwracanie tabel zagregowanych. Tabele te działają jak kolekcje struktur rekordowych języka PL/SQL i można ich używać tylko w instrukcjach języka SQL. Ten punkt opisuje zagadnienia związane z kolekcjami. Osoby, które dopiero poznają język PL/SQL, znajdą omówienie kolekcji w rozdziale 6. Kolekcje to tablice i listy zmiennych skalarnych oraz złożonych. Funkcje potokowe współdziałają tylko z tablicami VARRAY i tabelami zagnieżdżonymi. Do indeksowania kolekcji tych dwóch rodzajów służą kolejne liczby. Można też tworzyć kolekcje SQL-owych typów obiektowych zdefiniowanych przez użytkownika. Takie kolekcje są traktowane jak jednowymiarowe tablice liczb, łańcuchów znaków lub dat. Najprostsza wersja funkcji potokowych związana jest z kolekcją wartości skalarnych o typie danych z języka SQL. Poniższe polecenie definiuje typ danych NUMBERS, który jest tablicą VARRAY przechowującą elementy typu NUMBER: SQL> CREATE OR REPLACE 2 TYPE numbers AS VARRAY(10) OF NUMBER; 3 /

Liczba 10 w nawiasach po słowie VARRAY określa maksymalną liczbę elementów kolekcji, co opisano w rozdziale 6. Typy VARRAY są bardzo podobne do tablic. W większości języków programowania tablice mają stałą wielkość lub pamięć. Po utworzeniu typu kolekcji można wyświetlić jej opis przy użyciu wiersza poleceń języka SQL: SQL> DESCRIBE NUMBERS NUMBERS VARRAY(10) OF NUMBER

Rozdział 8. Funkcje i procedury

283

Przy tworzeniu typów w bazie danych polecenia DDL działają jak bloki języka PL/SQL. Polecenia tego typu wymagają użycia średnika w celu zakończenia instrukcji i ukośnika, aby je wykonać (lub skompilować w bazie danych).

Funkcje potokowe zależą od dostępnych typów kolekcji języka SQL lub PL/SQL. Typy te można tworzyć tylko na podstawie tablic VARRAY i tabel zagnieżdżonych. Można też zdefiniować typ kolekcji języka SQL przechowującej zmienne skalarne lub typu obiektowego zdefiniowanego przez użytkownika. Poniższy kod definiuje funkcję potokową, która zwraca listę liczb: SQL> 2 3 4 5 6 7 8 9 10 11

CREATE OR REPLACE FUNCTION pipelined_numbers RETURN NUMBERS PIPELINED IS list NUMBERS := numbers(0,1,2,3,4,5,6,7,8,9); BEGIN FOR i IN 1..list.LAST LOOP PIPE ROW(list(i)); END LOOP; RETURN; END; /

Ta funkcja zwraca kolekcję zdefiniowanego przez użytkownika typu danych NUMBERS języka SQL. Typ ten jest zapisany w katalogu danych. W funkcji w wierszu 4. zadeklarowano lokalną kolekcję typu NUMBERS przez jej zainicjowanie. W rozdziale 6. wyjaśniono, że w celu zainicjowania kolekcji należy wywołać nazwę zdefiniowanego przez użytkownika typu danych języka SQL i podać puste nawiasy lub listę wartości typu bazowego kolekcji. Tu typem bazowym jest typ NUMBERS. Wiersz 7. przypisuje elementy z kolekcji do potoku. Aby pobrać wyniki, należy użyć poniższego zapytania: SQL> SELECT * 2 FROM TABLE(pipelined_numbers);

Dane wyjściowe to jedna kolumna z liczbami porządkowymi z przedziału od 0 do 9. W funkcjach potokowych można też używać typów kolekcji języka PL/SQL. Typy kolekcji języka PL/SQL — podobnie jak ich odpowiedniki z języka SQL — mogą przechowywać zmienne skalarne lub typów obiektowych zdefiniowanych przez użytkownika. Mogą to być także kolekcje struktur rekordowych. Oznacza to, że pod tym względem są podobne do systemowych kursorów referencyjnych. W odróżnieniu od systemowych kursorów referencyjnych, typów kolekcji języka PL/SQL nie można definiować przy użyciu typów danych języka SQL, ale tylko za pomocą typów języka PL/SQL. Aby zwrócić wartości tego typu w funkcji składowanej, trzeba zdefiniować ten typ w specyfikacji pakietu. Pakiety opisano szczegółowo w rozdziale 9. Poniższa specyfikacja pakietu zawiera definicję struktury rekordowej, kolekcję elementów tego typu (account_record) i funkcję zwracającą taką kolekcję: SQL> CREATE OR REPLACE PACKAGE pipelined IS 2 /* Definicje typu rekordowego języka PL/SQL i kolekcji elementów tego typu. */ 3 TYPE account_record IS RECORD 4 ( account VARCHAR2(10) 5 , full_name VARCHAR2(42)); 6 TYPE account_collection IS TABLE OF account_record; 7 8 /* Definicja funkcji potokowej. */ 9 FUNCTION pf RETURN account_collection PIPELINED; 10 END pipelined; 11 /

Wiersz 6. deklaruje kolekcję elementów zadeklarowanej wcześniej struktury rekordowej. Wiersz 9. deklaruje funkcję pf (jest to funkcja potokowa). Zauważ, że kolekcja w wierszu 6. to tabela zagnieżdżona, a nie tablica asocjacyjna. Funkcje potokowe wymagają umieszczenia danych w tabeli zagnieżdżonej lub tablicy VARRAY. W ciele pakietu znajduje się implementacja funkcji pf:

284

Część II Programowanie w języku PL/SQL

SQL> CREATE OR REPLACE PACKAGE BODY pipelined IS 2 /* Implementacja funkcji potokowej. */ 3 FUNCTION pf 4 RETURN account_collection 5 PIPELINED IS 6 /* Deklaracja zmiennej sterującej kolekcji 7 i zmiennej typu kolekcji.*/ 8 counter NUMBER := 1; 9 account ACCOUNT_COLLECTION := account_collection(); 10 11 /* Definicja kursora. */ 12 CURSOR c IS 13 SELECT m.account_number 14 , c.last_name || ', '||c.first_name full_name 15 FROM member m JOIN contact c 16 ON m.member_id = c.member_id 17 ORDER BY c.last_name, c.first_name, c.middle_initial; 18 BEGIN 19 FOR i IN c LOOP 20 /* Przydzielanie pamięci i dodawanie wartości do kolekcji. */ 21 account.EXTEND; 22 account(counter).account := i.account_number; 23 account(counter).full_name := i.full_name; 24 /* Przypisywanie struktury rekordowej do potoku. */ 25 PIPE ROW(account(counter)); 26 counter := counter + 1; 27 END LOOP; 28 RETURN; 29 END pf; 30 END pipelined; 31 /

W ciele pakietu znajduje się implementacja funkcji pf. W wierszu 8. tworzony jest licznik dla kolekcji account. Wiersz 9. deklaruje i inicjuje tę kolekcję. Wartości pól account i full_name są ustawiane oddzielnie na podstawie danych z kursora, ponieważ typy rekordowe języka PL/SQL nie obsługują konstruktorów. Istnieje też wydajniejsza technika przypisywania. Można przypisać iterator kursora pętli FOR bezpośrednio do kolekcji, jeśli listy typów danych w obu strukturach pasują do siebie. W tym podejściu wiersze 22. i 23. można zastąpić następującą składnią: 22 23

account(counter) := i;

Jak widać, operacje przypisania wartości do dwóch odrębnych pól (wiersze 22. i 23.) przekształcono na jedno przypisanie rekordu z kursora (wiersz 22.). Przy stosowaniu kolekcji rekordów języka PL/SQL zapewne najczęściej będziesz stosował bezpośrednie przypisywanie danych z kursora. Zarówno tablice VARRAY, jak i tabele zagnieżdżone to obiekty wewnętrzne, jeśli zaimplementowano je w pakiecie. Choć gdy typem bazowym jest typ obiektowy, niezbędna jest jego jawna konstrukcja, nie można stosować tej techniki dla typów bazowych w postaci struktur rekordowych języka PL/SQL. Przed dodaniem elementów do tablic VARRAY lub tabel zagnieżdżonych trzeba przydzielić im pamięć. Metoda EXTEND przydziela pamięć dla jednego elementu (wiersz 19.), a następnie program przypisuje wartości do jego komponentów. Wcześniej wyjaśniono, że wartości można przypisywać do poszczególnych pól lub w postaci całych rekordów. Używane są do tego kursor lub iterator kursorowej pętli FOR. Wiersz 25. przypisuje kolekcję języka PL/SQL do potoku (PIPE), co prowadzi do przekształcenia kolekcji na zbiór wyników SQL-a. Można go następnie wyświetlić lub wykorzystać za pomocą funkcji TABLE. PIPE to działający w trybie FIFO (ang. First In, First Out) jednokierunkowy translator. Wiersz 28. zwraca wynik działania tego translatora, czyli kolekcję w postaci zbioru wyników SQL-a. Teraz można wywołać funkcję przy użyciu nazwy pakietu, selektora komponentów i nazwy funkcji: SQL> SELECT * 2 FROM TABLE(pipelined.pf);

Rozdział 8. Funkcje i procedury

285

To wywołanie zwróci ze struktury rekordowej następujące wiersze: ACCOUNT ---------B293-71447 B293-71447 ...

FULL_NAME -------------Sweeney, Ian Sweeney, Irving

Pozornie wydaje się, że programiści są ograniczeni do stosowania pakietów, ponieważ to w nich umieszczona jest definicja typu zwracanej wartości — account_table. Choć tablice VARRAY i tabele zagnieżdżone z pakietów nie są bezpośrednio dostępne w słowników danych, można używać ich w innych jednostkach programów języka PL/SQL, ponieważ są niejawnie tworzone w katalogu danych. Zadeklarowanie kolekcji w specyfikacji pakietu sprawia też, że można stosować je w funkcjach niezależnych. Poniższa definicja funkcji niezależnej pokazuje, jak utworzyć przedstawioną wcześniej funkcję potokową pf jako niezależną funkcję z poziomu schematu: SQL> 2 3 4 5 6 7 8 9 16 17 18 19 20 21 22 23 24 25 26 27 28

CREATE OR REPLACE FUNCTION pf RETURN pipelined.account_collection PIPELINED IS /* Deklaracja zmiennej sterującej kolekcji i zmiennej typu kolekcji. */ counter NUMBER := 1; account PIPELINED.ACCOUNT_COLLECTION := pipelined.account_collection(); ... Definicja kursora została pominięta w celu skrócenia kodu ... BEGIN FOR i IN c LOOP /* Przydzielanie pamięci i dodawanie wartości do kolekcji. */ account.EXTEND; account(counter) := i; /* Przypisywanie struktury rekordowej do instrukcji PIPE. */ PIPE ROW(account(counter)); counter := counter + 1; END LOOP; RETURN; END pf; /

Różnica między dwoma przytoczonymi wersjami funkcji polega na sposobie używania typu kolekcji języka PL/SQL. Zauważ, że wierszach 6. i 7. przed typem kolekcji podana jest nazwa pakietu pipelined. Jest to potrzebne w typie danych zmiennej (wiersz 6.) i w wywołaniu konstruktora (wiersz 7.). Aby wywołać funkcję, wystarczy użyć jej nazwy: SQL> SELECT * 2 FROM TABLE(pf);

Funkcji potokowych można używać do tworzenia widoków: SQL> 2 3 4

CREATE SELECT , FROM

OR REPLACE VIEW pipelined_view AS result.account result.full_name TABLE(pf) result;

Widoki oparte na wywołaniach funkcji potokowych wymagają użycia wyzwalaczy zastępujących (INSTEAD OF) do obsługi wstawienia, aktualizowania i usuwania danych. Takie wyzwalacze są niezbędne, jeśli programista chce umożliwić wykonywanie operacji DML. Funkcje potokowe umożliwiają stosowanie zmiennych skalarnych i struktur rekordowych. Przedstawiona wcześniej funkcja potokowa przekształca kolekcję języka PL/SQL na tabelę zagregowaną. Tabel potokowych nie można powtórnie wykorzystać w zasięgu innego programu języka PL/SQL, ale można ich użyć w zasięgu zapytań w języku SQL. W wersjach starszych niż Oracle Database 12c funkcje zwracające tabele potokowe były jedynym sposobem na uzyskanie dostępu do kolekcji języka PL/SQL w instrukcjach SQL-a. Obecnie w instrukcjach SQL-a można używać lokalnych tablic asocjacyjnych języka PL/SQL. Nie ma sensu powtarzanie

286

Część II Programowanie w języku PL/SQL

w tym miejscu całego przykładu z rozdziału 2.; poniższy blok anonimowy pokazuje, jak używać tablic asocjacyjnych języka PL/SQL w zapytaniu: SQL> 2 3 4 5 6 7 8 9 10

DECLARE lv_list TYPE_DEFS.PLSQL_TABLE; BEGIN list := implicit_convert; FOR i IN (SELECT column_value FROM TABLE(lv_list)) LOOP dbms_output.put_line(i.column_value); END LOOP; END; /

Wyniki działania funkcji potokowych są ograniczone do zasięgu programów języka SQL Programista może chcieć przesłać wynik działania funkcji potokowej do innego modułu kodu PL/SQL, jeśli nie wie, że zwracanych tabeli złożonych można używać tylko w instrukcjach SQL-owych. Jednak próba przekazania takich wyników jako argumentu do innego programu języka PL/SQL spowoduje błąd PLS-00653. Informuje on o tym, że w zasięgu programów języka PL/SQL nie można używać funkcji zwracających wartości typów złożonych i tabel takich wartości. Wyniki w postaci tabel potokowych są dostępne tylko w zasięgu programów języka SQL. Kompilacja poniższej procedury zakończy się sukcesem, ponieważ użyto tu poprawnego typu kolekcji języka PL/SQL: SQL> 2 3 4 5 6 7 8 9

CREATE OR REPLACE PROCEDURE read_pipe ( pipe_in pipelined.account_collection ) IS BEGIN FOR i IN 1..pipe_in.LAST LOOP dbms_output.put(pipe_in(i).account); dbms_output.put(pipe_in(i).full_name); END LOOP; END read_pipe; /

Chęć kontrolowania odczytu danych z tabeli potokowej wydaje się naturalna. Poniższe polecenie pokazuje, jak można by wywołać tę procedurę przez przekazanie do niej wyników wywołania funkcji potokowej pf: EXECUTE read_pipe(pf);

Spowoduje to wyświetlenie następującego komunikatu o błędzie: BEGIN read_pipe(pf); END; * ERROR at line 1: ORA-06550: line 1, column 10: PLS-00653: aggregate/table functions are not allowed in PL/SQL scope

Ten błąd pojawił się, ponieważ typ danych wartości przekazanej do procedury to potokowy typ złożony lub tabela o takich wartościach, a nie typ kolekcji języka PL/SQL. Na szczęście ten komunikat o błędzie daje bardzo precyzyjne informacje, jeśli programista wie, że potokowe tabele typów złożonych nie są typem kolekcji języka PL/SQL.

W wierszu 6. używana jest lokalna tablica asocjacyjna lv_list, oparta na typie tablicy asocjacyjnej zdefiniowanym w pakiecie type_defs. Oracle Database 12c potrafi przekształcić tablicę asocjacyjną przy przełączaniu kontekstu między językami PL/SQL i SQL, ponieważ tablica lv_list jest zadeklarowana w bloku lokalnym, w którym wywoływana jest instrukcja SELECT. Nauczyłeś się już używać funkcji potokowych oraz poznałeś ich zalety i wady. Takie funkcje to doskonałe narzędzie do pobierania wartości w zapytaniach i widokach, jeśli proces ten wymaga przetwarzania danych.

Rozdział 8. Funkcje i procedury

287

Funkcje zwracające tabele obiektowe Choć nie istnieje klauzula do tworzenia funkcji zwracających tabele obiektowe, jest to dobre miejsce na omówienie takich funkcji, ponieważ stanowią nową alternatywę wobec funkcji zwracających tabele potokowe. Funkcje zwracające tabele obiektów potrafią przekształcić kolekcję SQL-a na zbiór wyników tego języka. Dzięki temu prawie nigdy nie trzeba stosować tablic asocjacyjnych języka PL/SQL (chyba że programista chce utworzyć indeks rzadki oparty na łańcuchach znaków). Tabele zagnieżdżone SQL-a są równie wydajne jak tablice asocjacyjne. Jednocześnie dają więcej możliwości przy korzystaniu z nich w innych językach programowania, na przykład w Javie. Tworzenie funkcji zwracającej tabele obiektowe to proces trzyetapowy: należy zdefiniować strukturę rekordową jako typ obiektowy, zdefiniować kolekcję tego typu, a następnie zdefiniować funkcję, która zwraca tę kolekcję z kontekstu języka PL/SQL do kontekstu SQL-a. Ostatecznie wystarczy wywoływać zapytania do modelu w instrukcji SQL-a. W efekcie powstają listy i tablice typów obiektowych SQL-a, które można wykorzystać w zewnętrznych językach programowania (takich jak C#, C++, Java i PHP). Najpierw utwórz bazowy typ UDT SQL-a: SQL> CREATE OR REPLACE TYPE title_structure IS OBJECT 2 ( title varchar2(60) 3 , subtitle varchar2(60)); 4 /

Kolekcją może być tu tablica VARRAY lub tabela zagnieżdżona. Tabele zagnieżdżone dają więcej możliwości, ponieważ nie mają ograniczonej liczby elementów. Utwórzmy więc SQL-ową tabelę zagnieżdżoną elementów typu obiektowego: SQL> CREATE OR REPLACE 2 TYPE title_table IS TABLE OF title_structure; 3 /

Poniższa funkcja jest prosta, ale czytelna i krótka (przyjmuje o jeden parametr wejściowy mniej niż przedstawiony wcześniej blok anonimowy). Oczywiście gdy będziesz pisał kod rzeczywistej aplikacji, będzie on bardziej skomplikowany, ponieważ zadania z tego przykładu można wykonać za pomocą zwykłego zapytania. SQL> CREATE OR REPLACE FUNCTION get_full_titles 2 ( pv_title VARCHAR2 ) RETURN TITLE_TABLE IS 3 4 -- Deklaracja zmiennej licznika. 5 lv_counter PLS_INTEGER := 1; 6 7 -- Deklaracja typu kolekcji. 8 lv_title_table TITLE_TABLE := title_table(); 9 10 -- Deklaracja kursora dynamicznego. 11 CURSOR c ( cv_search VARCHAR2 ) IS 12 SELECT item_title, item_subtitle 13 FROM item 14 WHERE REGEXP_LIKE( item_title 15 , '^.*'||cv_search||'.*','i') 16 AND item_type = 17 (SELECT common_lookup_id 18 FROM common_lookup 19 WHERE common_lookup_type = 20 'DVD_WIDE_SCREEN') 21 ORDER BY release_date; 22 23 BEGIN 24 -- Otwieranie kursora i wiązanie wyników z kolekcją. 25 FOR i IN c (pv_title) LOOP 26 lv_title_table.EXTEND; -- Zajmowanie dodatkowej pamięci. 27

288 28 29 30 31 32 33 34 35 36 37 38

Część II Programowanie w języku PL/SQL /* Elementy kolekcji SQL-a są niezgodne z typem wartości zwracanych przez kursor, dlatego trzeba utworzyć egzemplarz typu obiektowego przed przypisaniem go do kolekcji. */ lv_title_table(lv_counter) := title_structure(i.item_title,i.item_subtitle); lv_counter := lv_counter + 1; -- Zwiększanie wartości licznika. END LOOP; RETURN lv_title_table; END; /

Wiersz 8. deklaruje zmienną typu kolekcji i tworzy egzemplarz tego typu (początkowo jest to kolekcja o wartości null). W pętli FOR w wierszu 26. zajmowana jest pamięć na nowy element kolekcji. Wiersze 32. i 33. przypisują egzemplarz typu title_structure do elementu kolekcji o określonym indeksie. Ważne jest to, że ta operacja przypisania wymaga jawnego utworzenia egzemplarza tego typu w wyniku przekazania parametrów odpowiednich typów. Teraz można pobrać wyniki w następujący sposób: SQL> SELECT title 2 FROM TABLE(get_full_titles('Harry'));

Jako nazwa kolumny używana jest nie nazwa kolumny z samej tabeli, ale nazwa elementu z typu obiektowego SQL-a. Ta technika działa inaczej niż pseudokolumna column_value w kolekcjach ADT opisanych w rozdziale 6. Teraz już nie wydaje się, że filmy o Harrym Potterze są niedostępne. Oto wyniki tego zapytania: TITLE -------------------------------------------Harry Potter and the Sorcerer's Stone Harry Potter and the Chamber of Secrets ...

Zmienne złożone są niezwykle przydatne w językach PL/SQL i SQL. Pozwalają definiować skomplikowany kod w blokach nazwanych, a następnie wywoływać go w programach w językach C#, Java i PHP. Tam, gdzie to możliwe, należy korzystać z zalet zmiennych złożonych.

Klauzula RESULT_CACHE Klauzulę RESULT_CACHE wprowadzono w wersji Oracle Database 11g. W wersji Oracle Database 12c możliwości funkcji zapisujących wyniki w pamięci podręcznej rozbudowano o obsługę programów z uprawnieniami jednostki wywołującej. Takie funkcje zapisują swoją nazwę, parametry wywołania, wyniki i wartość wywołania CURRENT_USER w obszarze SGA. Zapisywanie wartości wywołania CURRENT_ USER jest możliwe od wersji Oracle Database 12c. Przy użyciu tej wartości baza przechowuje różne zbiory wyników dla różnych jednostek wywołujących tę samą funkcję. Klauzula RESULT_CACHE informuje silnik języka PL/SQL o tym, że powinien wyszukiwać w pamięci podręcznej wywołania funkcji o argumentach zgodnych z aktualnym wywołaniem. Ponieważ wyniki wywołań są rejestrowane, można pobrać je z pamięci podręcznej i uniknąć konieczności ponownego wywoływania funkcji. Oznacza to, że program uruchamia funkcję tylko wtedy, gdy otrzyma nowe parametry. W funkcjach międzysesyjnych można używać tylko parametrów w trybie IN.

Prototyp klauzuli RESULT_CACHE obejmuje opcjonalną klauzulę RELIES_ON. Ta ostatnia klauzula jest bardzo ważna, ponieważ powoduje, że zmiany w używanej tabeli prowadzą do utraty ważności zapisanych wyników. Podobny skutek ma wywołanie transakcji DML, które mogłyby zmienić zbiór wyników. Klauzula RELIES_ON gwarantuje, że pamięć podręczna działa dynamicznie i odzwierciedla aktualne dane. W klauzuli RELIES_ON można podać dowolną liczbę tabel zależnych. W tym celu należy rozdzielić je przecinkami.

Rozdział 8. Funkcje i procedury

289

Następny przykład wymaga wcześniejszego uruchomienia skryptów, które można pobrać z witryny wydawnictwa Helion. Opis tego kodu znajduje się we wprowadzeniu. Przykładowy kod używa też kolekcji, które opisano w rozdziale 6. Poniższa instrukcja tworzy kolekcję wartości typu VARCHAR2: SQL> CREATE OR REPLACE 2 TYPE strings AS TABLE OF VARCHAR2(60); 3 /

Wyniki działania poniższej funkcji są zapisywane w pamięci podręcznej dostępnej wielu sesjom. W tym podprogramie użyto klauzuli RELIES_ON: SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

CREATE OR REPLACE FUNCTION get_title ( pv_partial_title VARCHAR2 ) RETURN STRINGS RESULT_CACHE RELIES_ON(item) IS /* Deklaracja zmiennej sterującej kolekcji i zmiennej typu kolekcji. */ counter NUMBER := 1; return_value STRINGS := strings(); -- Definicja kursora z parametrami. CURSOR get_title ( cv_partial_title VARCHAR2 ) IS SELECT item_title FROM item WHERE UPPER(item_title) LIKE '%'||UPPER(cv_partial_title)||'%'; BEGIN -- Odczyt danych i przypisywanie ich do kolekcji w pętli FOR opartej na kursorze. FOR i IN get_title(pv_partial_title) LOOP return_value.EXTEND; return_value(counter) := i.item_title; counter := counter + 1; END LOOP; RETURN return_value; END get_title; /

Wiersz 3. informuje, że funkcja get_title zapisuje wyniki w pamięci podręcznej. Jeśli funkcja jest deterministyczna, te wyniki nie zmieniają się między wywołaniami. Mogą się jednak zmieniać, jeśli funkcja jest niedeterministyczna. Wszystkie funkcje korzystające z danych z tabel są niedeterministyczne. Przy korzystaniu z takich funkcji należy stosować klauzulę RELIES_ON. Wartości z pamięci podręcznej są unieważniane w efekcie wprowadzenia zmian w dowolnej z tabel z listy z klauzuli RELIES_ON. W tym przykładzie Oracle unieważnia wyniki z pamięci podręcznej po wywołaniu instrukcji INSERT, UPDATE lub DELETE na tabeli item. Choć możliwe jest, że zmiany w niektórych tabelach nie uzasadniają unieważniania wyników z pamięci podręcznej, należy standardowo podawać w klauzuli RELIES_ON wszystkie tabele używane w funkcji. Ta klauzula gwarantuje zgodność zbioru wyników ze zmianami w źródle danych tylko wtedy, gdy obejmuje wszystkie używane tabele. W klauzuli RELIES_ON można podać jeden argument lub listę argumentów.

Aby przetestować funkcję get_title, można użyć poniższego bloku anonimowego: SQL> 2 3 4 5 6 7 8 9

DECLARE lv_list STRINGS; BEGIN lv_list := get_title('Harry'); FOR i IN 1..lv_list.LAST LOOP dbms_output.put_line('lv_list('||i||') : ['||lv_list(i)||']'); END LOOP; END; /

290

Część II Programowanie w języku PL/SQL

Tak jak we wcześniejszych przykładach ilustrujących stosowanie funkcji zwracających tabele potokowe, tak i tu można przetestować funkcję get_title w zapytaniu: SQL> SELECT column_value 2 FROM TABLE(get_title('Harry'));

Po wywołaniu funkcji, której wynik działania zapisywany jest w pamięci podręcznej, można wstawić, usunąć lub zaktualizować powiązane dane. Spowoduje to, że program wyświetli nowy zbiór wyników. Ta zmiana gwarantuje, że przestarzałe dane nigdy nie zmylą użytkowników. Klauzula RELIES_ON zapewnia integralność zbioru wyników, zwiększa jednak nieco koszty przetwarzania. Podczas projektowania kodu obsługującego magazyny danych warto zastanowić się nad pominięciem klauzuli RELIES_ON, aby zwiększyć wydajność przeprowadzania transakcji.

W ostatnich podpunktach omówiono opcje używane przy definiowaniu funkcji. Czytelnik powinien opanować ten materiał przed zapoznaniem się z opisem funkcji o parametrach przekazywanych przez wartość.

Funkcje o parametrach przekazywanych przez wartość Funkcje o parametrach przekazywanych przez wartość przyjmują w momencie wywołania kopie wartości. Po zakończeniu działania zwracają pojedynczą zmienną wyjściową. Skomplikowanym aspektem stosowania takich funkcji są typy danych wejściowych i wyjściowych. Dane wejściowe to parametry formalne, które w programach o parametrach przekazywanych przez wartość mogą mieć tylko jeden tryb — IN. To oznacza, że do funkcji przekazywane są nieprzetworzone dane wejściowe w postaci kopii wartości zmiennej lub literału. Te kopie to argumenty wywołania. Wszystkie nieprzetworzone materiały (argumenty) są zużywane w trakcie wytwarzania gotowych produktów — wartości zwracanych przez funkcje tego rodzaju. Wartość zwracaną przez funkcję trzeba przypisać do zmiennej w bloku języka PL/SQL lub zwrócić jako wyrażenie w zapytaniu SQL-a. Funkcje tego rodzaju zwracają pojedynczą zmienną wyjściową. Może to być wartość skalarna lub złożona. Oznacza to, że jeśli zwracana zmienna ma typ złożony, może zawierać wiele elementów. Funkcje o parametrach przekazywanych przez wartość można zdefiniować jako deterministyczne lub równoległe, jeśli nie zmieniają wartości zmiennych pakietu ani bazy danych. Można też zdefiniować funkcję tak, aby zwracała tabele potokowe odpowiadające kolekcjom języka SQL lub PL/SQL. Z uwagi na wyniki zwracane przez funkcje potokowe można ich używać tylko w zasięgu instrukcji języka SQL. Wyniki działania wszystkich funkcji oprócz tych zwracających dane potokowe można zapisać w pamięci podręcznej. Współdziałanie funkcji z systemem plików lub bazą danych nie ma wpływu na ich działanie w blokach PL/SQL. Funkcji można użyć do przypisania wyniku do zmiennej lub zwrócenia zmiennej w wyrażeniu. Rysunek 8.1 we wcześniejszej części rozdziału ilustruje użycie funkcji jako prawego operandu w operacji przypisania. Przykładowa funkcja hello_whom, odmiana klasycznej funkcji hello_world, ilustruje, jak wykorzystać funkcję o parametrach przekazywanych przez wartość: SQL> 2 3 4 5 6 7 8 9 10 11 12 13

CREATE OR REPLACE FUNCTION hello_whom ( pv_name IN VARCHAR2 ) RETURN VARCHAR2 IS /* Domyślne „imię”. */ lv_name VARCHAR2(10) := 'świecie'; BEGIN /* Sprawdzanie wprowadzonego imienia i ustawianie go jako właściwej wartości. */ IF pv_name IS NOT NULL THEN lv_name := pv_name; END IF; /* Zwracanie powitania. */ RETURN 'Witaj, '||lv_name||'!'; END; /

Rozdział 8. Funkcje i procedury

291

Gdy wywołasz funkcję hallo_whom w zapytaniu: SQL> SELECT hello_whom('Janek') AS "Powitanie" 2 FROM dual;

kod wyświetli następujące informacje: Powitanie ------------Witaj, Janek!

W wierszu 2. jawnie ustawiany jest tryb IN parametru przekazywanego przez wartość. Jest to tryb domyślny, stosowany, gdy programista nie określi żadnego trybu. Dlatego nawet jeśli pominiesz ten tryb w sygnaturze funkcji, parametr i tak będzie przekazywany przez wartość. Poniższa wersja wiersza 2. działa tak samo jak przedstawiony wcześniej kod: 2 ( pv_name VARCHAR2 ) RETURN VARCHAR2 IS

Określanie trybu zwiększa przejrzystość funkcji, jednak większość programistów go pomija. Niedoświadczeni programiści czasem odwracają logikę w bloku IF i sprawdzają, czy parametr jest równy null, a następnie próbują przypisać do niego zmienną lokalną: 7 8

IF pv_name IS NULL THEN pv_name := lv_name;

To doprowadzi do wyświetlenia następującego komunikatu: Errors for FUNCTION HELLO_WHOM: LINE/COL ERROR ---- --------------------------------------------------------------8/5 PL/SQL: Statement ignored 8/5 PLS-00363: expression 'PV_NAME' cannot be used as an assignment target

Funkcję zwracającą zmienną można stosować jako wyrażenie w wywołaniach wbudowanych funkcji języka PL/SQL. Oto przykład: SQL> EXECUTE dbms_output.put_line(TO_CHAR(pv(10000,5,6),'9,999.90'));

W tym przykładzie najbardziej wewnętrzna funkcja (o parametrach przekazywanych przez wartość) jest uruchamiana jako pierwsza. To oznacza, że wywołanie funkcji pv zwraca wartość używaną jako parametr wywołania funkcji wbudowanej TO_CHAR języka SQL. Ta funkcja formatuje wartość pierwszego parametru zgodnie z maską formatu podaną jako drugi parametr. Jeśli zmienna SERVEROUTPUT jest włączona, powyższy kod wyświetli następujące dane wyjściowe: 7,472.58

W tym przykładzie użyto funkcji pv przedstawionej w podpunkcie „Klauzula DETERMINISTIC” we wcześniejszej części rozdziału, a także funkcji wbudowanej TO_CHAR (jej opis znalazł się w dodatku D, poświęconym funkcjom wbudowanym SQL-a). Wcześniej wspomniano, że w Oracle Database 12c wprowadzono mechanizm tworzenia białych list. Poniższy kod pokazuje, jak utworzyć białą listę dla funkcji hello_whom: SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14

CREATE OR REPLACE FUNCTION hello_whom ( pv_name IN VARCHAR2 ) RETURN VARCHAR2 ACCESSIBLE BY ( FUNCTION video.gateway , PROCEDURE video.backdoor , PACKAGE video.api , TYPE video.hobbit ) IS /* Domyślne „imię”. */ lv_name VARCHAR2(10) := 'świecie'; BEGIN /* Sprawdzanie wprowadzonego imienia i ustawianie go jako właściwej wartości. */ IF pv_name IS NOT NULL THEN lv_name := pv_name; END IF;

292 15 16 17 18

Część II Programowanie w języku PL/SQL /* Zwracanie powitania. */ RETURN 'Witaj, '||lv_name||'!'; END; /

Wiersze od 3. do 7. określają listę jednostek uprawnionych do wywoływania tej funkcji. Jest to tak zwana biała lista. Umożliwia ona określonym funkcjom, procedurom, pakietom i typom obiektowym wykonywanie określonego zadania. Jest to przeciwieństwo czarnej listy, która blokuje dostęp podanym jednostkom. Po utworzeniu białej listy dla funkcji hello_whom możesz spróbować wywołać ją w zapytaniu: SQL> SELECT hello_whom('Janek') AS "Powitanie" 2 FROM dual;

Ten kod zgłosi następujący wyjątek: SELECT hello_whom('Janek') AS "Powitanie" * ERROR at line 1: ORA-06552: PL/SQL: Statement ignored ORA-06553: PLS-904: insufficient privilege to access object HELLO_WHOM

Wydaje się, że obecnie w bazie Oracle Database 12c nie istnieje sposób na przyznanie potrzebnych uprawnień zapytaniom ze schematu. To oznacza, że wywołanie funkcji hello_world trzeba umieścić w jednym z uprawnionych do tego modułów, na przykład w funkcji gateway: SQL> 2 3 4 5 6 7

CREATE OR REPLACE FUNCTION gateway ( pv_name IN VARCHAR2 ) RETURN VARCHAR2 IS BEGIN /* Zwracanie powitania. */ RETURN hello_whom(pv_name); END; /

Jeśli teraz wywołasz mającą potrzebne uprawnienia funkcję gateway w zapytaniu: SQL> SELECT gateway('Baśka') AS "Powitanie" 2 FROM dual;

wywoła ona w wierszu 5. funkcję z białą listą (hello_whom) i zwróci następujący wynik: Powitanie ------------Witaj, Baśka!

W definicjach funkcji języka PL/SQL o parametrach przekazywanych przez wartość obowiązuje sześć reguł:  Wszystkie parametry formalne muszą być przeznaczone tylko do zapisu, czyli mieć tryb IN.  Wszystkie parametry formalne muszą być zmiennymi o zasięgu lokalnym i nie można ich modyfikować w czasie wykonywania funkcji.  Parametry formalne mogą mieć dowolny typ języka SQL lub PL/SQL. W instrukcjach w języku SQL można używać tylko funkcji, które mają parametry o typach języka SQL.  Parametry formalne mogą mieć domyślne wartości początkowe.  Zwracana zmienna może mieć dowolny typ danych języka SQL lub PL/SQL, przy czym zwracanych tabel potokowych można używać tylko w instrukcjach w języku SQL. Nie można uzyskać dostępu do takich tabel w zasięgu innego programu w języku PL/SQL.  Systemowych kursorów referencyjnych rzutowanych z zapytań języka SQL na funkcje nie można zapisywać, dlatego trzeba je przekazywać przy użyciu parametrów w trybie IN.

Systemowe kursory referencyjne Wszystkie wyniki w kursorach to statyczne struktury przechowywane w obszarze SGA bazy danych Oracle. Zmienne kursorowe to w rzeczywistości referencje (uchwyty do danych) wskazujące wewnętrz-

Rozdział 8. Funkcje i procedury

293

nie przechowywany zbiór wyników przetworzenia zapytania. Zmienne kursorowe można zapełnić przez pobranie rekordów: OPEN nazwa_kursora FOR instrukcja_select;

Aby uzyskać dostęp do kursorów, należy użyć referencji (uchwytu), która umożliwia poruszanie się po danych. Po zadeklarowaniu kursora jawnego lub niejawnego można przypisać wskazującą na niego referencję do kursorowego typu danych języka SQL. Można też zwrócić zmienne kursorowe jako wartość zwracaną przez funkcję lub jako zmienne referencyjne w trybie IN lub IN OUT określone w sygnaturach funkcji i procedur. Zbiory wyników to struktury tylko do odczytu. Poniższy fragment pokazuje, jak zwrócić kursor przy użyciu funkcji: SQL> 2 3 4 5 6 7 8 9 10

CREATE OR REPLACE FUNCTION get_full_titles RETURN SYS_REFCURSOR IS lv_title_cursor SYS_REFCURSOR; BEGIN OPEN lv_title_cursor FOR SELECT item_title, item_subtitle FROM item; RETURN lv_title_cursor; END; /

W tej funkcji użyto wbudowanego typu SYS_REFCURSOR. Jest to systemowy kursor referencyjny o słabej kontroli typu. W kursorach tego rodzaju struktura rekordu jest określana w czasie wykonywania programu, podczas gdy kursory referencyjne o ścisłej kontroli typu są zakotwiczone do typu obiektu z katalogu bazy danych. Klauzula OPEN tworzy w obszarze SGA referencję wskazującą na kursor. Następnie można przekazać tę referencję do innego bloku języka PL/SQL jako zmienną kursorową, co przedstawia poniższy blok anonimowy: SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

DECLARE /* Definicja typu i deklaracja zmiennej. */ TYPE full_title_record IS RECORD ( item_title item.item_title%TYPE , item_subtitle item.item_subtitle%TYPE); lv_full_title_table FULL_TITLE_RECORD; /* Deklaracja zmiennej typu systemowego kursora referencyjnego. */ lv_title_cursor SYS_REFCURSOR; BEGIN /* Przypisanie wyniku działania funkcji do kursora referencyjnego. */ lv_title_cursor := get_full_titles; /* Wyświetlenie elementów jeden po drugim. */ LOOP FETCH lv_title_cursor INTO lv_full_title_table; EXIT WHEN lv_title_cursor%NOTFOUND; dbms_output.put_line('Tytuł ['||lv_full_title_table.item_title||']'); END LOOP; END; /

Jeśli program przekazuje kursor do podprogramu, nie należy używać instrukcji OPEN przed pętlą, ponieważ kursor jest już otwarty. Zmienne kursorowe to referencje, które wskazują na specjalny kursorowy obszar roboczy w pamięci SGA.

Blok pobierający i przetwarzający kursor musi mieć informacje o tym, jaki typ rekordowy jest zapisany w kursorze. Niektórzy programiści z powodu tego ograniczenia twierdzą, że należy używać tylko kursorów referencyjnych o ścisłej kontroli typów. W rozwiązaniach opartych tylko na języku PL/SQL rzeczywiście jest to uzasadnione. Z drugiej strony kursory referencyjne o słabej kontroli typów mogą być potrzebne, jeśli programista kieruje do nich zapytania z poziomu programów zewnętrznych przy użyciu bibliotek OCI. W językach zewnętrznych można dynamicznie sprawdzić strukturę kursorów referencyjnych i zarządzać nimi przy użyciu ogólnych algorytmów.

294

Część II Programowanie w języku PL/SQL

Deterministyczne funkcje o parametrach przekazywanych przez wartość Poniższa deterministyczna funkcja o parametrach przekazywanych przez wartość oblicza przyszłą wartość depozytów bankowych. Jest to funkcja fv: SQL> 2 3 4 5 6 7 8 9 10

CREATE OR REPLACE FUNCTION fv ( current_value NUMBER := 0 , periods NUMBER := 1 , interest NUMBER) RETURN NUMBER DETERMINISTIC IS BEGIN /* Odsetki przy kapitalizacji dziennej. */ RETURN current_value*(1+((1+((interest/100)/365))**365-1)*periods); END fv; /

Ta funkcja ma trzy parametry formalne. Dwa z nich są opcjonalne, ponieważ mają wartość domyślną. Określają one obecny stan konta i długość roku nieprzestępnego, czyli 365 dni. Trzeci parametr jest wymagany, ponieważ nie ma podanej wartości. Domyślnie tryb parametrów to IN i nie trzeba go podawać w definicjach funkcji. Zgodnie z ogólną praktyką parametry wymagane należy umieszczać przed opcjonalnymi. Jest to niezwykle istotne, jeśli programista przy przekazywaniu argumentów używa notacji opartej na pozycji. Oracle Database 11g obsługuje notację opartą na pozycji, na nazwie i mieszaną. Po zdefiniowaniu zmiennej wyjściowej można użyć instrukcji CALL do uruchomienia funkcji przy użyciu notacji opartej na nazwie: SQL> SQL> 2 3

VARIABLE future_value NUMBER CALL fv(current_value => 10000, periods => 5, interest => 4) INTO :future_value /

Następnie można sprawdzić wartość 10000 złotych po 5 latach przy odsetkach na poziomie 4% i kapitalizacji dziennej: SQL> SELECT :future_value FROM dual;

Można też sformatować dane przy użyciu środowiska SQL*Plus i wywołać funkcję w języku SQL za pomocą poniższej instrukcji: SQL> COLUMN future_value FORMAT 99,999.90 SQL> SELECT fv(current_value => 10000, periods => 5, interest => 4) FROM dual;

Zarówno instrukcja CALL, jak i zapytanie w języku SQL zwrócą wynik 12,040.42. Kapitalizacja odsetek daje dodatkowe 40,42 złotego ponad roczne oprocentowanie. Możliwe, że będzie to nawet grosz lub dwa więcej, zależy to jednak od tego, które z pięciu lat są przestępne, a funkcja nie uwzględnia w obliczeniach takich szczegółów. W rozdziale 2. opisano, że baza Oracle Database 12c umożliwia zagnieżdżanie funkcji w klauzuli WITH instrukcji SELECT. Wymaga to wcześniejszego zablokowania symbolu kończącego instrukcje SQL-a, którym domyślnie jest średnik (;). To oznacza, że instrukcję SELECT trzeba uruchomić w odpowiednio dostosowanym środowisku SQL*Plus. Lepsza technika polega na utworzeniu w sesji tego środowiska widoku, z którego inne programy mogą korzystać bez stosowania się do specjalnych reguł. Przedstawione poniżej deterministyczna funkcja glue i widok person pojawiły się już w rozdziale 2. Na ich podstawie można pokazać, jak zagnieździć funkcję deterministyczną w klauzuli WITH instrukcji SELECT. SQL> WITH 2 FUNCTION glue 3 ( pv_first_name VARCHAR2 4 , pv_last_name VARCHAR2) RETURN VARCHAR2 IS 5 lv_full_name VARCHAR2(100); 6 BEGIN 7 lv_full_name := pv_first_name || ' ' || pv_last_name;

Rozdział 8. Funkcje i procedury 8 9 10 11 12

295

RETURN lv_full_name; END; SELECT glue(a.first_name,a.last_name) AS person FROM actor a /

Funkcja z wierszy od 2. do 9. złącza dwa łańcuchy znaków i umieszcza między nimi spację. Jeśli programista wyłączył opcję SQLTERMINATOR w środowisku SQL*Plus, średniki są traktowane w zapytaniach jak zwykłe znaki. Warto zauważyć, że instrukcje SQL-a są uruchamiane w środowisku SQL*Plus za pomocą ukośnika, a kompletna instrukcja nie ma kończącego średnika (wiersz 11.). Funkcje o parametrach przekazywanych przez wartość opakowujące biblioteki Javy Kod składowanych funkcji i procedur z logiką programu można zapisać w bibliotekach Javy. Wtedy aby uzyskać dostęp do takich bibliotek, należy napisać nakładkę w języku PL/SQL. Poniższa biblioteka TwoSignersJava sprawdza, czy do konta przypisane są już dwie osoby upoważnione do składania podpisów. Jeśli można dodać drugą taką osobę, zwracana jest wartość 0 (oznaczająca false). Gdy są już dwie takie osoby, zwracana wartość to 1 (oznacza ona true). Tę bibliotekę można utworzyć z poziomu wiersza poleceń środowiska SQL*Plus: SQL> CREATE OR REPLACE AND COMPILE JAVA SOURCE NAMED "TwoSignersJava" AS 2 3 // Wymagane biblioteki klas. 4 import java.sql.*; 5 import oracle.jdbc.driver.*; 6 7 // Definicja klasy. 8 public class TwoSignersJava { 9 10 // Nawiązanie połączenia i sprawdzanie, czy wstawiane dane nie będą się duplikować. 11 public static int contactTrigger(Integer memberID) 12 throws SQLException { 13 Boolean duplicateFound = false; // Kontrolna wartość domyślna. 14 15 // Tworzenie połączenia (od wersji Java 5 i Oracle 11g). 16 Connection conn = 17 DriverManager.getConnection("jdbc:default:connection:"); 18 19 // Tworzenie przygotowanej instrukcji umożliwiającej powiązanie liczby. 20 PreparedStatement ps = 21 conn.prepareStatement("SELECT null " + 22 "FROM contact c JOIN member m " + 23 "ON c.member_id = m.member_id " + 24 "WHERE c.member_id = ? " + 25 "HAVING COUNT(*) > 1"); 26 27 // Wiązanie zmiennej lokalnej z miejscem na dane w instrukcji. 28 ps.setInt(1, memberID); 29 30 // Wykonywanie zapytania i sprawdzanie, czy istnieje druga wartość. 31 ResultSet rs = ps.executeQuery(); 32 if (rs.next()) 33 duplicateFound = true; // Zmiana wartości kontrolnej. 34 35 // Porządkowanie zasobów. 36 rs.close(); 37 ps.close(); 38 conn.close(); 39 40 /* Zwraca 1 (true), jeśli dostępne są dwie osoby, i 0 w przeciwnym razie. */ 41 if (duplicateFound) return 1; 42 else return 0; }} 43 /

296

Część II Programowanie w języku PL/SQL

Wiersze 16. i 17. normalnie znalazłyby się w jednym wierszu, jednak tu są podzielone z powodu ograniczonej szerokości strony. Wewnętrzna składnia do tworzenia połączeń (wiersz 17.) jest dostępna od Javy 5. Przy korzystaniu z bazy Oracle Database 10g (technicznie ta wersja nie jest już obsługiwana po pojawieniu się produkcyjnej wersji bazy Oracle Database 12c) należy zastąpić ten fragment poniższym kodem. Przy przenoszeniu bibliotek Javy z bazy Oracle Database 10g do wersji Oracle Database 12g lub 12c należy zmodyfikować stosowaną w produktach Oracle wewnętrzną składnię do tworzenia połączeń. 15 16

// Tworzenie połączenia JDBC w Oracle 10g. Connection conn = new OracleDriver().defaultConnection();

Oto napisana w języku PL/SQL nakładka na tę bibliotekę: SQL> 2 3 4 5

CREATE OR REPLACE FUNCTION two_signers ( pv_member_id NUMBER) RETURN NUMBER IS LANGUAGE JAVA NAME 'TwoSingersJava.contactTrigger(java.lang.Integer) return int'; /

Wiersz 3. oznacza, że ciało funkcji two_signers jest napisane w Javie. Wiersz 4. wiąże typy danych Javy z natywnymi typami Oracle. Poniższy fragment pokazuje, jak przetestować wszystkie elementy: SQL> 2 3 4 5 6 7

SELECT CASE WHEN two_signers(member_id) = 0 THEN 'Jedna jednostka podpisująca.' ELSE 'Dwie jednostki podpisujące.' END AS "Available for Assignment" FROM contact c JOIN member m USING (member_id) WHERE c.last_name = 'Sweeney' OFFSET 1 ROWS FETCH FIRST 1 ROWS ONLY;

Jednak częściej funkcja two_signers wywoływana będzie w klauzuli WHERE. Można to zrobić na przykład wtedy, gdy kod produkcyjny ma wstawiać dane z tabel contact i member, a klauzula WHERE ma sprawdzać, czy funkcja two_signers zwróciła wartość 0.

Niestety, nie można wyłączyć opcji SQLTERMINATOR przy wywoływaniu instrukcji w innych językach programowania. To oznacza, że każde zapytanie z funkcją zagnieżdżoną w klauzuli WITH trzeba umieścić w widoku. Załóżmy, że istnieje widok actor_v utworzony za pomocą przedstawionego wcześniej zapytania. Można wtedy pobrać wyniki w następujący sposób: SQL> COLUMN person FORMAT A18 SQL> SELECT a.person 2 FROM actor_v;

W efekcie pobrane zostaną następujące wyniki: PERSON -----------------Nicolas Cage Diane Kruger

Zagnieżdżanie deterministycznych funkcji w widokach jest dobrym podejściem, gdy dana aplikacja wymaga zastosowania takiej funkcji w zapytaniu. W końcu widoki służą do tworzenia abstrakcji i ukrywania logiki przed programistami aplikacji.

Niedeterministyczne funkcje o parametrach przekazywanych przez wartość Główna różnica między funkcjami niedeterministycznymi i deterministycznymi jest prosta — te pierwsze są oparte na parametrach wejściowych i danych, natomiast te drugie wykorzystują tylko parametry wejściowe. To oznacza, że funkcja niedeterministyczna dla tych samych parametrów może zwracać różne wyniki. W funkcjach deterministycznych nie jest to oczywiście możliwe.

Rozdział 8. Funkcje i procedury

297

W Oracle Database 12c można napisać funkcję niedeterministyczną na przykład za pomocą kursora, który szuka fragmentów łańcuchów znaków w imionach i nazwiskach. Tak działa poniższa funkcja full_name: SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

CREATE OR REPLACE FUNCTION full_name ( pv_search_name VARCHAR2) RETURN VARCHAR2 IS /* Deklaracja zwracanej zmiennej lokalnej. */ lv_retval VARCHAR2(50); /* Deklaracja kursora dynamicznego. */ CURSOR get_names ( cv_search_name VARCHAR2) IS SELECT c.first_name, c.middle_name, c.last_name FROM contact c WHERE REGEXP_LIKE(c.first_name, cv_search_name,'i') OR REGEXP_LIKE(c.last_name, cv_search_name,'i') OFFSET 1 ROWS FETCH FIRST 1 ROWS ONLY; -- Nowość w Oracle 12c. BEGIN /* Sprawdzanie drugiego imienia. */ FOR i IN get_names('^.*'||pv_search_name||'.*$') LOOP IF i.middle_name IS NOT NULL THEN lv_retval := i.first_name||' '||i.middle_name||' '||i.last_name; ELSE lv_retval := i.first_name||' '||i.last_name; END IF; END LOOP; END; /

Parametr wywołania wpływa na wyniki pobierane z tabeli z danymi. Funkcja zwraca tylko jeden wiersz, ponieważ w zapytaniu zastosowano wprowadzone w bazie Oracle Database 12c ograniczenie pierwszych n wierszy. Wiersz 12. gwarantuje, że funkcja zwróci tylko pierwszy wiersz, w którym imię i nazwisko pasują do kryteriów wyszukiwania. Wynik zmienia się po zmianie parametru, a także po zmodyfikowaniu danych w tabelach. Każdy znak spomiędzy wyrażeń regularnych ('^.*') i ('.*$') jest używany w dwóch miejscach kursora dynamicznego, ale zostaje przekazany tylko raz w parametrze wywołania kursora.

Ważną cechą funkcji niedeterministycznych jest to, że używają dwóch zmiennych rodzajów danych wyjściowych. Jednym są parametry wywołania, a drugim — dane przechowywane w bazie.  Parametr wywołania nie może się zmienić w trakcie wykonywania funkcji. To oznacza, że do parametrów formalnych nie można przypisywać danych w funkcjach. Próba takiego przypisania prowadzi do zgłoszenia błędu PLS-00363, informującego, że do wyrażenia (parametru formalnego) nie można przypisywać danych.  Funkcje niedeterministyczne mogą zwracać inne wyniki dla tych samych parametrów, ponieważ użytkownik między kolejnymi wywołaniami funkcji może zmodyfikować przechowywane w bazie dane.

Funkcje o parametrach przekazywanych przez wartość zgodne z instrukcjami DML W funkcjach można przetwarzać instrukcje DML. Niektórzy programiści uważają, że nie należy tego robić, ponieważ kiedyś służyły do tego procedury. Jedyną wadą umieszczania instrukcji DML w funkcjach jest to, że takich funkcji nie można wywoływać w zapytaniach — a przynajmniej nie można tego zrobić w tym samym kontekście operacyjnym, chyba że używana jest funkcja autonomiczna. Próba wywołania wewnątrzwierszowej (domyślnej) funkcji prowadzi do zgłoszenia błędu ORA-14551. Powiązany z nim komunikat o błędzie informuje, że w zapytaniach nie można umieszczać instrukcji DML. W omówieniu architektury funkcji i procedur wyjaśniono, że funkcje pesymistyczne informują o sukcesie lub niepowodzeniu za pomocą odpowiednich wartości. W zasięgu języka PL/SQL można

298

Część II Programowanie w języku PL/SQL

napisać funkcję pesymistyczną zwracającą wartości typu logicznego. Jeśli funkcja pesymistyczna jest uruchamiana w autonomicznym kontekście, musi też obejmować instrukcje TCL. Utwórzmy prostą tabelę avatar, aby zilustrować działanie funkcji pesymistycznych: SQL> CREATE TABLE avatar 2 ( avatar_id NUMBER GENERATED AS IDENTITY 3 , avatar_name VARCHAR2(30));

Poniższy kod przedstawia funkcję pesymistyczną wstawiającą (gdy jej działanie zakończy się powodzeniem) wiersz do tabeli avatar: SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

CREATE OR REPLACE FUNCTION add_avatar ( pv_avatar_name VARCHAR2 ) RETURN BOOLEAN IS /* Ustawianie funkcji tak, aby działała we własnym zasięgu transakcji. */ PRAGMA AUTONOMOUS_TRANSACTION; /* Ustawianie domyślnej wartości zwracanej. */ lv_retval BOOLEAN := FALSE; BEGIN /* Wstawianie wiersza do tabeli avatar. */ INSERT INTO avatar (avatar_name) VALUES (pv_avatar_name); /* Zapisywanie zmian we własnym zasięgu transakcji. */ COMMIT; /* Zmiana zwracanej wartości na true, co informuje o wykonaniu zadania. */ lv_retval := TRUE; RETURN lv_retval; END; /

Wiersz 2. definiuje zwracaną wartość typu logicznego. Wiersz 4. zawiera dyrektywę kompilatora, która sprawia, że funkcja jest autonomiczną jednostką programu. Wiersz 12. zatwierdza tylko te operacje, które wykonano w tej funkcji. Wiersz 15. zwraca lokalną zmienną logiczną. Nową funkcję pesymistyczną można przetestować za pomocą poniższego bloku anonimowego: SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

DECLARE /* Deklaracja zmiennej lokalnej. */ lv_avatar VARCHAR2(30); /* Deklaracja kursora lokalnego. */ CURSOR capture_result (cv_avatar_name VARCHAR2) IS SELECT avatar_name FROM avatar WHERE avatar_name = cv_avatar_name; BEGIN IF add_avatar('Earthbender') THEN dbms_output.put_line('Rekord został wstawiony'); ROLLBACK; ELSE dbms_output.put_line('Rekord nie został wstawiony'); END IF; OPEN capture_result('Earthbender'); FETCH capture_result INTO lv_avatar; CLOSE capture_result; dbms_output.put_line('Wartość ['||lv_avatar||'].'); END; /

Wiersz 11. wywołuje pesymistyczną funkcję add_avatar i zwraca wartość true. To oznacza, że funkcja autonomiczna add_avatar wstawiła wiersz w odrębnym zasięgu transakcji. Instrukcja ROLLBACK z wiersza 13. nie może wycofać tej transakcji, ponieważ została ona zatwierdzona w innym zasięgu transakcji. Program testowy wyświetla w wierszu 20. poniższy tekst (z zawartością zapisanego wiersza): Wartość [Earthbender].

Rozdział 8. Funkcje i procedury

299

Za pomocą poniższych zmian możesz zmodyfikować typ zwracanej wartości: SQL> CREATE OR REPLACE FUNCTION add_avatar 2 ( pv_avatar_name VARCHAR2 ) RETURN NUMBER IS ... 6 lv_retval NUMBER := 0; 7 BEGIN ... 13 /* Ustawianie zwracanej wartości na 1, co informuje o wykonaniu zadania. */ 14 lv_retval := 1; 15 RETURN lv_retval; 16 END; 17 /

W wierszu 2. typ wartości zwracanej przez funkcję zmieniono na NUMBER. Wiersz 6. ustawia początkową wartość lokalnej zwracanej zmiennej na 0, która zwykle oznacza false. Wiersz 14. zmienia tę wartość na 1, która zazwyczaj oznacza true. Zmodyfikowaną funkcję pesymistyczną można wywołać w instrukcji SELECT SQL-a: SQL> SELECT CASE 2 WHEN add_avatar('Firebender') = 1 THEN 'Sukces' ELSE 'Niepowodzenie' 3 END AS Autonomous 4 FROM dual;

Wiersz 2. zwraca łańcuch znaków Sukces po wstawieniu wartości Firebender. Jest to bardzo przydatny mechanizm funkcji autonomicznych, który dodatkowo zyskuje na wartości po umieszczeniu tego rodzaju zapytania w widoku.

Funkcje rekurencyjne Funkcje rekurencyjne to przydatne narzędzie do rozwiązywania pewnych złożonych problemów, na przykład przy zaawansowanym parsowaniu. Funkcje rekurencyjne wywołują same siebie, aby rozwiązać problem przez dochodzenie do zbieżnego wyniku. Funkcje te działają „z powrotem”, podczas gdy funkcje nierekurencyjne przy obliczaniu wyników poruszają się naprzód. Funkcje rekurencyjne to wyspecjalizowany rodzaj funkcji o parametrach przekazywanych przez wartość. Programy nierekurencyjne przyjmują parametry i rozpoczynają przetwarzanie — często w pętli — do momentu uzyskania wyniku. Oznacza to, że zaczynają działanie i kontynuują je do momentu osiągnięcia rezultatu na podstawie zestawu reguł i operacji. Dlatego programy rekurencyjne rozwiązują problemy przez poruszanie się naprzód. Funkcje rekurencyjne mają przypadki bazowe i rekurencyjne (ogólne). Przypadek bazowy to oczekiwany wynik. Przypadki rekurencyjne są uzyskiwane na podstawie wzoru, który obejmuje wywołania tej samej funkcji. Jedno wywołanie rekurencyjne to rekurencja liniowa (prosta). Przypadki rekurencyjne, które obejmują dwa lub więcej wywołań rekurencyjnych, są nieliniowe. Rekurencja liniowa jest zwykle dużo szybsza niż nieliniowa, a im więcej wywołań rekurencyjnych, tym wyższe koszty przetwarzania. Funkcje rekurencyjne używają przypadku rekurencyjnego tylko wtedy, jeśli przypadek bazowy nie jest spełniony. Aby uzyskać wynik, rekurencyjnie wywołana funkcja musi zwrócić wartość przypadku bazowego. Oznacza to, że rekurencyjne jednostki programów rozwiązują problemy, wykonując działania „z powrotem”, przechodząc rekurencyjnie przez wcześniejsze wywołania. Obliczanie silni to klasyczny problem rozwiązywany przy użyciu rekurencji liniowej. Poniższa funkcja zwraca silnię dowolnej liczby: SQL> 2 3 4 5 6 7 8 9 10

CREATE OR REPLACE FUNCTION factorial ( n BINARY_DOUBLE ) RETURN BINARY_DOUBLE IS BEGIN IF n 2 3 4 5 6 7 8 9 10 11

CREATE OR REPLACE FUNCTION "Fibonacci" ( n BINARY_DOUBLE ) RETURN BINARY_DOUBLE IS BEGIN /* Tworzenie przypadku bazowego. */ IF n < 2 THEN RETURN n; ELSE RETURN fibonacci(n - 2) + fibonacci(n - 1); END IF; END "Fibonacci"; /

Wiersze 6. i 8. zawierają instrukcję RETURN. Nie jest to optymalne rozwiązanie, ponieważ w funkcji powinna znajdować się tylko jedna taka instrukcja. Inna możliwość to dodanie do funkcji zmiennej lokalnej i przypisywanie do niej wyniku z przypadku bazowego lub rekurencyjnego. Operator dodawania w wierszu 8. ma niższy priorytet niż wywołanie funkcji. Dlatego wywołanie rekurencyjne po lewej stronie tego operatora jest przetwarzane do momentu zwrócenia wyniku. Następnie program przetwarza prawe wywołanie rekurencyjne. Dodawanie odbywa się po zwróceniu wyrażeń przez oba wywołania rekurencyjne. Wywoływanie funkcji z zachowaną wielkością liter w nazwie wymaga zastosowania pewnej przedstawionej poniżej sztuczki. Poniższa funkcja FibonacciSequence wywołuje funkcję Fibonacci osiem razy, co pozwala uzyskać klasyczny ciąg Fibonacciego (taki sam, jaki zostawił Jacques Saunière zamordowany w Luwrze przez Silasa w książce Kod da Vinci Dana Browna): SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

CREATE OR REPLACE FUNCTION "FibonacciSequence" RETURN VARCHAR2 IS /* Deklaracja zmiennej wyjściowej. */ lv_output VARCHAR2(40); BEGIN /* Przejście w pętli do miejsca wystarczającego do rozwiązania zagadki z Kodu da Vinci. */ FOR i IN 1..8 LOOP IF lv_output IS NOT NULL THEN lv_output := lv_output||', '||LTRIM(TO_CHAR("Fibonacci"(i),'999')); ELSE lv_output := LTRIM(TO_CHAR("Fibonacci"(i),'999')); END IF; END LOOP; RETURN lv_output; END; /

Rozdział 8. Funkcje i procedury

301

Wiersze 9. i 11. przyjmują liczbę typu BINARY_DOUBLE i formatują ją, aby uzyskana została liczba bez początkowych odstępów. Zauważ, że cudzysłów obejmuje tylko nazwę funkcji, a nie listę parametrów. To właśnie sztuczka stosowana przy wywoływaniu funkcji o nazwach z zachowaną wielkością liter. Wiersz 11. uruchamia pierwsze wywołanie funkcji Fibonacci, a wiersz 9. wykonuje dalsze wywołania. Wiersz 9. złącza wyniki z pierwotnym łańcuchem znaków. Aby wywołać funkcję FibonacciSequence (w jej nazwie wielkość znaków też jest zachowana), zastosuj następującą składnię: SQL> SELECT "FibonacciSequence" 2 FROM dual;

Uzyskasz wtedy następujące dane wyjściowe: FibonacciSequence ------------------------1, 1, 2, 3, 5, 8, 13, 21

Ta analiza pokazuje, jak stosować rekurencję. Warto zauważyć, że rekurencja związana jest z funkcjami o parametrach przekazywanych przez wartość, ponieważ programista chce otrzymać tylko wynik dla przypadku bazowego. Choć możliwe jest wywołanie funkcji rekurencyjnej o parametrach przekazywanych przez referencję, nie należy tego robić. W czasie wykonywania funkcji rekurencyjnych ich parametry nie powinny zmieniać wartości, ponieważ modyfikuje to działanie przypadku rekurencyjnego. Programista powinien rozumieć rekurencję, jeśli chce parsować łańcuchy znaków lub sprawdzać reguły składni. Jest to dużo skuteczniejsze rozwiązanie, niż poruszanie się naprzód łańcucha znaków. W tym punkcie opisano stosowanie funkcji o parametrach przekazywanych przez wartość. W następnym punkcie wykorzystano te informacje i omówiono funkcje o parametrach przekazywanych przez referencję.

Funkcje o parametrach przekazywanych przez referencję Funkcje o parametrach przekazywanych przez referencję mogą wykonywać wiele zadań opisanych we wcześniejszej części rozdziału. Takie funkcje mają parametry w trybie IN, IN OUT lub OUT. Parametr w trybie IN przekazuje wartość, która może się zmienić, ale zostaje „zużyta” w funkcji. Parametr w trybie IN OUT przekazuje referencję, której wartość może się zmienić, co pozwala zwrócić ją w nowym stanie. Parametr w trybie OUT nie przekazuje żadnych wartości, ale umożliwia ich zwracanie. Funkcji tego rodzaju należy używać, jeśli mają wykonać pewne operacje, zwrócić wartość, a także zmienić wybrane argumenty. Takie funkcje mogą działać tylko w zasięgu innego programu lub środowiska. Środowisko SQL*Plus umożliwia zdefiniowanie zmiennych poziomu sesji (zwanych też zmiennymi powiązanymi), których można użyć w wywołaniach takich funkcji. Jako parametrów zdefiniowanych w trybie IN lub IN OUT nie można przekazywać literałów (na przykład dat, liczb lub łańcuchów znaków). W języku PL/SQL funkcje o parametrach przekazywanych przez referencję muszą spełniać sześć poniższych reguł:  Przynajmniej jeden parametr formalny musi być zdefiniowany jako zmienna tylko do odczytu lub do odczytu i zapisu, do czego służą odpowiednio tryby IN i IN OUT.  Wszystkie parametry formalne to zmienne o zasięgu lokalnym, które można zmienić w czasie wykonywania operacji w funkcji.  Parametry formalne mogą mieć dowolny typ danych języka SQL lub PL/SQL. W instrukcjach w języku SQL można używać tylko funkcji o parametrach typów danych z tego języka.  Parametry formalne w trybie IN muszą mieć domyślną wartość początkową.  Zwracane zmienne mogą mieć dowolny typ danych języka SQL lub PL/SQL, przy czym zwracane tabele potokowe można stosować tylko w instrukcjach w języku SQL. Z takich tabel nie można korzystać w zasięgu innej jednostki programu PL/SQL.  Systemowe kursory referencyjne zrzutowane z zapytania w języku SQL nie umożliwiają zapisu, dlatego trzeba je przekazywać w trybie IN.

302

Część II Programowanie w języku PL/SQL

Poniższa funkcja o parametrach przekazywanych przez referencję, counter, pokazuje, jak zwrócić wartość i jednocześnie zmodyfikować zmienną wejściową. Zmienna pv_number w trybie IN OUT przekazuje zwiększaną w funkcji counter liczbę. Typ wartości zwracanej przez tę funkcję to VARCHAR2. Ta wartość to komunikat o pobranej i zwróconej wartości parametru pv_number. SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

CREATE OR REPLACE FUNCTION counter ( pv_number IN OUT INTEGER , pv_increment_by IN INTEGER DEFAULT 1) RETURN VARCHAR2 IS /* Deklaracja zwracanej wartości. */ lv_return VARCHAR2(50) := 'Otrzymana ['; BEGIN /* Dołączanie otrzymanych wartości do komunikatu. */ lv_return := lv_return || pv_number ||'] '; /* Zastępowanie wartości null, aby mieć pewność, że wartość zostanie zwiększona. */ IF pv_number IS NOT NULL THEN pv_number := pv_number + pv_increment_by; ELSE pv_number := 1; END IF; /* Dołączanie informacji do komunikatu. */ lv_return := lv_return || 'Zwracane [' || pv_number ||']'; /* Zwracanie komunikatu. */ RETURN lv_return; END; /

Wiersz 2. definiuje parametr pv_number o trybie IN OUT. Wiersz 12. sprawdza, czy parametr pv_ number ma wartość różną od null. Wiersz 13. zwiększa wartość parametru pv_number o 1. Jeśli przekazany parametr pv_number ma wartość null, wiersz 15. zmienia ją na 1. Nowe wartości do parametru pv_number przypisują wiersze 13. i 15. Jest to możliwe, ponieważ parametry przekazywane przez referencję, o trybie IN OUT lub OUT, są dozwolonym elementem docelowym przypisania. Różnią się pod tym względem od parametrów o trybie IN, które nie mogą być elementem docelowym przypisania. Aby przetestować funkcję counter, wykorzystaj poniższy blok anonimowy: SQL> 2 3 4 5 6 7 8 9 10 11 12

DECLARE /* Deklaracja dodawanej wartości. */ lv_counter INTEGER := 0; lv_increment_by INTEGER := 1; BEGIN /* Pięciokrotne wykonanie pętli. */ FOR i IN 1..5 LOOP dbms_output.put_line( 'Licznik ['||i||'] {'||counter(lv_counter)||'}'); END LOOP; END; /

Oto dane wyświetlane przez ten blok anonimowy: Licznik [1] {Otrzymana [0] Zwracana [1]} Licznik [2] {Otrzymana [1] Zwracana [2]} ...

Jak widać w danych wyjściowych, argument o trybie IN OUT jest zawsze zwiększany o jeden. Argumentu przeznaczonego tylko do zapisu (o trybie OUT) nie można wykorzystać w takim wywołaniu, ponieważ jego wartość nigdy nie jest wczytywana. Po zmianie trybu parametru pv_number z IN OUT na OUT uzyskasz zupełnie inną funkcję. Po wprowadzeniu na liście parametrów funkcji counter poniższej modyfikacji każde wywołanie otrzyma wartość null: 2

( pv_number

OUT INTEGER

Rozdział 8. Funkcje i procedury

303

Teraz ten sam blok anonimowy zwróci następujące wyniki: Licznik [1] {Otrzymana [] Zwracana [1]} Licznik [2] {Otrzymana [] Zwracana [1]} ...

W tym punkcie opisano definiowanie i używanie funkcji o parametrach przekazywanych przez referencję. Warto pamiętać, że są dwa rodzaje parametrów przekazywanych w ten sposób. Zmienne w trybie IN OUT mają wartość na początku i końcu działania funkcji, a parametry w trybie OUT początkowo zawsze mają wartość null, ale powinny mieć określoną wartość końcową. Powtórzenie materiału W tym podrozdziale omówiono następujące zagadnienia dotyczące działania i cech funkcji:  Klauzula DETERMINISTIC informuje, że program dla tych samych parametrów zawsze zwraca identyczne wyniki.  Klauzula PARALLEL_ENABLE oznacza, że daną funkcję można wywoływać w zapytaniach równoległych. Takie funkcje najlepiej tworzyć za pomocą obu klauzul: DETERMINISTIC i PARALLEL_ENABLE.  Klauzula PIPELINED służy do tworzenia funkcji zwracających tabele potokowe, przekształcających tablice asocjacyjne języka PL/SQL na zagregowane zbiory wyników SQL-a.  Zamiast stosować klauzulę PIPELINED, można przekształcić tablice asocjacyjne na tabele zagnieżdżone SQL-a.  Klauzula RESULT_CACHE umożliwia zapisywanie w pamięci podręcznej wyników wywołań funkcji deterministycznych i niedeterministycznych. W Oracle Database 12c działa ona także dla funkcji o uprawnieniach jednostki wywołującej.  Funkcje o parametrach przekazywanych przez wartość przyjmują zmienne tylko o trybie IN. W takich funkcjach nie można używać parametrów jako docelowych elementów przypisania.  Można utworzyć funkcję zwracającą systemowy kursor referencyjny języka PL/SQL.  Różnica między deterministycznymi i niedeterministycznymi funkcjami polega na tym, że funkcje niedeterministyczne zależą w czasie wykonywania programu od wewnętrznie używanych tabel.  Instrukcje DML można stosować w autonomicznych funkcjach o parametrach przekazywanych przez wartość. Wymaga to wywołania instrukcji TCL w takich funkcjach. W ten sposób powstają funkcje pesymistyczne.  Oracle umożliwia tworzenie liniowych i nieliniowych funkcji rekurencyjnych.  Oracle umożliwia stosowanie trybów IN OUT i OUT dla zmiennych przekazywanych przez referencję.

Procedury Procedura to w zasadzie funkcja zwracająca wartość typu void. Nie można jej używać jako prawego operandu, ponieważ nie ma zwracanej wartości. Procedury, podobnie jak funkcje, to czarne skrzynki. Procedury służą do tworzenia nazwanych procedur, które można wywoływać w zasięgu bloku języka PL/SQL. Choć ich działanie różni się w zależności od tego, czy ich parametry są przekazywane przez wartość, czy przez referencję, dane wyjściowe procedur i skutki ich działania to jedyny sposób wymiany wartości między blokiem wywołującym a samą procedurą. Procedur nie można używać jako prawych operandów ani wywoływać w instrukcjach w języku SQL. Ich parametry formalne mogą mieć tryb IN, OUT lub IN OUT. Procedury, podobnie jak funkcje, mogą obejmować zagnieżdżone bloki nazwane. Takie bloki to lokalne funkcje i procedury zdefiniowane w bloku deklaracji. Ponadto w sekcji wykonawczej procedury można umieszczać bloki anonimowe. Poniższy kod to prototyp bloku nazwanego w postaci procedury: [{EDITIONALBE | NONEDITIONALBE}] PROCEDURE nazwa_procedury [( parametr1 [IN][OUT] [NOCOPY] typ_danych_sql | typ_danych_plsql , parametr2 [IN][OUT] [NOCOPY] typ_danych_sql | typ_danych_plsql , parametr(n+1) [IN][OUT] [NOCOPY] typ_danych_sql | typ_danych_plsql)] [ACCESSIBLE BY

304

Część II Programowanie w języku PL/SQL

( [{FUNCTION | PROCEDURE | PACKAGE | TYPE}] [schema.]nazwa_jednostki) [,[{FUNCTION | PROCEDURE | PACKAGE | TYPE}] [schema.]nazwa_jednostki)] [,... ]]]) [ AUTHID DEFINER | CURRENT_USER ] IS deklaracje BEGIN wykonywane_instrukcje [EXCEPTION] obsługa_wyjątków END [nazwa_procedury]; /

Procedury mogą mieć parametry formalne, ale nie jest to wymagane. Parametry formalne w procedurach składowanych mogą być zmiennymi przekazywanymi albo przez wartość, albo przez referencję. Zmienne przekazywane przez referencję mogą mieć tryb IN i OUT. Podobnie jak w funkcjach brak podanego trybu parametru powoduje utworzenie procedury o parametrach przekazywanych przez wartość, ponieważ program użyje wtedy domyślnego trybu IN. Kompilowanie (tworzenie lub zastępowanie) procedury powoduje niejawne zastosowanie trybu IN, jeśli programista go nie określi. Podobnie jak w funkcjach, w procedurach parametry formalne w trybie IN mogą mieć wartość domyślną. Klauzula ACCESSIBLE BY umożliwia utworzenie białej listy jednostek wywołujących daną procedurę. Na takiej liście można podać funkcje, procedury, pakiety i typy obiektowe. Klauzula AUTHID określa model uprawnień używanych przy wykonywaniu kodu. Domyślnie używane są uprawnienia osoby definiującej, co oznacza, że jednostki z uprawnieniami do wykonywania danej procedury są traktowane jak właściciele danego schematu. Opcja CURRENT_USER zastępuje to domyślne ustawienie i sprawia, że używane są uprawnienia osoby wywołującej. Powoduje to, że wywoływane procedury działają na danych lokalnych. Wymaga to powielenia danych we wszystkich używanych schematach. Sekcja deklaracji znajduje się — podobnie jak w funkcjach — między słowami IS i BEGIN, podczas gdy pozostałe sekcje mają taką samą strukturę jak w blokach anonimowych. Procedury muszą działać w określonym środowisku wykonania, co oznacza, że trzeba je wywoływać w środowisku SQL*Plus lub innej jednostce programu. Jednostką wywołującą może być inny blok języka PL/SQL lub program zewnętrzny, w którym użyto biblioteki OCI lub JDBC. Procedury są używane najczęściej do wykonywania instrukcji DML i zarządzania transakcjami. Można zdefiniować je tak, aby działały w zasięgu bieżącej transakcji lub w zasięgu transakcji niezależnej. Podobnie jak w funkcjach, należy użyć dyrektywy PRAGMA AUTONOMOUS_TRANSACTION, aby określić, że procedura ma działać jako transakcja niezależna.

Procedury o parametrach przekazywanych przez wartość Procedury o parametrach przekazywanych przez wartość przyjmują kopie argumentów. Nie zwracają żadnych konkretnych danych do zasięgu wywołania, ale mogą wykonywać operacje na bazie. Procedury o parametrach przekazywanych przez wartość działają zgodnie z modelem delegowania. Takie procedury często są używane do grupowania i kontrolowania zestawów instrukcji DML wykonywanych w zasięgu jednej transakcji. W procedurach o parametrach przekazywanych przez wartość wszystkie parametry formalne działają w trybie IN. To oznacza, że w momencie wywołania procedura otrzymuje kopię zewnętrznej zmiennej albo literału liczbowego lub znakowego. Parametrów takich wywołań nie można zmieniać w trakcie wykonywania podprogramu. Wartość parametru można przypisać do zmiennej lokalnej w procedurze, a następnie zmodyfikować tę zmienną. Procedury o parametrach przekazywanych przez wartość można zdefiniować tak, aby działały autonomicznie w odrębnym zasięgu transakcji. Można też nie zmieniać ustawień domyślnych, które określają, że procedurę należy wykonać w bieżącym zasięgu transakcji. Procedury zwykle działają w zasięgu bieżącym i służą do organizowania instrukcji DML manipulujących bazą danych, na przykład operacji INSERT wstawiających dane do wielu tabel. Procedury o parametrach przekazywanych przez wartość w języku PL/SQL spełniają pięć warunków:

Rozdział 8. Funkcje i procedury     

305

Wszystkie parametry formalne muszą być zdefiniowane jako zmienne tylko do odczytu. Służy do tego tryb IN. Wszystkie parametry formalne to zmienne o zasięgu lokalnym, których wartości nie można zmienić w czasie wykonywania procedury. Parametry formalne mogą mieć dowolny typ danych języka SQL lub PL/SQL. Parametry formalne mogą mieć domyślną wartość początkową. Systemowe kursory referencyjne zrzutowane z zapytania w języku SQL nie umożliwiają zapisu, dlatego trzeba je przekazywać w trybie IN. Obejmuje to kursory przekazywane jako jawne zmienne kursorowe i rzutowane przy użyciu funkcji CURSOR. Jak wyjaśniono w punkcie „Systemowe kursory referencyjne” we wcześniejszej części rozdziału, zmienne kursorowe to referencje (uchwyty do danych). Referencje wskazują na zbiory wyników zapisane w wewnętrznej pamięci podręcznej, które są przeznaczone tylko do odczytu.

Czasem programista chce utworzyć mniejsze i nadające się do wielokrotnego użytku jednostki programu. Na przykład każdą instrukcję INSERT można umieścić w odrębnej procedurze składowanej. Aby takie rozwiązanie działało poprawnie, należy zastosować procedury o parametrach przekazywanych przez referencję. W takich procedurach listy parametrów obejmują też klucze główne i zewnętrzne. Modyfikacja listy parametrów umożliwia procedurom wymianę wartości między programami. Procedura adding_avatar wstawia wartości do dwóch tabel. Ta procedura ma dwa parametry. Pierwsza wartość trafia do pierwszej tabeli, a druga — do drugiej tabeli. Zastosowałem tu niewielką tabelę, aby przedstawić omawiane zagadnienie bez zajmowania dużej ilości miejsca kodem w SQL-u. W tym przykładzie używane są tabele avatar i episode. W ich definicjach zastosowano kolumny identyfikacyjne bazy Oracle Database 12c. Oto pierwsza tabela, avatar: SQL> CREATE TABLE avatar 2 ( avatar_id NUMBER GENERATED AS IDENTITY 3 CONSTRAINT avatar_pk PRIMARY KEY 4 , avatar_name VARCHAR2(30));

Wiersz 2. tworzy kolumnę identyfikacyjną avatar_id z ograniczeniem klucza głównego. Dla tej kolumny trzeba zdefiniować to ograniczenie, ponieważ w tabeli episode ta kolumna jest używana w kluczu zewnętrznym. A oto definicja tabeli episode: SQL> 2 3 4 5 6 7

CREATE TABLE episode ( episode_id NUMBER GENERATED AS IDENTITY CONSTRAINT episode_pk PRIMARY KEY , avatar_id NUMBER CONSTRAINT episode_nn1 NOT NULL , episode_name VARCHAR2(30) , CONSTRAINT episode_fk1 FOREIGN KEY(avatar_id) REFERENCES avatar(avatar_id));

Wiersz 4. definiuje ograniczenie NOT NULL dla kolumny avatar_id. Wiersze 6. i 7. definiują pozawierszowo ograniczenie klucza zewnętrznego dla tej samej kolumny. Te dwa ograniczenia sprawiają, że nie da się wstawić do tabeli episode wiersza bez podania jednej z wartości z kolumny klucza głównego avatar_id z kolumny avatar. Poniższa procedura adding_avatar pokazuje, jak za pomocą procedury o parametrach przekazywanych przez wartość zarządzać wieloma instrukcjami DML w ramach jednego zasięgu transakcji: SQL> 2 3 4 5 6 7 8 9

CREATE OR REPLACE PROCEDURE adding_avatar ( pv_avatar_name VARCHAR2 , pv_episode_name VARCHAR2 ) IS /* Deklaracja zmiennej lokalnej używanej do zarządzania kluczem sztucznym z kolumny identyfikacyjnej. */ lv_avatar_id NUMBER; BEGIN /* Tworzenie punktu zapisu. */

306 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

Część II Programowanie w języku PL/SQL SAVEPOINT all_or_none; /* Wstawianie wiersza do tabeli avatar. */ INSERT INTO avatar (avatar_name) VALUES (pv_avatar_name) RETURNING avatar_id INTO lv_avatar_id; /* Wstawianie wiersza do tabeli episode. */ INSERT INTO episode (avatar_id, episode_name) VALUES (lv_avatar_id, pv_episode_name); /* Zapisywanie zmian we własnym zasięgu transakcji. */ COMMIT; EXCEPTION WHEN OTHERS THEN ROLLBACK TO all_or_none; END; /

Wiersze 2. i 3. definiują parametry formalne procedury adding_avatar. Wiersz 10. ustawia punkt zapisu (SAVEPOINT), który jest punktem początkowym transakcji. Wiersze od 13. do 15. wstawiają wiersz do tabeli avatar. Klauzula RETURNING INTO zwraca wartość kolumny identyfikacyjnej do zmiennej lokalnej. W wierszu 19. zmienna lokalna lv_avatar_id jest używana jako wartość klucza zewnętrznego przy wstawianiu danych do tabeli episode. Po wywołaniu instrukcji INSERT dla obu tabel operacje te są zatwierdzane (wiersz 22.). Jeśli wystąpił wyjątek, wiersz 25. wycofuje wykonaną już część transakcji. Poniższy blok anonimowy pozwala przetestować tę procedurę: SQL> BEGIN 2 adding_avatar('Airbender','Odcinek 1'); 3 END; 4 /

Niestety, kolumny identyfikacyjne nie są obsługiwane w starszych wersjach bazy. Baza Oracle Database 11g umożliwia generowanie kolejnych wartości w instrukcjach INSERT za pomocą pseudokolumn .nextval i .currval. Pseudokolumna .nextval służy do generowania wartości dla kolumn z kluczem głównym. Za pomocą pseudokolumny .currval można tworzyć wartości dla kolumn z kluczem zewnętrznym. Wywołanie .currval musi znajdować się w danej sesji po wywołaniu .nextval. Oto analogiczny kod dostosowany do bazy Oracle Database 11g: ... 13 INSERT INTO avatar (avatar_id, avatar_name) 14 VALUES (avatar_s.nextval, pv_avatar_name); ... 18 INSERT INTO episode (episode_id, avatar_id, episode_name) 19 VALUES (episode_s.nextval, avatar_s.currval, pv_episode_name); ...

W jeszcze starszej wersji, Oracle Database 10g, wynik wywołania .nextval trzeba zapisać w zmiennej lokalnej, a dopiero potem zastosować ją w instrukcji INSERT. Oto potrzebny kod: 12 13 14 15

/* Zapisywanie kolejnych wartości do zmiennej lokalnej. */ SELECT avatar_s.nextval INTO lv_avatar_id FROM dual;

Procedury o parametrach przekazywanych przez wartość umożliwiają wykonywanie zadań z wykorzystaniem zasobów bazy danych lub zasobów zewnętrznych. Jeśli taka procedura jest za pomocą dyrektywy kompilatora oznaczona jako autonomiczna jednostka programu, działa jako moduł optymistyczny w trybie bez oczekiwania na wynik. Procedury o parametrach przekazywanych przez wartość pozwalają też zarządzać kluczami głównymi i zewnętrznymi w jednym zasięgu programu.

Rozdział 8. Funkcje i procedury

307

Procedury o parametrach przekazywanych przez referencję Podstawowa procedura o parametrach przekazywanych przez referencję przyjmuje jeden lub kilka parametrów przez referencję. Wewnątrz takich procedur można zmieniać wartości przekazanych przez referencję zmiennych. Zasięg takich procedur jest wyznaczany w jednostce wywołującej. Zmienne w takich procedurach traktują zmienne bardzo podobnie jak zagnieżdżone bloki anonimowe. Procedury o parametrach przekazywanych przez referencję można zdefiniować tak, aby działały autonomicznie, a następnie je uruchomić w odrębnym zasięgu transakcji. Można też przyjąć ustawienia domyślne i wywoływać je w bieżącym zasięgu transakcji. Procedury pozwalają uporządkować instrukcje DML przenoszące dane między programem a bazą danych oraz umożliwiają przekazywanie danych do zewnętrznych jednostek programów. Procedury o parametrach przekazywanych przez referencję w języku PL/SQL spełniają pięć poniższych reguł:  Przynajmniej jeden parametr musi być zdefiniowany jako zmienna tylko do zapisu (OUT) lub do odczytu i zapisu (IN OUT).  Wszystkie parametry formalne to zmienne o zasięgu lokalnym, które można zmieniać w czasie działania procedury.  Parametry formalne mogą mieć dowolny poprawny typ danych języka SQL lub PL/SQL.  Wszystkie parametry formalne w trybie IN mogą mieć domyślną wartość początkową.  Systemowe kursory referencyjne zrzutowane z zapytania w języku SQL nie umożliwiają zapisu, dlatego trzeba je przekazywać w trybie IN. Procedury o parametrach przekazywanych przez referencję umożliwiają wykonanie wielu instrukcji DML w jednym zasięgu transakcji i programu. W czasie ich wykonywania można współużytkować w „czarnej skrzynce” pewne wartości, na przykład klucze główne i zewnętrzne. Procedury o parametrach przekazywanych przez referencję mogą działać jak bloki pesymistyczne. W tym celu należy wykorzystać parametr o trybie OUT do sygnalizowania sukcesu lub niepowodzenia. Oto wersja procedury adding_avatar z parametrami przekazywanymi przez referencję: SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15

CREATE OR REPLACE PROCEDURE adding_avatar ( pv_avatar_name IN VARCHAR2 , pv_episode_name IN VARCHAR2 , pv_completion OUT BOOLEAN) IS /* Deklaracja zmiennej lokalnej używanej do zarządzania kolumną identyfikacyjną z kluczem zewnętrznym. */ lv_avatar_id NUMBER; BEGIN /* Ustawianie zmiennej sygnalizującej zakończenie pracy. */ pv_completion := FALSE; /* Tworzenie punktu zapisu. */ SAVEPOINT all_or_none; ...

24 25 26 27 28 29 30 31 32 33 34

/* Zapisywanie zmiany we własnym zasięgu transakcji. */ COMMIT; /* Ustawianie zmiennej sygnalizującej zakończenie pracy. */ pv_completion := TRUE; EXCEPTION WHEN OTHERS THEN ROLLBACK TO all_or_none; END; /

308

Część II Programowanie w języku PL/SQL

Wiersz 4. zawiera przekazywaną przez referencję zmienną pv_completion w trybie OUT. To oznacza, że nie można przekazać za pomocą tej zmiennej wartości do procedury, natomiast można pobrać wartość zmiennej po wykonaniu procedury. Wiersz 11. ustawia początkową wartość zmiennej pv_completion. Ta operacja znajduje się przed utworzeniem punktu zapisu transakcji. Po wywołaniu instrukcji COMMIT procedura przypisuje w wierszu 29. wartość TRUE do zmiennej pv_completion. Wyjątek powoduje zatrzymanie wykonywania transakcji i wycofanie instrukcji DML do punktu zapisu. Ta procedura zwraca wartość false, jeśli transakcja nie została ukończona, i wartość true, jeżeli transakcja zakończyła się powodzeniem. Jeśli dodasz dyrektywę kompilatora oznaczającą transakcje autonomiczne, powstanie procedura autonomiczna, na której zakończenie trzeba oczekiwać. Wynika to z tego, że parametr pv_completion należy zwrócić do zasięgu wywołania. Po usunięciu tego przekazywanego przez referencję parametru powstanie procedura o parametrach przekazywanych przez wartość, na której zakończenie nie trzeba oczekiwać. Rozwijanie podprogramów w miejscu wywołania Rozwijanie podprogramów w miejscu wywołania (ang. inlining) to wykonywana przez kompilator operacja, która polega na skopiowaniu podprogramu do innego programu. Pozwala to uniknąć kosztów ponoszonych przy częstym wywoływaniu podprogramu zewnętrznego. Choć zawsze można pozostawić decyzję o rozwinięciu podprogramu kompilatorowi, programista może też zasugerować, że należy skopiować dany podprogram zewnętrzny w określonym miejscu. Aby określić, że kompilator powinien rozwinąć podprogram w miejscu wywołania, należy użyć poniższego prototypu: PRAGMA INLINE(nazwa_podprogramu, 'YES'|'NO')

Ostatecznie to kompilator podejmuje decyzję o tym, czy rozwinąć podprogram, ponieważ instrukcje dla prekompilatora to jedynie wskazówki. Są pewne czynniki, które sprawiają, że w niektórych warunkach podprogramu nie należy rozwijać. Powyższa dyrektywa PRAGMA wpływa na działanie funkcji i procedur, jeśli poprzedza ich wywołanie. Ma wpływ także na każde wywołanie instrukcji CASE, CONTINUE-WHEN, EXECUTE IMMEDIATE, EXIT-WHEN, LOOP i RETURN. Wpływ wskazówki prekompilatora PRAGMA INLINE zmienia się w zależności od wartości zmiennej sesji PLSQL_OPTIMIZE_LEVEL. Podprogramy są rozwijane w miejscu wywołania, jeśli wartość tej zmiennej to 2, a wskazówka ta ma tylko wysoki priorytet, jeśli zmienna ma wartość 3. Ustawienie zmiennej na wartość 1 spowoduje, że podprogramy będą rozwijane tylko wtedy, gdy kompilator oceni to jako konieczne.

Mniejsze jednostki, takie jak funkcje o parametrach przekazywanych przez referencję, ułatwiają powtórne wykorzystanie kodu, jednak trudniej jest nimi zarządzać. Możliwe, że trzeba będzie utworzyć po jednej takiej procedurze dla wszystkich tabel i widoków aplikacji. Jednostki większe, na przykład procedury o parametrach przekazywanych przez wartość, pozwalają zarządzać złożonymi procesami w jednej czarnej skrzynce. Czasem takie procedury nazywa się jednostkami do organizacji działań. W procedurach o parametrach przekazywanych przez wartość zwykle procesy są ważniejsze niż w nakładkach, gdzie istotne są przede wszystkim dane. Ponadto konserwacja takich procedur jest łatwiejsza. Warto jednak zauważyć, że procedury o parametrach przekazywanych przez referencję to doskonałe rozwiązanie w bezstanowych aplikacjach sieciowych. Najlepszą praktyczną regułą jest uwzględnianie we wszystkich procedurach głównie operacji związanych z procesem. W drugiej kolejności można zastanawiać się, które podprogramy będą najbardziej skuteczne w wyjątkowych sytuacjach. Powtórzenie materiału W tym podrozdziale opisano następujące zagadnienia dotyczące działania i cech procedur:  Procedury definiuje się jako jednostki o parametrach przekazywanych przez wartość lub o parametrach przekazywanych przez referencję.  Procedura o parametrach przekazywanych przez wartość utworzona jako jednostka autonomiczna (za pomocą dyrektywy kompilatora) to podprogram optymistyczny.

Rozdział 8. Funkcje i procedury

309

Procedura o parametrach przekazywanych przez referencję to podprogram pesymistyczny. Nie zmienia tego utworzenie jej jako autonomicznej jednostki programu za pomocą dyrektywy kompilatora.  Procedury pozwalają tworzyć zgodne z modelem ACID transakcje obejmujące wiele tabel.  Za pomocą dyrektywy kompilatora można rozwinąć podprogram w miejscu wywołania. 

Pomocnicze skrypty W tym podrozdziale opisano pomocnicze programy dostępne w witrynie wydawnictwa Helion. 

Pliki deterministic.sql, java_library.sql, merging.sql, pass_by_reference.sql, pipelined.sql, recursive.sql i result_cache.sql zawierają kompletne wersje przykładów z podrozdziału „Funkcje” z tego rozdziału.



Plik avatar.sql obejmuje krótkie programy dotyczące podrozdziału „Procedury” z tego rozdziału.

Podsumowanie Czytelnik powinien rozumieć już działanie zasięgu transakcji, a także umieć tworzyć funkcje i procedury oraz wiedzieć, kiedy używać podprogramów poszczególnych rodzajów.

Test wiedzy Test wiedzy to zestaw pytań typu „prawda czy fałsz” i wielokrotnego wyboru, dzięki którym sprawdzisz, jak dobrze opanowałeś materiał z poszczególnych rozdziałów. Odpowiedzi na pytania znajdziesz w dodatku I.

Prawda czy fałsz? 1. __Funkcja o parametrach przekazywanych przez wartość przyjmuje parametry, które są wyko-

rzystywane i przekształcane na jedną zwracaną wartość. 2. __Dyrektywa kompilatora INLINE pozwala dołączyć niezależny moduł jako część skompilowanej jednostki programu. 3. __Funkcja o parametrach przekazywanych przez referencję przyjmuje literały we wszystkich parametrach wywołania. 4. __Procedura o parametrach przekazywanych przez wartość przyjmuje literały we wszystkich parametrach wywołania. 5. __Instrukcja RETURN we wszystkich funkcjach o parametrach przekazywanych przez wartość lub przez referencję zawsze musi zwracać literał lub zmienną. 6. __Dla lokalnych funkcji i procedur należy utworzyć namiastki z referencjami uprzedzającymi, aby uniknąć błędów „brak deklaracji w tym zasięgu”. 7. __W składowanej funkcji lub procedurze nie można przypisać nowej wartości do parametru w trybie IN. 8. __W składowanej funkcji lub procedurze nie można przypisać nowej wartości do parametru w trybie IN OUT. 9. __W funkcji, która ma być wywołana w instrukcji SELECT SQL-a, nie można używać instrukcji INSERT, UPDATE i DELETE. 10. __Niektóre funkcje można wywoływać tylko w zasięgu języka PL/SQL.

310

Część II Programowanie w języku PL/SQL

Pytania wielokrotnego wyboru 11. Które rodzaje podprogramów zwracają wartość po zakończeniu działania? Poprawnych może

być kilka odpowiedzi. A. Funkcja o parametrach przekazywanych przez wartość. B. Procedura o parametrach przekazywanych przez wartość. C. Funkcja o parametrach przekazywanych przez referencję. D. Procedura o parametrach przekazywanych przez referencję. E. Wszystkie z powyższych. 12. Które z poniższych klauzul są obsługiwane w języku PL/SQL? Poprawnych może być kilka odpowiedzi. A. Klauzula INLINE. B. Klauzula PIPELINED. C. Klauzula DETERMINISTIC. D. Klauzula NONDETERMINISTIC. E. Klauzula RESULT_CACHE. 13. Które notacje są obsługiwane w bazie Oracle Database 12c? Poprawnych może być kilka odpowiedzi. A. Notacja oparta na pozycjach. B. Notacja oparta na nazwach. C. Notacja mieszana. D. Notacja obiektowa. E. Notacja z pominięciem. 14. Która z poniższych funkcji nie może zapisywać wyników w pamięci podręcznej w bazie Oracle Database 12c? Poprawnych może być kilka odpowiedzi. A. Funkcja o parametrach przekazywanych przez wartość, deterministyczna, z uprawnieniami jednostki definiującej. B. Funkcja o parametrach przekazywanych przez wartość, deterministyczna, z uprawnieniami jednostki wywołującej. C. Funkcja o parametrach przekazywanych przez wartość, niedeterministyczna, z uprawnieniami jednostki definiującej. D. Funkcja o parametrach przekazywanych przez wartość, niedeterministyczna, z uprawnieniami jednostki wywołującej. E. Funkcja o parametrach przekazywanych przez referencję, niedeterministyczna, z uprawnieniami jednostki definiującej. 15. Która z poniższych dyrektyw kompilatora jest zgodna z wersją Oracle Database 8i? A. RESTRICT_ACCESS. B. INLINE. C. AUTONOMOUS. D. DETERMINISTIC. E. EXCEPTION_INIT.

ROZDZIAŁ

9

Pakiety

Pakiety to podstawowy element w rozwoju aplikacji opartych na bazie danych Oracle 11c. Umożliwiają grupowanie komponentów w postaci funkcji i procedur w biblioteki. Takie biblioteki mogą zawierać współużytkowane zmienne, typy i komponenty (czyli funkcje oraz procedury). Inaczej niż w niezależnych składowanych funkcjach i procedurach (opisanych w rozdziale 8.) deklaracja pakietów składowanych jest oddzielona od ich implementacji. Do publikowania deklaracji służą specyfikacje pakietów, a implementacja znajduje się w ciele pakietu. W tym rozdziale wyjaśniono, jak deklarować i implementować pakiety składowane, a także jak ich używać i jak nimi zarządzać. Tekst napisany jest w taki sposób, aby ułatwić początkującym zrozumienie pakietów, jednak zawarte w nim informacje są przydatne nawet dla zaawansowanych programistów języka PL/SQL. W rozdziale opisano następujące zagadnienia z zakresu pakietów:  Architektura pakietu  Specyfikacja pakietu  Ciało pakietu  Uprawnienia jednostki definiującej i wywołującej  Zarządzanie pakietami przy użyciu katalogu bazy danych Choć pakiety nie są typami obiektowymi, mają z nimi kilka cech wspólnych. W dalszej części rozdziału zobaczysz, że można zastosować dyrektywę prekompilatora SERIAL_REUSABLE, aby utworzyć pakiet stanowy. Jeśli nie wiesz, czym są typy obiektowe, możesz zajrzeć do rozdziału 11. przed przejściem do omówienia pakietów do seryjnego wielokrotnego uruchamiania.

Architektura pakietu Pakiety to biblioteki składowane w bazie danych. Podobnie jak tabele i widoki, należą do schematu użytkownika, w którym je utworzono. Ta przynależność sprawia, że w katalogu bazy danych pakiety to obiekty z poziomu schematu, podobnie jak niezależne funkcje i procedury. Specyfikacja pakietu obejmuje deklaracje zmiennych, typów danych, funkcji i procedur. Deklaracja powoduje opublikowanie tych elementów w lokalnym schemacie. Zmiennych i typów pakietu można używać w innych blokach języka PL/SQL. Blok wywołujący publiczne funkcje i procedury może znajdować się wewnątrz lub na zewnątrz pakietu z deklaracjami używanych elementów. W bazie Oracle Database 12c można ustawić białą listę dla pakietu, aby ograniczyć, które funkcje, procedury, pakiety i typy obiektowe mają prawo do wywoływania danego pakietu. Choć wszystkim użytkownikom oprócz właściciela trzeba przyznać uprawnienia do wykonywania pakietu (EXECUTE), aby mogli wywoływać publiczne komponenty, pakiet z białą listą musi dodatkowo dawać uprawnienia wywołującym go jednostkom.

312

Część II Programowanie w języku PL/SQL

Oprócz możliwości stosowania białej listy pakietów dotyczą te same reguły, co tabel, widoków, typów danych języka SQL. Dla niezależnych modułów (na przykład niezależnych funkcji i procedur) także można tworzyć białe listy. Kontekst komponentów publicznych to pakiet, podobnie jak schemat użytkownika jest kontekstem komponentów niezależnych. Model zabezpieczeń bazy danych Oracle 12c umożliwia przyznanie uprawnień EXECUTE do dowolnego pakietu wszystkim użytkownikom (przez publiczne przyznanie uprawnień). Dlatego możliwe jest przyznanie publicznego dostępu do pakietów. Programista może też ograniczyć taki dostęp. W wersjach starszych od Oracle Database 12c pakiet z uprawnieniami jednostki wywołującej zawsze działa z uprawnieniami takiej jednostki. Oznacza to, że jeśli uprawnienia jednostki wywołującej są wyższe od uprawnień właściciela, taki pakiet może wykonywać operacje nieprzeznaczone lub zakazane dla właściciela. W bazie Oracle Database 12c zawężono zasięg programów z uprawnieniami jednostki wywołującej. Domyślnie nie można teraz dziedziczyć uprawnień użytkownika. Aby uzyskać rozwiązanie z wcześniejszych wersji bazy Oracle, trzeba zastąpić ustawienia domyślne (co nie jest tu dobrym pomysłem) i przyznać jednostce wywołującej pakiet uprawnienia INHERIT PRIVILEGES lub INHERIT ANY PRIVILEGES. Ten mechanizm zabezpieczeń bazy Oracle umożliwia przyznanie uprawnień tylko docelowym jednostkom. W ciele pakietu można zdefiniować (zadeklarować i zaimplementować) funkcje i procedury dostępne tylko w zasięgu tego pakietu. Takie podprogramy mają dostęp do wszystkich elementów opisanych w specyfikacji pakietu, a także kodu zadeklarowanego wcześniej w ciele, nie mogą natomiast używać jednostek zadeklarowanych w dalszej części ciała. Dzieje się tak, ponieważ parser języka PL/SQL przechodzi przez kod tylko raz. Parsery umieszczają identyfikatory w tymczasowej przestrzeni nazw w trakcie wczytywania kodu źródłowego. Jeśli identyfikator zostanie użyty przed jego zadeklarowaniem, parser zgłosi błąd. Dlatego deklaracje identyfikatorów w sekcji deklaracji bloków języka PL/SQL mają określoną kolejność. Zwykle umieszcza się je w następującym porządku: typy danych, zmienne, wyjątki, funkcje i procedury. Uporządkowanie identyfikatorów rozwiązuje wiele problemów związanych z referencjami uprzedzającymi, ale nie wszystkie (zobacz ramkę). Czasem implementacja komponentu wymaga dostępu do innego elementu przed utworzeniem tego ostatniego. Choć można zmienić kolejność niektórych jednostek w celu rozwiązania problemu, często lepiej jest zadeklarować namiastkę działającą jako referencja uprzedzająca. Referencje tego rodzaju służą do deklarowania podprogramów bez ich implementowania i można ich używać w sekcji deklaracji. Specyfikacja pakietu służy do deklarowania implementowanych jednostek. Ciało pakietu zawiera implementacje deklaracji znalezionych w specyfikacji. W ciele pakietu trzeba zaimplementować wszystkie funkcje i procedury zdefiniowane w specyfikacji pakietu. Jeśli nie udostępnisz lokalnych funkcji i procedur w ciele pakietu, mogą wystąpić błędy. Rysunek 9.1 ilustruje specyfikację i ciało pakietu. Specyfikacja jest jednocześnie interfejsem ciała pakietu. Zmienne, typy i komponenty można deklarować zarówno w specyfikacji, jak i w ciele pakietu. Elementy zadeklarowane w specyfikacji pakietu są publikowane, podczas gdy te znajdujące się tylko w ciele pakietu stanowią komponenty lokalne (prywatne).

Rysunek 9.1. Architektura pakietów języka PL/SQL

Rozdział 9. Pakiety

313

Referencje uprzedzające Działanie referencji uprzedzających jest proste. Nie można wysłać wiadomości SMS do znajomych poznanych na konferencji bez wcześniejszego zapisania ich numerów telefonicznych. Podobnie nie można wywołać funkcji ani procedur, jeśli nie są znane ich nazwy ani listy parametrów formalnych. Poniższy przykładowy kod (podobny do tego z rozdziału 3.) ilustruje, że procedura first nie może wywołać procedury second do momentu zadeklarowania tej ostatniej (czyli umieszczenia jej w zasięgu). W tym przykładzie nie istnieje namiastka pełniąca funkcję referencji uprzedzającej dla procedury second, dlatego procedura first nie ma żadnych informacji o procedurze second, gdy chce ją wywołać: SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15

DECLARE -- Miejsce na namiastkę pełniącą funkcje referencji uprzedzającej. PROCEDURE first(pv_caller VARCHAR2) IS BEGIN dbms_output.put_line('"First" wywołana przez ['||pv_caller||']'); second('First'); END; PROCEDURE second(pv_caller VARCHAR2) IS BEGIN dbms_output.put_line('"Second" wywołana przez ['||pv_caller||']'); END; BEGIN first('Main'); END; /

Ten program zgłosi poniższy wyjątek z powodu braku pełniącej funkcję referencji uprzedzającej namiastki dla lokalnej procedury second: second('First'); * ERROR at line 6: ORA-06550: line 6, column 5: PLS-00313: 'SECOND' not declared in this scope ORA-06550: line 6, column 5: PL/SQL: Statement ignored

Aby naprawić ten błąd, dodaj w wierszu 2. namiastkę pełniącą funkcję referencji uprzedzającej. Powinna ona wyglądać tak: 2

PROCEDURE second(pv_caller VARCHAR2);

Teraz program wyświetli następujące dane: "First" wywołana przez [Main] "Second" wywołana przez [First]

Sekcja wykonawcza ma wszystkie informacje zapisane w jej sekcji deklaracji i zewnętrznych sekcjach deklaracji. Referencja uprzedzająca umożliwia parserowi języka PL/SQL (który przechodzi przez kod tylko raz) umieścić deklarację procedury second na liście identyfikatorów. Parser przechodzi przez kod od góry do dołu, dlatego doda tę deklarację przed wczytaniem procedury first. Kiedy parser dojdzie do procedury first, będzie już miał dostęp do deklaracji procedury second, dlatego uzna jej wywołanie za poprawne i poszuka implementacji w dalszej części programu, aby móc poprawnie skompilować kod. Jeśli po wczytaniu całego kodu źródłowego okaże się, że ta implementacja nie istnieje, parser zgłosi błąd PLS-00328. Parser języka Java przechodzi przez kod dwukrotnie, dlatego nie trzeba stosować referencji uprzedzających.

Publicznych typów zdefiniowanych przez użytkownika z poziomu pakietu można używać w innych programach, jednak nie jest to możliwe w przypadku typów prywatnych. Bloki nazwane zdefiniowane w implementacji komponentu to moduły prywatne wchodzące w skład czarnych skrzynek w postaci lokalnych funkcji lub procedur (szczegółowe informacje na temat czarnych skrzynek znajdziesz w ramce „Czarne skrzynki” w rozdziale 8.).

314

Część II Programowanie w języku PL/SQL

Typów można używać w zewnętrznych blokach języka PL/SQL. Do zmiennych pakietu można przypisywać wartości i używać ich. Stałe to specjalne zmienne, do których nie można przypisywać danych. Można tylko używać wartości takich elementów jako prawych operandów. W zewnętrznych blokach języka PL/SQL można wywoływać funkcje i procedury pakietu, jeśli zostały zadeklarowane w specyfikacji. Komponenty zadeklarowane tylko w ciele pakietu mogą wywoływać komponenty publiczne na podstawie ich deklaracji. W rozdziale 4. zostały opisane skalarne i złożone typy danych dostępne w blokach anonimowych i nazwanych. Wszystkie te typy są dostępne także w pakietach, które stanowią przecież bloki nazwane. Programista może użyć dowolnej zmiennej skalarnej lub złożonej dostępnej w specyfikacji lub ciele pakietu. Jeśli programista zdefiniuje taki typ w specyfikacji pakietu, będzie on dostępny dla jednostek mających odpowiednie uprawnienia lub umieszczonych na białej liście. Jeśli jednak definicja znajdzie się w ciele pakietu, dany element stanie się prywatny i dostępny tylko dla bloków języka PL/SQL zaimplementowanych w ciele tego pakietu. Podobnie jak w funkcjach i procedurach, zmienne, typy i komponenty można deklarować w specyfikacji lub ciele pakietu. Z drugiej strony typy danych ze specyfikacji pakietu — inaczej niż elementy z niezależnych funkcji i procedur — są dostępne w innych blokach języka PL/SQL. Programista musi jedynie poprzedzić nazwę komponentu nazwą pakietu i selektorem komponentów (.), tak jak w poniższym wywołaniu: EXECUTE pakiet.procedura('łańcuch_znaków');

Typy w pakietach mogą być także współużytkowanymi kursorami. W bazie danych Oracle 12c takie kursory są w czasie wykonywania programu strukturami dostępnymi na wyłączność, co oznacza, że w danym momencie może je uruchomić tylko jeden proces.

Przeciążanie Przeciążanie polega na utworzeniu więcej niż jednej funkcji lub procedury o takim samym identyfikatorze (takiej samej nazwie komponentu), ale o różnych sygnaturach. Sygnatury funkcji i procedur są określane przez ich listy parametrów formalnych. Komponenty przeciążone mają albo inną liczbę parametrów, albo różne typy parametrów znajdujących się na tych samych pozycjach. Choć język PL/SQL obsługuje (od wersji Oracle 11g) notację opartą na nazwie, mieszaną i opartą na pozycji, parametry formalne wyróżnia wyłącznie ich pozycja i typ danych. Jeśli listy różnią się tylko nazwami parametrów, są traktowane jako takie same. Przykładowo nie można przeciążyć funkcji adding przyjmującej dwie liczby, zmieniając nazwy parametrów formalnych: SQL> CREATE OR REPLACE PACKAGE not_overloading IS 2 FUNCTION adding (a NUMBER, b NUMBER) RETURN NUMBER; 3 FUNCTION adding (one NUMBER, two NUMBER) RETURN BINARY_INTEGER; 4 END not_overloading; 5 /

Język PL/SQL umożliwia przeciążenie funkcji i procedur przez zmianę nazw zmiennych, jednak w czasie wykonywania programu wieloznaczność spowoduje wtedy zgłoszenie wyjątku PLS-00307. Można skompilować powyższą specyfikację pakietu i zaimplementować jego ciało bez wywołania błędu czasu kompilacji. Jednak przy próbie wywołania przeciążonej funkcji okaże się, że istnieje zbyt wiele jej deklaracji. Ta wieloznaczność spowoduje zgłoszenie wyjątku PLS-00307. Typ danych wartości zwracanej przez funkcję nie jest częścią sygnatury. Dlatego sygnatury dwóch funkcji różniących się tylko tym typem nie będą traktowane jako odmienne. Zmiana deklaracji w poniższy sposób umożliwia wywołanie funkcji adding. Teraz typy danych użyte w obu deklaracjach różnią się od siebie. SQL> CREATE OR REPLACE 2 FUNCTION adding 3 FUNCTION adding 4 FUNCTION adding

PACKAGE overloading IS (a NUMBER, b NUMBER) RETURN NUMBER; (a VARCHAR2, b NUMBER) RETURN NUMBER; (a NUMBER, b VARCHAR2) RETURN NUMBER;

Rozdział 9. Pakiety

5 6 7

315

FUNCTION adding (a VARCHAR2, b NUMBER) RETURN BINARY_INTEGER; END overloading; /

Poniższy rysunek przedstawia działanie przeciążania wewnątrz czarnej skrzynki. W pierwszej sygnaturze typ danych drugiego parametru to CLOB, a trzeciego — DATE, podczas gdy w drugiej sygnaturze ich pozycje są zamienione. Na rysunku ilustrującym przykładową funkcję adding znalazłyby się dwa okrągłe wloty przy parametrach typu VARCHAR2 i dwa wloty prostokątne przy parametrach typu NUMBER.

Przy wywoływaniu przeciążonych funkcji i procedur należy podać listę argumentów. W czarnej skrzynce silnik wykonawczy określa kolejność i typy danych argumentów, a następnie dopasowuje wywołanie do różnych odmian podprogramu. Kiedy znajdzie pasującego kandydata, przekazuje argument do odpowiedniej wersji danej funkcji lub procedury. Informacje o przeciążonych wersjach są przechowywane w katalogu bazy danych. Można je wyświetlić przy użyciu widoków CDB_ARGUMENTS, ALL_ARGUMENTS, DBA_ARGUMENTS lub USER_ARGUMENTS. Jeśli nie ma sygnatury pasującej do wywołania, silnik wykonawczy języka PL/SQL zgłosi błąd ORA-06576, który informuje o wywołaniu nieprawidłowej funkcji lub procedury.

316

Część II Programowanie w języku PL/SQL

Implementowanie zmiennych i typów odbywa się tak samo niezależnie od tego, czy programista chce to zrobić w specyfikacji, czy w ciele pakietu. Ponadto wyjaśniono tu, że funkcje i procedury nie tylko udostępniają wszystkie możliwości omówione w rozdziale 8., ale ponadto umożliwiają przeciążanie. Przeciążanie jest mechanizmem z języków obiektowych i umożliwia definiowanie funkcji o tej samej nazwie, ale o różnych sygnaturach (listach parametrów). Przeciążać można funkcje i procedury zdefiniowane w specyfikacjach pakietu. Niestety, nie można przeciążać funkcji i procedur zdefiniowanych oraz zaimplementowanych w ciele pakietu. W trzech następnych punktach opisano definiowanie i implementowanie pakietów. Poznasz w nich szczegóły dotyczące specyfikacji i ciała pakietu, a także dowiesz się, jak zarządzać pakietami za pomocą katalogu bazy Oracle Database 12c. Powtórzenie materiału W tym podrozdziale opisano następujące zagadnienia dotyczące architektury pakietów:  Pakiety mają publiczną specyfikację i prywatne ciało.  Prywatne ciało obejmuje implementację wszystkich elementów zdefiniowanych w publicznej specyfikacji, a także może zawierać implementację prywatnych typów danych zdefiniowanych przez użytkownika, zmiennych, kursorów, funkcji i procedur.  W pakiecie można określić białą listę określającą, które jednostki mają uprawnienia do wywoływania danego pakietu.  Specyfikacja pakietu sprawia, że nie trzeba tworzyć referencji uprzedzających dla funkcji i procedur, ponieważ ich definicje znajdują się w katalogu danych.  Prywatne funkcje i procedury z ciała pakietu wymagają namiastek pełniących funkcję referencji uprzedzających, ponieważ w procesie kompilacji używany jest jednoprzebiegowy parser.  Pakiety obsługują przeciążanie sygnatur publicznych funkcji i procedur.

Specyfikacja pakietu Specyfikacja pakietu służy do zadeklarowania go jako czarnej skrzynki w schemacie użytkownika. Deklaracja pozwala opublikować dostępne funkcje i procedury. Po skompilowaniu specyfikacji pakietu można tworzyć pakiety i funkcje, które z niej korzystają. Implementacja nie jest potrzebna do momentu testowania fragmentów kodu. Możesz użyć polecenia DESCRIBE środowiska SQL*Plus do wyświetlenia wszystkich funkcji i procedur wchodzących w skład pakietu. Niestety, informacje te nie obejmują zmiennych ani typów danych. Aby znaleźć zmienne i typy danych pakietu, należy zapoznać się z jego specyfikacją dostępną w kolumnie text w widokach administracyjnych CDB_SOURCE, ALL_SOURCE, DBA_SOURCE i USER_SOURCE. Choć kolumna text zawiera dostępne w katalogu danych informacje na temat specyfikacji pakietu, nie zawsze obejmuje implementację, ponieważ kod może zostać opakowany w trakcie kompilacji. Opakowywanie implementacji powoduje zamaskowanie kodu w wyniku przekształcenia go na niezrozumiały ciąg znaków. Opakowywanie kodu w języku PL/SQL opisano w dodatku F. Informacje z katalogu danych, a czasem także sam kod źródłowy programów z uprawnieniami jednostki definiującej, można pobrać z widoku USER_SOURCE. Aby wyświetlić dane o programach mających uprawnienia jednostki wywołującej, które zwykle należą do innego użytkownika, musisz mieć uprawnienia administratora do widoku administracyjnego CDB_SOURCE, ALL_SOURCE lub DBA_SOURCE. W środowisku SQL*Plus można pobrać dane z takich widoków za pomocą poniższych poleceń formatujących i instrukcji SELECT: SQL> SQL> SQL> SQL> SQL> SQL> SQL> 2

-- Ustawianie przełamania strony na wartość maksymalną w SQL*Plus. SET PAGESIZE 49999 -- Kolumna ma wyświetlać 80 znaków. COLUMN line FORMAT 99999 HEADING "Wiersz nr" COLUMN text FORMAT A73 HEADING "Text" -- Zapytanie o kod źródłowy dostępny na koncie użytkownika. SELECT line , text

Rozdział 9. Pakiety 3 4

317

FROM user_source WHERE UPPER(name) = UPPER('&input_name');

W Oracle Database 12c i wcześniejszych wersjach tej bazy danych wszystkie metadane są domyślnie przechowywane jako tekst zapisany przy użyciu wielkich liter. W Oracle 12c można to zmienić, co opisuje ramka „Nazwy tabel i kolumn z uwzględnianiem wielkości znaków” w dodatku B. Funkcja UPPER zastosowana do nazwy kolumny gwarantuje, że zawsze będzie można dopasować ją do łańcuchów znaków o wyłącznie wielkich literach.

W pięciu najbliższych punktach opisano elementy prototypu i dyrektywę prekompilatora SERIALLY_REUSABLE dla specyfikacji pakietu. Dowiesz się też, jak używać zmiennych, typów i komponentów. W tych punktach omówiono różnice w działaniu pakietów przeznaczonych do seryjnego uruchamiania oraz nieprzeznaczonych do tego. Domyślnie pakiety nie są przeznaczone do seryjnego uruchamiania. Dostępne typy to struktury, kursory i kolekcje.

Elementy prototypu Wszystkie komponenty w poniższym prototypie specyfikacji pakietu są opcjonalne, ponieważ można utworzyć pakiet, który nie zawiera żadnych elementów. Tu przedstawiono możliwości związane ze zmiennymi, typami i podprogramami (funkcjami i procedurami) pakietu. W porównaniu z poprzednim wydaniem tej książki w prototypie specyfikacji pakietu znalazły się dwa nowe elementy: opcjonalna klauzula EDITIONABLE (wprowadzona w wersji Oracle Database 11g Release 2) i służąca do tworzenia białych list klauzula ACCESSIBLE BY (wprowadzona w Oracle Database 12c). Ogólny prototyp specyfikacji pakietu wygląda następująco: CREATE [OR REPLACE] PACKAGE nazwa_pakietu [ EDITIONABLE | NONEDITIONABLE ] [AUTHID {DEFINER | CURRENT_USER}] [ ACCESSIBLE BY ( FUNCTION nazwa_schematu.nazwa_funkcji [, PROCEDURE nazwa_schematu.nazwa_procedury [, PACKAGE nazwa_schematu.nazwa_pakietu [, TYPE nazwa_schematu.nazwa_typu_obiektowego]]] )] IS [PRAGMA SERIALLY_REUSABLE;] [nazwa_zmiennej [CONSTANT] skalarny_typ_danych [:= wartość];] [nazwa_kolekcji [CONSTANT] typ_danych_kolekcji [:= konstruktor];] [nazwa_obiektu [CONSTANT] obiektowy_typ_danych [:= konstruktor];] [TYPE struktura_rekordowa IS RECORD ( nazwa_pola typ_danych [,nazwa_pola typ_danych [, ... ]]);] [CURSOR nazwa_kursora [(nazwa_parametru [,nazwa_parametru [, ... ]])] IS instrukcja_select;]

typ_danych typ_danych

[TYPE kursor_ref IS REF CURSOR [RETURN { wiersz_katalogu | struktura_rekordowa }];] [nazwa_wyjątku_użytkownika EXCEPTION; [PRAGMA EXCEPTION_INIT(nazwa_wyjątku_użytkownika,-20001);]] [FUNCTION nazwa_funkcji [( parametr [IN][OUT] [NOCOPY] typ_danych_sql | typ_danych_plsql [, parametr [IN][OUT] [NOCOPY] typ_danych_sql | typ_danych_plsql [, ... ]])] RETURN { typ_danych_sql | typ_danych_plsql } [ DETERMINISTIC | PARALLEL_ENABLED ] [ PIPELINED ] [ RESULT_CACHE [ RELIES_ON (nazwa_tabeli) ]];]

318

Część II Programowanie w języku PL/SQL

[ PRAGMA RESTRICT_REFERENCES ({ DEFAULT | nazwa_funkcji } , opcja [, opcja [, ... ]]); ] [PROCEDURE nazwa_procedury [( parametr1 [IN][OUT] [NOCOPY] typ_danych_sql | typ_danych_plsql [, parametr2 [IN][OUT] [NOCOPY] typ_danych_sql | typ_danych_plsql [, parametr(n+1) [IN][OUT] [NOCOPY] typ_danych_sql | typ_danych_plsql ])];] END nazwa_pakietu; /

Klauzula OR REPLACE jest bardzo ważna, ponieważ jeśli programista jej nie użyje, będzie musiał usunąć specyfikację pakietu przed próbą jego ponownego zadeklarowania.

Pakiety z uprawnieniami jednostki definiującej mają atrybut AUTHID o wartości DEFINER, natomiast w pakietach z uprawnieniami jednostki wywołującej ten atrybut ma wartość CURRENT_USER. Uprawnienia jednostek definiującej i wywołującej opisano szczegółowo w dodatku A, a pokrótce także w ramce „Programy z poziomu schematu” w dalszej części rozdziału. Klauzula EDITIONABLE umożliwia jednoczesne utworzenie wielu egzemplarzy tego samego pakietu w bazie danych. Pakiety z tą klauzulą są dostępne tylko w niektórych edycjach bazy danych i umożliwiają niemal natychmiastowe aktualizowanie baz. Więcej informacji o edycjach baz Oracle Database 12c Release 2 i Oracle Database 12c znajdziesz w poradniku Oracle Database Advanced Application Developer’s Guide. Klauzula ACCESSIBLE BY służy do tworzenia białych list w pakietach. W ten sposób można określić listę jednostek uprawnionych do wywoływania publicznych funkcji i procedur. Białe listy dotyczą także publicznych zmiennych i typów danych. Omówienie białych list jako nowej funkcji bazy znajdziesz w rozdziale 2. Białą listę dla specyfikacji pakietu można utworzyć w następujący sposób: SQL> CREATE OR REPLACE PACKAGE small_one 2 ACCESSIBLE BY 3 ( FUNCTION video.gateway 4 , PROCEDURE video.backdoor 5 , PACKAGE video.api 6 , TYPE video.hobbit ) IS 7 FUNCTION add 8 ( lv_a NUMBER 9 , lv_b NUMBER ) RETURN NUMBER; 10 END small_one; 11 /

Klauzulę ACCESSIBLE BY można zastosować tylko w specyfikacji pakietu, co tu pokazano w wierszach od 2. do 6. Czekanie z przedstawieniem ciała (implementacji) pakietu small_one do punktu „Ciało pakietu” spowodowałoby zerwanie ciągłości opisu, dlatego ciało to zaprezentowano poniżej: SQL> CREATE OR REPLACE PACKAGE BODY small_one IS 2 FUNCTION add 3 ( lv_a NUMBER 4 , lv_b NUMBER ) RETURN NUMBER IS 5 BEGIN 6 RETURN lv_a + lv_b; 7 END add; 8 END small_one; 9 /

Zwróć uwagę na brak klauzuli ACCESSIBLE BY w ciele pakietu. Następnie można utworzyć funkcję gateway wywołującą funkcję small_one.add. Ta funkcja się skompiluje, ponieważ jest wymieniona na białej liście w specyfikacji pakietu small_one. Funkcja gateway przyjmuje dwa parametry i przekazuje je do powiązanej z białą listą funkcji add. Ilustruje to przedstawiony poniżej kod funkcji gateway: SQL> 2 3 4

CREATE OR REPLACE FUNCTION gateway ( pv_a NUMBER , pv_b NUMBER ) RETURN NUMBER IS BEGIN

Rozdział 9. Pakiety 5 6 7

319

RETURN small_one.add(pv_a, pv_b); END; /

Teraz możesz przetestować całe rozwiązanie za pomocą poniższego bloku anonimowego: SQL> BEGIN 2 dbms_output.put_line(gateway(2,2)); 3 END; 4 /

W tym wywołaniu do funkcji gateway przekazywane są dwie dwójki, a zwracany wynik to cztery.

Dyrektywa prekompilatora SERIALLY_REUSABLE Dyrektywy (instrukcji prekompilatora) PRAGMA SERIALLY_REUSABLE można użyć tylko w kontekście pakietu. Trzeba ją podać zarówno w specyfikacji, jak i w ciele pakietu. Inaczej działają opisane wcześniej instrukcje PRAGMA związane z wyjątkami, funkcjami i procedurami. Dyrektywa PRAGMA SERIALLY_ REUSABLE jest ważna, jeśli programista chce współużytkować zmienne i kursory, ponieważ gwarantuje ich stan początkowy przy każdym wywołaniu. Kwalifikator CONSTANT pozwala określić zmienne jako przeznaczone tylko do odczytu i statyczne. Choć nie wspomniano o tym we wcześniejszych rozdziałach, zmienną można oznaczyć jako CONSTANT (stała) w dowolnym bloku anonimowym lub nazwanym. Do stałych nie można przypisywać wartości. Stałe są bardziej przydatne, jeśli programista współużytkuje je między specyfikacjami pakietów. Do zmiennych pakietu zdefiniowanych jako stałe nie można przypisywać wartości. Próba wykonania takiej operacji spowoduje zgłoszenie wyjątku PLS-00363.

Wyjątki pakietu to przydatne narzędzie programistyczne, ponieważ można ich używać w innych jednostkach programu. W tym celu wystarczy w innym programie poprzedzić nazwę wyjątku nazwą pakietu i selektorem komponentów. Przykładowa deklaracja wyjątku wygląda następująco: sample_exception EXCEPTION; PRAGMA EXCEPTION_INIT(sample_exception,-20003);

Stosowanie wyjątków opisano w rozdziale 7. W pakietach możne je deklarować w taki sam sposób, jak w niezależnych funkcjach i procedurach oraz w blokach anonimowych. W punkcie „Systemowe kursory referencyjne” w rozdziale 4. opisano tylko kursory referencyjne o ścisłej i słabej kontroli typów. Omówiono w nim kursory referencyjne o ścisłej kontroli typów jako typy danych zakotwiczone do typu obiektu z katalogu, na przykład do tabeli lub widoku. Specyfikacje pakietów umożliwiają współużytkowanie definicji typów rekordowych z innymi blokami języka PL/SQL. To rozwiązanie pozwala współdzielić typy rekordowe z innymi blokami języka PL/SQL i zakotwiczać typ kursorów referencyjnych do typów rekordowych zdefiniowanych w pakiecie. Powyższa definicja funkcji zagnieżdżonej pokazuje, że wyniki można zapisywać w potoku lub pamięci podręcznej. Należy pamiętać, że funkcje potokowe zwracają wartości typu kolekcji. Jeśli programista użyje innego typu, w trakcie kompilacji wystąpi błąd PLS-00630 informujący o tym, że funkcja musi zwracać obsługiwany typ kolekcji. Zapisywanie wyników działania funkcji w pamięci podręcznej działa w przypadku funkcji niezależnych (z poziomu schematu), nie można jednak korzystać z tego mechanizmu w funkcjach należących do pakietów.

Tabela w ramce „Problemy ze zgodnością funkcji wstecz” w rozdziale 8. zawiera opis opcji prekompilatora, które ograniczają działanie funkcji. W specyfikacjach pakietów używany jest tryb DEFAULT, który powoduje zastosowanie ograniczeń do wszystkich funkcji zdefiniowanych w danym pakiecie. Warto pamiętać, że opcje prekompilatora służące do ograniczania działania funkcji oraz opcja TRUST są dostępne przede wszystkim z uwagi na zachowanie zgodności wstecz, a nie do pisania nowych programów.

320

Część II Programowanie w języku PL/SQL

Programy z poziomu schematu Składowane funkcje, procedury, pakiety i obiekty to programy z poziomu schematu. Tylko takie programy można powiązać z uprawnieniami jednostki definiującej lub wywołującej. Tryb domyślny to uprawnienia jednostki definiującej, co oznacza, że kod działa z uprawnieniami właściciela schematu. Programy można też zdefiniować w modelu opartym na uprawnieniach jednostki wywołującej. W tym celu trzeba dołączyć atrybut AUTHID o wartości CURRENT_USER. Przy stosowaniu tego modelu program działa z uprawnieniami schematu, który wywołał dany komponent. Przy uprawnieniach jednostki definiującej program działa z uprawnieniami schematu, do którego należy. Jest to rozwiązanie dobre przy przetwarzaniu scentralizowanym. Użycie atrybutu AUTHID o wartości DEFINER powoduje, że program z poziomu schematu będzie działał z uprawnieniami jednostki definiującej, jednak nie trzeba jawnie podawać tej wartości, ponieważ jest ona domyślna. Model oparty na uprawnieniach jednostki wywołującej wymaga przechowywania wielu kopii tabel i widoków w różnych schematach lub bazach danych. Specyfikacja pakietu definiuje go. Ciało pakietu zawiera jedynie implementację deklaracji podanej w specyfikacji. Specyfikacja pakietu to program z poziomu schematu. Można zdefiniować pakiet z uprawnieniami jednostki definiującej lub wywołującej, a wszystkie komponenty pakietu dziedziczą ten sam tryb działania. Próba określenia trybu działania funkcji lub procedur wchodzących w skład pakietu wywoła wyjątek PLS-00157. Funkcje i procedury zdefiniowane w ramach pakietu nie są programami z poziomu schematu. Są to zagnieżdżone komponenty pakietu i dziedziczą jego tryb działania.

Zmienne Pakiety domyślnie nie umożliwiają seryjnego wielokrotnego uruchamiania. Oznacza to, że drugi użytkownik nie ma gwarancji, że będzie używał tego samego pakietu, jeśli wcześniej wywołał go ktoś inny. Ten domyślny tryb działa dobrze, jeśli w specyfikacji pakietu nie zadeklarowano zmiennych ani kursorów współużytkowanych, ponieważ funkcje i procedury umożliwiają wielokrotne wywoływanie (przynajmniej wtedy, gdy nie zależą od zmiennych pakietu). Jeśli pakiet zawiera zmienne współużytkowane, należy zawsze tworzyć go jako pakiet do seryjnego uruchamiania. Aby zdefiniować pakiet do seryjnego uruchamiania, należy umieścić dyrektywę SERIALLY_REUSABLE PRAGMA w specyfikacji pakietu. Ta dyrektywa zmienia działanie zmiennych pakietu. Pakiety do seryjnego uruchamiania tworzą nową (świeżą) kopię pakietu, jeśli zostaną wywołane przez inną jednostkę programu, podczas gdy pakiety domyślne (nie do seryjnego uruchamiania) powtórnie wykorzystują zmienne. PRAGMA SERIALLY_REUSABLE;

Choć programista deklaruje wtedy zmienne w taki sam sposób, jak w innych blokach anonimowych i nazwanych, nie są one ukryte wewnątrz czarnej skrzynki. Zmienne z poziomu pakietu są dostępne także w innych blokach języka PL/SQL, co oznacza, że są współużytkowane. Ponadto różne programy mogą je modyfikować. Czas istnienia zmiennych z poziomu pakietu zmienia się w poszczególnych sesjach. Ten okres może rozciągać się na czas trwania połączenia, lecz może ulec skróceniu, jeśli inne pakiety spowodują usunięcie zmiennej z obszaru SGA. Starsze i mniej używane pakiety z czasem wygasają, ponieważ do zarządzania obszarem SGA służy algorytm usuwający najrzadziej wywoływane elementy. Wspomniany algorytm pełni w bazie danych funkcję narzędzia do przywracania pamięci i bardzo przypomina taki mechanizm z maszyny wirtualnej języka Java (ang. Java Virtual Machine — JVM). Umożliwianie innym jednostkom programu modyfikowania zmiennych z poziomu pakietu to zwykle złe rozwiązanie. Powoduje ono powiązanie działania dwóch lub większej liczby programów na podstawie elementu, który może nieoczekiwanie zmienić stan. Zgodnie z ogólną regułą należy unikać stosowania zmiennych publicznych. Lepiej implementować zmienne pakietu w ciele. Wtedy działają one jak atrybuty chronione w językach obiektowych (takich jak C++, C# i Java). Programista może korzystać ze współużytkowanych stałych i zmiennych zapisanych w specyfikacji pakietu. Stałe mają niezmienną wartość niezależnie od tego, czy pakiet jest przeznaczony do seryjnego uruchamiania, czy nie. Zmienne nigdy nie mają stałej wartości. Pakiet do seryjnego uruchamiania gwarantuje początkowe wartości zmiennych, ponieważ przy jego wywołaniu zawsze powstaje nowa kopia pakietu. Zwykłe pakiety tego nie zapewniają. Pakiet nieprzeznaczony do seryjnego uruchamia-

Rozdział 9. Pakiety

321

nia zwraca albo wartość początkową, albo ostatnio zapisaną. Program używa tej ostatniej wartości, jeśli pakiet został już wywołany w tej samej sesji i wciąż znajduje się w obszarze SGA. Poniższy kod to specyfikacja pakietu shared, który posłuży do przedstawienia działania pakietów nieprzeznaczonych do seryjnego uruchamiania. Ten pakiet zawiera stałą i zmienną. Można go użyć do przetestowania zmiennych współużytkowanych. SQL> CREATE OR REPLACE PACKAGE shared IS 2 lv_protected CONSTANT NUMBER := 1; 3 lv_unprotected NUMBER := 1; 4 END shared; 5 /

Procedura change_unprotected zmienia stan zmiennych z poziomu pakietu, a następnie wyświetla wartość zmiennej lv_unprotected. Przyjmuje jeden parametr formalny, którym może być dowolna liczba. SQL> 2 3 4 5 6 7 8 9 10 11 12 13

CREATE OR REPLACE PROCEDURE change_unprotected (pv_value NUMBER) IS /* Deklaracja początkowej wartości zmiennej pakietu. */ lv_package_var NUMBER := shared.lv_unprotected; /* Powoduje, że dana jednostka działa w odrębnej sesji. */ PRAGMA AUTONOMOUS_TRANSACTION; BEGIN shared.lv_unprotected := shared.lv_unprotected + pv_value; dbms_output.put_line( 'Wywołanie ['||pv_value||'] + ['||lv_package_var||']' || ' = ['||shared.lv_unprotected||']'); END change_unprotected; /

Dostęp do zmiennych ze specyfikacji pakietu można uzyskać w blokach języka PL/SQL, ale już nie w poleceniach języka SQL.

Wiersz 4. pobiera wartość zmiennej z poziomu pakietu przed przypisaniem nowej wartości w wierszu 8. Wiersze od 9. do 11. wyświetlają dodawaną liczbę, początkową wartość zmiennej lv_unprotected z poziomu pakietu, a także nową wartość tej zmiennej. Można sprawdzić trwałość współużytkowanej zmiennej z poziomu pakietu, uruchamiając kilkakrotnie przedstawiony blok anonimowy. Poniższy kod czterokrotnie wywołuje funkcję change_unprotected. SQL> BEGIN 2 FOR i IN 1..4 LOOP 3 change_unprotected(i); 4 END LOOP; 5 END; 6 /

Ten program wyświetli następujące dane: Wywołanie Wywołanie Wywołanie Wywołanie

[1] [2] [3] [4]

+ + + +

[1] [2] [4] [7]

= = = =

[2] [4] [7] [11]

Zauważ, że dodawana liczba zwiększa się o jeden, a początkowa wartość zaczyna się od 1, a następnie staje się nową wartością przy każdym kolejnym wywołaniu funkcji change_unprotected. Takie zwiększanie wartości trwa do czasu unieważnienia pakietu w obszarze SGA lub zmiany połączeń. Poniższe polecenie powoduje unieważnienie pakietu w obszarze SGA i wyzerowanie jego wartości: SQL> ALTER PACKAGE shared_variables COMPILE SPECIFICATION;

Jeśli procedura znajdzie się w pakiecie do seryjnego uruchamiania, zawsze zwróci liczbę 3. Dzieje się tak, ponieważ każde wywołanie pakietu spowoduje utworzenie jego nowej kopii. Pakiety do seryjnego uruchamiania ponownie inicjują wartości zmiennych współużytkowanych. Jedyna różnica między zmienną do seryjnego używania a stałą polega na tym, że stała nigdy nie zmienia wartości, podczas

322

Część II Programowanie w języku PL/SQL

gdy w przypadku zmiennej jest to możliwe. Jeśli jednak pakiet jest przeznaczony do seryjnego uruchamiania, nowa wartość zmiennej zostaje utracona przy kolejnym wywołaniu danego pakietu. Zgodnie z praktyczną regułą zmienne w specyfikacji pakietu zawsze powinny być stałymi.

Typy danych Są dwa ogólne rodzaje typów danych, które można definiować w pakietach: statyczne i dynamiczne. Typy danych to zwykle struktury, kolekcje, kursory referencyjne i kursory języka PL/SQL. Wszystkie one mogą być dynamiczne lub statyczne. Typ danych jest dynamiczny, jeśli programista w deklaracji zakotwiczy go do definicji wiersza lub kolumny. Jak opisano to w punkcie „Kotwiczenie atrybutów i tabel” w rozdziale 3., do zakotwiczania typu do typu wiersza służy atrybut %ROWTYPE, podczas gdy atrybut %TYPE służy do zakotwiczania do typu kolumny. Typy statyczne opierają się na jawnie podanych typach danych języka SQL, takich jak DATE, INTEGER, NUMBER lub VARCHAR2. Zgodnie z ogólną zasadą specyfikacje pakietów są niezależne od innych obiektów z poziomu schematu. Programista tworzy zależności, kiedy zakotwicza typy zadeklarowane w specyfikacji pakietu do obiektów z katalogu, takich jak tabele i widoki. Jeśli programista zmodyfikuje taki obiekt, specyfikacja pakietu stanie się nieprawidłowa. Jak opisano to w dalszej części rozdziału, w punkcie „Zarządzanie pakietami w katalogu bazy danych”, zmiany w specyfikacji pakietu mogą doprowadzić do reakcji kaskadowej i spowodować brak poprawności wielu ciał pakietów i niezależnych programów z poziomu schematu. Pseudotypy (atrybuty) %ROWTYPE i %TYPE działają jak pseudotypy, ponieważ dziedziczą podstawowy typ katalogowy odpowiednio po tabeli lub kolumnie. Co ważniejsze, pośrednio zakotwiczają typ zmiennej języka PL/SQL do typu z katalogu bazy danych, współużytkowanych kursorów z pakietu lub kursorów lokalnych. Te elementy są także nazywane atrybutami, ponieważ poprzedza je wskaźnik atrybutów (symbol %). Należy pamiętać, że te atrybuty dziedziczą typ danych i zakotwiczają typ zmiennej do obiektów z katalogu bazy danych.

Oprócz podziału typów w pakietach na dynamiczne i statyczne warto wyróżnić kursory współużytkowane zadeklarowane w pakiecie. Są one dynamiczne pod tym względem, że przy kolejnych wywołaniach mogą zwracać różne dane. Inne typy z pakietów nie dziedziczą niczego oprócz wartości domyślnej, którą programista może przypisać do nich w deklaracji. Typów rekordowych i kolekcji języka PL/SQL zadeklarowanych w specyfikacji pakietu można używać jako typów parametrów formalnych i wartości zwracanych przez funkcję w nazwanych blokach języka PL/SQL, nie można natomiast stosować ich w instrukcjach języka SQL. Bloki języka PL/SQL używające rekordów i kolekcji z poziomu pakietu są od niego zależne. Jeśli specyfikacja pakietu stanie się nieprawidłowa, niepoprawne będą też zewnętrzne jednostki programów, które od niego zależą. W punkcie „Klauzula PIPELINED” w rozdziale 8. znajduje się przykład zastosowania tej techniki. Zadeklarowana tam specyfikacja pakietu pipelined obejmuje typ rekordowy i typ kolekcji. Ten ostatni jest zależny od struktury rekordu. Niezależna funkcja potokowa pf zwraca tabelę zagregowaną do środowiska SQL. Funkcja ta używa typu kolekcji z poziomu pakietu, który to typ pośrednio opiera się na strukturze rekordu z poziomu pakietu. Ten przykładowy kod pokazuje, jak w zewnętrznych blokach języka PL/SQL używać typów rekordowych i kolekcji ze specyfikacji pakietu. Zadeklarowanie kursora współużytkowanego w specyfikacji pakietu zakotwicza typ kursora do tabel lub widoków użytych w instrukcji SELECT. Uzależnia to specyfikację pakietu od określonych tabel lub widoków. Zmiany wprowadzone w tych obiektach powodują, że nieprawidłowe stają się specyfikacja pakietu i wszystkie ciała pakietów powiązaną z tą niepoprawną specyfikacją. Różne jednostki programów mogą jednocześnie kierować zapytania do kursorów współużytkowanych. Pierwszy program, który otworzy kursor, uzyskuje nad nim kontrolę do czasu zwolnienia go przy użyciu polecenia CLOSE. W wersjach starszych niż Oracle 11g ta operacja nie była spójna ze względu na odczyt, a aby zapewnić tę spójność, trzeba było zadeklarować pakiet jako przeznaczony do seryjnego uruchamiania. Próba pobrania danych ze współużytkowanego kursora otwartego przez inną jed-

Rozdział 9. Pakiety

323

nostkę jest odrzucana. System powinien zgłosić wtedy wyjątek ORA-06511, informujący o tym, że kursor jest już otwarty, jednak ten komunikat można zablokować, jeśli program wywołujący działa w ramach transakcji autonomicznej. Takie transakcje blokują ten błąd i zgłaszają wtedy wyjątek ORA-06519. Niestety, język PL/SQL nie udostępnia polecenia WAIT n (sekund), co umożliwiłoby oczekiwanie na otwarcie kursora. Jest to prawdopodobnie jeden z powodów unikania kursorów współużytkowanych przez niektórych programistów. Poniższy kod przedstawia definicję kursora współużytkowanego w specyfikacji pakietu: SQL> CREATE OR REPLACE PACKAGE shared_types IS 2 CURSOR item_cursor IS 3 SELECT i.item_id 4 , i.item_title 5 FROM item i; 6 END shared_types; 7 /

Następnie można użyć tego kursora w bloku anonimowym lub nazwanym w następujący sposób: SQL> BEGIN 2 FOR i IN shared_types.item_cursor LOOP 3 dbms_output.put_line('['||i.item_id||']['||i.item_title||']'); 4 END LOOP; 5 END; 6 /

Można też używać typów kolekcji ze specyfikacji pakietu; w tym celu należy poprzedzić nazwę typu nazwą pakietu i selektorem komponentów.

Atrakcyjne może wydawać się rozwiązanie polegające na użyciu kursora referencyjnego zdefiniowanego przy użyciu struktury rekordowej. Programista może zdecydować się na to podejście, jeśli nie chce tworzyć widoku. Poniższy kod to deklaracja kursora referencyjnego o ścisłej kontroli typów, którego można używać tylko w blokach języka PL/SQL: SQL> CREATE OR REPLACE PACKAGE shared_types IS 2 CURSOR item_cursor IS 3 SELECT i.item_id 4 , i.item_title 5 FROM item i; 6 TYPE item_type IS RECORD 7 ( item_id item.item_id%TYPE -- Zakotwiczony do typu z katalogu danych. 8 , item_title item.item_title%TYPE); -- Zakotwiczony do typu z katalogu danych. 9 END shared_types; 10 /

Teraz można użyć kursora referencyjnego, jednak nie wraz z kursorem z poziomu pakietu. Kursory referencyjne współdziałają tylko z kursorami jawnymi. Można przetestować współużytkowaną strukturę rekordową z poziomu pakietu i kursor. W tym celu najpierw należy utworzyć SQL-ową zmienną z poziomu sesji (powiązaną): SQL> VARIABLE refcur REFCURSOR

Następnie można uruchomić poniższy blok anonimowy: SQL> 2 3 4 5 6 7 8 9

DECLARE TYPE package_typed IS REF CURSOR RETURN shared_types.item_type; quick PACKAGE_TYPED; BEGIN OPEN quick FOR SELECT item_id, item_title FROM item; :refcur := quick; END; /

Zmienna typu package_typed używa typu ze specyfikacji pakietu do utworzenia kursora referencyjnego o ścisłej kontroli typów, zależnego od typu z poziomu pakietu, a nie od typu tabeli lub widoku z poziomu schematu. Struktura rekordowa to obiekt z katalogu zadeklarowany w kontekście pakietu.

324

Część II Programowanie w języku PL/SQL

Ten blok anonimowy zwraca wynik działania kursora do zmiennej powiązanej. Można sprawdzić zawartość tej zmiennej w następujący sposób: SQL> SELECT :refcur FROM dual;

To zapytanie zwróci wyniki jawnego zapytania określonego w klauzuli FOR. Warto zauważyć, że próba wykonania instrukcji OPEN kursor_referencyjny FOR instrukcje_języka_SQL; się nie powiedzie, jeśli programista zmodyfikuje zapytanie w taki sposób, że będzie ono zwracać inny zestaw typów danych lub kolumn. Podstawianie dynamicznych referencji w literałach z zapytaniem powoduje wyjątek PLS-00455, który informuje o tym, że takiego kursora nie można użyć w dynamicznej instrukcji OPEN języka SQL.

Współużytkowane struktury rekordowe, kolekcje i kursory referencyjne to najbezpieczniejsze typy, które można umieścić w specyfikacji pakietu. Są dostępne dla wszystkich użytkowników o uprawnieniach EXECUTE do pakietu, nie wchodzą jednak w skład danych wyjściowych wyświetlanych przy opisywaniu pakietu. Jak wspomniano na początku punktu „Specyfikacja pakietu”, aby wyświetlić typy ze specyfikacji pakietu, należy wywołać zapytanie o kod źródłowy.

Komponenty — funkcje i procedury Komponenty umieszczane w specyfikacji pakietu to funkcje lub procedury. Jednostki te działają nieco inaczej niż ich odpowiedniki z poziomu schematu. Funkcje i procedury w specyfikacjach pakietów to namiastki odgrywające rolę referencji uprzedzających. Umożliwiają zdefiniowanie przestrzeni nazw dla funkcji i procedur oraz powiązanych z nimi sygnatur. Funkcje mają także typ zwracanej wartości. Informacje ze specyfikacji pakietu znajdują się w widokach CDB_ARGUMENTS, ALL_ARGUMENTS, DBA_ ARGUMENTS i USER_ARGUMENTS dostępnych w katalogu. Te widoki opisano w punkcie „Sprawdzanie zależności” w dalszej części rozdziału. Definicja namiastki funkcji wygląda następująco: FUNCTION nazwa_funkcji (a NUMBER := 1 , b NUMBER) RETURNS NUMBER;

Poniżej przedstawiono namiastkę procedury: PROCEDURE nazwa_procedury (a NUMBER := 1 , b NUMBER);

W tych przykładowych deklaracjach pierwszy parametr ma wartość domyślną, dlatego jest opcjonalny. Jeśli taki parametr znajduje się przed parametrami wymaganymi, należy używać notacji opartej na nazwie. W specyfikacji pakietu można też umieścić instrukcje PRAGMA związane z funkcjami i procedurami z poziomu pakietu. Są dwie instrukcje PRAGMA, które dotyczą albo całego pakietu, albo wszystkich jego funkcji. Dyrektywa SERIALLY_REUSABLE musi znajdować się zarówno w specyfikacji, jak i w ciele pakietu. Dyrektywa RESTRICT_REFERENCES dotyczy wszystkich funkcji, jeśli programista użyje słowa kluczowego DEFAULT zamiast nazwy funkcji. Poniższe dyrektywy ograniczają działanie wszystkich funkcji pakietu i gwarantują, że nie mogą one zmienić stanu bazy danych: SQL> CREATE OR REPLACE PACKAGE financial IS 2 FUNCTION fv 3 (current NUMBER, periods NUMBER, interest NUMBER) RETURN NUMBER; 4 FUNCTION pv 5 (future NUMBER, periods NUMBER, interest NUMBER) RETURN NUMBER; 6 PRAGMA RESTRICT_REFERENCES DEFAULT, WNDS); 7 END financial; 8 /

Rozdział 9. Pakiety

325

W rozdziale 8. znajduje się implementacja funkcji fv i pv zadeklarowanych w powyższej specyfikacji pakietu. Funkcje te nie zapisują stanu, a ich kod należy umieścić w ciele pakietu. Powtórzenie materiału W tym podrozdziale opisano następujące zagadnienia dotyczące specyfikacji pakietów:  Za pomocą specyfikacji pakietu można udostępnić publiczne funkcje i procedury. Specyfikacja nie powoduje jednak opublikowania publicznych zmiennych i typów danych zdefiniowanych przez użytkownika.  Zmienne i typy danych zdefiniowane przez użytkownika w pakiecie nie są standardowo widoczne. Aby je zobaczyć, trzeba fizycznie zbadać specyfikację pakietu zapisaną w katalogu danych.  Aby wyświetlić kod źródłowy specyfikacji pakietu, należy wykorzystać widok administracyjny CDB_ SOURCE, ALL_SOURCE, DBA_SOURCE lub USER_SOURCE, przy czym specyfikacja nie może być opakowana (zobacz dodatek F).  Można ograniczyć dostęp do specyfikacji pakietu przez ustawienie wersji, dla której jest przeznaczony, lub utworzenie białej listy z nazwanymi blokami mającymi uprawnienia do wywoływania publicznych funkcji i procedur pakietu.  Pakiety to komponenty z języków obiektowych. Pakiety umożliwiają przeciążanie funkcji i procedur, a także przechowują stan zmiennych publicznych w czasie trwania sesji lub do momentu unieważnienia pakietu w obszarze SGA bazy danych.  Aby zagwarantować stan publicznych zmiennych i typów danych, trzeba zastosować instrukcję prekompilatora SERIALLY_REUSABLE. Oto krótka porada: w językach obiektowych deklarowanie zmiennych publicznych jest uznawane za złe rozwiązanie.

Ciało pakietu Ciało pakietu obejmuje elementy publiczne i prywatne. Elementy publiczne są zdefiniowane w specyfikacji pakietu. Fragmenty prywatne są deklarowane i implementowane wyłącznie w ciele pakietu. W ciele pakietu trzeba zaimplementować wszystkie publiczne funkcje i procedury (zadeklarowane w specyfikacji pakietu za pomocą prototypów poszczególnych funkcji i procedur). W kodzie w ciele pakietu trzeba zadbać o to, aby sygnatury publicznych funkcji i procedur dokładnie pasowały do ich prototypów. To oznacza, że wszystkie parametry z ich list muszą pasować do nazw, typów danych i wartości domyślnych z powiązanych prototypów. W ciele pakietu znajdują się też prywatne zmienne, typy danych, funkcje i procedury. Prywatne funkcje i procedury programista może zaimplementować w dowolny sposób. W czasie przenoszenia przestarzałych baz z wersji Oracle 9i trzeba pamiętać o tym, że w tej wersji zadeklarowane w specyfikacji parametry formalne nie musiały znajdować się w ciele pakietu. To sprawia, że trzeba ręcznie je dodać, aby przenieść starszy kod w języku PL/SQL do nowszych wersji bazy Oracle Database.

W czterech dalszych punktach opisano elementy prototypu ciała pakietu, a także implementowanie zmiennych, typów i komponentów w ciele. Zapoznasz się też z różnicami między pakietami przeznaczonymi do seryjnego wielokrotnego uruchamiania i pakietami, które nie są do tego przeznaczone. Domyślnie pakiety nie nadają się do seryjnego wielokrotnego uruchamiania. Tak jak we wcześniejszym punkcie „Specyfikacja pakietu”, tak i tu omówienie jest podzielone na fragmenty dotyczące struktur, kursorów i kolekcji.

Elementy prototypu Prototyp ciała pakietu jest bardzo podobny do prototypu jego specyfikacji. W ciele pakietu można zadeklarować niemal wszystkie elementy obsługiwane w specyfikacji. Wyjątki są dwa. W ciele pakietu nie można stosować nowej, wprowadzonej w Oracle Database 12c klauzuli ACCESSIBLE BY, ponieważ jest ona dozwolona tylko w specyfikacji pakietu. Ponadto w ciele pakietu nie można definiować instrukcji

326

Część II Programowanie w języku PL/SQL

PRAGMA dla funkcji. Próba dodania takiej instrukcji powoduje błąd PLS-00708, który informuje, że takie

instrukcje trzeba umieszczać w specyfikacji pakietu. Można stosować polecenie EXCEPTION_INIT PRAGMA dla wyjątków z poziomu pakietów, pod warunkiem jednak, że różnią się one od wyjątków zadeklarowanych w specyfikacji. Ponadto można przeciążać zmienne zadeklarowane w specyfikacji pakietu. W tym celu należy ponownie zadeklarować zmienną w ciele pakietu. To jednak sprawia, że w ciele pakietu nie można korzystać z danej zmiennej. Próba użycia zmiennej o dwóch deklaracjach spowoduje wyjątek PLS-00371 w momencie kompilowania ciała pakietu. Ten wyjątek informuje, że dozwolona jest tylko jedna deklaracja zmiennej. Tak więc firma Oracle nie tylko nie przewidziała możliwości dwukrotnego deklarowania zmiennych, ale też uznała taką operację za błąd. Prototyp ciała pakietu wygląda następująco: CREATE [OR REPLACE] PACKAGE nazwa_pakietu [EDITIONABLE | NONEDITIONABLE] IS [PRAGMA SERIALLY_REUSABLE;] [nazwa_zmiennej [CONSTANT] skalarny_typ_danych [:= wartość];] [nazwa_kolekcji [CONSTANT] typ_danych_kolekcji [:= konstruktor];] [nazwa_obiektu [CONSTANT] obiektowy_typ_danych [:= konstruktor];] [TYPE struktura_rekordowa IS RECORD ( nazwa_pola typ_danych [,nazwa_pola typ_danych [, ... ]]);] [CURSOR nazwa_kursora [(nazwa_parametru [,nazwa_parametru [, ...]])] IS instrukcja_select;]

typ_danych typ_danych

[TYPE kursor_ref IS REF CURSOR [RETURN { wiersz_katalogu | struktura_rekordowa }];] [nazwa_wyjątku_użytkownika EXCEPTION; [PRAGMA EXCEPTION_INIT(nazwa_wyjątku_użytkownika,-20001);]] -- To pełniąca funkcję referencji uprzedzającej namiastka później zaimplementowanej funkcji. [FUNCTION nazwa_funkcji [( parametr [IN][OUT] [NOCOPY] typ_danych_sql | typ_danych_plsql [, parametr [IN][OUT] [NOCOPY] typ_danych_sql | typ_danych_plsql [, ... )] RETURN { typ_danych_sql | typ_danych_plsql } [ DETERMINISTIC | PARALLEL_ENABLED ] [ PIPELINED ] [ RESULT_CACHE [ RELIES_ON (nazwa_tabeli) ]];] -- To pełniąca funkcję referencji uprzedzającej namiastka później zaimplementowanej procedury. [PROCEDURE nazwa_procedury [( parametr [IN][OUT] [NOCOPY] typ_danych_sql | typ_danych_plsql [, parametr [IN][OUT] [NOCOPY] typ_danych_sql | typ_danych_plsql [, ... )];] [FUNCTION nazwa_funkcji [( parametr [IN][OUT] [NOCOPY] typ_danych_sql | typ_danych_plsql [, parametr [IN][OUT] [NOCOPY] typ_danych_sql | typ_danych_plsql [, ... ]])] RETURN { typ_danych_sql | typ_danych_plsql } [ DETERMINISTIC | PARALLEL_ENABLED ] [ PIPELINED ] [ RESULT_CACHE [ RELIES_ON (nazwa_tabeli) ]] IS [ PRAGMA AUTONOMOUS_TRANSACTION;] -- Zobacz zasady opisane w rozdziale 8. deklaracje; -- Zobacz zasady opisane w rozdziale 8. BEGIN wykonywane_instrukcje; -- Zobacz zasady opisane w rozdziale 8.

Rozdział 9. Pakiety [EXCEPTION WHEN określony_wyjątek THEN instrukcje_obsługi_wyjątku;] END [nazwa_funkcji];] [PROCEDURE nazwa_procedury [( parametr [IN][OUT] [NOCOPY] [, parametr [IN][OUT] [NOCOPY] [, ... ]])] IS [ PRAGMA AUTONOMOUS_TRANSACTION;] deklaracje; BEGIN wykonywane_instrukcje; [EXCEPTION WHEN określony_wyjątek THEN instrukcje_obsługi_wyjątku;] END [nazwa_funkcji];] END nazwa_pakietu; /

327

-- Zobacz zasady opisane w rozdziale 7.

typ_danych_sql | typ_danych_plsql typ_danych_sql | typ_danych_plsql -- Zobacz zasady opisane w rozdziale 8. -- Zobacz zasady opisane w rozdziale 8. -- Zobacz zasady opisane w rozdziale 8. -- Zobacz zasady opisane w rozdziale 7.

Jeśli dyrektywa PRAGMA SERIALLY_REUSABLE znajduje się w specyfikacji pakietu, trzeba jej użyć także w ciele. To rozwiązanie różni się od sposobu stosowania wcześniej opisanych instrukcji PRAGMA.

Zmienne Zmienne z poziomu pakietu, czyli zadeklarowane w ciele, różnią się od zmiennych zadeklarowanych w specyfikacji. Do zmiennych z poziomu pakietu nie można uzyskać dostępu poza samym pakietem (to dlatego czasem nazywa się je chronionymi lub prywatnymi). Takich zmiennych mogą używać tylko funkcje i procedury opublikowane w specyfikacji pakietu. Sprawia to, że zmienne te są bardzo podobne do zmiennych egzemplarza z języków obiektowych, na przykład z Javy, ponieważ zachowują stan między wywołaniami funkcji i procedur pakietu (przynajmniej do momentu zakończenia sesji lub wygaśnięcia zmiennej w obszarze SGA). Pakiety działają jak klasy, a funkcje i procedury pakietu przypominają metody z klas z języków obiektowych. Opublikowane funkcje i procedury to jednostki publiczne, funkcje i procedury z poziomu pakietu to jednostki chronione (ograniczone do zasięgu pakietu), a lokalne funkcje i procedury to jednostki prywatne. Zmienne z poziomu pakietu programiści nazywają zamiennie chronionymi lub prywatnymi, jednak należy traktować je jak prywatne dla pakietu i chronione dla funkcji oraz procedur tego pakietu. Poniższa specyfikacja pakietu obejmuje funkcję i procedurę. Funkcja get zwraca wartość zmiennej z ciała pakietu. Procedura set służy do modyfikowania wartości tej zmiennej. Jest to pakiet nieprzeznaczony do seryjnego uruchamiania, co oznacza, że wartość zmiennej jest zachowywana do momentu jej wygaśnięcia w obszarze SGA. SQL> CREATE OR REPLACE PACKAGE package_variables IS 2 /* Deklaracja komponentów pakietu. */ 3 PROCEDURE set(value VARCHAR2); 4 FUNCTION get RETURN VARCHAR2; 5 END package_variables; 6 /

Specyfikacja nie ma informacji o tym, jakie zmienne zostały zaimplementowane w ciele pakietu. Szczegóły implementacji komponentów prywatnych są dostępne wyłącznie w ciele. Publiczne funkcje i procedury mogą używać wszystkich komponentów z poziomu pakietu, na przykład prywatnych zmiennych, typów danych, funkcji i procedur. Inne programy języka PL/SQL mogą korzystać z publicznych funkcji i procedur — przynajmniej wtedy, gdy mają uprawnienia EXECUTE do pakietu lub znajdują się na białej liście uprawnionych jednostek wywołujących. W ciele pakietu deklarowane są prywatne zmienne, typy danych, funkcje i procedury. Publiczne funkcje i procedury mogą korzystać z takich prywatnych komponentów, ponieważ działają w tym samym zasięgu implementacji. Prywatne funkcje i procedury mogą też wywoływać inne prywatne jednostki tego rodzaju. W funkcjach i procedurach z ciała pakietu w wywołaniach innych jednostek tego

328

Część II Programowanie w języku PL/SQL

pakietu nie trzeba podawać jego nazwy ani selektora komponentów (.). Jednak część programistów uważa, że dodawanie nazwy pakietu przed wywołaniami prywatnych funkcji i procedur to dobry zwyczaj. Moim zdaniem z pewnością pozwala to uniknąć wieloznaczności. Ponadto można dodać nazwę pakietu i selektor komponentów przy wskazywaniu lokalnych (prywatnych) zmiennych i typów danych. Poniżej przedstawiono ciało pakietu z implementacją przedstawionej wcześniej specyfikacji: SQL> CREATE OR REPLACE PACKAGE BODY package_variables IS 2 /* Deklaracja zmiennej dostępnej w zasięgu pakietu. */ 3 variable VARCHAR2(20) := 'Wartość początkowa'; 4 /* Implementacja funkcji publicznej. */ 5 FUNCTION get RETURN VARCHAR2 IS 6 BEGIN 7 RETURN variable; 8 END get; 9 /* Implementacja procedury publicznej. */ 10 PROCEDURE set(value VARCHAR2) IS 11 BEGIN 12 variable := value; 13 END set; 14 END package_variables; 15 /

Funkcja get zwraca zmienną z poziomu pakietu. Procedura set umożliwia zmianę wartości tej zmiennej. Po skompilowaniu programu można zadeklarować zmienną z poziomu sesji i przetestować działanie pakietu. Aby zapisać wartość w zmiennej powiązanej, należy wywołać funkcję get. Następnie można skierować zapytanie o tę zmienną: SQL> VARIABLE outcome VARCHAR2(20) SQL> CALL package_variables.get() INTO :outcome; SQL> SELECT :outcome AS outcome FROM dual;

Dane wyjściowe wyglądają następująco: OUTCOME -----------Wartość początkowa

Następnie należy uruchomić procedurę set, aby zmienić wartość zmiennej, oraz ponownie wywołać funkcję get przed wywołaniem zapytania o zmienną powiązaną: SQL> EXECUTE package_variables.set('Nowa wartość'); SQL> CALL package_variables.get() INTO :outcome; SQL> SELECT :outcome AS outcome FROM dual;

Tym razem program zwróci następujące dane wyjściowe: OUTCOME -----------Nowa wartość

Po ponownym uruchomieniu funkcji get z pakietu package_variables w celu powtórzenia testu w tej samej sesji program zadziała inaczej. Tym razem wyświetli tekst „Nowa wartość” zamiast „Wartość początkowa”, ponieważ pakiet nie wygasł jeszcze w obszarze SGA. Polecenie DDL CREATE OR REPLACE powoduje zastąpienie specyfikacji pakietu, pod warunkiem że pojawiły się w niej zmiany. Jeśli specyfikacja okazuje się taka sama, ten krok jest pomijany. Implementację ciała pakietu można zmodyfikować bez zmieniania stanu ani definicji specyfikacji. Można wymusić zmianę i odświeżanie zmiennych przez wywołanie polecenia ALTER, które powoduje ponowną kompilację specyfikacji pakietu. Po tej operacji wszystkie zmienne otrzymują wartości początkowe. Jeśli programista wywoła to polecenie przed uruchomieniem skryptu, program wyświetli te same wyniki, co przy pierwszym wykonaniu. Składnia powodująca ponowną kompilację samej specyfikacji pakietu wygląda następująco: ALTER PACKAGE package_variables COMPILE SPECIFICATION;

Tylko zmienne lokalne (zadeklarowane w funkcjach i procedurach) otrzymują „świeżą” wartość przy każdym wywołaniu. Dzieje się tak, ponieważ nie zachowują wartości między wywołaniami.

Rozdział 9. Pakiety

329

Jeśli programista użyje pakietu do seryjnego uruchamiania zamiast pakietu zwykłego, kod testów zadziała inaczej. Każde wywołanie pakietu do seryjnego wykonywania powoduje utworzenie nowej kopii jego specyfikacji i ciała. Dlatego zmienna z poziomu pakietu zawsze będzie miała tę samą wartość. Pakietu przeznaczonego do seryjnego uruchamiania nie można wywoływać w instrukcji SELECT.

Warto rozważyć deklarowanie pakietów jako bibliotek nieprzeznaczonych do seryjnego uruchamiania. Jeśli programista zastosuje to podejście, powinien unikać tworzenia zmiennych publicznych. Deklarowanie zmiennych publicznych zachęca do stosowania ich w innych programach i tworzenia w ten sposób powiązań między programami. Jeśli już musisz deklarować zmienne publiczne (chciałbym zobaczyć sytuację, w której jest to niezbędne), powinieneś tworzyć je jako stałe w specyfikacji pakietu. Jeżeli chcesz tworzyć niezależne pakiety i uniknąć powiązania (zależności), zadeklaruj zmienne pakietu w jego ciele. Inna możliwość to deklarowanie zmiennych lokalnych w publicznych i prywatnych funkcjach oraz procedurach. Najlepiej jest, gdy wszystkie zmienne w pakiecie są chronione lub prywatne. Ponieważ w języku PL/SQL nie ma formalnych modyfikatorów dostępu, z jakich można korzystać w językach C++, C# lub Java, dostęp do zmiennych określa się na podstawie następujących reguł:  Zmienne zadeklarowane w specyfikacji pakietu są publiczne, co oznacza, że można ich używać w dowolnym innym module kodu w języku PL/SQL.  Zmienne zadeklarowane w ciele pakietu są chronione, dlatego dostęp do nich mają tylko podprogramy tego pakietu.  Zmienne zadeklarowane w sekcji deklaracji podprogramów są lokalne (prywatne) i dostępne tylko dla podprogramu, w którym je umieszczono. Niektórzy programiści, dla których najważniejsza w pracy jest szybkość, nie przestrzegają przedstawionych tu zaleceń. Niestety, pakiety, których autorzy nie dbają o maksymalizowanie niezależności i minimalizowanie powiązań, nie są lepsze od niezależnych funkcji i procedur. Wzorzec projektowy singleton Wzorzec projektowy singleton umożliwia tworzenie tylko jednego egzemplarza obiektu. Zastosowanie tego wzorca gwarantuje, że późniejsze próby utworzenia takiego egzemplarza będą kończyły się niepowodzeniem do momentu usunięcia egzemplarza pierwotnego. Wzorzec ten jest powszechnie używany w obiektowych językach programowania, na przykład w C++, C# i Javie. Można też zapewnić utworzenie tylko jednego egzemplarza pakietu w sesji. W tym celu wystarczy dodać wywołanie określonej funkcji lub procedury o zasięgu lokalnym na początku wszystkich opublikowanych funkcji i procedur. W tej funkcji lub procedurze o zasięgu lokalnym należy umieścić zmienną lokalną, która powinna odpowiadać zmiennej kontrolnej z poziomu pakietu. Jeśli wartości tych zmiennych są takie same, lokalny podprogram powinien zmodyfikować zmienną z poziomu pakietu w celu jego zablokowania. Potrzebna jest też druga funkcja lub procedura o zasięgu lokalnym, wywoływana na końcu wszystkich funkcji i procedur publicznych. Jej zadanie polega na przywracaniu wyjściowego stanu wszystkich zmiennych pakietu. Najłatwiej napisać w tym celu procedurę, która przypisuje tym zmiennym wartości domyślne. Tę procedurę należy następnie wywoływać jako ostatnią instrukcję w funkcjach i procedurach publicznych. Trzeba też pamiętać o przywróceniu wartości zmiennej kontrolnej przy manipulowaniu innymi zmiennymi. Jeśli programista o tym zapomni, pakiet będzie zablokowany do czasu zakończenia sesji lub jego wygaśnięcia w obszarze SGA.

Typy Podobnie jak w specyfikacji pakietu, w ciele można deklarować typy dynamiczne i statyczne. Są to zwykle struktury, kolekcje, kursory referencyjne i kursory języka PL/SQL. Aby zadeklarować typ dynamiczny, należy zakotwiczyć go do typu wiersza lub kolumny (zobacz punkt „Kotwiczenie atrybutów i tabel” w rozdziale 3.). Statyczne typy danych są deklarowane jako typy języka SQL.

330

Część II Programowanie w języku PL/SQL

Ciało pakietu zależy od jego specyfikacji, a także od innych obiektów z poziomu schematu używanych w kodzie komponentów. Typy w ciele pakietu zachowują się prawie tak samo jak w specyfikacji. Jedyna różnica polega na tym, że bloki języka PL/SQL spoza ciała pakietu nie mogą uzyskać dostępu do zadeklarowanych w nim elementów.

Komponenty — funkcje i procedury Komponenty to implementacje funkcji i procedur publicznych lub deklaracje albo definicje funkcji i procedur przeznaczonych do użytku tylko w obrębie pakietu. Komponenty lokalne można też zadeklarować wewnątrz funkcji i procedur publicznych lub dostępnych tylko w pakiecie. Deklaracja komponentu przed jego implementacją to tak zwana referencja uprzedzająca (lub prototyp). Kompletny przykład ilustrujący tę technikę znajdziesz w ramce „Referencje uprzedzające” we wcześniejszej części rozdziału. Definicje komponentów lokalnych obejmują zarówno ich deklarację, jak i implementację. Czasem programista musi zadeklarować komponent, zanim będzie mógł go zaimplementować. Można to zrobić przy użyciu namiastki odgrywającej rolę referencji uprzedzającej funkcji lub procedury. W komponentach można określić tylko to, czy mają działać w ramach transakcji lokalnej, czy autonomicznej. Te pierwsze działają w istniejącym już zasięgu transakcji. Te drugie działają niezależnie we własnym zasięgu. Domyślnie wszystkie funkcje i procedury działają w ramach transakcji lokalnej. Aby zadeklarować funkcję lub procedurę jako autonomiczną, należy użyć dyrektywy AUTONOMOUS_TRANSACTION. W zewnętrznych blokach języka PL/SQL można wywoływać tylko funkcje i procedury publiczne. Funkcji z poziomu pakietu można używać w komponentach trzech rodzajów: publicznych, z poziomu pakietu i lokalnych. Komponenty lokalne są deklarowane i implementowane (czyli definiowane) w komponentach publicznych lub z poziomu pakietu. Inna możliwość polega na zdefiniowaniu komponentu lokalnego wewnątrz innego komponentu tego rodzaju. Zasady deklarowania i implementowania funkcji oraz procedur zawiera rozdział 8. Specyfikacja pakietu components obejmuje tylko getter (funkcję get) i setter (procedurę set). Gettery służą do pobierania danych z czarnej skrzynki, a settery ustawiają wartość początkową lub zmieniają aktualną wartość. Są to podstawowe mechanizmy programowania obiektowego. Jak wspomniano we wcześniejszej części rozdziału, można ich używać także w pakietach języka PL/SQL, które nie są przeznaczone do seryjnego uruchamiania. Specyfikacja pakietu components wygląda następująco: SQL> CREATE OR REPLACE PACKAGE components IS 2 PROCEDURE set (value VARCHAR2); -- Deklaracja publicznej procedury. 3 FUNCTION get RETURN VARCHAR2; -- Deklaracja publicznej funkcji. 4 END components; 5 /

W kodzie w języku PL/SQL deklaracje funkcji prawie zawsze znajdują się przed procedurami, jednak w specyfikacji pakietu ich kolejność nie ma znaczenia. Jeśli natomiast programista deklaruje je jako funkcje i procedury lokalne, porządek jest istotny z uwagi na referencje uprzedzające. W ciele pakietu components znajdują się dodatkowo funkcja i procedura z poziomu pakietu oraz dwie zmienne współużytkowane. Jedna z nich służy do implementacji wzorca singleton w pakiecie języka PL/SQL. Druga powinna mieć zawsze wartość początkową. Ciało pakietu components wygląda następująco: SQL> CREATE OR REPLACE PACKAGE BODY components IS 2 -- Deklaracje zmiennych współużytkowanych z poziomu pakietu. 3 key NUMBER := 0; 4 variable VARCHAR2(20) := 'Wartość początkowa'; 5 -- Definicje funkcji i procedury dostępnych tylko w pakiecie. 6 FUNCTION locked RETURN BOOLEAN IS 7 key NUMBER := 0; 8 BEGIN 9 IF components.key = key THEN 10 components.key := 1;

Rozdział 9. Pakiety 11 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

331

RETURN FALSE; ELSE RETURN TRUE; END IF; END locked; PROCEDURE unlock IS key NUMBER := 1; BEGIN IF components.key = key THEN components.key := 0; -- Wyzerowanie klucza. variable := 'Wartość początkowa'; -- Przywrócenie wartości początkowej zmiennej współużytkowanej. END IF; END unlock; -- Definicje funkcji i procedury publicznych. FUNCTION get RETURN VARCHAR2 IS BEGIN RETURN variable; END get; PROCEDURE set (value VARCHAR2) IS BEGIN IF NOT locked THEN variable := value; dbms_output.put_line('Nowa wartość do momentu odblokowania to ['||get||'].'); unlock; END IF; END set; END components; /

Kluczowe operacje zachodzą w procedurze set. Blokuje ona pakiet na czas wprowadzania zmian, modyfikuje zmienną współużytkowaną, pobiera kopię wartości tymczasowej (zwanej też wartością przejściową) tej zmiennej, a następnie odblokowuje pakiet. Procedura odblokowująca przywraca wartość klucza kontrolnego i zmiennej współużytkowanej pakietu. Aby przetestować działanie tego pakietu, należy najpierw utworzyć zmienną sesji (powiązaną): SQL> VARIABLE current_content VARCHAR2(20)

Po jej przygotowaniu można wywołać funkcję i zapisać zwróconą wartość w zmiennej powiązanej. Instrukcja SELECT umożliwia sprawdzenie wartości początkowej zmiennej pakietu: SQL> CALL components.get() INTO :current_content; SQL> SELECT :current_content AS contents FROM dual;

Program wyświetli wtedy następujące dane: CONTENTS ----------------Wartość początkowa

Wywołanie procedury set powoduje wyświetlenie w oknie konsoli wartości tymczasowej. Trzeba pamiętać o wcześniejszym włączeniu zmiennej SERVEROUTPUT: SQL> SET SERVEROUTPUT ON SIZE 1000000 SQL> EXECUTE components.set('Nowa wartość');

Dane wyjściowe powinny wyglądać następująco: Nowa wartość do momentu odblokowania to [Nowa wartość].

Późniejsze wywołanie funkcji get spowoduje zwrócenie pierwotnej wartości zmiennej pakietu. Pakiet components ilustruje zastosowanie wzorca projektowego singleton do współużytkowanych zmiennych pakietu. Funkcja locked i procedura unlock gwarantują, że stan pakietu jest zawsze taki sam. Użytkownik może wywołać procedurę set, aby zmienić wartość zmiennej, a następnie wyświetlić nową wartość przy użyciu funkcji get. Nie byłoby to możliwe w pakietach do seryjnego uruchamiania. Wtedy wywołanie funkcji get w funkcji set zawsze powodowałoby zwrócenie nowej kopii pakietu.

332

Część II Programowanie w języku PL/SQL

Pakiet components przedstawia jedną z technik zarządzania współużytkowanymi zmiennymi pakietu między wywołaniami. W tym przykładzie zmienna współużytkowana pakietu działa jak zmienna egzemplarza w obiektach zdefiniowanych przez użytkownika (obiekty tego rodzaju opisano w rozdziale 11.). Przykładowy kod pokazuje, że współużytkowanie zmiennych i gwarantowanie, że przy następnym wywołaniu pakiet będzie zawierał tę samą wartość początkową, wymaga dużo wysiłku. Można też dodać rejestrującą dane procedurę z poziomu pakietu i utworzyć ją jako autonomiczną. W tym celu należy dodać poniższy kod w końcowej części pakietu components: 37 PROCEDURE log (value VARCHAR2) IS 38 PRAGMA AUTONOMOUS_TRANSACTION; -- Ustawianie funkcji jako autonomicznej. 39 BEGIN 40 /* Rejestrowanie wartości w dzienniku. */ 41 INSERT INTO logger VALUES (value); 42 /* Zatwierdzanie wpisu w autonomicznej sesji. */ 43 COMMIT; 44 END; 45 END components;

Wiersz 38. powoduje, że procedura log pakietu components będzie działać w transakcji autonomicznej. To oznacza, że stan tej procedury jest niezależny od reszty pakietu. Po zapisaniu danych w tabeli logger autonomiczna procedura log musi zatwierdzić operację w wierszu 43., ponieważ jej zasięg transakcji jest odmienny niż innych wywołań pakietu components. Przedstawiony w tym punkcie kod nie tylko ilustruje zastosowania wzorca singleton, ale też wywoływanie komponentów z poziomu pakiet przy użyciu deklaracji publicznych. Zobaczyłeś też, jak utworzyć w pakiecie procedurę autonomiczną. Komponenty z poziomu pakietu to ukryte elementy pakietu. Powtórzenie materiału W tym punkcie opisano następujące zagadnienia dotyczące ciała pakietu:  Ciało pakietu obejmuje implementację publicznych funkcji i procedur.  W ciele pakietu nie można stosować klauzul AUTHID ani ACCESSIBLE BY. Są one dostępne tylko w specyfikacji pakietu.  Dla zewnętrznych jednostek wywołujących oraz w funkcjach i procedurach z innych pakietów funkcje i procedury prywatne są dostępne tylko za pośrednictwem publicznych funkcji i procedur.  Publiczne i prywatne zmienne pakietu zachowują swój stan w czasie, gdy pakiet pozostaje w obszarze SGA.  Publiczne i prywatne funkcje oraz procedury mogą działać w sesji lokalnej (jest to ustawienie domyślne) lub w sesji niezależnej. Aby funkcja lub procedura pakietu działała w sesji niezależnej, należy zastosować dyrektywę AUTONOMOUS_TRANSACTION.  Pakiet można zaimplementować za pomocą wzorca projektowego singleton, jednak lepiej jest wtedy zastosować typ obiektowy, ponieważ właśnie takie jest przeznaczenie typów obiektowych w systemach zarządzania obiektowo-relacyjnymi bazami danych.  Zmienne chronione deklaruje się na poziomie pakietu, a zmienne prywatne są deklarowane jako zmienne lokalne w poszczególnych funkcjach i procedurach.  Pakietów przeznaczonych do wielokrotnego seryjnego uruchamiania nie można wywoływać w instrukcji SELECT.

Uprawnienia jednostki definiującej i jednostki wywołującej We wcześniejszych punktach i rozdziałach poruszono zagadnienie uprawnień jednostki definiującej i wywołującej. Są to modele działania. Model domyślny działania programów składowanych oparty jest na uprawnieniach jednostki definiującej. Takie programy używają obiektów z katalogu z tego samego schematu i mają wszystkie uprawnienia jego właściciela.

Rozdział 9. Pakiety

333

W tym modelu nie wszystkie zadeklarowane obiekty z katalogu muszą należeć do tego samego schematu. Można używać synonimów powiązanych z obiektami z katalogu należącymi do innego użytkownika, jeśli przyznał on uprawnienia do tych obiektów. Obiektami z katalogu mogą być funkcje, pakiety, procedury, widoki utrwalone, sekwencje, tabele lub widoki. Rysunek 9.2 przedstawia model oparty na uprawnieniach jednostki definiującej, w którym wszystkie obiekty z katalogu należą do jednego użytkownika.

Rysunek 9.2. Model oparty na uprawnieniach jednostki definiującej z lokalnymi obiektami z katalogu

Schemat to kontener na programy składowane. Przy jego użyciu można przez uprawnienia przyznać dostęp do programów składowanych (czarnych skrzynek). Użytkownicy zewnętrzni mogą utworzyć synonimy, aby uprościć wywołania kierowane do programów zewnętrznych. Synonimy można przetłumaczyć (powiązać), jeśli zawierający je schemat ma odpowiednie uprawnienia, programy składowane i obiekty katalogów. Połączenie synonimów i instrukcji GRANT umożliwia użytkownikom zewnętrznym wywoływanie programów przy użyciu danych wejściowych i pobieranie w innym schemacie danych wyjściowych z programów składowanych. Model oparty na uprawnieniach jednostki definiującej jest doskonały, jeśli programista chce używać jednego zestawu programów składowanych, które działają na lokalnych obiektach z katalogu. Można go stosować także wtedy, gdy dostęp do wszystkich jednostek ma się odbywać przez jeden scentralizowany schemat. Scentralizowany model dostępu jest nieco bardziej złożony, ponieważ schemat obsługujący dostęp może zawierać synonimy wskazujące programy składowane z innych schematów. Z kolei te programy składowane mają uprawnienia jednostki definiującej do obiektów z katalogów zawierających je schematów. Działanie narzędzi Oracle E-Business Suite jest oparte na schemacie przeznaczonym do scentralizowanej obsługi dostępu.

Model oparty na uprawnieniach jednostki definiującej pozwala poszczególnym użytkownikom operować danymi przechowywanymi we wspólnym repozytorium. Programy składowane zarządzają dostępem i uwierzytelnianiem przy użyciu pakietu dbms_application_info w celu określenia wartości kolumny client_info z widoku V$SESSION. Te programy przeplatają dane, dodając kolumnę związaną z firmą lub jednostką biznesową użytkownika. Model oparty na uprawnieniach jednostki wywołującej wymaga ustawienia wartości atrybutu AUTHID na CURRENT_USER we wszystkich programach z poziomu schematu. To podejście wymaga znalezienia wszystkich obiektów z katalogu powiązanych z programami o uprawnieniach jednostki wywołującej. Po wykryciu zależności trzeba powielić potrzebne obiekty w każdym schemacie, w którym

334

Część II Programowanie w języku PL/SQL

Przyznawanie uprawnień i synonimy Programista zadeklarował pakiet manage_items i zaimplementował go z uprawnieniami jednostki definiującej w schemacie video bazy PDB. Teraz chce utworzyć drugi schemat, purchasing, i umożliwić jego użytkownikom dostęp do pakietu manage_items. Aby umożliwić swobodny dostęp tego rodzaju, trzeba wykonać dwie operacje. Pierwsza polega na nawiązaniu połączenia z bazą danych z konta użytkownika video i przyznaniu nowemu schematowi purchasing uprawnień EXECUTE do pakietu manage_items. Służy do tego poniższe polecenie: GRANT EXECUTE ON manage_items TO purchasing;

Po przyznaniu uprawnień EXECUTE do tego pakietu użytkownikowi schematu purchasing ten będzie mógł z niego korzystać. Jednak użytkownik purchasing musi używać nazwy schematu plsql i selektora komponentów, aby uzyskać dostęp do pakietu: SQL> DESCRIBE video.manage_items

Jeśli uprawnienia zostały przyznane przez role, użytkownik ma ograniczone przywileje. Funkcje i procedury zawierające instrukcje języka SQL mogą spowodować błędy w czasie wykonywania programu, jeśli użytkownik nie otrzymał potrzebnych uprawnień przez bezpośrednie instrukcje GRANT. Można pominąć nazwę schematu i selektor komponentu, tworząc synonim (SYNONYM) w schemacie purchasing. Synonim przekształca alias na pełną nazwę, na przykład na video.manage_items. Do utworzenia synonimu można użyć nazwy pakietu: CREATE SYNONYM manage_items FOR video.manage_items;

Po utworzeniu synonimu można za jego pomocą wyświetlić informacje o pakiecie. Umożliwia to pominięcie nazwy schematu i selektora komponentu przed nazwą pakietu lub dowolnego innego obiektu z katalogu. Uprawnienia EXECUTE można przyznać także wszystkim użytkownikom. W tym celu należy zastąpić nazwę schematu słowem PUBLIC. Poniższa instrukcja przyznaje uprawnienia wszystkim użytkownikom bazy danych: GRANT EXECUTE ON manage_items TO PUBLIC;

Przyznawanie uprawnień i synonimy to mechanizmy dające duże możliwości. Instrukcje GRANT dostępne są w widoku administracyjnym USER_TAB_PRIVS, a wartości synonimów zawiera widok USER_ SYNONYMS.

mają działać programy o takich uprawnieniach. Wynika to z tego, że moduły o uprawnieniach jednostki wywołującej interpretują nazwy na podstawie przywilejów jednostki wywołującej, a nie definiującej. Wyzwalacze bazy danych i funkcje wywoływane w widokach są zawsze uruchamiane z uprawnieniami jednostki definiującej i działają z przywilejami użytkownika, do którego należy tabela lub widok związane z wyzwalaczem.

Model oparty na uprawnieniach jednostki wywołującej służy do obsługi rozproszonych repozytoriów danych. Pomocne może być wtedy pojedyncze repozytorium kodu z instrukcjami GRANT i synonimami ułatwiającymi wykonywanie transakcji przez sieć. Służy do tego narzędzie DB_LINK. Umożliwia ono interpretowanie aliasów sieciowych przy użyciu pliku tnsnames.ora w celu znalezienia innej bazy danych Oracle. Konfigurowanie i używanie tego pliku omówiono w dodatku A. Model oparty na uprawnieniach jednostki wywołującej ułatwia obsługę danych przechowywanych w odrębnych schematach użytkowników. Jest to także doskonałe rozwiązanie do zarządzania rozproszonymi bazami danych, które działają w jednym egzemplarzu bazy. Trzeba pamiętać, że przy zdalnym wywoływaniu procedur za pomocą łączy bazodanowych obowiązują poważne ograniczenia. Powtórzenie materiału W tym podrozdziale opisano następujące zagadnienia dotyczące porównania uprawnień jednostki definiującej i uprawnień jednostki wywołującej:

Rozdział 9. Pakiety

335

Programy z uprawnieniami jednostki definiującej korzystają głównie z danych z własnego schematu, natomiast programy z uprawnieniami jednostki wywołującej używają danych lokalnych.  Programy z uprawnieniami jednostki definiującej działają we własnym schemacie. Jeśli inny użytkownik ma móc ich używać, trzeba mu przyznać uprawnienia EXECUTE do odpowiednich programów.  Za pomocą instrukcji GRANT można zezwolić na uruchamianie pakietu z uprawnieniami jednostki definiującej.  Instrukcja SYNONYM powoduje dodanie nazwy schematu i selektora komponentów do katalogu danych oraz nadanie pakietowi nazwy dostępnej w schemacie innego użytkownika. 

Wywołania zdalne Wywołania zdalne są kierowane z jednego egzemplarza bazy danych do drugiego. Do ich wykonywania służą łącza bazodanowe (DB_LINK). Aby móc utworzyć takie łącze, użytkownik musi mieć uprawnienia CREATE DATABASE LINK. Można je przyznać z konta użytkownika SYSTEM za pomocą poniższego polecenia: GRANT CREATE DATABASE LINK TO nazwa_schematu;

Po przyznaniu uprawnień schematowi można utworzyć łącze bazodanowe z innym schematem. Prototyp tworzenia łączy DB_LINK wygląda następująco: CREATE DATABASE LINK nazwa_łącza CONNECT TO nazwa_schematu IDENTIFIED BY hasło_schematu USING 'aliasy_nazw_tns'

Łącze bazodanowe to statyczny obiekt zapisany w bazie danych. Przechowuje nazwę i hasło schematu potrzebne przy zdalnych połączeniach. Po zmianie hasła schematu ze zdalnej bazy danych trzeba zaktualizować także łącze. Łącza bazodanowe pozwalają na dostęp do innych egzemplarzy bazy danych oraz innych schematów z tej samej bazy. W przykładowym kodzie z tej ramki znajduje się łącze DB_LINK o nazwie loopback, które umożliwia ponowne nawiązanie połączenia z tym samym egzemplarzem. Nie trzeba zmieniać żadnych fragmentów pliku tnsnames.ora, aby to łącze działało poprawnie. Jednak przy kierowaniu wywołań przez zdalne połączenia trzeba pamiętać o pewnych zasadach. Użytkownik może wywoływać komponenty z poziomu schematu, jeśli nie wymagają one argumentów. Na przykład przy użyciu łącza bazodanowego loopback można wywołać funkcję status w następujący sposób: SQL SELECT status@loopback FROM dual;

PL/SQL BEGIN dbms_output.put_line('Status: ['||status@loopback||']'); END; /

Zdalne komponenty z poziomu schematu mogą zawierać instrukcje DDL i DML. Nie można zwracać za ich pomocą uchwytów do dużych obiektów. Próba wykonania tej operacji spowoduje wyjątek ORA-22992, który informuje o tym, że nie można użyć lokalizatora dużego obiektu z tabeli zdalnej.

Zarządzanie pakietami w katalogu bazy danych Wraz z rozwojem bazy danych wzrasta też liczba powiązanych z nią programów składowanych. Niezależnie od tego, czy programista wybierze uprawnienia jednostki definiującej, czy jednostki wywołującej, ważne jest, aby wiedział, co dodał do schematu. W trzech następnych punktach wyjaśniono, jak wyszukiwać pakiety, zarządzać nimi i sprawdzać ich poprawność oraz zależności w bazie danych Oracle 12c.

336

Część II Programowanie w języku PL/SQL

Wyszukiwanie, walidacja i opisywanie pakietów Do wyszukiwania pakietów służą widoki administracyjne CDB_OBJECTS, ALL_OBJECTS, DBA_OBJECTS i USER_OBJECTS. Można ich użyć także do sprawdzenia, czy specyfikacja lub ciało pakietu są prawidłowe. Zamiast tworzyć nowe jednostki kodu, w tym przykładzie użyto pakietu pipelined i funkcji pf znanych z punktu „Klauzula PIPELINED” z rozdziału 8. Poniższe zapytanie pozwala stwierdzić, że specyfikacja i ciało pakietu istnieją i są poprawne. Formatowanie kolumn w środowisku SQL*Plus gwarantuje, że dane wyjściowe są czytelnie wyświetlane na jednym, 80-kolumnowym ekranie. COLUMN SELECT , , , , FROM WHERE

object_name FORMAT A10 object_name object_type last_ddl_time timestamp status user_objects object_name IN ('PIPELINED', 'PF');

To zapytanie powinno zwrócić następujące informacje: OBJECT_NAME -----------------PF PIPELINED PIPELINED

OBJECT_TYPE ----------------FUNCTION PACKAGE PACKAGE BODY

LAST_DDL_ ---------03-JAN-08 03-JAN-08 03-JAN-08

TIMESTAMP ------------------2008-01-03:22:50:23 2008-01-03:22:50:19 2008-01-03:22:50:20

STATUS -----VALID VALID VALID

Jeśli programista umieści w ciele pakietu pipelined niepoprawny znak, uruchomienie pakietu zakończy się niepowodzeniem. Po próbie skompilowania błędnego ciała pakietu i ponownym wysłaniu zapytania o dane powinny pojawić się następujące informacje: OBJECT_NAM --------------PF PIPELINED PIPELINED

OBJECT_TYPE ----------------FUNCTION PACKAGE PACKAGE BODY

LAST_DDL_ ---------03-JAN-08 03-JAN-08 03-JAN-08

TIMESTAMP ------------------2008-01-03:22:50:23 2008-01-03:22:50:19 2008-01-03:22:53:34

STATUS -----VALID VALID INVALID

Nieprawidłowe ciało pakietu nie powoduje, że funkcja pf staje się niepoprawna. Wynika to z tego, że funkcja zależy od specyfikacji pakietu, a nie jego ciała. Należy poprawić ciało pakietu pipelined i ponownie je skompilować przed przystąpieniem do dalszych operacji. Jeśli programista umieści błędny znak w specyfikacji pakietu pipelined, pojawi się błąd przy próbie jego skompilowania. Ponowne zapytanie o dane z widoku USER_SOURCE informuje, że teraz nieprawidłowe są także ciało pakietu i funkcja pf: OBJECT_NAM --------------PF PIPELINED PIPELINED

OBJECT_TYPE ----------------FUNCTION PACKAGE PACKAGE BODY

LAST_DDL_ ---------03-JAN-08 03-JAN-08 03-JAN-08

TIMESTAMP ------------------2008-01-03:22:50:23 2008-01-03:23:06:10 2008-01-03:22:53:34

STATUS -----INVALID INVALID INVALID

Można ponownie zbudować pakiet pipelined, wywołując skrypt pipelined.sql dostępny w witrynie wydawnictwa Helion. Po ponownym skompilowaniu specyfikacji i ciała pakietu funkcja pf w katalogu bazy danych wciąż jest nieprawidłowa (nie jest to prawdą po ponownym uruchomieniu wymienionego skryptu). Można przywrócić poprawność funkcji przy użyciu jawnej instrukcji kompilacji: ALTER FUNCTION pf COMPILE;

Można też po prostu wywołać funkcję, co spowoduje sprawdzenie poprawności powiązanych obiektów przed uruchomieniem instrukcji. Jest to tak zwana kompilacja leniwa, nazywana też automatyczną ponowną kompilacją: SELECT * FROM TABLE(pf);

Rozdział 9. Pakiety

337

Informacje o pakiecie można wyświetlić w środowisku SQL*Plus w podobny sposób, jak dane o tabelach lub widokach: SQL> DESCRIBE pipelined

Spowoduje to wyświetlenie następujących danych: FUNCTION PF RETURNS ACCOUNT_COLLECTION

Można zauważyć, że typy rekordowy i kolekcji — zadeklarowane w specyfikacji pakietu — nie zostały wyświetlone. Nie ma w tym nic dziwnego. Jak wspomniano w punkcie „Specyfikacja pakietu” we wcześniejszej części rozdziału, aby wyświetlić pełną deklarację pakietu, trzeba skierować zapytanie do widoków CDB_SOURCE, ALL_SOURCE, DBA_SOURCE lub USER_SOURCE. Informacje na temat opakowanego ciała pakietu będą miały niezrozumiałą formę, dlatego można je pominąć. W tym punkcie Czytelnik dowiedział się, jak wyszukiwać pakiety, sprawdzać ich poprawność i wyświetlać informacje na ich temat. Następny punkt zawiera opis śledzenia zależności.

Sprawdzanie zależności Widoki administracyjne CDB_DEPENDENCIES, ALL_DEPENDENCIES, DBA_DEPENDENCIES i USER_DEPENDENCIES umożliwiają sprawdzenie zależności między programami składowanymi. Podobnie jak w poprzednim punkcie, przykładowy kod w tym miejscu używa pakietu pipelined i funkcji pf utworzonych w punkcie „Klauzula PIPELINED” w rozdziale 8. Poniższe zapytanie wyświetla zależności funkcji pf. Formatowanie kolumn w środowisku SQL*Plus gwarantuje, że dane wyjściowe są czytelnie wyświetlane na jednym, 80-kolumnowym ekranie. COLUMN COLUMN COLUMN COLUMN COLUMN SELECT , , , , FROM WHERE

name FORMAT A10 type FORMAT A8 referenced_name FORMAT A30 referenced_type FORMAT A10 dependency_type FORMAT A4 name type referenced_name referenced_type dependency_type user_dependencies name = 'PF';

Poniższy rysunek przedstawia dane z tabeli user_dependencies. Funkcja pf ma powiązania typu HARD z pakietem pipelined i dwa bezpośrednie powiązania tego rodzaju z tabelami contact i member. Te dwie tabele są używane w klauzuli FROM kursora zadeklarowanego w tej funkcji.

W tym punkcie Czytelnik dowiedział się, jak wykrywać zależności między programami składowanymi. Informacje z tych punktów pozwolą znajdować zależności i określać ich poziom.

338

Część II Programowanie w języku PL/SQL

Metody sprawdzania poprawności — znaczniki czasu i sygnatury Programy składowane są określane jako prawidłowe lub nieprawidłowe przy użyciu znaczników czasu lub sygnatur. Model oparty na znacznikach czasu jest domyślny w większości baz danych Oracle. Polega na sprawdzaniu kolumny last_ddl_time, która znajduje się w widokach CDB_OBJECTS, ALL_OBJECTS, DBA_OBJECTS i USER_OBJECTS. Jeśli obiekt podstawowy ma późniejszy znacznik czasu niż obiekt zależny, ten ostatni zostanie ponownie skompilowany. Daty i znaczniki czasu zawsze są związane z pewnymi ciekawymi zagadnieniami. Jeśli użytkownicy pracują w rozproszonym środowisku i dwa egzemplarze bazy danych działają w odmiennych strefach czasowych, porównywanie czasu może dawać błędne wyniki. Ponadto przy rozproszonych serwerach w tej samej strefie czasowej może zachodzić niepotrzebna ponowna kompilacja. Obiekty zależne są czasem kompilowane nawet wtedy, gdy zmiana w kolumnie last_ddl_time nie wiąże się z modyfikacjami podstawowego obiektu. Inny problem z walidacją przy użyciu znaczników czasu pojawia się, kiedy kod PL/SQL jest rozproszony między serwerem a narzędziem Oracle Forms. Wtedy zmiana w kodzie bazowym nie może wywołać ponownej kompilacji, ponieważ wersja wykonawcza Oracle Forms nie obejmuje tego kodu. Do walidacji można używać sygnatur zamiast znaczników czasu. W tym podejściu porównywane są sygnatury funkcji i procedur z poziomów schematu oraz pakietu. Trzeba zmodyfikować bazę danych z konta uprawnionego użytkownika, aby zmienić model walidacji na metodę opartą na sygnaturach. Służy do tego następująca składnia: ALTER SYSTEM SET REMOTE_DEPENDENCIES_MODE = SIGNATURE;

Użytkownik musi mieć uprawnienia ALTER SYSTEM, aby wywołać to polecenie.

Ta instrukcja zmienia parametr remote_dependencies_mode w pliku konfiguracyjnym spfile.ora lub pfile.ora. Jeśli programista chce, aby zmiany były trwałe, powinien wprowadzić je w odpowiednim pliku konfiguracyjnym. Model oparty na sygnaturach polega na sprawdzaniu, czy sygnatura podstawowego obiektu zmieniła się między kolejnymi kompilacjami. Jeśli tak się stało, następuje kompilacja pakietów zależnych. Informacje o sygnaturach zawierają widoki CDB_ARGUMENTS, ALL_ARGUMENTS, DBA_ARGUMENTS i USER_ ARGUMENTS. Zdalne wywołania procedur mogą spowodować wyjątek ORA-04062, jeśli zdalna baza danych używa modelu opartego na znacznikach czasu, a nie sygnaturach.

Model oparty na znacznikach czasu to doskonałe rozwiązanie w środowiskach scentralizowanych. Model oparty na sygnaturach jest czasem wydajniejszy w scentralizowanych środowiskach programistycznych, zwykle jednak zaleca się stosowanie go w rozproszonych aplikacjach bazodanowych. Powtórzenie materiału W tym podrozdziale opisano następujące zagadnienia związane z zarządzaniem pakietami w katalogu bazy danych:  Za pomocą katalogu bazy danych można wykrywać dostępność i stan funkcji, procedur i pakietów.  Polecenie DESCRIBE umożliwia wyświetlanie publicznych funkcji i procedur z niezależnych pakietów i zbiorów pakietów.  Oracle udostępnia dwie metody sprawdzania poprawności funkcji, procedur i pakietów — opartą na znacznikach czasu i opartą na sygnaturach.  Sprawdzanie poprawności na podstawie znaczników czasu może działać nieprawidłowo, gdy serwery znajdują się w innych strefach czasowych. Dlatego w środowisku rozproszonym należy stosować sprawdzanie poprawności oparte na sygnaturach.

Rozdział 9. Pakiety

339

Podsumowanie W tym rozdziale wyjaśniono, dlaczego pakiety to podstawowy element przy tworzeniu aplikacji opartych na bazie danych Oracle Database 12c. Nauczyłeś się łączyć funkcje i procedury w biblioteki, a także przeciążać podprogramy. Poznałeś także różnice między zmiennymi, typami i komponentami pakietu oraz lokalnymi, a także dowiedziałeś się, jak planować tworzenie takich elementów i zarządzać nimi.

Test wiedzy Test wiedzy to zestaw pytań typu „prawda czy fałsz” i wielokrotnego wyboru, dzięki którym sprawdzisz, jak dobrze opanowałeś materiał z poszczególnych rozdziałów. Odpowiedzi na pytania znajdziesz w dodatku I.

Prawda czy fałsz? 1. __W specyfikacji pakietu można definiować tylko funkcje i procedury. 2. __W ciele pakietu można definiować zmienne, typy danych, funkcje i procedury. 3. __Specyfikacja pakietu zawiera definicję funkcji i procedur, a ciało pakietu — ich implementację. 4. __Można zdefiniować namiastkę funkcji i udostępnić jej implementację w ciele pakietu. 5. __Referencja uprzedzająca jest potrzebna dla każdej funkcji i procedury. Pozwala to uchronić

programistę przed przypadkowym użyciem danej jednostki, zanim zostanie ona zaimplementowana w ciele pakietu. 6. __Przyznanie uprawnień EXECUTE do pakietu pozwala użytkownikowi innego schematu na uruchamianie pakietu z uprawnieniami jednostki definiującej z danymi lokalnymi takiej jednostki. 7. __Instrukcja SYNONYM tworzy alias dla uprawnień. 8. __Pakiet musi zawierać tylko autonomiczne lub tylko nieautonomiczne funkcje i procedury. 9. __Pakiet zachowuje wartość zmiennej do czasu jej unieważnienia w obszarze SGA lub wywołania instrukcji FLUSH VARIABLE nazwa_zmiennej. 10. __W instrukcjach SELECT można wywoływać pakiety przeznaczone do seryjnego uruchamiania.

Pytania wielokrotnego wyboru 11. Które z poniższych instrukcji PRAGMA (dyrektyw kompilatora) można stosować tylko w pakie-

tach? Poprawnych może być kilka odpowiedzi. A. AUTONOMOUS_TRANSACTION. B. AUTO_TRANSACTION. C. SERIALLY_REUSABLE. D. EXCEPTION_INIT. E. ACCESSIBLE_BY. 12. Które z poniższych elementów można zdefiniować w specyfikacji pakietu? Poprawnych może być kilka odpowiedzi. A. Typ obiektowy. B. Typ rekordowy. C. Funkcja. D. Procedura. E. Funkcja autonomiczna. 13. Które z poniższych zmiennych są publicznie dostępne? Poprawnych może być kilka odpowiedzi. A. Zmienna zadeklarowana w funkcji pakietu. B. Zmienna zadeklarowana w procedurze pakietu.

340

Część II Programowanie w języku PL/SQL C. Zmienna zadeklarowana w specyfikacji pakietu. D. Zmienna zadeklarowana w ciele pakietu poza funkcją lub procedurą. E. Wszystkie z powyższych.

14. Które z poniższych elementów obsługują przeciążanie? Poprawnych może być kilka odpowiedzi. A. Funkcje niezależne. B. Procedury niezależne. C. Funkcje zadeklarowane w specyfikacji pakietu. D. Procedury zadeklarowane w specyfikacji pakietu. E. Funkcje zadeklarowane w ciele pakietu. 15. Co gwarantuje, że zmienna przy każdym wywołaniu pakietu będzie miała świeżą wartość? Po-

prawnych może być kilka odpowiedzi. A. Zadeklarowanie zmiennej w funkcji autonomicznej. B. Zadeklarowanie zmiennej w lokalnej procedurze. C. Zadeklarowanie zmiennej w lokalnej funkcji. D. Zadeklarowanie zmiennej poza funkcjami i procedurami w ciele pakietu. E. Zadeklarowanie zmiennej poza funkcjami i procedurami w specyfikacji pakietu.

ROZDZIAŁ

10

Duże obiekty

Duże obiekty (ang. Large Objects — LOB) to bardzo przydatne struktury danych, służące do przechowywania tekstu, zdjęć, plików muzycznych i nagrań wideo w bazie danych. W Oracle 11g duże obiekty zostały znacznie zmodyfikowane. W Oracle Database 12c rozwinięto te zmiany i zoptymalizowano silniki obsługujące duże obiekty. Obecnie działają szybciej i — dzięki mechanizmowi SecureFiles — są bezpieczniejsze. Można użyć tego mechanizmu przy definiowaniu kolumn BLOB, CLOB lub CLOB w czasie tworzenia tabeli bądź jej modyfikowania. Duże obiekty mogą przechowywać maksymalnie od 8 do 128 terabajtów danych. Wartość ta zależy od konfiguracji parametru db_block_size bazy danych. W Oracle Database 12c ten parametr można ustawić na wartość od 2 kilobajtów do 32 kilobajtów. Dostępne wartości tego parametru to wielokrotności dwójki: 2, 4, 8, 16 i 32. Wartość domyślna wynosi 8 kilobajtów. Poniższy wzór pozwala błyskawicznie obliczyć maksymalną wielkość dużych obiektów: Maksymalna wielkość = (4GB – 1) * db_block_size Aby sprawdzić maksymalny rozmiar dużych obiektów w używanej bazie danych, należy użyć funkcji get_storage_limit z pakietu dbms_lob. Do przechowywania dużych obiektów służą zmienne i kolumny typów BLOB i CLOB, a typ BFILE (odpowiadający plikom binarnym) umożliwia przechowywanie danych poza bazą. Kolumny typu BFILE przechowują lokalizatory, które określają fizyczną lokalizację plików zewnętrznych. W punkcie „Długie łańcuchy znaków” w dodatku B omówiono składnię związaną z zapisywaniem dużych obiektów. Takie obiekty są przechowywane w bazie danych w segmentach, które są wierszami specjalnych tabel. W wersji Oracle Database 11g Release 2 do pakietu dbms_lob dodano nowe metody związane z mechanizmem SecureFiles. Choć ten rozdział jest poświęcony zarządzaniu dużymi obiektami, omówienie mechanizmu SecureFiles obejmuje tylko nowe metody z tego pakietu. Więcej o tym mechanizmie dowiesz się z podręcznika Oracle Database SecureFiles and Large Objects Developer’s Guide 12c Release. W tym rozdziale wyjaśniono, jak używać języka PL/SQL do korzystania z różnych typów danych LOB. Rozdział ten opisuje następujące zagadnienia:  Pracę z wewnętrznie przechowywanymi typami dużych obiektów  Wczytywanie plików do wewnętrznie przechowywanych kolumn  Pliki binarne — typ BFILE  Pakiet dbms_lob Metody obsługi typów BLOB, CLOB i NCLOB są bardzo podobne. Dlatego najpierw omówione jest korzystanie z wewnętrznie przechowywanych typów dużych obiektów. Dalej dowiesz się, jak wczytywać duże pliki do wewnętrznie przechowywanych typów CLOB i BLOB. Najpierw omówione są typy CLOB i NCLOB, ponieważ umożliwiają skoncentrowanie się na zarządzaniu transakcjami z wykorzystaniem dużych bloków tekstu. Następny podrozdział dotyczy typu BLOB i wykorzystano w nim zagadnienia omówione w punktach poświęconych dużym obiektom znakowym. Typy BLOB służą do przechowywania

342

Część II Programowanie w języku PL/SQL

w bazie danych dokumentów binarnych, na przykład plików Adobe PDF (ang. Portable Document Format), obrazów i filmów. Przy dostępie do obiektów typu BLOB i wyświetlaniu ich pomocny jest język PHP, przydatny do generowania obrazów na stronach internetowych. Po wprowadzeniu do wewnętrznie zarządzanych dużych obiektów nauczysz się tworzyć, konfigurować i wczytywać obiekty typu BFILE oraz zarządzać nimi. Operacje te wymagają więcej pracy, ponieważ katalog przechowuje tylko lokalizator danych, a programista musi zagwarantować ich fizyczną obecność w systemie plików. Na zakończenie rozdziału omówiono pakiet dbms_lob, ponieważ nie wszystkie jego funkcje są potrzebne do przedstawienia sposobów używania dużych obiektów. Każdy kolejny podrozdział opiera się na informacjach z poprzednich, ale możliwe jest też wyrywkowe czytanie poszczególnych punktów w celu szybkiego znalezienia informacji.

Praca z wewnętrznie składowanymi dużymi obiektami Typów CLOB, NCLOB i BLOB można używać do definiowania kolumn tabel i tabel zagnieżdżonych. Maksymalny fizyczny rozmiar tych typów to od 8 do 128 terabajtów. Dane są przechowywane w segmentach zdefiniowanych za pomocą klauzuli STORAGE tabeli. Tę klauzulę opisano w punkcie „Duże łańcuchy znaków” w dodatku B. Typy danych CLOB i NCLOB służą do przechowywania dużych plików tekstowych. Różnica między typami polega na tym, że w typie NCLOB trzeba określić używany zbiór znaków. Takie typy mogą zawierać rozdział książki, całą książkę lub fragment danych w formacie XML. Typ BLOB jest przeznaczony do przechowywania dużych plików binarnych, na przykład rysunków, piosenek, filmów lub plików PDF. W tym podrozdziale opisano, jak używać dużych obiektów wymienionych typów. Typów do obsługi danych w formacie XML (są one podtypami typu CLOB) nie opisano w tym miejscu. Więcej na temat typów związanych z XML-em dowiesz się z podręcznika Oracle XML DB Developer’s Guide 12cRelease.

Przypisywanie do dużych obiektów danych o wielkości poniżej 32 kilobajtów Kolumny typu CLOB, NCLOB i BLOB są zazwyczaj przechowywane odrębnie od pozostałych wierszy tabeli. W kolumnie fizycznie zapisywany jest tylko deskryptor (lokalizator). Lokalizator określa miejsce fizycznego przechowywania zawartości dużego obiektu w prywatnym obszarze roboczym obszaru SGA. Ten obszar roboczy pozwala poruszać się po zawartości obiektu i zapisywać nowe porcje danych. W niektórych materiałach z dokumentacji starszych wersji baz Oracle lokalizatory obiektów typów BLOB, CLOB i NCLOB są nazywane deskryptorami, podczas gdy lokalizator to pojęcie używane przy opisie obsługi zewnętrznych obiektów typu BFILE. W dokumentacji bazy danych Oracle 12c oba te elementy są nazywane lokalizatorami. CLOB, NCLOB i BLOB to typ obiektowy, dlatego wymaga niejawnego lub jawnego utworzenia egzemplarza obiektu. W językach SQL i PL/SLQ możliwe jest niejawne i jawne tworzenie egzemplarzy typów CLOB i NCLOB na podstawie typów VARCHAR2 i NVARCHAR2 oraz literałów znakowych. Możliwe jest także niejawne i jawne tworzenie egzemplarzy typu BLOB na podstawie szesnastkowych literałów znakowych. Ani SQL, ani PL/SQL nie obsługuje jednak niejawnego przekształcania wartości typu LONG na typ CLOB lub NCLOB. Funkcja TO_CHAR i instrukcja SELECT INTO nie potrafią przekształcić wartości typu LONG na długi łańcuch znaków lub wartości typu LONG RAW na długi binarny łańcuch znaków. W ramce „Przekształcanie typu LONG na CLOB” w dalszej części rozdziału pokazano, jak użyć pakietu dbms_sql, aby przekształcić dane typu LONG na wartość typu CLOB. Można niejawnie utworzyć zmienną typu CLOB lub NCLOB w wyniku bezpośredniego przypisania do niej liczby lub znaku. Przy przypisaniu liczby jest ona najpierw rzutowana na typ znakowy, który system przekształca następnie na typ CLOB. Niestety, środowiska języków SQL i PL/SQL ograniczają konwersję znaków typów CHAR, NCHAR, NVARCHAR i VARCHAR2. Gdy parametr max_string_size ma wartość STANDARD, SQL-u można przekształcić strumienie znaków o długości do 4000 bajtów, natomiast gdy ten

Rozdział 10. Duże obiekty

343

parametr ma wartość EXTENDED, SQL umożliwia konwersję strumieni znaków o długości do 32 767 bajtów. Język PL/SQL umożliwia jednoczesną konwersję 32767 bajtów danych znakowych. Choć nie istnieje konstruktor umożliwiający utworzenie nowego egzemplarza typu CLOB o fizycznej wielkości większej niż obowiązujące w środowisku ograniczenie 32 767 bajtów, za pomocą pakietu dbms_lob można tworzyć większe wartości. Ten pakiet umożliwia zapis danych do elementów typów CLOB, NCLOB i BLOB na dwa sposoby. Jedno podejście polega na jednorazowym zapisie całego dużego obiektu. Druga technika wymaga zapisywania danych fragment po fragmencie. Można na przykład zainicjować nowy egzemplarz za pomocą procedury write z pakietu wbudowanego dbms_lob, a następnie wykorzystać procedurę append do dodania danych. Wspomniany pakiet jest dostępny w języku PL/SQL, a także w Javie i w dowolnym języku wywoływalnym z poziomu języka C. W Javie dostęp do tego pakietu można uzyskać za pomocą biblioteki JDBC (ang. Java Database Connectivity), a w programach w językach wywoływalnych w języku C — za pomocą bibliotek OCI (ang. Oracle Call Interface).

Przypisywanie wartości do niewielkich dużych obiektów W dwóch najbliższych podpunktach pokazano, jak przypisywać wartości do elementów typów BLOB, CLOB i NCLOB. W przypadku tych dwóch ostatnich przedstawione są tylko przypisania do typu CLOB, ponieważ przypisywanie wartości do typu NCLOB przebiega tak samo.

Przypisywanie wartości do typów CLOB i NCLOB w języku PL/SQL Poniższy blok anonimowy pokazuje, jak zadeklarować zmienną typu CLOB w bloku programu w języku PL/SQL. Jest to przypomnienie materiału z rozdziału 4. SQL> 2 3 4 5 6 7 8

DECLARE var1 CLOB; -- Deklaracja pustej referencji typu CLOB. var2 CLOB := empty_clob(); -- Deklaracja pustego obiektu typu CLOB. var3 CLOB := 'łańcuch_znaków'; -- Deklaracja obiektu CLOB zawierającego literał znakowy. BEGIN ... END; /

W języku PL/SQL obiekt typu CLOB może mieć wartość NULL, a także być pusty lub zapełniony łańcuchem znaków. W SQL-u można używać obiektów tego typu zapełnionych łańcuchami znaków. Wiersz 2. przypisuje do zmiennej var1 wartość null. Wiersz 3. przypisuje do zmiennej var2 pusty obiekt CLOB, a wiersz 4. — literał znakowy do zmiennej var3. Ponadto w języku PL/SQL możliwe jest przypisywanie wartości od lewej do prawej za pomocą instrukcji SELECT INTO, a pakiet dbms_lob umożliwia wczytanie pliku bezpośrednio do dużego obiektu. Jak wcześniej wspomniano, instrukcja SELECT INTO nie obsługuje przypisywania wartości typu LONG do elementów typów CLOB i NCLOB. Niestety, tylko pakiet dbms_lob umożliwia przypisywanie łańcuchów znaków dłuższych niż 32 767 bajtów. Za pomocą procedur z tego pakietu możliwe jest przypisywanie bardzo długich łańcuchów (dłuższych niż 32 767 bajtów). Proces zapisywania takich łańcuchów znaków opisano w punkcie „Metody do manipulowania dużymi obiektami” w dalszej części rozdziału. Można też wczytać zewnętrzny plik i przypisać jego zawartość do elementu typu CLOB lub NCLOB. Procedury loadfromfile i loadclobfromfile z pakiet dbms_lob umożliwiają bezpośrednie przypisywanie danych z plików zewnętrznych. Więcej informacji na temat przypisywania plików do elementów typów CLOB i NCLOB znajdziesz w punkcie „Wczytywanie plików do zewnętrznie przechowywanych kolumn” w dalszej części rozdziału. Niestety, język SQL nie daje tylu możliwości. SQL nie obsługuje instrukcji SELECT INTO ani procedur pakietu dbms_lob przeznaczonych do manipulowania dużymi obiektami. Jest tak, ponieważ SQL nie obsługuje procedur o parametrach przekazywanych przez referencję (działają one tylko w języku PL/SQL oraz interfejsach ODBC, OCI i JDBC). W SQL-u dostępne są trzy możliwości:  Przypisanie literału znakowego o długości mniejszej niż 32 767 bajtów w klauzuli VALUES instrukcji INSERT lub jako wartości kolumny z listy SELECT w instrukcji INSERT.  Przypisanie wyniku wywołania konstruktora empty_clob w klauzuli VALUES instrukcji INSERT lub jako wartości kolumny z listy SELECT w instrukcji INSERT.

344

Część II Programowanie w języku PL/SQL



Przypisanie zainicjowanego elementu typu CLOB za pomocą funkcji o parametrach przekazywanych przez wartość języka PL/SQL lub funkcji języka PL/SQL działającej jako nakładka na program wywoływalny w języku C. Można to zrobić w klauzuli VALUES instrukcji INSERT lub dla kolumny z listy SELECT w instrukcji INSERT.

Przypisywanie wartości do typu BLOB w języku PL/SQL Podobnie jak przy stosowaniu typów CLOB i NCLOB istnieją trzy sposoby na zadeklarowanie zmiennej typu BLOB w kontekście języka PL/SQL. Poniższy blok anonimowy pokazuje, jak zadeklarować zmienną tego typu: SQL> 2 3 4 5 6 7 8

DECLARE var1 BLOB; -- Deklaracja pustej referencji typu BLOB. var2 BLOB := empty_blob(); -- Deklaracja pustego obiektu typu BLOB. var3 BLOB := '43'||'41'||'52'; -- Deklaracja za pomocą wartości szesnastkowych. BEGIN ... END; /

Deklaracja zmiennej typu BLOB za pomocą wartości null w wierszu 2. odbywa się tak samo jak w przypadku typów CLOB i NCLOB. Wiersz 2. deklaruje pusty obiekt typu BLOB za pomocą wywołania funkcji empty_blob (zamiast empty_clob). Choć do zmiennych typu BLOB można przypisywać wartości szesnastkowe, rzadko stosuje się tę technikę. Ponadto możliwe jest przypisywanie wartości z kolumn typu RAW, jednak takie kolumny są uznawane za przestarzałe. Kolumny typu RAW mają małą pojemność, ograniczoną do 32 760 bajtów, co zwykle nie wystarcza na zapisywanie strumieni danych binarnych. SQL udostępnia trzy możliwości podobne do tych używanych przy przypisywaniu bardzo dużych łańcuchów znaków do elementów typów CLOB i NCLOB. Jeśli łańcuch binarny jest krótszy niż 32 767 bajtów, można wykorzystać sekwencję łańcuchów znaków w formacie szesnastkowym. Inne podejście to zainicjowanie kolumny lub zmiennej w wyniku wywołania funkcji empty_blob. Można też użyć funkcji języka PL/SQL o parametrach przekazywanych przez wartość, jeśli łańcuch binarny jest dłuższy niż 32 767 bajtów (pod warunkiem, że funkcja zwraca zainicjowany długi strumień binarny). Do kolumn typu BLOB często przypisuje się pliki binarne. Służy do tego procedura loadblobfromfile z pakietu dbms_lob. W tym rozdziale opisano ten proces w punkcie „Wczytywanie plików lokalnych do kolumn typu BLOB”.

Przypisywanie do dużych obiektów danych o wielkości powyżej 32 kilobajtów W tym punkcie opisano, jak przypisywać duże łańcuchy znaków i strumienie binarne do elementów typów CLOB, NCLOB i BLOB. Proces ten wygląda inaczej niż przypisywanie wartości o wielkości mniejszej niż 32 kilobajty, ponieważ środowisko SQL*Plus ogranicza techniki wstawiania i aktualizowania tak dużych wartości. Dzięki pakietowi dbms_lob można przypisać pierwszą porcję danych, a następnie dołączać dalsze porcje do momentu wczytania wszystkich fragmentów dużego łańcucha lub strumienia binarnego. Inicjowanie obiektów Aby zadeklarować zmienną skalarną, należy określić jej typ i wartość. W wywołaniach funkcji trzeba podać argumenty. Z kolei do deklarowania egzemplarzy obiektów służą specjalne funkcje, które inicjują zmienne typu obiektowego. Zainicjowane zmienne typu obiektowego to obiekty lub egzemplarze typu obiektowego. Proces inicjowania to konstrukcja obiektu. Wymaga to wywołania specjalnej funkcji, która zwykle ma tę samą nazwę, co typ obiektowy, i zwraca jego egzemplarz. Ta funkcja to konstruktor. W słownictwie związanym z programowaniem obiektowym wyrażenia — inicjowanie, konstrukcja i tworzenie egzemplarza — są stosowane wymiennie. Wszystkie te pojęcia oznaczają powołanie typu obiektowego do życia przez utworzenie jego egzemplarza.

Rozdział 10. Duże obiekty

345

Kolumny CLOB, NCLOB i BLOB działają inaczej niż kolumny skalarnych typów danych, ponieważ ich stan to nie tylko NULL lub NOT NULL. Obiekty typu BLOB, CLOB i NCLOB mogą mieć wartość NULL, a także być puste lub zapełnione, co opisano w tabeli 10.1. Tabela 10.1. Stany danych typu BLOB, CLOB i NCLOB Stan

Opis

NULL

Kolumna w wierszu tabeli zawiera wartość null.

Pusty

Kolumna zawiera lokalizator (deskryptor) pustego egzemplarza. Można to wykryć przy użyciu funkcji dbms_lob.getlength. Funkcja ta zwraca wartość 0, jeśli kolumna typu BLOB, CLOB lub NCLOB jest pusta.

Zapełniony

Kolumna zawiera lokalizator LOB, a funkcja dbms_lob.getlength zwraca dodatnią liczbę całkowitą dla kolumny typu BLOB, CLOB i NCLOB.

Tworzenie dużych obiektów na potrzeby przypisywania wartości o wielkości większej niż 32 767 bajtów Wstawianie łańcucha znaków o długości większej niż 32 767 bajtów bezpośrednio do niezainicjowanej kolumny typu CLOB, NCLOB lub BLOB za pomocą instrukcji INSERT lub UPDATE nie jest obsługiwane. Można jednak wstawić bardzo duży łańcuch znaków do elementów typów CLOB lub NCLOB oraz bardzo duży strumień binarny do kolumny typu BLOB, a także aktualizować dane tego rodzaju. Wymaga to jednak zastosowania funkcji lub procedury w języku PL/SQL. Najłatwiejszym rozwiązaniem problemu jest napisanie funkcji języka PL/SQL. Aby tworzyć obiekty typu CLOB i NCLOB, możesz napisać i wywołać niestandardową funkcję języka PL/SQL o parametrach przekazywanych przez wartość. Ta funkcja powinna przyjmować tabelę zagnieżdżoną obejmującą łańcuch znaków lub strumień bajtów o długości 32 767 bajtów, a następnie zwracać odpowiednio duży obiekt. W kodzie funkcji należy zastosować klauzulę RETURNING INTO w instrukcji INSERT lub UPDATE. Oto prototyp klauzuli RETURNING_INTO (jest ona przedstawiona na rysunku 10.1): RETURNING źródłowy_lokalizator INTO docelowy_lokalizator

Rysunek 10.1. Architektura działania niejawnych lokalizatorów typów LOB Źródłowy_lokalizator określa kolumnę z dużymi obiektami, a docelowy_lokalizator pozwala uzyskać dwukierunkowy potok używany do zapisywania w segmentach (porcjach) bardzo dużego łańcucha znaków lub strumienia binarnego. Początkowo dane są wstawiane lub aktualizowane za pomocą funkcji empty_blob (dla typu BLOB) albo empty_clob (dla typów CLOB i NCLOB). Funkcje empty_blob i empty_clob gwarantują, że początkowa wartość ma typ dużego obiektu. Klauzula RETURNING INTO zapewnia dwukierunkowo potok umożliwiający zapisywanie i dołączanie danych w kolumnie z dużymi obiektami.

346

Część II Programowanie w języku PL/SQL

Za pomocą tego podejścia można w instrukcjach INSERT i UPDATE bezpośrednio zapisywać bardzo duże łańcuchy znaków w kolumnach typu CLOB i NCLOB, przy czym wspomniane instrukcje należy umieścić w zasięgu transakcji. Takie instrukcje powinny rozpoczynać transakcję, a polecenia COMMIT lub ROLLBACK — zamykać zasięg transakcji. Poniższe prototypy instrukcji INSERT i UPDATE ilustrują specjalną technikę zarządzania typami LOB. To podejście działa w funkcjach języka PL/SQL i w zewnętrznych językach programowania (takich jak C, C++, C# i Java). Słowo kluczowe RETURNING w klauzuli RETURNING INTO może początkowo wydawać się dziwne. W tym kontekście oznacza ono przekazywanie referencji z kolumny do zmiennej lokalnej.

Instrukcja INSERT dla typów CLOB i NCLOB Poniższa instrukcja INSERT inicjuje kolumnę CLOB, a następnie zwraca lokalizator do zmiennej lokalnej przy użyciu klauzuli RETURNING INTO. Ta zmienna lokalna jest przekazywana przez referencję i ma tryb OUT. Szczegółowy opis tego trybu znalazł się w rozdziale 3. Najważniejsze jest, że takie ustawienie uniemożliwia określenie wartości parametru w sygnaturze funkcji. W instrukcji INSERT przypisanie w klauzuli VALUES odbywa się w ramach operacji w trybie IN. Instrukcja INSERT otwiera także zasięg transakcji. W jej zasięgu można dodawać i zastępować dane wskazywane przez lokalizator Oto prototyp opisywanej instrukcji INSERT:

Instrukcja UPDATE dla typów CLOB i NCLOB Poniższa instrukcja UPDATE ustawia wartość kolumny typu CLOB za pomocą funkcji empty_clob, a następnie zapisuje lokalizator z kolumny w zmiennej lokalnej przy użyciu klauzuli RETURNING INTO. Ta zmienna lokalna jest przekazywana przez referencję i ma tryb OUT. Podobnie jak instrukcja INSERT, instrukcja UPDATE otwiera zasięg transakcji, w którym można dodawać i zastępować dane wskazywane przez lokalizator. Oto prototyp omawianej instrukcji UPDATE:

Kod potrzebny dla typu BLOB wygląda bardzo podobnie jak dla typów CLOB i NCLOB. W poniższych podpunktach przedstawiono prototypy potrzebnych instrukcji INSERT i UPDATE.

Rozdział 10. Duże obiekty

347

Instrukcja INSERT dla typu BLOB Poniższa instrukcja INSERT inicjuje kolumnę typu BLOB, a następnie zwraca lokalizator do zmiennej lokalnej przy użyciu klauzuli RETURNING INTO. Ta zmienna lokalna jest przekazywana przez referencję i ma tryb OUT. Szczegółowy opis tego trybu znalazł się w rozdziale 3., a tu warto przypomnieć, że takie ustawienie uniemożliwia określenie wartości parametru w sygnaturze funkcji. W instrukcji INSERT przypisanie w klauzuli VALUES odbywa się w ramach operacji w trybie IN. Instrukcja INSERT otwiera także zasięg transakcji, w którym można dodawać i zastępować dane wskazywane przez lokalizator. Oto prototyp opisanej instrukcji INSERT:

Instrukcja UPDATE dla typu BLOB W tej instrukcji UPDATE typ kolumny nazwa_kolumny2 to BLOB. Instrukcja określa wartość tej kolumny, a następnie zapisuje lokalizator w zmiennej lokalnej, do czego służy klauzula RETURNING INTO. Ta zmienna lokalna jest przekazywana przez referencję i ma tryb OUT. Podobnie jak instrukcja INSERT, instrukcja UPDATE otwiera zasięg transakcji, w którym można dodawać i zastępować dane wskazywane przez lokalizator. Oto prototyp potrzebnej instrukcji UPDATE:

Jeśli obiekt jest naprawdę duży, początkowe przypisanie przy użyciu funkcji empty_clob lub empty_ blob jest zwykle najbardziej wydajne ze względu na wykorzystanie zasobów. Wskazówkę tę zawiera podręcznik Oracle Database Large Objects Developer’s Guide. Poniższa instrukcja INSERT SQL-a używa konstruktora empty_clob do wstawienia danych do kolumny item_desc tabeli item: SQL> INSERT INTO item VALUES 2 ( item_s1.nextval 3 ,'ASIN: B00003CXI1' ... 7 ,'Harry Potter and the Sorcerer''s Stone' 8 ,'Two-Disc Special Edition' 9 , empty_clob() -- Kolumna item_desc. 10 , empty_blob() -- Kolumna item_desc.

348 11 12

Część II Programowanie w języku PL/SQL , 'PG' , 'MPAA' ...

Wiersz 9. wstawia pusty obiekt typu CLOB do kolumny item_desc, a wiersz 10. wstawia pusty obiekt typu BLOB do kolumny item_blob. Po wstawieniu pustych obiektów typów CLOB i BLOB można zaktualizować je za pomocą pakietu dbms_lob. Podstawowa instrukcja UPDATE umożliwia zaktualizowanie kolumny typu CLOB lub NCLOB za pomocą łańcucha znaków o długości do 4000 lub 32 767 bajtów (w zależności od ustawienia parametru max_string_size). Taki limit obowiązuje przy próbie przypisania wartości typu VARCHAR2 lub NVARCHAR2. Ograniczenie to jest nieco inne przy aktualizowaniu kolumny typu CLOB zmienną typu LONG lub kolumny typu BLOB zmienną typu LONG RAW. Wynika to z tego, że oba typy LONG mają limit wielkości równy 32 760 bajtów. Aby wyeliminować to ograniczenie, można napisać funkcję składowaną, która tworzy i zwraca zmienną typu CLOB, NCLOB lub BLOB. Przekształcanie wartości z typu LONG na typ CLOB Przekształcenie kolumny typu LONG na wartość typu CLOB wymaga wykonania kilku kroków. Operacja ta jest wykonywana rzadko, ponieważ kolumny typu LONG występują tylko w tabelach baz Oracle. Takie kolumny zawierają zwykle tekst widoków i ciała wyzwalaczy bazy danych. Firma Oracle już od dawna ostrzega, że kolumny LONG zostaną uznane za przestarzałe. Choć ten typ istnieje w bazie Oracle Database 12c, wśród nowych funkcji bazy Oracle nie ma nowych narzędzi służących do przekształcania wartości tego typu. Funkcja to_char nie obsługuje parametrów typu LONG. Także instrukcja SELECT INTO nie potrafi przypisywać wartości kolumn typu LONG do zmiennych lokalnych. Trzeba więc zastosować pakiet dbms_sql i przekształcać wartości typu LONG na zwykłe łańcuchy znaków. Poniżej przedstawiono kompletne rozwiązanie przekształcające wartości z typu LONG na CLOB. Ten kod opakowuje nową, dostępną w pakiecie dbms_utility procedurę o parametrach przekazywanych przez referencję — expand_sql_text. Firma Oracle zaprojektowała tę procedurę, aby umożliwić przekształcenie widoków zależnych od innych widoków na zapytanie oparte wyłącznie na tabelach. Projekt procedury expand_sql_text powoduje pewne problemy, ponieważ widoki są przechowywane w kolumnach typu LONG, które trudno jest przekształcać na inne typy znakowe. Tylko pakiet dbms_sql (zobacz rozdział 13.) umożliwia przekształcanie wartości typu LONG na dane typu VARCHAR2, CLOB lub NCLOB. Procedura opakowująca przyjmuje nazwę widoku, przekształca dane typu LONG na dane typu CLOB, wywołuje nową procedurę, a następnie zwraca wynik typu CLOB. To rozwiązanie to funkcja o parametrach przekazywanych przez wartość, co oznacza, że można ją wywoływać w zapytaniach. W kodzie używana jest funkcja lokalna sprawdzająca nazwę widoku, ponieważ pakiet dbms_assert umożliwia tylko badanie nazw tabel. W rozwiązaniu zastosowano kilka zaawansowanych sztuczek. Komentarze powinny ułatwić Ci ich zrozumienie. W pokazanej poniżej funkcji wykorzystano nieomówione jeszcze techniki z pakietu dbms_sql, przedstawione w rozdziale 13. Najważniejsze wiersze kodu wykorzystujące ten pakiet są wyróżnione pogrubieniem. Jeśli masz jakieś pytania na temat korzystania z pakietu dbms_sql do generowania dynamicznych instrukcji SQL-a, zapoznaj się z rozdziałem 13. SQL> CREATE OR REPLACE FUNCTION expand_sql_text 2 ( pv_view_name VARCHAR2 ) 3 RETURN CLOB AS 4 5 /* Deklaracja zmiennych do przechowywania widoków. */ 6 lv_input_view CLOB; 7 lv_output_view CLOB; 8 9 /* Deklaracja zmiennej (potrzebna z powodu ograniczeń instrukcji SELECT INTO). */ 10 lv_long_view LONG; 11 12 /* Deklaracje zmiennych lokalnych związanych z dynamicznymi instrukcjami SQL-a. */ 13 lv_cursor INTEGER := dbms_sql.open_cursor; 14 lv_feedback INTEGER; -- Potwierdzenie wykonania dynamicznej instrukcji. 15 lv_length INTEGER; -- Długość łańcucha znaków. 16 lv_return CLOB; -- Dane wyjściowe funkcji.

Rozdział 10. Duże obiekty

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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78

lv_stmt lv_string

VARCHAR2(2000); VARCHAR2(32760);

-- Dynamiczna instrukcja SQL-a. -- Maksymalna długość typu LONG.

/* Deklaracja wyjątku zdefiniowanego przez użytkownika. */ invalid_view_name EXCEPTION; PRAGMA EXCEPTION_INIT(invalid_view_name, -20001); /* Deklaracja kursora dynamicznego. */ CURSOR c (cv_view_name VARCHAR2) IS SELECT text FROM user_views WHERE view_name = cv_view_name; FUNCTION verify_view_name ( pv_view_name VARCHAR2 ) RETURN BOOLEAN AS /* Domyślna zwracana wartość. */ lv_return_result BOOLEAN := FALSE; /* Deklaracja kursora używanego do sprawdzania nazwy widoku. */ CURSOR c (cv_view_name VARCHAR2) IS SELECT NULL FROM user_views WHERE view_name = cv_view_name; BEGIN FOR i IN c (pv_view_name) LOOP lv_return_result := TRUE; END LOOP; RETURN lv_return_result; END verify_view_name; BEGIN /* Zgłaszanie wyjątku, gdy nazwa widoku jest nieprawidłowa. */ IF NOT verify_view_name(pv_view_name) THEN RAISE invalid_view_name; END IF; /* Otwieranie, pobieranie i zamykanie kursora w celu zapisania tekstu widoku. */ OPEN c(pv_view_name); FETCH c INTO lv_long_view; -- Pobranie do zmiennej typu LONG. CLOSE c; /* Tworzenie dynamicznej instrukcji. */ lv_stmt := 'SELECT text'||CHR(10) || 'FROM user_views'||CHR(10) || 'WHERE view_name = '''||pv_view_name||''''; /* Parsowanie danych i definiowanie kolumny typu LONG. */ dbms_sql.parse(lv_cursor, lv_stmt, dbms_sql.native); dbms_sql.define_column_long(lv_cursor,1); /* Próba przetworzenia zwracanej wartości po pobraniu danych. */ IF dbms_sql.execute_and_fetch(lv_cursor) = 1 THEN dbms_sql.column_value_long( lv_cursor , 1 , LENGTH(lv_long_view) -- Określanie długości zmiennej typu LONG. , 0 , lv_string -- Zwracanie łańcucha znaków typu VARCHAR2. , lv_length); END IF;

349

350

Część II Programowanie w języku PL/SQL

79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105

/* Sprawdzanie, czy kursor jest otwarty. */ IF dbms_sql.is_open(lv_cursor) THEN dbms_sql.close_cursor(lv_cursor); END IF; /* Tworzenie lokalnej tymczasowej wartości typu CLOB w pamięci: - Wywołanie zwraca utworzony obiekt lv_input_view. - Nieuwzględniana jest wartość z pamięci podręcznej, - Parametr czasu życia jest ustawiany na 12 (jest to wartość zmiennej dbms_lob.call z poziomu pakietu), a wartość domyślna to 10. */ dbms_lob.createtemporary(lv_input_view, FALSE, dbms_lob.CALL); /* Dołączanie danych typu LONG do pustej tymczasowej wartości typu CLOB. */ dbms_lob.WRITE(lv_input_view, LENGTH(lv_long_view), 1, lv_string); /* Przekazywanie tekstu widoku i pobieranie kompletnego tekstu. */ dbms_utility.expand_sql_text(lv_input_view, lv_output_view); /* Zwracanie wyjściowej wartości typu CLOB. */ RETURN lv_output_view; EXCEPTION WHEN invalid_view_name THEN RAISE_APPLICATION_ERROR(-20001,'Nieprawidłowa nazwa widoku.'); WHEN OTHERS THEN RETURN NULL; END expand_sql_text; /

Wiersze od 55. do 57. otwierają kursor powiązany z widokiem, pobierają z niego dane, a następnie zamykają go. Instrukcja pobierania z wiersza 56. zapisuje wartość kolumny typu LONG w zmiennej lokalnej tego samego typu. Choć nie można bezpośrednio wczytać wartości zmiennej typu LONG, wbudowana instrukcja LENGTH SQL-a umożliwia określenie długości zmiennej. Na podstawie tej długości można wczytać zawartość zmiennej typu LONG do zmiennej typu CLOB za pomocą jednego wywołania procedury dbms_sql.column_value_long (wiersze od 70. do 76.). Wiersz 89. tworzy tymczasową zmienną typu CLOB i działa podobnie jak funkcja empty_clob. Różnica polega na tym, że użyty tu kod otwiera uchwyt, który pozwala zapisywać dane bezpośrednio do zmiennej lokalnej typu CLOB, co program robi w wierszu 92. Wiersz 95. to wywołanie nowej procedury expand_sql_text z pakietu dbms_utility. Ta procedura przyjmuje widok zależny od innych widoków i przekształca go na zapytanie oparte wyłącznie na tabelach. Jeśli chcesz pobrać tylko tekst widoku zapisany jako obiekt typu CLOB, zmień wiersz 95. Zamiast wywoływać procedurę expand_sql_text, przypisz jedną zmienną typu CLOB do drugiej zmiennej tego typu: 95 lv_output_view := lv_input_view;

Ponieważ długość widoków jest ograniczona do 32 760 bajtów (jest to maksymalny rozmiar typu LONG), ta funkcja może też zwracać wartość typu VARCHAR2.

Problem z opakowywaniem tworzenia dużych obiektów w funkcje polega na tym, że pamięć jest wtedy ograniczona pojemnością obszaru SGA w bazie Oracle Database 12c. Choć stosowanie instrukcji UPDATE nie jest w tym kontekście optymalnym rozwiązaniem, poniżej pokazano, jak można zaktualizować wartość kolumny item_desc literałem znakowym: SQL> 2 3 4

UPDATE item SET item_desc = 'Harry Potter na pozór jest zwyczajnym jedenastolatkiem' WHERE item_title = 'Harry Potter and the Sorcerer''s Stone' AND item_type IN ...

Ta instrukcja UPDATE ustawia wartość kolumny item_desc w wierszu 2. na łańcuch znaków krótszy niż 32 767 bajtów. Gdy używane duże obiekty są większe, trzeba zastosować procedurę writeappend z pakietu dbms_lob, aby dołączać dodatkowe dane po zapisaniu początkowej wartości. To podejście

Rozdział 10. Duże obiekty

351

pokazano w punkcie „Wczytywanie plików lokalnych do kolumn typów CLOB i NCLOB” w dalszej części rozdziału. Warto zauważyć, że wartości do kolumn typu CLOB można przypisywać także za pomocą klauzuli VALUES w instrukcji INSERT. W tym celu należy napisać funkcję zdefiniowaną przez użytkownika. W następnym podpunkcie pokazano, jak utworzyć taką funkcję.

Używanie funkcji języka PL/SQL do przypisywania wartości do elementów typu CLOB W języku PL/SQL istnieje też drugi sposób wstawiania łańcuchów znaków do kolumn typu CLOB. Jest on dostępny wtedy, gdy długość wstawianego łańcucha nie przekracza 32 767 bajtów. Choć istnieje tylko jeden sposób przypisywania łańcuchów znaków o długości większej niż 32 767 bajtów, dobrze jest mieć kilka możliwości wstawiania krótszych łańcuchów. Omawiana tu technika umożliwia bezpośrednie wstawienie łańcucha do kolumny typu CLOB bez wcześniejszego inicjowania kolumny za pomocą wywołania funkcji empty_clob. W tym celu należy napisać funkcję opakowującą o parametrach przekazywanych przez wartość. Funkcja ta powinna przyjmować dane typu VARCHAR2 o długości do 32 767 bajtów. Przedstawiona poniżej funkcja create_clob przyjmuje łańcuch znaków, a następnie wywołuje dwie dostępne w pakiecie dbms_lob procedury o parametrach przekazywanych przez referencję, aby przekształcić ten łańcuch na wartość typu CLOB. Oto kod tej funkcji: SQL> CREATE OR REPLACE FUNCTION create_clob 2 ( pv_input_string VARCHAR2 ) 3 RETURN CLOB AS 4 /* Deklaracja lokalnej zmiennej typu CLOB. */ 5 lv_return CLOB; 6 BEGIN 7 /* Tworzenie tymczasowej zmiennej typu CLOB w pamięci w zasięgu wywołania. */ 8 dbms_lob.createtemporary(lv_return, FALSE, dbms_lob.CALL); 9 10 /* Zapisywanie łańcucha znaków do pustej tymczasowej zmiennej typu CLOB. */ 11 dbms_lob.WRITE(lv_return, LENGTH(pv_input_string), 1, pv_input_string); 12 13 /* Zwracanie wartości typu CLOB. */ 14 RETURN lv_return; 15 END create_clob; 16 /

Wiersz 8. tworzy tymczasową zmienną typu CLOB.Wiersz 11. sprawdza długość łańcucha znaków i przekształca go na zmienną typu CLOB. Wiersz 14. zwraca tymczasową zmienną typu CLOB i umożliwia umieszczenie wywołania tej funkcji w klauzuli VALUES. Jest to możliwe, ponieważ kod przypisuje zainicjowaną wartość typu CLOB do kolumny tego samego typu. To oznacza, że nie trzeba rzutować typów. Powtórzenie materiału W tym podrozdziale opisano następujące zagadnienia dotyczące korzystania z dużych obiektów:  Typy CLOB, NCLOB i BLOB umożliwiają zapisywanie łańcuchów znaków o długości od 8 do 128 terabajtów. W bazie Oracle Database 12c parametr db_block_size można ustawić na wartość z przedziału od 2 do 32 kilobajtów.  CLOB, NCLOB i BLOB to typy obiektowe. Baza Oracle nie udostępnia wygodnej funkcji wbudowanej SQL-a przeznaczonej do tworzenia dużych obiektów.  Pakiet dbms_lob zawiera funkcje i procedury służące do wczytywania, zapisywania i dołączania danych w zmiennych oraz kolumnach typu CLOB.  Pojęcia deskryptor i lokalizator czasem są stosowane zamiennie, jednak firma Oracle preferuje słowo lokalizator do określania wskaźnika do lokalizacji obiektów typu CLOB przechowywanych w bazie danych.  Zmienne i kolumny typu CLOB mogą mieć wartość null, a także mogą być puste lub zapełnione.

352

Część II Programowanie w języku PL/SQL

Oracle obsługuje bezpośrednie zapisywanie łańcuchów znaków w języku PL/SQL, ale już nie w SQL-u. To oznacza, że trzeba napisać funkcję, która przyjmuje łańcuch znaków i zwraca wartość typu CLOB, aby móc przekazać łańcuch znaków do klauzuli VALUES w instrukcji INSERT.  Przy wstawianiu lub aktualizowaniu danych o długości większej niż 32 767 bajtów trzeba zainicjować obiekt typu CLOB za pomocą wywołania funkcji empty_clob. Segmenty danych należy przekazywać za pomocą lokalizatora w porcjach nie większych niż 32 767 bajtów. 

Wczytywanie plików do wewnętrznie przechowywanych kolumn Pakiet dbms_lob udostępnia wszystkie narzędzia potrzebne do wczytywania dużych obiektów, jeśli ich rozmiar przekracza ograniczenia obecne w językach SQL i PL/SQL. Pierwszy etap polega na zdefiniowaniu katalogu wirtualnego. Katalog wirtualny to wewnętrzny alias katalogu wskazujący ścieżkę w postaci kanonicznej. Poniższy fragment kodu tworzy katalog wirtualny wskazujący lokalny katalog tymczasowy. Aby zdefiniować katalog wirtualny, trzeba nawiązać połączenie jako użytkownik system i użyć poleceń odpowiednich dla systemu operacyjnego: Linux lub Unix SQL> CREATE DIRECTORY generic AS '/tmp';

Windows SQL> CREATE DIRECTORY generic AS 'C:\Windows\temp';

Po utworzeniu katalogu wirtualnego trzeba przyznać uprawnienia odczytu danych z tego katalogu użytkownikowi student z bazy CDB lub PDB. Służy do tego następująca składnia: SQL> GRANT READ ON DIRECTORY generic TO student;

Dostępne są dwie techniki wczytywania plików. Opisano je w dwóch następnych punktach. Jedna metoda polega na uruchomieniu na serwerze procedury składowanej języka PL/SQL. Druga wymaga zastosowania zewnętrznego języka programowania. Tu będzie to język PHP.

Wczytywanie lokalnych plików do kolumn typu CLOB lub NCLOB Następny krok polega na wczytaniu pliku i zapisaniu danych w kolumnie typu CLOB. Choć możliwe jest przedstawienie tych operacji za pomocą kilku fragmentów kodu, poniżej znajduje się cały przykładowy program. Dzięki temu możesz skopiować kod i wkleić go we własnej aplikacji. W przykładzie użyto języka NDS (ang. Native Dynamic SQL). Mechanizmy jego działania zostały opisane w rozdziale 13. Poniższa procedura load_clob_from_file przedstawia wczytywanie danych z pliku: SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15

CREATE OR REPLACE PROCEDURE load_clob_from_file ( src_file_name IN VARCHAR2 , table_name IN VARCHAR2 , column_name IN VARCHAR2 , primary_key_name IN VARCHAR2 , primary_key_value IN VARCHAR2 ) IS /* Definicja zmiennych lokalnych na potrzeby procedury DBMS_LOB.LOADCLOBFROMFILE. */ des_clob CLOB; src_clob BFILE := BFILENAME('GENERIC',src_file_name); des_offset NUMBER := 1; src_offset NUMBER := 1; src_size INTEGER; ctx_lang NUMBER := dbms_lob.default_lang_ctx; warning NUMBER;

Rozdział 10. Duże obiekty

353

16 17 /* Definicja zmiennej lokalnej używanej w kodzie NDS. */ 18 stmt VARCHAR2(2000); 19 BEGIN 20 /* Otwarcie pliku źródłowego jest konieczne. */ 21 IF dbms_lob.fileexists(src_clob) = 1 AND NOT 22 dbms_lob.isopen(src_clob) = 1 THEN 23 src_clob_size := dbms_lob.getlength(src_clob); 24 dbms_lob.open(src_clob,DBMS_LOB.LOB_READONLY); 25 END IF; 26 27 /* Dynamiczne przypisanie łańcucha znaków do instrukcji. */ 28 stmt := 'UPDATE '||table_name||' ' 29 || 'SET '||column_name||' = empty_clob() ' 30 || 'WHERE '||primary_key_name||' = '||''''||primary_key_value||''' ' 31 || 'RETURNING '||column_name||' INTO :locator'; 32 33 /* Uruchomienie dynamicznej instrukcji. */ 34 EXECUTE IMMEDIATE stmt USING OUT des_clob; 35 36 /* Odczyt pliku i jego zapis do obiektu typu CLOB oraz zamknięcie pliku. */ 37 dbms_lob.loadclobfromfile( dest_lob => des_clob 38 , src_bfile => src_clob 39 , amount => dbms_lob.getlength(src_clob) 40 , dest_offset => des_offset 41 , src_offset => src_offset 42 , bfile_csid => dbms_lob.default_csid 43 , lang_context => ctx_lang 44 , warning => warning ); 45 dbms_lob.close(src_clob); 46 47 /* Sprawdzanie warunków wyjściowych przed zatwierdzeniem operacji. */ 48 IF src_clob_size = dbms_lob.getlength(des_clob) THEN 49 $IF $$DEBUG = 1 $THEN 50 dbms_output.put_line('Sukces!'); 51 $END 52 COMMIT; 53 ELSE 54 $IF $$DEBUG = 1 $THEN 55 dbms_output.put_line('Niepowodzenie.'); 56 $END 57 RAISE dbms_lob.operation_failed; 58 END IF; 59 END load_clob_from_file; 60 /

Ta procedura przyjmuje argumenty, które umożliwiają użycie jej dla każdej tabeli o dwóch odpowiednich kolumnach: jednej typu CLOB i drugiej z kluczem głównym. Funkcja bfilename (wiersz 10.) daje dostęp do ścieżki kanonicznej do katalogu bazy danych i dołącza do niej nazwę pliku. Procedura open (wiersz 24.) otwiera plik zewnętrzny i wczytuje go do obiektu typu BFILE. Dynamiczna instrukcja UPDATE ustawia wartość kolumny CLOB przez wywołanie konstruktora empty_clob(). Następnie instrukcja UPDATE zwraca określoną kolumnę do zmiennej wyjściowej. W użytej instrukcji języka NDS jest to zmienna :locator. Po uruchomieniu instrukcji języka NDS program przypisuje lokalizator obiektu typu CLOB do zmiennej des_clob. Instrukcja UPDATE z klauzulą RETURNING INTO modyfikuje wartości docelowej kolumny we wszystkich aktualizowanych wierszach.

Przedstawione operacje wczytują plik źródłowy i przenoszą lokalizator kolumny typu CLOB do zasięgu programu. Przy użyciu tych dwóch uchwytów zasobów procedura loadclobfromfile (wiersze od 37. do 44.) przesyła zawartość otwartego pliku do obiektu wskazanego przez lokalizator typu CLOB. Tego rodzaju operacje odczytu i zapisu nie podlegają ograniczeniu rozmiaru do 32767 bajtów obowiązującemu dla typu VARCHAR2.

354

Część II Programowanie w języku PL/SQL

Choć przykładowy kod wczytuje cały plik w jednej operacji, możliwe, że programista chce wczytywać fragmenty dużych plików bezpośrednio do kolumn typu CLOB. Wartości pozycji w pliku źródłowym (src_offset) i w docelowej kolumnie typu CLOB (dest_offset) umożliwiają przetwarzanie fragmentów pliku i umieszczanie ich we wspomnianej kolumnie. Wystarczy dodać pętlę, aby wczytywać duże pliki we fragmentach, a nie w jednym przejściu. Można przetestować utworzoną wcześniej procedurę składowaną, uruchamiając poniższy program w postaci bloku anonimowego: SQL> BEGIN 2 FOR i IN (SELECT item_id 3 FROM item 4 WHERE item_title = 'The Lord of the Rings - Fellowship ...' 5 AND item_type IN 6 (SELECT common_lookup_id 7 FROM common_lookup 8 WHERE common_lookup_table = 'ITEM' 9 AND common_lookup_column = 'ITEM_TYPE' 10 AND REGEXP_LIKE( common_lookup_type 11 ,'^(DBD|VHS)*'))) LOOP 12 13 -- Wywołanie procedury wczytującej i zapisującej dane typu CLOB. 14 load_clob_from_file( src_file_name => 'LOTRFellowship.txt' 15 , table_name => 'ITEM' 16 , column_name => 'ITEM_DESC' 17 , primary_key_name => 'ITEM_ID' 18 , primary_key_value => TO_CHAR(i.item_id) ); 19 END LOOP; 20 END; 21 /

Wywołanie procedury load_clob_from_file (wiersze od 14. do 18.) wczytuje tę samą wartość do każdego wiersza, w którym sztuczny klucz główny item_id jest zgodny z regułą biznesową określoną za pomocą wyrażenia regularnego. To wyrażenie powoduje, że kod pobiera wszystkie wiersze reprezentujące płyty DVD i kasety VHS, w których kolumna item_title ma wartość "The Lord of the Rings - Fellowship of the Ring", a tekst w kolumnie item_type rozpoczyna się od podłańcucha DVD lub VHS. W dodatku E dokładniej opisano używanie wyrażeń regularnych w kodzie PL/SQL w Oracle Database 12c. Aby się przekonać, że kolumny CLOB w trzech nowych wierszach zawierają dane o długości ponad 4000 bajtów, należy uruchomić poniższy kod formatujący i zapytanie: SQL> SQL> SQL> SQL> SQL> SQL> 2 3 4 5

-- Formatowanie kolumn w celu ich wyświetlenia. COL item_id FORMAT 9999 COL item_title FORMAT A50 COL size FORMAT 9,999,999 -- Zapytanie o rozmiar kolumny. SELECT item_id , item_title , dbms_lob.getlength(item_desc) AS "SIZE" FROM item WHERE dbms_lob.getlength(item_desc) > 0;

Ten kod wyświetli trzy poniższe wiersze: ITEM_ID ----------1037 1038 1039

ITEM_TITLE ---------------------------------------------The Lord of the Rings - Fellowship of the Ring The Lord of the Rings - Fellowship of the Ring The Lord of the Rings - Fellowship of the Ring

SIZE ----5,072 5,072 5,072

W tym punkcie pokazano, jak bezpośrednio wczytać dane z pliku do kolumny typu CLOB. Tych samych technik można używać do obsługi typów NCLOB, natomiast korzystanie z typu BLOB związane jest z pewnym specyficznym zagadnieniem, opisanym w następnym punkcie. Dowiedziałeś się także,

Rozdział 10. Duże obiekty

355

jak używać pakietu dbms_lob do wczytywania plików zewnętrznych. Warto zauważyć, że z omówionymi metodami wiąże się mniej ograniczeń w obszarze zabezpieczeń niż z pakietem utl_file oraz zewnętrznymi operacjami wejścia i wyjścia na plikach w języku Java.

Wczytywanie plików lokalnych do kolumn typu BLOB Z tego punktu dowiesz się, jak wczytać plik i zapisać jego zawartość do kolumny typu BLOB. Podobnie jak w poprzednim punkcie tak i tu można zaprezentować rozwiązanie za pomocą kilku krótszych fragmentów kodu, jednak przedstawiony jest kompletny przykład, który można wyciąć i wkleić do aplikacji. W tym kodzie wykorzystano język NDS, którego omówienie znajdziesz w rozdziale 13. Poniższa procedura load_blob_from_file przedstawia wczytywanie danych z pliku: SQL> 2 3 4 5 6 7 8 9 10 11 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

CREATE OR REPLACE PROCEDURE load_blob_from_file ( src_file_name IN VARCHAR2 , table_name IN VARCHAR2 , column_name IN VARCHAR2 , primary_key_name IN VARCHAR2 , primary_key_value IN VARCHAR2 ) IS /* Definicja zmiennych lokalnych potrzebnych w procedurze DBMS_LOB.LOADCLOBFROMFILE. */ des_blob BLOB; src_blob BFILE := BFILENAME('GENERIC',src_file_name); des_offset NUMBER := 1; src_offset NUMBER := 1; /* Określenie rozmiaru przed wczytywaniem. */ src_blob_size NUMBER; /* Definicja zmiennej lokalnej używanej w kodzie NDS. */ stmt VARCHAR2(2000); BEGIN /* Otwarcie pliku źródłowego jest niezbędne. */ IF dbms_lob.fileexists(src_blob) = 1 AND NOT dbms_lob.isopen(src_blob) = 1 THEN src_blob_size := dbms_lob.getlength(src_blob); dbms_lob.open(src_blob,DBMS_LOB.LOB_READONLY); END IF; /* Dynamiczne przypisanie łańcucha znaków do instrukcji. */ stmt := 'UPDATE '||table_name||' ' || 'SET '||column_name||' = empty_blob() ' || 'WHERE '||primary_key_name||' = '||''''||primary_key_value||''' ' || 'RETURNING '||column_name||' INTO :locator'; /* Uruchomienie instrukcji dynamicznej. */ EXECUTE IMMEDIATE stmt USING OUT des_blob; /* Odczyt pliku i zapis danych w kolumnie typu BLOB. */ dbms_lob.loadblobfromfile( dest_lob => des_blob , src_bfile => src_blob , amount => dbms_lob.getlength(src_blob) , dest_offset => des_offset , src_offset => src_offset ); /* Zamykanie otwartego pliku źródłowego. */ dbms_lob.close(src_blob); /* Zatwierdzenie zapisu. */ IF src_blob_size = dbms_lob.getlength(des_blob) THEN $IF $$DEBUG = 1 $THEN dbms_output.put_line('Sukces!'); $END COMMIT; ELSE $IF $$DEBUG = 1 $THEN dbms_output.put_line('Porażka.'); $END RAISE dbms_lob.operation_failed; END IF; END load_blob_from_file; /

356

Część II Programowanie w języku PL/SQL

Ta procedura przyjmuje pięć argumentów (wiersze od 2. do 6.), które umożliwiają użycie jej dla każdej tabeli o kolumnie typu BLOB i jednokolumnowym sztucznym kluczu głównym. Funkcja bfilename zapewnia dostęp do ścieżki kanonicznej (wiersz 9.). Program sprawdza, czy plik zewnętrzny istnieje i nie jest otwarty (wiersze 18. i 19.), a następnie za pomocą procedury open (wiersz 21.) otwiera go i wczytuje do obiektu typu BFILE. Dynamiczna instrukcja UPDATE (wiersze od 24. do 27.) ustawia wartość kolumny BLOB przez wywołanie funkcji empty_blob. Następnie program zwraca określoną kolumnę do zmiennej wyjściowej. W użytej instrukcji języka NDS jest to zmienna powiązana :locator. Po uruchomieniu instrukcji języka NDS program przypisuje lokalizator obiektu typu CLOB do zmiennej des_clob. Wywołanie procedury loadblobfromfile (wiersze od 31. do 35.) powoduje wczytanie danych do obiektu typu BFILE. W wierszu 39. program porównuje wielkość pliku zewnętrznego z wielkością danych w kolumnie typu BLOB, a następnie (wiersz 43.) zatwierdza transakcję. Przy testowaniu procedury na wcześniejszym etapie sesji należy ustawić parametr PLSQL_CCFLAGS, co opisano w rozdziale 5. Przedstawione operacje wczytują plik źródłowy i lokalizator docelowej kolumny typu BLOB do zasięgu programu. Przy użyciu tych dwóch uchwytów procedura loadclobfromfile przesyła zawartość otwartego pliku do obiektu wskazanego przez lokalizator typu BLOB. Tego rodzaju operacje odczytu i zapisu umożliwiają bezpośredni zapis dużych porcji pliku do kolumny typu BLOB. Wartości pozycji w pliku źródłowym (src_offset) i w docelowej kolumnie typu BLOB (dest_offset) umożliwiają przetwarzanie fragmentów pliku i umieszczanie ich w tej kolumnie. Można dodać pętlę, aby przesyłać porcje danych, jeśli program korzysta z bardzo dużych plików binarnych, na przykład zawierających filmy. Aby przetestować utworzoną wcześniej procedurę składowaną, należy uruchomić poniższy program w postaci bloku anonimowego: SQL> BEGIN 2 FOR i IN (SELECT item_id 3 FROM item 4 WHERE item_title = 'Harry Potter and the Sorcerer''s Stone' 5 AND item_type IN 6 (SELECT common_lookup_id 7 FROM common_lookup 8 WHERE common_lookup_table = 'ITEM' 9 AND common_lookup_column = 'ITEM_TYPE' 10 AND REGEXP_LIKE(common_lookup_type,'^(dvd|vhs)*','i'))) LOOP 11 /* Wywoływanie procedury dla odpowiednich wierszy. */ 12 load_blob_from_file( src_file_name => 'HarryPotter1.png' 13 , table_name => 'ITEM' 14 , column_name => 'ITEM_BLOB' 15 , primary_key_name => 'ITEM_ID' 16 , primary_key_value => TO_CHAR(i.item_id) ); 17 END LOOP; 18 END; 19 /

Program wywołuje procedurę load_blob_from_file przy wykryciu każdej wartości item_id zgodnej z regułami biznesowymi określonymi w wyrażeniu regularnym. To wyrażenie powoduje, że kod pobiera wszystkie wiersze reprezentujące płyty DVD i kasety VHS, w których kolumna item_title ma wartość „Harry Potter and the Sorcerer’s Stone” (w kodzie apostrof w tym łańcuchu znaków wymaga poprzedzenia drugim apostrofem), a tekst w kolumnie item_type rozpoczyna się od podłańcucha DVD lub VHS. Powoduje to wczytanie obrazów do wszystkich docelowych kolumn w dopasowanych wierszach. Używanie wyrażeń regularnych w kodzie PL/SQL w Oracle Database 12c dokładniej opisano w dodatku E. Aby się przekonać, że kolumny BLOB w dwóch zmodyfikowanych wierszach zawierają dłuższe strumienie danych binarnych, należy uruchomić poniższy kod formatujący i zapytanie: SQL> SQL> SQL> SQL> SQL> SQL> 2

-- Formatowanie kolumn w celu ich wyświetlenia. COL item_id FORMAT 9999 COL item_title FORMAT A50 COL size FORMAT 9,999,999 -- Zapytanie o rozmiar kolumny. SELECT item_id , item_title

Rozdział 10. Duże obiekty 3 4 5

357

, dbms_lob.getlength(item_blob) AS "SIZE" FROM item WHERE dbms_lob.getlength(item_blob) > 0;

Ten kod wyświetli trzy poniższe wiersze: ITEM_ID ----------1021 1022

ITEM_TITLE SIZE ---------------------------------------------- ------Harry Potter and the Sorcerer's Stone 121,624 Harry Potter and the Sorcerer's Stone 121,624

W tym punkcie pokazano, jak bezpośrednio wczytywać dane z pliku do kolumny typu BLOB. Czytelnik przypomniał sobie także, jak używać pakietu dbms_lob do wczytywania plików zewnętrznych. Warto zauważyć, że z omówionymi metodami wiąże się mniej ograniczeń w obszarze zabezpieczeń niż z pakietem utl_file oraz zewnętrznymi operacjami wejścia i wyjścia na plikach w języku Java.

Używanie dużych obiektów za pomocą stron WWW Obecne w języku PL/SQL ograniczenia związane z przesyłaniem oraz zapisem kolumn typu CLOB i NCLOB obowiązują także w innych językach programowania. Programista musi określić, czy chce zapisywać małe porcje danych (32 767 bajtów), czy duże (1 megabajt i więcej). W tym punkcie omówiono wczytywanie i zapis dużych porcji danych. Rozwiązanie oparte na języku PHP jest łatwiejsze niż stosowanie ziaren EJB (ang. Enterprise JavaBeans), ponieważ obejmuje mniej elementów wymagających skonfigurowania. Warto zauważyć, że w tym rozwiązaniu tworzona jest procedura języka PL/SQL, która współdziała z dowolnym sieciowym językiem programowania obsługującym biblioteki Oracle JDBC lub OCI8. W kontekście języka PHP zapoznasz się z mechanizmem wczytywania pliku na serwer. We wcześniejszym rozwiązaniu opartym na języku PL/SQL ten plik znajdował się już w katalogu /tmp (w magiczny sposób utworzonym przez administratora bazy Oracle). W trakcie analizy przedstawianego tu programu zakładam, że umiesz programować w języku PHP. Jeśli tego nie potrafisz, zapoznaj się z książką Oracle Database 10g Express Edition PHP Web Programming (Oracle Press, 2006) albo podręcznikiem The Underground PHP and Oracle Manual lub Oracle Database 2 Day + PHP Developer’s Guide (oba przygotowane przez firmę Oracle). W tekście znajdziesz tylko najważniejsze elementy kodu w języku PHP. Kompletny przykład możesz pobrać z witryny wydawnictwa Helion. Wersja OCI8 obsługująca mechanizm DRCP (ang. Database Resident Connection Pooling) jest dostępna w bazie Oracle Database 11g i jej nowszych edycjach.

Przygotowania potrzebne do utworzenia tego rozwiązania różnią się od tego, co trzeba zrobić w trakcie pisania kodu w samym języku PL/SQL (wtedy należy na przykład utworzyć katalog wirtualny i przyznać uprawnienia do odczytu danych). W rozwiązaniu opartym na języku PHP trzeba zainstalować narzędzia Apache HTTP Server i Zend Core for Oracle, a następnie utworzyć katalog fizyczny w katalogu DocumentRoot. Ścieżkę kanoniczną do tego katalogu znajdziesz w pliku httpd.conf serwera Apache. Ścieżka kanoniczna do katalogu DocumentRoot wygląda zwykle tak: Linux lub Unix /var/www/html

Windows C:\Program Files\Apache Group\Apache2\htdocs

Po pobraniu potrzebnych plików z witryny wydawnictwa dodaj do katalogu DocumentRoot katalog temp. Następnie skopiuj niezbędne pliki do katalogu DocumentRoot. Ponadto umieść pliki graficzne i tekstowe w lokalnym katalogu klienckim, z poziomu którego chcesz uruchomić sesję przeglądarki.

Procedury wczytujące dane do kolumn typu CLOB, NCLOB lub BLOB W tym punkcie utworzysz dwie procedury składowane: jedną do zarządzania wartościami typów CLOB i NCLOB, a drugą do zarządzania wartościami typu BLOB. Jeśli używasz bazy PDB videodb, skompiluj w jej schemacie poniższą procedurę o parametrach przekazywanych przez referencję. Programy w języku

358

Część II Programowanie w języku PL/SQL

PHP będą używać tej procedury do zabezpieczania połączenia w trakcie wczytywania dużych plików tekstowych i graficznych. SQL> 2 3 4 5 6 7 8 9 10 11

CREATE OR REPLACE PROCEDURE web_load_clob_from_file ( item_id_in IN NUMBER , descriptor IN OUT CLOB ) IS BEGIN /* To transakcja DML. */ UPDATE item SET item_desc = empty_clob() WHERE item_id = item_id_in RETURNING item_desc INTO descriptor; END web_load_clob_from_file; /

Procedura web_load_clob_from_file umożliwia otwarcie lokalizatora CLOB i uzyskanie do niego dostępu z poziomu pliku PHP. Ma ona trzy kluczowe cechy. Po pierwsze, parametr formalny w wierszu 3. to lokalizator typu CLOB w trybie IN OUT. Po drugie, klauzula RETURNING INTO w wierszu 9. zapewnia połączenie między zmienną lokalną a zmienną z klauzuli SET z wiersza 7. Po trzecie, brak instrukcji COMMIT w tej procedurze składowanej powoduje, że kolumna CLOB pozostaje zablokowana, a zasięg transakcji DML jest otwarty przez cały czas wykonywania zewnętrznego programu sieciowego. Teraz trzeba skompilować poniższą procedurę składowaną, aby zarządzała wczytywaniem kolumn typu BLOB w schemacie video z bazy PDB videodb: SQL> 2 3 4 5 6 7 8 9 10 11

CREATE OR REPLACE PROCEDURE web_load_blob_from_file ( item_id_in IN NUMBER , descriptor IN OUT BLOB ) IS BEGIN -- Klauzula FOR UPDATE spowoduje utworzenie transakcji DML. UPDATE item SET item_blob = empty_blob() WHERE item_id = item_id_in RETURNING item_blob INTO descriptor; END web_load_blob_from_file; /

Procedura web_load_blob_from_file jest prawie identyczna z procedurą web_load_clob_from_file. Różnią się one jednym aspektem — zmienna w trybie IN OUT jest tu typu BLOB, a nie CLOB. Choć uniwersalna klasa bazowa dla typów BLOB i CLOB (na przykład LOB) byłaby bardzo wygodna, obecnie w bazie Oracle Database 12c nie można jej zdefiniować.

Kod w językach HTML i PHP używany do wczytywania danych typu CLOB i BLOB Pierwszym krokiem w kodzie PHP jest wczytanie fizycznego pliku z klienta na serwer. Wymaga to zastosowania formularza HTML-a i pliku ze skryptem PHP. Dla typów CLOB i BLOB należy utworzyć osobne formularze HTML-a. Oba pliki HTML z formularzami znajdziesz w witrynie wydawnictwa Helion. Oto istotny fragment pliku UploadItemDescriptionForm.htm:

...



Para nazwa – wartość w atrybucie action określa, który plik PHP należy wywołać w odpowiedzi na przesłanie formularza. Przy przesłaniu danych w formacie CLOB wywoływany jest plik UploadItemDesc.php. Wyświetloną stronę przedstawia rysunek 10.2. Ilustruje on, co powinieneś zobaczyć w trakcie testowania kodu.

Rozdział 10. Duże obiekty

359

Rysunek 10.2. Plik UploadItemDescriptionForm.htm

Rysunek 10.2 przedstawia formularz z danymi wejściowymi z tabeli 10.2. Ścieżka kanoniczna w systemach Linux i Unix rozpoczyna się od punktu montowania. W systemie Windows program rozpoczyna pracę od napędu logicznego w postaci katalogu htdocs. Niezależnie od używanego systemu operacyjnego przycisk Przeglądaj powoduje wyświetlenie okna dialogowego przeznaczonego do wybierania plików. Po wybraniu wczytywanego pliku w tym oknie należy wcisnąć przycisk wczytać plik. Tabela 10.2. Dane wejściowe dla formularza z pliku UpdateItemDescriptionForm.htm Pole

Dane

Identyfikator

1021

Tytuł

Harry Potter and the Sorcerer’s Stone

Wybierz plik

{ścieżka_kanoniczna}HarryPotter1.txt

Jedyna różnica między formularzami HTML-a dla typów BLOB i CLOB to parametr action, dlatego dla formularza dla typu BLOB wystarczy zaprezentować poniższy fragment:

Wyświetloną stronę przedstawia rysunek 10.3, który ilustruje, co powinieneś zobaczyć w trakcie testowania kodu.

Rysunek 10.3. Plik UploadItemBlobForm.htm

Rysunek 10.3 przedstawia formularz z danymi wejściowymi z tabeli 10.3. Ścieżka kanoniczna w systemach Linux i Unix rozpoczyna się od punktu montowania. W systemie Windows program rozpoczyna pracę od napędu logicznego w postaci katalogu htdocs. Niezależnie od używanego systemu operacyjnego przycisk Przeglądaj powoduje wyświetlenie okna dialogowego przeznaczonego do wybierania plików. Po wybraniu wczytywanego pliku w tym oknie należy wcisnąć przycisk wczytać plik.

360

Część II Programowanie w języku PL/SQL

Tabela 10.3. Dane wejściowe dla formularza z pliku UpdateItemBlobForm.htm Pole

Dane

Identyfikator

1021

Tytuł

Harry Potter and the Sorcerer’s Stone

Wybierz plik

{ścieżka_kanoniczna}HarryPotter1.png

Kod z pliku UploadItemDesc.php i UploadItemBlob.php najpierw wywołuje funkcję PHP process_ uploaded_file. Ta funkcja zapisuje tymczasowy plik w domyślnej lokalizacji, czyli w katalogu temp, który należy utworzyć w katalogu htdocs. Po skopiowaniu pliku do kontrolowanej lokalizacji program wczytuje zawartość pliku do pamięci, a następnie zapisuje dane do bazy. Oto kod funkcji PHP process_uploaded_file: // Zarządza przesyłaniem pliku i zwraca jego zawartość jako łańcuch znaków. function process_uploaded_file() { // Deklaracja zmiennej do przechowywania zawartości pliku. $contents = ""; // Definicja nazwy przesyłanego pliku na potrzeby systemów Windows i Linux. if (ereg("Win32",$_SERVER["SERVER_SOFTWARE"])) $upload_file = getcwd()."\\temp\\".$_FILES['userfile']['name']; else $upload_file = getcwd()."/temp/".$_FILES['userfile']['name']; // Sprawdzanie dostępności i przenoszenie przesłanego pliku. if (is_uploaded_file($_FILES['userfile']['tmp_name'])) move_uploaded_file($_FILES['userfile']['tmp_name'],$upload_file); // Otwarcie uchwytu pliku i stłumienie błędów niedostępności pliku. if ($fp = @fopen($upload_file,"r")) { // Wczytywanie danych do momentu dojścia do znacznika końca pliku. while (!feof($fp)) $contents .= fgetc($fp); // Zamknięcie otwartego uchwytu pliku. fclose($fp); } // Zwrócenie zawartości pliku jako łańcucha znaków. return $contents; }

Ta funkcja sprawdza, czy serwer działa na komputerze z systemem Windows, a następnie określa docelową ścieżkę kanoniczną określającą miejsce zapisywania pliku. Funkcja umieszcza plik w katalogu temp, po czym wczytuje zawartość tego pliku do łańcucha znaków. Zawartość pliku zwracana jest właśnie jako łańcuch znaków. Zauważ, że w PHP łańcuchy znaków mogą mieć znacznie większą długość niż 32 767 bajtów, co jest maksymalnym rozmiarem takich łańcuchów w języku PL/SQL. Tu jednak łańcuch znaków ma długość tylko 6737 bajtów. Po wywołaniu funkcji process_uploaded_file następnym krokiem w pliku UploadItemDesc.php jest wywołanie procedury web_load_clob_from_file. Program wiąże zmienne lokalne w bloku anonimowym, a następnie wywołuje tę procedurę. Zmienna powiązana :item_desc służy do przekazywania lokalizatora do programu PHP. Ten lokalizator umożliwia zapisanie danych: $rlob->save($item_desc);

Poniżej znajdziesz dłuższy fragment rozwiązania (kompletny kod jest dostępny w witrynie wydawnictwa Helion):