<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	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/"
	
	>
<channel>
	<title>
	Comments on: Medians pre-SQL 2012	</title>
	<atom:link href="https://lobsterpot.com.au/blog/2015/01/27/medians-pre-sql-2012/feed/" rel="self" type="application/rss+xml" />
	<link>https://lobsterpot.com.au/blog/2015/01/27/medians-pre-sql-2012/</link>
	<description></description>
	<lastBuildDate>Mon, 09 Feb 2015 18:31:22 +0000</lastBuildDate>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1.3</generator>
	<item>
		<title>
		By: TheSQLGuru		</title>
		<link>https://lobsterpot.com.au/blog/2015/01/27/medians-pre-sql-2012/#comment-2534</link>

		<dc:creator><![CDATA[TheSQLGuru]]></dc:creator>
		<pubDate>Mon, 09 Feb 2015 18:31:22 +0000</pubDate>
		<guid isPermaLink="false">http://blogs.lobsterpot.com.au/?p=3420#comment-2534</guid>

					<description><![CDATA[Joe, please run your versions against the others using Aaron Bertrand&#039;s 1M row test harness and report back on their performance.]]></description>
			<content:encoded><![CDATA[<p>Joe, please run your versions against the others using Aaron Bertrand&#8217;s 1M row test harness and report back on their performance.</p>
]]></content:encoded>
		
			</item>
		<item>
		<title>
		By: Rob Farley		</title>
		<link>https://lobsterpot.com.au/blog/2015/01/27/medians-pre-sql-2012/#comment-2533</link>

		<dc:creator><![CDATA[Rob Farley]]></dc:creator>
		<pubDate>Sun, 01 Feb 2015 23:08:39 +0000</pubDate>
		<guid isPermaLink="false">http://blogs.lobsterpot.com.au/?p=3420#comment-2533</guid>

					<description><![CDATA[Thank you Joe - terrific fun to read this.]]></description>
			<content:encoded><![CDATA[<p>Thank you Joe &#8211; terrific fun to read this.</p>
]]></content:encoded>
		
			</item>
		<item>
		<title>
		By: Joe Celko		</title>
		<link>https://lobsterpot.com.au/blog/2015/01/27/medians-pre-sql-2012/#comment-2532</link>

		<dc:creator><![CDATA[Joe Celko]]></dc:creator>
		<pubDate>Sun, 01 Feb 2015 22:48:00 +0000</pubDate>
		<guid isPermaLink="false">http://blogs.lobsterpot.com.au/?p=3420#comment-2532</guid>

					<description><![CDATA[The median was a hot topic in SQL in the early 1990&#039;S . There were two newsstand magazines for databases, DBMS and DATABASE PROGRAMMING &#038; DESIGN. They started at two separate publishers, but thanks to mergers and buyout found themselves under CMP and they were merged into Intelligent Enterprise later. I wrote columns in both magazines. Chris Date got my old position when I moved to DBMS. He has collected his old columns into a series of books form Addison-Wesley. 
Chris would write a column, then i would write a reply, much like the famous Fred Allen &#038; Jack Benny “feud” on early radio (&lt;a href=&quot;http://www.otrcat.com/jack-benny-and-fred-allen-feud-p-48618.html&quot; rel=&quot;nofollow ugc&quot;&gt;http://www.otrcat.com/jack-benny-and-fred-allen-feud-p-48618.html&lt;/a&gt;). We wanted to sell both magazines! 
Chris and I both finished a column with an SQL puzzle or problem. The median became the hot topic in 1992 and 1993, after the SQL-92 standard came. Date proposed two different solutions for the median. His first solution was based on the fact that if you duplicate every row in a table, the median will stay the same. The duplication will guarantee that you always work with a table that has an even number of rows. The first version that appeared in his column was wrong and drew some mail from me and from others who had different solutions. Here is a corrected version of his first solution, with his famous Parts table. 
CREATE VIEW Temp1
AS SELECT weight FROM Parts
 UNION ALL
 SELECT weight FROM Parts;
CREATE VIEW Temp2
AS SELECT weight
 FROM Temp1
 &#060;= (SELECT COUNT(*)
 FROM Temp1 AS T1
 WHERE T1.weight &#062;= Temp1.weight)
 AND (SELECT COUNT(*) FROM Parts)
 &#060;= (SELECT COUNT(*)
 FROM Temp1 AS T2
 WHERE T2.weight &#060;= Temp1.weight);
SELECT AVG(DISTINCT weight) AS median
 FROM Temp2;
This involves the construction of a doubled table of values, which can be expensive in terms of both time and storage space. You will also find that this requires a good implementation of the SQL-92 standard that allows you to use a correlated scalar subquery in place of a scalar value expression. Most SQL implementations are not yet that sophisticated. 
The use of AVG(DISTINCT x) is important because leaving it out would return the simple average instead of the median. Consider the set of weights (12, 17, 17, 14, 12, 19). The doubled table, Temp1, is then (12, 12, 12, 12, 14, 14, 17, 17, 17, 17, 19, 19). But because of the duplicated values, Temp2 becomes (14, 14, 17, 17, 17, 17), not just (14, 17). The simple average is (96 / 6.0) = 16; it should be (31 / 2.0) = 15.5 instead. 
My first solution involved a slight modification of Date&#039;s solution to avoid the use of a doubled table, but it depends on a SQL-92 implementation that has a CEILING() function or particular vendor of rounding and truncation.
SELECT MIN(weight) -- smallest value in upper half
 FROM Parts
 WHERE weight IN (SELECT P1.weight
 FROM Parts AS P1, Parts AS P2
 WHERE P2.weight &#062;= P1.weight
 GROUP BY P1.weight
 HAVING COUNT(*) &#060;=
 (SELECT CEILING(COUNT(*) / 2.0)
 FROM Parts))
UNION
SELECT MAX(weight) -- largest value in lower half
 FROM Parts
 WHERE weight IN (SELECT P1.weight
 FROM Parts AS P1, Parts AS P2
 WHERE P2.weight &#060;= P1.weight
 HAVING COUNT(*) &#060;=
 (SELECT CEILING(COUNT(*) / 2.0)
 FROM Parts));
or using the same idea and a CASE expression:
 FROM (SELECT MAX(weight)
 FROM Parts AS B1
 WHERE (SELECT COUNT(*) + 1
 FROM Parts
 WHERE weight &#060; B1.weight)
 &#060;= (SELECT CEILING (COUNT(*)/2.0)
 FROM Parts)
 UNION ALL
 SELECT MAX(weight)
 FROM Parts AS B
 WHERE (SELECT COUNT(*) + 1
 FROM Parts
 WHERE weight &#060; B.weight)
 &#060;= CASE (SELECT MOD (COUNT(*), 2)
 FROM Parts)
 WHEN 0
 THEN (SELECT CEILING (COUNT(*)/2.0) + 1
 FROM Parts)
 ELSE (SELECT CEILING (COUNT(*)/2.0)
 FROM Parts)
 END) AS Medians(weight);
At this point, we got more and more answers. And the code got more and more complicated. If you want to look at it, it is in Chapter 23 of the Third Edition of SQL FOR SMARTIES. I would not use any of them today, but I have a discussion and a detailed explanation of how I improved my code. 
Date&#039;s second solution in 1995 was based on Celko&#039;s median, folded into one query where I had used VIEWs for readability (CTE did not exist yet). 
SELECT AVG(DISTINCT Parts.weight) AS median
 FROM Parts
 WHERE Parts.weight IN
 (SELECT MIN(weight)
 FROM Parts
 WHERE Parts.weight IN
 (SELECT P2.weight
 FROM Parts AS P1, Parts AS P2
 WHERE P2.weight &#060;= P1.weight
 GROUP BY P2.weight
 HAVING COUNT(*)
 &#060;= (SELECT CEILING(COUNT(*) / 2.0)
 FROM Parts))
 UNION
 SELECT MAX(weight)
 FROM Parts
 WHERE Parts.weight IN
 (SELECT P2.weight
 FROM Parts AS P1, Parts AS P2
 WHERE P2.weight &#062;= P1.weight
 GROUP BY P2.weight
 HAVING COUNT(*)
 &#060;= (SELECT CEILING(COUNT(*) / 2.0)
 FROM Parts)));
Date mentions that this solution will return a NULL for an empty table and that it assumes there are no NULLs in the column. If there are NULLs, the WHERE clauses should be modified to remove them.
Rory Murchison was a regular contributor to puzzle answers. He came up with the idea of concatenating the key to each alue to make sure that every value is seen as a unique entity. Selecting the middle values is then a special case of finding the n-th item in the table.
My Second Median used a working table with the values and a tally of their occurrences from the
original table. This working table should be quite a bit smaller than the original table, and very fast to construct if there is an index on the target column. An awful self-join then gave you the counts of rows before and after each row&#039;s value. Finding the middle was easy. 
-- construct table of cumulative tallies
CREATE TABLE Summary
(weight REAL NOT NULL,
 occurs INTEGER NOT NULL, -- number of occurrences
 pretally INTEGER NOT NULL, -- cumulative tally before
 posttally INTEGER NOT NULL); -- cumulative tally after
SELECT AVG(S3.weight) AS median
 FROM Summary AS S3
 WHERE (S3.posttally &#062; (SELECT MAX(posttally) / 2.0 FROM Summary)
 AND S3.pretally &#060; (SELECT MAX(posttally) / 2.0 FROM Summary))
 OR S3.pretally = (SELECT MAX(posttally) / 2.0 FROM Summary)
 OR S3.posttally = (SELECT MAX(posttally) / 2.0 FROM Summary);
A simple median technique based on all of these methods was proposed by Philip Vaughan of San Jose, CA. It derives a VIEW with unique weights and number of occurrences and then a VIEW of the middle weights.
Anatoly Abramovich, Yelena Alexandrova, and Eugene Birger presented a series of articles in SQL Forum magazine on computing the median (SQL Forum 1993, 1994). They define a characteristic
function, which they call delta, using the SIGN() function. The delta or characteristic function accepts a Boolean expression as an argument and returns one if it is TRUE and zero if it is FALSE or UNKNOWN. In SQL-92 we have a CASE expression, which can be used to construct the delta function. CASE was new to SQL-92.
The authors also distinguish between the statistical median, whose value must be a member of the set, and the financial median, whose value is the average of the middle two members of the set. A statistical median exists when there is an odd number of items in the set. If there is an even number of items, you
must decide if you want to use the highest value in the lower half (they call this the left median) or the lowest value in the upper half (they call this the right median).
The left statistical median of a unique column can be found with this query, if you will assume that we have a column called bin which represents the storage location of a part.
SELECT P1.bin
 FROM Parts AS P1, Parts AS P2
 GROUP BY P1.bin
HAVING SUM(CASE WHEN (P2.bin &#060;= P1.bin) THEN 1 ELSE 0 END)
 = (COUNT(*) / 2.0);
Changing the direction of the theta test in the HAVING clause will allow you to pick the right statistical median Getting the other medians involves playing with this self-join query and the CASE expressions. 
The statistical median of a column with duplicate values can be found with a query based on the same ideas, but you have to adjust the HAVING clause to allow for overlap. 
My Third Median
Another approach made easier with SQL-92 involves looking at a picture of a line of sorted values and seeing where the median would fall. Every value in column weight of the table partitions the table into three sections, the values which are less than weight, equal to weight or greater than weight. We can get a profile of each value with a tabular subquery expression. 
Now the question is how to define a median in terms of the partitions. Clearly, the definition of a median means that if (lesser = greater) then weight is the median.
SELECT AVG(DISTINCT weight)
 FROM (SELECT P1.pno, P1.weight,
 SUM(CASE WHEN P2.weight &#060; P1.weight
 SUM(CASE WHEN P2.weight = P1.weight
 THEN 1 ELSE 0 END),
 SUM(CASE WHEN P2.weight &#062; P1.weight
 THEN 1 ELSE 0 END)
 FROM Parts AS P1, Parts AS P2
 GROUP BY P1.pno, P1.weight)
 AS Partitions (pno, weight, lesser, equal, greater)
 WHERE lesser = greater
 OR (lesser &#060;= (SELECT COUNT(*) FROM Parts)/2.0
 AND greater &#060;= (SELECT COUNT(*) FROM Parts)/2.0);
It is also worth noting that you can use either AVG(DISTINCT i) or AVG(i) in the SELECT clause. The AVG(DISTINCT i) will return the usual median when there are two values. This happens when you have an even number of rows and a partition in the middle, such as (1,2,2, 3, 3, 3) which has (2, 3) in the middle, which gives us 2.5 for the median. The AVG(i) will return the weighted median instead. This happens when you also factor in the number of times the two values in the middle of a table with an even number of rows. The table with (1,2,2, 3, 3, 3) would return (2,2, 3, 3, 3) in the middle, which gives us 2.6 for the weighted median. The weighted median is a more accurate description of the data.
I sent this attempt to Richard Romley, who made it quite a bit simpler: , but let me take you thru the steps so you can see the reasoning.
Look at the WHERE clause. It could use some algebra and since it deals only with aggregate functions and scalar subqueries, you could move it into a HAVING clause. Moving things from the WHERE clause into the HAVING clause in a grouped query is important for performance, but it is not always possible.
SELECT AVG(DISTINCT weight)
 FROM (SELECT P1.weight
 FROM Parts AS P1, Parts AS P2
 GROUP BY P1.pno, P1.weight
 THEN 1 ELSE 0 END)
 &#062;= ABS(SUM(CASE WHEN P2.weight &#060; P1.weight THEN 1
 WHEN P2.weight &#062; P1.weight THEN -1
 ELSE 0 END)))
 AS Partitions;
If you prefer to use functions instead of a CASE expression, then use this version of the query:
SELECT AVG(DISTINCT weight)
 FROM (SELECT P1.weight
 FROM Parts AS P1, Parts AS P2
 GROUP BY P1.pno, P1.weight
 HAVING SUM(ABS(1 - SIGN(P1.weight - P2.weight))
 &#062;= ABS(SUM(SIGN (P1.weight - P2.weight)))
 AS Partitions;]]></description>
			<content:encoded><![CDATA[<p>The median was a hot topic in SQL in the early 1990&#8217;S . There were two newsstand magazines for databases, DBMS and DATABASE PROGRAMMING &amp; DESIGN. They started at two separate publishers, but thanks to mergers and buyout found themselves under CMP and they were merged into Intelligent Enterprise later. I wrote columns in both magazines. Chris Date got my old position when I moved to DBMS. He has collected his old columns into a series of books form Addison-Wesley.<br />
Chris would write a column, then i would write a reply, much like the famous Fred Allen &amp; Jack Benny “feud” on early radio (<a href="http://www.otrcat.com/jack-benny-and-fred-allen-feud-p-48618.html" rel="nofollow ugc">http://www.otrcat.com/jack-benny-and-fred-allen-feud-p-48618.html</a>). We wanted to sell both magazines!<br />
Chris and I both finished a column with an SQL puzzle or problem. The median became the hot topic in 1992 and 1993, after the SQL-92 standard came. Date proposed two different solutions for the median. His first solution was based on the fact that if you duplicate every row in a table, the median will stay the same. The duplication will guarantee that you always work with a table that has an even number of rows. The first version that appeared in his column was wrong and drew some mail from me and from others who had different solutions. Here is a corrected version of his first solution, with his famous Parts table.<br />
CREATE VIEW Temp1<br />
AS SELECT weight FROM Parts<br />
 UNION ALL<br />
 SELECT weight FROM Parts;<br />
CREATE VIEW Temp2<br />
AS SELECT weight<br />
 FROM Temp1<br />
 &lt;= (SELECT COUNT(*)<br />
 FROM Temp1 AS T1<br />
 WHERE T1.weight &gt;= Temp1.weight)<br />
 AND (SELECT COUNT(*) FROM Parts)<br />
 &lt;= (SELECT COUNT(*)<br />
 FROM Temp1 AS T2<br />
 WHERE T2.weight &lt;= Temp1.weight);<br />
SELECT AVG(DISTINCT weight) AS median<br />
 FROM Temp2;<br />
This involves the construction of a doubled table of values, which can be expensive in terms of both time and storage space. You will also find that this requires a good implementation of the SQL-92 standard that allows you to use a correlated scalar subquery in place of a scalar value expression. Most SQL implementations are not yet that sophisticated.<br />
The use of AVG(DISTINCT x) is important because leaving it out would return the simple average instead of the median. Consider the set of weights (12, 17, 17, 14, 12, 19). The doubled table, Temp1, is then (12, 12, 12, 12, 14, 14, 17, 17, 17, 17, 19, 19). But because of the duplicated values, Temp2 becomes (14, 14, 17, 17, 17, 17), not just (14, 17). The simple average is (96 / 6.0) = 16; it should be (31 / 2.0) = 15.5 instead.<br />
My first solution involved a slight modification of Date&#8217;s solution to avoid the use of a doubled table, but it depends on a SQL-92 implementation that has a CEILING() function or particular vendor of rounding and truncation.<br />
SELECT MIN(weight) &#8212; smallest value in upper half<br />
 FROM Parts<br />
 WHERE weight IN (SELECT P1.weight<br />
 FROM Parts AS P1, Parts AS P2<br />
 WHERE P2.weight &gt;= P1.weight<br />
 GROUP BY P1.weight<br />
 HAVING COUNT(*) &lt;=<br />
 (SELECT CEILING(COUNT(*) / 2.0)<br />
 FROM Parts))<br />
UNION<br />
SELECT MAX(weight) &#8212; largest value in lower half<br />
 FROM Parts<br />
 WHERE weight IN (SELECT P1.weight<br />
 FROM Parts AS P1, Parts AS P2<br />
 WHERE P2.weight &lt;= P1.weight<br />
 HAVING COUNT(*) &lt;=<br />
 (SELECT CEILING(COUNT(*) / 2.0)<br />
 FROM Parts));<br />
or using the same idea and a CASE expression:<br />
 FROM (SELECT MAX(weight)<br />
 FROM Parts AS B1<br />
 WHERE (SELECT COUNT(*) + 1<br />
 FROM Parts<br />
 WHERE weight &lt; B1.weight)<br />
 &lt;= (SELECT CEILING (COUNT(*)/2.0)<br />
 FROM Parts)<br />
 UNION ALL<br />
 SELECT MAX(weight)<br />
 FROM Parts AS B<br />
 WHERE (SELECT COUNT(*) + 1<br />
 FROM Parts<br />
 WHERE weight &lt; B.weight)<br />
 &lt;= CASE (SELECT MOD (COUNT(*), 2)<br />
 FROM Parts)<br />
 WHEN 0<br />
 THEN (SELECT CEILING (COUNT(*)/2.0) + 1<br />
 FROM Parts)<br />
 ELSE (SELECT CEILING (COUNT(*)/2.0)<br />
 FROM Parts)<br />
 END) AS Medians(weight);<br />
At this point, we got more and more answers. And the code got more and more complicated. If you want to look at it, it is in Chapter 23 of the Third Edition of SQL FOR SMARTIES. I would not use any of them today, but I have a discussion and a detailed explanation of how I improved my code.<br />
Date&#8217;s second solution in 1995 was based on Celko&#8217;s median, folded into one query where I had used VIEWs for readability (CTE did not exist yet).<br />
SELECT AVG(DISTINCT Parts.weight) AS median<br />
 FROM Parts<br />
 WHERE Parts.weight IN<br />
 (SELECT MIN(weight)<br />
 FROM Parts<br />
 WHERE Parts.weight IN<br />
 (SELECT P2.weight<br />
 FROM Parts AS P1, Parts AS P2<br />
 WHERE P2.weight &lt;= P1.weight<br />
 GROUP BY P2.weight<br />
 HAVING COUNT(*)<br />
 &lt;= (SELECT CEILING(COUNT(*) / 2.0)<br />
 FROM Parts))<br />
 UNION<br />
 SELECT MAX(weight)<br />
 FROM Parts<br />
 WHERE Parts.weight IN<br />
 (SELECT P2.weight<br />
 FROM Parts AS P1, Parts AS P2<br />
 WHERE P2.weight &gt;= P1.weight<br />
 GROUP BY P2.weight<br />
 HAVING COUNT(*)<br />
 &lt;= (SELECT CEILING(COUNT(*) / 2.0)<br />
 FROM Parts)));<br />
Date mentions that this solution will return a NULL for an empty table and that it assumes there are no NULLs in the column. If there are NULLs, the WHERE clauses should be modified to remove them.<br />
Rory Murchison was a regular contributor to puzzle answers. He came up with the idea of concatenating the key to each alue to make sure that every value is seen as a unique entity. Selecting the middle values is then a special case of finding the n-th item in the table.<br />
My Second Median used a working table with the values and a tally of their occurrences from the<br />
original table. This working table should be quite a bit smaller than the original table, and very fast to construct if there is an index on the target column. An awful self-join then gave you the counts of rows before and after each row&#8217;s value. Finding the middle was easy.<br />
&#8212; construct table of cumulative tallies<br />
CREATE TABLE Summary<br />
(weight REAL NOT NULL,<br />
 occurs INTEGER NOT NULL, &#8212; number of occurrences<br />
 pretally INTEGER NOT NULL, &#8212; cumulative tally before<br />
 posttally INTEGER NOT NULL); &#8212; cumulative tally after<br />
SELECT AVG(S3.weight) AS median<br />
 FROM Summary AS S3<br />
 WHERE (S3.posttally &gt; (SELECT MAX(posttally) / 2.0 FROM Summary)<br />
 AND S3.pretally &lt; (SELECT MAX(posttally) / 2.0 FROM Summary))<br />
 OR S3.pretally = (SELECT MAX(posttally) / 2.0 FROM Summary)<br />
 OR S3.posttally = (SELECT MAX(posttally) / 2.0 FROM Summary);<br />
A simple median technique based on all of these methods was proposed by Philip Vaughan of San Jose, CA. It derives a VIEW with unique weights and number of occurrences and then a VIEW of the middle weights.<br />
Anatoly Abramovich, Yelena Alexandrova, and Eugene Birger presented a series of articles in SQL Forum magazine on computing the median (SQL Forum 1993, 1994). They define a characteristic<br />
function, which they call delta, using the SIGN() function. The delta or characteristic function accepts a Boolean expression as an argument and returns one if it is TRUE and zero if it is FALSE or UNKNOWN. In SQL-92 we have a CASE expression, which can be used to construct the delta function. CASE was new to SQL-92.<br />
The authors also distinguish between the statistical median, whose value must be a member of the set, and the financial median, whose value is the average of the middle two members of the set. A statistical median exists when there is an odd number of items in the set. If there is an even number of items, you<br />
must decide if you want to use the highest value in the lower half (they call this the left median) or the lowest value in the upper half (they call this the right median).<br />
The left statistical median of a unique column can be found with this query, if you will assume that we have a column called bin which represents the storage location of a part.<br />
SELECT P1.bin<br />
 FROM Parts AS P1, Parts AS P2<br />
 GROUP BY P1.bin<br />
HAVING SUM(CASE WHEN (P2.bin &lt;= P1.bin) THEN 1 ELSE 0 END)<br />
 = (COUNT(*) / 2.0);<br />
Changing the direction of the theta test in the HAVING clause will allow you to pick the right statistical median Getting the other medians involves playing with this self-join query and the CASE expressions.<br />
The statistical median of a column with duplicate values can be found with a query based on the same ideas, but you have to adjust the HAVING clause to allow for overlap.<br />
My Third Median<br />
Another approach made easier with SQL-92 involves looking at a picture of a line of sorted values and seeing where the median would fall. Every value in column weight of the table partitions the table into three sections, the values which are less than weight, equal to weight or greater than weight. We can get a profile of each value with a tabular subquery expression.<br />
Now the question is how to define a median in terms of the partitions. Clearly, the definition of a median means that if (lesser = greater) then weight is the median.<br />
SELECT AVG(DISTINCT weight)<br />
 FROM (SELECT P1.pno, P1.weight,<br />
 SUM(CASE WHEN P2.weight &lt; P1.weight<br />
 SUM(CASE WHEN P2.weight = P1.weight<br />
 THEN 1 ELSE 0 END),<br />
 SUM(CASE WHEN P2.weight &gt; P1.weight<br />
 THEN 1 ELSE 0 END)<br />
 FROM Parts AS P1, Parts AS P2<br />
 GROUP BY P1.pno, P1.weight)<br />
 AS Partitions (pno, weight, lesser, equal, greater)<br />
 WHERE lesser = greater<br />
 OR (lesser &lt;= (SELECT COUNT(*) FROM Parts)/2.0<br />
 AND greater &lt;= (SELECT COUNT(*) FROM Parts)/2.0);<br />
It is also worth noting that you can use either AVG(DISTINCT i) or AVG(i) in the SELECT clause. The AVG(DISTINCT i) will return the usual median when there are two values. This happens when you have an even number of rows and a partition in the middle, such as (1,2,2, 3, 3, 3) which has (2, 3) in the middle, which gives us 2.5 for the median. The AVG(i) will return the weighted median instead. This happens when you also factor in the number of times the two values in the middle of a table with an even number of rows. The table with (1,2,2, 3, 3, 3) would return (2,2, 3, 3, 3) in the middle, which gives us 2.6 for the weighted median. The weighted median is a more accurate description of the data.<br />
I sent this attempt to Richard Romley, who made it quite a bit simpler: , but let me take you thru the steps so you can see the reasoning.<br />
Look at the WHERE clause. It could use some algebra and since it deals only with aggregate functions and scalar subqueries, you could move it into a HAVING clause. Moving things from the WHERE clause into the HAVING clause in a grouped query is important for performance, but it is not always possible.<br />
SELECT AVG(DISTINCT weight)<br />
 FROM (SELECT P1.weight<br />
 FROM Parts AS P1, Parts AS P2<br />
 GROUP BY P1.pno, P1.weight<br />
 THEN 1 ELSE 0 END)<br />
 &gt;= ABS(SUM(CASE WHEN P2.weight &lt; P1.weight THEN 1<br />
 WHEN P2.weight &gt; P1.weight THEN -1<br />
 ELSE 0 END)))<br />
 AS Partitions;<br />
If you prefer to use functions instead of a CASE expression, then use this version of the query:<br />
SELECT AVG(DISTINCT weight)<br />
 FROM (SELECT P1.weight<br />
 FROM Parts AS P1, Parts AS P2<br />
 GROUP BY P1.pno, P1.weight<br />
 HAVING SUM(ABS(1 &#8211; SIGN(P1.weight &#8211; P2.weight))<br />
 &gt;= ABS(SUM(SIGN (P1.weight &#8211; P2.weight)))<br />
 AS Partitions;</p>
]]></content:encoded>
		
			</item>
	</channel>
</rss>
