Kursory - pobieranie danych z baz danych do programów PL/SQL



Podobne dokumenty
Oracle PL/SQL. Paweł Rajba.

Plan wykładu BAZY DANYCH II WYKŁAD 4. Co to jest kursor? Rodzaje kursorów

Język PL/SQL. Rozdział 2. Kursory

Kursor jawny. Rozdział 10a Kursory. Deklarowanie kursora (1) Deklarowanie kursora (2)

Kursor. Rozdział 10a Kursory. Otwieranie kursora. Deklarowanie kursora

DECLARE <nazwa_zmiennej> typ [(<rozmiar> )] [ NOT NULL ] [ { := DEFAULT } <wartość> ];

Kursory i wyjątki. (c) Instytut Informatyki Politechniki Poznańskiej 1

Deklarowanie kursora. CURSOR nazwa [ ( param1 typ1 [,param2 typ2]... ) ] [RETURN typ zwracany] IS zapytanie SQL;

Deklarowanie kursora

PL/SQL. Zaawansowane tematy PL/SQL

Materiały. Technologie baz danych. Plan wykładu Kursory. Wykład 5: Kursory jawne. Podprogramy. Kursory jawne. Kursory niejawne

1 Kursory 1. 2 Wyjątki Wyjątki predefiniowane Wyjątki niezdefiniowane wcześniej Definiowanie własnych wyjątków...

Instrukcje SQL można podzielić na pięć kategorii, które zostały przedstawione w poniższej tabeli.

Programowanie w SQL. definicja bloku instrukcji BEGIN...END, warunkowe wykonanie instrukcji IF...ELSE, wyrażenie CASE,

Plan wykładu BAZY DANYCH II WYKŁAD 3. Zasięg zmiennych. Zasięg zmiennych

PODSTAWY BAZ DANYCH 13. PL/SQL

Plan wykładu Projekt fizyczny bazy danych Wprowadzenie PL/SQL PL/SQL Cechy PL/SQL

Bazy danych wykład dwunasty PL/SQL, c.d. Konrad Zdanowski ( Uniwersytet Kardynała Stefana Bazy danych Wyszyńskiego, wykładwarszawa)

Bloki anonimowe w PL/SQL

DECLARE VARIABLE zmienna1 typ danych; BEGIN

Plan wykładu PL/SQL. PL/SQL - historia TWORZENIE APLIKACJI BAZODANOWYCH

15. Funkcje i procedury składowane PL/SQL

Bloki anonimowe w PL/SQL

Uwagi dotyczące notacji kodu! Moduły. Struktura modułu. Procedury. Opcje modułu (niektóre)

SQL (ang. Structured Query Language)

Październik Instytut Informatyki Teoretycznej i Stosowanej Politechnika Częstochowska. Systemy baz danych - wykład III. dr inż.

Ustawienie na poziomie sesji (działa do zmiany lub zakończenia sesji zamknięcia połączenia).

Paweł Rajba

Procedury składowane. Funkcje vs. procedury Funkcja. Procedura. zazwyczaj ma parametry tylko typu IN; można wywoływać z poziomu

Procedury i funkcje składowane

Administracja i programowanie pod Microsoft SQL Server 2000

Wyzwalacz - procedura wyzwalana, składowana fizycznie w bazie, uruchamiana automatycznie po nastąpieniu określonego w definicji zdarzenia

Oracle11g: Wprowadzenie do SQL

Wykład 8. SQL praca z tabelami 5

Laboratorium nr 4. Temat: SQL część II. Polecenia DML

Pętle. Dodał Administrator niedziela, 14 marzec :27

1 Podstawy c++ w pigułce.

Oracle PL/SQL. Paweł Rajba.

Opis: Instrukcja warunkowa Składnia: IF [NOT] warunek [AND [NOT] warunek] [OR [NOT] warunek].

OBSŁUGA WYJĄTKÓW. Mechanizm języka PL/SQL. Wyjątki:

Kolekcje Zbiory obiektów, rodzaje: tablica o zmiennym rozmiarze (ang. varray) (1) (2) (3) (4) (5) Rozszerzenie obiektowe w SZBD Oracle

Systemy GIS Tworzenie zapytań w bazach danych

Kursory. A. Pankowska 1

Trigger jest obiektem związanym z tablicą, który aktywuje się gdy do tablicy następuje odpowiednie zapytanie.

T-SQL dla każdego / Alison Balter. Gliwice, cop Spis treści. O autorce 11. Dedykacja 12. Podziękowania 12. Wstęp 15

Podstawy Programowania C++

Materiały do laboratorium MS ACCESS BASIC

Plan. Formularz i jego typy. Tworzenie formularza. Co to jest formularz? Typy formularzy Tworzenie prostego formularza Budowa prostego formularza

Język PL/SQL. Rozdział 5. Pakiety podprogramów. Dynamiczny SQL

Autor: Joanna Karwowska

Wyzwalacze. do automatycznego generowania wartości kluczy głównych. Składnia instrukcji tworzacej wyzwalacz

Przestrzenne bazy danych Podstawy języka SQL

Oracle Developer Suite. Budowa aplikacji użytkownika końcowego

Struktury sterowania PL/SQL

Język DML. Instrukcje DML w różnych implementacjach SQL są bardzo podobne. Podstawowymi instrukcjami DML są: SELECT INSERT UPDATE DELETE

Oracle PL/SQL. Paweł Rajba.

Oracle PL/SQL. Paweł Rajba.

ORACLE (Wykład 1) aragorn.pb.bialystok.pl/~aonisko. Typy rozproszonych baz danych. Systemy klient-serwer. Klient-serwer: Przykład

Obiektowy PHP. Czym jest obiekt? Definicja klasy. Składowe klasy pola i metody

1. ELEMENTY JĘZYKA PL/SQL

Systemy operacyjne. Laboratorium 9. Perl wyrażenia regularne. Jarosław Rudy Politechnika Wrocławska 28 lutego 2017

SQL Server i T-SQL w mgnieniu oka : opanuj język zapytań w 10 minut dziennie / Ben Forta. Gliwice, Spis treści

Pakiety podprogramów Dynamiczny SQL

Oracle PL/SQL. Paweł Rajba.

Pętla for. Wynik działania programu:

SQL w języku PL/SQL. 2) Instrukcje języka definicji danych DDL DROP, CREATE, ALTER, GRANT, REVOKE

Oracle PL/SQL. Paweł Rajba.

P o d s t a w y j ę z y k a S Q L

41. Zmienne lokalne muszą mieć nazwę, którą poprzedza (maksymalnie 128 znaków) oraz typ (każdy z wyjątkiem: text, ntext oraz image)

Język PL/SQL. Rozdział 3. Obsługa błędów wykonania Wyjątki predefiniowane i użytkownika, zgłaszanie i obsługa wyjątków.

Sprawdzenie czy połączenie przebiegło poprawnie if (mysqli_connect_errno()) { echo Błąd; Połączenie z bazą danych nie powiodło się.

Zarządzanie bazą danych. Bazy Danych i Systemy informacyjne Wykład 4. Piotr Syga

Oracle11g: Programowanie w PL/SQL

Pętle. for, while, do... while, foreach. Materiał pomocniczy do kursu Podstawy programowania Autor: Grzegorz Góralski ggoralski.

Język PL/SQL Procedury i funkcje składowane

Instrukcja do ćwiczenia P4 Analiza semantyczna i generowanie kodu Język: Ada

Składowane procedury i funkcje

PL/SQL. Część 1 Bloki PL/SQL. Piotr Medoń

Wykład 5. SQL praca z tabelami 2

Microsoft SQL Server Podstawy T-SQL

E.14 Bazy Danych cz. 18 SQL Funkcje, procedury składowane i wyzwalacze

Zaawansowane bazy danych i hurtownie danych semestr I

Wstęp 5 Rozdział 1. Podstawy relacyjnych baz danych 9

Pakiety są logicznymi zbiorami obiektów takich jak podprogramy, typy, zmienne, kursory, wyjątki.

Systemy baz danych 2 laboratorium Projekt zaliczeniowy

KOLEKCJE - to typy masowe,zawierające pewną liczbę jednorodnych elementów

SQL - Structured Query Language -strukturalny język zapytań SQL SQL SQL SQL

Podstawy programowania. Wykład: 9. Łańcuchy znaków. dr Artur Bartoszewski -Podstawy programowania, sem 1 - WYKŁAD

Języki programowania zasady ich tworzenia

Programowanie w SQL procedury i funkcje. UWAGA: Proszę nie zapominać o prefiksowaniu nazw obiektów ciągiem [OLIMP\{nr indeksu}] Funkcje użytkownika

Rozdział 17. Zarządzanie współbieżnością zadania

Podstawy programowania skrót z wykładów:

Technologie baz danych WYKŁAD 7: Wyjątki

Wyzwalacze (triggery) Przykład

znajdowały się różne instrukcje) to tak naprawdę definicja funkcji main.

Instrukcja podwaja zarobki osób, których imiona zaczynają się P i dalsze litery alfabetu zakładamy, że takich osbób jest kilkanaście.

