1

I want to pivot a table from long to wide that has multiple id columns. I found solutions for one column but not really for multiple columns. The closest solution that I could adapt for one column was this one T-SQL PIVOT data from long form to wide by a date

My table looks more or less like this,

create table t (id int, date date, varA_id int, VarB_id int, value int)
insert into t values
 (1,'2005-01-20',1, 1,197)
,(2,'2005-01-20',1,2,58)
,(3,'2005-01-20',1,3,90)
,(4,'2005-01-20',2,1,210)
,(5,'2005-01-20',2,2,133)
,(6,'2005-01-20',2,3,67)
,(7,'2005-01-20',3,1,87)
,(8,'2005-01-20',3,2,87)
,(9,'2005-01-20',3,3,87)

Actually without the date, but that's fine. I want to spread in a way that I get columns for each permutation of VarA_id and VarB_id

So my expected result would look like this

enter image description here

My actual table has three _id columns and more permutations, so I really need a generic solution.

Based on the other solution in my link I was hoping something like this would work. I adjust the top part that creates the column names and this would work. I dont know how to realy adjust the bottom part that fetches the values.

declare @cols nvarchar(max);
declare @sql  nvarchar(max);
  select @cols = stuff((
    select distinct 
      ', ' + 'VarA_'+convert(varchar(10),varA_id) + '_VarB_'+convert(varchar(10),varB_id)
      from t 
      order by 1
      for xml path (''), type).value('.','nvarchar(max)')
    ,1,2,'')
select  @sql = '
 select Id, date, ' + @cols + '
  from  (
    select Id, date, varA_id = ''v''+convert(varchar(10),varA_id), value
      from t
      ) as t
 pivot (sum([value]) for [varA_id] in (' + @cols + ') ) p'
select @sql
exec(@sql);
Max M
  • 806
  • 14
  • 29
  • Expected results would really help us help you, but it *sounds* like you want a dynamic pivot. In truth, this is best done in your presentation layer, not the database layer. – Thom A Nov 26 '21 at 09:36
  • u mean after extracting the data to another software like R or Power Query? – Max M Nov 26 '21 at 09:42
  • I mean in what ever application is displaying the data to the end user; presumably some kind of reporting software as that's normally why such requirements are wanted. – Thom A Nov 26 '21 at 09:44
  • There are some other reason why I would need to do this within SQL. I changed the data and added the output picture – Max M Nov 26 '21 at 09:48
  • 1
    I *assume* that there could be more than 9 rows for a single date? – Thom A Nov 26 '21 at 09:49
  • You can't have an arbitrary number of columns in SQL, the language. Columns are like properties in an object. They have to be known at query time – Panagiotis Kanavos Nov 26 '21 at 09:50
  • Does this answer your question? [Group by column and multiple Rows into One Row multiple columns](https://stackoverflow.com/questions/63521138/group-by-column-and-multiple-rows-into-one-row-multiple-columns) – Thom A Nov 26 '21 at 09:50
  • Yes my real data has three id columns with more than three entries each, so that I would end up with #VarA x #VarB x #VarC columns and entries per date – Max M Nov 26 '21 at 09:51
  • Yep, dynamic pivot. The dupe I've proposed is what you are after. You need to pivot on the row number, as well as the value; it's messy, it's not friendly, and (if you aren't *very* familiar with T-SQL) difficult to understand, but if you are going down this rabbit hole, it's *you* who needs to understand it. Which is why it's recommended you *don't* do this in SQL. – Thom A Nov 26 '21 at 09:52
  • What are you trying to do? `some other reason` what reasons? All reporting tools can pivot data. It's easier than trying to do so in SQL. You can't create a query without specifying the columns in the query itself. If you want to handle an arbitrary number of variables, especially nested ones, you *can't* put them in columns. At the very least you'd have to determine the actual column names and add them using dynamic SQL – Panagiotis Kanavos Nov 26 '21 at 09:53
  • I need to fetch the data from the data base within another program where it need to be wide. Explaining which programs and such would not really help. I added an adapted solution from the other question, to better show what I want. @larnu does your other question really help me? I want to compose the var names based on the ids within my table as it is done in my referenced question – Max M Nov 26 '21 at 10:00
  • Wait, it's based on the ID, not the row number? So what happens when you have 2 different dates? You just have a load of `NULL` columns..? Then we're back to a (for lack of better word) "Normal" dynamic pivot. – Thom A Nov 26 '21 at 10:04
  • *"within another program"* So why isn't *that* program pivoting the data? – Thom A Nov 26 '21 at 10:04

