Sei sulla pagina 1di 11

Database relazionali e loro integrazione con RDF/SPARQL

Rosario Turco

In questo articolo mettiamo in evidenza le potenzialit che pu offrire OWL per mettere daccordo, in tempo reale, due database relazionali attraverso le D2RQ interface e avere la possibilit di disporre di metadati ontologici separati dai dati. Lobiettivo dellarticolo di mostrare situazioni in cui OWL lunica soluzione, permettendo di usare software open source e di sviluppare in sostanza poco software (ad esempio un XSLT). Supponiamo che esista lesigenza di dover mantenere due database distinti e che non si voglia un terzo database di raccordo, con tecniche ETL; ma in ogni caso serve effettuare delle query, in tempo reale, che tengano conto dei dati di entrambi i database ed i dati semanticamente uguali sono denominati in modo diverso. Come vedremo, tale esigenza copribile solo con ontologie OWL e query SPARQL. Per fare un esempio pratico, supponiamo di caricare su MYSQL (installatevelo per lesempio) una collezione di dati provenienti da Outlook e da Eudora, dati che, magari, sono statu estratti con degli script Python e esportati su dei file di estensione csv. Notoriamente Outlook ed Eudora hanno address book e nomi di campi differenti. In APPENDICE c lo script che potete ricavare per la creazione dei due Database ed il caricamento dei dati in formato csv di esempio. Se creassimo solo i due database, noteremmo che esiste una evidente difficolt nellaggregare i dati e rispondere a domande particolari, in tempo reale, come listare tutti i numeri possibili (mobile, phone etc) di una persona, oppure ricavare tutte le informazioni di Bobby Fisher che abita al 2304 Eight Lane e che nellaltro database scritto come Robert L. Fisher 8th Ln. Lidea di base in questo esempio : 1) ottenere prima una rappresentazione RDF dei due database relazionali, sfruttando le D2RQ interface. 2) Con SWOOP editor generare automaticamente lontologia, mappare i campi dei due database 3) Ottenere la sintassi RDF/OWL per sfruttarla in futuro al variare dei dati 4) Sfruttare Pellet e/o SPARQL per le query o il framework kbfe che li mette assieme Per poter ottenere tutto questo, serve un http-server che implementa le D2RQ interface; suggeriamo ad esempio lutilizzo dellefficace d2r-server (scaricatelo); il vantaggio che d2r-server fornisce una serie di utility che implementano le D2RQ interface. In particolare le D2RQ interface permettono, attraverso un file di mapping, di tradurre la richiesta SPARQL ad una query per il database relazionale. Inoltre le D2RQ interface e d2r-server forniscono delle utility (generate-mapping) che generano il mapping in formato N3 (notation 3). Per lesempio scaricate lo zip example.zip contenente i due csv dal link: https://docs.google.com/leaf?id=0BxVqe5c4FHDsNTljNDhmZWEtM2Y0Yy00MDU4LTkyOWUtNzY3ZTE1MzE 4MWRj&hl=en_US 1

Su MySQL create prima i due DB. come in appendice, eventualmente modificate il comando LOAD INFILE DATA per puntare alla cartella dove metterete i due file csv che vi siete scaricati in example.zip. Poi unzippate d2r_server.zip in C:. A questo punto vi consiglio di inserire una variabile dambiente D2RQ_HOME che punta alla home e aggiungere a PATH anche %D2RQ_HOME%\bin Ora possiamo metterci nella directory dei file csv e lanciare il comando che permette di creare i file di mapping che ci servono:
%D2RQHOME%\generate-mapping o jdbc:mysql://localhost:<port>/eudora %D2RQHOME%\generate-mapping o jdbc:mysql://localhost:<port>/outlook eudoraMapping.ttl u <user> -p <password>

outlookMapping.ttl

<user>

-p

<password>

Al posto delle variabili <user>, <password> e <port> inserire i vostri valori del DB Mysql. Otterrete intanto due file con estensione .ttl di mapping nella directory. Adesso vediamo con d2r_server cosa possiamo fare con uno dei file di mapping prodotti. Innanzitutto spostiamoci di directory: cd %D2RQ_HOME% Qui ci copiamo i file generati e poi facciamo il comando: dr2-server eudoraMapping.ttf A questo punto possiamo usare il browser poter accedere al D2R server: http://localhost:2020/ Naturalmente 2020 la porta di configurazione di default, se non lavete cambiata; altrimenti usate quella che avete configurato.

