views:

67

answers:

2

I have two tables(entries and tags) with a many-to-many linking table. Right now, I'm making a query to retrieve all the entries that meet my criteria, and a query for each entry to retrieve the tags.

Would there be a way to say, have the tags returned through a new column as an array in the first query?

A: 

You can use GROUP BY and GROUP_CONCAT function to get all tags at once as a concatenated string. http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html

codeholic
Be aware that group_concat has a limit, you have to `SET group_concat_max_len = 2048;` at the start to ensure that GROUP_CONCAT accommodates the numerous fields.
Pentium10
+1  A: 

This query:

SELECT     e.id                               entry_id
,          GROUP_CONCAT(t.txt ORDER BY t.txt) tags
FROM       entry e
LEFT JOIN  entry_tag et
ON         e.id = et.entry_id
LEFT JOIN  tag t
ON         et.tag_id = t.id
GROUP BY   e.id

Will return the tags as a comma-separated list. You can read up on GROUP_CONCAT for more options: http://dev.mysql.com/doc/refman/5.1/en/group-by-functions.html#function_group-concat

In your app you should be able to expand this into an array quite easily. For example in php, you could use explode: http://php.net/manual/en/function.explode.php

If you need more attributes from the tag or entry_tag tables, you can either add yet more GROUP_CONCAT columns, or think of some serialization format for your data (like JSON) and use GROUP_CONCAT on that, or you can simply return multiple rows per entry and process the results in the application to keep tags together with entries:

$sql = '
    SELECT     e.id                  entry_id
    ,          t.id                  tag_id
    ,          t.txt                 tag_text
    ,          t.published           tag_published
    FROM       entry e
    LEFT JOIN  entry_tag et
    ON         e.id = et.entry_id
    LEFT JOIN  tag t
    ON         et.tag_id = t.id
    ORDER BY   e.id
';        
$result = mysql_query($ql);
$entry_id = NULL;
$entry_rows = NULL;
while ($row = mysql_fetch_assoc($result)) {
    if ($entry_id != $row['entry_id']) {
        if (isset($entry_id)) {           //ok, found new entry
            process_entry($entry_rows);   //process the rows collected so far
        }
        $entry_id = $row['entry_id'];
        $entry_rows = array();
    }
    $entry_rows[] = $row;                 //store row for his entry for later processing
}
if (isset($entry_id)){                    //process the batch of rows for the last entry
    process_entry($entry_rows);           
}
Roland Bouman
Be aware that group_concat has a limit you have to `SET group_concat_max_len = 2048;` at the start to ensure that GROUP_CONCAT accommodates the numerous fields.
Pentium10
@Pentium10: that is one way. Anotherr way is specifying it in your my.cnf. Your size of 2048 seems arbitrary though. Personally I recommend setting it to @@max_allowed_packet if you want to be really sure.
Roland Bouman
We have a lot of tags, and this 2048 was working just fine. Thanks for sharing that variable.
Pentium10
So there isn't a way to make them truly multi-dimensional?This seems like a workable solution, but I actually want to return more than just a single value from each tag(such as id, name, published), so would this really be my best method, or is my current method the ideal way to do it?It just seems like that is a workaround for my actual problem...
Donny Swany
If your current way, which is best enough, gives you speed issues, try caching the tags query output (they don't change frequently) and you can reset the cache if you store the last edit date of the tags.
Pentium10
Donny, SQL is a single-minded language: it works on, and retuns tables - the implementation of a relation. By definition, that is not multidimensional. You can of course use one query to return all results you need, but you're going to need some processing in the application. See my amended answer
Roland Bouman
Okay, this pretty much answered my question. I actually thought about doing it the way you did in the second section, but I thought that was somewhat redundant returning the same fields that often. I just wanted multi-dimensional results, but apparently that can't be done. I probably will stick to how I'm doing it now because there is actually a lot more data I require than my simple example. I don't see any performance issues right now, but I'll probably implement some sort of caching to keep any issues from arising. Thanks!
Donny Swany