Data en PCDATA

Diederik Gerth van Wijk

dgerth@kluwer.nl

In de serie rode vuistdikke pillen die Wrox voor bitse vaklui uitgeeft is al weer een half jaar gelden Professional XML Databases verschenen. Wie een verzameling met XML gemarkeerde teksten heeft en denkt in dat boek te kunnen lezen hoe je een database kunt maken waarin je ze kunt opslaan en beheren (ons congresthema van vorig jaar) komt bedrogen uit. Wie daarentegen een relationele database heeft en wil weten hoe hij bij uitwisseling van data XML kan gebruiken, die kan een boel leren uit dit boek.

Hoe vang je de wereld in bits

Er zijn verschillende manieren om de wereld in stukjes te delen. De databasewereld is gewend te denken in termen van gerelateerde tabellen. Een tabel kun je zien als een verzameling elementen met elk een vast aantal attributen van een bepaald type. Je hebt een vast aantal tabellen, per tabel een onbeperkt aantal regels met een vast aantal kolommen, en tussen de tabellen heb je een vast aantal relaties die beperkingen leggen op de soort manipulaties die je met de regels zou willen uitvoeren. Dit model fungeert heel goed voor het inrichten van administratieve systemen, zoals een boekhouding of een adressenbeatand. De nadruk ligt op data, zoals adressen, unieke codes, korte namen, die meestal uit verschillende tabellen samengevoegd moeten worden om tot een zinnig geheel te komen, maar die juist omdat ze apart opgeslagen worden wel gemakkelijk manipuleerbaar zijn. Stel je hebt een tabel met docenten (met daarin bijvoorbeeld kolommen voor docentnummer, naam, adres en telefoonnummer), een tabel waarn staat in welk jaar welke docent aan welke groep les gaf (kolommen docentnummer, jaar en groepnummer), een tabel met leerlingen (leerlingnummer, naam, adres) en een tabel waarin staat in welk jaar een leerling in welke groep zat (leerlingnummer, jaar en groepnummer). Je kunt nu gemakkelijk een uitdraai maken waarin je per docent laat zien welke leerling hij in welk jaar had, of per jaar welke groep(en) hij had, of per groep welke docent(en) er dit jaar zijn en welke leerlingen er dit jaar in die groep zitten, of per leerling in welk jaar hij welke docent(en) had. Zowel de manier van sorteren als de manier van groeperen is iets dat je pas op het moment van exporteren van gegevens uit de database bepaalt, en je kunt het keer op keer anders doen. Voor teksten zijn deze systemen minder geschikt, omdat tekst twee belangrijke eigenschappen heeft die andere soorten manipulaties wenselijk maken. De eerste is dat volgorde belangrijk is. De meeste boeken winnen niet aan leesbaarheid als je de alinea's alfabetisch sorteert. Als dat wel het geval is, was het een woordenboek of een encyclopedie en waren het geen alinea's maar lemmata die je sorteerde. De tweede is dat teksten vaak een zekere hiërarchie kennen. Alinea's worden gegroepeerd tot paragrafen, paragrafen tot hoofdstukken, hoofdstukken tot delen en die weer tot een boek. In de inhoudsopgave vind je die indeling terug. Omdat tekst een zekere opbouw kent is ook de manier van groeperen meestal vast, en maakt hij deel uit van de informatie die je in een tekst vastlegt. Naarmate de volgorde en groepering minder vrij manipuleerbaar zijn zonder de waarde van de er in opgeslagen informatie aan te tasten, ligt een relationele database minder voor de hand en ga je meer aan een hiërarchische methodiek denken zoals die bij SGML gebruikelijk is.

Neem in gedachten een factuur. Leverancier, opdrachtgever, factuuradres, afleveradres, datum van opdracht, contactpersoon, factuurnummer, een aantal orderregels bestaande uit besteld aantal, productcode, productomschrijving, prijs per eenheid, eventueel kortingspercentage, BTW-klasse en totaalbedrag van de orderregel en tot slot het totaalbedrag van de factuur zonder en met BTW, u kent het wel. Vraag de DTD-ontwerper van uitgeverij Elsduwer en een systeemanalist van ERP-leverancier ZEP om dat formulier te analyseren, en het resultaat in de vorm van een DTD te presenteren, wat krijg je dan? Waarschijnlijk zal de eerste iets als dit opleveren:

<!ELEMENT factuur (opdrachtgever, orderregel+, totaalexbtw, btw*, totaalinclbtw)>
<!ATTLIST factuur datumopdracht NMTOKEN #REQUIRED
                  factuurnummer NMTOKEN #REQUIRED>
