SQL Server Unpivot and align rows for each field

evrscar2  于 2023-06-21  发布在  其他
关注(0)|答案(2)|浏览(107)

How can I align the rows and attributes of each field in a table to appear in the same line, using the given table as an example?. Mark this all fields in this table are varchar so as to enable uniformity.
| STD | SUBJECT | MARKS | COMMENT | TDATE |
| ------------ | ------------ | ------------ | ------------ | ------------ |
| ST1 | MATH | 25% | POOR | 1/02/2021 |
| ST1 | ENGLISH | 88% | DIST | 2/02/2021 |
| ST1 | SCIENCE | 56% | PASS | 4/02/2021 |

The table should appear like this after unpivoting the contents in [Subject] :

STDMATHCOMMENTTDATEENGLISHCOMMENTTDATESCIENCECOMMENTTDATE
ST125%POOR1/02/202188%DIST2/02/202156%PASS4/02/2021

I tried with my code and got lots of errors, so maybe I could get some help to achieve the desired result.

SELECT
             [MATH],
             [ENGLISH],
             [SCIENCE]
         FROM (
             SELECT
                 STD, stdn,
                 cont,
                 x,
                 SUBJECT
             FROM
                 [dbo].[Exam]
             UNPIVOT (
                 x
                 for cont in (COMMENT, TDATE)
             ) a
        ) a
        PIVOT (
           MAX(x) 
           FOR SUBJECT IN (
                [MATH],
                [ENGLISH],
                [SCIENCE],
            )
        ) p
        WHERE p.stdn IN (SELECT STD FROM [dbo].[exam])
jtjikinw

jtjikinw1#

i think i solved it when i used MAX(CASE)

SELECT [STD], 
         MAX(CASE WHEN SUBJECT = 'MATH' THEN MARKS ELSE '' END) MATH,
         MAX(CASE WHEN SUBJECT = 'MATH' THEN COMMENT ELSE '' END) COMMENT,
         MAX(CASE WHEN SUBJECT = 'MATH' THEN TDATE ELSE '' END) TDATE ,
   
         MAX(CASE WHEN SUBJECT = 'ENGLISH' THEN MARKS ELSE '' END) ENGLISH,
         MAX(CASE WHEN SUBJECT = 'ENGLISH' THEN COMMENT ELSE '' END) COMMENT,
         MAX(CASE WHEN SUBJECT = 'ENGLISH' THEN TDATE ELSE '' END) TDATE ,
    
         MAX(CASE WHEN SUBJECT = 'SCIENCE' THEN MARKS ELSE '' END) SCIENCE,
         MAX(CASE WHEN SUBJECT = 'SCIENCE' THEN COMMENT ELSE '' END) COMMENT,
         MAX(CASE WHEN SUBJECT = 'SCIENCE' THEN TDATE ELSE '' END) TDATE 

    FROM [dbo].[EXAM]  GROUP BY STD
ql3eal8s

ql3eal8s2#

Pivots for presentation purposes are not well suited to SQL and often better performed by a "front end" or "reporting tool" despite this, it is possible to pivot this data using T-SQL.

I assume that an unstated requirement is that the columns are presented in a specific left-to-right "block sequence" of "subject-mark-comment-tdate" so there needs to be a way to organize the columns in that sequence. A further probable requirement is that the pivot cater for an unknown number of total columns, for this you need "dynamic sql".

Note: This "unpivot" is complicated by the fact that there are different data types involved, one way to avoid this is to cast all data to strings or sql_variant (which is the approach seen below). Another (that I prefer) is to use JSON explained here but I wasn't also able to control the the "rnk" value as I needed for this query to formulate the numeric sequence of columns.

So, this is proposed:

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

SELECT @cols = STUFF((SELECT ',' + quotename(concat(c.column_name, cj.rn))
            from INFORMATION_SCHEMA.COLUMNS c
            cross join (
                select row_number() over(order by subject) rn
                from (select distinct subject from exam) e
                ) cj
            WHERE c.TABLE_NAME = 'Exam'
            AND c.column_name <> 'STD'
            order by cj.rn, c.ORDINAL_POSITION
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'');

SET @query = 'SELECT STD, ' + @cols + ' 
            from 
            (
                SELECT STD, 
                       concat(COLUMN_NAME,rnk) as COLUMN_NAME, 
                       VALUE
                FROM
                (
                    SELECT STD, 
                           CAST(SUBJECT AS sql_variant) AS SUBJECT, 
                           CAST(MARKS AS sql_variant) AS MARKS, 
                           CAST(COMMENT AS sql_variant) AS COMMENT, 
                           CAST(convert(varchar(10), TDATE ,120) AS sql_variant) AS TDATE,
                           dense_rank() over(partition by STD order by MARKS) as rnk
                    FROM Exam
                ) AS subquery
                UNPIVOT
                (
                    VALUE
                    FOR COLUMN_NAME IN (SUBJECT, MARKS, COMMENT, TDATE)
                ) AS unpvt
            ) x
            pivot 
            (
                max(VALUE)
                for COLUMN_NAME in (' + @cols + ')
            ) p ';

EXECUTE(@query);
STDSUBJECT1MARKS1COMMENT1TDATE1SUBJECT2MARKS2COMMENT2TDATE2SUBJECT3MARKS3COMMENT3TDATE3
ST1MATH25%POOR2021-02-01SCIENCE56%PASS2021-02-04ENGLISH88%DIST2021-02-02

see this as a working demo

nb: As I ended up converting the date column into a string to control the format, you could replace the "as sql_variant" with "as varchar(100)"(length of 100 is a guess)

相关问题