Baza danych. Baza danych to:

Ćwiczenie rozpocznie się od wprowadzenia do laboratorium, po którym omówimy składnię ę polecenia INSERT pozwalającego ą na wstawianie krotek do

PRZESTRZENNE BAZY DANYCH WYKŁAD 2

Transkrypt:

Kursory - pobieranie danych z baz danych do programów PL/SQL Rozważania będą dotyczyć sposobów pobierania danych z baz danych do programów przechowywanych w pamięci. Interakcja języka PL/SQL z językiem SQL polega na łączeniu instrukcji SQL z instrukcjami PL/SQL w bloku PL/SQL. Największy problem związany z procesem pobierania danych wynika przede wszystkim z faktu, iż SQL jest językiem zorientowanym na obsługę zbiorów (ang. set-oriented), natomiast języki proceduralne, do których przecież zalicza się język PL/SQL, są zorientowane na obsługę rekordów (ang. record-oriented). A zatem pobieranie danych z bazy danych jest operacją nieco skomplikowaną, ponieważ wewnętrzne struktury i operatory języka SQL i PL/SQL nie do końca są zgodne. Przykładowo, jeśli w instrukcji SELECT języka SQL w kryteriach zastosujemy znak wieloznaczny, np. %, to instrukcja ta może zwracać dowolną liczbę wierszy spełniających tego typu kryterium, które można potraktować matematycznie jako zbiór. Natomiast typowa zmienna PL/SQL (o ile nie jest kolekcją) operuje w danym momencie tylko jednym elementem danych. W ten sposób podczas próby pobierania wielu wierszy danych z bazy danych do programu języka PL/SQL, spotykamy się z problemem polegającym na tym, iż zmienne języka PL/SQL mogą przechowywać jednocześnie jeden element danych. Niekiedy tego rodzaj niezgodności bywa określany mianem niedopasowania falowego (ang. impedance mismatch). Aby pokonać tego typu trudność, kluczowym elementem związanym z przetwarzaniem danych w kodzie programu PL/SQL jest umieszczenie w nim elementów języka SQL. Elementami tymi są tzw. kursory (ang. cursors), które umożliwiają pobranie danych z baz danych do programów języka PL/SQL, a dopiero następnie po pobraniu w ten sposób danych do programów PL/SQL mamy możliwość przetwarzania ich za pomocą struktur i programów tego języka proceduralnego. Kursory Aby w programach języka PL/SQL można było przetwarzać dane przechowywane w bazie danych, należy je uprzednio pobrać do programów. Służą do tego kursory, które pozwalają programowi jawnie sterować przetwarzaniem instrukcji SQL, a ponadto, jak się okazuje, dzięki zastosowaniu których uzyskuje się dodatkowe możliwości. Definicja i podział kursorów. Każdej instrukcji SQL przetwarzanej przez system Oracle jest przydzielany pewien obszar pamięci nazwany obszarem kontekstu. Obszar ten zawiera wskaźnik do zanalizowanej składniowo tzw,. parsowanej reprezentacji instrukcji SQL, informacje konieczne do zakończenia przetwarzania tej instrukcji, włącznie z liczbą wierszy przetwarzanych przez instrukcję lub liczbą wierszy będących zbiorem wynikowym, który jest zbiorem wierszy zwracanych w wyniku zapytania. Kursor (ang. cursor) jest uchwytem lub inaczej wskaźnikiem do obszaru kontekstu. Można powiedzieć, iż w przypadku zapytania kursor jest wskaźnikiem do takiego obszaru pamięci, który przechowuje zbiór wyników wielowierszowego tego zapytania. Oracle otwiera ten obszar roboczy w celu przetwarzania zbioru wyników wielowierszowych zapytań. Za pośrednictwem kursora program PL/SQL może zarządzać obszarem kontekstu oraz mieć wpływ na to, co się z nim stanie po przetworzeniu instrukcji. A zatem kursor nadaje temu obszarowi nazwę i może też służyć do przetworzenia wierszy zwróconych przez zapytanie wielowierszowe. Wyróżnia się dwa rodzaje kursorów: 1

kursory jawne (ang. explicit cursors) są takim rodzajem kursora, w którym nazwa kursora jest jawnie przydzielona instrukcji SELECT za pomocą instrukcji CURSOR...IS. Przetwarzanie kursora jawnego obejmuje cztery kroki, które zostaną poniżej dokładnie omówione. kursory niejawne (ang. implicit cursors)- są takim rodzajem kursorów, które są definiowane przez system Oracle i dotyczą instrukcji SELECT...INTO, INSERT, UPDATE oraz DELETE. Niejawność tego typu kursora wynika z braku bezpośrednich odwołań do niego w kodzie PL/SQL. Operacje związane z kursorem niejawnym są automatycznie wykonywane przez mechanizm PL/SQL Korzystanie z kursora jawnego Kursor jawny jest taką strukturą w kodzie programu PL/SQL, której przetwarzanie wymaga wykonania następujących czynności: 1. zadeklarowanie kursora, 2. otwarcie kursora dla zapytania, 3. pobranie wyników do zmiennych PL/SQL, 4. zamknięcie kursora. Deklaracja kursora jest jedynym krokiem, który odbywa się w sekcji deklaracji bloku; pozostałe trzy są wykonywane w sekcji wykonania lub w sekcji wyjątków. Deklarowanie kursora Deklarację kursora można potraktować jako sposób przypisania nazwy do instrukcji SELECT. Składania operacji przypisania kursorowi nazwy, czyli deklaracja kursora jest następująca: gdzie: CURSOR nazwa_kursora IS instrukcja_select; nazwa_kursora jest nazwą kursora, czyli nazwą przypisaną instrukcji SELECT; instrukcja_select jest zapytaniem, które będzie przetwarzane przez ten kursor. Instrukcja SELECT skojarzona z kursorem jawnym nie może zawierać klauzuli INTO. Nazwy kursora podlegają tym samym regułom zakresu i widoczności jak w przypadku identyfikatorów PL/SQL. A zatem ponieważ nazwa kursora jest identyfikatorem PL/SQL, musi być zadeklarowana przed jej wywołaniem, natomiast zasięg kursora jest określony blokiem PL/SQL, w którym został on zdefiniowany, lub jest on ograniczony którymś z bloków zagnieżdżonych. Wynika to z faktu, iż bloki zewnętrzne nie mogą się odwoływać się do kursorów zdefiniowanych w obrębie bloków zagnieżdżonych. W deklaracji kursora można zastosować dowolną instrukcję SELECT, włącznie z klauzulami GROUP BY i ORDER, ze złączeniami oraz operatorami działającymi na zbiorach, takimi jak UNION, INTERSECT lub MINUS. Zarówno przed deklaracją kursora za pomocą instrukcji CURSOR...IS, jak i po niej mogą znaleźć się inne deklaracje. Kolejność deklarowania kursorów i innych zmiennych jest nieistotna, z pewnym wyjątkiem. Otóż również w samej deklaracji kursora może wystąpić odwołanie do zmiennych PL/SQL. Mogą się one pojawić w klauzuli WHERE i wówczas tego typu zmienne PL/SQL określane są mianem zmiennych dowiązanych. W tym przypadku kolejność deklarowania zmiennych PL/SQL i samego kursora jest istotna. Wynika to z faktu, iż zmienne te muszą być widoczne w miejscu deklaracji kursora. A zatem zmienne dowiązane muszą być zadeklarowane przed deklaracją samego kursora, w którym są 2

one wykorzystywane. Poniższy fragment kodu ilustruje poprawny sposób deklaracji kursora i zmiennych PL/SQL, które są wykorzystywane w klauzuli WHERE tego kursora, czyli zmiennych dowiązanych: z_idprodukt z_nazwaprodukt z_cenaprodukt z_stanprodukt z_wycofanyprodukt towary.nr%type; towary.nazwa%type; towary.cena%type; towary.stan%type; produky.wycofany%type; CURSOR k_produkty IS SELECT id, nazwa, cena, stan, wycofany WHERE nazwa= z_nazwaprodukt AND wycofany = z_wycofanyprodukt; Następna sekcja deklaracji nie jest prawidłowa, ponieważ najpierw następuje odwołanie do zmiennych z_nazwaprodukt oraz z_wycofanyprodukt, a dopiero potem ich deklaracja. CURSOR k_produkty IS SELECT id, nazwa, cena, stan, wycofany WHERE nazwa= z_nazwaprodukt AND wycofany = z_wycofanyprodukt; z_idprodukt z_nazwaprodukt z_cenaprodukt z_stanprodukt towary.nr%type; towary.nazwa%type; towary.cena%type; towary.stan%type; Wskazówka: Aby mieć pewność, iż wszystkie zmienne użyte w deklaracji kursora są deklarowane przed ich zastosowaniem, najlepiej deklarować wszystkie kursory na końcu sekcji deklaracji. Otwieranie kursora W sekcji wykonania uprzednio zadeklarowany kursor można otworzyć w bieżącym zasięgu bloku. Zasadniczo istnieją dwa sposoby otwierania kursorów jawnych jawnie za pomocą polecenia OPEN lub niejawnie korzystając z pętli kursora FOR. Poniżej omówimy ten pierwszy sposób, zaś wykorzystanie kursorów z pętlami zostanie omówione nieco później. Otwierając kursor w sposób jawny korzystamy z instrukcji OPEN, której składnia jest następująca: OPEN nazwa_kursora gdzie: nazwa_kursora określa kursor, który został uprzednio zadeklarowany. Po otwarciu kursora mają miejsce następujące zdarzenia: sprawdzenie wartości zmiennych dowiązanych, określenie zbioru wyników na podstawie wartości zmiennych dowiązanych, ustawienie wskaźnika zbioru wynikowego na pierwszym wierszu. Podczas otwierania kursora system Oracle w tle analizuje składnię instrukcji i odpowiednio wiąże z nią te wiersze tabeli, które w danej chwili stanowią wynik zapytania. Ten wynikowy zbiór rekordów jest 3

