MainframeSupports
tip uge 19/2002:

Følgende tip har jeg længe ønsket at kunne skrive, men det er først i den forløbne uge, jeg har haft lejlighed til at udtænke og afprøve det i praksis. Nu om dage er det næsten umuligt at skrive et SQL-kald uden at benytte mindst to tabeller som selvfølgelig skal joines sammen. Det er DB2 faktisk ret god til, så længe de kolonner, man joiner på har præcis samme definition.

I de nyeste versioner af DB2 (efter version 4) er der sket en opblødning, så nu kan VARCHAR og CHAR-kolonner med forskellig længde joines med succes og det samme gælder også INTEGER med SMALLINT. Men det kniber mellem forskellige DECIMAL definitioner og blandingen CHAR/VARCHAR med INTEGER eller DECIMAL kommer i følge IBM aldrig til at virke fornuftigt. Hvad gør vi så i sidstnævnte tilfælde?

Nogen vil straks erklære, at dette er et hypotetisk problem, da man selvfølgelig har defineret kolonner med samme indhold på samme måde. Praksis viser bare noget helt andet. Lad os antage, at vi har en kunde-tabel med en unik nøgle kaldet kundenr, som vi i tidernes morgen definerede som DECIMAL(10,0). Der er nu gået nogle år og der er kommet en hel del systemer til, hvoraf et enkelt (det nye internet system) af forskellige årsager definerer kundenr som CHAR(10) i den centrale tabel internetordre.

Efter et stykke tid i produktion finder vi ud af, at tabel internetordre skal joines med tabel kunde (det kan være en simpel adhoc query eller i et program). Vi udtænker følgende SQL-kald:

SELECT K.KUNDENR, K.POSTNR, I.ORDRENR, I.ORDRETYPE
FROM KUNDE K, INTERNETORDRE I
WHERE K.KUNDENR = I.KUNDENR
  AND K.INTERNETKUNDE = 'J'

Dette simple SQL-kald kører selvfølgelig i rigtig lang tid. Enten vælger DB2 en merge-scan join (det bedste i dette tilfælde) eller en nested loop join (døden). Nested loop join vil blive udført med tablespace scan på den indre tabel og så er antallet af tablespace scans kun afhængig af, hvor mange rækker fra den ydre tabel, der skal joines. Vi kan hoppe og danse med indexer, vi kan lave nested table expressions og andre julenumre, der er intet at gøre. Denne join er bare langsom til ekstremt langsom på grund af de forskellige definitioner af KUNDENR.

Alternativet er at lave et program med en kodet join. Men man kan faktisk også bruge en global temporary table. Til eksemplet vil jeg erklære den således:

CREATE GLOBAL TEMPORARY TABLE INTERNETKUNDE
(KUNDENR CHAR(10) NOT NULL, POSTNR SMALLINT)

Kolonnen POSTNR erklæres med samme definition som i tabel KUNDE. Herefter kan SQL-kaldet omformes til følgende to SQL-kald:

INSERT INTO INTERNETKUNDE
SELECT DIGITS(KUNDENR), POSTNR
FROM KUNDE
WHERE INTERNETKUNDE = 'J'
;
SELECT K.KUNDENR, K.POSTNR, I.ORDRENR, I.ORDRETYPE
FROM INTERNETKUNDE K, INTERNETORDRE I
WHERE K.KUNDENR = I.KUNDENR
;

Det første SQL-kald opretter en temporær tabel med det udseende, vi erklærede tidligere og fylder den med de rækker fra tabel KUNDE, der er internet-kunder. I select-listen sørger vi for at konvertere fra DECIMAL(10) til CHAR(10) med DIGITS-funktionen. Hvis problemstillingen havde været den omvendte, hvor CHAR skulle konverteres til DECIMAL, så kan DECIMAL-funktionen anvendes.

Det næste SQL-kald vil så lave selve udtrækket. Hvis der ellers er et index på KUNDENR på tabel INTERNETORDRE, så vil dette SQL-kald være færdig i løbet af kort tid. Om resultatet er tilfredsstillende er ret afhængig af, om kundenr i tabel INTERNETORDRE er udfyldt med foranstillede nuller og i øvrigt ikke indeholder andet end numeriske tegn.

Fordelene ved at anvende en global temporary table er, at den kan bruges igen og igen, man kan køre explain imod den, for at se om man opnår det ønskede resultat, og man skal ikke tænke på at rydde op i den, da indholdet forsvinder igen ved næste commit eller rollback. Netop det sidste kan give problemer med at anvende metoden i QMF, da QMF typisk laver COMMIT efter hvert SQL-kald.

Forrige danske tip        Last tip in english        Tip oversigten