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 #'.
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.