-1

I have the following data which I am trying to unpivot. The number of columns I am dealing with goes all the way to F600

enter image description here

Basically the SEQ_NUM row data becomes a column named SEQ_NUM and Cells remains a column but without the SEQ_NUM row and a new column "NewCol" will have the content data except SEQ_NUM row data which is now a column.

I want to get this format. I can now do with UNION ALL and cursor looping through all of the columns from F2 to F600 and cross join with the data from SEQ_NUM but I am thinking there is a better solution.

enter image description here

Dale K
  • 25,246
  • 15
  • 42
  • 71
  • Tag our question with the database you are using. – Gordon Linoff Oct 01 '19 at 20:37
  • Have you tried anything? I'm thinking this is "basic" unpivot with a lot of columns . . . [Unpivot with column name](https://stackoverflow.com/questions/19055902/unpivot-with-column-name) – avery_larry Oct 01 '19 at 21:28
  • The unpivot works fine except for the part where I need to make SEQ_NUM a column. That is where I am stuck. – user1913919 Oct 01 '19 at 21:54

2 Answers2

0

A version of your data that can be worked with is as follows:

create table #t (cells varchar(15), f2 int, f3 int, f4 int);

insert #t values 
    ('seq_num', 1, 2, 3), 
    ('linkA', 4290, 42521, 42551),
    ('linkB', 0, 0, 0),
    ('linkC', 1332, 0, 15);

As avery_larry mentions, this seems like a basic unpivot. I think where you may be thrown off is that 'seq_num' is redundant with the column names. Columns 'f2' to 'f600' are already in order. So just unpivot, extract the integer from the unpivoted column label, and subtract one. And in doing all this, ignore the 'seq_num' row.

select      up.cells,
            seq_num = try_convert(int,replace(up.col, 'f', '')) - 1,
            up.val
from        #t
unpivot     (val for col in (f2, f3, f4)) up -- you write out to 'f600'
where       cells <> 'seq_num';

If you would like to avoid writing out 'f2' to 'f600', you can go dynamic. First build the string that holds all the column names, and then insert that into the 'in' statement.

declare 
    @cols varchar(max) = '',
    @f int = 1,
    @maxF int = 4; -- you change to 600

while @f < @maxF begin
    set @f += 1;
    set @cols += 'f' + convert(varchar(3), @f) + iif(@f <> @maxF, ',', '');
end

declare @sql nvarchar(max) = '
    select      up.cells,
                seq_num = try_convert(int,replace(up.col, ''f'', '''')) - 1,
                up.val
    from        #t
    unpivot     (val for col in (' + @cols + ')) up
    where       cells <> ''seq_num''
';

 exec (@sql);


Edit: Accounting for 'seq_num' not being a simple offset of the field headers

Okay, so you say seq_num isn't just an offset of the column names. Imagine that we swapped out the seq_num lin in the table insert with the following:

insert #t values 
    ('seq_num', 2, 4, 8), 
    ...

You'll have to pretty much separate it from the rest of the rows and treat it independently. But the fortunate thing is that you act on it in parallel fashion. That is, just like my query above unpivots the data for cells <> 'seq_num', you do the exact same thing except for cells = 'seq_num'. You'll need to join the two result sets back together so output the unpivoted field label in each.

Replace the @sql variable above with this:

declare @sql nvarchar(max) = '
    select      up.cells,
                up.col,
                up.val
    from        #t
    unpivot     (val for col in (' + @cols + ')) up
    where       cells @operator ''seq_num''
';

Note that I output up.col, instead of modifying it, and I also replace '<>' with '@operator'. Unlike @cols, '@operator' is directly part of the surrounding string.

Right now, @sql is acting as a template. Use it to create one instance for cells being equal to 'seq_num' and one for cells not equal to 'seq_num'. These instances should be in their own CTE expressions, which you subsequently re-join in the final statement. Overwrite the existing @sql variable with this statement and then execute it, as shown below:

set @sql = '

    with

        linkVals as (' + replace(@sql, '@operator', '<>') + '),
        seqVals as (' + replace(@sql, '@operator', '=') + ')

        select      l.cells, 
                    seq_num = s.val, 
                    l.val
        from        linkVals l
        join        seqVals s on l.col = s.col

';

print (@sql);
exec (@sql);

I put in a print statement in case the final form of @sql is not clear, though you'll likely want to reformat it.

pwilcox
  • 5,542
  • 1
  • 19
  • 31
  • pwilcox thank you so much. This is a good start. while it will work for this example data the issue I was running into was that my other spreadsheets that I need to process don't have easy correlation (munus 1) between the column names (F1, F2 ...) and the SEQ_NUM values. For example, some of them the column name could be F2 but SEQ-NUM could have a value 33 and so on.So, I was looking for away to use the SEQ_NUM row values while pivoting instead of deriving since its not easy to derive from other spreadsheet as they don't meet specific series definition – user1913919 Oct 02 '19 at 01:04
0

Worst case scenario you can do it the long way:

select b.f2 as newcol, b.cells, a.f2 as seq_num from t a cross join t b where a.cells = 'SEQ_Num' and b.cells <> 'SEQ_Num'
union select b.f3, b.cells, a.f3 from t a cross join t b where a.cells = 'SEQ_Num' and b.cells <> 'SEQ_Num'
union select b.f4, b.cells, a.f4 from t a cross join t b where a.cells = 'SEQ_Num' and b.cells <> 'SEQ_Num'
union select b.f5, b.cells, a.f5 from t a cross join t b where a.cells = 'SEQ_Num' and b.cells <> 'SEQ_Num'
union select b.f6, b.cells, a.f6 from t a cross join t b where a.cells = 'SEQ_Num' and b.cells <> 'SEQ_Num'
union select b.f7, b.cells, a.f7 from t a cross join t b where a.cells = 'SEQ_Num' and b.cells <> 'SEQ_Num'
union select b.f8, b.cells, a.f8 from t a cross join t b where a.cells = 'SEQ_Num' and b.cells <> 'SEQ_Num'
union select b.f9, b.cells, a.f9 from t a cross join t b where a.cells = 'SEQ_Num' and b.cells <> 'SEQ_Num'
union select b.f10, b.cells, a.f10 from t a cross join t b where a.cells = 'SEQ_Num' and b.cells <> 'SEQ_Num'
union select b.f18, b.cells, a.f18 from t a cross join t b where a.cells = 'SEQ_Num' and b.cells <> 'SEQ_Num'
order by seq_num, cells

Not cute, but gets the job done.

The Impaler
  • 45,731
  • 9
  • 39
  • 76