|
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.
|