<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>logon2 Blog &#187; MySQL</title>
	<atom:link href="http://www.logon2.com.au/blog/archive/category/coding/mysql-coding/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.logon2.com.au/blog</link>
	<description>Better use of the web for everybody</description>
	<lastBuildDate>Tue, 13 Jul 2010 12:44:29 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2</generator>
		<item>
		<title>Returning the entire row of maximum value for each group (Group-wise Maximum in SQL / MySQL )</title>
		<link>http://www.logon2.com.au/blog/archive/coding/mysql-coding/groupwise-maximum-mysql/</link>
		<comments>http://www.logon2.com.au/blog/archive/coding/mysql-coding/groupwise-maximum-mysql/#comments</comments>
		<pubDate>Thu, 08 Jul 2010 02:01:36 +0000</pubDate>
		<dc:creator>Tim</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[database]]></category>
		<category><![CDATA[groupwise]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[solution]]></category>
		<category><![CDATA[sql]]></category>

		<guid isPermaLink="false">http://www.logon2.com.au/blog/?p=178</guid>
		<description><![CDATA[NOTE: The content of this article is inaccurate and needs to be amended. If you wish to be notified when the update is made, you may subscribe via email. Last week I was joyfully hacking away at a project, progressing from from one task to another. Then it came time to do a non-trivial SQL [...]]]></description>
			<content:encoded><![CDATA[<h3>NOTE: The content of this article is inaccurate and needs to be amended. If you wish to be notified when the update is made, you may <a href="http://www.feedburner.com/fb/a/emailverifySubmit?feedId=1395571&amp;loc=en_US">subscribe via email</a>.</h3>
<p><img class="size-thumbnail wp-image-180" style="float: right;" title="Some SQL" src="http://www.logon2.com.au/blog/wp-content/SQL-150x150.jpg" alt="Some SQL" width="150" height="150" /><span style="color: #c0c0c0;">Last week I was joyfully hacking away at a project, progressing from from one task to another. Then it came time to do a non-trivial SQL query, though at the time I still thought the query would be nothing more than a select with a GROUP BY. I was wrong.</span></p>
<h3><span style="color: #c0c0c0;">The Problem</span></h3>
<p><span style="color: #c0c0c0;">What I wanted was to return the most recent log for each task for a particular user.</span></p>
<p><span style="color: #c0c0c0;">Here&#8217;s my table:<br />
</span> </p>
<pre>+--------+---------+---------+---------------------+----------+--------------------------------------------------------------------------------------------------------+
| log_id | user_id | task_id | when                | duration | description                                                                                            |
+--------+---------+---------+---------------------+----------+--------------------------------------------------------------------------------------------------------+
|      1 |       1 |       1 | 0000-00-00 00:00:00 |      120 | getting logging working at a very basic level                                                          |
|      2 |       1 |       1 | 2010-06-26 13:52:49 |       20 | polishing off log insertion                                                                            |
|      3 |       1 |       2 | 2010-06-26 15:00:44 |      120 |                                                                                                        |
|      4 |       1 |       3 | 2010-06-27 14:00:40 |      300 | over 30 serves for school lunches                                                                      |
|      6 |       1 |       4 | 2010-06-28 15:00:51 |      150 | YesMen - WordPress theme                                                                               |
|      7 |       1 |       4 | 2010-06-29 16:30:03 |      240 | on the YesMen WordPress theme                                                                          |
|      8 |       1 |       5 | 2010-06-30 17:32:13 |      240 | Checking out the market, looking at competition and discussions involving successful developers   12pm |
|      9 |       1 |       1 | 2010-06-30 07:00:05 |       40 |                                                                                                        |
+--------+---------+---------+---------------------+----------+--------------------------------------------------------------------------------------------------------+
</pre>
<p><span style="color: #c0c0c0;">Originally, this was the query that I thought would do it:</span></p>
<pre><span style="color: #c0c0c0;">SELECT * FROM `logs` WHERE `user_id` = '1' GROUP BY `task_id` ORDER BY `when` DESC;</span></pre>
<p><span style="color: #c0c0c0;">Which gave me:</span></p>
<pre>+--------+---------+---------+---------------------+----------+--------------------------------------------------------------------------------------------------------+
| log_id | user_id | task_id | when                | duration | description                                                                                            |
+--------+---------+---------+---------------------+----------+--------------------------------------------------------------------------------------------------------+
|      8 |       1 |       5 | 2010-06-30 17:32:13 |      240 | Checking out the market, looking at competition and discussions involving successful developers   12pm |
|      6 |       1 |       4 | 2010-06-28 15:00:51 |      150 | YesMen - WordPress theme                                                                               |
|      4 |       1 |       3 | 2010-06-27 14:00:40 |      300 | over 30 serves for school lunches                                                                      |
|      3 |       1 |       2 | 2010-06-26 15:00:44 |      120 |                                                                                                        |
|      1 |       1 |       1 | 0000-00-00 00:00:00 |      120 | getting logging working at a very basic level                                                          |
+--------+---------+---------+---------------------+----------+--------------------------------------------------------------------------------------------------------+
</pre>
<p><span style="color: #c0c0c0;">It looks fine, until you notice task_id 1.</span></p>
<p><span style="color: #c0c0c0;">&#8220;What?&#8221; I thought, &#8220;That&#8217;s the least recent log for task_id 1!&#8221;. Then it struck me, &#8220;Oh, ORDER BY is performed after GROUP BY.&#8221; <strong>(Edit)</strong><a href="http://fullof.bs/">John Haugeland</a> for bringing up a problem in  this post. My expectation of valid results was unfounded because  no explicit execution order is defined &#8211; it may  not always (or ever) produce the correct results. In other words, I shouldn&#8217;t have expected it to work.</span> Thanks to commenter</p>
<p><span style="color: #c0c0c0;">So I thought a bit and said &#8220;I know!&#8221; and this was my next attempt:</span></p>
<pre>SELECT * FROM `logs` GROUP BY `task_id` HAVING `when` = MAX(`when`);
</pre>
<p><span style="color: #c0c0c0;">Which yielded:</span></p>
<pre>+--------+---------+---------+---------------------+----------+--------------------------------------------------------------------------------------------------------+
| log_id | user_id | task_id | when                | duration | description                                                                                            |
+--------+---------+---------+---------------------+----------+--------------------------------------------------------------------------------------------------------+
|      3 |       1 |       2 | 2010-06-26 15:00:44 |      120 |                                                                                                        |
|      4 |       1 |       3 | 2010-06-27 14:00:40 |      300 | over 30 serves for school lunches                                                                      |
|      8 |       1 |       5 | 2010-06-30 17:32:13 |      240 | Checking out the market, looking at competition and discussions involving successful developers   12pm |
+--------+---------+---------+---------------------+----------+--------------------------------------------------------------------------------------------------------+
</pre>
<p><span style="color: #c0c0c0;">Though I was hoping that query would do what I wanted, I had a feeling it would do exactly this. It is similar to the last query except it completely removes the logs that aren&#8217;t the most recent. This is just as bad. By this point, I had done some searching and even went through my old RMIT Database Concepts Assignments looking for answers. &#8220;What now? Will I have to give in and use a subquery? Oh, please no&#8230;&#8221; As a final resort, I hopped over to #mysql on freenode (I&#8217;m SickAnimations).</span></p>
<p><span style="color: #c0c0c0;">Somebody linked me to a post about <a href="http://www.dnorth.net/2007/07/28/groupwise-maximum/">Groupwise Maximum queries on David North&#8217;s blog</a>, which was about exactly the same problem as mine. However, the solution there didn&#8217;t to work for me &#8211; it gave the same results as my second attempt. Knowing the new term &#8220;Groupwise Maximum&#8221; and how to use The Google, I came across this little nugget on the official MySQL website: <a href="http://dev.mysql.com/doc/refman/5.0/en/example-maximum-column-group-row.html">3.6.4. The Rows Holding the Group-wise Maximum of a Certain Column</a>. Jackpot.</span></p>
<h3>The Solution</h3>
<p><span style="color: #c0c0c0;">As I feared, the solution did indeed require a subquery. Here&#8217;s my application of MySQL&#8217;s suggestion solution:</span></p>
<pre>SELECT *
FROM `logs` outer_logs
WHERE
	`outer_logs`.`user_id` = 1 AND
	`outer_logs`.`when` = (
		SELECT MAX( `inner_logs`.`when` )
		FROM LOGS inner_logs
		WHERE `outer_logs`.`task_id` = `inner_logs`.`task_id`
)
</pre>
<p><span style="color: #c0c0c0;">Providing the desired output:</span></p>
<pre>+--------+---------+---------+---------------------+----------+--------------------------------------------------------------------------------------------------------+
| log_id | user_id | task_id | when                | duration | description                                                                                            |
+--------+---------+---------+---------------------+----------+--------------------------------------------------------------------------------------------------------+
|      3 |       1 |       2 | 2010-06-26 15:00:44 |      120 |                                                                                                        |
|      4 |       1 |       3 | 2010-06-27 14:00:40 |      300 | over 30 serves for school lunches                                                                      |
|      7 |       1 |       4 | 2010-06-29 16:30:03 |      240 | on the YesMen WordPress theme                                                                          |
|      8 |       1 |       5 | 2010-06-30 17:32:13 |      240 | Checking out the market, looking at competition and discussions involving successful developers   12pm |
|     17 |       1 |       1 | 2010-07-08 06:50:39 |       45 | Finalising transition to Linux development environment                                                 |
+--------+---------+---------+---------------------+----------+--------------------------------------------------------------------------------------------------------+
</pre>
<p><span style="color: #c0c0c0;">Feels mostly good, except the subquery part. Oh well.</span></p>
<p><em>Image from <a href="http://www.flickr.com/photos/wingler/3429634150/">mwin</a>.</em></p>
<h3>Related Posts:</h3><ul class="related_post"><li><a href="http://www.logon2.com.au/blog/archive/coding/lomadaba/lomadaba-the-automatic-mysql-database-dump-backup-php-script/" title="Lomadaba, The Automatic MySQL Database Dump Backup PHP Script">Lomadaba, The Automatic MySQL Database Dump Backup PHP Script</a></li><li><a href="http://www.logon2.com.au/blog/archive/troubleshooting/check-if-headers-have-already-been-sent-in-php/" title="Check if Headers Have Already Been Sent in PHP">Check if Headers Have Already Been Sent in PHP</a></li></ul>]]></content:encoded>
			<wfw:commentRss>http://www.logon2.com.au/blog/archive/coding/mysql-coding/groupwise-maximum-mysql/feed/</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
	</channel>
</rss>

