Mini-mémoire BDA (T2 2006), Cecile de Mauroy, Thibault Devedeux
Le langage XML émerge comme standard d’échange de données
sur le Web.
Il peut être intéressant de vouloir stocker des données
XML dans un SGBD relationnel, et c’est dans ce cadre que s’inscrit
cette étude. Après avoir choisi une méthode de stockage
de données XML dans un SGBD relationnel, nous nous intéressons
ici à la traduction de requêtes XPath en requêtes SQL.
Plan de l’étude :
1) Choix d’une méthode
de stockage pour notre problème : un stockage découlant de la
relation de parenté.
2) Exemple d’application du stockage sur un fichier XML: menu.xml
3) Menu.xml: représentation arborescente
4) Stockage dans la base de données relationnelle
1) Exemples de requêtes
SQL correspondant à un XPath
2) Recherche d’une méthode systématique de traduction.
Lorsque l’on veut stocker
des données XML dans une base de données relationnelle, plusieurs
mappings sont possibles, certains privilégiant plutôt la rapidité
de la recherche et d’autres un stockage moindre.
Nous avons choisi ici une méthode de stockage centrée sur la relation
parent/enfant :
- une table « Parenté » permet de stocker la relation parent/enfant,
les balises et la position.
- une table « Feuilles » contient les différentes valeurs
attribuées.
Il s’agit d’un format de stockage générique, intéressant pour trouver une méthode de traduction systématique.
Afin de mieux visualiser le stockage, nous allons nous pencher sur ‘exemple du fichier « menu.xml ».
<menu>
<entree>
<nom>Salade</nom>
<prix>4 euros</prix>
</entree>
<plat_princ>
<nom>Entrecote frites</nom>
<prix>8 euros</prix>
</plat_princ>
<dessert>
<nom>Boule de glace</nom>
<parfums>
<parfum>Vanille</parfum>
<parfum>Chocolat</parfum>
</parfums>
<prix>3,5 euros</prix>
</dessert>
</menu>

Nous effectuons le stockage dans les tables « Parenté » et « Feuilles ». On peut ainsi reconstituer le fichier XML de façon exacte.
Parenté |
||||
Id |
Parent |
Label |
Type |
Position |
i1 |
i0 |
menu |
ref |
1 |
i2 |
i1 |
entree |
ref |
1 |
i3 |
i1 |
plat_princ |
ref |
2 |
i4 |
i1 |
dessert |
ref |
3 |
i5 |
i2 |
nom |
cdata |
1 |
i6 |
i2 |
prix |
cdata |
2 |
i7 |
i3 |
nom |
cdata |
1 |
i8 |
i3 |
prix |
cdata |
2 |
i9 |
i4 |
nom |
cdata |
1 |
i10 |
i4 |
parfums |
ref |
2 |
i11 |
i4 |
prix |
cdata |
3 |
i12 |
i10 |
parfum |
cdata |
1 |
i13 |
i10 |
parfum |
cdata |
2 |
| Feuilles |
|
Id |
Valeur |
i5 |
Salade |
i6 |
4
euros |
i7 |
Entrecote
Frites |
i8 |
8
euros |
i9 |
Boule
de glace |
i11 |
3,5
euros |
i12 |
Vanille |
i13 |
Chocolat |
Nous allons maintenant chercher une méthode systématique de traduction de XPath en requêtes SQL. Afin de nous familiariser avec le problème, nous allons tout d’abord étudier quelques exemples avant de trouver un algorithme systématique.
XPATH : /menu/entree/nom
SELECT P3.id
FROM Parente P1, Parente P2, Parente P3
WHERE P3.Label = "nom" and
P2.Label = "entree" and
P2.id = P3.Parent and
P1.Label = "menu" and
P1.id = P2.Parent and
P1.Parent = 0;
Cet exemple permet de trouver
une méthode pour traduire les / dans les XPaths.
Le / entre deux balises a et b, indiquant la parenté, se traduit par
les « clauses where » suivantes dans la requête SQL :
- une clause établissant le lien de parenté
- les clauses spécifiant le label de la balise (a ou b)
Enfin, si l’un des labels possède une valeur (en fin de parcours), on peut effectuer la jointure entre la table « Feuille » et la table « parenté ».
XPATH : //nom
SELECT P1.id
FROM Parente P1
WHERE P1.Label = "nom";
Cet exemple illustre le //
dans le X-Path.
On a alors les clauses where suivantes dans la requête SQL :
- une pour le type de label
- une pour effectuer la jointure avec la table des valeurs.
XPATH : //*[parfum]
SELECT P1.id
FROM Parente P1, Parente P2
WHERE P2.Label = "parfum" and
P2.Parent = P1.id;
Avec //*[parfum], on cherche
un nœud ayant pour fils le label ‘parfum’.
On a alors :
- une clause where servant à établir la parenté avec les
nœuds fils.
- une clause where servant à préciser le label des nœuds
fils (parfum).
XPATH :/menu/dessert[parfums/parfum="Vanille"]/nom
SELECT P5.id
FROM Parente P1, Parente P2, Parente P3, Parente P4, Parente P5, Feuilles F
WHERE P5.Label = "nom" and (1)
P5.Label = P2.id and (2)
P2.Label = "dessert" and (3)
P1.Label = "menu" and (4)
P1.id = P2.Parent and (5)
P4.Label = "parfum" and (6)
P3.Label = "parfums" and (7)
P3.id = P4.Parent and (8)
P3.Parent = P2.id and (9)
P4.id = F.id and (10)
F.Valeur = \"Vanille\" and (11)
P1.Parent = 0; (12)
Ce XPath plus complexe invite
à se pencher sur les prédicats [ ].
Il faut trouver les clauses where correspondant au XPath entre les [ ]. Ici
il s’agit des clauses 6, 7, 8, 10, 11.
Il faut ensuite effectuer le lien de parenté entre les [ ] et le reste
du XPath : clause 9.
XPATH : //parfum[position()=2]
SELECT P1.id
FROM Parente P1
WHERE P1.Label = "parfum" and
P1.position = 2;
Cet exemple fait intervenir une fonction : la fonction position(). Comme la position est indiquée dans la table « Parenté », il est facile d’établir la clause where correspondante.
Avant d’effectuer l’implémentation, établissons le design de la traduction.
Grâce aux exemples,
nous avons établi différents cas à étudier :
- a/b
- //a
- //*[X]
- a/b[X]
- a[position()=n]
Pour trouver un algorithme systématique, nous allons établir un tableau permettant de différencier les cas.
Les « mots » XPath seront représentés par des lettres majuscules, et les éléments par des lettres minuscules.
Le tableau ci-dessous n’est pas exhaustif, et permet juste de définir un moyen d’implémentation pour les cas principaux.
| X-Path |
Requête SQL correspondante |
| X/a |
SELECT
P1.id P2.id correspond au nœud avec lequel il faut établir la parenté dans X |
| //a |
SELECT
P1.id |
| //*[a] |
SELECT
P1.id |
| //*[X] |
SELECT
P1.id FROM Parenté P1, ParentéP2, tables concernées par X |
| //X |
Requête correspondant à X avec SQL, sans qu’il soit nécessaire d’être raccordé à la racine. |
| X/b[Z]/Y |
SELECT
P4.id |
| //a[position()=n] |
SELECT
P1.id |
(Voir le code de l’implémentation
en annexe)
Notre implémentation se décompose en 2 parties principales :
- l’analyse de la requête XPath entrante
- la traduction suivant le cas dans lequel on se trouve
Avant de traiter la requête,
il convient d’analyser ses caractéristiques.
Une requête XPath de base est du type /X[Y]Z, avec possibilité
de définir des axes, d’utiliser des fonctions…
Ainsi, on détecte la présence
- d’un prédicat (soit celle de [ et ])
- de l’axe // en début de requête
- de *
- d’autres éléments suite au prédicat
- d’un test de valeur dans le prédicat (on peut ne vouloir que
les éléments qui ont un nœud N, comme on peut vouloir ceux
qui ont une feuille F="texte")
- de la fonction position() dans le prédicat
On s’est limité à la fonction position(), et à l’utilisation de * dans les requêtes du type /(ou //)elem1/elem2/…/elemn/*
En effet, l'étoile
* sélectionne tous les éléments localisés par ce
qui la précède dans le chemin.
Par exemple
- /A/B/C/* sélectionne tous les éléments inclus dans les
éléments /A/B/C/
- /*/*/*/E sélectionne tous les éléments E qui ont trois
ancêtres. Il est assez difficile dans l’implémentation actuelle,
de détecter les 3 *, et de définir les clauses amenant au résultat
désirée.
Suite à l’analyse,
on obtient plusieurs booléens, qui définissent la requête.
De même, les éléments avant, dans, et à la suite
du prédicat sont stockés dans un tableau (en utilisant la fonction
PHP explode, par rapport à, entre autres, la chaine "/").
On utilise les booléens
pour définir :
- les tables qui seront utilisées dans la clause FROM
- les conditions de la clause WHERE permettant de réaliser les jointures
L’implémentation s’est avérée difficile en
ce qui concerne la conservation de la syntaxe de la requête SQL (ne pas
avoir deux « and » qui se suivent, par exemple), et l’expression
de toutes les informations contenues dans la requête XPath.
Il aurait été peut être plus « propre » de différencier les cas, en utilisant des expressions régulières prédéfinies, et en essayant de les faire correspondre à la requête entrante, mais l’utilisation des Regex n’est pas triviale…
Ci-dessous, une capture d’écran du traitement d’une requête :

Site : http://www.tgl0be.org/projets/bda/
Nous avons ainsi mis en place une méthode de traduction XPath/SQL. Ceci
peut permettre de consulter des données XML stockées sous forme
d’un SGBD relationnel.
Néanmoins, les requêtes contiennent beaucoup de jointures sur des
tables qui peuvent être de grande taille, et l’on pourrait chercher
à optimiser le problème. Le mapping choisi ici permet une faible
capacité de stockage mais augmente par contre la taille des tables et
donc la rapidité de la recherche.
Nous nous sommes limités ici à une méthode de stockage
précise, mais il existe plusieurs mappings possibles. L’optimisation
du problème peut varier d’un mapping à l’autre, certains
favorisant la recherche dans les tables et d’autres le stockage. Les méthodes
de traduction XPath/SQL sont différentes selon le stockage de la base
de données, pouvant inviter à une étude de la traduction
selon la méthode de stockage utilisée.