178

I have a table like this...

CustomerID DBColumnName Data
1 FirstName Joe
1 MiddleName S
1 LastName Smith
1 Date 12/12/2009
2 FirstName Sam
2 MiddleName S
2 LastName Freddrick
2 Date 1/12/2009
3 FirstName Jaime
3 MiddleName S
3 LastName Carol
3 Date 12/1/2009

And I want this...

Is this possible using PIVOT?

CustomerID FirstName MiddleName LastName Date
1 Joe S Smith 12/12/2009
2 Sam S Freddrick 1/12/2009
3 Jaime S Carol 12/1/2009
Anonymous
  • 835
  • 1
  • 5
  • 21
ctrlShiftBryan
  • 27,092
  • 26
  • 73
  • 78

10 Answers10

214

yes, but why !!??

   Select CustomerID,
     Min(Case DBColumnName When 'FirstName' Then Data End) FirstName,
     Min(Case DBColumnName When 'MiddleName' Then Data End) MiddleName,
     Min(Case DBColumnName When 'LastName' Then Data End) LastName,
     Min(Case DBColumnName When 'Date' Then Data End) Date
   From table
   Group By CustomerId
ctrlShiftBryan
  • 27,092
  • 26
  • 73
  • 78
Charles Bretana
  • 143,358
  • 22
  • 150
  • 216
  • 2
    ^^ This worked for me. PIVOT isnt efficient for non-numeric values. – Dienekes Aug 25 '10 at 09:49
  • 6
    This is a great alternative. I was using `Pivot` in my query, then I switched to this and looked at the execution plan for running both together. This approach cost 8% and the Pivot approach took 92%! – mafue Apr 24 '12 at 19:44
  • 3
    Really love this solution, also it makes sure the columns contains the correct data instead of the Pivot one, thanks! – Tenerezza Aug 10 '12 at 11:18
  • 1
    This solved an issue with a dynamic pivot generating rows greater than the allowable maximum row size of 8060. – Nicholas May 27 '15 at 08:04
  • 1
    I had 70 columns of vertical data to "pivot" with text values and this worked way better for me than pivot or 70 joins. Thank you so much. – Gracie Jul 14 '16 at 16:56
  • 2
    This work great! But how do I prevent - `Warning: Null value is eliminated by an aggregate or other SET operation` – John Mills Oct 17 '18 at 20:05
  • 1
    This as far as I am concerned is THE only way to do this because it handles duplicate rows flawlessly which is the problem I was having. Excellent solution. – dyslexicanaboko Oct 18 '19 at 18:35
  • Thank you this worked great for pivoting qualitative data and saved me from trying to decipher massive t-sql "solutions" on code project. Very elegant. – Mwspencer May 30 '20 at 18:05
  • @JohnMills, you'll have to wrap the isnull() function around the relevant fields in your query. – Andrew Jens Feb 27 '21 at 05:20
  • Saved my Day, PIVOT is not working as there is space in values – Muhammad Waqas Aziz Aug 12 '21 at 06:06
  • Adding my thanks to the many coming up on the 12 year anniversary of this answer haha...much easier solution for categorical data! – pauliec Aug 13 '21 at 13:09
129

You can use the MAX aggregate, it would still work. MAX of one value = that value..

In this case, you could also self join 5 times on customerid, filter by dbColumnName per table reference. It may work out better.

gbn
  • 422,506
  • 82
  • 585
  • 676
  • 1
    that actually wont work if you have 2 costumers with the same first name – Leonardo Feb 20 '15 at 18:38
  • 1
    That will work. Remember that DBColumnName is metadata - you literally filter by "CustomerID = 1 AND DBColumnName = 'FirstName'". Of course, this breaks if you have multiple FirstName rows for a given CustomerID, but if you are creating your tables properly both CustomerID and DBColumnName are part of your primary key... – 4AM Jun 01 '16 at 15:51
  • 11
    Some code/mocking as an example would have been great and made this answer perfectly complete. – DavidScherer Jul 12 '18 at 20:42
43
WITH pivot_data AS
(
SELECT customerid, -- Grouping Column
dbcolumnname, -- Spreading Column
data -- Aggregate Column
FROM pivot2 
)
SELECT customerid, [firstname], [middlename], [lastname]
FROM pivot_data
PIVOT (max(data) FOR dbcolumnname IN ([firstname],[middlename],[lastname])) AS p;
Vishwanath Dalvi
  • 35,388
  • 41
  • 123
  • 155
  • 4
    This should be the accepted answer as it shows the proper use of the TSQL Pivot command. – Ubercoder Jan 02 '19 at 15:05
  • 2
    It's worth noting that in this query, "pivot2" is the name of the table in which the original data resides. Also, use of the CTE here is superfluous - the `SELECT` statement below the CTE could just have specified the name of the original table. – STLDev Mar 30 '19 at 02:24
  • @STLDev Actually STLDev that's not how pivot works. We don't know all of the columns in table "pivot2". There could, in fact, be other columns OP did not specify that are in the table. So unless you restrict the columns - using a CTE or Derived table query - then ALL columns in the table are used in the grouping. In other words, the PIVOT returns something but not what we expect. This is a concept covered in-depth for the 70-761 certification exam. – Zorkolot Jul 01 '19 at 17:57
  • 4
    It's worth noting that PIVOT automatically groups by what ever columns are not used in the PIVOT itself. So in this example [data] and [dbcolumnname] are in the PIVOT so everything will be grouped by [CustomerId] – Sal Jul 16 '19 at 19:50
