Question

I'm trying to generate some XML with various levels of nesting, and at the risk of over-simplifying, the output XML will be loosely of the format:

<invoice number="1">
 <charge code="foo" rate="123.00">
  <surcharge amount="10%" />
 </charge>
 <charge code="bar" />
</invoice>

The database schema I have inherited for this happens to have charges stored in differing tables, which means that surcharges are stored differently based on the table from where the charge was from.

Given that you cannot use UNIONs with FOR XML, I've done some UNIONing in a CTE, so something along the lines of:

WITH Charges ( [@code], [@rate], surcharge, InvoiceId ) AS (
    SELECT code AS [@Code], amount AS [@rate], NULL as surcharge, InvoiceId
    FROM item.charges
UNION ALL
    SELECT 
        code AS [@Code], 
        amount AS [@rate],
        (
             SELECT amount AS [@amount]
             FROM order.surcharges os
             WHERE oc.ChargeId = os.ChargeId
             FOR XML PATH('surcharge'), TYPE
        ), 
        InvoiceId
    FROM order.charges oc
)
SELECT
    Number AS [@number],
    (
         SELECT
             [@code],
             [@rate],
             surcharge
         FROM Charges
         WHERE Charges.InvoiceId = i.InvoiceId
    )
FROM Invoices i
FOR XML PATH( 'invoice' ), TYPE

Now, that is incredibly close, giving (Note the nested <surcharge>):

<invoice number="1">
 <charge code="foo" rate="123.00">
  <surcharge>
   <surcharge amount="10%" />
  </surcharge>
 </charge>
 <charge code="bar" />
</invoice>

But I need to find a way of getting the end query to include the value of an XML column to be treated as the content of the element, rather than as a new element. Is this possible, or do I need to take a new approach?

Was it helpful?

Solution 2

It appears that naming the (fake) column as "*" will use that the content of that column as the content of the element, so changing the SQL as below makes it work:

WITH Charges ( [@code], [@rate], surcharge, InvoiceId ) AS (
    SELECT code AS [@Code], amount AS [@rate], NULL as surcharge, InvoiceId
    FROM item.charges
UNION ALL
    SELECT 
        code AS [@Code], 
        amount AS [@rate],
        (
             SELECT amount AS [@amount]
             FROM order.surcharges os
             WHERE oc.ChargeId = os.ChargeId
             FOR XML PATH('surcharge'), TYPE
        ), 
        InvoiceId
    FROM order.charges oc
)
SELECT
    Number AS [@number],
    (
         SELECT
             [@code],
             [@rate],
             surcharge AS [*] -- Thsi will embed the contents of the previously generated XML in here.
         FROM Charges
         WHERE Charges.InvoiceId = i.InvoiceId
    )
FROM Invoices i
FOR XML PATH( 'invoice' ), TYPE

OTHER TIPS

You have a column query which returns mulitple rows (@charge, @rate, and an XML type.) I would expect the query you post to give the error:

Only one expression can be specified in the select list when the subquery is not introduced with EXISTS.

However, that's easily fixed by moving the query to an outer apply. To remove the double surcharge element, you could move the XML column names as far to the bottom as possible, like:

;WITH Charges (code, rate, surcharge, InvoiceId) AS 
    (
    SELECT  code, amount, NULL, InvoiceId
    FROM    @charges
    UNION ALL
    SELECT  code
    ,       amount
    ,       (
            SELECT  amount AS [@amount]
            FROM    @surcharges os
            WHERE   oc.ChargeId = os.ChargeId
            FOR XML PATH('surcharge'), TYPE
            )
    ,       InvoiceId
    FROM    @charges oc
)
SELECT  Number AS [@number]
,       c.code as [charge/@code]
,       c.rate as [charge/@rate]
,       c.surcharge as [charge]
FROM    @Invoices i
outer apply
        (
        SELECT  code
        ,       rate
        ,       surcharge
        FROM    Charges
        WHERE   Charges.InvoiceId = i.InvoiceId
        ) c
WHERE   i.InvoiceID = 1
FOR XML PATH( 'invoice' ), TYPE

This would print, for example:

<invoice number="1">
  <charge code="1" rate="1" />
</invoice>
<invoice number="1">
  <charge code="1" rate="1">
    <surcharge amount="1" />
  </charge>
</invoice>

The first element comes from the top part of the union, where surcharge = null.

I think you can do this by omitting the root node type in your "for xml path('surcharge')" statement. That is, use "for xml path('')" instead.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top