Search code examples
mysqlsqlentity-attribute-value

Flattening an EAV type SQL query


I have a MySQL database used for a mailing list, and I need to extract the data, so one member record is represented by one row. The mailing list stores the user attributes as EAV. I can extract all the details I need using the following SQL query, but each member record takes up multiple rows:

SELECT a.id, a.name, a.email, b.id, b.name, b.email, c.title, d.val
FROM lists a, listmembers b, fields c, fieldsdata d
WHERE a.id = b.nl
AND b.id = d.eid
AND c.id = d.fid
ORDER BY b.id, a.id, b.name

This returns something like this:

'6', 'Mailing List name', 'owner@mailinglist.com', '10478', 'username', 'mailinglistmember@emailaddress.com', 'Firstname', 'John'
'6', 'Mailing List name', 'owner@mailinglist.com', '10478', 'username', 'mailinglistmember@emailaddress.com', 'Lastname', 'Smith'
'6', 'Mailing List name', 'owner@mailinglist.com', '10478', 'username', 'mailinglistmember@emailaddress.com', 'Country', 'UK'
'6', 'Mailing List name', 'owner@mailinglist.com', '10478', 'username', 'mailinglistmember@emailaddress.com', 'Town', 'Cambridge'
'6', 'Mailing List name', 'owner@mailinglist.com', '10478', 'username', 'mailinglistmember@emailaddress.com', 'Shoesize', '7'
'6', 'Mailing List name', 'owner@mailinglist.com', '10478', 'username', 'mailinglistmember@emailaddress.com', 'Favourite Colour', 'Purple'

I need to flatten this into one row using SQL, only requiring values relating to the keys firstname, lastname, town and country

The database is not huge, the fieldsdata table being the largest with about 5500 rows.

Seems like a real pain, so any pointers would be gratefully received. !


Solution

  • You can use MAX with CASE to pivot your results if I'm understanding your question correctly:

    SELECT l.id, l.name, l.email, lm.id, lm.name, lm.email, 
       MAX(CASE WHEN f.title = 'Firstname' THEN fd.val END) FirstName,
       MAX(CASE WHEN f.title = 'Lastname' THEN fd.val END) Lastname,
       MAX(CASE WHEN f.title = 'Country' THEN fd.val END) Country,
       MAX(CASE WHEN f.title = 'Town' THEN fd.val END) Town
    FROM lists l
       JOIN listmembers lm ON l.id=lm.nl
       JOIN fieldsdata fd ON fd.eid = lm.id
       JOIN fields f ON f.id = fd.fid 
    GROUP BY l.id, lm.id
    

    This assumes the id field from your lists table is your unique identifier. If not, you'll need to add additional fields to your GROUP BY (most likely the id field from your listmembers table).