Il D2R Server vi da la possibilit di esplorare le tabelle e il database, ma la cosa pi interessante la parte 3: SPARQL Endpoint che vi d pi in basso il link this AJAX-based SPARQL Explorer che vi fa accedere ad un browser SPARL per fare query (vedi figura).

La prima query di assaggio di solito : SELECT ?s ?p ?o WHERE { ?s ?p ?o . }

Con cui otterrete la visualizzazione di un bel po di record. Ora gli possiamo chiedere qualcosa come: Fammi vedere tutte le informazioni di un telefonino che ha numero (328) 618-8442, che in SPARQL va tradotto in:
SELECT ?p ?o WHERE { ?s vocab:entries_mobile "(328) 618-8442" . ?s ?p ?o . }

Quando lanciate la query, la sessione dove avete startato prima d2r-server con il file di mapping scrolla tutti i record.

Potremmo ripetere la stessa cosa per laltro file di mapping, con un analogo comando differenziato per quello che ci interessa; ma vedremmo solo i dati dellaltro e forse sono anche diversi. Quindi non ci rimane che combinare opportunamente le due cose; ma come? Serve creare un altro mapping che permetta di fare le query contemporaneamente su i due database. Ognuno dei file di mapping precedente ha map: come prefix. Lo cambiamo in emap: in quello di eudora e omap: in quello di outlook. Un altro cambiamento utile quello sostituire il prefix vocab: in eud: per il mapping eudora e in out: quello di outlook e combiniamo i due file in uno solo (past & cut) che chiamiamo comboMapping.ttl ed eliminiamo le duplicazioni. Occorre stare attenti a mapping come questo:
emap:entries_email1 a d2rq:PropertyBridge; d2rq:belongsToClassMap emap:entries; d2rq:property eud:entries_email1; # d2rq:column "entries.email1"; d2rq:uriPattern "mailto:@@entries.email1@@"; d2rq:condition "entries.email1 <> ''";

Se consultate il D2RQ User Manual and Language Specification [DR1] si capisce che le property sono un bridge per legare colonne del DB con lRDF oppure in alternativa si possono usare le d2rq:uriPattern come nellesempio sopra e commentare le column. In questo secondo modo pi chiara la connessione tra i diversi dati. Dire per a D2RQ di voler leggere una mail joe@boh.com non per un URI; ecco perch mettiamo anche il prefisso mailto:; inoltre per dobbiamo mettere anche la condizione che la stringa entries.email1 non sia vuota, cos non avremo nel caso di stringa vuota un mailto: da solo e inutile. Dopo questo cambiamento occorre fare qualcosa di analogo per outlook nella parte
omap:entries_email2Address a d2rq:PropertyBridge; d2rq:belongsToClassMap omap:entries; d2rq:property out:entries_email2Address;

d2rq:column "entries.email2Address"; d2rq:uriPattern "mailto:@@entries.email2Address@@"; d2rq:condition "entries.email2Address <> ''";

A questo punto siamo pronti e facciamo: cd %D2RQ_HOME% e copiamo qui comboMapping.ttl ed eseguiamo il comando: dr2-server comboMapping.ttl Con delle prove potreste aver bisogno, come me, di commentare in due diversi punti: #jdbc:autoReconnect "true"; #jdbc:zeroDateTimeBehavior "convertToNull";

Ora siamo in grado di fare query SPARQL, come prima con Snorql (linterfaccia Web). Le D2RQ interface offrono anche una fantastica utility dump, che sfruttiamo per generare dati caricabili poi da un editor come SWOOP: cd %D2RQ_HOME% dump-rdf m comboMapping.ttl f RDF/XML o datadump.rdf Per lhelp digitare dump-rdf a vuoto. Dateci unocchiata che utile. Ho dovuto usare f per ottenere un file caricabile da SWOOP. Fate partire SWOOP adesso. Fate File->Load->Ontology e caricate datadump.rdf

