Wydział Elektrotechniki, Informatyki i Telekomunikacji Instytut Informatyki i Elektroniki Instrukcja do zajęć laboratoryjnych Nr ćwiczenia: 2 Temat: Przypomnienie wiadomości na temat języka PL/SQL WAGI: Parametry wejściowe procedur powinny być zdefiniowane jako zakotwiczone (ang. anchor). Ta sama uwaga dotyczy się również wszystkich innych zmiennych odnoszących się do poszczególnych kolumn w tabelach. W wszystkich procedurach i funkcjach należy dokładnie przemyśleć oraz zaimplementować obsługę ew. błędów, tak aby programy nie wysypywały się w sposób niekontrolowany. Zwrócić również uwagę na konwencje w nazewnictwie zmiennych (powinna być jednolita we wszystkich programach). Proponujemy używać przedrostków in_, inout_ oraz out_ w nazwach parametrów wejściowych procedur i funkcji (w zależności od trybu zmiennej patrz wykład) oraz przedrostków c_ w nazwach kursorów. Pozostałe zmienne mogą mieć przedrostek uv_ (od user variable) aby łatwiej je było odróżnić od np. identyfikatorów nazw kolumn. Proponujemy również przyjąć zasadę, że wszystkie słowa kluczowe języka SQL oraz PL/SQL piszemy dużymi literami a pozostałe identyfikatory definiowane przez użytkownika małymi. Bardzo wydatnie zwiększa to czytelność programów! Należy również pamiętać o konsekwentnym i jednolitym stosowaniu wcięć, których brak bardzo utrudnia czytanie programów. NAJPROSTSZE PROGRAMY W JĘZYK PL/SQL. BLOKI ANONIMOWE. OBSŁGA WYJĄTKÓW: 1. Napisz anonimowy blok PL/SQL, w którym zostaną zadeklarowane cztery zmienne. Jedna będzie typu NMBER, druga typu VARCHAR2, trzecia typu DATE. Zmienne te powinny zostać zainicjowane dowolnymi wartościami. Czwarta zmienna, typu DATE, powinna być zadeklarowana jako CONSTANT i również zainicjowana dowolną wartością. W dalszej części programu należy wyświetlić na konsoli wartości tych zmiennych (użyć funkcji PT_LINE z pakietu DBMS_OTPT). Spróbować również programowo zmodyfikować czwartą zmienną (o atrybucie CONSTANT) i zaobserwować reakcję systemu Oracle. 2. Napisz anonimowy blok PL/SQL, który wypisze ile dni minęło od twojej daty urodzenia. 3. Napisz anonimowy blok PL/SQL, który będzie pobierał od użytkownika 2 liczby (z konsoli SQL*Plus-a) a następnie wyświetli ich sumę, różnicę, iloczyn, iloraz oraz wartość pierwszej liczby podniesionej do potęgi wskazywanej przez drugą liczbę. Studiując samodzielnie opracował: dr inż. Artur Gramacki 1
dokumentację wykonać jeszcze trzy inne, dowolnie wybrane działania, na podanych liczbach. Pobieranie liczb z konsoli może wyglądać jak poniżej:... Podaj wartość dla liczba1: 10 stare 2: licz1 NMBER := &liczba1; nowe 2: licz1 NMBER := 10; Podaj wartość dla liczba2: 40 stare 3: licz2 NMBER := &liczba2; nowe 3: licz2 NMBER := 40; Suma: 50 Procedura PL/SQL została zakończona pomyślnie. SQL> Następnie postaraj się tak ustawić odpowiednie zmienne środowiska SQL*Plus, aby wyłączyć wypisywanie na ekranie informacji o pobieranych zmiennych.... Podaj wartość dla liczba1: 10 Podaj wartość dla liczba2: 20 Suma: 30 Procedura PL/SQL została zakończona pomyślnie. SQL> 4. Napisz anonimowy blok PL/SQL, który poprosi użytkownika o podanie liczby całkowitej N a następnie wpisze liczby od 1 do N. Napisać trzy wersje programu, każdy z wykorzystaniem innego rodzaju pętli ( LOOP, FOR-LOOP, WHILE-LOOP). Za pomocą zdefiniowanego wyjątku sprawdzić, czy podana liczba mieści się w przedziale <1...10>. żyj procedury RAISE_APPLICATION_ERROR do zdefiniowania własnego błędu (tak, jest to możliwe!). 5. Napisz anonimowy blok PL/SQL, który wyświetli podstawowe dane o pracowniku (tabela EMP), który zarabia najmniej oraz najwięcej. żyj tzw. kursora niejawnego. Definiując wymagane zmienne użyj tzw. deklaracji zakotwiczonych (ang. anchor). Pamiętaj, że należy obsłużyć sytuację, gdy istnieje więcej niż jeden pracownik zarabiający najmniej lub najwięcej. W takim przypadku wyświetl ilość rekordów spełniających te warunki, np.: Najwięcej zarabia: Imię1 Nazwisko1. Zarobki: 2000 Najmniej zarabia: Imię2 Nazwisko2: Zarobki: 550 Pracowników, którzy zarabiają najmniej jest: 3. Zarobki 2000 Pracowników, którzy zarabiają najwięcej jest: 2. Zarobki 550 lub 6. Napisz anonimowy blok PL/SQL. Zdefiniuj tzw. kursor jawny, który pobierze i wyświetli dane wszystkich pracowników. Zastosuj pętlę LOOP... END LOOP. 7. Powtórz polecenie z poprzedniego punktu ale tym razem użyj pętli FOR... IN, aby uniknąć jawnego deklarowania kursora (CRSOR...IS...), pobierania rekordów (za pomocą FETCH) oraz zamykania kursora (CLOSE). opracował: dr inż. Artur Gramacki 2
8. Napisz anonimowy blok PL/SQL i zdefiniuj kursor z parametrami. Jego zadaniem ma być wyświetlenie wszystkich zamówień (tabela ORD) złożonych w podanym okresie (np. zmienne DATA_OD, DATA_DO). Wyświetlając dane o zamówieniu pobieraj również dane o kliencie, którego dotyczy zamówienie (z tabeli CSTOMER) oraz dane o pracowniku obsługującym dane zamówienie (tabela EMP). PAKIETY, PROCEDRY, FNKCJE PL/SQL: 1. ADD_EMP zadaniem procedury ma być dodawanie nowego pracownika do tabeli EMP. Numer powinien być pobierany automatycznie ze zdefiniowanej w tym celu sekwencji. 2. CHANGE_EMP procedura modyfikuje dane wskazanego pracownika. 3. DELETE_EMP procedura kasuje dane wskazanego pracownika. 4. CHANGE_SALARY zadaniem tej procedury jest zmiana zarobków wskazanego pracownika. Podajemy procentową zmianę zarobków. Przykładowo, gdy podamy 50 oznacza to wzrost zarobków o 50% a gdy podamy -10 oznacza to obniżkę zarobków o 10%. 5. TOP_N_EMP wyświetla listę N pracowników (N podawane jako parametr wejściowy procedury), którzy zarabiają najwięcej. Dane o tych pracownikach (imię, nazwisko, zarobki) powinni zostać dodatkowo zapisani do tabeli o nazwie TOP_N_EMP (czy tabelę tą można stworzyć bezpośrednio w kodzie procedury?). 6. CHANGE_DEPT procedura zmienia przypisanie pracownika do wydziału (tabela DEPT). 7. CRRENCY_SALARY zadaniem procedury jest wypisanie zarobków w podanej jako parametr walucie. Dane na temat kursów walut powinny być pobierane z utworzonej wcześniej tabeli CRRENCY (kolumny: id, currency_symbol, rate). Przykładowo wywołanie funkcji w postaci CRRENCY_SALARY('PLN') wypisze zarobki pracowników po przeliczeniu ich na złotówki. Zakładamy, że zarobki, wpisane w tabeli EMP są w walucie SD. 8. STAT_EMP zadaniem funkcji jest zwrócenie wartości (w zależności od podanego parametru) zarobków maksymalnych, minimalnych, średniej lub też sumy zarobków wszystkich pracowników. Funkcja powinna przyjmować tylko jeden z czterech parametrów: MAX, MIN, AVG, SM. Podanie innego parametru powinno wygenerować stosowne ostrzeżenie. 9. ORD_SM zadaniem funkcji jest zwrócenie całkowitej wartości wskazanego zamówienia (suma iloczynów ITEM.PRICE * ITEM.QANTITY). Wewnątrz funkcji powinno wykonać się sprawdzenie spójności danych z danymi w tabeli ORD (patrz następny punkt). Gdy dane są niespójne zamiast sumy iloczynów powinien być zwrócony stosowny komunikat). 10. CHECK_ORD_ITEM zadaniem procedury jest sprawdzenie, czy dane w tabelach ORD oraz ITEM są spójne. W tabeli ORD występuje kolumna TOTAL natomiast w tabeli ITEM występują kolumny PRICE oraz QANTITY (patrz rysunek poniżej). Zgodnie z logiką modelu SMMIT2 mamy, że kolumna ORD.TOTAL zawiera (powinna zawierać) sumaryczną wartość sprzedaży na danym zamówieniu. Jest ona ilościowo równa wartości wszystkich zamówionych produktów, których szczegóły zapisane są w tabeli ITEM (czyli jest to suma wszystkich iloczynów ITEM.PRICE * ITEM.QANTITY dla danego zamówienia). opracował: dr inż. Artur Gramacki 3
fragment tabel ORD <pk> <fk> fragment tabeli ITEM ITEM_ ORD_ TOTAL PRICE QANTITY Procedura ma wyświetlić dane zamówień, które są niespójne. Na ekranie powinniśmy zobaczyć wynik podobny jak w poniższym przykładzie (pokazano przypadek, gdy dwa rekordy w tabeli ORD nie są spójne w sensie podanym powyżej): Numer zamówienia: 100 total : 5000 price * quantity: ord_id=1: 100 * 10 = 1000 ord_id=2: 20 * 40 = 800 ord_id=3: 10 * 50 = 500 ------ 2300 Numer zamówienia: 101 total : 3000 price * quantity: ord_id=10: 10 * 10 = 100 ord_id=20: 20 * 50 = 1000 ------ 1100 WYZWALACZE BAZODANOWE: 1. Napisać wyzwalacz dla tabeli EMP, który sprawdza czy w polu SALARY nie wpisano błędnie liczby ujemnej (zarobki pracownika nie mogą być ujemne!). 2. Jak wiadomo w tabeli EMP możliwe jest przechowywanie informacji o odległościach pracowników (kolumny oraz MANAGER_). Napisać wyzwalacz, który będzie kontrolował, czy wpisywane dla danego pracownika zarobki nie są większe niż zarobki jego bezpośredniego szefa. Poniżej pokazano część danych z tabeli EMP. Wynika z niej, że pracownicy o numerach =20 oraz =21 nie mogą zarabiać więcej niż 1100, gdyż tyle zarabia ich bezpośredni przełożony. Podobnie pracownicy o numerach =22 oraz =23 nie mogą zarabiać więcej niż 1300. MANAGER_ Imie, Nazwisko SALARY ---------- ---------- ------------------------------ ---------- 8 2 Ben Biri 1100 20 8..Chad Newman 750 21 8..Alexander Markarian 850 9 2 Antoinette Catchpole 1300 22 9..Eddie Chang 800 23 9..Radha Patel 795 3. Napisz wyzwalacz, który będzie kontrolował (i ew. poprawiał), czy wpisując do tabeli EMP imię oraz nazwisko pracownika pierwsza litera jest duża (przykładowo imię "Marek" powinno zostać opracował: dr inż. Artur Gramacki 4
przyjęte bez zmian a imię "marek" powinno zostać automatycznie skorygowane do postaci "Marek"). 4. Do tabeli DEPT dodać kolumnę NMBER_OF_EMP (poleceniem ALTER TABLE...). Zadaniem tej kolumny będzie przechowywanie informacji o aktualnej liczbie pracowników, którzy pracują w danym dziale. Napisać wyzwalacz, który będzie dbał o aktualność danych w tej kolumnie. Poniższe zapytanie wylicza liczbę pracowników w poszczególnych działach. tworzony wyzwalacz powinien automatycznie wyliczać tą liczbę i uaktualniać kolumnę NMBER_OF_EMP. Powinien on uruchamiać się w przypadku rejestrowania nowego pracownika, kasowania istniejącego pracownika lub też modyfikacji istniejącego. SELECT r.name "Region", d.name "Dział", count(*) "Liczba prac." FROM emp E, dept D, region R WHERE E.dept_id = D.id AND D.region_id = R.id GROP BY D.name, D.region_id, R.name; Region Dział Liczba prac. -------------------- ---------------- ------------ North America Administration 2 North America Finance 1 North America Operations 4 South America Operations 3 Africa / Middle East Operations 3 Asia Operations 2 Europe Operations 3 North America Sales 2 South America Sales 1 Africa / Middle East Sales 1 Asia Sales 2 Europe Sales 1 12 wierszy zostało wybranych. 5. Napisać wyzwalacz, który będzie sprawdzał, czy dane w tabelach ORD oraz ITEM są spójne. Jeżeli stwierdzona zostanie niespójność wyzwalacz powinien odpowiednio zmodyfikować wartość kolumny TOTAL w tabeli ORD. Zgodnie z logiką modelu SMMIT2 mamy, że kolumna ORD.TOTAL zawiera (powinna zawierać) sumaryczną wartość sprzedaży na danym zamówieniu. Jest ona ilościowo równa wartości wszystkich zamówionych produktów, których szczegóły zapisane są w tabeli ITEM (czyli jest to suma wszystkich iloczynów ITEM.PRICE * ITEM.QANTITY dla danego zamówienia). fragment tabel ORD <pk> <fk> fragment tabeli ITEM ITEM_ ORD_ TOTAL PRICE QANTITY opracował: dr inż. Artur Gramacki 5
6. Zaprojektować a następnie zaimplementować (korzystając z odpowiednio zbudowanego wyzwalacza) mechanizm pozwalający rejestrować wszelkie operacje (w praktyce chodzi o INSERT, PDATE, DELETE) wykonywane na tabeli EMP. Chodzi tu o rodzaj historii zmian zachodzących w tabeli EMP. Poniżej pokazano przykład działania opisanego wyżej mechanizmu dla bardzo prostej tabeli TEST. Na początek utwórzmy tą tabelę i wpiszmy do niej przykładowe dane: DROP TABLE test; CREATE TABLE test (id NMBER, kol VARCHAR2(10)); INSERT INTO test VALES (100, 'a'); INSERT INTO test VALES (101, 'b'); INSERT INTO test VALES (102, 'c'); SELECT * FROM test; Tabela TEST wygląda więc następująco: KOL 100 a 101 b 102 c Tabela TEST_HIST rejestrująca zmiany, które dokonują się w tabeli TEST niech wygląda jak poniżej: KOL _HIST TYP DATA 100 a 1001 09-09-2003 100 aa 1002 12-10-2003 100 aaa 1003 15-10-2003 103 d 1004 I 12-11-2003 101 b 1005 25-11-2003 101 bb 1006 D 29-11-2003 100 aaaa 1007 D 01-12-2003 Zawiera ona wszystkie kolumny tabeli TEST plus kilka kolumn "systemowych", które zaznaczono szarym kolorem. Widać, że znajduje się w niej 7 rekordów. Rekordy te zostały wstawione automatycznie przez odpowiednio zaprojektowany wyzwalacz. Są one efektem wykonania na tabeli TEST czterech operacji PDATE jednej operacji INSERT oraz dwóch operacji DELETE: PDATE test SET kol='aa' WHERE id='100'; PDATE test SET kol='aaa' WHERE id='100'; PDATE test SET kol='aaaa' WHERE id='100'; INSERT INTO test VALES (103, 'd'); PDATE test SET kol='bb' WHERE id='101'; DELETE FROM test WHERE id=101; DELETE FROM test WHERE id=100; SELECT * FROM test; opracował: dr inż. Artur Gramacki 6
Zwróćmy uwagę na kolumnę _HIST, która pozwala nam odtworzyć kolejność dokonywanych w tabeli TEST zmian (liczby te najwygodniej generować za pomocą sekwencji). Kolumna TYP przechowuje symbol wykonywanej operacji ( PDATE, I INSERT, D DELETE). Tabela TEST po wykonaniu powyższych operacji ma następującą zawartość: 102 c 103 d KOL Zwróćmy uwagę, że dane tabelę TEST_HIST można bardzo łatwo wykorzystać do wykonania operacji odtwarzających pierwotny stan tabeli TEST (ang. undo). W poniższej tabeli pokazano operacje, które należy wykonać aby powrócić do stanu pierwotnego: TYP D D I Operacja do wykonania INSERT INTO test VALES (100, 'aaaa'); INSERT INTO test VALES (101, 'bb'); PDATE test SET id=101, kol='b' WHERE id='101'; DELETE FROM test WHERE id='103'; PDATE test SET id=100, kol='aaa' WHERE id='100'; PDATE test SET id=100, kol='aa' WHERE id='100'; PDATE test SET id=100, kol='a' WHERE id='100'; Z kolei dzięki kolumnie DATA można zrealizować operację automatycznego odtwarzani stanu tabeli TEST do określonego punktu w przeszłości (ang. point in time recovery). Czy potrafiłbyś stworzyć procedurę PL/SQL która będzie to realizowała? Zastanów się, czy równie prosto można zrealizować problem odtwarzania do określonego punktu w przeszłości stanu wielu powiązanych ze sobą tabel (więzami integralności FOREIGN KEY)? opracował: dr inż. Artur Gramacki 7