<!ELEMENT opdrachtgever (naambedrijf, afleveradres, factuuradres?, contactpersoon)>
<!ELEMENT orderregel (aantal, prodomschr, prijspereenheid, kortingperc?, totaalvanregel)>
<!ATTLIST orderregel productcode NMTOKEN #REQUIRED
                     btwklasse (hoog | laag | bijzonder | nul | geen) hoog>
<!ELEMENT btw (totaalbedragwaarover, btwtarief, btwbedrag)>
<!ATTLIST btw btwklasse (hoog | laag | bijzonder | nul | geen) #REQUIRED>

De declaratie van elementen met data content of adresgegevens heb ik maar weggelaten. Deze DTD beschrijft keurig een bestaande factuur, en is prima om bijvoorbeeld de factuur naar een print-afdeling te sturen waar hij dan opgemaakt wordt:

<factuur datumopdracht="20011005" factuurnummer="0023763">
 <opdrachtgever>
  <naambedrijf>Ministerie zonder portefeuilles</naambedrijf>
  <afleveradres>
   <straat>postbus</straat><nr>51</nr>
   <postcode>2501 CB</postcode><plaats>Den Haag</plaats>
  </afleveradres>
  <contactpersoon>R. van Vught</contactpersoon>
 </opdrachtgever>
 <orderregel productcode="900147972" btwklasse="laag">
  <aantal>1000</aantal>
  <prodomschr>Dummy</prodomschr>
  <prijspereenheid>4,95</prijspereenheid>
  <kortingperc>5</kortingperc>
  <totaalvanregel>4702,50</totaalvanregel>
 </orderregel>
 <orderregel productcode="x129" btwklasse="hoog">
  <aantal>1</aantal>
  <prodomschr>Boekenkast</prodomschr>
  <prijspereenheid>1500,00</prijspereenheid>
  <totaalvanregel>1500,00</totaalvanregel>
 </orderregel>
 <orderregel productcode="x612" btwklasse="hoog">
  <aantal>1</aantal>
  <prodomschr>Portefeuille</prodomschr>
  <prijspereenheid>69,50</prijspereenheid>
  <totaalvanregel>69,50</totaalvanregel>
 </orderregel>
 <totaalexbtw>6272,00</totaalexbtw>
 <btw btwklasse="laag">
  <totaalbedragwaarover>4702,50</totaalbedragwaarover>
  <btwtarief>6%</btwtarief>
  <btwbedrag>282,15</btwbedrag>
 </btw>
 <btw btwklasse="hoog">
  <totaalbedragwaarover>1569,50</totaalbedragwaarover>
  <btwtarief>19%</btwtarief>
  <btwbedrag>298,20</btwbedrag>
 </btw>
 <totaalinclbtw>6852,35</totaalinclbtw>
</factuur>

De leverancier, het logo, de plaats waar de adregegevens moeten worden afgedrukt (boven aan, onder op een acceptgiro) wordt overgelaten aan het stijlboek.

Maar kun je een factuur die volgens deze DTD is opgebouwd gebruiken als de klant belt om te zeggen dat hij verhuisd is van Amsterdam naar Leeuwarden en dat hij daarom in plaats van de Provinciale regelingen van Noord Holland de regelingen van Friesland wil ontvangen? Nee, want het factuur- en afleveradres moet natuurlijk niet alleen op de factuur, maar vooral in de klantendatabase aangepast worden, en de wijziging in de bestelling moet ook in de orderadministratie verwerkt worden. Kortom, voor manipulatie heb je een andere inrichting van je systeem nodig. Een databaseanalist is geschoold om een database zodanig te normaliseren dat alle redundantie uit de gegevens verwijderd wordt. Dat is gemakkelijk gezegd, maar niet altijd gedaan. Stel, je neemt in een orderregel niet de prijs op, maar via de procuctcode haal je die uit de tabel met productinformatie. Tussen bestelling en facturering wordt de prijs aangepast. Moet je dan automatisch de nieuwe prijs factureren? En als je over een jaar, na een prijsverlaging, er achter komt dat de factuur nog niet betaald is, en je vraagt hem opnieuw op, wil je dan inderdaad de huidige, lagere, prijs in je aanmaning afdrukken? Nee toch. Maar nu houdt de analist van ZEP daar even geen rekening mee (anders valt er later niets meer te wijzigen aan het systeem, en dat scheelt omzet), en krijgen we de volgende DTD als onderdeel van een boekhouding in XML:

