Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
456 views
in Technique[技术] by (71.8m points)

sql server - How to convert row values to columns with dynamic columns count?

I have the following data:

DECLARE @DataSource TABLE
(
     [ColumnA] INT
    ,[ColumnB] INT
    ,[ColumnC] INT
)

INSERT INTO @DataSource ([ColumnA], [ColumnB], [ColumnC])
VALUES   (5060,1006,100118)
        ,(5060,1006,100119)
        ,(5060,1006,100120)
        ,(5060,1007,100121)
        ,(5060,1007,100122)
        ,(5060,1012,100123)

SELECT [ColumnA]
      ,[ColumnB]
      ,[ColumnC]
FROM @DataSource

enter image description here

and I need to converted like this:

enter image description here

The difficult part is that the data is dynamic (I do not know how many columns I will have) and I am not able to use a standard pivot here because the values in ColumnC are different and as a result I am going to have as many columns as values appears in ColumnC.

Is there any technique to achieve this? Any kind of help (answers, articles, suggestions) will be appreciated.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

My suggestion whenever you are working with PIVOT is to alway write the query first with the values hard-coded, then you can easily convert the query to a dynamic solution.

Since you are going to have multiple values of columnC that will be converted to columns, then you need to look at using the row_number() windowing function to generate a unique sequence for each columnc based on the values of columnA and columnB.

The starting point for your query will be:

select [ColumnA],
  [ColumnB],
  [ColumnC],
  'SampleTitle'+
  cast(row_number() over(partition by columna, columnb
                          order by columnc) as varchar(10)) seq
from DataSource;

See Demo. This query will generate the list of new columns names SampleTitle1, etc:

| COLUMNA | COLUMNB | COLUMNC |          SEQ |
|---------|---------|---------|--------------|
|    5060 |    1006 |  100118 | SampleTitle1 |
|    5060 |    1006 |  100119 | SampleTitle2 |
|    5060 |    1006 |  100120 | SampleTitle3 |

You can then apply the pivot on columnC with the new column names listed in seq:

select columnA, columnB, 
  SampleTitle1, SampleTitle2, SampleTitle3
from
(
   select [ColumnA],
    [ColumnB],
    [ColumnC],
    'SampleTitle'+
      cast(row_number() over(partition by columna, columnb
                              order by columnc) as varchar(10)) seq
   from DataSource
) d
pivot
(
  max(columnc)
  for seq in (SampleTitle1, SampleTitle2, SampleTitle3)
) piv;

See SQL Fiddle with Demo.

Once you have the correct logic, you can convert the data to dynamic SQL. The key here is generating the list of new column names. I typically use FOR XML PATH for this similar to:

select STUFF((SELECT distinct ',' + QUOTENAME(seq) 
                from
                (
                  select 'SampleTitle'+
                    cast(row_number() over(partition by columna, columnb
                                            order by columnc) as varchar(10)) seq
                  from DataSource
                ) d
        FOR XML PATH(''), TYPE
        ).value('.', 'NVARCHAR(MAX)') 
    ,1,1,'')

See Demo. Once you have the list of column names, then you will generate your sql string to execute, the full code will be:

DECLARE @cols AS NVARCHAR(MAX),
    @query  AS NVARCHAR(MAX)

select @cols = STUFF((SELECT distinct ',' + QUOTENAME(seq) 
                    from
                    (
                      select 'SampleTitle'+
                        cast(row_number() over(partition by columna, columnb
                                                order by columnc) as varchar(10)) seq
                      from DataSource
                    ) d
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')

set @query = 'SELECT columnA, ColumnB,' + @cols + ' 
             from 
             (
               select [ColumnA],
                [ColumnB],
                [ColumnC],
                ''SampleTitle''+
                  cast(row_number() over(partition by columna, columnb
                                          order by columnc) as varchar(10)) seq
               from DataSource
            ) x
            pivot 
            (
                max(columnc)
                for seq in (' + @cols + ')
            ) p '

execute sp_executesql @query;

See SQL Fiddle with Demo. These give a result:

| COLUMNA | COLUMNB | SAMPLETITLE1 | SAMPLETITLE2 | SAMPLETITLE3 |
|---------|---------|--------------|--------------|--------------|
|    5060 |    1006 |       100118 |       100119 |       100120 |
|    5060 |    1007 |       100121 |       100122 |       (null) |
|    5060 |    1012 |       100123 |       (null) |       (null) |

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...