2010-02-23 7 views
0
Using IBM DB2 database, I have a three relational tables: 

    Project: id, title, description 
    Topic: projectId, value 
    Tag: projectId, value 

I need to produce the following XML file from the previous table: 

    <projects> 
     <project id="project1"> 
     <title>title1</title> 
     <description>desc1</description> 
     <topic>topic1</topic> 
     <topic>topic2</topic> 
     <tag>tag1</tag> 
     <tag>tag2</tag> 
     <tag>tag3</tag> 
     </project> 
     ... 
    </projects> 


I've tried the following query, and it works: 

    XQUERY 
    let $projects := db2-fn:sqlquery('SELECT XMLELEMENT(NAME "project", XMLATTRIBUTES(id, title, description)) AS project FROM mydb.Project') 
    let $TopicSet := db2-fn:sqlquery('SELECT XMLELEMENT(NAME "row", XMLATTRIBUTES(projectId, value)) FROM mydb.Topic') 
    let $TagSet := db2-fn:sqlquery('SELECT XMLELEMENT(NAME "row", XMLATTRIBUTES(projectId, value)) FROM mydb.Tag') 

    for $project in $projects return 
    <project> 
    {$project/@ID} 
    <title>{$project/fn:string(@TITLE)}</title> 
    <description>{$project/fn:string(@DESCRIPTION)}</description> 
    {for $row in $TopicSet[@PROJECTID=$project/@ID] return <Topic>{$row/fn:string(@VALUE)}</Topic>} 
    {for $row in $TagSet[@PROJECTID=$project/@ID] return <Tag>{$row/fn:string(@VALUE)}</Tag>} 
    </project> 
    ; 

However, it took 9 hours to complete (there 200k projects in the table) 

How can I improve that? 
Do I really need to create the three intermediate db2-fn:sqlquery to achieve this? is there another way? 
Would it be faster if I create these 3 three intermediate db2-fn:sqlquery and put them in a table (with only one row and one attribute), and then index this before querying the "for $project in $projects return" part? 


Or, how would you proceed to achieve my goal? 


Best regards, 
David 

--- 
As proposed by Peter Schuetze, I tried the XMLAGG as follows: 
SELECT 
XMLSERIALIZE(
    XMLDOCUMENT(
    XMLELEMENT(
     NAME "Project", 
     XMLATTRIBUTES(P.project), 
     XMLAGG(XMLELEMENT(NAME "Topic", Topic.value)), 
     XMLAGG(XMLELEMENT(NAME "Tag", Tag.value)), 
    ) 
) AS CLOB(1M) 
) 
FROM mydb.project P 
LEFT JOIN mydb.Topic Topic ON (P.project = Topic.project) 
LEFT JOIN mydb.Tag Tag ON (P.project = Tag.project) 
GROUP BY P.project; 

This works indeed much much faster! 
However, if a project has not any topic, it will still display topic element, with a blank text, such as: 
    <projects> 
     <project id="project1"> 
     <title>title1</title> 
     <description>desc1</description> 
     <topic></topic> 
     <tag>tag1</tag> 
     <tag>tag2</tag> 
     <tag>tag3</tag> 
     </project> 
     ... 
    </projects> 
How to remove this "<topic></topic>"? 

Répondre

0

Regardez la fonction XMLAGG. Cela devrait être parfait pour votre besoin. Je ne l'ai pas encore essayé mais l'exemple sur la page liée est presque exactement ce que vous voulez faire.

+0

Merci, ça marche beaucoup plus vite comme ça. Toutefois, des éléments vides apparaissent maintenant lorsqu'il n'y a pas de sujet. (voir la question éditée ci-dessus). Une idée? –

1

Utilisation XMLFOREST au lieu de XMLELEMENT s'il y a la possibilité que la colonne pourrait être NULL et vous ne voulez pas une balise d'élément vide lorsque cela se produit. Donc, pour les sujets, vous souhaitez remplacer sa fonction XMLELEMENT avec

XMLFOREST(Topic.value AS "topic")

Il y a un problème avec la façon dont vous avez inclus deux fonctions xmlagg dans la même instruction SELECT. Si vous avez juste un XMLAGG dans votre déclaration, il n'y a pas de problème, puisque le GROUP BY sur la clé parent va s'effondrer soigneusement les entrées de l'enfant qui sont spécifiées dans XMLAGG. Cependant, lorsque vous spécifiez plus d'une fonction XMLAGG dans la même SELECT, la requête produit un produit cartésien en interne, dans ce cas, vous verrez éléments qui se répètent à l'intérieur de chaque groupe retourné par XMLAGG. L'exemple que vous avez donné avec seulement qu'il y ait des zéro ou un des sujets d'un projet ne démontre pas ce problème, mais si un projet avait deux thèmes et trois balises, vous verriez chaque sujet répété trois fois, et chaque étiquette répétée deux fois. Pour éviter cela, vous devez déplacer chaque XMLAGG vers une sous-requête ou une expression de table commune qui produit un seul fragment XML afin que vous puissiez le référencer en toute sécurité à partir de la requête principale.

Voici un exemple qui pousse le XMLAGG vers le bas dans les expressions de table commune. Il supprime également le besoin de XMLFOREST, car XMLAGG ne produira aucun résultat pour un ensemble d'entrées vide.

 
WITH 
topicxml(projectid, xmlfragment) AS (
SELECT topic.projectid, 
XMLAGG(XMLELEMENT(NAME "topic", topic.value) ORDER BY topic.value) 
FROM mydb.topic topic 
GROUP BY topic.projectid 
), 
tagxml (projectid, xmlfragment) AS (
SELECT projectid, 
XMLAGG(XMLELEMENT(NAME "tag", tag.value) ORDER BY tag.value) 
FROM mydb.tag tag 
GROUP BY tag.projectid 
) 
SELECT XMLSERIALIZE (CONTENT XMLELEMENT(NAME "project", 
XMLATTRIBUTES(p.id AS "id"), 
XMLELEMENT(NAME "title", p.title), 
XMLELEMENT(NAME "description", p.description), 
XMLCONCAT(topicxml.xmlfragment, tagxml.xmlfragment) 
) AS VARCHAR(2000)) 
FROM mydb.project p 
LEFT OUTER JOIN topicxml ON topicxml.projectid = p.id 
LEFT OUTER JOIN tagxml ON tagxml.projectid = p.id 
; 
+0

Cette requête va super rapide, et il résout complètement ma question, merci! –

Questions connexes