{"id":85395,"date":"2019-10-17T17:16:27","date_gmt":"2019-10-17T17:16:27","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=85395"},"modified":"2026-04-30T09:41:22","modified_gmt":"2026-04-30T09:41:22","slug":"what-does-percentile_cont-do","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/what-does-percentile_cont-do\/","title":{"rendered":"PERCENTILE_CONT in SQL Server: Continuous Percentile and Interpolation Explained"},"content":{"rendered":"\n<p><strong>PERCENTILE_CONT is a SQL Server windowing function that computes a continuous percentile value from an ordered set of numbers. Given a percentile P (between 0 and 1), it returns the value at that position in the sorted set &#8211; interpolating between two adjacent values if the exact position falls between them. <\/strong><\/p>\n\n\n\n<p><strong>For example, PERCENTILE_CONT(0.5) returns the median: the middle value of an odd-count set, or the average of the two middle values of an even-count set. PERCENTILE_CONT requires SQL Server 2012 or later and uses the WITHIN GROUP (ORDER BY \u2026) syntax. <\/strong><\/p>\n\n\n\n<p><strong>This article explains how the interpolation actually works, contrasts PERCENTILE_CONT with PERCENTILE_DISC (which returns one of the actual values rather than interpolating), and presents a pre-2012 equivalent query using classic T-SQL windowing functions for anyone still working on older SQL Server versions.<\/strong><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-what-is-percentile-cont-in-sql-server\">What is PERCENTILE_CONT in SQL Server?<\/h2>\n\n\n\n<p>I have been living and breathing T-SQL windowing functions for years. I couldn\u2019t tell you how many times I have presented on the topic, but I expect it is well over 50. I had also used these functions quite often when I worked in consulting, but now I spend more time teaching others instead of solving real queries with the functions. I love my job as editor of Simple-Talk, but I do sometimes miss those days of writing and tuning queries for customers.<\/p>\n\n\n\n<p>I was recently playing with the analytical group of windowing functions, and I wanted to understand how they worked \u201cunder the covers.\u201d I ran into a little logic puzzle with<code> PERCENTILE_CONT<\/code> by trying to write a query that returned the same results using pre-2012 functionality.<\/p>\n\n\n\n<p>Given a list of ranked values, you can use the <code>PERCENTILE_CONT<\/code> function to find the value at a specific percentile. For example, if you have the grades of 100 students, you can use <code>PERCENTILE_CONT<\/code> to locate the score in the middle of the list, the median, or at some other percent such as the grade at 90%. This doesn\u2019t mean that the score was 90%; it means that the position of the score was at the 90<sup>th<\/sup> percentile. If there is not a value at the <em>exact <\/em>location, <code>PERCENTILE_CONT<\/code> interpolates the answer.<\/p>\n\n\n\n<p>The <a href=\"https:\/\/docs.microsoft.com\/en-us\/sql\/t-sql\/functions\/percentile-cont-transact-sql?view=sql-server-2017\">definition<\/a> from Microsoft for <code>PERCENTILE_CONT<\/code> is:<\/p>\n\n\n\n<p><em>\u201cCalculates a percentile based on a continuous distribution of the column value in SQL Server. The result is interpolated and might not be equal to any of the specific values in the column.\u201d<\/em><\/p>\n\n\n\n<p>There is also a function called <code>PERCENTILE_DISC<\/code> that is similar. Instead of interpolating to find the exact value, it returns one of the actual values in the set.<\/p>\n\n\n\n<p>At first glance, the <code>PERCENTILE_CONT<\/code> function doesn\u2019t seem so difficult to understand, but I had a hard time writing a query that produced the same results using older functionality. Before I show you the path I travelled to come up with the pre-2012 solution, take a look at how to use <code>PERCENTILE_CONT<\/code> with an example from AdventureWorks that ranks the count of sales for each month in a given year. I also include <code>PERCENTILE_DISC<\/code> in this query so that you can see the difference between the two functions.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT MONTH(SOH.OrderDate) AS OrderMonth,\n       COUNT(*) AS OrderCount,\n       PERCENTILE_CONT(0.5) \n           WITHIN GROUP(ORDER BY COUNT(*)) OVER () AS Median,\n       PERCENTILE_DISC(0.5) \n           WITHIN GROUP(ORDER BY COUNT(*)) OVER () AS NotTheMedian\nFROM Sales.SalesOrderHeader AS SOH\nWHERE SOH.OrderDate &gt;= '1\/1\/2012'\n      AND SOH.OrderDate &lt; '1\/1\/2013'\nGROUP BY MONTH(SOH.OrderDate);<\/pre>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"863\" height=\"601\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/10\/word-image-49.png\" alt=\"\" class=\"wp-image-85396\"\/><\/figure>\n\n\n\n<p>The <code>PERCENTILE_DISC<\/code> function returns the value 321, which is close to but not the median. The <code>PERCENTILE_CONT<\/code> function returns the average of the two values around the median, 321 and 336, for an answer of 328.5. If there were an odd number of items in the list, then the two answers would be the same. The results of <code>PERCENTILE_CONT<\/code> and <code>PERCENTILE_DISC<\/code> do not depend on the values in the set, just the position. For example, if you tried to find the median of this list (1,2,3,4,1000) the answer is 3. It doesn&#8217;t matter that 1000 would skew the results of the <em>mean<\/em>.<\/p>\n\n\n\n<p>The syntax of <code>PERCENTILE_CONT<\/code> and <code>PERCENTILE_DISC<\/code> is different than the other windowing functions because these require the <code>WITHIN GROUP<\/code> clause. Inside that, you must provide an <code>ORDER BY<\/code> expression that returns a list of numbers such as scores, heights, sales, etc. You still need an <code>OVER<\/code> clause, but it supports only the <code>PARTITION BY<\/code>.<\/p>\n\n\n\n<p>Here\u2019s another example looking for three different percentiles:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT MONTH(SOH.OrderDate) AS OrderMonth,\n       COUNT(*) AS OrderCount,\n       PERCENTILE_CONT(0.25) \n           WITHIN GROUP(ORDER BY COUNT(*)) OVER () AS [25%],\n       PERCENTILE_CONT(0.5) \n           WITHIN GROUP(ORDER BY COUNT(*)) OVER () AS [50%],\n       PERCENTILE_CONT(0.75) \n           WITHIN GROUP(ORDER BY COUNT(*)) OVER () AS [75%]\nFROM Sales.SalesOrderHeader AS SOH\nWHERE SOH.OrderDate &gt;= '1\/1\/2012'\n      AND SOH.OrderDate &lt; '1\/1\/2013'\nGROUP BY MONTH(SOH.OrderDate);<\/pre>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"820\" height=\"604\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/10\/word-image-50.png\" alt=\"\" class=\"wp-image-85397\"\/><\/figure>\n\n\n\n<p>This example returns the <code>PERCENTILE_CONT<\/code> values at 25%, 50%, and 75% respectively. These will be used as examples for testing the code for the pre-2012 solution.<\/p>\n\n\n\n<section id=\"my-first-block-block_35ceb7575b120e884d7b5feb953fcab9\" class=\"my-first-block alignwide\">\n    <div class=\"bg-brand-600 text-base-white py-5xl px-4xl rounded-sm bg-gradient-to-r from-brand-600 to-brand-500 red\">\n        <div class=\"gap-4xl items-start md:items-center flex flex-col md:flex-row justify-between\">\n            <div class=\"flex-1 col-span-10 lg:col-span-7\">\n                <h3 class=\"mt-0 font-display mb-2 text-display-sm\">Fast, reliable and consistent SQL Server development&#8230;<\/h3>\n                <div class=\"child:last-of-type:mb-0\">\n                                            &#8230;with SQL Toolbelt Essentials. 10 ingeniously simple tools for accelerating development, reducing risk, and standardizing workflows.                                    <\/div>\n            <\/div>\n                            <a href=\"https:\/\/www.red-gate.com\/products\/sql-toolbelt-essentials\/\" class=\"btn btn--secondary btn--lg\">Learn more &amp; try for free<\/a>\n                    <\/div>\n    <\/div>\n<\/section>\n\n\n<h2 class=\"wp-block-heading\" id=\"h-a-possible-formula\">A Possible Formula<\/h2>\n\n\n\n<p>Of course, I started by searching for an explanation of the function, but I ran into a <a href=\"https:\/\/www.sqlskills.com\/blogs\/bobb\/what-exactly-does-percentile_cont-do-anyhow\/\">formula<\/a> that worked for 50% but fell apart when checking any other percentile. Here\u2019s the description from the article:<\/p>\n\n\n<div class=\"block-core-list\">\n<ol class=\"wp-block-list\">\n<li>Find the row number at the percentile with this formula: <br><code>RN = (1 + (P*(N-1))<\/code> where <code>P <\/code>is the percentile and <code>N<\/code> is the number of rows.<\/li>\n\n\n\n<li>Use the <code>CEILING<\/code> and <code>FLOOR<\/code> functions to find the rows above (<code>CRN<\/code>) and below (<code>FRN<\/code>) the row number found in step 1.<\/li>\n\n\n\n<li>If <code>CRN = RN<\/code> and <code>FRN = RN<\/code>, use the value found at <code>RN<\/code>.<\/li>\n\n\n\n<li>Otherwise, add together the results of the values found at those two rows each multiplied by <code>P<\/code>. <br><code>P *  Value at CRN + P * Value at FRN<\/code><\/li>\n<\/ol>\n<\/div>\n\n\n<p>Here are the calculations for each of the percentiles in this example:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1199\" height=\"359\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/10\/table1.png\" alt=\"\" class=\"wp-image-85405\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>The formula works only with the 50<sup>th<\/sup> percentile and not for the others. The calculated values were not even close for 25% and 75%.<\/p>\n\n\n\n<p>Here\u2019s a batch that you can use to verify the results. Change the value of <code>@P<\/code> to try different percentiles:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">DECLARE @P DECIMAL(2, 2) = 0.5;\n--Using Percentile_Cont\nWITH PC AS (\n   SELECT MONTH(OrderDate) AS OrderMonth, \n      COUNT(*) AS OrderCount, \n      PERCENTILE_CONT(@P) \n          WITHIN GROUP(ORDER BY COUNT(*)) OVER() AS PercentileCont\n   FROM Sales.SalesOrderHeader AS SOH\n   WHERE SOH.OrderDate &gt;= '1\/1\/2012'\n      AND SOH.OrderDate &lt; '1\/1\/2013'\n   GROUP BY MONTH(OrderDate))\nSELECT DISTINCT PC.PercentileCont\nFROM PC;\n--Works for the median\nWITH GetRowNumbers\nAS (\n   SELECT ROW_NUMBER() OVER (ORDER BY COUNT(*)) AS RowNum,\n      MONTH(SOH.OrderDate) AS OrderMonth,\n      COUNT(*) AS OrderCount,\n      (1 + (@P * (COUNT(*) OVER () - 1))) AS RN,\n      CEILING((1 + (@P * (COUNT(*) OVER () - 1)))) AS CRN,\n      FLOOR((1 + (@P * (COUNT(*) OVER () - 1)))) AS FRN\n    FROM Sales.SalesOrderHeader AS SOH\n    WHERE SOH.OrderDate &gt;= '1\/1\/2012'\n       AND SOH.OrderDate &lt; '1\/1\/2013'\n    GROUP BY MONTH(SOH.OrderDate)), FindValues\nAS (SELECT MIN(   \n      CASE\n          WHEN GetRowNumbers.RN = GetRowNumbers.CRN\n             AND GetRowNumbers.RN = GetRowNumbers.FRN \n          THEN GetRowNumbers.OrderCount\n      END ) AS ActualValue,\n      SUM(   \n\t  CASE\n              WHEN GetRowNumbers.RowNum = GetRowNumbers.FRN \n\t      THEN GetRowNumbers.OrderCount\n          END\n              ) AS FloorValue,\n      SUM(   \n         CASE\n              WHEN GetRowNumbers.RowNum = GetRowNumbers.CRN \n              THEN GetRowNumbers.OrderCount\n         END\n              ) AS CeilingValue\n    FROM GetRowNumbers)\nSELECT COALESCE(FindValues.ActualValue, FindValues.FloorValue * \n          @P + FindValues.CeilingValue * @P) AS CalcAnswer\nFROM FindValues;<\/pre>\n\n\n\n<p>The first CTE, <code>GetRowNumbers<\/code>, completes step 1, finding the correct row numbers. The second CTE, <code>FindValues<\/code>, completes step 2, locating the values. The outer query completes steps 3 and 4, checking for an exact value or applying the formula.<\/p>\n\n\n\n<p>Since the formula was not correct, I had to come up with something else.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-a-logical-next-step\">A Logical Next Step<\/h2>\n\n\n\n<p>My first thought was that the difference between the two rows should be multiplied by <code>P<\/code> and added to the bottom value. This didn\u2019t work either, even though it seemed to make sense. Again, this works for the 50<sup>th<\/sup> percentile but falls apart for the others. One good thing about this revised formula is that the answers were always between the floor and ceiling values, but in one case my answer was too low and in the other too high.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1191\" height=\"362\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/10\/table2.png\" alt=\"\" class=\"wp-image-85407\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>At this point, I was beginning to question the function itself since my idea made sense, well, to me at least.<\/p>\n\n\n\n<p>Here\u2019s a batch to test my hypothesis:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">DECLARE @P DECIMAL(2, 2) = 0.25;\n--Using Percentile_Cont\nWITH PC AS (\n   SELECT MONTH(OrderDate) AS OrderMonth, \n      COUNT(*) AS OrderCount, \n      PERCENTILE_CONT(@P) \n          WITHIN GROUP(ORDER BY COUNT(*)) OVER() AS PercentileCont\n   FROM Sales.SalesOrderHeader AS SOH\n   WHERE SOH.OrderDate &gt;= '1\/1\/2012'\n      AND SOH.OrderDate &lt; '1\/1\/2013'\n   GROUP BY MONTH(OrderDate))\nSELECT DISTINCT PC.PercentileCont\nFROM PC;\n--My hypothesis\nWITH GetRowNumbers\nAS (\n   SELECT ROW_NUMBER() OVER (ORDER BY COUNT(*)) AS RowNum,\n      MONTH(SOH.OrderDate) AS OrderMonth,\n      COUNT(*) AS OrderCount,\n      (1 + (@P * (COUNT(*) OVER () - 1))) AS RN,\n      CEILING((1 + (@P * (COUNT(*) OVER () - 1)))) AS CRN,\n      FLOOR((1 + (@P * (COUNT(*) OVER () - 1)))) AS FRN\n    FROM Sales.SalesOrderHeader AS SOH\n    WHERE SOH.OrderDate &gt;= '1\/1\/2012'\n       AND SOH.OrderDate &lt; '1\/1\/2013'\n    GROUP BY MONTH(SOH.OrderDate)), FindValues\nAS (SELECT MIN(   \n      CASE\n          WHEN GetRowNumbers.RN = GetRowNumbers.CRN\n             AND GetRowNumbers.RN = GetRowNumbers.FRN \n             THEN GetRowNumbers.OrderCount\n      END ) AS ActualValue,\n      SUM(   \n\t  CASE\n              WHEN GetRowNumbers.RowNum = GetRowNumbers.FRN \n\t      THEN GetRowNumbers.OrderCount\n          END\n              ) AS BottomValue,\n       SUM(   \n          CASE\n             WHEN GetRowNumbers.RowNum = GetRowNumbers.CRN \n             THEN GetRowNumbers.OrderCount\n          END\n              ) AS TopValue\n    FROM GetRowNumbers)\nSELECT COALESCE(FindValues.ActualValue, \n     FindValues.BottomValue + \n         (FindValues.TopValue - FindValues.BottomValue) *@P)\n         AS CalcAnswer\nFROM FindValues;<\/pre>\n\n\n\n<p>The CTEs are the same as the previous example since steps 1 \u2013 3 work. The difference is in the outer query where the new formula is used.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-the-solution\">The Solution<\/h2>\n\n\n\n<p>As I do with many problems, I started with a new query window and took it one step at a time. I knew that I was finding the correct rows for the floor and ceiling values at least. My next step was to look at the percentage of the difference between the two values. Was there a pattern?<\/p>\n\n\n\n<p>I used this calculation:<\/p>\n\n\n\n<p><code>(Answer \u2013 Floor value)\/(Ceiling value \u2013 Floor value) <\/code><\/p>\n\n\n\n<p>Here\u2019s a table that shows the results:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1197\" height=\"396\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/10\/table3.png\" alt=\"\" class=\"wp-image-85408\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>Bazinga! Notice that the <em>Percent of difference<\/em> in each case is equal to the fractional part of <em>RN<\/em>. This makes sense! If you view the numbers as being on a line, the value found at precisely the <em>RN<\/em> point is the answer!<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1055\" height=\"159\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/10\/word-image-51.png\" alt=\"\" class=\"wp-image-85398\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>The correct value can be found at the floor row (left side of the decimal point of the row number) plus a percentage (right side of the decimal point of the row number) of the way to the ceiling row. Here is the correct formula:<\/p>\n\n\n\n<p><code>floor value + (ceiling value \u2013 floor value) * (RN modulo FRN)<\/code><\/p>\n\n\n\n<p>By using modulo, the fractional part of <code>RN<\/code> can be found. Finally, here is the query:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">DECLARE @P DECIMAL(2, 2) = 0.25;\n--Using Percentile_Cont\nWITH PC AS (\n   SELECT MONTH(OrderDate) AS OrderMonth, \n      COUNT(*) AS OrderCount, \n      PERCENTILE_CONT(@P) \n          WITHIN GROUP(ORDER BY COUNT(*)) OVER() AS PercentileCont\n   FROM Sales.SalesOrderHeader AS SOH\n   WHERE SOH.OrderDate &gt;= '1\/1\/2012'\n      AND SOH.OrderDate &lt; '1\/1\/2013'\n   GROUP BY MONTH(OrderDate))\nSELECT DISTINCT PC.PercentileCont\nFROM PC;\n--The answer!\nWITH GetRowNumbers\nAS (\n   SELECT ROW_NUMBER() OVER (ORDER BY COUNT(*)) AS RowNum,\n      MONTH(SOH.OrderDate) AS OrderMonth,\n      COUNT(*) AS OrderCount,\n      (1 + (@P * (COUNT(*) OVER () - 1))) AS RN,\n      CEILING((1 + (@P * (COUNT(*) OVER () - 1)))) AS CRN,\n      FLOOR((1 + (@P * (COUNT(*) OVER () - 1)))) AS FRN\n    FROM Sales.SalesOrderHeader AS SOH\n    WHERE SOH.OrderDate &gt;= '1\/1\/2012'\n       AND SOH.OrderDate &lt; '1\/1\/2013'\n    GROUP BY MONTH(SOH.OrderDate)), \nFindValues\nAS (SELECT MIN(   \n      CASE\n          WHEN GetRowNumbers.RN = GetRowNumbers.CRN\n             AND GetRowNumbers.RN = GetRowNumbers.FRN \n\t THEN GetRowNumbers.OrderCount\n      END ) AS ActualValue,\n      SUM(   \n\t  CASE\n              WHEN GetRowNumbers.RowNum = GetRowNumbers.FRN \n\t      THEN GetRowNumbers.OrderCount\n          END\n              ) AS FloorValue,\n       SUM(   \n           CASE\n              WHEN GetRowNumbers.RowNum = GetRowNumbers.CRN \n              THEN GetRowNumbers.OrderCount\n           END\n              ) AS CeilingValue, RN, FRN\n    FROM GetRowNumbers\n\tGROUP BY RN , FRn )\nSELECT COALESCE(FindValues.ActualValue, \n       FindValues.FloorValue  + \n       (FindValues.CeilingValue - FindValues.FloorValue) * (RN % FRN))\n       AS CalcAnswer\nFROM FindValues;<\/pre>\n\n\n\n<p>Once again, the only difference is the formula in the outer query. The CTEs are the same.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-conclusion\">Conclusion<\/h2>\n\n\n\n<p>Figuring out how <code>PERCENTILE_CONT<\/code> is calculated is not something that I needed to do, but I do enjoy solving the occasional T-SQL puzzle. The other windowing functions are not difficult to understand, but this one had me scratching my head for a bit. I thought that sharing my thought process might be interesting to others who are getting started with these functions.<\/p>\n\n\n\n<p>The main benefit of using windowing functions is that they make writing queries to solve tricky problems easier, often eliminating anti-patterns such as cursors or triangular joins. The solution does take advantage of the windowing functions introduced with 2005. Trying to accomplish the same thing with SQL Server 2000 or earlier would be even more difficult.<\/p>\n\n\n\n<section id=\"my-first-block-block_724c032785bd8487c1b4ccd09e3f6f52\" class=\"my-first-block alignwide\">\n    <div class=\"bg-brand-600 text-base-white py-5xl px-4xl rounded-sm bg-gradient-to-r from-brand-600 to-brand-500 red\">\n        <div class=\"gap-4xl items-start md:items-center flex flex-col md:flex-row justify-between\">\n            <div class=\"flex-1 col-span-10 lg:col-span-7\">\n                <h3 class=\"mt-0 font-display mb-2 text-display-sm\">Simple Talk is brought to you by Redgate Software<\/h3>\n                <div class=\"child:last-of-type:mb-0\">\n                                            Take control of your databases with the trusted Database DevOps solutions provider. Automate with confidence, scale securely, and unlock growth through AI.                                    <\/div>\n            <\/div>\n                            <a href=\"https:\/\/www.red-gate.com\/solutions\/overview\/\" class=\"btn btn--secondary btn--lg\">Discover how Redgate can help you<\/a>\n                    <\/div>\n    <\/div>\n<\/section>\n\n\n<section id=\"faq\" class=\"faq-block my-5xl\">\n    <h2>FAQs<\/h2>\n\n                        <h3 class=\"mt-4xl\">1. What does PERCENTILE_CONT do in SQL Server?<\/h3>\n            <div class=\"faq-answer\">\n                <p>PERCENTILE_CONT computes a continuous percentile value from an ordered set of numbers. It returns the value at the specified percentile position in the sorted set, interpolating between adjacent values when the exact position falls between them. For example, PERCENTILE_CONT(0.5) computes the median &#8211; for an even-count set like (10, 20, 30, 40), it returns 25, the average of the two middle values. The function requires SQL Server 2012 or later and uses the WITHIN GROUP (ORDER BY column) clause to specify the ordering. Combine it with an OVER (PARTITION BY &#8230;) clause to compute percentiles per group.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">2. What is the difference between PERCENTILE_CONT and PERCENTILE_DISC in SQL Server?<\/h3>\n            <div class=\"faq-answer\">\n                <p>PERCENTILE_CONT (continuous) interpolates between adjacent values to compute the exact percentile position &#8211; the result may be a value that doesn&#8217;t exist in the input set. PERCENTILE_DISC (discrete) returns one of the actual values from the set &#8211; the smallest value whose cumulative distribution is greater than or equal to the requested percentile. For the median (0.5): given (1, 2, 3, 4, 5), both return 3. Given (1, 2, 3, 4), PERCENTILE_CONT returns 2.5 (the interpolated middle) while PERCENTILE_DISC returns 2 (the actual value at the median position). Use CONT when you want a statistically smooth result; use DISC when you need an actual value from the dataset (for example, a real customer ID or a valid product code).<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">3. How do I calculate the median in SQL Server using PERCENTILE_CONT?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Use PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY column) OVER (). The OVER () clause with no partition means the median is computed across all rows. For a median per group, use OVER (PARTITION BY group_column). Example: SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY OrderAmount) OVER () AS OverallMedian FROM Sales. The result is returned on every row &#8211; if you only want one value, use DISTINCT or an aggregated subquery. Note that PERCENTILE_CONT is technically an analytic function (not an aggregate), so it must be used with OVER ().<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">4. How do I calculate percentiles in SQL Server 2008 without PERCENTILE_CONT?<\/h3>\n            <div class=\"faq-answer\">\n                <p>SQL Server 2008 and earlier don&#8217;t support PERCENTILE_CONT. The workaround is to use ROW_NUMBER() or NTILE() to identify the correct row positions and interpolate manually. For the median: compute ROW_NUMBER() OVER (ORDER BY column) and COUNT(*) OVER (), find the row numbers flanking the centre position, and average those two values. The article walks through the complete pre-2012 equivalent query for arbitrary percentiles, handling both odd-count and even-count sets and interpolating correctly for non-median percentiles (0.25, 0.75, etc.).<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">5. Can I use PERCENTILE_CONT with GROUP BY in SQL Server?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Not directly &#8211; PERCENTILE_CONT is an analytic function, not an aggregate, so it cannot be mixed with GROUP BY in the same query. To compute percentiles per group, use PERCENTILE_CONT(p) WITHIN GROUP (ORDER BY col) OVER (PARTITION BY group_col). The PARTITION BY in the OVER clause replaces GROUP BY for this function. The result is returned on every row in each partition; use SELECT DISTINCT or wrap in a CTE with ROW_NUMBER() = 1 to get one row per partition.<\/p>\n            <\/div>\n            <\/section>\n","protected":false},"excerpt":{"rendered":"<p>How the SQL Server PERCENTILE_CONT function computes continuous percentiles using interpolation, with a worked example from AdventureWorks, comparison with PERCENTILE_DISC, and a pre-2012 equivalent query using older T-SQL windowing functions.&hellip;<\/p>\n","protected":false},"author":110218,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143531],"tags":[5134],"coauthors":[11292],"class_list":["post-85395","post","type-post","status-publish","format-standard","hentry","category-t-sql-programming-sql-server","tag-sql-prompt"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/85395","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/110218"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=85395"}],"version-history":[{"count":14,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/85395\/revisions"}],"predecessor-version":[{"id":110229,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/85395\/revisions\/110229"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=85395"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=85395"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=85395"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=85395"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}