Search code examples
mysqlsqlpivotentity-attribute-value

Query and ordering on "key-value" table


I have a problem and my SQL is too basic to get over it.
I have two tables:

TABLE1

  • id (int) - (primary key)
  • email (char)
  • other fields...

TABLE2

  • id (int) - (This is not the primary key nor is unique. This is a foreign key with respect to TABLE1.id)
  • key (char)
  • value (char)
  • other fields...

The second table has the structure "key-value".

For every TABLE1.id I have "many" rows in TABLE2 with TABLE1.id == TABLE2.id. In particular I can have the following situation:

A row in TABLE1

id = 10, email = "albert.einstein@genius.com", (other fields omitted)...

Corresponding rows in TABLE2

id = 10, key = "first_name", value = "Albert", (other fields omitted)...
id = 10, key = "last_name", value = "Einstein", (other fields omitted)...

I would like to write a SQL query which is able to return a table TABLE3 with the following structure:

TABLE3

  • id (int)
  • first_name (char)
  • last_name (char)
  • email (char)

The TABLE3 should contain a row for each row presents in TABLE1 and should be ordered by last_name and first_name

Following the example, I should obtain a table with rows similar to this one:

id = 10, first_name = "Albert", last_name = "Einstein", email = "albert.einstein@genius.com"

I'm getting crazy!!!


Solution

  • You need to pivot these key value pairs into columns, unfortunately, MySQL doesn't have a pivot table operator. But you can use the CASE expression to do so:

    SELECT
      t1.id,
      MAX(CASE WHEN t2.key = 'first_name' THEN value END) AS first_name,
      MAX(CASE WHEN t2.key = 'last_name' THEN value END) AS last_name,
      t1.email
    FROM table1 AS t1 
    INNER JOIN table2 AS t2 ON t1.id = t2.id
    GROUP BY t1.id,
             t1.email;
    

    SQL Fiddle Demo


    If you want to do this dynamically and not to write all these values manually you have to use dynamic SQL to do this:

    SET @sql = NULL;
    SELECT
      GROUP_CONCAT(DISTINCT CONCAT('MAX(IF(t2.key = ''',
          t2.key, ''', t2.value, 0)) AS ', '''',   t2.key, '''')
      ) INTO @sql
    FROM table2 AS t2;
    
    SET @sql = CONCAT('
      SELECT
        t1.id,', @sql, ',
        t1.email
      FROM table1 AS t1 
      INNER JOIN table2 AS t2 ON t1.id = t2.id
      GROUP BY t1.id, t1.email');
    
    prepare stmt 
    FROM @sql;
    
    execute stmt;
    

    SQL Fiddle Demo