<!ELEMENT orderadministratie (relaties?, adressen?, producten?
                             , btwtarieven?, orderregels?, facturen?)>

<!ELEMENT relaties (relatie+)> 
<!ELEMENT relatie EMPTY>
<!ATTLIST relatie relatiecode ID #REQUIRED
                  naam CDATA #REQUIRED>

<!ELEMENT adressen (adres+)>
<!ELEMENT adres EMPTY>
<!ATTLIST adres adrescode ID #REQUIRED
                straat CDATA #REQUIRED
                huisnr CDATA #REQUIRED
                postcode CDATA #REQUIRED
                plaats CDATA #REQUIRED>

<!ELEMENT producten (product+)>
<!ELEMENT product EMPTY>
<!ATTLIST product productcode ID #REQUIRED
                  productomschrijving CDATA #REQUIRED
                  prijs NMTOKEN #REQUIRED
                  btwcode IDREF #REQUIRED>

<!ELEMENT btwtarieven (btwtarief+)>
<!ELEMENT btwtarief EMPTY>
<!ATTLIST btwtarief btwcode ID #REQUIRED
                    btwpercentage NMTOKEN #REQUIRED>

<!ELEMENT orderregels (orderregel+)>
<!ELEMENT orderregel EMPTY>
<!ATTLIST orderregel factuurcode IDREF #REQUIRED
                     productcode IDREF #REQUIRED
                     aantal NMTOKEN "1"
                     korting NMTOKEN "0">

<!ELEMENT facturen (factuur+)>
<!ELEMENT factuur EMPTY>
<!ATTLIST factuur factuurcode ID #REQUIRED
                  relatiecode IDREF #REQUIRED
                  factuuradres IDREF #REQUIRED
                  afleveradres IDREF #REQUIRED
                  datumopdracht NMTOKEN #REQUIRED
                  contactpersoon NMTOKEN #REQUIRED>

De database is het top-element, en bestaat uit een aantal tabellen. Elke tabel bestaat uit een of meer regels (empty elements). Elke regel bestaat uit twee of meer kolommen (attributen), die via ID/IDREF met elkaar verbonden kunnen worden. Merk een paar dingen op. In de eerste plaats zitten er geen uitrekenbare dingen in: bedrag per orderregel, totaalbedrag, en btw kan allemaal uitegrekend worden, is dus redundant en wordt derhalve niet meegezonden. En merk verder op dat om één factuur over te zenden het voldoende is om een selectie uit de hele database te sturen, zolang alle IDREFS maar aankomen bij de bijbehorende ID. Wat je overstuurt, is afhankelijk van het gebruik dat je wilt maken. Als je alleen een factuur wilt printen, stuur je alleen de relevante gegevens, als je hem wilt kunnen laten muteren moet je in feite de hele product- en adrestabellen mee laten gaan, maar hoef je de andere facturen met hun orderregels niet mee te zenden. Voor alleen afdrukken zou het uit te wisselen uittreksel uit de database er als volgt uitzien:

<orderadministratie>
 <relaties>
  <relatie relatiecode="relatie-minzop" naam="Ministerie zonder portefeuilles"/>
 </relaties>
 <adressen>
  <adres adrescode="adres-310938" straat="Postbus" huisnr="51" postcode="2501 CB" plaats="Den Haag"/>
 </adressen>
 <producten>
  <product productcode="product-900147972" productomschrijving="Dummy" prijs="4,95" btwcode="btw-laag"/>
  <product productcode="product-x129" productomschrijving="Boekenkast" prijs="1500" btwcode="btw-hoog"/>
  <product productcode="product-x612" productomschrijving="Portefeuille" prijs="69,50" btwcode="btw-hoog"/>
 </producten>
 <btwtarieven> 
  <btwtarief btwcode="btw-hoog" btwpercentage="19"/> 
  <btwtarief btwcode="btw-laag" btwpercentage="6"/> 
 </btwtarieven> 
 <orderregels>
  <orderregel factuurcode="factuur-0023763" productcode="product-900147972" aantal="1000" korting="0.05"/>
  <orderregel factuurcode="factuur-0023763" productcode="product-x129" aantal="1" korting="0"/>
  <orderregel factuurcode="factuur-0023763" productcode="product-x612" aantal="1" korting="0"/>
 </orderregels>
 <facturen>
  <factuur
   factuurcode="factuur-0023763" relatiecode="relatie-minzop" factuuradres="adres-310938" 
   afleveradres="adres-310938" datumopdracht="20011005" contactpersoon="R. van Vught"/>
 </facturen>
