I have created MySQL data model to store content along with its custom attributes/field. Below is a simplified version of it.
One for storing content:
CREATE TABLE `cms_content` (
`id` int(11) NOT NULL,
`title` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`content_type_id` int(6) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
ALTER TABLE `cms_content`
ADD PRIMARY KEY (`id`),
ADD KEY `content_type_id` (`content_type_id`);
The second one for content type (e.g. article, movie, person):
CREATE TABLE `cms_content_type` (
`id` int(6) NOT NULL,
`name` varchar(20) COLLATE utf8_unicode_ci NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
ALTER TABLE `cms_content_type`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `key` (`name`);
Custom fields:
CREATE TABLE `cms_custom_field` (
`id` int(11) NOT NULL,
`name` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`content_type_id` int(6) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
ALTER TABLE `cms_custom_field`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `name` (`name`,`content_type_id`),
ADD KEY `name_2` (`name`);
And finally one for connecting custom fields and the data:
CREATE TABLE `cms_content_data` (
`id` int(11) NOT NULL,
`custom_field_id` int(11) NOT NULL,
`content_id` int(11) NOT NULL,
`value` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`value_content_id` int(11) DEFAULT NULL,
`value_taxonomy_value_id` int(11) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
ALTER TABLE `cms_content_data`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `key` (`value`,`content_id`,`custom_field_id`) USING BTREE,
ADD KEY `content_id` (`content_id`),
ADD KEY `custom_field_id` (`custom_field_id`),
ADD KEY `value` (`value`),
ADD KEY `value_content_id` (`value_content_id`),
ADD KEY `value_taxonomy_value_id` (`value_taxonomy_value_id`),
ADD KEY `custom_field_id_2` (`custom_field_id`,`content_id`);
The challenge
I need to create a query that fetched content of a certain type with all of its custom fields.
NB! One trick here is that there can be multiple content_data
values for one custom field.
E.g. a movie has a field genre
which can have multiple rows in content_data
.
The non-working query
I was able to dynamically generate the following query:
SELECT
`content`.`id` AS `_content_id`,
`content`.`title` AS `_content_title`,
`content_data_0`.`value_taxonomy_value_id` AS `genres`,
`content_data_1`.`value_content_id` AS `production_company`,
`content_data_2`.`value` AS `date_released`,
`content_data_3`.`value` AS `runtime`,
`content_data_4`.`value` AS `tagline`,
`content_data_5`.`value` AS `imdb_rating`,
`content_data_6`.`value` AS `rt_rating`,
`content_data_7`.`value` AS `imdb_id`,
`content_data_8`.`value` AS `trailer_url`,
`content_data_9`.`value` AS `api_id`,
`content_data_10`.`value_taxonomy_value_id` AS `rating`,
`content_data_11`.`value` AS `revenue`,
`content_data_12`.`value` AS `poster_original_path`,
`content_data_13`.`value` AS `rt_rating_users`,
`content_data_14`.`value` AS `rt_url`,
`content_data_15`.`value` AS `rt_consensus`,
`content_data_16`.`value` AS `mc_rating`,
`content_data_17`.`value` AS `year`,
`content_data_18`.`value` AS `budget`,
`content_data_19`.`value_taxonomy_value_id` AS `original_language`,
`content_data_20`.`value_taxonomy_value_id` AS `production_countries`,
`content_data_21`.`value` AS `original_title`
FROM `cms_content` AS `content`
INNER JOIN cms_content_type ON cms_content_type.id = content.content_type_id
INNER JOIN `cms_content_data` AS `content_data_0` ON `content_data_0`.`content_id` = `content`.`id`
INNER JOIN `cms_content_data` AS `content_data_1` ON `content_data_1`.`content_id` = `content`.`id`
INNER JOIN `cms_content_data` AS `content_data_2` ON `content_data_2`.`content_id` = `content`.`id`
INNER JOIN `cms_content_data` AS `content_data_3` ON `content_data_3`.`content_id` = `content`.`id`
INNER JOIN `cms_content_data` AS `content_data_4` ON `content_data_4`.`content_id` = `content`.`id`
INNER JOIN `cms_content_data` AS `content_data_5` ON `content_data_5`.`content_id` = `content`.`id`
INNER JOIN `cms_content_data` AS `content_data_6` ON `content_data_6`.`content_id` = `content`.`id`
INNER JOIN `cms_content_data` AS `content_data_7` ON `content_data_7`.`content_id` = `content`.`id`
INNER JOIN `cms_content_data` AS `content_data_8` ON `content_data_8`.`content_id` = `content`.`id`
INNER JOIN `cms_content_data` AS `content_data_9` ON `content_data_9`.`content_id` = `content`.`id`
INNER JOIN `cms_content_data` AS `content_data_10` ON `content_data_10`.`content_id` = `content`.`id`
INNER JOIN `cms_content_data` AS `content_data_11` ON `content_data_11`.`content_id` = `content`.`id`
INNER JOIN `cms_content_data` AS `content_data_12` ON `content_data_12`.`content_id` = `content`.`id`
INNER JOIN `cms_content_data` AS `content_data_13` ON `content_data_13`.`content_id` = `content`.`id`
INNER JOIN `cms_content_data` AS `content_data_14` ON `content_data_14`.`content_id` = `content`.`id`
INNER JOIN `cms_content_data` AS `content_data_15` ON `content_data_15`.`content_id` = `content`.`id`
INNER JOIN `cms_content_data` AS `content_data_16` ON `content_data_16`.`content_id` = `content`.`id`
INNER JOIN `cms_content_data` AS `content_data_17` ON `content_data_17`.`content_id` = `content`.`id`
INNER JOIN `cms_content_data` AS `content_data_18` ON `content_data_18`.`content_id` = `content`.`id`
INNER JOIN `cms_content_data` AS `content_data_19` ON `content_data_19`.`content_id` = `content`.`id`
INNER JOIN `cms_content_data` AS `content_data_20` ON `content_data_20`.`content_id` = `content`.`id`
INNER JOIN `cms_content_data` AS `content_data_21` ON `content_data_21`.`content_id` = `content`.`id`
INNER JOIN `cms_content_type` AS `content_type` ON `content_type`.`id` = `content`.`content_type_id`
WHERE `content_type`.`name` = 'movie'
AND `content_data_0`.`custom_field_id` = 141
AND `content_data_1`.`custom_field_id` = 143
AND `content_data_2`.`custom_field_id` = 144
AND `content_data_3`.`custom_field_id` = 146
AND `content_data_4`.`custom_field_id` = 148
AND `content_data_5`.`custom_field_id` = 154
AND `content_data_6`.`custom_field_id` = 155
AND `content_data_7`.`custom_field_id` = 156
AND `content_data_8`.`custom_field_id` = 158
AND `content_data_9`.`custom_field_id` = 159
AND `content_data_10`.`custom_field_id` = 162
AND `content_data_11`.`custom_field_id` = 163
AND `content_data_12`.`custom_field_id` = 164
AND `content_data_13`.`custom_field_id` = 174
AND `content_data_14`.`custom_field_id` = 175
AND `content_data_15`.`custom_field_id` = 176
AND `content_data_16`.`custom_field_id` = 177
AND `content_data_17`.`custom_field_id` = 185
AND `content_data_18`.`custom_field_id` = 184
AND `content_data_19`.`custom_field_id` = 186
AND `content_data_20`.`custom_field_id` = 187
AND `content_data_21`.`custom_field_id` = 188
DESC LIMIT 100
The problems here:
1. Wrong return. Apparently, since there can be multiple values for every content_data
MySQL combines them in all sorts of way and I end up with one movie
with various combinations of custom fields.
2. The query is super slow (probably because of many joins as well as combinations of field values)
The question
Is there any way in MySQL (plus some PHP backend magic, of course) to return all the required data in one single query? Considering the current data model.
As for multiple rows, having values in comma-separated field value would work for me (unless there is a better way).
Or is this data model simply very flawed and I need to change it somehow?
Thanks in advance!
2
Answers
You want conditional aggregation. I think this is:
Note: It seems strange that
value
is not being used for all the columns. You might wantGROUP_CONCAT()
is there are multiple rows for a givencontent_id
with the samecustomer_field_id
.You could approach this with conditional aggregation. With this technique, a single join is need, and then conditional aggregate expressions pull out the expected values.
For your quer, that would look like: