Merging XML Variables Like a Table Join

  • Hi All,

    I have several stored procedures that will query tables and return xml data. I would like to join these returned xml pieces into one large xml file, but I'm having trouble merging them all together at the correct hierarchical levels. It may be that I have set off down the wrong road and need to be moved onto another, but here is what I am trying to do at the moment:

    One procedure will return the top level information into an xml variable (@TopLevel as demonstrated in the first example below), going down to bottom level of StoreNumber. All other procedures will create an xml variable with a top level of StoreNumber and then levels below that, group by group (in the example, Employees going into @EmployeeInfo, but there are other groups, like maybe @addressInfo and @TakingsInfo and the list will keep changing depending on a set of parametrised requirements).

    So here is what @TopLevel might look like:

    <Regions>

    <Region>

    <Name>North</Name>

    <Stores>

    <Store>

    <StoreNumber>10</StoreNumber>

    </Store>

    </Stores>

    </Region>

    <Region>

    <Name>South</Name>

    <Stores>

    <Store>

    <StoreNumber>11</StoreNumber>

    </Store>

    <Store>

    <StoreNumber>12</StoreNumber>

    </Store>

    </Stores>

    </Region>

    </Regions>

    and the @EmployeeInfo might look like this:

    <Store>

    <StoreNumber>10</StoreNumber>

    <Employees>

    <Employee>

    <Name>John</Name>

    </Employee>

    <Employee>

    <Name>Beverley</Name>

    </Employee>

    </Employees>

    </Store>

    <Store>

    <StoreNumber>11</StoreNumber>

    <Employees>

    <Employee>

    <Name>Billie</Name>

    </Employee>

    </Employees>

    </Store>

    <Store>

    <StoreNumber>12</StoreNumber>

    <Employees>

    <Employee>

    <Name>Joel</Name>

    </Employee>

    </Employees>

    </Store>

    so my question is this - how would I combine these two bits of xml into something that looks like this:

    <Regions>

    <Region>

    <Name>North</Name>

    <Stores>

    <Store>

    <StoreNumber>10</StoreNumber>

    <Employees>

    <Employee>

    <Name>John</Name>

    </Employee>

    <Employee>

    <Name>Beverley</Name>

    </Employee>

    </Employees>

    </Store>

    </Stores>

    </Region>

    <Region>

    <Name>South</Name>

    <Stores>

    <Store>

    <StoreNumber>11</StoreNumber>

    <Employees>

    <Employee>

    <Name>Billie</Name>

    </Employee>

    </Employees>

    </Store>

    <Store>

    <StoreNumber>12</StoreNumber>

    <Employees>

    <Employee>

    <Name>Joel</Name>

    </Employee>

    </Employees>

    </Store>

    </Stores>

    </Region>

    </Regions>

    I had envisioned that the combining would be done by an master procedure, which would check some configuration table and would include the sub-store level details only as required.

    If I were dealing with tables, I would join the @Toplevel table to the @EmployeeInfo table on @Toplevel.StoreNumber = @EmployeeInfo.StoreNumber - is there an equivalent method in xml? Given that I have created the xml from tables in the first place, I don't really want to convert the information back into tables to make the join - it seems good to me that each sub-store level is separated into a different procedure as it keeps everything discrete and tidy, but maybe this approach is unworkable?

    I have made each bit of xml and have them all waiting here, but I just can't glue them together! Any help much appreciated.

  • Here's an example which might prove useful:

    declare @addinfo xml;

    declare @empinfo xml;

    declare @toplevel xml;

    set @toplevel='<Regions>

    <Region>

    <Name>North</Name>

    <Stores>

    <Store>

    <StoreNumber>10</StoreNumber>

    </Store>

    </Stores>

    </Region>

    <Region>

    <Name>South</Name>

    <Stores>

    <Store>

    <StoreNumber>11</StoreNumber>

    </Store>

    <Store>

    <StoreNumber>12</StoreNumber>

    </Store>

    </Stores>

    </Region>

    </Regions>'

    set @empinfo='<Store>

    <StoreNumber>10</StoreNumber>

    <Employees>

    <Employee>

    <Name>John</Name>

    </Employee>

    <Employee>

    <Name>Beverley</Name>

    </Employee>

    </Employees>

    </Store>

    <Store>

    <StoreNumber>11</StoreNumber>

    <Employees>

    <Employee>

    <Name>Billie</Name>

    </Employee>

    </Employees>

    </Store>

    <Store>

    <StoreNumber>12</StoreNumber>

    <Employees>

    <Employee>

    <Name>Joel</Name>

    </Employee>

    </Employees>

    </Store>'

    set @addinfo='<Store>

    <StoreNumber>10</StoreNumber>

    <Address>

    <Street> 123Main</Street>

    <city>AnyTown</city>

    </Address>

    </Store>

    <Store>

    <StoreNumber>11</StoreNumber>

    <Address>

    <Street> 123Main</Street>

    <city>BobVille</city>

    </Address>

    </Store>

    <Store>

    <StoreNumber>12</StoreNumber>

    <Address>

    <Street> 123Main</Street>

    <city>JoeCity</city>

    </Address>

    </Store>'

    ;with TopCTE as (

    selectc.value('(../../Name)[1]','varchar(500)') reg,

    c.value('(./StoreNumber)[1]','int') snum,

    c.query('.') Store

    from @toplevel.nodes('/Regions/Region/Stores/Store') X(c)),

    EmpCTE as (

    select

    c.value('(./StoreNumber)[1]','int') snum,

    c.query('./Employees/*') Emp

    from @empinfo.nodes('/Store') X(c)),

    AddCTE as (

    select

    c.value('(./StoreNumber)[1]','int') snum,

    c.query('./Address/*') Addr

    from @addinfo.nodes('/Store') X(c))

    Select TopOut.reg 'Name',

    (Select topIn.snum 'StoreNumber',

    (Select Emp from EmpCTE where topIn.snum=EmpCTE.snum for XML PATH(''), type) 'Employees',

    (Select Addr from AddCTE where topIn.snum=AddCTE.snum for XML PATH(''), type) 'Address'

    from TopCTE topIn where TopOut.reg=topIn.reg

    for XML PATH('Store'), root('Stores'), TYPE)

    from (select distinct reg from topcte) TopOut for XML PATh('Region'), root('Regions')

    ----------------------------------------------------------------------------------
    Your lack of planning does not constitute an emergency on my part...unless you're my manager...or a director and above...or a really loud-spoken end-user..All right - what was my emergency again?

  • Alternatively, a way that might help if the elements to be inserted can vary by each run - using XML.modify to insert the child elements into the TopLevel document.

    Each child XML could easily be included or not by the use of IF statements.

    -- Store the top level xml in a table (variable) so it can be used in an UPDATE statement

    declare @t table(x xml);

    insert @t(x) values(@toplevel);

    --== Add the Employees nodes into the toplevel XML ==--

    -- This requires a loop as xml.modify doesn't allow multiple updates in one batch

    while exists(

    -- check if there are any unprocessed elements in the empinfo xml fragment

    select 1

    from @t

    cross apply x.nodes('//Store') tl(store)

    cross apply (select tl.store.value('(StoreNumber/text())[1]','int')) as store(number)

    cross apply (select @empinfo.query('(//Store[StoreNumber/text()=sql:column("store.number")]/Employees)[1]')) emp(nd)

    where x.exist('(//Store[StoreNumber/text()=sql:column("store.number")]/Employees)[1]')=0

    )

    update top(1) t

    set x.modify('insert sql:column("emp.nd") as last into (//Store[StoreNumber/text()=sql:column("store.number")])[1]')

    from @t AS t

    cross apply x.nodes('//Store') tl(store) -- Process each Store

    cross apply (select tl.store.value('(StoreNumber/text())[1]','int')) as store(number) -- convert the StoreNumber to a non-xml data type

    cross apply (select cast(@empinfo.query('(//Store[StoreNumber/text()=sql:column("store.number")]/Employees)[1]') as xml)) emp(nd) -- find the related Employees for this store

    where x.exist('(//Store[StoreNumber/text()=sql:column("store.number")]/Employees)[1]')=0 -- prevent updates of existing elements

    --== Add the Address nodes into the toplevel XML ==--

    while exists(

    select 1

    from @t

    cross apply x.nodes('//Store') tl(store)

    cross apply (select tl.store.value('(StoreNumber/text())[1]','int')) as store(number)

    cross apply (select [highlight="#ffff11"]@addinfo[/highlight].query('(//Store[StoreNumber/text()=sql:column("store.number")]/[highlight="#ffff11"]Address[/highlight])[1]')) [highlight="#ffff11"]addr[/highlight](nd)

    where x.exist('(//Store[StoreNumber/text()=sql:column("store.number")]/[highlight="#ffff11"]Address[/highlight])[1]')=0

    )

    update top(1) t

    set x.modify('insert sql:column("[highlight="#ffff11"]addr[/highlight].nd") as last into (//Store[StoreNumber/text()=sql:column("store.number")])[1]')

    from @t AS t

    cross apply x.nodes('//Store') tl(store)

    cross apply (select tl.store.value('(StoreNumber/text())[1]','int')) as store(number)

    cross apply (select cast([highlight="#ffff11"]@addinfo[/highlight].query('(//Store[StoreNumber/text()=sql:column("store.number")]/[highlight="#ffff11"]Address[/highlight])[1]') as xml)) [highlight="#ffff11"]addr[/highlight](nd)

    where x.exist('(//Store[StoreNumber/text()=sql:column("store.number")]/[highlight="#ffff11"]Address[/highlight])[1]')=0

    select x from @t

    I have used the test data from Matt's answer, and have sections for Employees and Address.

    Each new section you want to add in is then just a "copy and paste" of an existing section, then edit the highlighted parts (as seen in the Address section of my code).

    MM



    select geometry::STGeomFromWKB(0x0106000000020000000103000000010000000B0000001000000000000840000000000000003DD8CCCCCCCCCC0840000000000000003DD8CCCCCCCCCC08408014AE47E17AFC3F040000000000104000CDCCCCCCCCEC3F9C999999999913408014AE47E17AFC3F9C99999999991340000000000000003D0000000000001440000000000000003D000000000000144000000000000000400400000000001040000000000000F03F100000000000084000000000000000401000000000000840000000000000003D0103000000010000000B000000000000000000143D000000000000003D009E99999999B93F000000000000003D009E99999999B93F8014AE47E17AFC3F400000000000F03F00CDCCCCCCCCEC3FA06666666666FE3F8014AE47E17AFC3FA06666666666FE3F000000000000003D1800000000000040000000000000003D18000000000000400000000000000040400000000000F03F000000000000F03F000000000000143D0000000000000040000000000000143D000000000000003D, 0);

  • Forum Etiquette: How to post Reporting Services problems
  • [/url]
  • Forum Etiquette: How to post data/code on a forum to get the best help - by Jeff Moden
  • [/url]
  • How to Post Performance Problems - by Gail Shaw
  • [/url]

  • Thank you both; I will have a look at the results from each method and see what can be done. Given that both of these methods require a bit of work to convert the xml back out to table structure before recombining, I think I may need to change my approach entirely. Rather than having several procedures all contributing xml to a larger document, it may be more appropriate to prepare my data in several tables and build the xml at the end in a single pass. I will talk with the other guys in the team and we'll see which way is best.

    It has been an education and very helpful

    Thanks again

    Mark

  • Viewing 4 posts - 1 through 3 (of 3 total)

    You must be logged in to reply to this topic. Login to reply