często określany mianem aktywnego zbioru wierszy (ang. active set of rows), na którym nie można wykonywać żadnych operacji. Wskaźnik kursora jest ustawiony na pierwszy rekord w aktywnym zbiorze wierszy. Zwykle otwarcie kursora następuje tuż przed pobraniem danych. To, ile wierszy danych zostanie umieszczonych w zbiorze wynikowym zapytania, zależy od kryteriów, które zostały umieszczone w jego strukturze. Kryteria te mogą oczywiście zawierać zmienne dowiązane, które uprzednio powinny być zadeklarowane. Wartości zmiennych dowiązanych są sprawdzane tylko w czasie otwarcia kursora i ich zmiana nie będzie już miała wpływu na aktywny zbiór wierszy zwracany przez to zapytanie. Oto przykład ilustrujący sytuację zmiany wartości zmiennych dowiązanych po otwarciu kursora: z_idprodukt z_nazwaprodukt z_cenaprodukt z_stanprodukt z_wycofanyprodukt towary.nr%type; towary.nazwa%type; towary.cena%type; towary.stan%type; towary.wycofany%type; CURSOR k_produkty IS SELECT id, nazwa, cena, stan, wycofany WHERE nazwa= z_nazwaprodukt AND wycofany = z_wycofanyprodukt; -- Należy przypisać wartości zmiennym dowiązanym przed otwarciem kursora za pomocą instrukcji OPEN z_nazwaprodukt:= Czekolada mleczna ; z_wycofanyprodukt:= Nie ; --Otwarcie kursora OPEN k_produkty; /* Ponowne przypisanie wartości zmiennym dowiązanym ta operacja nie odniesie żadnego skutku ponieważ kursor jest już otwarty */ z_nazwaprodukt:= Masło extra ; z_wycofanyprodukt:= Tak ; Jak wynika, choćby z analizy powyższego przykładu, aktywny zbiór wierszy, który jest zwracany przez zapytanie, jest określony w momencie otwierania kursora. Przykładowo w powyższym zapytaniu zostaną zwrócone wiersze określające produkty o nazwie Czekolada mleczna, które nie są produktem wycofanym. Jest to następstwem faktu, iż w klauzuli WHERE sprawdza się tabelę lub tabele, do których następuje odwołanie w klauzuli FROM tego zapytania. Każdy wiersz, dla którego warunek przyjmuje wartość TRUE, jest dodawany do zbioru wynikowego. W przykładzie powyżej w momencie otwierania kursora k_produkty zmienne z_nazwaprodukt oraz z_wycofanyprodukt zawierają wartości, odpowiednio Czekolada mleczna oraz Nie, które będą użyte w zapytaniu. Nawet jeśli wartości zmiennych z_nazwaprodukt oraz z_wycofanyprodukt zmienią się już po wykonaniu instrukcji OPEN, to zbiór wynikowy zapytania nie zmieni się. To zjawisko nazywane jest spójnością odczytu. Aby sprawdzenie odbyło się na podstawie nowych wartości zmiennych, należy zamknąć kursor i ponownie go odtworzyć. 4

Uwaga: Nie należy otwierać już otwartego kursora. Powoduje to zgłoszenie predefiniowanego wyjątku PL/SQL, a mianowicie ORA-6511: Cursor already open. Należy również zauważyć, iż w momencie otwierania kursora ustalany jest również wskaźnik w zbiorze wynikowym. Wskaźnik ten pokazuje, który ma być pobrany przez kursor jako następny. Pobieranie danych za pomocą kursora Do pobierania wiersza danych służy instrukcja FETCH. Istnieją następujące postacie tej instrukcji: albo albo gdzie: FETCH nazwa_kursora INTO lista_zmiennych; FETCH nazwa_kursora INTO rekord_pl/sql; FETCH nazwa_kursora INTO table_name%rowtype; nazwa_kursora nazwa zadeklarowanego i w danym momencie otwartego kursora, lista zmiennych rozdzielona przecinkami lista wcześniej zadeklarowanych zmiennych PL/SQL, rekord_pl/sql uprzednio jawnie zadeklarowany rekord PL/SQL, table_name%rowtype typ rekordowy, w którym atrybuty odpowiadają nazwom kolumn tabeli, która jest określona nazwą table_name. W kazdym przypadku składni instrukcji FETCH...INTO należy zagwarantować, iż dla każdej kolumny danych występującej w instrukcji SELECT kursora istnieje odpowiednia pod względem typu zmienna PL/SQL. Jak z tego wynika, kolejność umieszczania zmiennych PL/SQL po klauzuli INTO ma znaczenie. Biorąc pod uwagę poprzednią deklarację kursora k_produkty, prawidłowa jest następująca instrukcja FETCH: FETCH k_produkty INTO z_idprodukt, z_nazwaprodukt, z_cenaprodukt, z_stanprodukt, z_wycofanyprodukt; Z uwagi na to, iż w powyższym przykładzie za pomocą kursora jest pobierany rekord danych, który składa się ze wszystkich pól tabeli produkty możemy wykorzystać następującą uproszczoną składnię instrukcji FETCH z pseudokolumną ROWTYPE: FETCH k_produkty INTO produkty%rowtype; W przypadku, gdy liczba zmiennych po klauzuli INTO nie odpowiada liczbie kolumn w instrukcji SELECT kursora zostanie wygenerowany błąd PLS-394: wrong number of values in the INTO list of FETCH statement (PLS-394: niewłaściwa liczba wartości na liście INTO instrukcji FETCH). Każda instrukcja pobrania danych FETCH zwraca kolejny wiersz z wynikowego zbioru danych wybranych poprzez instrukcję SELECT kursora. Po każdym pobraniu za pomocą instrukcji FETCH wskaźnik zbioru wynikowego zwiększa wartość odpowiednio dla następnego wiersza. W ten sposób dla każdego pobrania instrukcji FETCH będą zwracane kolejne wiersze, dopóki nie zostanie zwrócony cały zbiór wynikowy. W rzeczywistości w celu pobrania określonej liczby wierszy z tabeli nie zapisuje się na ogół serii identycznych instrukcji FETCH, ale umieszcza się taką pojedynczą w pętli. Podczas wykonywania serii instrukcji FETCH należy pamiętać o następujących faktach: 5

