PL/SQL v příkladech / Kompozitní datové typy
Stručný seriál, který krok po kroku představí konstrukce jazyka PL/SQL společnosti Oracle na jednoduchých příkladech. Předpokládá znalost základních principů procedurálního programování.

Pole

Problematika struktur, které jsou schopné pojmout více hodnot, je díky rozdělení databáze Oracle na SQL část a procedurální PL/SQL nadstavbu poněkud rozsáhlejší. Také jednotlivé verze databáze přináší drobné odlišnosti v použití daných struktur. Omezím se tedy jen na ukázku nejpoužívanějších typů, a to asociativního pole („index-by table“) deklarovaného v PL/SQL a vnořené tabulky („nested table“) deklarované na úrovni SQL.

Asociativní pole

PL/SQL nadstavba bohužel neobsahuje žádné předpřipravené typy pro pole čísel nebo řetězců, je tedy nutné pokaždé nejdřív provést deklaraci typu a až následně deklaraci vlastní proměnné (na použití typů deklarovaných v packagi se zaměřím v některém z dalších článků).

DECLARE
  -- asociativní pole čísel indexované celočíselně
  TYPE Number_TT IS TABLE OF NUMBER INDEX BY PLS_INTEGER;
  lt_Cislo Number_TT;
  -- asociativní pole slov indexované řetězcem
  TYPE Dictionary_TT IS TABLE OF VARCHAR2(4000) INDEX BY VARCHAR2(4000);
  lt_Slovnik Dictionary_TT;
BEGIN
  lt_Cislo(1):=NULL;
  lt_Cislo(2):=200;
  dbms_output.put_line(lt_Cislo(2)); -- 200
  -- počet prvků v poli
  dbms_output.put_line(lt_Cislo.COUNT); -- 2
  lt_Slovnik('prague'):='praha';
  lt_Slovnik('munich'):='mnichov';
  dbms_output.put_line(lt_Slovnik('prague')); -- praha
  -- existuje prvek na daném indexu?
  IF (lt_Slovnik.EXISTS('vienna')) THEN
    -- neexistuje, nic se nevypíše
    dbms_output.put_line(lt_Slovnik('vienna'));
  END IF;
  -- smazání pole
  lt_Slovnik.DELETE;
  dbms_output.put_line(lt_Slovnik.COUNT); -- 0
END;

Stejně jako u skalární proměnné, i do kompozitního typu lze přiřazovat hodnoty pomocí SQL dotazu. K tomu je určen příkaz BULK COLLECT INTO.

DECLARE
  TYPE Number_TT IS TABLE OF NUMBER INDEX BY PLS_INTEGER;
  lt_Employee_ID Number_TT;
BEGIN
  -- načtení všech zaměstnaneckých čísel seřazených vzestupně
  SELECT employee_id
    BULK COLLECT INTO
         lt_Employee_ID
    FROM employees
    ORDER BY employee_id ASC
  ;
  -- existuje alespoň jedno?
  IF (lt_Employee_ID.COUNT>0) THEN
    dbms_output.put_line(lt_Employee_ID(1)); -- 100
  END IF;
END;

Nested table

Dalším velmi používaným druhem pole je takzvaná nested-tabulka. Po deklaraci, která se provádí v SQL části, s ní může pracovat kdokoli s patřičnými právy. Není tedy omezena jen na konkrétní blok.

CREATE OR REPLACE TYPE Number_TT AS TABLE OF NUMBER;
/
DECLARE
  lt_Cislo Number_TT;
  lt_JineCislo Number_TT;
BEGIN
  -- inicializace 
  lt_Cislo:=Number_TT();
  -- alokace tří pozic
  lt_Cislo.EXTEND(3);
  -- naplnění
  lt_Cislo(1):=100;
  lt_Cislo(2):=200;
  lt_Cislo(3):=300;
  dbms_output.put_line(lt_Cislo.COUNT); -- 3
  -- naplnění rovnou při inicializaci
  lt_JineCislo:=Number_TT(100, 200, 300);
  dbms_output.put_line(lt_JineCislo.COUNT); -- 3
