0

I have a query such as this-

WITH xtbl AS (SELECT xmltype ('<root>
                    <parent>
                        <item>
                            <item_detail>AAA</item_detail>
                            <item_amount>1000</item_amount>
                        </item>
                        <item>
                            <item_detail>BBB</item_detail>
                            <item_amount>2000</item_amount>
                        </item>
                    </parent>
                </root>') AS xcol FROM dual)
SELECT xmlcast (
           xmlquery ('root/parent/string-join[item/item_detail/text()]' PASSING xcol RETURNING CONTENT) AS VARCHAR2 (2000))
           AS item_details
  FROM xtbl;

Although the example specified above contains an node, in my real problem not every <parent> node will contain an <item> node. Thus I cannot use the XMLTable solution as described in this answer.

The SELECT xmlcast(... query is what I have tried so far above and it isn't working. My expected output is this-

ITEM_DETAILS
------------------
AAA 1000, BBB 2000

Please suggest how I can modify my XMLQuery.

2 Answers 2

2

You can use an XMLTable call still:

SELECT x.*
FROM xtbl
CROSS JOIN xmltable('/root/parent/item'
       PASSING xcol
       COLUMNS item_detail VARCHAR2(10) path 'item_detail',
               item_amount NUMBER path 'item_amount'
) x;

ITEM_DETAI ITEM_AMOUNT
---------- -----------
AAA               1000
BBB               2000

and then concatenate and aggregate:

SELECT listagg(x.item_detail ||' '|| x.item_amount, ', ')
  WITHIN GROUP (ORDER BY item_num) AS item_details
FROM xtbl
CROSS JOIN xmltable('/root/parent/item'
       PASSING xcol
       COLUMNS item_detail VARCHAR2(10) path 'item_detail',
               item_amount NUMBER path 'item_amount',
               item_num FOR ORDINALITY
) x;

ITEM_DETAILS                                      
--------------------------------------------------
AAA 1000, BBB 2000

For a parent with no items that will just return null.

However, you can also do it with an XMLQuery call:

SELECT xmlquery(
  'let $d :=
     for $i in /root/parent/item
       return concat($i/item_detail, " ", $i/item_amount)
   return string-join($d, ", ")'
  PASSING xcol
  RETURNING CONTENT
) AS item_details
FROM xtbl;

ITEM_DETAILS                                      
--------------------------------------------------
AAA 1000, BBB 2000

and again with a parent with no items that just returns null.

The let $d := part concatenates the detail and amount for each item, with a space between them, using concat(). Then all the $d values that generates are aggregated using string-join().

That returns an XMLType, so to get the result as a plain string you can use XMLCast as in your question, or more simply use .getStringVal():

SELECT xmlquery(
  ...
).getStringVal() AS item_details
FROM xtbl;
Sign up to request clarification or add additional context in comments.

Comments

1
WITH xtbl AS (SELECT xmltype ('<root>
                    <parent>
                        <item>
                            <item_detail>AAA</item_detail>
                            <item_amount>1000</item_amount>
                        </item>
                        <item>
                            <item_detail>BBB</item_detail>
                            <item_amount>2000</item_amount>
                        </item>
                    </parent>
                </root>') AS xcol FROM dual)
SELECT xmlcast (
           xmlquery (
           'string-join(/root/parent/item/string-join((*), " "), ", ")' 
           PASSING xcol
           RETURNING CONTENT) AS VARCHAR2 (2000)
       )
       AS item_details
  FROM xtbl;

EDIT: There isn't anything special there with *. It is the short hand for child::*

The inner string-join: /root/parent/item/string-join(child::*, " ") joins the values of each child element of /root/parent/item (here it refers to <item_detail> and <item_amount>. the delimiter is a space.

outer string-join for its part, joins the results of each inner string-join with the limiter of a comma.

EDIT-2:

'string-join(/root/parent/item/string-join(
                                           (item_detail, item_amount)
                                           , " "
                                          )
             , ", "
            )' 

3 Comments

Please explain your answer. What is the significance of * in string-join((*)...?
updated. pls see the edit. Try changing the delimiter for each string-join() and test it out.
Is it possible to qualify the nodes instead of just using *? How is it done?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.