Ora salviamolo con Save AS con nome postSwoop.rdf e confrontiamo quello che abbiamo ottenuto con datadump.rdf: SWOOP ci ha creato una semplice ontologia, aggiungendo OWL dichiarazioni come:
<owl:DatatypeProperty rdf:about="&eudora;entries_address"/> <owl:DatatypeProperty rdf:about="&eudora;entries_city"/> <owl:DatatypeProperty rdf:about="&eudora;entries_country"/> <owl:DatatypeProperty rdf:about="&eudora;entries_fax"/>

Enrichement ontologico: le equivalenze colonne - uno a uno (mapping) A questo punto si potrebbe usare SWOOP per arricchire lontologia per quando si faranno query SPARQL. Ad esempio, iniziamo a dire che la colonna workState del DB eudora equivalente alla colonna businessState del Db outlook. Vediamo come. Su SWOOP a sinistra a met pagina, clicchiamo sul tab Property Tree e troviamo entries_workState. A destra c il tab RDF/XML dove si vede la rappresentazione owl:DatatypeProperty di workState e dice tra laltro anche a quale file di provenienza associato. Ritorniamo al Concise Format. Selezionando sempre entries_workState da Property Tree, cliccate su Editable in alto a destra e poi su Equivalent to: per creare il mapping o lequivalenza scegliendo il campo di outlook entries_businessState e fare Apply_Changes.

Ora salviamo lontologia o con Save As oppure con Ctrl-S. Riguardate il file postSwoop.rdf vedrete che c il cambiamento di questultima cosa. Cercate workState e vdrete laggiunta dellequivalente. 6

Ovviamente si possono fare anche altre integrazioni come


Enrichment ontologico: equivalenze semantiche

eudora:entries_firstName equivalente a

outlook:entries_firstName, eudora:entries_lastName equivalente a outlook:entries_lastName, etc.

Unaltra cosa che potrebbe essere utile per individuare tutti i numeri di telefono di Adams di indicare che tutte le phone properties hanno la semantica in comune. Vediamo come: basta creare una nuova phone properties e tutte le altre metterle come sotto propriet. Clicchiamo a sinistra su Add P. Sulla dialog box selezionare OWL Datatype Property ( una stringa), poi Sub-property-of a None, come ID mettiamo phone e facciamo Add & Close. Selezioniamo phone e facciamo adesso SuperProperty Add e mettiamo come sotto propriet le seguenti:
entries_businessFax entries_businessPhone entries_businessPhone2 entries_carPhone entries_homeFax entries_homePhone entries_homePhone2 entries_mobile entries_mobilePhone entries_otherPhone entries_phone entries_primaryPhone entries_workMobile

Fate Apply Changes. Non lo dimenticate. Ora ci rimane di fare unaltra cosa: dobbiamo far capire al Reasoner Pellet dopo che Bobby Fisher e Robert L. Fisher sono la stessa persona. Noi abbiamo le properties emap:entries_email1 e out:entries_email2Address (basta guardare il tab RDF/XML in corrispondenza dei campi nel Property Tree), che gi met dellopera. Ora basta aggiungere che si tratta di funzioni inverse e che, quindi, poich lemail sar uguale per emap: e out: allora Robert L. Fisher equivalente a Bobby Fisher. Selezioniamo entries_email1 e facciamo a destra Inverse of Add e mettiamo esntries_email2Address. Apply Changes. Facciamo ancora Ctrl-S e salviamo il file ottenuto. In realt su questo file si possono gi fare query, non necessariamente con d2r_server, ma anche con il framework kbfe. Per noi vogliamo anche che lontologia la possiamo usare anche per gestire nuove versioni dati del database (anche se ci fossero dei cambiamenti), allora la strategia di avere una ontologia che non fa riferimento ai dati (le istanze) a cui va associata un file rdf in cui ci sono i dati. In tal caso dobbiamo usare un editor per eliminare le parti che SWOOP genera sullistanze (valorizzazione delle classi); in particolare va tagliata la parte sotto <!-- Instances --> e salvare il file come properties.owl Ora immaginiamo che i dati su MYSQL hanno subito delle modifiche (update). Occorre allora riottenere il file datadump.rdf come prima e sfruttare properties.owl per fare le query SPARQL; ma la query va fatta su un file merge dei due e che chiameremo combo.rdf. Come facciamo il merge? Nel nostro esempio per semplificare, usiamo il datadump di prima (il concetto non cambia) e per combinare i due file ci creiamo un XSLT di trasformazione rdfcat.xsl (vedi APPENDICE) in modo che quando lo lanciamo otteniamo un solo file RDF combinato. Vedi la riga xsltproc &dash;&dash;xinclude rdfcat.xsl rdfcat.xml > combo.rdf che equivale a:
xsltproc --xinclude rdfcat.xsl rdfcat.xml > combo.rdf

