Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Transform vertical result into horizontal mode (T-SQL)

Here are the sample data :

CalculationDatePLResult
2014-01-02       100         
2014-01-03       200         
2014-02-03       300         
2014-02-04       400         
2014-02-27       500         

Here are the expected result (in logical format) :

January                                 February                                 
CalculationDatePLResultCalculationDatePLResult  
2014-01-02       100         2014-02-03       300          
2014-01-03       200         2014-02-04       400          
                                         2014-02-27       500          

Here are the expected result (using T-SQL Query) :

Jan-CalculationDateJan-PLResultFeb-CalculationDateFeb-PLResult  
2014-01-02              100                2014-02-03              300                  
2014-01-03              200                2014-02-04              400                  
                                                       2014-02-27              500                  

Objective:

  • Classify the result according to the month. In the above example, the January's results are placed in the January breakdown.
  • The number of months can be dynamic. In the above example, it only shows January and February because there are only results for 2 months
  • The result will be displayed through Excel. Actually I can query multiple query tables to aggregate the result across different months, but if it's possible to return all the result through one single query, then it will be easier to be maintained and debugged.

Here are the scripts to populate the sample data :

CREATE TABLE #PLResultPerDay ( CalculationDate DATETIME, PLResult DECIMAL(18,8) )
INSERT INTO #PLResultPerDay ( CalculationDate, PLResult ) VALUES ('2014-01-02' , 100 )
INSERT INTO #PLResultPerDay ( CalculationDate, PLResult ) VALUES ('2014-01-03' , 200 )
INSERT INTO #PLResultPerDay ( CalculationDate, PLResult ) VALUES ('2014-02-03' , 300 )
INSERT INTO #PLResultPerDay ( CalculationDate, PLResult ) VALUES ('2014-02-04' , 400 )

So far here is my attempt in building the query :

SELECT 
    CalculationDate, [January], CalculationDate, [February]
FROM 
(
    SELECT CalculationDate, PLResult, DATENAME(MONTH, CalculationDate) AS [MTH]
    FROM #PLResultPerDay
) x
PIVOT
( 
    MIN(PLResult)
    FOR [MTH] IN ([January], [February])
) p
like image 427
hadi teo Avatar asked Dec 30 '13 09:12

hadi teo


People also ask

How do I show a SQL result vertically?

You can output query results vertically instead of a table format. Just type \G instead of a ; when you end you queries in mysql prompt.


1 Answers

As has been said this isn't actually possible, the closest you could get is:

January2014CalculationDate | January2014PLResult | February2014CalculationDate | February2014PLResult
---------------------------+---------------------+-----------------------------+------------------
    2014-01-02             |       100           |       2014-02-03            |       300
    2014-01-03             |       200           |       2014-02-04            |       400
    NULL                   |       NULL          |       2014-02-27            |       500

And even that is not simple and I would still advise handling formatting like this outside of sql. The first step is to partition the data by month, and then rank the dates in each month:

SELECT  CalculationDate,
        PLResult,
        CalculationMonth,
        DenseRank = DENSE_RANK() OVER(PARTITION BY CalculationMonth ORDER BY CalculationDate)
FROM    (   SELECT  CalculationDate,
                    PLResult,
                    CalculationMonth = DATEADD(MONTH, DATEDIFF(MONTH, 0, CalculationDate), 0)
            FROM    #PLResultPerDay
        ) pl;

This gives:

CalculationDate PLResult    CalculationMonth    DenseRank
2014-01-02      100         2014-01-01          1
2014-01-03      200         2014-01-01          2
2014-02-03      300         2014-02-01          1
2014-02-04      400         2014-02-01          2
2014-02-27      500         2014-02-01          3

You can then pivot this data:

WITH Data AS
(   SELECT  CalculationDate,
            PLResult,
            CalculationMonth,
            DenseRank = DENSE_RANK() OVER(PARTITION BY CalculationMonth ORDER BY CalculationDate)
    FROM    (   SELECT  CalculationDate,
                        PLResult,
                        CalculationMonth = DATEADD(MONTH, DATEDIFF(MONTH, 0, CalculationDate), 0)
                FROM    #PLResultPerDay
            ) pl
)
SELECT  Jan2014CalcDate = MIN(CASE WHEN CalculationMonth = '20140101' THEN CalculationDate END),
        Jan2014Result = SUM(CASE WHEN CalculationMonth = '20140101' THEN PLResult END),
        Feb2014CalcDate = MIN(CASE WHEN CalculationMonth = '20140201' THEN CalculationDate END),
        Feb2014Result = SUM(CASE WHEN CalculationMonth = '20140201' THEN PLResult END)
FROM    Data
GROUP BY DenseRank
ORDER BY DenseRank;

This gives:

Jan2014CalcDate Jan2014Result   Feb2014CalcDate Feb2014Result
2014-01-02      100             2014-02-03      300
2014-01-03      200             2014-02-04      400
NULL            NULL            2014-02-27      500

Then since you have a dynamic number of months you need to build the above statement dynamically and use SP_EXECUTESQL to run it:

DECLARE @SQL NVARCHAR(MAX) = '';

WITH Months AS
(   SELECT  M,
            ColName = DATENAME(MONTH, M) + DATENAME(YEAR, M),
            CharFormat = CONVERT(VARCHAR(8), M, 112)
    FROM    (   SELECT  DISTINCT M = DATEADD(MONTH, DATEDIFF(MONTH, 0, CalculationDate), 0)
                FROM    #PLResultPerDay
            ) m
)
SELECT  @SQL = 'WITH Data AS
                (   SELECT  CalculationDate,
                            PLResult,
                            CalculationMonth,
                            DenseRank = DENSE_RANK() OVER(PARTITION BY CalculationMonth ORDER BY CalculationDate)
                    FROM    (   SELECT  CalculationDate,
                                        PLResult,
                                        CalculationMonth = DATEADD(MONTH, DATEDIFF(MONTH, 0, CalculationDate), 0)
                                FROM    #PLResultPerDay
                            ) pl
                )
                SELECT  ' + 
                STUFF(( SELECT  ', ' + ColName + 'CalculationDate = MIN(CASE WHEN CalculationMonth = ''' + CharFormat + ''' THEN CalculationDate END), ' + 
                                ColName + 'PLResult = SUM(CASE WHEN CalculationMonth = ''' + CharFormat + ''' THEN PLResult END)'
                        FROM    Months
                        ORDER BY M
                        FOR XML PATH(''), TYPE
                    ).value('.', 'NVARCHAR(MAX)'), 1, 2, '') + 
                'FROM   Data
                GROUP BY DenseRank
                ORDER BY DenseRank;';

EXECUTE SP_EXECUTESQL @SQL;

Example on SQL Fiddle

Please note, I still advise against this technique, and think SQL should be left to storing/retrieving data, and the presentation layer for formatting it

like image 86
GarethD Avatar answered Sep 24 '22 02:09

GarethD