MainframeSupports
tip uge 37/2010:

Tilbage i DB2 version 8 lancerede IBM endelig muligheden for at lave ægte rekursiv SQL. Det pudsige er, at rekursiv SQL har været muligt i Oracle lige siden en gang i 1980'erne. På dette punkt må jeg konstatere, at DB2 har haltet håbløst bagud. Implementeringen af rekursiv SQL i Oracle er også simpel og elegant. Det kan jeg desværre ikke sige om IBM's implementering i DB2. Jeg håber, at jeg forklare det tilstrækkelig godt, til at mit tip også er anvendeligt for dig.

Jeg har skruet et eksempel sammen, som benytter en af katalog-tabellerne, så du kan kopiere det direkte til din egen installation og konstatere, at det rent faktisk eksekverer korrrekt. Muligvis vil den rekursive del af SQL-kaldet slet ikke blive aktiveret, da det kræver, at der på den DB2, du eksekverer SQL'et på, er mindst et view på SYSIBM-tabellerne, der er baseret på et andet view. En del DB2-produkter laver den slags, så måske vil du få et rekursivt resultat. Selve SQL-kaldet ser således ud:

WITH RECURSIVERESULT (LEVEL, BNAME, BCREATOR, DNAME, DCREATOR, STARTNAME) AS
( SELECT 1, CHAR(V.BNAME, 18), CHAR(V.BCREATOR,8)
       , CHAR(V.DNAME, 18), CHAR(V.DCREATOR,8), V.DNAME
  FROM SYSIBM.SYSVIEWDEP V
  WHERE V.BCREATOR = 'SYSIBM'
  UNION ALL
  SELECT LEVEL + 1, CHAR(V.BNAME, 18), CHAR(V.BCREATOR,8)
       , CHAR(V.DNAME, 18), CHAR(V.DCREATOR,8), STARTNAME
  FROM RECURSIVERESULT R
     , SYSIBM.SYSVIEWDEP V
  WHERE R.DNAME = V.BNAME
    AND R.DCREATOR = V.BCREATOR
)
SELECT *
FROM RECURSIVERESULT
ORDER BY STARTNAME, LEVEL

Hvis du interesserer dig for SQL syntaks, så er det bemærkelsesværdige ved ovennævnte SELECT, at det faktisk starter med WITH. Hvis du har lavet programmer, der udsteder dynamisk SQL og benytter PREPARE til SELECT kald og EXECUTE til resten, så må du hellere sørge for at WITH fremover ikke hører til resten. Her er jeg i hvert fald selv faldet i.

Siden DB2 version 8 består et SELECT statement altså af en valgfri WITH del efterfulgt af den obligatoriske SELECT del. Du skal altså lede efter beskrivelsen af WITH under SELECT statements i SQL reference. Der findes ikke et WITH statement. WITH delen kan bruges til at opbygge temporærere tabeller, der kan benyttes i SELECT delen. Du skal altså opbygge en tabel i WITH delen, der indeholder resultatet af dit rekursive SQL. Den første linie i eksemplet definerer en tabel kaldet RECURSIVERESULT med de angivne kolonner. Efter AS definerer du så, hvordan indholdet skal opbygges med en SELECT i parentes, altså en sub-select.

Denne sub-select skal opbygges som angivet for at hele SQL-kaldet bliver rekursivt. Før UNION ALL angives den SELECT der udtrækker de initielle rækker i det rekursive resultat. I eksemplet udtrækker jeg alle de tabeller eller views, der er base-tabeller i andre views og har SYSIBM som creator. Hvis der ikke er defineret nogen views på SYSIBM tabellerne, så vil denne del af UNION ALL ikke returnere nogen rækker og dermed vil slutresultat også blive nul rækker.

Efter UNION ALL angives en SELECT, der udføres rekursivt indtil der ikke er flere rækker, der opfylder denne SELECT. Hvis SELECT'en før UNION ALL returnerer et antal rækker, så vil alle disse rækker blive gemt i RECURSIVERESULT og indgå i join'en med SYSVIEWDEP i SELECT'en efter UNION all. Det er altså join-kriteriet i denne SELECT, der definerer den eller de kolonner, der skaber rekursionen. I eksemplet benyttes view'ets navn og creator til at finde ud af, om der er nogen view's, der er baseret på dette view.

Selve rekursionen foregår altså ved først at udføre SELECT'en før UNION ALL. Herefter udføres SELECT'en efter UNION ALL på alle de rækker, der blev returneret af den initielle SELECT. Hvis denne SELECT returnerer nogen rækker, så udføres SELECT'en efter UNION ALL en gang til, og sådan fortsætter DB2, indtil der ikke returneres nogen rækker af SELECT'en efter UNION ALL. Alle de returnerede rækker gemmes automatisk i RECURSIVERESULT.

Hvis der findes en række i SYSVIEWDEP, hvor BNAME = DNAME og BCREATOR = DCREATOR, så loop'er hele SQL-kaldet. Det kan heldigvis ikke lade sig gøre i ovennævnte eksempel, da DB2 ikke tillader views med samme navn som tabellen. Jeg har indført kolonnen LEVEL for at kunne følge antallet af udførte rekursioner. Det kan være en rigtig god ide at inkludere en sådan kolonne i WHERE-clausen i SELECT'en efter UNION ALL for at forhindre et loop. Jeg kunne eksempelvis have indført AND LEVEL < 10, hvilket effektivt vil stoppe rekursionen, når et view er defineret oven på 9 andre view's.

Selve SELECT delen til sidst i eksemplet udtrækker nu de rækker, der er blevt gemt i RECURSIVERESULT. Jeg har indført kolonnen STARTNAME, som fødes med navnet på det første view på det initielle niveau, for at kunne vise sammenhørende rækker. Uden denne kolonne bliver det ret uoverskueligt at læse resultatet af SQL-kaldet. Og så er der vist ikke andet tilbage end at sige held og lykke med dine egne rekursive SQL-kald.

Forrige danske tip        Last tip in english        Tip oversigten