简体   繁体   中英

MySQL EAV SELECT one-to-many single row results

I've seen similar questions on here, but haven't been able to find one that matches my specific scenario. I have the following three tables:

content
id, [other fields]

attributes
id, title

attributes_values
id, attribute_id[foreign to attributes.id], content_id[foreign to content.id], value

What I'd like is a single query (or group of sub-queries) that can return the appropriate data to avoid having to manipulate this in the programming.

A little more info about the tables: attributes has three records (Model #, Part #, and Show in Cart).

I need to select all records from content where the attribute_value for Show in Cart = 1. Along with this, I'd like the other related attribute_values to be returned as their related attribute.title. The results would look something like this:

id    [other fields]    Model # [from attributes.title field]    Part # [from attributes.title field]
1     [any data]        value from attributes_values             value from attributes_values
2     [any data]        value from attributes_values             value from attributes_value

So far, my only way of accomplishing this is not very elegant as I'm having to do multiple joins to the same table:

SELECT * FROM content AS a
LEFT JOIN attributes_values AS b ON (b.content_id = a.id AND b.attribute_id = [Model # ID])
LEFT JOIN attributes_values AS c ON (c.content_id = a.id AND c.attribute_id = [Part # ID])
WHERE a.id IN (
    SELECT content_id FROM attributes_values WHERE attribute_id = [Show in Cart ID] AND value = 1
)

As you can see, I'm having to hard-code related keys into the JOIN statements, which doesn't make this very scalable (and just feels dirty).

Some last few notes: I'm using PHP and Joomla!, so if that would affect your answer (or if there's some secret Joomla tip), feel free to include it. Also, this is a pre-established table schema that I'm not able to alter, so I need a query solution for the above mentioned tables, not a suggestion on a better table design.

Any help will be greatly appreciated.

Cheers!

Instead of joining your table multiple times for each attribute, you can use an aggregated function like MAX() in combination with a CASE WHEN statement:

SELECT
  content.id,
  [other_fields],
  MAX(CASE WHEN attributes.title='Model #' THEN attributes_values.value END) AS Model_N,
  MAX(CASE WHEN attributes.title='Part #' THEN attributes_values.value END) AS Part_N
FROM
  content INNER JOIN attributes_values
  ON content.id = attributes_values.content_id
  INNER JOIN attributes
  ON attributes_values.attribute_id = attributes.id
GROUP BY
  id,
  [other_fields]
HAVING
  MAX(CASE WHEN attributes.title='Show in Cart' THEN attribute_values.value END)='1'

a little explanation:

CASE WHEN attributes.title='Model #' THEN attributes.value END

will return the value when the attribute is 'Model #' and NULL otherwise, and

MAX(...)

will return the maximum non-NULL value, which is the value where attributes.title='Model #'.

dynamic attributes

MySQL does not have a Pivot function, so if you want your list to be dynamic, you have to dinamically create a SQL string with all attributes and execute this string.

You can fetch all attributes from the attributes table:

SELECT
    CONCAT(
      'MAX(CASE WHEN attributes.title=''',
      REPLACE(attributes.title, '''', ''''''),
      ''' THEN attributes_values.value END) AS `',
      REPLACE(attributes.title, '`', '``'),
      '`'
    )
FROM
  attributes;

and combine everything together with GROUP_CONCAT:

SELECT GROUP_CONCAT(...the concat above...)
FROM attributes;

so your final query will be something like this:

SELECT
  CONCAT('SELECT content.id,',

  GROUP_CONCAT(
    CONCAT(
      'MAX(CASE WHEN attributes.title=''',
      REPLACE(attributes.title, '''', ''''''),
      ''' THEN attributes_values.value END) AS `',
      REPLACE(attributes.title, '`', '``'),
      '`'
    )
  ),

  ' FROM content INNER JOIN attributes_values ',
  'ON content.id = attributes_values.content_id ',
  'INNER JOIN attributes ',
  'ON attributes_values.attribute_id = attributes.id ',
  'GROUP BY id HAVING ',
  'MAX(CASE WHEN attributes.title=''Show in Cart'' THEN attributes_values.value END)=''1'''
  )
FROM
  attributes
INTO @SQL;

now the @SQL variable will hold the value of the query to be executed, and you can execute it with:

PREPARE stmt FROM @sql;
EXECUTE stmt;

The only problem here is that GROUP_CONCAT has a maximum length limit , you can increase it if your table is holding many attributes.

Please see an example fiddle here .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM