How can I combine CTEs with a FOR XML clause?
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 UNION
s with FOR XML
, I've done some UNION
ing 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%" /&开发者_运维百科gt;
</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?
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
.
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
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.
精彩评论