Kolejna instrukcja FETCH nadpisuje wartości pochodzące z poprzedniej. A zatem przed powtórzeniem tej instrukcji należy przeprowadzić odpowiednie operacje na zmiennych, które w danym momencie zostały pobrane operacja FETCH i podstawione pod odpowiednie zmienne PL/SQL. Nie można pobrać wiersza, który został już uprzednio pobrany. Można jedynie zamknąć i otworzyć kursor, ale oznacza to rozpoczęcie pobierania wierszy z aktywnego zbioru wierszy od samego początku, czyli o pierwszego wiersza. Wynika to ze znanego już faktu, iż w momencie otwarcia kursora wskaźnik ustawia się na pierwszym rekordzie aktywnego zbioru wierszy. Po pobraniu wszystkich wierszy z aktywnego zbioru wierszy wykonanie kolejnej instrukcji FETCH nie daje żadnego rezultatu nie spowoduje przypisania nowych danych do zmiennych, ale także nie spowoduje przechwycenia żadnych wyjątków. Lokalne zmienne PL/SQL posiadają ostatnie pobrane wartości z tabeli. Wartości te nie zmienią się aż do momentu, gdy nie zostaną bezpośrednio nadpisane lub nie zakończy się działanie programu. W celu wskazania momentu, w którym został pobrany już cały zbiór wynikowy można wykorzystać tzw. atrybut kursora %NOTFOUND, który zostanie omówiony nieco później. Nie należy pobierać danych, w przypadku, gdy kursor nie został otwarty. Powoduje to zgłoszenie błędu ORA-01001: invalid kursor (niewłaściwy kursor) lub ORA-01002: Fetch out of sequence (pobieranie poza kolejnością). Zamykanie kursora Po ukończeniu pobierania wszystkich wierszy z aktywnego zbioru wierszy należy zamknąć kursor. Taka operacja informuje mechanizm PL/SQL, że przetwarzanie kursora zakończyło się i zasoby z tym związane (pamięć używana do przechowywania zbioru wynikowego, jak również tymczasowa przestrzeń wykorzystywana do wyznaczania tego zbioru) mogą być zwolnione. Składnia instrukcji zamykającej kursor jest następująca: CLOSE nazwa_kursora gdzie: nazwa_kursora nazwa uprzednio otwartego kursora, który jest zamykany instrukcją CLOSE. Po zamknięciu kursora pobieranie z niego danych jest nieprawidłowe. Próba zamknięcia kursora, który wcześniej nie został otwarty również skutkuje zgłoszeniem błędu ORA-01001: invalid kursor lub błędu ORA-1002: Pobieranie poza kolejnością. Uwaga: Należy zawsze pamiętać o zamykaniu otwartych kursorów. W przypadku, gdy liczba otwartych kursorów jest zbyt duża może to skutkować zgłoszeniem błędu Zbyt duża liczba otwartych kursorów. Standardowo maksymalna liczba jednocześnie otwartych kursorów wynosi 50. Uwaga: Należy pamiętać, iż instrukcja zamknięcia kursora CLOSE powinna zawsze występować po instrukcji FETCH. W przypadku, gdy instrukcja FETCH jest umieszczona w pętli, instrukcja zamknięcia kursora CLOSE powinna być umieszczona w kodzie programu dopiero po zamknięciu pętli. W przeciwnym razie zostanie zgłoszony błąd niedozwolonego wczytania (ang. illegal fetch). Atrybuty kursora PL/SQL udostępnia wiele użytecznych właściwości dotyczących kursorów. Są one dostępne poprzez tzw. atrybuty kursora (ang. cursor attributes). Dostęp do tych predefiniowanych właściwości można uzyskać poprzez podanie znaku procent, po którym należy umieścić nazwę odpowiedniego atrybutu, podobnie jak w przypadku atrybutów %TYPE oraz %ROWTYPE. Tym niemniej sposób, w jaki następuje w kodzie programu odwołanie do atrybutu jest taki sam, jak gdyby była to zmienna. Atrybuty 6

kursora, podobnie jak zmienne, a w przeciwieństwie %TYPE i %ROWTYPE, zwracają wartość, która może być używana w wyrażeniach. Występują cztery atrybuty kursora dostępne w języku PL/SQL: %FOUND, %NOTFOUND, %ISOPEN oraz %ROWCOUNT (od Oracle8i występuje dodatkowy atrybut - %BULK_ROWCOUNT służący do pobierania tablic.). Tabela. Przeznaczenie atrybutów kursora Atrybut %FOUND %NOTFOUND %ISOPEN %ROWCOUNT Przeznaczenie Wskazuje, czy instrukcja FETCH pobrała wiersz danych z aktywnego zbioru wierszy czy też nie. Wskazuje, czy instrukcja FETCH instrukcja FETCH zakończyła się niepowodzeniem lub czy nie ma już żadnych wierszy do pobrania z aktywnego zbioru wierszy. Wskazuje, czy kursor jest otwarty czy też nie. Wskazuje liczby wierszy pobranych za pomocą kursora do danego momentu. Poniższa tabela tabela_tymcz, która zawiera dwa wiersze posłuży do dokładnego omówienia własności poszczególnych atrybutów kursora: kol_num kol_znak 10 Litera A 20 Litera B Rozważmy przykładowy kod programu dla prezentacji atrybutu kursora --Deklaracja kursora CURSOR k_danetymcz IS SELECT * FROM tabela_tymcz z_rekordtymcz k_danetymcz%rowtype; -- punkt1 OPEN k_danetymcz; --Otwarcie kursora -- punkt2 FETCH k_danetymcz INTO z_rekordtymcz; --Pobranie pierwszego wiersza -- punkt3 FETCH k_danetymcz INTO z_rekordtymcz; --Pobranie drugiego wiersza -- punkt4 FETCH k_danetymcz INTO z_rekordtymcz; --Pobranie trzeciego wiersza -- punkt5 CLOSE k_danetymcz; -- punkt6 7

Atrybut %FOUND Położenie Wartość atrybutu kursora Wyjaśnienie k_tempdata%found Punkt1 Błąd: ORA-1001 Kursor k_danetymcz nie został jeszcze otwarty. Nie ma związanego z nim żadnego zbioru wynikowego. Punkt2 NULL Chociaż kursor k_danetymcz został otwarty, nie zostało wykonane żadne pobranie. Wartość atrybutu nie może być określona. Punkt3 TRUE Uprzednie pobranie zwróciło pierwszy wiersz w tabeli tabela_tymcz Punkt4 TRUE Poprzednie pobranie zwróciło drugi wiersz w tabeli tabela_tymcz Punkt5 FALSE Poprzednie pobranie nie zwróciło żadnych danych, ponieważ wszystkie wiersze w zbiorze wynikowym zostały pobrane. Punkt6 Błąd: ORA-1001 Kursor k_danetymcz został zamknięty, co spowodowało usuniecie wszystkich zapisanych informacji dotyczących zbioru wynikowego Atrybut %FOUND jest atrybutem logicznym. Zwraca wartość TRUE, jeśli uprzednia instrukcja FETCH zwraca wiersz oraz FALSE, jeśli instrukcja FETCH nie zwraca wiersza. W przypadku próby sprawdzenia atrybutu %FOUND dla kur-sora, który nie został otwarty, jest zwracany błąd ORA-1001 (niewłaściwy kursor). Atrybut %NOTFOUND Położenie Wartość atrybutu kursora k_tempdata%notfound 8 Wyjaśnienie Punkt1 Błąd: ORA-1001 Kursor k_danetymcz nie został jeszcze otwarty. Nie ma związanego z nim żadnego zbioru wynikowego. Punkt2 NULL Chociaż kursor k_danetymcz został otwarty, nie zostało wykonane żadne pobranie. Wartość atrybutu nie może być określona. Punkt3 FALSE Uprzednie pobranie zwróciło pierwszy wiersz w tabelitabela_tymcz Punkt4 FALSE Poprzednie pobranie zwróciło drugi wiersz w tabeli tabela_tymcz Punkt5 TRUE Poprzednie pobranie nie zwróciło żadnych danych, ponieważ wszystkie wiersze w zbiorze wynikowym zostały pobrane. Punkt6 Błąd: ORA-1001 Kursor k_danetymcz został zamknięty, co spowodowało usuniecie wszystkich zapisanych informacji dotyczących zbioru wynikowego Atrybut %NOTFOUND działa odwrotnie do atrybutu %FOUND jeżeli uprzednie pobranie za pomocą instrukcji FETCH zwróciło wiersz, to wartością atrybutu %NOTFOUND jest FALSE. Atrybut %NOTFOUND zwraca wartość TRUE tylko wtedy, gdy poprzednia instrukcja FETCH nie zwróciła wiersza. Atrybut %NOTFOUND jest często wykorzystywany jako warunek wyjścia z pętli pobrania.

Atrybut %ISOPEN Położenie Wartość atrybutu kursora k_tempdata%notfound Wyjaśnienie Punkt1 FALSE Kursor k_danetymcz nie został jeszcze otwarty. Punkt2 TRUE Kursor k_danetymcz został otwarty. Punkt3 TRUE Kursor k_danetymcz jest jeszcze otwarty. Punkt4 TRUE Kursor k_danetymcz jest jeszcze otwarty. Punkt5 TRUE Kursor k_danetymcz jest jeszcze otwarty. Punkt6 FALSE Kursor k_danetymcz został zamknięty. Atrybut %ISOPEN, który jest atrybutem logicznym, jest używany do ustalania, czy związany z nim kursor jest otwarty czy nie. Jeśli kursor jest otwarty, to atrybut %ISOPEN zwraca wartość TRUE; w przeciwnym razie zwraca wartość FALSE. Atrybut %ROWCOUNT Położenie Wartość atrybutu kursora k_tempdata%rowcount Wyjaśnienie Punkt1 Błąd: ORA-1001 Kursor k_danetymcz nie został jeszcze otwarty. Nie ma związanego z nim żadnego zbioru wynikowego. Punkt2 0 Chociaż kursor k_danetymcz został otwarty, ale nie zostało wykonane żadne pobranie. Punkt3 1 Został pobrany pierwszy wiersz z tabeli tabela_tymcz Punkt4 2 Został pobrany drugi wiersz z tabeli tabela_tymcz Punkt5 2 Dotychczas zostały pobrane dwa wiersze z tabeli tabela_tymcz. Punkt6 Błąd: ORA-1001 Kursor k_danetymcz został zamknięty, co spowodowało usuniecie wszystkich zapisanych informacji dotyczących zbioru wynikowego Porównanie atrybutów kursora Położenie Wartość atrybutu kursora k_tempdata%found Wartość atrybutu kursora k_tempdata%notfound Wartość atrybutu kursora k_tempdata%isopen Wartość atrybutu kursora k_tempdata%rowcount Punkt1 Błąd: ORA-1001 Błąd: ORA-1001 FALSE Błąd: ORA-1001 Punkt2 NULL NULL TRUE 0 Punkt3 TRUE FALSE TRUE 1 Punkt4 TRUE FALSE TRUE 2 Punkt5 FALSE TRUE TRUEE 2 Punkt6 Błąd: ORA-1001 Błąd: ORA-1001 FALSE Błąd: ORA-1001 9