Per poter fare queste operazioni necessario installare xsltproc su Windows (vedi http://www.zlatkovic.com/libxml.en.html o da http://xmlsoft.org/XSLT/downloads.html )

In questo caso combo.rdf lunione che ci serviva delle ontologie e dei due DB e possiamo fare adesso le query su esso. Riferimenti [DR1] http://www4.wiwiss.fu-berlin.de/bizer/d2rq/spec/20061030/

APPENDICE Creazione dei 2 DB e caricamento dei cvs Comando da usare: mysql -u <user> --password=<Password> < createDatabase.sql
# Create two address book databases and load data into them. # No warrantee expressed or implied. # In case they already exist... #DROP DATABASE eudora; #DROP DATABASE outlook; CREATE DATABASE eudora; USE eudora; CREATE TABLE entries ( nickname VARCHAR(20), email1 VARCHAR(50), fullName VARCHAR(30), firstName VARCHAR(15), lastName VARCHAR(20), address VARCHAR(60), city VARCHAR(20), state CHAR(2), country CHAR(20), zip CHAR(10), phone CHAR(21), fax CHAR(21), mobile CHAR(21), web CHAR(70), workOrganization VARCHAR(30), workTitle VARCHAR(20), workAddress VARCHAR(60), workCity VARCHAR(20), workState CHAR(2), workCountry CHAR(20), workZip CHAR(10), workPhone CHAR(21), workFax CHAR(21), workMobile CHAR(21), workWebAddress CHAR(70), otherEmail VARCHAR(50), otherPhone VARCHAR(20), otherWebPages VARCHAR(60),

notes VARCHAR(256), PRIMARY KEY (lastName,firstName) ); LOAD DATA INFILE 'c:/owlrdbms/eudora.csv' INTO TABLE entries FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' LINES TERMINATED BY '\r\n'; CREATE DATABASE outlook; USE outlook; CREATE TABLE entries ( title VARCHAR(20), firstName VARCHAR(15), middleName VARCHAR(15), lastName VARCHAR(20), suffix VARCHAR(5), company VARCHAR(30), department VARCHAR(30), jobTitle VARCHAR(20), businessStreet VARCHAR(30), businessStreet2 VARCHAR(30), businessStreet3 VARCHAR(20), businessCity VARCHAR(20), businessState VARCHAR(2), businessPostalCode VARCHAR(10), businessCountry VARCHAR(20), homeStreet VARCHAR(30), homeStreet2 VARCHAR(30), homeStreet3 VARCHAR(20), homeCity VARCHAR(20), homeState VARCHAR(2), homePostalCode VARCHAR(10), homeCountry VARCHAR(20), otherStreet VARCHAR(30), otherStreet2 VARCHAR(30), otherStreet3 VARCHAR(20), otherCity VARCHAR(20), otherState VARCHAR(2), otherPostalCode VARCHAR(10), otherCountry VARCHAR(20), assistantsPhone CHAR(21), businessFax CHAR(21), businessPhone CHAR(21), businessPhone2 CHAR(21), callback CHAR(21), carPhone CHAR(21), companyMainPhone CHAR(21), homeFax CHAR(21), homePhone CHAR(21), homePhone2 CHAR(21), ISDN CHAR(21), mobilePhone CHAR(21), otherFax CHAR(21), otherPhone CHAR(21), pager CHAR(21), primaryPhone CHAR(21), radioPhone CHAR(21), TTYTDDPhone CHAR(21), telex CHAR(21), account VARCHAR(20), anniversary VARCHAR(20), assistantsName VARCHAR(30), billingInformation VARCHAR(30),

