Kollekciók kezelése

BINARY_INTEGER típusú indexekkel rendelkező asszociatív tömb indexeinek maximális tartományát ezen típus tartománya határozza meg: –231..231.

Beágyazott tábla és dinamikus tömb esetén az indexek lehetséges maximális felső határa 231.

Asszociatív tömbök esetén egy i indexű elemnek történő értékadás létrehozza az adott elemet, ha eddig még nem létezett, illetve felülírja annak értékét, ha az már létezik. Beágyazott tábla és dinamikus tömb esetén biztosítani kell, hogy az adott indexű elem létezzen az értékadás előtt. Az i indexű elemre való hivatkozás csak a létrehozása után lehetséges, különben a NO_DATA_FOUND kivétel váltódik ki.

1. példa (Az előző példa folytatása)

  ⋮
  -- inicializálatlan elemre hivatkozás
  <<blokk2>>
  BEGIN
    v_Szam := v_Matrix(20)(20);
  EXCEPTION
    WHEN NO_DATA_FOUND THEN
      DBMS_OUTPUT.PUT_LINE('Kivétel blokk2-ben: ' || SQLERRM);
  END blokk2;
  ⋮

A beágyazott tábla és a dinamikus tömb speciális objektumtípusok (az objektumtípusok részletes tárgyalását lásd a 14. fejezetben). Amikor egy ilyen kollekciót deklarálunk, tulajdonképpen egy referencia típusú változó jön létre, amelynek automatikus kezdőértéke NULL. A kollekciót explicit módon inicializálni az objektumtípusnak megfelelően példányosítással (lásd 14. fejezet) lehet. A példányosításhoz az adott típus konstruktorát kell meghívni. A konstruktor egy rendszer által létrehozott függvény, amelynek neve megegyezik a típus nevével, paramétereinek száma tetszőleges, paraméterei típusának a megfelelő kollekciótípus elemeinek típusával kompatibilisnek kell lennie. A dinamikus tömb konstruktorának maximum annyi paraméter adható meg, amennyi a deklarált maximális elemszám.

A konstruktor meghívható paraméterek nélkül, ekkor egy üres kollekció jön létre. Javasoljuk, hogy a kollekció deklarálásakor hajtsunk végre explicit inicializálást, itt hívjuk meg a konstruktort.

Ha a beágyazott tábla és a dinamikus tömb kollekcióknál nem létező elemre hivatkozunk, akkor a SUBSCRIPT_BEYOND_COUNT kivétel, ha az index a LIMIT-nél nagyobb vagy nem pozitív szám, akkor SUBSCRIPT_OUTSIDE_LIMIT kivétel váltódik ki.

2. példa

  ⋮
  v_Szerzo := v_Szerzok_ab(2); -- Létező elem, értéke NULL
  <<blokk3>>
  BEGIN
    v_Tetel := v_Konyvlista_I(3); -- Nem létező elem
  EXCEPTION
    WHEN SUBSCRIPT_BEYOND_COUNT THEN
      DBMS_OUTPUT.PUT_LINE('Kivétel blokk3-ban: ' || SQLERRM);
  END blokk3;
  <<blokk4>>
  BEGIN
    -- t_konyvlista dinamikus tömb maximális mérete 10
    v_Tetel := v_Konyvlista_I(20); -- A maximális méreten túl hivatkozunk
  EXCEPTION
    WHEN SUBSCRIPT_OUTSIDE_LIMIT THEN
      DBMS_OUTPUT.PUT_LINE('Kivétel blokk4-ben: ' || SQLERRM);
  END blokk4;
  ⋮

Ha az index NULL, vagy nem konvertálható a kulcs típusára, akár a kulcs típusának korlátozása miatt, akkor a VALUE_ERROR kivétel következik be. Ha beágyazott tábla és dinamikus tömb kollekciók esetén nem inicializált kollekcióra hivatkozunk, a COLLECTION_IS_NULL kivétel következik be.

Beágyazott tábla és dinamikus tömb NULL értéke tesztelhető. Beágyazott táblák egyenlősége is vizsgálható akkor, ha azonos típusúak és az elemek is összehasonlíthatók egyenlőség szerint. Rendezettségre nézve még a beágyazott táblák sem hasonlíthatók össze.

