Terug naar de beginpaginaCentor Homepage

Wat allemaal kan met Nested Tables en VARRAY’s

PL/SQL‑tables zijn datastructuren waarmee in het gebruikersgeheugen een geïndexeerde lijst kan worden aangemaakt. Dat indexeren gebeurt via een BINARY_INTEGER waarvan iedere entry kan bestaan uit een waarde van één datatype of een record. Deze PL/SQL‑tabellen lenen zich uitstekend voor snelle toegang tot (lijsten van) gegevens in het geheugen. Ze zijn waardevol voor het cachen van data uit de database in het geheugen, voor het omzeilen van het bekende ‘mutating table’‑probleem en voor de emulatie van bi‑directionele cursoren waarmee voor‑ en achterwaarts door een resultaatset kan worden genavigeerd.




Sinds Oracle 8 zijn naast PL/SQL‑tables — die in Oracle 8 werden omgedoopt tot 'index‑by tables' — nog twee collectiesoorten geïntroduceerd: VARRAY’s en Nested Tables.

VARRAY's zijn beschikbaar in SQL en PL/SQL en zijn bedoeld om kleine lijstjes op te slaan, bijvoorbeeld in een kolom van een relationele tabel. VARRAY's hebben een (te definiëren) maximumaantal entries. De volgorde waarin de elementen in een variabele of kolom van het type VARRAY worden opgeslagen, is van groot belang.

Nested Tables (NT) daarentegen hebben geen maximumaantal entries, hoewel ze natuurlijk wel afhankelijk zijn van de hoeveelheid intern geheugen. Ook de volgorde van de elementen is niet van belang. NT's zijn zgn. 'multisets': een set waarin de volgorde wordt genegeerd, maar dubbele entries wel degelijk bewaard blijven. Daarbij moet worden opgemerkt dat de multisets {a,b,c} en {b,c,a} hetzelfde zijn, maar {a,b,c} en {a,a,b,c} beslist niet!

Oracle 9
Toen VARRAY’s en Nested Tables voor het eerst in Oracle 8 werden geïmplementeerd, konden ze nog niet worden genest. In Oracle 9 kan dat wel. De volgende code geeft een voorbeeld van het nesten van zo’n Nested Table. In dit geval wordt een collectie ‘onderdelen’ genest in een collectie ‘wedstrijden’.


CREATE OR REPLACE TYPE onderdeel_nt AS
       TABLE OF VARCHAR2(30); 
CREATE OR REPLACE TYPE wedstrijd_nt AS
       TABLE OF onderdeel_nt;
DECLARE v_wed wedstrijd_nt :=
       wedstrijd_nt(onderdeel_nt('100 meter')
                   ,onderdeel_nt('verspringen'));
BEGIN
    DBMS_OUTPUT.put_line (v_wed (2) (1));
END;

Output: verspringen

In deze listing worden enkele constructies duidelijk, die anders zijn dan bij het werken met Oracle 7 PL/SQL‑tables. Er kan een Collection Type gedefinieerd worden dat verschilt van een andere Collection Type (regel 2). Andere verschillen zijn bijvoorbeeld het instantiëren van (eigenschappen toekennen aan) de Nested Table‑variabele via een Constructor — met dezelfde naam als het TYPE: regels 4 en 5 — en het opvragen van een waarde uit een cel; in dit geval de cel op positie (2)(1).

Opvallend is overigens dat de volgorde van de ‘indices’ andersom is dan velen wellicht verwachten. De buitenste collectie‑index staat immers achter de binnenste. Een ander punt dat opvalt is dat collecties van records of objecten niet anoniem zijn maar een naam hebben.

Het voordeel van geneste collections is dat het complexe datastructuren waarin hiërarchie zit vereenvoudigt in PL/SQL‑variabelen: een ‘platgeslagen’ tabel met ALL_ARGUMENTS dat veel bruikbaarder is.

Oracle 10g
In Oracle 10g introduceert Oracle enkele nieuwe operators die collections kunnen bewerken: MULTISET UNION, MULTISET UNION DISTINCT, MULTISET EXCEPT, MULTISET INTERSECT en SET. Hieronder volgt een korte uiteenzetting van die operators:

  • MULTISET UNION: Omdat Nested Tables verzamelingen zijn waarbij dubbele entries wel belangrijk zijn en de volgorde niet, verschilt MULTISET UNION (waarbij twee collecties bij elkaar worden gevoegd) van de SQL‑operator UNION. De collection MULTISET UNION verwijdert geen duplicaten, terwijl SQL's UNION dat wel doet.
  • MULTISET UNION DISTINCT: Wordt gebruikt voor het samenvoegen van collections waarvan de duplicaten moeten worden verwijderd.
  • MULTISET EXCEPT en INTERSECT komen overeen met SQL's MINUS en INTERSECT.
  • SET verwijdert duplicaten uit een collectie.