birthday VARCHAR(20), businessAddressPOBox VARCHAR(10), categories VARCHAR(30), children VARCHAR(40), directoryServer VARCHAR(20), emailAddress VARCHAR(50), emailType VARCHAR(20), emailDisplayName VARCHAR(50), email2Address VARCHAR(50), email2Type VARCHAR(20), email2DisplayName VARCHAR(50), email3Address VARCHAR(50), email3Type VARCHAR(20), email3DisplayName VARCHAR(50), gender CHAR(1), governmentIDNumber VARCHAR(20), hobby VARCHAR(30), homeAddressPOBox VARCHAR(10), initials VARCHAR(3), internetFreeBusy VARCHAR(20), keywords VARCHAR(30), language VARCHAR(5), location VARCHAR(20), managersName VARCHAR(30), mileage VARCHAR(5), notes VARCHAR(256), officeLocation VARCHAR(20), organizationalIDNumber VARCHAR(10), otherAddressPOBox VARCHAR(10), priority VARCHAR(3), private CHAR(1), profession VARCHAR(20), referredBy VARCHAR(30), sensitivity VARCHAR(16), spouse VARCHAR(30), user1 VARCHAR(20), user2 VARCHAR(20), user3 VARCHAR(20), user4 VARCHAR(20), webPage VARCHAR(60), PRIMARY KEY (lastName,firstName) ); LOAD DATA INFILE 'c:/owlrdbms/outlook.csv' INTO TABLE entries FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' LINES TERMINATED BY '\r\n';

RDFCAT.XSL <!-Assumes use of an XSLT processor with xinclude processor. For xsltproc, run with xinclude parameter, e.g. xsltproc &dash;&dash;xinclude rdfcat.xsl rdfcat.xml > combo.rdf No warrantee expressed or implied.

--> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" xmlns:owl="http://www.w3.org/2002/07/owl#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <xsl:strip-space elements="*"/> <xsl:output indent="yes"/>

10

<!-- The owl namespace declaration in the following is only here so that it doesn't get added to each rdf:Description element. To really reduce the bulk of the output, add another namespace declaration after the xmlns:owl one for the namespace of the elements being replaced by the "*[@rdf:about]" template rule (the main namespace being output by d2r1). Without this, that namespace declaration gets added to every single child of the rdf:Description element. --> <xsl:template match="rdfcat"> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:owl="http://www.w3.org/2002/07/owl#"> <xsl:apply-templates/> </rdf:RDF> </xsl:template> <!-- Previous template rule covers the rdf:RDF element, so we don't need to copy it from the input to the output. --> <xsl:template match="rdf:RDF"> <xsl:apply-templates select="*"/> <!-- OWL DL software will want to see each resource identified as part of a class. --> <xsl:for-each select="//@rdf:resource"> <!-- But don't bother with the ones in OWL, RDF, or RDFS elements. --> <xsl:if test="namespace-uri(..) != 'http://www.w3.org/2002/07/owl#' and namespace-uri(..) != 'http://www.w3.org/2000/01/rdf-schema#' and namespace-uri(..) != 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' "> <owl:Thing rdf:about="{.}"/> </xsl:if> </xsl:for-each> </xsl:template> <!-- OWL DL software will want to see the class of each subject identified. --> <xsl:template match="rdf:Description[@rdf:about]"> <!-- If @rdf:about starts with #, make it a full URL. --> <xsl:variable name="rdfAboutPrefix"> <xsl:if test="substring(@rdf:about,1,1) = '#'"> <xsl:text>http://localhost/addressbook</xsl:text> </xsl:if> </xsl:variable> <rdf:Description rdf:about="{$rdfAboutPrefix}{@rdf:about}"> <rdf:type> <owl:Class rdf:about="{local-name()}"/> </rdf:type> <xsl:apply-templates select="node()"/> </rdf:Description> </xsl:template>

<xsl:template match="rdf:Descriptin"> <xsl:apply-templates/> </xsl:template>

<!-- Just copy everything else verbatim. --> <xsl:template match="@*|node()"> <xsl:copy> <xsl:apply-templates select="@*|node()"/> </xsl:copy> </xsl:template> </xsl:stylesheet>

11