3. példa

  ⋮
  /* Kivétel NULL kollekció esetén */
  <<blokk5>>
  BEGIN
    -- v_Konyvlista_N nem volt explicite inicializálva, értéke NULL.
    v_Tetel := v_Konyvlista_N(1);
  EXCEPTION
    WHEN COLLECTION_IS_NULL THEN
      DBMS_OUTPUT.PUT_LINE('Kivétel blokk5-ben: ' || SQLERRM);
  END blokk5;
  /* Nem asszociatív tömb kollekciók NULL tesztelése lehetséges */
  IF v_Konyvlista_N IS NULL THEN
    DBMS_OUTPUT.PUT_LINE('v_Konyvlista_N null volt.');
  END IF;
  IF v_Konyvlista_I IS NOT NULL THEN
    DBMS_OUTPUT.PUT_LINE('v_Konyvlista_I nem volt null.');
  END IF;
  /* Csak beágyazott táblák egyenlősége vizsgálható, és csak akkor,
  ha az elemeik is összehasonlíthatók. */
  DECLARE
    TYPE t_vektor_bt IS TABLE OF NUMBER;
    v_Vektor_bt t_vektor_bt := t_vektor_bt(1,2,3);
  BEGIN
    IF v_Vektor_bt = v_Vektor_bt THEN
      DBMS_OUTPUT.PUT_LINE('Egyenlőség...');
    END IF;
  END;
  /* A rekordot tartalmazó beágyazott tábla és bármilyen elemű dinamikus
  tömb vagy asszociatív tömb egyenlőségvizsgálata fordítási hibát
  eredményezne. Például ez is:
  IF v_Matrix(1) = v_Vektor THEN
    DBMS_OUTPUT.PUT_LINE('Egyenlőség...');
  END IF;
  */
END;
/

Az előző példák blokkjának futási eredménye:
15
10
ISBN: "ISBN 963 8453 09 5", id: 10
Kivétel blokk2-ben: ORA-01403: nincs adat
Kivétel blokk3-ban: ORA-06533: Számlálón kívüli index érték
Kivétel blokk4-ben: ORA-06532: Határon kívüli index
Kivétel blokk5-ben: ORA-06531: Inicializálatlan gyűjtőre való hivatkozás
v_Konyvlista_N null volt.
v_Konyvlista_I nem volt null.
Egyenlőség ...

A PL/SQL eljárás sikeresen befejeződött.

Csak beágyazott tábláknál alkalmazhatók az SQL nyelv kollekciót kezelő operátorai és függvényei.

Logikai operátorok: IS [NOT] A SET, IS [NOT] EMPTY, MEMBER, SUBMULTISET.

Kollekció operátorok: MULTISET EXCEPT [{ALL|DISTINCT}], MULTISET INTERSECT [{ALL|DISTINCT}], MULTISET UNION.

PL/SQL-ben is használható kollekció függvények: CARDINALITY, SET.

A COLLECT, POWERMULTISET és POWERMULTISET_BY_CARDINALITY kollekciófüggvények, a DECODE-hoz hasonlóan, PL/SQL-ben közvetlenül nem használhatók.

A következő példa ezek használatát szemlélteti PL/SQL-ben. Részletes leírásukat lásd [8].

4. példa

/*  Az egyes műveletek mögött láthatók az eredmények.
  Ezek ellenőrzéséhez az SQL*Developer vagy más IDE debuggerét javasoljuk.
  Ehhez szükséges a DEBUG CONNECT SESSION jogosultság.
  Megoldás lenne még az eredmények köztes kiíratása is.*/
-- Néhány példához adatbázisban kell létrehozni típust
CREATE TYPE T_Multiset_ab IS
  TABLE OF CHAR(1)
/
CREATE TYPE T_Multiset_multiset_ab IS
  TABLE OF T_Multiset_ab;
