2

I have three MYSQL tables, simplified, with following columns:

ModuleEntries (MDE_ID)
ModuleFields (MDF_ID, MDF_Label)
ModuleEntryFieldValues (MFV_ID, MFV_MDE_ID, MFV_MDF_ID, MFV_Value)

As you can see from the simplified column list, ModuleEntryFieldValues is the table which states that "for an entry, this field has this value".

Now, I would like to retrieve this information in one "flat" recordset. I have managed to get things working, but not entirely to what i want.

With the help of a different SO article, I managed to get the dynamic, variable amount of columns to work, by using a cursor.

  declare finish int default 0;
  declare fldid int;
  declare str varchar(10000) default 'SELECT *,';
  declare curs cursor for select MDF_ID from ModuleFields group by MDF_ID;
  declare continue handler for not found set finish = 1;
  open curs;
  my_loop:loop
    fetch curs into fldid;
    if finish = 1 then
      leave my_loop;
    end if;
    set str = concat(str, 'max(case when MFV_MDF_ID = ',fldid,' then MFV_Value else null end) as field_',fldid,',');
  end loop;
  close curs;
  set str = substr(str,1,char_length(str)-1);
  set @str = concat(str, ' from ModuleEntries LEFT join ModuleEntryFieldValues ON MDE_ID = MDF_MDE_ID GROUP BY MDE_ID');
  prepare stmt from @str;
  execute stmt;
  deallocate prepare stmt;
What this code doesn't do, is allow me to put the column values of MDF_Label as the actual column headers of the rows. Now, the above code gives me "field_1, field_2, ..."

Is it possible to join these three tables, and have the MDF_Label as column header for the rows that are now columns in the joined table ?

I want this...
ModuleEntries  |   ModuleFields         |   ModuleEntryFieldValues
-------------  |   ------------------   |   -----------------------------------
MDE_ID         |   MDF_ID - MDF_Label   |   MFV_MDE_ID - MFV_MDF_ID - MFV_Value
1              |   1      - Height      |   1          - 1          - 120cms
               |   2      - Width       |   1          - 2          - 30cms
               |   3      - Weight      |   

into this

Recordset
---------
MDE_ID - Height - Width - Weight
1      - 120cms - 30cms - null

I hope my question was clear enough. If not please comment and I will give more information where needed, if I can.

Steven Ryssaert
  • 1,989
  • 15
  • 25

1 Answers1

3

I'd just use GROUP_CONCAT() instead of cursors to generate the prepared statement:

SELECT CONCAT(
 ' SELECT MDE_ID,'

,  GROUP_CONCAT(
     't', MDF_ID, '.MFV_Value AS `', REPLACE(MDF_Label, '`', '``'), '`'
   )

,' FROM ModuleEntries '

, GROUP_CONCAT(
    'LEFT JOIN ModuleEntryFieldValues AS t', MDF_ID, '
       ON t', MDF_ID, '.MFV_MDE_ID = ModuleEntries.MDE_ID
      AND t', MDF_ID, '.MFV_MDF_ID = ', MDF_ID
  SEPARATOR ' ')

) INTO @qry FROM ModuleFields;

Save for whitespace editing to make it more readable, with your sample data @qry would then contain:

SELECT MDE_ID,
       t1.MFV_Value AS `Height`,
       t2.MFV_Value AS `Width`,
       t3.MFV_Value AS `Weight`
FROM   ModuleEntries
  LEFT JOIN ModuleEntryFieldValues AS t1
         ON t1.MFV_MDE_ID = ModuleEntries.MDE_ID AND t1.MFV_MDF_ID = 1
  LEFT JOIN ModuleEntryFieldValues AS t2
         ON t2.MFV_MDE_ID = ModuleEntries.MDE_ID AND t2.MFV_MDF_ID = 2
  LEFT JOIN ModuleEntryFieldValues AS t3
         ON t3.MFV_MDE_ID = ModuleEntries.MDE_ID AND t3.MFV_MDF_ID = 3

One can then prepare and execute that statement:

PREPARE stmt FROM @qry;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @qry = NULL;

Which yields the following results:

MDE_ID    Height    Width    Weight
     1    120cms    30cms    (null)

See it on sqlfiddle.

eggyal
  • 122,705
  • 18
  • 212
  • 237
  • I am getting an error when there are no records in the tables. Is there a way to overcome this? `You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'NULL' at line 1` – Steven Ryssaert May 25 '12 at 09:08
  • @UwConcept: Move the comma after `MDE_ID` into the `GROUP_CONCAT` and define `SEPARATOR ''`. – eggyal May 25 '12 at 20:58
  • I did what you suggested, but I still got an error. The problem was that if there were no ModuleFields at all, the variable `@qry` was null. I added a simple check on that variable's value, and only execute the prepared statement when `@qry is not null`. I marked this as answer for your elegant solution and great assistance. – Steven Ryssaert May 27 '12 at 16:46
  • 1
    It is worth noting that the `GROUP_CONCAT()` function is truncated to the maximum length that is given by the `group_concat_max_len` system variable. The syntax to change the value of `group_concat_max_len` at runtime is as follows, where val is an unsigned integer: `SET [GLOBAL | SESSION] group_concat_max_len = val;` For more info see [MySQL Reference - Group Concat](http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html#function_group-concat) – Steven Ryssaert Jun 02 '12 at 15:21