0

Let's say we have the following XML:

<root>
  <row>
    <column>row 1 col 1</column>
    <column>row 1 col 2</column>
    <column>row 1 col 3</column>
  </row>
  <row>
    <column>row 2 col 1</column>
    <column>row 2 col 2</column>
    <column>row 2 col 3</column>
  </row>
  <row>
    <column>row 3 col 1</column>
    <column>row 3 col 2</column>
    <column>row 3 col 3</column>
  </row>
</root>

How do I transpose this, using T-SQL XQuery to:

<root>
    <column>
        <row>row 1 col 1</row>
        <row>row 2 col 1</row>
        <row>row 3 col 1</row>
    </column>
    <column>
        <row>row 1 col 2</row>
        <row>row 2 col 2</row>
        <row>row 3 col 2</row>
    </column>
    <column>
        <row>row 1 col 3</row>
        <row>row 2 col 3</row>
        <row>row 3 col 3</row>
    </column>
</root>
2
  • Are there always three rows & columns, or do you want it to work with an arbitrary number? Commented Jan 15, 2010 at 10:56
  • @AalashM it would be best if it could work in any case. Commented Jan 15, 2010 at 11:03

2 Answers 2

1

I suspect there might be a really nice approach using PIVOT, but I don't know it well enough to be able to say for sure. What I offer here works. I have split it up into chunks for better formatting and to provide commentary:

To start with let's capture the example data

-- Sample data
DECLARE @x3 xml

SET @x3 = '
<root>
  <row>
    <column>row 1 col 1</column>
    <column>row 1 col 2</column>
    <column>row 1 col 3</column>
  </row>
  <row>
    <column>row 2 col 1</column>
    <column>row 2 col 2</column>
    <column>row 2 col 3</column>
  </row>
  <row>
    <column>row 3 col 1</column>
    <column>row 3 col 2</column>
    <column>row 3 col 3</column>
  </row>
</root>
'

DECLARE @x xml
SET @x = @x3

-- @x is now our input

Now the actual transposing code:

Establish the size of the matrix:

WITH Size(Size) AS
(
    SELECT CAST(SQRT(COUNT(*)) AS int) 
    FROM @x.nodes('/root/row/column') T(C)
)

Shred the data, use ROW_NUMBER to capture the index (the -1 is to make it zero based), and use modulo and integer divide on the index to work out the new row and column numbers:

,Flattened(NewRow, NewCol, Value) AS
(
    SELECT
        -- i/@size as old_r, i % @size as old_c, 
        i % (SELECT TOP 1 Size FROM Size) AS NewRow, 
        i / (SELECT TOP 1 Size FROM Size) AS NewCol, 
        Value
    FROM (
        SELECT
            (ROW_NUMBER() OVER (ORDER BY C)) - 1 AS i, 
            C.value('.', 'nvarchar(100)') AS Value
        FROM @x.nodes('/root/row/column') T(C)
        ) ShreddedInput
)

With this CTE FlattenedInput available, all we now need to do is get the FOR XML options and query structure right and we're done:

SELECT
    (
        SELECT Value 'column'
        FROM
            Flattened t_inner
        WHERE
            t_inner.NewRow = t_outer.NewRow
        FOR XML PATH(''), TYPE
    ) row
FROM
    Flattened t_outer
GROUP BY NewRow
FOR XML PATH(''), ROOT('root')

Sample output:

<root>
  <row>
    <column>row 1 col 1</column>
    <column>row 2 col 1</column>
    <column>row 3 col 1</column>
  </row>
  <row>
    <column>row 1 col 2</column>
    <column>row 2 col 2</column>
    <column>row 3 col 2</column>
  </row>
  <row>
    <column>row 1 col 3</column>
    <column>row 2 col 3</column>
    <column>row 3 col 3</column>
  </row>
</root>

Works on any size 'square' data. Note the lack of sanity checking / error handling.

Sign up to request clarification or add additional context in comments.

Comments

0
SET @x3 = ' 
<root> 
  <row> 
    <column>row 1 col 1</column> 
    <column>row 1 col 2</column> 
    <column>row 1 col 3</column> 
  </row> 
  <row> 
    <column>row 2 col 1</column> 
    <column>row 2 col 2</column> 
    <column>row 2 col 3</column> 
  </row> 
  <row> 
    <column>row 3 col 1</column> 
    <column>row 3 col 2</column> 
    <column>row 3 col 3</column> 
  </row> 
</root> 
' 

select @x3 = replace(@x3,'<row>','<rowtemp>')
select @x3 = replace(@x3,'<column>','<row>')
select @x3 = replace(@x3,'<rowtemp>','<column>')

select @x3

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.