Parametryzacja kursorów W przypadku, gdy kursor będzie stosowany w więcej niż jednym miejscu kodu programu i związane są z nim pewne zmienne dowiązane, istnieje dodatkowy sposób powiązania wartości zmiennych dowiązanych z kursorem. W tym celu można wykorzystać tzw. kursor parametryzowany, który pobiera wartości zmiennych dowiązanych podobnie, jak składowana procedura pobiera swoje argumenty. W ten sposób eliminuje się konieczność definiowania wielu kursorów (tego samego typu) i trwałego kodowania wartości zmiennych dowiązanych w każdym kursorze. W celu ilustracji działania kursora parametryzowanego przekształcimy kursor jawny k-produkty, rozważany powyżej w kursor parametryzowany: z_nazwaprodukt towary.nazwa%type; z_wycofanyprodukt towary.wycofany%type; CURSOR k_produkty IS SELECT * WHERE nazwa= z_nazwaprodukt AND wycofany= z_wycofanyprodukt; Kursor k_produkty zawiera dwie zmienne z_nazwaprodukt oraz z_wycofanyprodukt. Można zmodyfikować kursor k_produkty do postaci równoważnego kursora parametryzowanego w następujący sposób: CURSOR k_produkty(p_nazwa towary.nazwa%type, p_wycofany towary.wycofany %TYPE) IS SELECT * WHERE nazwa= z_nazwaprodukt AND wycofany= z_wycofanyprodukt; Jak wynika choćby z powyższego przykładu, parametry kursorów definiujemy bezpośrednio po nazwie kursora, umieszczając w nawiasach nazwę parametru i jego typ. Ten rodzaj parametrów jest określany mianem parametrów formalnych (ang. formal parameters). Do przekazania bieżących wartości dla parametrów formalnych kursora parametryzowanego, tzw. parametrów aktualnych (ang. actual parameters) jest wykorzystywana instrukcja OPEN. A zatem nasz rozważany kursor k_produkty można otworzyć za pomocą następującej instrukcji: OPEN k_produkty( Czekolada mleczna, Nie ); W tym przypadku Czekolada mleczna byłby przekazywany do parametru p_nazwa, a wartość Nie do parametru p_wycofany. Oczywiście w kodzie programu można, po uprzednim zamknięciu kursora, ponownie go otworzyć za pomocą instrukcji OPEN o tej samej składni, ale z innymi wartościami parametrów aktualnych, np. OPEN k_produkty( Masło extra, Tak ); Jako parametry można podawać tylko te wartości, które odpowiadają zmiennym. Parametrami nie mogą być nazwy tabel lub kolumn. Parametry mogą być przekazywane za pomocą zapisu pozycyjnego lub za pośrednictwem nazw. Przetwarzanie kursorów niejawnych Omówione dotychczas kursory jawne są używane do przetwarzania instrukcji SELECT, które zwracają więcej niż jeden wiersz. Podobnie jak wielowierszowe instrukcje SELECT, również pozostałe instrukcje 10

DML, tzn. INSERT, UPDATE, DELETE oraz jednowierszowe instrukcje SELECT...INTO, są wykonywane w kontekście pewnego obszaru roboczego. Dlatego też język PL/SQL dla przetwarzania również tych instrukcji DML dostarcza inny rodzaj kursora, tzw. kursor SQL. W odróżnieniu od kursorów jawnych, kursor SQL nie jest otwierany ani zamykany przez program. Mechanizm PL/SQL niejawnie otwiera kursor SQL, przetwarza zawartą w nim instrukcję SQL, a następnie go zamyka. Z tego też powodu kursor SQL bywa nazywany kursorem niejawnym. Należy zwrócić uwagę, iż kursor tego typu jest nazywany kursorem SQL i nie posiada konkretnej nazwy zdefiniowanej przez programistę. Ponieważ kursor SQL jest otwierany i zamykany przez mechanizm PL/SQL, polecenia OPEN, FETCH oraz CLOSE nie mają zastosowania. Atrybuty kursorów niejawnych Podobnie jak w przypadku kursorów jawnych, również dla kursorów niejawnych język PL/SQL dostarcza atrybuty, których znaczenie prezentuje poniższa tabela: Tabela 6.1. Znaczenie atrybutów kursora niejawnego Atrybut %FOUND %NOTFOUND %ISOPEN %ROWCOUNT Przeznaczenie Wskazuje, czy instrukcja INSERT, UPDATE lub DELETE miała wpływ na jakiś wiersz czy też nie. Wskazuje, czy instrukcji INSERT, UPDATE lub DELETE nie udało się zmodyfikować żadnego wiersza. Wskazuje, czy kursor jest otwarty czy też nie. Zawsze ma wartość FALSE, ponieważ kursor niejawny zostaje automatycznie zamknięty po wykonaniu którejś z instrukcji DML: INSERT, UPDATE lub DELETE. Wynika stąd prosty wniosek, iż atrybutu tego nie warto sprawdzać dla kursora niejawnego. Wskazuje liczby wierszy zmodyfikowanych za pomocą którejś z instrukcji INSERT, UPDATE lub DELETE. Jednak atrybuty kursora mogą być stosowane dla kursora SQL. W poniższym przykładowym bloku programu będzie wykonywana instrukcja INSERT, jeśli instrukcja UPDATE nie modyfikuje żadnych wierszy: UPDATE klienci SET kraj = Polska WHERE id = 1216; /* Jeżeli klient o id=123 nie istnieje (nie został mu zatem przypisany kraj Polska ) należy wprowadzić go do tabeli klienci, innymi słowy, gdy żaden wiersz z tabeli klienci nie spełnia warunku id=1216, należy wprowadzić wiersz do tej tabeli */ IF SQL%NOTFOUND THEN INSERT INTO klienci (id, nazwa, kraj) VALUES (1216, Martex Sp. z o.o., Polska ); 11

END IF; To samo zadanie również wykonać, korzystając z atrybutu kursora SQL%ROWCOUNT: UPDATE klienci SET kraj = Polska WHERE id = 1216; /* Jeżeli klient o id=123 nie istnieje (nie został mu zatem przypisany kraj Polska ) należy wprowadzić go do tabeli klienci, innymi słowy, jeśli liczba wierszy spełniająca warunek id=1216 wynosi 0, należy wprowadzić wiersz do tabeli*/ IF SQL%ROWCOUNT=0 THEN INSERT INTO klienci (id, nazwa, kraj) VALUES (1216, Martex Sp. z o.o., Polska ); END IF; Nie jest zalecane użycie atrybutu kursora SQL%ROWCOUNT z instrukcjami SELECT...INTO, gdyż w przypadku, gdy żaden wiersz nie spełnia jej warunku, generowany jest błąd systemu Oracle: ORA-1403: nie znaleziono danych Ten błąd spowoduje natychmiastowe przekazanie sterowania do sekcji obsługi wyjątków tego bloku, nie dopuszczając do sprawdzenia atrybutu kursora SQL%ROWCOUNT. Uwaga: Atrybuty SQL%FOUND, SQL%NOTFOUND i SQL%ROWCOUNT należy sprawdzać zawsze po instrukcji DML. Pętle kursora. Efektywne wykorzystanie kursorów ma miejsce w momencie umieszczenia ich w pętli. Wynika to choćby z faktu, iż najczęściej wykonywaną operacją związaną z kursorem jest pobieranie wszystkich wierszy znajdujących się w zbiorze wynikowym. Obywa się to za pomocą pętli pobierania, która jest po prostu pętlą przetwarzającą jeden po drugim, każdy wiersz zbioru wynikowego. Aby mieć pewność, iż za pomocą tak umiejscowionego kursora zostały pobrane wszystkie wiersze zwracane przez instrukcje SELECT należy oczywiście wykorzystać atrybut kursora %NOTFOUND. Kursor w pętli prostej i pętli WHILE Jak już wspomniano, najczęściej kursory jawne służą do pobierania wszystkich wierszy zbioru wynikowego generowanego przez własną instrukcję SELECT. Pętlę pobrania, która realizuje właśnie tego typu zadanie, można skonstruować wykorzystując składnie pętli prostej lub pętli WHILE. W celu sprawdzenia, czy wszystkie wierszy z aktywnego zbioru wierszy zostały pobrane lub też ile wierszy tego zbioru zostało już pobranych, inaczej mówiąc, ile iteracji pętli pobrania zostało już wykonanych, wykorzystuje się odpowiednie atrybuty kursora jawnego są używane do sprawdzenia liczby wykonanych iteracji pętli. Poniższy blok języka PL/SQL ilustruje konstrukcję pętli pobrania z wykorzystaniem kursora powiązanego z pętlą prostą: 12