2 Answers2

1

The main problem with your dynamic sql?
It was that the name constructed in the source query didn't match the generated column names.

Here's a fix :

declare @cols varchar(max) = null;
declare @sql  nvarchar(max);

select @cols = concat(@cols+', '+char(10), quotename(concat('VarA_', varA_id, '_VarB_', varB_id))) 
from test
group by varA_id, varB_id
order by varA_id, varB_id;

-- select @cols as cols;

set @sql = 'select * '+char(10)+
  'from ( ' +char(10)+
  ' select [date], [value], ' +char(10)+
  ' concat(''VarA_'',varA_id,''_VarB_'',varB_id) as Col ' +char(10)+
  ' from test ' +char(10)+
  ') as src ' +char(10)+
  'pivot (sum([value]) for Col in ('+char(10)+ @cols +char(10)+')) pvt';
 
-- select @sql as sql;

exec(@sql);
date VarA_1_VarB_1 VarA_1_VarB_2 VarA_1_VarB_3 VarA_2_VarB_1 VarA_2_VarB_2 VarA_2_VarB_3 VarA_3_VarB_1 VarA_3_VarB_2 VarA_3_VarB_3
2005-01-20 197 58 90 210 133 67 87 87 87

db<>fiddle here

LukStorms
  • 28,916
  • 5
  • 31
  • 45
  • I just had to change the part to define `@cols`, Ill accept yours, cause it is better than mine `select @cols = stuff(( select distinct ', ' + 'VarA_'+convert(varchar(10),varA_id)+ '_VarB_' +convert(varchar(10),varB_id) from t order by 1 for xml path (''), type).value('.','nvarchar(max)') ,1,2,'')` – Max M Nov 26 '21 at 13:30
  • 1
    Thanks. And I just noticed that you had used a DISTINCT. So I changed the solution by adding a GROUP BY for the creation of the field names. – LukStorms Nov 26 '21 at 13:41
  • Ah did not think of that, and that is more understandable for me then the other code, that I do not realy understand – Max M Nov 26 '21 at 13:53
0

Ok my own solution so far is to add a help column and basically just do what the other questions does. I need to improve on this, so I dont add a column and I would like better names, but at least this shows what I want.

alter table t add help_col nvarchar(10)
Update t
set help_col=convert(varchar(10),varA_id)+convert(varchar(10),varB_id)

declare @cols nvarchar(max);
declare @sql  nvarchar(max);
  select @cols = stuff((
    select distinct 
      ', ' + 'v'+convert(varchar(10),help_col)
      from t 
      order by 1
      for xml path (''), type).value('.','nvarchar(max)')
    ,1,2,'')
select  @sql = '
 select date, ' + @cols + '
  from  (
    select date, help_col = ''v''+convert(varchar(10),help_col), value
      from t
      ) as t
 pivot (sum([value]) for [help_col] in (' + @cols + ') ) p'
select @sql
exec(@sql);

Which results in

   select date, v11, v12, v13, v21, v22, v23, v31, v32, v33    from  (      select date, help_col = 'v'+convert(varchar(10),help_col), value        from t        ) as t   pivot (sum([value]) for [help_col] in (v11, v12, v13, v21, v22, v23, v31, v32, v33) ) p

which yields

date        v11 v12 v13 v21 v22 v23 v31 v32 v33
2005-01-20  197 58  90  210 133 67  87  87  87
Max M
  • 806
  • 14
  • 29