из реляционных таблиц во вложенный XML-файл вывода - PullRequest
0 голосов
/ 23 февраля 2010
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>"?

Ответы [ 2 ]

1 голос
/ 24 февраля 2010

Используйте XMLFOREST вместо XMLELEMENT, если существует вероятность того, что столбец может иметь значение NULL, и вам не нужен пустой тег элемента, когда это происходит. Так что для тем вы бы заменили функцию XMLELEMENT на

XMLFOREST( Topic.value AS "topic" )

Существует проблема с тем, как вы включили две функции XMLAGG в один оператор SELECT. Если в вашем выражении есть только один XMLAGG, проблем нет, так как GROUP BY в родительском ключе аккуратно свернет дочерние записи, указанные в XMLAGG. Однако, когда вы указываете более одной функции XMLAGG в одном и том же SELECT, запрос создает декартово произведение внутри, поэтому в этом случае вы увидите повторяющиеся элементы внутри каждой группы, возвращаемой XMLAGG. Пример, который вы привели с тем, что для проекта было только ноль или одна тема, не демонстрирует этой проблемы, но если в проекте есть две темы и три тега, вы увидите, что каждая тема повторяется три раза, а каждый тег повторяется дважды. Чтобы предотвратить это, вам нужно переместить каждый XMLAGG в подзапрос или общее табличное выражение, которое создает один фрагмент XML, чтобы вы могли безопасно ссылаться на него из основного запроса.

Ниже приведен пример, который толкает XMLAGG в общие табличные выражения. Это также избавляет от необходимости XMLFOREST, так как XMLAGG не даст никаких результатов для пустого набора ввода.

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 голосов
/ 23 февраля 2010

Посмотрите на функцию XMLAGG . Это должно быть идеально подходит для ваших нужд. Я еще не пробовал, но пример на связанной странице - это почти то, что вы хотите сделать.

...