-- Zadeklarowanie zmiennych do przechowywania danych dotyczących produktów wycofanych z_idprodukt towary.id%type; z_nazwaprodukt towary.nazwa%type z_cenaprodukt towary.cena%type; -- Kursor do pobrania danych dotyczących produktów wycofanych CURSOR k_produkty IS SELECT id, nazwa, cena, stan WHERE wycofany = TAK ; -- Otwarcie kursora i zainicjowanie zbioru wynikowego OPEN k_produkty; LOOP --Pobranie danych następnego towaru FETCH k_produkty INTO z_idprodukt, z_nazwaprodukt, z_cenaprodukt; -- Wyjscie z pętli kiedy nie ma więcej wierszy do pobrania EXIT WHEN k_produkty%notfound /* W tym przypadku przetwarzanie pobranych wierszy polega na ich wstawieniu do tabeli produkty_wycofane */ INSERT INTO produkty_wycofane(id, nazwa, cena) VALUES(z_IdProdukt, z_nazwaprodukt, z_cenaprodukt); END LOOP; -- Zamknięcie kursora - zwolnienie obszaru pamięci wykorzystywanego przez kursor CLOSE k_produkty W przykładzie poniżej pętla pobrania, która realizuje to samo zadanie co poprzednio, zostaje skonstruowana za pomocą pętli WHILE. /* Zadeklarowanie zmiennych do przechowywania danych dotyczących produktów wycofanych */ z_idprodukt towary.id%type; z_nazwaprodukt towary.nazwa%type z_cenaprodukt towary.cena%type; -- Kursor do pobrania danych dotyczących produktów wycofanych CURSOR k_produkty IS SELECT id, nazwa, cena, stan WHERE wycofany = TAK ; -- Otwarcie kursora i zainicjowanie zbioru wynikowego OPEN k_produkty; FETCH k_produkty INTO z_idprodukt, z_nazwaprodukt, z_cenaprodukt; WHILE k_produkty%found LOOP -- Wyjście z pętli gdy nie ma więcej wierszy do pobrania 13

--Pobranie danych następnego towaru FETCH k_produkty INTO z_idprodukt, z_nazwaprodukt, z_cenaprodukt; /* W tym przypadku przetwarzanie pobranych wierszy polega na ich wstawieniu do tabeli produkty_wycofane */ INSERT INTO produkty_wycofane(id, nazwa, cena) VALUES(z_IdProdukt, z_nazwaprodukt, z_cenaprodukt); END LOOP; -- Zamknięcie kursora - zwolnienie obszaru pamięci wykorzystywanego przez kursor CLOSE k_produkty Należy pamiętać o pewnych zasadach dotyczących tworzenia pętli pobrania za pomocą pętli prostej i pętli WHILE: Instrukcja OPEN, która otwiera kursor, powinna koniecznie znajdować się przed pętlą. Ponadto kursor powinien być otwierany tylko raz za pomocą tej instrukcji. W przypadku, kiedy pętla pobrania jest realizowana za pomocą pętli WHILE, konieczne jest, aby pierwsza instrukcja FETCH znalazła się już przed rozpoczęciem pętli WHILE. Takie usytuowanie instrukcji FETCH jest nieodzowne, aby mógł być spełniony warunek pętli WHILE i w ten sposób, aby rozpoczęła się pierwsza iteracja pętli. Wynika to z faktu, iż nałożony warunek zawiera atrybut kursora %FOUND, który przyjmuje wartość TRUE, jeśli w aktywnym zbiorze wierszy znajduje się przynajmniej jeden wiersz. Instrukcje znajdujące się w pętli prostej przed instrukcją EXIT WHEN są wykonywane przynajmniej raz. Dzieje się tak nawet w przypadku, gdy aktywny zbiór wierszy nie zawiera ani jednego wiersza. Ten fakt z kolei jest spowodowany tym, iż sterowanie powoduje wejście do pętli prostej jeszcze przed wczytaniem pierwszego wiersza danych. W przypadku pętli WHILE wejście do pętli następuje dopiero po pierwszym wczytaniu (patrz punkt poprzedni) i dopiero wtedy instrukcje znajdujące się przed instrukcją EXIT WHEN są przynajmniej raz wykonane. Instrukcja EXIT WHEN powinna znajdować się wewnątrz pętli prostej, bezpośrednio po instrukcji FETCH. W ten sposób po pobraniu ostatniego wiersza atrybut kursora nazwa_kursora%notfound przyjmuje wartość TRUE i instrukcja EXIT WHEN nazwa_kursora%notfound spowoduje wyjście z pętli (patrz przykład powyżej). Takie usytuowanie instrukcji EXIT WHEN powoduje, iż nie będą obsługiwane duplikaty wierszy każdy pobrany wiersz zostanie tylko raz przetworzony. Pominięcie instrukcji EXIT WHEN wewnątrz pętli prostej powoduje, iż pętla będzie wykonywana nieskończenie wiele razy. W przypadku pętli WHILE nie ma potrzeby umieszczania instrukcji EXIT WHEN po instrukcji FETCH wewnątrz pętli. Zakończenie wykonywania pętli jest sterowane warunkiem pętli WHILE w momencie, gdy warunek przyjmuje wartość FALSE, pętla przestaje być wykonywana Instrukcja CLOSE, zamykająca kursor, powinna znajdować się za pętlą. Pętle FOR kursora Istnieje jeszcze jeden sposób skojarzenia kursora z pętlą. Są to tzw. pętle FOR kursora, które, jak się okazuje, pozwalają uprościć obsługę przetwarzania kursora za pomocą pętli. Jak wynika z choćby z 14

przykładów z poprzedniego rozdziału, w których użyto pętlę prostą i pętlę WHILE do przetwarzania kursorów jawnych, obie te pętle wymagają jawnego przetwarzania kursorów za pomocą instrukcji OPEN, FETCH i CLOSE. Wszystkie operacje związane z przetwarzaniem kursora, która wykonywane są za pomocą tych instrukcji, mogą odbywać się niejawnie i wtedy oczywiście nie ma potrzeby umieszczania tych instrukcji w kodzie. Możliwość taką daje użycie właśnie pętli FOR kursora, gdzie te czynności odbywają się w sposób niejawny. W ten sposób przetwarzanie kursora z wykorzystaniem pętli FOR kursora znacznie upraszcza kod programu. Ilustruje to poniższy przykład programu, który realizuje to samo zadanie, co przykładowe programy z poprzedniego rozdziału, w którym do przetwarzania kursora wykorzystano pętlę prostą i pętlę WHILE. -- Kursor do pobrania danych dotyczących produktów wycofanych CURSOR k_produkty IS SELECT id, nazwa, cena, stan WHERE wycofany = TAK ; /* Rozpoczęcie wykonywania pętli operacja OPEN kursora k_produkty jest wykonywana w tym miejscu w sposób niejawny */ FOR z_daneprodukt IN k_produkty LOOP /* W tym przypadku przetwarzanie pobranych wierszy polega na ich wstawieniu do tabeli produkty_wycofane */ INSERT INTO produkty_wycofane(id, nazwa, cena) VALUES(z_DaneProdukt.id, z_daneprodukt.nazwa, z_daneprodukt.cena); /* Przed dalszym wykonaniem pętli będzie niejawnie sprawdzony atrybut kursora %NOTFOUND, który sprawdzi, czy kursor nadal pobiera wiersze z aktywnego zbioru wierszy */ END LOOP; /* Pętla zostaje zamknięta powoduje to wykonanie w sposób niejawny operacji zamknięcia kursora, a to z kolei powoduje zwolnienie obszaru pamięci wykorzystywanego przez kursor */ CLOSE k_produkty Podobnie, jak w przypadku wykorzystania pętli prostej i pętli WHILE do przetwarzania kursora, podamy pewne zasady charakterystyczne dla pętli FOR kursora wykorzystanych do przetwarzania kursorów jawnych: Nie ma potrzeby otwierania (instrukcja OPEN), wczytywania (FETCH) i zamykania (CLOSE) kursora. Wszystkie te operacje są wykonywane automatycznie w sposób niejawny przez system Oracle. Rekord z_daneprodukt nie musi być deklarowany w sekcji deklaracji bloku. Zmienna ta jest niejawnie deklarowana przez kompilator PL/SQL, podobnie jak w przypadku indeksu pętli dla pętli numerycznej FOR. Typem tej zmiennej jest k_produkty%rowtype, a zakresem zmiennej z_daneprodukt jest tylko pętlą FOR. 15

