Function to Calculate Median in SQL Server Function to Calculate Median in SQL Server sql-server sql-server

Function to Calculate Median in SQL Server


If you're using SQL 2005 or better this is a nice, simple-ish median calculation for a single column in a table:

SELECT( (SELECT MAX(Score) FROM   (SELECT TOP 50 PERCENT Score FROM Posts ORDER BY Score) AS BottomHalf) + (SELECT MIN(Score) FROM   (SELECT TOP 50 PERCENT Score FROM Posts ORDER BY Score DESC) AS TopHalf)) / 2 AS Median


2019 UPDATE: In the 10 years since I wrote this answer, more solutions have been uncovered that may yield better results. Also, SQL Server releases since then (especially SQL 2012) have introduced new T-SQL features that can be used to calculate medians. SQL Server releases have also improved its query optimizer which may affect perf of various median solutions. Net-net, my original 2009 post is still OK but there may be better solutions on for modern SQL Server apps. Take a look at this article from 2012 which is a great resource: https://sqlperformance.com/2012/08/t-sql-queries/median

This article found the following pattern to be much, much faster than all other alternatives, at least on the simple schema they tested. This solution was 373x faster (!!!) than the slowest (PERCENTILE_CONT) solution tested. Note that this trick requires two separate queries which may not be practical in all cases. It also requires SQL 2012 or later.

DECLARE @c BIGINT = (SELECT COUNT(*) FROM dbo.EvenRows);SELECT AVG(1.0 * val)FROM (    SELECT val FROM dbo.EvenRows     ORDER BY val     OFFSET (@c - 1) / 2 ROWS     FETCH NEXT 1 + (1 - @c % 2) ROWS ONLY) AS x;

Of course, just because one test on one schema in 2012 yielded great results, your mileage may vary, especially if you're on SQL Server 2014 or later. If perf is important for your median calculation, I'd strongly suggest trying and perf-testing several of the options recommended in that article to make sure that you've found the best one for your schema.

I'd also be especially careful using the (new in SQL Server 2012) function PERCENTILE_CONT that's recommended in one of the other answers to this question, because the article linked above found this built-in function to be 373x slower than the fastest solution. It's possible that this disparity has been improved in the 7 years since, but personally I wouldn't use this function on a large table until I verified its performance vs. other solutions.

ORIGINAL 2009 POST IS BELOW:

There are lots of ways to do this, with dramatically varying performance. Here's one particularly well-optimized solution, from Medians, ROW_NUMBERs, and performance. This is a particularly optimal solution when it comes to actual I/Os generated during execution – it looks more costly than other solutions, but it is actually much faster.

That page also contains a discussion of other solutions and performance testing details. Note the use of a unique column as a disambiguator in case there are multiple rows with the same value of the median column.

As with all database performance scenarios, always try to test a solution out with real data on real hardware – you never know when a change to SQL Server's optimizer or a peculiarity in your environment will make a normally-speedy solution slower.

SELECT   CustomerId,   AVG(TotalDue)FROM(   SELECT      CustomerId,      TotalDue,      -- SalesOrderId in the ORDER BY is a disambiguator to break ties      ROW_NUMBER() OVER (         PARTITION BY CustomerId         ORDER BY TotalDue ASC, SalesOrderId ASC) AS RowAsc,      ROW_NUMBER() OVER (         PARTITION BY CustomerId         ORDER BY TotalDue DESC, SalesOrderId DESC) AS RowDesc   FROM Sales.SalesOrderHeader SOH) xWHERE   RowAsc IN (RowDesc, RowDesc - 1, RowDesc + 1)GROUP BY CustomerIdORDER BY CustomerId;


In SQL Server 2012 you should use PERCENTILE_CONT:

SELECT SalesOrderID, OrderQty,    PERCENTILE_CONT(0.5)         WITHIN GROUP (ORDER BY OrderQty)        OVER (PARTITION BY SalesOrderID) AS MedianContFROM Sales.SalesOrderDetailWHERE SalesOrderID IN (43670, 43669, 43667, 43663)ORDER BY SalesOrderID DESC

See also : http://blog.sqlauthority.com/2011/11/20/sql-server-introduction-to-percentile_cont-analytic-functions-introduced-in-sql-server-2012/