/
ALTER SESSION SET plsql_debug=true;
CREATE OR REPLACE PROCEDURE proc_multiset_op_fv_teszt IS
  TYPE t_multiset_plsql IS TABLE OF CHAR(1);
  -- t_multiset változók - fő operandusok
  v_Ures t_multiset_plsql := t_multiset_plsql();
  v_abc t_multiset_plsql := t_multiset_plsql('a','b','c');
  v_abca t_multiset_plsql := t_multiset_plsql('a','b','c','a');
  v_abc_nullal t_multiset_plsql := t_multiset_plsql('a','b','c',NULL);
  v_aaabbcdee t_multiset_plsql :=
  t_multiset_plsql('a','a','a','b','b','c','d','e','e');
  v_ccdd t_multiset_plsql := t_multiset_plsql('c','c','d','d');
  v_abc_ab T_Multiset_ab := T_Multiset_ab('a','b','c');
  -- eredménytárolók
  b BOOLEAN;
  m t_multiset_plsql;
  i BINARY_INTEGER;
  mm T_Multiset_multiset_ab;
  -- segédeljárás: az adatbázisbeli típusú paraméter tartalmát
  -- a lokális típusú paraméterbe másolja, mert a debugger
  -- csak a lokális típusú változókba tud belenézni
  PROCEDURE convert_to_plsql(p_From T_Multiset_ab, p_To IN OUT NOCOPY t_multiset_plsql) IS
    j BINARY_INTEGER;
  BEGIN
    p_To.DELETE;
    p_To.EXTEND(p_From.COUNT);
    FOR i IN 1..p_From.COUNT
    LOOP
      p_To(i) := p_From(i);
    END LOOP;
  END convert_to_plsql;
BEGIN
  /* Logikai kifejezések */
  -- IS [NOT] A SET
  b := v_abc IS A SET; -- TRUE;
  b := v_abca IS A SET; -- FALSE;
  -- IS [NOT] EMPTY
  b := v_abc IS NOT EMPTY; -- TRUE;
  b := v_Ures IS NOT EMPTY; -- FALSE;
  -- MEMBER
  b := 'a' MEMBER v_abc; -- TRUE;
  b := 'z' MEMBER v_abc; -- FALSE;
  b := NULL MEMBER v_abc; -- NULL;
  b := 'a' MEMBER v_abc_nullal; -- TRUE;
  b := 'z' MEMBER v_abc_nullal; -- NULL;
  b := NULL MEMBER v_abc_nullal; -- NULL;
  -- SUBMULTISET
  b := v_Ures SUBMULTISET v_abc; -- TRUE;
  b := v_abc SUBMULTISET v_abca; -- TRUE;
  b := v_abca SUBMULTISET v_abc; -- FALSE;
  /* Kollekció kifejezések */
  -- MULTISET {EXCEPT|INTERSECT|UNION} [{ALL|DISTINCT}] operátorok
  m := v_abca MULTISET EXCEPT v_ccdd; -- {a,b,a}
  m := v_aaabbcdee MULTISET EXCEPT v_abca; -- {a,b,d,e,e}
  m := v_aaabbcdee MULTISET EXCEPT DISTINCT v_abca; -- {d,e}
  m := v_aaabbcdee MULTISET INTERSECT v_abca; -- {a,a,b,c}
  m := v_aaabbcdee MULTISET INTERSECT DISTINCT v_abca; -- {a,b,c}
  m := v_abca MULTISET UNION v_ccdd; -- {a,b,c,a,c,c,d,d}
  m := v_abca MULTISET UNION DISTINCT v_ccdd; -- {a,b,c,d}
  /* PL/SQL-ben közvetlenül is alkalmazható kollekció függvények */
  -- CARDINALITY, vesd össze a COUNT metódussal
  i := CARDINALITY(v_abc); -- 3
  i := v_abc.COUNT; -- 3
  i := CARDINALITY(v_Ures); -- 0
  i := v_Ures.COUNT; -- 0
  -- SET
  m := SET(v_abca); -- {a,b,c}
  b := v_abc = SET(v_abca); -- TRUE;
  /* PL/SQL-ben közvetlenül nem alkalmazható kollekció függvények */
  -- COLLECT
  FOR r IN (
  SELECT grp, CAST(COLLECT(col) AS T_Multiset_ab) collected
  FROM (
    SELECT 1 grp, 'a' col FROM dual UNION ALL
    SELECT 1 grp, 'b' col FROM dual UNION ALL
    SELECT 2 grp, 'c' col FROM dual
  )
  GROUP BY grp
  ) LOOP
    i := r.grp; -- 1 majd 2
    -- debuggerrel tudjuk vizsgálni m értékét a konverzió után
    convert_to_plsql(r.collected, m); -- {a,b} majd {c}
  END LOOP;
  -- POWERMULTISET
  SELECT CAST(POWERMULTISET(v_abc_ab) AS T_Multiset_multiset_ab)
  INTO mm
  FROM dual;
  -- mm : { {a}, {b}, {a,b}, {c}, {a,c}, {b,c}, {a,b,c} }
  i := mm.COUNT; -- 7
  convert_to_plsql(mm(1), m); -- {a}
  convert_to_plsql(mm(2), m); -- {b}
  convert_to_plsql(mm(3), m); -- {a,b}
  convert_to_plsql(mm(4), m); -- {c}
  convert_to_plsql(mm(5), m); -- {a,c}
  convert_to_plsql(mm(6), m); -- {b,c}
  convert_to_plsql(mm(7), m); -- {a,b,c}
  -- POWERMULTISET_BY_CARDINALITY
  SELECT CAST(POWERMULTISET_BY_CARDINALITY(v_abc_ab, 2)
  AS T_Multiset_multiset_ab)
  INTO mm
  FROM dual;
  -- mm : { {a,b}, {a,c}, {b,c} }
  i := mm.COUNT; -- 3
  convert_to_plsql(mm(1), m); -- {a,b}
  convert_to_plsql(mm(2), m); -- {a,c}
  convert_to_plsql(mm(3), m); -- {b,c}
