In general there are two approaches (well, rather three):
- Use a lot of typing to create a statement covering all your columns. This is ugly but fast. The biggest draw-back: Adding a column in the future will force you to re-do your queries.
- Use a generic approach, which will work with any count of columns. The biggest draw-back: This won't be fast.
- Dynamic SQL: Use Metadata to create the statement of suggestion 1. dynamically. The biggest draw-back: This will never work in ad-hoc/inline queries.
To show you one generic approach you might test this:
DECLARE @tbl TABLE(col1 VARCHAR(10),col2 VARCHAR(10),col3 VARCHAR(10),col4 INT,col5 INT,col6 INT);
INSERT INTO @tbl VALUES
('a','b','c',NULL,500,200)
,('w','x','y',1000,300,NULL)
,('z','g','h',200,NULL,600);
--The generic unpivot query
SELECT t.col1,col2,col3
,ROW_NUMBER() OVER(PARTITION BY col1,col2,col3 ORDER BY B.attr) AS GroupIndex
,B.attr.value('local-name(.)','nvarchar(max)') ColumnName
,B.attr.value('.','int') ColumnValue
FROM @tbl t
CROSS APPLY(SELECT(SELECT t.* FOR XML RAW,TYPE)
.query('<cols>{/row/@*[not(local-name()=("col1","col2","col3"))]}</cols>')) A(x)
CROSS APPLY A.x.nodes('/cols/@*') B(attr);
--This we can use within a PIVOT
query
SELECT p.*
FROM
(
SELECT t.col1,col2,col3
,ROW_NUMBER() OVER(PARTITION BY col1,col2,col3 ORDER BY B.attr) AS GroupIndex
,B.attr.value('local-name(.)','nvarchar(max)') ColumnName
,B.attr.value('.','int') ColumnValue
FROM @tbl t
CROSS APPLY(SELECT(SELECT t.* FOR XML RAW,TYPE)
.query('<cols>{/row/@*[not(local-name()=("col1","col2","col3"))]}</cols>')) A(x)
CROSS APPLY A.x.nodes('/cols/@*') B(attr)
) intermediateResult
PIVOT
(
MAX(ColumnValue) FOR ColumnName IN(col4,col5,col6)
)p;
The idea in short:
- We use
APPLY
create an intermediate XML representation of your columns behind col1,col2 and col3. In order to achieve this, we first create a XML and use query()
to return all columns except col1, col2, col3 as attributes in each row.
- We use XML's default to omit NULL values
- We add this as column
A.x
to the result set.
- Another
APPLY
will call the XML method .nodes()
. This will add one row per existing attribute.
- The
intermediateResult
is an unpivoted set. This is the format you should use for storage actually...
- Now we can use
PIVOT
to get the output wanted.
The biggest advantage is, that you can add col7
without any need to change this (in the generic part). The output-list of PIVOT
will need any new column explicitly.