Search code examples
mysqlsqlpivotpivot-table

Mysql query to dynamically convert rows to columns on the basis of two columns


I have followed a question here to use a Mysql query to dynamically convert rows to columns. This works fine, but i need to convert this on basis of two columns,

The query mentioned in above link works for a single column "data", but i want to work for two columns which are "data" and "price".

i have added an example here,

Given a table A, which look like

Table A

|  id|order|data|item|Price|
-----+-----+----------------
|   1|    1|   P| 1  | 50  |
|   1|    1|   P| 2  | 60  |
|   1|    1|   P| 3  | 70  |
|   1|    2|   Q| 1  | 50  |
|   1|    2|   Q| 2  | 60  |
|   1|    2|   Q| 3  | 70  |
|   2|    1|   P| 1  | 50  |
|   2|    1|   P| 2  | 60  |
|   2|    1|   P| 4  | 80  |
|   2|    3|   S| 1  | 50  |
|   2|    3|   S| 2  | 60  |
|   2|    3|   S| 4  | 80  |

I like to write a query that looks like the following:

Result Table

|  id|order1|order2|order3|item1|item2|item3|item4|
-----+-----+---------------------------------------
|   1|    P |    Q |      | 50  | 60  | 70  |     |
|   2|    P |      |    S | 50  | 60  |     | 80  |

I have tried to create two different queries and then a join to achieve this, but that may not be a good solution. Can any one suggest a solution same like mentioned in the link above.

Thanks


Solution

  • If you had a known number of values for both order and item, then you could hard code the query into:

    select id,
      max(case when `order` = 1 then data end) order1,
      max(case when `order` = 2 then data end) order2,
      max(case when `order` = 3 then data end) order3,
      max(case when item = 1 then price end) item1,
      max(case when item = 2 then price end) item2,
      max(case when item = 3 then price end) item3,
      max(case when item = 4 then price end) item4
    from tableA
    group by id;
    

    See Demo. But part of the problem that you are going to have is because you are trying to transform multiple columns of data. My suggestion to get the final result would be to unpivot the data first. MySQL does not have an unpivot function but you can use a UNION ALL to convert the multiple pairs of columns into rows. The code to unpivot will be similar to the following:

    select id, concat('order', `order`) col,  data value
    from tableA
    union all
    select id, concat('item', item) col, price value
    from tableA;
    

    See Demo. The result of this will be:

    | ID |    COL | VALUE |
    -----------------------
    |  1 | order1 |     P |
    |  1 | order1 |     P |
    |  1 | order1 |     P |
    |  1 |  item1 |    50 |
    |  1 |  item2 |    60 |
    |  1 |  item3 |    70 |
    

    As you can see this has taken the multiple columns of order/data and item/price and convert it into multiple rows. Once that is completed, then you can convert the values back into columns using an aggregate function with a CASE:

    select id, 
      max(case when col = 'order1' then value end) order1,
      max(case when col = 'order2' then value end) order2,
      max(case when col = 'order3' then value end) order3,
      max(case when col = 'item1' then value end) item1,
      max(case when col = 'item2' then value end) item2,
      max(case when col = 'item3' then value end) item3
    from
    (
      select id, concat('order', `order`) col,  data value
      from tableA
      union all
      select id, concat('item', item) col, price value
      from tableA
    ) d
    group by id;
    

    See Demo. Finally, you need to convert the above code into a dynamic prepared statement query:

    SET @sql = NULL;
    SELECT
      GROUP_CONCAT(DISTINCT
        CONCAT(
          'max(case when col = ''',
          col,
          ''' then value end) as `', 
          col, '`')
      ) INTO @sql
    FROM
    (
      select concat('order', `order`) col
      from tableA
      union all
      select concat('item', `item`) col
      from tableA
    )d;
    
    SET @sql = CONCAT('SELECT id, ', @sql, ' 
                      from
                      (
                        select id, concat(''order'', `order`) col,  data value
                        from tableA
                        union all
                        select id, concat(''item'', item) col, price value
                        from tableA
                      ) d
                      group by id');
    
    PREPARE stmt FROM @sql;
    EXECUTE stmt;
    DEALLOCATE PREPARE stmt;
    

    See SQL Fiddle with demo. This gives a result:

    | ID | ORDER1 | ORDER2 | ORDER3 | ITEM1 | ITEM2 |  ITEM3 |  ITEM4 |
    -------------------------------------------------------------------
    |  1 |      P |      Q | (null) |    50 |    60 |     70 | (null) |
    |  2 |      P | (null) |      S |    50 |    60 | (null) |     80 |