Deklaracja niejawna indeksu pętli oraz zakres tej deklaracji dają w efekcie działanie takie samo jak dla pętli numerycznej FOR. Z tego powodu nie można przypisać wartości do zmiennej pętli wewnątrz pętli FOR kursora. Możemy uzyskać dostęp do poszczególnych kolumn w instrukcji SELECT kursora. korzystając z notacji kropki. Notacja ta pozwala na użycie atrybutów typów rekordowych poprzez umieszczenie za nazwą indeksu kropki i nazwy kolumny występującej w instrukcji SELECT kursora. W przykładzie u góry zastosowano taki właśnie sposób odwołań do pól z instrukcji SEECT kursora, np. z_daneprodukt.nazwa. Uwaga: Nie należy utożsamiać pętli FOR kursora z pętlą numeryczną FOR. Pętla FOR kursora jest wynikiem połączenia kursora zawierającego instrukcję SELECT z numeryczną pętlą FOR. Natomiast w przypadku zwykłej pętli numerycznej FOR wcale nie musi być przetwarzany jakikolwiek kursor. Przeznaczenie pętli FOR kursora oraz pętli prostej i pętli WHILE skojarzonych z kursorami: Pętla FOR kursora jest najefektywniejszym sposobem zaprojektowania kursora. Sposób ten jest zalecany szczególnie w przypadku, gdy bezwarunkowo należy przetworzyć wszystkie wiersze pobierane za pomocą kursora, a dokładniej mówiąc, wszystkie wiersze zwracane jak zbiór wynikowy przez instrukcję SELECT kursora. W przypadku pętli prostej i pętli WHILE konwencjonalna metoda przetwarzania kursora polega na zastosowaniu instrukcji OPEN, FETCH i CLOSE. Z drugiej strony, zastosowanie tych instrukcji umożliwia ponadto w sposób bardziej elastyczny niż przy wykorzystaniu pętli FOR kursora, pominięcie niektórych wierszy z aktywnego zbioru wierszy zwracanych poprzez instrukcję SELECT kursora. W ten sposób dalsze przetwarzanie niektórych wierszy, które zostały uprzednio pobrane, może zostać pominięte. Niejawne pętle FOR kursora Okazuje się, że składnię pętli FOR kursora można jeszcze bardziej skrócić. Oprócz niejawnej deklaracji rekordu także sam kursor może być zadeklarowany niejawnie. Ilustruje taki przypadek poniższy przykład: /* Rozpoczęcie pętli. Niejawne wykonanie instrukcji OPEN oraz niejawny sposób deklaracji samego kursora skojarzonego z pętlą FOR kursora. Sposób niejawnej deklaracji kursora nie musi być uprzednio deklarowany w sekcji deklaracji bloku */ FOR z_daneprodukt IN ( SELECT id, nazwa, cena, stan WHERE wycofany = TAK ;) LOOP /* Niejawne wykonanie instrukcji FETCH i sprawdzenie atrybutu %NOTFOUND. Przetwarzanie pobranych wierszy polega na wprowadzeniu każdego pobranego produktu wycofanego do tabeli produkty_wycofane */ INSERT INTO produkty_wycofane(nr, nazwa, cena) VALUES(z_DaneProdukt.id, z_daneprodukt.nazwa, z_daneprodukt.cena); END LOOP; /* W tym miejscu programu, po zakończeniu pętli, niejawnie jest wykonywana instrukcja CLOSE */ 16

Z jednej strony takie podejście deklarowania kursora w pętli FOR kursora powoduje, iż istnieje mniejsze ryzyko popełnienia błędów, gdyż kod programu jest krótszy i bardziej zwięzły. Nie zawsze jednak tak daleko posunięte upraszczanie w ten sposób kodu programu jest pożądane. Kursor deklarowany niejawnie nie ma choćby nazwy. Nie można go zatem ponownie wykorzystywać, o ile zachodzi taka potrzeba. Ponadto, niekiedy łatwiejsza jest interpretacja kodu programu, o ile wszystkie instrukcje SELECT znajdują się w części deklaracji bloku PL/SQL. Kursory z klauzulą FOR UPDATE instrukcji SELECT W przypadku, gdy w pętlach pobierania występuje potrzeba modyfikowania pobranych danych należy zastosować pewną specyficzną składnię kursora. Otóż należy umieścić dwie dodatkowe klauzule tej składni: klauzulę FOR UPDATE w deklaracji kursora oraz klauzulę WHERE CURRENT OF w instrukcji UPDATE lub DELETE. Definiowanie kursora SELECT FOR UPDATE Kursor SELECT FOR UPDATE definiujemy za pomocą klauzuli FOR UPDATE OF, która w ten sposób jest integralną częścią instrukcji SELECT. Klauzula ta prawidłowo występuje jako ostatnia klauzula instrukcji SELECT, po klauzuli ORDER BY (o ile ta ostatnia występuje). Średnik kończący instrukcję SELECT jest zatem umieszczany dopiero po tej ostatniej klauzuli tej instrukcji. Składnia tej klauzuli jest następująca: gdzie: FOR UPDATE [OF odnosnik_do_kolumny] [NOWAIT], odnosnik_do_kolumny jest kolumną tabeli, na której jest wykonywane zapytanie. Może to być również lista kolumn; klauzula NOWAIT jest przewidziana dla sytuacji, gdy w innej sesji nałożono blokady na wierszach w zbiorze wynikowym. Wówczas operacja SELECT... FOR UPDATE czeka na zwolnienie tych blokad w innej sesji. Przy tym dla tego okresu oczekiwania nie występuje limit czasu operacja SELECT... FOR UPDATE będzie zawieszona dopóty, dopóki blokada nie zostanie zwolniona w innej sesji. W poniższym przykładzie w sekcji deklaracji zdefiniowano dwa kursory, w których w sposób prawidłowy wstawiono klauzulę FOR UPDATE OF. Tym niemniej druga forma kursora, w której w składni klauzuli FOR UPDATE OF nie jest wylistowana żadna kolumna instrukcji SELECT kursora, nie jest zalecana. -- Ten kursor wylistuje dwie kolumny dla klauzuli UPDATE CURSOR k_produkty IS SELECT * FOR UPDATE OF nazwa, cena; -- Ten kursor nie wylistuje żadnej kolumny CURSOR k_produkty IS SELECT nazwa, cena WHERE cena>1000 FOR UPDATE; 17

W ogólnym przypadku instrukcja SELECT nie nakłada żadnych blokad na wiersze, do których jest uzyskiwany dostęp. Pozwala to w czasie innych sesji połączenia z bazą danych zmieniać wybierane dane. Jednak zbiór wynikowy pozostaje w dalszym ciągu spójny. W czasie otwarcia za pomocą instrukcji OPEN, kiedy określany jest zbiór wynikowy, system Oracle wykonuje migawkę tabeli. Wszystkie zmiany, które zostały zatwierdzone do tego momentu, będą odzwierciedlone w zbiorze wynikowym. Wszystkie zmiany dokonane po tym momencie, nawet jeśli zostaną zatwierdzone, nie będą odzwierciedlone, dopóki kursor nie zostanie ponownie otwarty, co spowoduje powtórne wybranie zbioru wynikowego. Jeśli jednak występuje klauzula FOR UPDATE, wyłączne blokady wierszy są nakładane na wiersze w zbiorze wynikowym przed wykonaniem instrukcji OPEN. Te blokady uniemożliwiają dokonywanie zmian wierszy wchodzących w skład zbioru wynikowego w innych sesjach dopóki transakcja nie zostanie zatwierdzona lub wycofana. UWAGA: Jeżeli wiersze są zablokowane w innej sesji, to instrukcja OPEN spowoduje wystąpienie błędu systemu Oracle: ORA-54: zasoby zajęte i uzyskiwane za pomocą klauzuli NOWAIT W tym przypadku można spróbować wykonać instrukcję OPEN w późniejszym terminie lub zmienić zbiór wynikowy do pobrania niezablokowanych wierszy. Sposoby użycia kursora z klauzulą FOR UPDATE OF Po zdefiniowaniu kursora z klauzulą FOR UPDATE OF można go wykorzystać do przetwarzania pobranych wierszy. W tym celu najlepiej wykorzystać klauzulę WHERE CURRENT OF, która zapewnia, iż każdy pobrany wiersz zostanie zmodyfikowany w pożądany sposób. Ponadto przy stosowaniu tego typu kursorów trzeba przestrzegać pewnych zasad dotyczących pracy z danymi w różnych sesjach, szczególnie w przypadku, gdy dane modyfikowane są blokowane, a istnieje potrzeba dokonania zatwierdzania dokonywanych modyfikacji danych. Klauzula WHERE CURRENT OF Po zdefiniowaniu kursora z klauzulą FOR UPDATE OF, można użyć klauzuli WHERE CURRENT OF, aby przetworzyć zwrócone przez niego wiersze. W tym celu należy wykorzystać klauzulę WHERE CURRENT OF. Składnia tej klauzuli jest następująca: WHERE CURRENT OF nazwa_kursora; gdzie: nazwa_kursora jest nazwą kursora, który został zadeklarowany z klauzulą FOR UPDATE. Znaczenie zastosowania klauzuli WHERE CURRENT OF jest dość oczywiste i sprowadza się do tego, iż klauzula ta powoduje przetwarzanie w pożądany przez programistę sposób wiersza, który został właśnie pobrany przez kursor. Oczywiście możliwe jest również takie samo zmodyfikowanie pobranych przez kursor wierszy z klauzulą FOR UPDATE OF bez konieczności stosowania klauzuli WHERE CURRENT OF. Jednak w tym przypadku konieczne jest przy wykorzystaniu klucza głównego tabeli zaktualizowanie lub usunięcie wierszy pobranych przez kursor (patrz następny rozdział). Poniższy, przykładowy blok ilustruje aktualizację pobranych wierszy przez kursor z klauzulą FOR UPDATE OF z wykorzystaniem klauzuli WHERE CURRENT OF. Przykład dotyczy wprowadzenia modyfikacji opłaty przewozowej, tzw. frachtu, o podaną wielkość, dla wszystkich faktur zrealizowanych w miesiącu maju: z_zmiana NUMBER:= 0,95; -- obniżka frachtu o 5% z_miesiac NUMBER:= 5; -- miesiąc maj 18

