FMFF
FMFF

Reputation: 1718

Add an attribute to the XML Column from another column in the same/another table

Here's my scenario:

--ORDER table
OrderID OrderCode DateShipped    ShipmentXML
1       ABC       08/06/2013     <Order><Item CustomerName="BF" City="Philadelphia" State="PA"></Item></Order>
2       XYZ       08/05/2013     <Order><Item CustomerName="TJ" City="Richmond" State="VA"></Item></Order>

At some point in the process, I will know the respective TrackingNumber for these Orders. The tracking numbers are available in another table like this:

 --TRACKING table
 TrackingID    OrderCode   TrackingNumber    
 98            ABC         1Z1               
 99            XYZ         1Z2

The output I'm expecting is as below:

   OrderID OrderCode     ShipmentXML
   1       ABC           <Order><Item CustomerName="BF" City="Philadelphia" State="PA" DateShipped="08/06/2013" TrackingNumber="1Z1"></Item></Order>
   2       XYZ           <Order><Item CustomerName="TJ" City="Richmond" State="VA" DateShipped="08/05/2013" TrackingNumber="1Z2"></Item></Order>`

As you can see, I'm trying to get the TrackingNumber and the DateShipped for each OrderCode and have them as an attribute. The intent is a SELECT, not UPDATE.

All the examples I've seen demonstrate how to update the XML with a Constant value or a variable. I couldn't find one that demonstrates XML updates with a JOIN. Please help with how this can be accomplished.

UPDATE:

By 'Select not Update', I meant that no updates to the permanent table; UPDATE on temp tables are perfectly fine, as Mikael commented below the first answer.

Upvotes: 2

Views: 2019

Answers (3)

roman
roman

Reputation: 117345

Prevous answer is good, but you have to explicitly specify columns and cast them into varchar, and that's not good for future support (if you add attributes to ShipmentXML you'll have to modify the query).
Instead, you could use XQuery:

select
    O.OrderID, O.OrderCode,
    (
        select
            (select O.DateShipped, T.TrackingNumber for xml raw('Item'), type),
            O.ShipmentXML.query('Order/*')
        for xml path(''), type
    ).query('<Order><Item>{for $i in Item/@* return $i}</Item></Order>')
from [ORDER] as O
    left outer join [TRACKING] as T on T.OrderCode = O.OrderCode

or even like this:

select
    O.OrderID, O.OrderCode,
    O.ShipmentXML.query('
            element Order {
                element Item {
                    attribute DateShipped {sql:column("O.DateShipped")},
                    attribute TrackingNumber {sql:column("T.TrackingNumber")},
                    for $i in Order/Item/@* return $i
                }
            }')
from [ORDER] as O
    left outer join [TRACKING] as T on T.OrderCode = O.OrderCode

see sqlfiddle with examples

Upvotes: 3

Mikael Eriksson
Mikael Eriksson

Reputation: 138960

A version using a temp table to add the attributes to the XML.

select OrderID,
       OrderCode,
       DateShipped,
       ShipmentXML
into #Order
from [Order]

update #Order
set ShipmentXML.modify
  ('insert attribute DateShipped {sql:column("DateShipped")} 
    into (/Order/Item)[1]')

update O
set ShipmentXML.modify
  ('insert attribute TrackingNumber {sql:column("T.TrackingNumber")} 
    into (/Order/Item)[1]')
from #Order as O
  inner join Tracking as T
    on O.OrderCode = T.OrderCode

select OrderID,
       OrderCode,
       ShipmentXML
from #Order

drop table #Order

Upvotes: 3

i-one
i-one

Reputation: 5120

The only way I know allowing partial modification of data in columns of xml type is using modify method, but as stated in documentation

The modify() method of the xml data type can only be used in the SET clause of an UPDATE statement.

Since UPDATE is not desired, as a workaround I see shredding and reassembling it manually as:

select
    o.OrderID,
    o.OrderCode,
    (
        cast((select
            t.c.value('@CustomerName', 'varchar(50)') as '@CustomerName',
            t.c.value('@City', 'varchar(50)') as '@City',
            t.c.value('@State', 'varchar(50)') as '@State',
            o.DateShipped as '@DateShipped',
            tr.TrackingNumber as '@TrackingNumber'
        for xml path('Item'), root('Order')) as xml)
    ) as ShipmentXML
from
    [ORDER] o
    join [TRACKING] tr on tr.OrderCode = o.OrderCode
    cross apply o.ShipmentXML.nodes('Order/Item') t(c)

You may have to apply formatting to o.DateShipped.

Upvotes: 1

Related Questions