Performance
Het in Oracle 8i geïntroduceerde ‘bulkprocessing’ van collecties heeft gezorgd voor spectaculaire prestatieverbeteringen in het gebruik van collections in combinatie met SQL‑statements. De statements FORALL en BULK COLLECT reduceren namelijk het aantal context‑switches tussen de PL/SQL‑engine en de SQL‑engine die Oracle moet ondergaan als vanuit PL/SQL een SQL‑statement wordt uitgevoerd.

Ter illustratie schrijven we de volgende loop:


BEGIN 
  FOR i IN
     onderdelentab.FIRST.. onderdelentab.LAST
  LOOP
     UPDATE wedstrijd_onderdelen
        SET onderdeel =
             onderdelentab(i).onderdeel
     WHERE  onderdeel_id =
             onderdelentab(i).onderdeel_id
  END LOOP;
END;

Als de ‘onderdelen’‑tabel ettelijke duizenden records bevat, is dat goed voor evenzoveel context‑switches. Dat betekent een relatief dure oplossing. Als een eenvoudige SQL‑updatestatement in zo’n situatie niet voldoet, omdat er bijvoorbeeld nog allerlei ingewikkelde transformaties moeten plaatsvinden voor elke record en de ontwikkelaar genoodzaakt is tot een loop door de records, werkt een FORALL‑statement veel sneller:


BEGIN 
  FORALL i IN
     onderdelen_tab.FIRST..onderdelen_tab.LAST
    UPDATE wedstrijd_onderdelen
       SET onderdeel = onderdelen_tab(i)
     WHERE onderdeel_id = onderdelenid_tab(i);
END;

Maar let op: in het geval van FORALL (en ook bij BULK COLLECT) zijn in Oracle 8i geen collections van records mogelijk. Daarom worden in het bovenstaande voorbeeld twee aparte collections genoemd. In Oracle 9i is deze beperking opgeheven. Dat maakt het gebruik van bulkprocessing een stuk eenvoudiger.
De grootste verbetering is dat er een dynamisch SQL kan worden samengesteld in combinatie met FORALL en BULK COLLECT. Dat zorgt voor een nagenoeg ongelimiteerde flexibiliteit zonder toe te geven op performance.

Kijk maar eens naar de volgende statement:


BEGIN
 FORALL i IN
   onderdelen_tab.FIRST..onderdelen_tab.LAST
 EXECUTE IMMEDIATE
   'UPDATE wedstrijd_onderdelen 
    SET onderdeel =
       REPLACE(onderdeel,'||str_in||')
    WHERE onderdeel_id = :1
    RETURNING onderdeel INTO :2'
   USING onderdelenid_tab (i) 
   RETURNING BULK COLLECT
      INTO wedstrijdonderdelentab; 
END;

Het aantal redenen om geen bulkprocessing te gebruiken is dankzij deze mogelijkheden drastisch teruggebracht. Redenen om toch conventionele loops te gebruiken in plaats van FORALL‑ of BULK COLLECT‑statements zijn: complexe processing per rij of geheugenlimieten doordat een collection in het gebruikersgeheugen staat en niet is gedeeld, of het behoud van meer controle over het proces.
Maar in veel gevallen kan bulkprocessing een aanzienlijke prestatieverbetering opleveren. Dat kan oplopen tot honderden of zelfs duizenden malen sneller! Dat zijn toch getallen die het ‘t proberen waard maken? Fouten die optreden tijdens bulkprocessing kunnen in Oracle 9i ook bewaard worden in een pseudo‑collection. Die wordt na het bulkproces uitgelezen en kan eventueel worden afgehandeld. Deze pseudo‑collectie heet SQL%BULK_EXCEPTIONS en wordt gevuld door middel van het statement SAVE EXCEPTIONS in een bulkproces.

Qnxo
En dan is er nog Qnxo, een nieuwe tool die het leven van de PL/SQL‑ontwikkelaar veraangenaamt. Qnxo, wat staat voor ‘Quality In, Excellence Out’, is bedoeld om een brug te slaan tussen de low‑level Integrated Development Environments (IDE's) die momenteel bestaan — en voor veel productiviteitsverhoging zorgen! — en de denkwereld van de ontwikkelaar bij het oplossen van een probleem: het denken in hogere niveauconcepten en entititeiten in plaats van in tabellen, functies en procedures. Qnxo is geen vervanger van IDE's, maar een aanvullend product waarin codegeneratie een belangrijke rol speelt.

Kijk, voor een werkende trial‑versie van Qnxo, op www.swyg.com.

 

O.C. Centor BV Symfonielaan 24
3438EV
Nieuwegein
tel. 030 6020060

Productie en V
ormgeving:

Beaumont
Tekst en Ontwerp

H. Dunantweg 20 2400BD
Alphen a/d Rijn
tel. 0172 419370