Reputation: 1950
I'm developing a content management system at the moment, and I wanted to hear your thoughts on the following:
I have one table, page
. Let's assume it looks like this
ID | Title | Content
1 | Test | This is a test
As well as this, I have a page_option
table (so I can store options relating to the page, but I don't want to have a finite list of options - modules could add their own options to a page if required.)
The page_option
table could look like this:
page_id | option_key | option_value
1 | background | red
1 | module1_key | chicken
Now to retrieve a page object, I do the following using the Active Record class (this was pseudo coded for this question):
function get_by_id($page_id) {
$this->db->where('id', $page_id);
$page_object = $this->db->get('page');
if($page_object->num_rows() > 0) {
$page = $page_object->row();
$this->db->where('page_id', $page_id);
$options_object = $this->db->get('option');
if($options_object->num_rows() > 0) {
$page->options = $options_object->result();
}
return $page;
}
return $page_object->row();
}
What I want to know, is there a way to do this in one query, so that the option keys become virtual columns in my select, so I'd get:
ID | Title | Content | background | module1_key
1 | Test | This is a test | red | chicken
In my results, rather than doing a seperate query for every row. What if there were 10,000? Etc.
Many thanks in advance!
Upvotes: 2
Views: 131
Reputation: 43464
Using the EAV (Entity-Attribute-Value) model you will always have to cope with these kind of issues. They're also not ver efficient due to the complexity of the queries (pivoting is required in most of them).
SELECT page_id,
MAX(CASE WHEN option_key = 'background' THEN option_value END) background,
MAX(CASE WHEN option_key = 'module1_key' THEN option_value END) module1_key,
MAX(CASE WHEN option_key = 'module2_key' THEN option_value END) module2_key
FROM page_option
GROUP BY page_id
For example, given this table:
| PAGE_ID | OPTION_KEY | OPTION_VALUE |
|---------|-------------|--------------|
| 1 | background | red |
| 1 | module1_key | chicken |
| 2 | module1_key | duck |
| 3 | module1_key | cow |
| 4 | background | blue |
| 4 | module2_key | alien |
| 4 | module1_key | chicken |
You will the following output:
| PAGE_ID | BACKGROUND | MODULE1_KEY | MODULE2_KEY |
|---------|------------|-------------|-------------|
| 1 | red | chicken | (null) |
| 2 | (null) | duck | (null) |
| 3 | (null) | cow | (null) |
| 4 | blue | chicken | alien |
Fiddle here.
Then just join with the page
table and that's it :) I've omitted that part in order to focus the query in the grouping itself.
Upvotes: 1
Reputation: 6037
If option_key
is uniqe per page_id
(you don't have two or more background
with page_id==1
) you can do:
SELECT page.page_id, page.title, page.content,
GROUP_CONCAT(option_key SEPARATOR '@') AS option_keys,
GROUP_CONCAT(option_value SEPARATOR '@') as option_values,
FROM page
LEFT JOIN page_option ON page_option.page_id=page.page_id
WHERE page.page_id=USER_SPECIFIED_ID
You can execute this SQL-query and put its result into $result
. After you should do every item of $result
:
$result[$i]["options"] = array_combine(
explode("@",$result[$i]["option_keys"]),
explode("@",$result[$i]["option_values"])
);
You can do it with a foreach
or you can use array_walk
too.
After these you've an associative array with options in $result[$i]["options"]
:
{
"background" => "red",
"module_key1"=> "chicken"
}
I hope it's what do you want.
Upvotes: 0
Reputation: 13725
If you can add virtual fields with the activerecord class you can do something similar:
$this->db->add_field("(select group_concat(concat(option_key,':',option_value) SEPARATOR ' ') from page_option where page_id=$page_id group by page_id)");
It wont be optimal...
Upvotes: 0