END proc_multiset_op_fv_teszt;
/
show errors

A DML utasításokban használható a

TABLE (kollekciókifejezés)

utasításrész, ahol a kollekciókifejezés lehet alkérdés, oszlopnév, beépített függvény hívása. Minden esetben beágyazott tábla vagy dinamikus tömb típusú kollekciót kell szolgáltatnia. A TABLE segítségével az adott kollekció elemeihez mint egy tábla soraihoz férhetünk hozzá. Ha a kollekció elemei objektum típusúak, akkor a TABLE által szolgáltatott virtuális tábla oszlopainak a neve az objektumtípus attribútumainak nevével egyezik meg. Skalár típusú elemek kollekciójánál COLUMN_VALUE lesz az egyetlen oszlop neve.

Ugyancsak DML utasításokban alkalmazható a CAST függvény. Segítségével adatbázis vagy kollekció típusú értékeket tudunk másik adatbázis- vagy kollekciótípusra konvertálni. Alakja:

CAST({kifejezés|(alkérdés)|MULTISET(alkérdés)} AS típusnév)

A típusnév adja meg azt a típust, amelybe a konverzió történik. A típusnév lehet adatbázistípus vagy adatbázisban tárolt kollekciótípus neve. A kifejezés és az alkérdés határozza meg a konvertálandó értéket. Az egyedül álló alkérdés csak egyetlen értéket szolgáltathat. MULTISET esetén az alkérdés akárhány sort szolgáltathat, ezekből a típusnév által meghatározott kollekció elemei lesznek.

Kollekciók csak kompatibilis elem típusú kollekciókká konvertálhatók.

A 12.1. táblázat a CAST-tal megvalósítható konverziókat szemlélteti.

12.1. táblázat - Az adatbázistípusok konverziói

 

CHAR, VARCHAR2

NUMBER

Dátum/ intervallum

RAW

ROWID, UROWID

NCHAR, NVARCHAR2

CHAR, VARCHAR2

X

X

X

X

X

 

NUMBER

X

X

       

Dátum/intervallum

X

 

X

     

RAW

X

   

X

   

ROWID, UROWID

X

     

X

 

NCHAR, NVARCHAR2

 

X

X

X

X

X

Nézzünk néhány példát a TABLE és a CAST használatára.

5. példa

/* CAST csak SQL-ben van. A típusnak adatbázistípusnak
  kell lennie, viszont skalár is lehet. */
CREATE TYPE T_Rec IS OBJECT (szam NUMBER, nev VARCHAR2(100));
/