END;

Poměrně zdlouhavou práci s inicializací a alokací paměti, která u asociativního pole odpadá, vyvažuje výhoda v možnosti dotazování se pomocí jazyka SQL. Pole stačí obalit do klíčového slova TABLE(), k hodnotě se přistupuje přes VALUE().

DECLARE
  l_Soucet NUMBER;
  l_Prumer NUMBER;
  lt_Cislo Number_TT;
BEGIN
  lt_Cislo:=Number_TT(100, 200, 300);
  SELECT SUM(VALUE(t))
       , AVG(VALUE(t))
    INTO l_Soucet
       , l_Prumer
    FROM TABLE(lt_Cislo) t -- "t" slouží pouze jako alias
  ;
  dbms_output.put_line(l_Soucet); -- 600
  dbms_output.put_line(l_Prumer); -- 200
END;

Rekord

O něco pokročilejším kompozitním typem je rekord suplující klasickou strukturu, jakou známe z ostatních programovacích jazyků. Jde o proměnnou sdružující několik logicky souvisejících prvků.

DECLARE
  -- deklarace typu
  TYPE Employee_T IS RECORD(
        emp_id NUMBER
      , first_name VARCHAR2(20)
      , last_name VARCHAR2(20)
    );
  -- deklarace proměnné daného typu
  l_Employee Employee_T;
BEGIN
  l_Employee.emp_id:=100;
  l_Employee.first_name:='Steven';
  l_Employee.last_name:='King';
  dbms_output.put_line(l_Employee.last_name); -- King
END;

Datový typ rekord se hodí hlavně na dočasné uložení záznamu z tabulky. Pro načtení jednoho řádku se všemi sloupci tabulky slouží typ %ROWTYPE.

DECLARE
  TYPE Employee_T IS RECORD(
        emp_id employees.employee_id%TYPE
      , first_name employees.first_name%TYPE
      , last_name employees.last_name%TYPE
    );
  l_Employee Employee_T;
  l_Job jobs%ROWTYPE; -- typ řádek
BEGIN
  -- počet sloupců a typy se musí shodovat
  SELECT employee_id
       , first_name
       , last_name
    INTO l_Employee
    FROM employees
    WHERE (employee_id=100)
  ;
  dbms_output.put_line(l_Employee.last_name); -- King
  -- všechny sloupce
  SELECT *
    INTO l_Job
    FROM jobs
    WHERE (job_id='IT_PROG')
  ;
  dbms_output.put_line(l_Job.job_title); -- Programmer
END;

A na závěr dva příklady – asociativní pole rekordů a asociativní pole řádků tabulky.

DECLARE
  TYPE Employee_T IS RECORD(
        emp_id employees.employee_id%TYPE
      , first_name employees.first_name%TYPE
      , last_name employees.last_name%TYPE
    );
  -- pole rekordů
  TYPE Employee_TT IS TABLE OF Employee_T INDEX BY PLS_INTEGER;
  -- vlastní proměnná typu pole rekordů
  lt_Employee Employee_TT;
BEGIN
  -- jen vybrané sloupce
  SELECT employee_id
       , first_name
       , last_name
    BULK COLLECT INTO
         lt_Employee
    FROM employees
  ;
  dbms_output.put_line(lt_Employee.COUNT); -- 107
END;
DECLARE
  -- pole řádků
  TYPE Job_TT IS TABLE OF jobs%ROWTYPE INDEX BY PLS_INTEGER;
  -- vlastní proměnná typu pole řádků
  lt_Job Job_TT;
BEGIN
  -- všechny sloupce, všechny řádky
  SELECT *
    BULK COLLECT INTO
         lt_Job
    FROM jobs
  ;
  dbms_output.put_line(lt_Job.COUNT); -- 19
END;


ZANECHTE KOMENTÁŘ


Velké díky pro teslathemes