13

Ok, sorry for the poor question. gbn got me on the right track. This is what I was looking for in an answer.

SELECT [FirstName], [MiddleName], [LastName], [Date] 
FROM #temp 
PIVOT
(   MIN([Data]) 
    FOR [DBColumnName] IN ([FirstName], [MiddleName], [LastName], [Date]) 
)AS p

Then I had to use a while statement and build the above statement as a varchar and use dynmaic sql.

Using something like this

SET @fullsql = @fullsql + 'SELECT ' + REPLACE(REPLACE(@fulltext,'(',''),')','')
SET @fullsql = @fullsql + 'FROM #temp '
SET @fullsql = @fullsql + 'PIVOT'
SET @fullsql = @fullsql + '('
SET @fullsql = @fullsql + ' MIN([Data])'
SET @fullsql = @fullsql + ' FOR [DBColumnName] IN '+@fulltext
SET @fullsql = @fullsql + ')'
SET @fullsql = @fullsql + 'AS p'

EXEC (@fullsql)

Having a to build @fulltext using a while loop and select the distinct column names out of the table. Thanks for the answers.

ctrlShiftBryan
  • 27,092
  • 26
  • 73
  • 78
9
SELECT
main.CustomerID,
f.Data AS FirstName,
m.Data AS MiddleName,
l.Data AS LastName,
d.Data AS Date
FROM table main
INNER JOIN table f on f.CustomerID = main.CustomerID
INNER JOIN table m on m.CustomerID = main.CustomerID
INNER JOIN table l on l.CustomerID = main.CustomerID
INNER JOIN table d on d.CustomerID = main.CustomerID
WHERE f.DBColumnName = 'FirstName' 
AND m.DBColumnName = 'MiddleName' 
AND l.DBColumnName = 'LastName' 
AND d.DBColumnName = 'Date' 

Edit: I have written this without an editor & have not run the SQL. I hope, you get the idea.

shahkalpesh
  • 33,172
  • 3
  • 63
  • 88
9

The OP didn't actually need to pivot without agregation but for those of you coming here to know how see:

sql parameterised cte query

The answer to that question involves a situation where pivot without aggregation is needed so an example of doing it is part of the solution.

Community
  • 1
  • 1
bielawski
  • 1,466
  • 15
  • 20
2

This should work:

select * from (select [CustomerID]  ,[Demographic] ,[Data]
from [dbo].[pivot]
) as Ter

pivot (max(Data) for  Demographic in (FirstName, MiddleName, LastName, [Date]))as bro
Pang
  • 9,564
  • 146
  • 81
  • 122
1

Try this:

SELECT CUSTOMER_ID, MAX(FIRSTNAME) AS FIRSTNAME, MAX(LASTNAME) AS LASTNAME ...

FROM
(

SELECT CUSTOMER_ID, 
       CASE WHEN DBCOLUMNNAME='FirstName' then DATA ELSE NULL END AS FIRSTNAME,
       CASE WHEN DBCOLUMNNAME='LastName' then DATA ELSE NULL END AS LASTNAME,
        ... and so on ...
GROUP BY CUSTOMER_ID

) TEMP

GROUP BY CUSTOMER_ID
mitchimus
  • 830
  • 1
  • 10
  • 24
1

Here is a great way to build dynamic fields for a pivot query:

--summarize values to a tmp table

declare @STR varchar(1000)
SELECT  @STr =  COALESCE(@STr +', ', '') 
+ QUOTENAME(DateRange) 
from (select distinct DateRange, ID from ##pivot)d order by ID

---see the fields generated

print @STr

exec('  .... pivot code ...
pivot (avg(SalesAmt) for DateRange IN (' + @Str +')) AS P
order by Decile')
Neo
  • 3,309
  • 7
  • 35
  • 44
0

By definition, all pivots aggregate, however there is a simple way to make sure all the data gets pivoted. The columns besides for the pivot are the group by's. So you can create a row_number in your data partioned by the other group by's and include that in your pivot data. for example:

with data as (
    select 'a' key1,'1' key2,'samplecolumn' as col,'3' as val
    union all
    select 'a' key1,'1' key2,'samplecolumn' as col,'6' as val
    union all
    select 'a' key1,'2' key2,'samplecolumn' as col,'7' as val
    union all
    select 'a' key1,'2' key2,'samplecolumn' as col,'9' as val
    union all
    select 'b' key1,'1' key2,'samplecolumn' as col,'23' as val
    union all
    select 'b' key1,'1' key2,'samplecolumn' as col,'45' as val
),
data_with_rownum as (
    select *, ROW_NUMBER() over (partition by key1,key2 order by (select null)) rownum from data
)
select key1,key2,rownum,samplecolumn from data_with_rownum
PIVOT (max(val) FOR col IN (samplecolumn)) AS p;
user890332
  • 1,315
  • 15
  • 15