BD2_IO_K2

Transkrypt

BD2_IO_K2
Bazy danych 2 – Laboratorium 2
Język PL/SQL:
•
złożony język programowania, dzięki któremu mamy możliwość uzyskać dostęp do bazy
danych Oracle z różnych środowisk;
•
jest to język zintegrowany z serwerem bazy danych, co ma wpływ na szybkość i wydajność
jego przetwarzania;
•
cel zajęć, to zapoznanie z podstawami języka PL/SQL takimi, jak:
◦
bloki,
◦
zmienne,
◦
typy danych,
◦
wyrażenia i operatory,
◦
struktury sterujące,
◦
rekordy,
◦
styl programowania.
Dlaczego język PL/SQL?
•
Strukturalny język zapytań (SQL) pozwala na uzyskanie dostępu i korzystanie z relacyjnej
bazy danych.
•
SQL jest elastycznym i wydajnym językiem, pozwalającym na manipulowanie i
przetwarzanie danych w relacyjnej bazie danych, np.
◦
create table A (
A number(2) primary key,
B varchar2(20) default 'Ala',
C date default sysdate,
D char(3) NOT NULL check (D in ('TAK', 'NIE'))
);
•
◦
insert into A(A,D) values (1, 'TAK');
◦
select * from A where upper(B) like upper('%r%');
◦
delete from A where A between 2 and 4;
◦
update A set B = B || 'owa' where D = 'NIE';
SQL to:
◦
język czwartej generacji, tzn. że opisuje żądane zadanie, a nie sposób jego wykonania;
◦
jest prosty i udostępnia mniejszą liczbę poleceń;
◦
izoluje również użytkownika od wewnętrznych struktur danych i algorytmów;
1
Bazy danych 2 – Laboratorium 2
•
PL/SQL (Procedural Language SQL) łączy możliwości i elastyczność SQL z konstrukcjami
proceduralnymi języka trzeciej generacji:
◦
zmienne i typy danych (predefiniowane i definiowane przez użytkownika);
◦
struktury sterowania, np. instrukcja I F – THEN - ELSE i pętle.
◦
procedury i funkcje;
◦
typy obiektów i metody.
ORACLE 11 g
•
http://www.oracle.com/technetwork/database/enterprise-edition/downloads/index.html
•
system jest zgodny z normą ANSI (American National Standard Institute) języka SQL
(SQL99 lub SQL2);
•
norma SQL99 nie definiuje rozszerzeń języka trzeciej generacji udostępnionych przez
PL/SQL, ale system ORACLE 9i i wyżej jest zgodny z większością właściwości
wymaganych przez część CORE tego standardu.
BLOK PL/SQL
•
Podstawowa jednostka programu PL/SQL.
•
Może być anonimowy (tworzone dynamicznie i wykonywane jednorazowo) i nazwane takie,
jak:
◦
bloki oznaczone etykietą – to bloki anonimowe, którym przydzielono etykietę, więc są
tworzone dynamicznie i wykonywane jednorazowo. Pozwala to na odwoływanie się do
zmiennych tego bloku;
◦
podprogramy – procedury i funkcje, są składowane w bazie danych i wykonywane są
jawnie przez odwołanie do nich;
◦
wyzwalacze – są to bloki PL/SQL skojarzone ze zdarzeniami zachodzącymi w bazie
danych. Są składowane w bazie danej i mogą być wielokrotnie, niejawnie wywoływane.
2
Bazy danych 2 – Laboratorium 2
Ogólna struktura programu-bloku jest następująca:
[DECLARE
/* deklaracja zmiennych, stałych, kursorów, wyjątków w ramach bloku */
sekcja deklaracji]
BEGIN
sekcja instrukcji/wykonania
[EXCEPTION
sekcja obsługi wyjątków]
END;
Struktura programu PL/SQL umożliwia zagnieżdżanie bloków (ang. nested blocks).
-- początek głównego bloku
[DECLARE
/* deklaracja zmiennych, stałych, kursorów, wyjątków w ramach głównego
bloku */
sekcja deklaracji]
BEGIN
sekcja instrukcji
-- początek podbloku
[DECLARE
/* deklaracja zmiennych, stałych, kursorów, wyjątków w ramach
podbloku */
sekcja deklaracji]
[BEGIN
...sekcja instrukcji
[EXCEPTION
...sekcja obsługi wyjątków]
END;]
-- koniec podbloku
[EXCEPTION
...sekcja obsługi wyjątków]
EBD;
-- koniec głównego bloku
3
Bazy danych 2 – Laboratorium 2
Zmienne, stałe, kursory i wyjątki są pewnymi obiektami programu. Każdy z obiektów
zadeklarowany w bloku jest traktowany jako obiekt lokalny, tzn. nie jest dostępny w żadnym z jego
bloków nadrzędnych. Jest natomiast dostępny we wszystkich blokach podrzędnych, tzn. jest on
globalny dla wszystkich podbloków. Dostępność obiektu w innych blokach jest określana mianem
tzw. zakresu obiektu. W tym samym bloku może istnieć tylko jeden obiekt o danej nazwie.
Natomiast w bloku podrzędnym można zdefiniować obiekt o takiej samej nazwie, jaki istnieje
w bloku nadrzędnym.
W programie PL/SQL mogą również występować komentarze:
--
- komentarz jedno-linijkowy;
/*...*/ - komentarz wielo-linijkowy.
Jednostki leksykalne:
•
małe i duże litery alfabetu: a..z i A..Z;
•
cyfry: 0-9;
•
znaki odstępu: tabulacja, spacja, znak nowego wiersza;
•
symbole matematyczne: +, -, *, /, <, >, =;
•
znaki interpunkcyjne: ( ) { } [ ] ? ! ~ ; : . ' '' @ # $ ^ & _ |.
Identyfikatory:
•
MUSZĄ zaczynać się od litery, po której może wystąpić dowolna sekwencja znaków
złożona z liter, cyfr, znaków dolara ($), znaków podkreślenia (_) i znaków # (i tylko
takich!).
•
NIE MOGĄ być dłuższe niż 30 znaków.
•
Identyfikatory NIE MOGĄ brzmieć tak samo jak słowa zarezerwowane (np. DATE,
BEGIN).
•
W języku PL/SQL duże i małe litery nie są rozróżniane.
•
Jeżeli w identyfikatorze mają być rozróżniane duże i małe litery, i ma on zwierać spacje lub
słowa kluczowe, to identyfikator ujmujemy w cudzysłów.
4
Bazy danych 2 – Laboratorium 2
Ograniczniki
To symbole, które mają szczególne znaczenie i są wykorzystywane do oddzielania identyfikatorów
od siebie:
Symbol
Opis
Symbol
Opis
+
Operator dodawania
-
Operator odejmowania
*
Operator mnożenia
/
Operator dzielenia
=
Operator równości
<
Operator mniejszości
>
Operator większości
(
Początkowy ogranicznik wyrażenia
)
Końcowy ogranicznik wyrażenia
;
Znak końca instrukcji
%
Wskaźnik atrybutu
,
Separator elementów
.
Wskaźnik składnika
@
Wskaźnik łącza bazy danych
'
Ogranicznik ciągu znaków
''
Ogranicznik cytowanego ciągu znaków
:
Wskaźnik zmiennej związanej
**
Operator potęgowania
<>
Operator nierówności
!=
Operator nierówności (równoważny z <>)
~=
Operator nierówności (równoważny z !=)
^=
Operator nierówności (równoważny z ~=)
<=
Operator „mniejszy lub równy”
>=
Operator „większy lub równy”
:=
Operator przypisania
=>
Operator skojarzenia
..
Operator zakresu
||
Operator konkatenacji ciągu znaków
<<
Początkowy ogranicznik etykiety
>>
Końcowy ogranicznik etykiety
--
Wskaźnik komentarza jednowierszowego
/*
Początkowy wskaźnik komentarza
wielowierszowego
*/
Końcowy wskaźnik komentarza wielowierszowego
<spacja> Znak spacji
<tab>
Znak tabulatora
<cr>
Znak powrotu do nowego wiersza
Literały
To wartość znakowa, numeryczna lub logiczna, która nie jest identyfikatorem.
•
Znakowe – bez konwersji mogą być przypisywane zmiennym typu CHAR i VARCHAR2,
np. '12345', 'Ala', '100%', 'Sto lat temu', ' ' ' ', ' ' - pusty ciąg znaków identyczny z NULL.
•
Numeryczne – reprezentują wartości liczb całkowitych lub rzeczywistych, bez konwersji
mogą być przypisywane zmiennym typu NUMBER, np.: 123, 7, -12, 0, -17.1, 23.0.
•
Logiczne: TRUE, FALSE lub NULL.
5
Bazy danych 2 – Laboratorium 2
Sekcja DECLARE – składnia.
DECLARE
nazwa_zmiennej
typ_zmiennej [długość]
[CONSTANT]
[:= | DEFAULT wartość_domyślna]
[NOT NULL];
UWAGA: Zmienna zadeklarowana, lecz nie zainicjowana posiada wartość NULL.
Przykład 1 Deklarowanie zmiennych i stałych – przykłady.
Poniższe trzy deklaracje są równoważne.
licznik NUMBER(4);
licznik NUMBER(4) := NULL;
licznik NUMBER(4) DEFAULT NULL;
Nadanie wartości zmiennym przez przypisanie.
ilosc NUMBER(2) := 50;
Można też tak:
ilosc_2 NUMBER(2) DEFAULT 100;
Literały (w tym daty) ujmujemy w apostrofy.
imie VARCHAR2(25) DEFAULT 'Artur';
czy_instalowac CHAR(3) := 'TAK';
data_domyslna DATE := '01-01-2000';
Wartość domyślna SYSDATE zwraca bieżącą datę oraz czas systemowy z dokładnością do jednej
sekundy
data_zatr DATE DEFAULT SYSDATE NOT NULL;
Typ logiczny. Uwaga: język SQL nie posiada takiego typu.
flaga BOOLEAN := FALSE;
Stałe. Poniżej zadeklarowanych stałych NIE MOŻNA zmieniać w programie. Jest to wygodny
sposób na unikanie prostych błędów.
6
Bazy danych 2 – Laboratorium 2
grudzien CONSTANT NUMBER(2) := 31;
uczelnia CONSTANT VARCHAR2(100) := 'EWSIE';
drobinka CONSTANT NUMBER := 0.000001;
Typy danych.
Uwaga: typy danych w PL/SQL nie odpowiadają dokładnie analogicznym typom w SQL.
NUMERYCZNE
NUMBER [ ( p [ ,s] ) ]
p maksymalnie o wartości 38, s wartość z przedziału -84 do 127. Ujemna wartość s oznacza
zaokrąglenie wartości do określonej liczby miejsc z lewej strony kropki dziesiętnej.
-- przykłady: NUMBER, NUMBER(10), NUMBER(10,3)
DEC, DECIMAL, DOUBLE PRECISION, FLOAT, NUMERIC, REAL.
BINARY_INTEGER
od
-2147483647 do
2147483647
NATURA
od
0
do
2147483647
NATURALN
od
0
do
2147483647 NOT NULL
POSITIVE
od
1
do
2147483647
POSITIVEN
od
1
do
2147483647 NOT NULL
SIGNTYPE
od
-1, 0, 1
PLS_INTEGER
od
-2147483647 do
2147483647
-- Wydajniejsza niż INTEGER i NUMBER,
ZNAKOWE
VARCHAR2 (L [CHAR | BYTE])
od
1
do
32767 bajtów
-- Domyślnie CHAR
-- Uwaga: w tabelach ten typ może mieć maksymalnie 4000 bajtów.
VARCHAR (L)
-- Nie zaleca się stosować. Używać raczej VARCHAR2.
CHAR (L [CHAR | BYTE])
od
1
do
32767 bajtów
-- Typ o stałej długości. Niedobór uzupełniany spacjami.
NCHAR, NVCHAR2
7
Bazy danych 2 – Laboratorium 2
-- narodowy zestaw znaków
DATE
(wiek, rok, miesiąc, dzień, godzina, minuta, sekunda)
7 bajtów
-- Data i godzina, dokładność do 1 sek.
TIMESTAMP [(P)] (rok, miesiąc, dzień, godzina, minuta, sekunda)
-- P dokładność pola sekundy (wartością domyślną jest 6)
INTERVAL YEAR [(P)] TO MONTH
przechowuje dane o odcinku czasu pomiędzy
dwoma punktami; liczbę lat i miesięcy
-- P liczba cyfr pola roku (wartością domyślną jest 2)
INTERVAL DAY [(DP)] TO SECOND[(SP)]
liczbę dni i sekund
-- DP liczba cyfr pola dnia (wartością domyślną jest 2)
-- SP liczba cyfr pola sekund (wartością domyślną jest 6)
ROWID
przechowuje identyfikator wiersza rowid – unikalny klucz każdego
wiersza bazy danych. Wartości binarne o stałej długości zależnej od
systemu operacyjnego.
UROWID
logiczne lub binarne identyfikatory wierszy.
BOOLEAN
-- Może przyjmować wartości: TRUE, FALSE, NULL.
RECORD
TABLE
VARRAYS
REF CURSOR
BFILE
LOB
BLOB
CLOB
NCLOB
8
Bazy danych 2 – Laboratorium 2
Przykład 2
Program "nic nie robiący".
BEGIN
null;
END;
Przykład 3
Program "Hello, world".
BEGIN
DBMS_OUTPUT.PUT_LINE('Hello, world');
END;
Aby zobaczyć wynik na ekranie należy ustawić wartość zmiennej SET SERVEROUTPUT na ON
Przy wpisywaniu dłuższych programów wygodniej jest ich kod zapisywać do pliku tekstowego i
uruchamiać wydając polecenie START lub @ (jak poniżej). Oba polecenia są prawie równoważne
sobie (szczegóły patrz dokumentacja do programu SQL*Plus).
SQL> @c:\temp\p1.txt
SQL> START c:\temp\p1.txt
UWAGA: Jeśli pojawi się komunikat „Input truncated to 1 characters”, można się go pozbyć
dodając na końcu jedną pustą linię (po znaku ukośnika).
Wpisanie znaku ukośnika powoduje wykonanie ostatniego bloku PL/SQL, który jest
przechowywany w buforze.
SQL> /
Hello, world
PL/SQL procedure successfully completed.
SQL>
9
Bazy danych 2 – Laboratorium 2
Polecenie run działa podobnie do / (ukośnik), z tą różnicą, że wykonywane polecenie jest
listowane.
SQL> RUN
1 BEGIN
2 DBMS_OUTPUT.PUT_LINE('Hello, world');
3* END;
Hello, world
PL/SQL procedure successfully completed.
Polecenie execute pozwala wykonać pojedyncze polecenie PL/SQL. Pełną nazwę polecenia można
skrócić do EXEC.
SQL> EXECUTE DBMS_OUTPUT.PUT_LINE('Hello, world');
Hello, world
PL/SQL procedure successfully completed.
Trzeba uważać, na zmienną LINESIZE (bo mogą pojawić się mylące wyniki).
SQL> SET LINESIZE 5
SQL> EXECUTE DBMS_OUTPUT.PUT_LINE('HHHHeeeellllllo')
HHHHe
eeell
llllo
PL/SQL procedure successfully completed.
Polecenie LIST przytacza zawartość bufora. Wydane z parametrem lub parametrami wyświetla
tylko wskazaną linię lub zakres linii. Gwiazdka wskazuje linię bieżącą.
SQL> LIST
1 BEGIN
2 DBMS_OUTPUT.PUT_LINE('HHHHeeeellllllo');
3* END;
SQL>
SQL> LIST 2
2* DBMS_OUTPUT.PUT_LINE('Hello, world');
SQL> LIST 1
1* BEGIN
10
Bazy danych 2 – Laboratorium 2
SQL>
SQL> LSIT 2 3
2 DBMS_OUTPUT.PUT_LINE('Hello, world');
3* END;
Kod programu zawiera błąd składniowy (usunięto średnik kończący polecenie w drugiej linii).
SQL> @'e:\p1.txt'
END;
*
ERROR at line 3:
ORA-06550: line 3, column 1:
PLS-00103: Encountered the symbol "END" when expecting one of
the following: := . ( % ;
The symbol ";" was substituted for "END" to continue.
ZMIENNE TYPU ZŁOŻONEGO:
–
zmienna typu rekordowego składa się z wielu zmiennych elementarnych, zwanych polami.
Deklarujemy ją na dwa sposoby:
–
przy użyciu pseudoatrubutu %ROWTYPE – umożliwia deklarowanie zmiennych
typu rekordowego, których struktura jest zgodna ze strukturą wiersza tabeli lub
wiersza wyznaczonego przez tzw. kursor.
pracownik_rekord
pracownik%ROWTYPE;
pracownik_rekord jest zmienną o strukturze odpowiadającej strukturze tabeli pracownik. Innymi
słowy zmienna rekordowa składa się z pól odpowiadających kolumnom tabeli pracownik. Możemy
odwołać się do dowolnego pola tej zmiennej poprzez użycie '.':
pracownik_rekord.nazwisko;
–
przy użyciu złożonego typu danych RECORD – umożliwia deklarowanie zmiennej
o dowolnej strukturze. Najpierw musimy zadeklarować typ rekordowy, by
deklarować zmienne tego typu.
–
11
Bazy danych 2 – Laboratorium 2
DECLARE
TYPE pracownik_rekord1 IS RECORD (
nazwisko pracownik.nazwisko%TYPE,
pensja number(6,2) not null :=900
);
gdzie %TYPE oznacza pseudoatrybut określający typ zmiennej zgodny z typem
kolumny nazwisko w tabeli pracownik.
pracownik1 pracownik_rekord1;
- typ tablicowy – struktura dwukolumnowej macierzy, w której pierwsza kolumna jest
wykorzystywana do indeksowania wierszy tablicy i musi być typu binary_integer; druga kolumna
może być zadeklarowana z wykorzystaniem jednego z predefiniowanych typów prostych, np. char,
number, date. Liczba wierszy tablicy jest praktycznie nieograniczona.
DECLARE
TYPE nazwa_typu_tablicowego IS TABLE OF
typ_kolumny [NOT NULL]
INDEX BY binary_integer;
zmienna_tablicowa nazwa_typu_tablicowego;
zmienna_tablicowa(numer_komórki); --wartość znajdująca się w tablicy na pozycji numer_komórki
Przykład.4
declare
type tablica is table of number index by binary_integer;
tab_liczb tablica;
begin
tab_liczb(0) := 2; -- odwołanie do 0 elementu tablicy
dbms_output.put_line(tab_liczb(0));
tab_liczb(1) := 2 * tab_liczb(0);
dbms_output.put_line(tab_liczb(1));
tab_liczb(2) := 3*tab_liczb(1)-tab_liczb(0);
dbms_output.put_line(tab_liczb(2));
end;
/
12
Bazy danych 2 – Laboratorium 2
INSTRUKCJE STERUJĄCE
–
instrukcja warunkowa
IF warunek1 THEN instrukcje;
ELSIF warunek2 THEN instrukcje;
ELSE instrukcje;
END IF;
warunek ma składnię:
zmienna operator wartość
Operator: >,<,=,!=, <=,>=, and, or, not, is null, like, between...and..., in, is not null, not like, not
between... and..., not in.
Wartość warunku: true, false, null.
Przykład.5
DECLARE
i NUMBER := 3;
BEGIN
IF i > 2 THEN DBMS_OUTPUT.PUT_LINE(‘Wartosc zmiennej wieksza od 2’);
ELSIF i < 2 THEN DBMS_OUTPUT.PUT_LINE(‘Wartosc zmiennej mniejsza od 2’);
ELSE DBMS_OUTPUT.PUT_LINE(‘Wartosc zmiennej wynosi: ‘||i);
END IF;
END;
/
–
instrukcja case
CASE zmienna
WHEN wartosc THEN instrukcje;
...
ELSE instrukcje;
END CASE;
13
Bazy danych 2 – Laboratorium 2
Jeśli chcemy wprowadzić wartość z klawiatury (interaktywnie) musimy użyć w tym celu deklaracji
zmiennej lokalnej z użyciem &, a następnie wykorzystać wprowadzoną wartość do podstawienia
pod zmienną w bloku.
Przykład 6
DECLARE
grade CHAR(1);
BEGIN
grade := '&grade';
CASE grade
WHEN 'A' THEN DBMS_OUTPUT.PUT_LINE('Excellent');
WHEN 'B' THEN DBMS_OUTPUT.PUT_LINE('Very Good');
WHEN 'C' THEN DBMS_OUTPUT.PUT_LINE('Good');
WHEN 'D' THEN DBMS_OUTPUT.PUT_LINE('Fair');
WHEN 'F' THEN DBMS_OUTPUT.PUT_LINE('Poor');
ELSE DBMS_OUTPUT.PUT_LINE('No such grade');
END CASE;
END;
/
–
pętla podstawowa
LOOP
INSTRUKCJE;
EXIT WHEN warunek; -–wyjście z pętli
IF warunek THEN exit;
END LOOP;
Wyjście z pętli możliwe jest również poprzez użycie poleceń: goto lub raise.
–
pętla while
WHILE warunek LOOP
instrukcje;
-- dopóki spełniony warunek
-- wykonuj polecenia-instrukcje
END LOOP;
14
Bazy danych 2 – Laboratorium 2
–
pętla numeryczna for
FOR zmienna_licznikowa IN x..y LOOP
instrukcje;
END LOOP;
Polecenia pętli wykonywane są y razy. Przed pierwszą iteracją zmienna_licznikowa przyjmuje
wartość x. Po kolejnych iteracjach pętli wartość zmiennej_licznikowej jest zwiększana o 1
(domyślnie). Polecenia wykonywane są dopóki wartość tej zmiennej jest mniejsza równa y.
Klauzula reverse odwraca kierunek iteracji.
Przykład 7
BEGIN
- - Zmienna iteracyjna NIE MUSI być wcześniej deklarowana ani inicjowana.
FOR i IN 1..5 LOOP
DBMS_OUTPUT.PUT_LINE(‘Wartosc zmiennej wynosi: ‘||TO_CHAR(i));
END LOOP;
- - Uwaga na słowo kluczowe REVERSE. Odwraca ono kierunek iteracji.
FOR i IN REVERSE 1..5 LOOP
DBMS_OUTPUT.PUT_LINE(‘Wartosc zmiennej wynosi: ‘||TO_CHAR(i));
END LOOP;
- - Do wcześniejszego wyjścia z pętli można użyć polecenia EXIT
FOR i IN 1..5 LOOP
DBMS_OUTPUT.PUT_LINE(‘Wartosc zmiennej wynosi: ‘||TO_CHAR(i));
EXIT WHEN i = 3;
END LOOP;
- - Pętla nie wykona się ani razu.
FOR i IN 6..5 LOOP
DBMS_OUTPUT.PUT_LINE(‘Wartosc zmiennej wynosi: ‘||TO_CHAR(i));
END LOOP;
END;
/
Z pętlami można wiązać etykiety, umożliwiające ich identyfikację, opuszczanie i testowanie
wartości zawartych w nich zmiennych iteracyjnych.
15
Bazy danych 2 – Laboratorium 2
Przykład 8
BEGIN
<<petla1>> -- etykieta pętli
for i in 1..10 loop
<<petla2>>
for j in 1..20 loop
if petla1.i > 5 then dbms_output.put_line('i: '||i);
else dbms_output.put_line('j: '||j);
end if;
exit petla1 when i=7;
end loop petla2;
end loop petla2;
END;
/
INSTRUKCJA SKOKU – zmienia kolejność wykonywania instrukcji oraz pozwala opuścić pętle.
GOTO <etykieta>
Przykład 9
DECLARE
tekst VARCHAR2(100);
BEGIN
-- Etykieta musi poprzedzać polecenie wykonywane. GOTO nie może przeskakiwać
-- warunkowych części poleceń. IF-THEN-ELSE, do polecenia LOOP i do bloku podrzędnego.
<<pierwszy>>
tekst := 'Ala ';
GOTO drugi;
<<wyswietl>>
DBMS_OUTPUT.PUT_LINE(tekst);
GOTO koniec;
<<drugi>>
tekst := tekst||'ma ';
GOTO trzeci;
<<trzeci>>
tekst := tekst||'kota.';
GOTO wyswietl;
<<koniec>>
NULL;
-- Polecenie NULL nie wykonuje żadnej akcji.
END;
/
16
Bazy danych 2 – Laboratorium 2
Przykład 10
BEGIN
for i in 1..&n loop
dbms_output.put_line(i);
end loop;
END;
/
Przykład 11
DECLARE
n integer;
BEGIN
n:=&n;
for i in 1..n loop
dbms_output.put_line(i);
end loop;
END;
/
Przykład 12
DECLARE
tekst varchar(20);
BEGIN
tekst:=&tekst; -- Wprowadzany tekst z klawiatury musi być ujęty w
-- apostrofy ''. Inne rozwiązanie to: tekst:='&tekst';
-- Również zapis daty wymaga apostrofów.
for i in 1..4 loop
dbms_output.put_line(tekst ||' ' || i);
end loop;
END;
/
17