Search code examples
sql-serverpivotdynamic-sqldynamic-columns

How to change rows into columns in SQL Server 2008 (multiple tables, joins etc)


I need help with creating a report for a survey application. There are 5 main tables involved:

  • Surveys (ID, ReportColumnName)
  • Sections (ID, SectionName)
  • Surveys_Sections(FK_Surveys, FK_Sections, Active)
  • Sections_Surveys_ScoreValues (FK_Surveys, FK_Sections, FK_ScoreCategories, SectionScoreValue)
  • ScoreCategories (ID, ScoreStart, ScoreEnd, CategoryName)

Every time a survey is created, a new ReportColumnName is generated that will need to be a column in the report. For each of these columns, and for each section (that is active in a survey), there is going to be a SectionScoreValue. This is linked further and checked whether it's inside an interval from ScoreCategories, and a resulting category (CategoryName) is shown for each survey and section.

I've been looking for days now on how to properly pivot these columns and show the needed values properly. By making an adaptation to some code got from a website, I thought this was going to work:

declare @SQL nvarchar(max), @Cols nvarchar(max)
select @Cols = 
stuff(
(
  select ', ' + quotename(dt)
  from 
  (
    select TOP 100 PERCENT Sv.ID, Sv.ReportColumnName as dt 
    from Surveys AS Sv
    ORDER BY Sv.ID
  ) X
  ORDER BY X.ID FOR XML PATH('')
),1,1,''
)

SELECT @Cols

set @SQL = 
'SELECT Sv.ReportColumnName, SSSV.* 
FROM Surveys AS Sv 
INNER JOIN Sections_Surveys__ScoreValues AS SSSV
   ON SSSV.FK_Surveys_ID = Sv.ID
INNER JOIN Sections AS Sc
   ON Sc.ID = SSSV.FK_Sections_ID
INNER JOIN Sections_Surveys AS SS 
   ON SS.FK_Surveys_ID = Sv.ID
   AND SS.FK_Sections_ID = Sc.ID
INNER JOIN ScoreCategories AS ScoreC 
   ON ScoreC.ID = SSSV.FK_ScoreCategories_ID

PIVOT (max(SSSV.SectionScoreValue) FOR Sv.ReportColumnName IN (' + @Cols
+')) pvt'

execute (@sql)

The @Cols variable holds the columns I need (i.e. the surveys' names) and it displays them properly (and delimited by a comma). So this part is working fine.

My issue is with the next part that should have put (initially) the SectionScoreValue for each SectionName (on the first column), and each of the surveys (each one on a separate column).

These surveys columns are added dynamically upon creation of a new one, so they are never a constant number.

Each of these tables has also a CreateDate column that has a default to GetDATE(). The errors I'm getting are:

The column 'CreateDate' was specified multiple times for 'pvt'.
The multi-part identifier "Sv.ReportColumnName" could not be bound.
The column prefix 'SSSV' does not match with a table name or alias name used in the query.

Please help in properly displaying my data. It should show something like:

Section Survey1 Survey2 Survey3 ......
s1        1.33    1.66    2.5
s2        0.00    3.33    4
s3        2.33    2       1.66
s4        0.66    2.5     3

The numbers can also be replaced with a value from ScoreCategoryName (like 'Good', 'Excellent' etc, according to the interval in which the number resides).

Thank you

EDIT 1: this is what PRINT @SQL yields:

SELECT Sv.ReportColumnName, SSSV.* 
FROM Surveys AS Sv 
INNER JOIN Sections_Surveys__ScoreValues AS SSSV
    ON SSSV.FK_Surveys_ID = Sv.ID
INNER JOIN Sections AS Sc
    ON Sc.ID = SSSV.FK_Sections_ID
INNER JOIN Sections_Surveys AS SS 
    ON SS.FK_Surveys_ID = Sv.ID
    AND SS.FK_Sections_ID = Sc.ID
INNER JOIN ScoreCategories AS ScoreC 
    ON ScoreC.ID = SSSV.FK_ScoreCategories_ID

PIVOT (max(SSSV.SectionScoreValue) FOR Sv.ReportColumnName 
IN ( [S1-1-2 -3], [S217], [al 3], [S37], [R4], [ult_nume_col])) pvt

EDIT 2: thanks to Giorgi I managed to get to a solution! Here's the final code that displays the columns exactly how I needed them:

declare @SQL nvarchar(max), @Cols nvarchar(max)
select @Cols = 
stuff(
(
    select ', ' + quotename(dt)
    from 
    (
        select TOP 100 PERCENT Sv.ID, Sv.ReportColumnName as dt 
        from Surveys AS Sv
        ORDER BY Sv.ID
    ) X
    ORDER BY X.ID FOR XML PATH('')



),1,1,''
)

SELECT @Cols

SET @SQL = ';
WITH    cte
          AS ( SELECT Sv.CreateDate, Sv.ReportColumnName, Sc.SectionName, SSSV.SectionScoreValue 
                FROM Surveys AS Sv 
                INNER JOIN Sections_Surveys__ScoreValues AS SSSV
                    ON SSSV.FK_Surveys_ID = Sv.ID
                INNER JOIN Sections AS Sc
                    ON Sc.ID = SSSV.FK_Sections_ID
                INNER JOIN Sections_Surveys AS SS 
                    ON SS.FK_Surveys_ID = Sv.ID
                    AND SS.FK_Sections_ID = Sc.ID
                INNER JOIN ScoreCategories AS ScoreC 
                    ON ScoreC.ID = SSSV.FK_ScoreCategories_ID
             )
    SELECT  SectionName , ' + @Cols + '
    FROM    cte PIVOT ( MAX(cte.SectionScoreValue) FOR cte.ReportColumnName IN (' + @Cols + ') ) pvt'

print @sql

execute (@sql)

Thanks for all the help guys, and for the "vote down" (if I could get to a result on my own I wouldn't have posted here).


Solution

  • Always use table expressions when dialing with pivoting. Something like this:

    SET @SQL = ';
    WITH    cte
              AS ( SELECT   SSSV.SectionScoreValue ,
                            Sv.ReportColumnName ,
                            Section
                   FROM     Surveys AS Sv
                            INNER JOIN Sections_Surveys__ScoreValues AS SSSV ON SSSV.FK_Surveys_ID = Sv.ID
                            INNER JOIN Sections AS Sc ON Sc.ID = SSSV.FK_Sections_ID
                            INNER JOIN Sections_Surveys AS SS ON SS.FK_Surveys_ID = Sv.ID AND SS.FK_Sections_ID = Sc.ID
                            INNER JOIN ScoreCategories AS ScoreC ON ScoreC.ID = SSSV.FK_ScoreCategories_ID
                 )
        SELECT  Section , ' + @Cols + '
        FROM    cte PIVOT ( MAX(SSSV.SectionScoreValue) FOR Sv.ReportColumnName IN (' + @Cols + ') ) pvt'
    

    Also you can look at my answer on similar question:

    SQL Pivot query based on "SUM of a column divided by SUM of another column"