sql-serverxmlt-sqlsql-server-2019

Produce nested xml from table; what method is best Path, Element?


I need to produce XML out of my table in the format like below. Can you advise what method I should go with (Path, Element, Raw) or could it be a combination of several?

Please find below my test data and some code I just started. Also see the requested XML format with a lot of nests. Can this be done in a single SELECT or can I compose various parts by level?

SELECT *  INTO #t FROM (
SELECT  2020 yy, 'Alpha' CompanyName, 
   'BossA'  FirstName_Owner, 'Smith'  LastName_Owner,  'John1' FirstName_Client , 'Dow1'  LasttName_Client,'Yes'  Active union
SELECT  2020 yy, 'Alpha' CompanyName, 
   'BossA'  FirstName_Owner, 'Smith'  LastName_Owner,  'Peter2' FirstName_Client  , 'Redd2'  LasttName_Client,'No'   Active UNION
SELECT  2020 yy, 'Alpha' CompanyName, 
 'BossyBB' FirstName_Owner, 'Green'  LastName_Owner, 'Mary' FirstName_Client ,  'Robbins3'  LasttName_Client,'Yes'  Active
)a  ---   SELECT TOP 100 * FROM #t           --    DROP TABLE IF EXISTS  #t


SELECT    yy,   CompanyName  
  --SELECT 
  -- FirstName_Owner,   LastName_Owner,   
  -- FirstName_Client , LasttName_Client,  Active  
FROM #t
FOR XML RAW('CompanyInfo') , ELEMENTS -- Path
--FOR XML RAW('Client'), ROOT ('Clients')  

Requested output in XML format for my test data:

<GemReport> 
    <Version>2.0</Version> 
    <Company>  
        <CompanyInfo> 
                     <Year>2020</Year> 
                     <CompanyName>Alpha</CompanyName> 
            <Owners> 
                <Owner> 
                        <FirstName>John</FirstName> 
                        <LastName>Doe</LastName> 
                    <Clients> 
                        <Client> 
                             <FirstName>John1</FirstName> 
                             <LastName>Doe1</LastName> 
                             <Contract> 
                                <Active>Yes</Active> 
                             </Contract> 
                        </Client> 
                        <Client> 
                             <FirstName>Peter2</FirstName> 
                             <LastName>Redd2</LastName> 
                             <Contract> 
                                <Active>NO</Active> 
                             </Contract> 
                        </Client> 
                    </Clients> 
                </Owner> 
                <Owner> 
                        <FirstName>BossyBB</FirstName> 
                        <LastName>Green</LastName> 
                    <Clients> 
                        <Client> 
                             <FirstName>Mary</FirstName> 
                             <LastName>Robbins3</LastName> 
                             <Contract> 
                                <Active>Yes</Active> 
                             </Contract> 
                        </Client>               
                </Owners> 
        </CompanyInfo> 
    </Company> 
</GemReport>

enter image description here


Solution

  • As your design is denormalised, you're going to need to use some self JOINs here using CTEs to the DISTINCT values of the company and company owner details. Then you can nest your FOR XML PATHs for each layer (Owners, and Clients) with subqueries.

    Most likely getting a performant solution, with a larger dataset, will actually be to fix your design and normalise it; then you don't need to scan the same table multiple times, and instead can have a table for the companies, owners, and clients, each with relevant primary/foreign key constraints and indexes.

    USE Sandbox;
    GO
    
    SELECT *
    INTO dbo.YourTable
    FROM (SELECT 2020 AS yy,
                 'Alpha' AS CompanyName,
                 'BossA' AS FirstName_Owner,
                 'Smith' AS LastName_Owner,
                 'John1' AS FirstName_Client,
                 'Dow1' AS LasttName_Client,
                 'Yes' AS Active
          UNION --ALL? 
          SELECT 2020 AS yy,
                 'Alpha' AS CompanyName,
                 'BossA' AS FirstName_Owner,
                 'Smith' AS LastName_Owner,
                 'Peter2' AS FirstName_Client,
                 'Redd2' AS LasttName_Client,
                 'No' AS Active
          UNION --ALL? 
          SELECT 2020 AS yy,
                 'Alpha' AS CompanyName,
                 'BossyBB' AS FirstName_Owner,
                 'Green' AS LastName_Owner,
                 'Mary' AS FirstName_Client,
                 'Robbins3' AS LasttName_Client,
                 'Yes' AS Active) a;
    
    GO
    WITH Companies AS(
        SELECT DISTINCT
               CompanyName,
               yy
        FROM dbo.YourTable),
    CompanyOwners AS(
        SELECT DISTINCT
               CompanyName,
               yy,
               FirstName_Owner,
               LastName_Owner
        FROM dbo.YourTable)
    SELECT 2.0 AS Version,
           (SELECT C.yy AS [Year],
                   C.CompanyName AS [CompanyName],
                   (SELECT CO.FirstName_Owner AS FirstName,
                           CO.LastName_Owner AS LastName,
                           (SELECT YT.FirstName_Client AS FirstName,
                                   YT.LasttName_Client AS LastName,
                                   YT.Active AS [Contract/Active]
                            FROM dbo.YourTable YT
                            WHERE YT.yy = CO.yy
                              AND YT.CompanyName = CO.CompanyName
                              AND YT.FirstName_Owner = CO.FirstName_Owner --I hope this can't be NULL
                              AND YT.LastName_Owner = CO.LastName_Owner --I hope this can't be NULL
                            FOR XML PATH('Client'),ROOT('Clients'),TYPE)
                    FROM CompanyOwners CO
                    WHERE CO.yy = C.yy
                      AND CO.CompanyName = C.CompanyName
                    FOR XML PATH('Owner'),ROOT('Owners'),TYPE)
            FROM Companies C
            FOR XML PATH('CompanyInfo'), ROOT('Company'),TYPE)
    FOR XML PATH('GemReport')
    
    GO
    DROP TABLE dbo.YourTable;
    

    Which produces the following XML:

    <GemReport>
      <Version>2.0</Version>
      <Company>
        <CompanyInfo>
          <Year>2020</Year>
          <CompanyName>Alpha</CompanyName>
          <Owners>
            <Owner>
              <FirstName>BossA</FirstName>
              <LastName>Smith</LastName>
              <Clients>
                <Client>
                  <FirstName>John1</FirstName>
                  <LastName>Dow1</LastName>
                  <Contract>
                    <Active>Yes</Active>
                  </Contract>
                </Client>
                <Client>
                  <FirstName>Peter2</FirstName>
                  <LastName>Redd2</LastName>
                  <Contract>
                    <Active>No</Active>
                  </Contract>
                </Client>
              </Clients>
            </Owner>
            <Owner>
              <FirstName>BossyBB</FirstName>
              <LastName>Green</LastName>
              <Clients>
                <Client>
                  <FirstName>Mary</FirstName>
                  <LastName>Robbins3</LastName>
                  <Contract>
                    <Active>Yes</Active>
                  </Contract>
                </Client>
              </Clients>
            </Owner>
          </Owners>
        </CompanyInfo>
      </Company>
    </GemReport>
    

    Note, this does not match the OP's required XML, as that is not valid XML. The last Clients and Owner nodes are not closed. I assumed this was in error.