</orderadministratie>

Content management en database management

Als je, dit wetend, als uitgever een boek te bespreken krijgt dat XML Databases heet, dan verwacht je half en half dat je er uit zult leren hoe je je klassieke boeken moet analyseren om ze in een database te stoppen. Niet alles wat in een boek zit is immers tekst. Zaken als metadata (auteur, titel, trefwoorden) lenen zich uitstekend voor externe opslag en beheer, om nog maar te zwijgen van ingewikkelde (meer dan tweedimensionale) tabellen waar je bij het afdrukken op papier voor slechts één van de mogelijke views kiest, maar bij elektronische raadpleging ook op andere manieren zou willen kunnen sorteren. Neem bijvoorbeeld een reisgids met een tabel met de gemiddelde temperatuur en neerslag per maand per streek. Op papier moet je kiezen tussen bijvoorbeeld

 JanuariFebruari...
Malaga12,5° 59 mm12,9° 49 mm...
Utrecht2,2°67 mm2,5° 48 mm...
...... ... ...

of

 ...2-3°...12-13°...
..................
40-49 mm...Utrecht, Februari...Malaga, Februari...
50-59 mm.........Malaga, Januari...
60-69 mm....Utrecht, Januari.........
..................

maar als je die gids via het beeldschem en toetsenbord wilt exploiteren kun je de gebruiker meer met diezelfde data laten doen ("geef me een lijst van alle gebieden waar het in october tussen de 20 en 25 graden is en de neerslag onder de 10 mm ligt").

Vanuit die optiek is het hier besproken boek niet geschreven. Integendeel, het gaat er van uit dat je een relationele database hebt en dat je nu iets over XML wilt leren. Zoals de inleiding stelt: This book teaches us how to integrate XML into our current relational data source strategies en zo is het. Mijn belangrijkste vraag is dan: waarom zou je? Om data uit te wisselen, of om een mooi rapport te maken, en dat zijn goede redenen. In een flink aantal hoofdstukken wordt de databasebeheerder in XML-land rondgeleid. Min of meer impliciet zijn deze hoofdstukken verdeeld in vijf delen:

Het boek is redelijk Web- en Microsoft georienteerd, maar dat heb je al snel met XML-boeken. De basis, hoe analyseer je, hoe vertaal je data naar XML, gebruik je elementen of attributen is gewoon algemeen en nuttig, ook voor traditionele SGML'ers. De strategiën, met duidelijke voorschriften zoals als je data tussen XML en een relationele database uitwisselt, moet elke tabel een element worden met alleen elementen als content, en een element met elementen als content moet altijd een tabel worden - tenzij je extra (de)normalisatieslagen toepast, en de kolommen moeten hetzij elementen met #PC-data als content worden, hetzij attributenzijn helder, en kloppen gelukkig met wat ik in mijn factuurvoorbeeld toegepast heb (ik had dat al geschreven voor ik het boek las, geen betere test of een boek klopt dan of ze hetzelfde beweren als je zelf denkt, sprak hij eigenwijs en niet van plan iets nieuws te leren).

Een boek dat vooral voor en door relationele databasers geschreven is, moet toch geschreven zijn door mensen die enige aversie jegens redundantie hebben. Als je dan ziet hoe keer op keer hetzelfde maar dan vanuit een net iets andere optiek beweerd wordt, en anderzijds het overgote deel van het boek gewijd is aan informatie die ook elders te vinden is, dan luidt mijn conclusie dat ik al heel tevreden was geweest met een ingekorte en beter geïntegreerde versie van het eerste deel. 90% van de meer dan duizend pagina's van het boek zouden bij een serieuze normalisatie gesneuveld zijn. Maar ik besef dat het boek niet geschreven is voor mij, die al het nodige over XML in de kast heeft staan, maar voor de databasebeheerder die hip wil doen, en dan heeft het wellicht wel zin. Maar toch, tien hanteerbare boekjes van elk 100 pagina's, wat is daar nu mis mee? Dat je die maar één keer per klant verkoopt, zegt mijn uitgeversverstand.

Een eerste (en volgens mij eerlijk gezegd voldoende) indruk van het boek kunt u op het web vinden, waar hoofdstuk 2 (XML Structures for Existing Databases) staat: http://www-106.ibm.com/developerworks/library/x-struct/?n-x-1041.

Kevin Williams e.v.a., Professional XML Databases, Wrox Press, Birmingham, 2000, ISBN 1-861003-58-7