/* kursor służy do wybrania faktur zrealizowanych w miesiącu maju; zauważmy ponadto, iż klauzula FOR UPDATE jest integralną częścią kursora (dopiero po niej następuje średnik) */ CURSOR k_faktury_miesiac IS SELECT nr, data_wystawienia, fracht FROM faktury WHERE nr IN (SELECT nr FROM faktury WHERE TO_CHAR( data_wystawienia, MM )= 05 ) FOR UPDATE OF stan; FOR z_danefaktura IN k_faktury_miesiac LOP -- uaktualnienie wielkości frachtu dla faktur z wybranego miesiąca UPDATE faktury SET fracht = fracht * z_zmiana; WHERE CURRENT OF k_faktury_miesiac; END LOOP; -- zatwierdzenie działań i zwolnienie blokad COMMIT; Należy zwrócić, uwagę, że instrukcja UPDATE uaktualnia tylko kolumnę wskazaną w klauzuli FOR UPDATE OF deklaracji kursora. W przypadku, gdy po klauzuli FOR UPDATE OF nie jest wymieniona żadna kolumna, teoretycznie może być uaktualniona dowolna. Tym niemniej nie jest to dobry styl programowania i nie należy go stosować. Uwaga: Nie można stosować klauzuli WHERE CURRENT OF dla kursora, który został zadeklarowany bez klauzuli FOR UPDATE. Kursor SELECT z klauzulą FOR UPDATE OF ma dwie istotne zalety: blokuje wiersze po otwarciu kursora, a wiersze zbioru wynikowego są identyfikowane w celu modyfikacji; eliminuje drugie wczytywanie wierszy w celu wykonania aktualizacji oraz zachowuje bieżący wiersz za pomocą klauzuli WHERE CURRENT OF. Pobieranie z instrukcjami COMMIT W powyżej przytoczonym przykładzie wykorzystania kursora z klauzulą FOR UPDATE OF została użyta instrukcja COMMIT do zatwierdzenia wprowadzonych zmian. Jak z niego wynika, instrukcja COMMIT jest wykonywana dopiero po zakończeniu pętli pobierania. Przyczyną tego jest fakt, iż instrukcja COMMIT zwalnia blokady utrzymywane w sesji połączenia z bazą danych. Ponieważ klauzula FOR UPDATE zakłada blokady, zostałyby one zwolnione przez instrukcję COMMIT. W takim przypadku kursor uszkodziłby się. Każde następne pobrania spowodowałyby wystąpienie błędu Oracle: ORA-1002: pobieranie poza kolejnością. W przypadku, gdy kursor nie jest zdefiniowany za pomocą SELECT FOR UPDATE, problem ten nie występuje i instrukcja COMMIT może być umieszczona wewnątrz pętli pobrania. Rozważmy poniższy przykład, który ilustruje powstanie błędu ORA-1002: 19

--Poniższy kursor pobiera wszystkich klientów, jak również służy do zablokowania wierszy CURSOR k_produktywycofane SELECT * FOR UPDATE stan; z_daneprodukt towary%rowtype; -- Otwarcie kursora - założenie blokad OPEN k_produktywycofane -- Pobranie pierwszego rekordu FETCH k_produktywycofane INTO z_daneprodukt; -- Wydanie instrukcji COMMIT - spowoduje to zwolnienie blokad i unieważnienie kursora COMMIT; -- Ta instrukcja FETCH spowoduje powstanie błędu ORA-1002. FETCH k_produktywycofane INTO z_daneprodukt;... W ten sposób, jeżeli instrukcja COMMIT występuje wewnątrz pętli pobierania SELECT FOR UPDATE, nie powiodą się pobrania po wykonaniu instrukcji COMMIT. Dlatego też nie jest zalecane używanie polecenia COMMIT wewnątrz pętli. Nie zaleca się wykonywania instrukcji COMMIT wewnątrz pętli pobierania nawet wtedy, gdy nie definiuje się kursora z klauzulą FOR UPDATE. Np. jeżeli zapytanie dotyczy wielu wierszy, gdyż może wystąpić błąd ORA-1555: snapshot too old (migawka zbyt stara). W przypadku, gdy zachodzi konieczność umieszczenia polecenia COMMIT wewnątrz pętli pobrania, to oczywiście klauzula WHERE CURRENT OF nie jest dostępna, ponieważ kursor nie może być zdefiniowany za pomocą klauzuli FOR UPDATE. W celu modyfikacji każdego pobranego wiersza można użyć klucza głównego tabeli w klauzuli WHERE instrukcji UPDATE. Jednak w tym przypadku, w przeciwieństwie do użycia klauzuli WHERE CURRENT OF, nie ma nałożonej blokady na modyfikowane wiersze. A zatem, jeżeli dane będą wykorzystywane współbieżnie w innych sesjach, działanie bloku może różnić się od oczekiwań. Jeżeli tabela użyta w zapytaniu nie ma klucza głównego, to zamiast niego można zastosować pseudokolumnę ROWID. Można ją pobrać dla każdego wiersza do zmiennej PL/SQLSQL (zadeklarowanej jako zmienna typu ROWID lub UROWID) i wykorzystać w klauzuli WHERE postaci WHERE rowid=z_idwiersza. Poniższy przykład ilustruje przypadek wykorzystania klucza głównego tabeli do modyfikacji wierszy pobranych w pętli pobrania: 20

z_zmiana NUMBER:=0,95; -- obniżka frachtu o 5% z_miesiac NUMBER:=5; -- miesiąc maj /* kursor służy do wybrania faktur zrealizowanych w miesiącu maju; zauważmy ponadto, iż klauzula FOR UPDATE jest integralną częścią kursora (dopiero po niej następuje średnik) */ CURSOR k_faktury_miesiac IS SELECT nr, data_wystawienia, fracht FROM faktury WHERE nr IN (SELECT nr FROM faktury WHERE TO_CHAR( data_wystawienia, MM )= 05 ); FOR z_danefaktura IN k_faktury_miesiac LOP -- uaktualnienie wielkości frachtu dla faktur z wybranego miesiąca UPDATE faktury SET fracht = fracht * z_zmiana; WHERE nr=z_danefalktura.id; -- wykorzystanie klucza głównego /* Można wykonać zatwierdzenie poleceniem COMMIT wewnątrz pętli, ponieważ kursor nie jest zadeklarowany z klauzulą FOR UPDATE */ COMMIT; -- zatwierdzenie zmian wewnątrz pętli END LOOP; Istotnie, jak wynika choćby z powyższego przykładu, nie jest konieczne używanie klauzuli WHERE CURRENT OF w celu modyfikacji wierszy pobranych w pętli pobrania. Tym niemniej należy liczyć się z faktem, iż w przypadku, gdy dane są często używane w wielu sesjach, mogą być w nich modyfikowane, gdyż nie są zablokowane, co uzyskiwaliśmy poprzez zastosowanie klauzuli WHERE CURRENT OF. Należy zatem liczyć się, iż wynik, jaki uzyskamy modyfikując w ten sposób dane, może znacznie różnic się od oczekiwanego. Powyższy przykład w zasadzie symuluje działanie klauzuli WHERE CURRENT OF, z tym że nie nakłada blokad na wierszach w zbiorze wynikowym. Jednak dobrym stylem programowania, jak również, aby mieć pewność, co do wyniku wprowadzanych zmian, jest wykorzystywanie do tego celu klauzuli WHERE CURRENT OF. Rekordy kursorowe Rekord kursorowy (ang. cursor-orineted record) jest rekordem o strukturze przypominającej kursor PL/SQL. Poszczególne pola rekordu kursorowego odpowiadają pod względem nazwy i struktury kolumnom wymienionym w instrukcji SELECT kursora jawnego w bloku PL/SQL. Rekord kursorowy tworzymy w oparciu o nazwę kursora i operator %ROWTYPE, a składnia instrukcji tworzącej ten typ rekordu jest następująca: gdzie: nazwa_zmiennej_typu_rekordowego nazwa_kursora%rowtype; nazwa_zmiennej_typu_rekordowego jest nazwą zmiennej typu rekordowego, która ma taką samą strukturę rekordu, jak wiersz w kursorze o nazwie nazwa_kursora; 21