CREATE TYPE T_Dinamikus IS VARRAY(10) OF T_Rec;
/

CREATE TYPE T_Beagyazott IS TABLE OF T_Rec;
/

DECLARE
  v_Dinamikus T_Dinamikus;
  v_Beagyazott T_Beagyazott;
BEGIN
  SELECT CAST(v_Beagyazott AS T_Dinamikus)
  INTO v_Dinamikus
  FROM dual;
  SELECT CAST(MULTISET(SELECT id, cim FROM konyv ORDER BY UPPER(cim))
    AS T_Beagyazott)
  INTO v_Beagyazott
  FROM dual;
END;
/

DROP TYPE T_Beagyazott;
DROP TYPE T_Dinamikus;
DROP TYPE T_Rec;

6. példa

SELECT * FROM ugyfel, TABLE(konyvek);
SELECT * FROM TABLE(SELECT konyvek FROM ugyfel WHERE id = 15);

CREATE OR REPLACE FUNCTION fv_Szerzok(p_Konyv konyv.id%TYPE)
RETURN T_Szerzok IS
  v_Szerzo T_Szerzok;
BEGIN
  SELECT szerzo
  INTO v_Szerzo
  FROM konyv
  WHERE id = p_Konyv;
  RETURN v_Szerzo;
EXCEPTION
  WHEN NO_DATA_FOUND THEN
    RETURN T_Szerzok();
END fv_Szerzok;
/
show errors

SELECT * FROM TABLE(fv_Szerzok(15));
SELECT * FROM TABLE(fv_Szerzok(150));

BEGIN
  /* Ha skalár elemű kollekción végzünk el lekérdezést,
  akkor az egyetlen oszlop neve COLUMN_VALUE lesz. */
  FOR szerzo IN (SELECT * FROM TABLE(fv_Szerzok(15))) LOOP
    DBMS_OUTPUT.PUT_LINE(szerzo.COLUMN_VALUE);
  END LOOP;
END;
/

7. példa

DECLARE
  /* Kilistázzuk a kölcsönzött könyvek azonosítóját és a kölcsönzött
  példányok számát. */
  v_Konyvek T_Konyvek;
  v_Cim konyv.cim%TYPE;

  /* Megadja egy könyv címét */
  FUNCTION a_cim(p_Konyv konyv.id%TYPE) RETURN konyv.cim%TYPE IS
    v_Konyv konyv.cim%TYPE;
  BEGIN
    SELECT cim INTO v_Konyv FROM konyv WHERE id = p_Konyv;
    RETURN v_Konyv;
  END a_cim;
BEGIN
  /* Lekérdezzük az összes kölcsönzést egy változóba */
  SELECT CAST(MULTISET(SELECT konyv, datum FROM kolcsonzes) AS T_Konyvek)
  INTO v_Konyvek
  FROM dual;

  /* Noha v_Konyvek T_Konyvek típusú, mivel változó, szükség van
  a CAST operátorra, hogy az SQL utasításban használhassuk. */
  FOR konyv IN (
    SELECT konyv_id AS id, COUNT(1) AS peldany
    FROM TABLE(CAST(v_Konyvek AS T_Konyvek))
    GROUP BY konyv_id
    ORDER BY peldany ASC
  ) LOOP
    DBMS_OUTPUT.PUT_LINE(LPAD(konyv.id, 3) || ' '
      || LPAD(konyv.peldany, 2) || ' ' || a_cim(konyv.id));
  END LOOP;
END;
/

/* Eredmény:
5 1 A római jog története és institúciói
 10 1 A teljesség felé
 15 1 Piszkos Fred és a többiek
 20 1 ECOOP 2001 - Object-Oriented Programming
 40 1 The Norton Anthology of American Literature - Second Edition - Volume 2
 25 1 Java - start!
 30 2 SQL:1999 Understanding Relational Language Components
 45 2 Matematikai zseblexikon
 50 2 Matematikai Kézikönyv
 35 2 A critical introduction to twentieth-century American drama - Volume 2

A PL/SQL eljárás sikeresen befejeződött.
*/

Kollekciókat visszaadó függvények hatékonyabban implementálhatók a PIPELINED opció használatával (lásd [19]).