Есть ли лучший способ рассчитать медиану (не среднюю) - PullRequest
14 голосов
/ 17 сентября 2010

Предположим, у меня есть следующее определение таблицы:

CREATE TABLE x (i serial primary key, value integer not null);

Я хочу вычислить МЕДИАНУ value (не AVG). Медиана - это значение, которое делит множество на два подмножества, содержащих одинаковое количество элементов. Если число элементов четное, медиана является средним значением наибольшего значения в самом нижнем сегменте и самым низким значением самого большого сегмента. (Подробнее см. Википедию.)

Вот как мне удается вычислить МЕДИАНУ, но я думаю, что должен быть лучший способ:

SELECT AVG(values_around_median) AS median
  FROM (
    SELECT
       DISTINCT(CASE WHEN FIRST_VALUE(above) OVER w2 THEN MIN(value) OVER w3 ELSE MAX(value) OVER w2 END)
        AS values_around_median
      FROM (
        SELECT LAST_VALUE(value) OVER w AS value,
               SUM(COUNT(*)) OVER w > (SELECT count(*)/2 FROM x) AS above
          FROM x
          GROUP BY value
          WINDOW w AS (ORDER BY value)
          ORDER BY value
        ) AS find_if_values_are_above_or_below_median
      WINDOW w2 AS (PARTITION BY above ORDER BY value DESC),
             w3 AS (PARTITION BY above ORDER BY value ASC)
    ) AS find_values_around_median

Есть идеи?

Ответы [ 7 ]

23 голосов
/ 07 января 2015

Да, в PostgreSQL 9.4 вы можете использовать недавно введенную функцию обратного распределения PERCENTILE_CONT(), агрегатную функцию с упорядоченным набором, которая также указана в стандарте SQL.

WITH t(value) AS (
  SELECT 1   UNION ALL
  SELECT 2   UNION ALL
  SELECT 100 
)
SELECT
  percentile_cont(0.5) WITHIN GROUP (ORDER BY value)
FROM
  t;

Эта эмуляция MEDIAN() через PERCENTILE_CONT() также задокументирована здесь .

15 голосов
/ 17 сентября 2010

Действительно, есть более простой способ.В Postgres вы можете определить свои собственные агрегатные функции.Некоторое время назад я опубликовал функции для выполнения медианы, а также режима и диапазона в библиотеке фрагментов PostgreSQL.

http://wiki.postgresql.org/wiki/Aggregate_Median

7 голосов
/ 21 ноября 2011

Более простой запрос для этого:

WITH y AS (
   SELECT value, row_number() OVER (ORDER BY value) AS rn
   FROM   x
   WHERE  value IS NOT NULL
   )
, c AS (SELECT count(*) AS ct FROM y) 
SELECT CASE WHEN c.ct%2 = 0 THEN
          round((SELECT avg(value) FROM y WHERE y.rn IN (c.ct/2, c.ct/2+1)), 3)
       ELSE
                (SELECT     value  FROM y WHERE y.rn = (c.ct+1)/2)
       END AS median
FROM   c;

Основные баллы

  • Игнорирует значения NULL.
  • Основной функцией является оконная функция row_number () , которая существует с версии 8.4
  • Финальный SELECT получает одну строку для нечетных чисел и avg() из двух строк для четных чисел. Результат числовой, округленный до 3 знаков после запятой.

Тест показывает, что новая версия в 4 раза быстрее (и в отличие от правильных результатов) запроса в вопросе:

CREATE TEMP TABLE x (value int);
INSERT INTO x SELECT generate_series(1,10000);
INSERT INTO x VALUES (NULL),(NULL),(NULL),(3);
0 голосов
/ 06 апреля 2017

Используйте функцию ниже для нахождения n-го процентиля

CREATE or REPLACE FUNCTION nth_percentil(anyarray, int)
    RETURNS 
        anyelement as 
    $$
        SELECT $1[$2/100.0 * array_upper($1,1) + 1] ;
    $$ 
LANGUAGE SQL IMMUTABLE STRICT;

В вашем случае это 50-й процентиль.

Используйте запрос ниже, чтобы получить медиану

SELECT nth_percentil(ARRAY (SELECT Field_name FROM table_name ORDER BY 1),50)

Это даст вам 50-й процентиль, который в основном является медианой.

Надеюсь, это полезно.

0 голосов
/ 06 апреля 2017
CREATE TABLE array_table (id integer, values integer[]) ;

INSERT INTO array_table VALUES ( 1,'{1,2,3}');
INSERT INTO array_table VALUES ( 2,'{4,5,6,7}');

select id, values, cardinality(values) as array_length,
(case when cardinality(values)%2=0 and cardinality(values)>1 then (values[(cardinality(values)/2)]+ values[((cardinality(values)/2)+1)])/2::float 
 else values[(cardinality(values)+1)/2]::float end) as median  
 from array_table

Или вы можете создать функцию и использовать ее где угодно в ваших дальнейших запросах.

CREATE OR REPLACE FUNCTION median (a integer[]) 
RETURNS float AS    $median$ 
Declare     
    abc float; 
BEGIN    
    SELECT (case when cardinality(a)%2=0 and cardinality(a)>1 then 
           (a[(cardinality(a)/2)] + a[((cardinality(a)/2)+1)])/2::float   
           else a[(cardinality(a)+1)/2]::float end) into abc;    
    RETURN abc; 
END;    
$median$ 
LANGUAGE plpgsql;

select id,values,median(values) from array_table
0 голосов
/ 21 ноября 2016

Простой sql только с собственными функциями postgres:

select 
    case count(*)%2
        when 1 then (array_agg(num order by num))[count(*)/2+1]
        else ((array_agg(num order by num))[count(*)/2]::double precision + (array_agg(num order by num))[count(*)/2+1])/2
    end as median
from unnest(array[5,17,83,27,28]) num;

Конечно, вы можете добавить coalesce () или что-то еще, если хотите обрабатывать нули.

0 голосов
/ 09 сентября 2016

Для Google: есть также http://pgxn.org/dist/quantile Медиана может быть рассчитана в одну строку после установки этого расширения.

...