Prosty sposób obliczania mediany za pomocą MySQL

Jaki jest najprostszy (i mam nadzieję, że nie zbyt wolny) sposób obliczania mediany za pomocą MySQL? Użyłem AVG(x) do znalezienia średniej, ale mam trudności ze znalezieniem prostego sposobu obliczania mediany. Na razie zwracam wszystkie wiersze do PHP, robię sortowanie, a następnie wybieram środkowy wiersz, ale na pewno musi być jakiś prosty sposób na zrobienie tego w jednym zapytaniu MySQL.

Przykładowe dane:

id | val
--------
 1    4
 2    7
 3    2
 4    2
 5    9
 6    8
 7    3

Sortowanie na val daje 2 2 3 4 7 8 9, więc mediana powinna wynosić 4, a SELECT AVG(val) które = = 5.

Author: Török Gábor, 2009-08-18

30 answers

In MariaDB / MySQL:

SELECT AVG(dd.val) as median_val
FROM (
SELECT d.val, @rownum:=@rownum+1 as `row_number`, @total_rows:=@rownum
  FROM data d, (SELECT @rownum:=0) r
  WHERE d.val is NOT NULL
  -- put some where clause here
  ORDER BY d.val
) as dd
WHERE dd.row_number IN ( FLOOR((@total_rows+1)/2), FLOOR((@total_rows+2)/2) );

Steve Cohen zwraca uwagę, że po pierwszym przejściu @rownum będzie zawierać całkowitą liczbę wierszy. Można to wykorzystać do określenia mediany, więc nie jest potrzebne drugie przejście lub połączenie.

Również AVG(dd.val) i dd.row_number IN(...) jest używane do prawidłowego generowania mediany, gdy istnieje parzysta liczba rekordów. Rozumowanie:

SELECT FLOOR((3+1)/2),FLOOR((3+2)/2); -- when total_rows is 3, avg rows 2 and 2
SELECT FLOOR((4+1)/2),FLOOR((4+2)/2); -- when total_rows is 4, avg rows 2 and 3

Wreszcie, MariaDB 10.3.3+ zawiera funkcję mediany

 194
Author: velcrow,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-27 20:00:02

Właśnie znalazłem inną odpowiedź online w komentarzach :

Dla median w prawie każdym SQL:

SELECT x.val from data x, data y
GROUP BY x.val
HAVING SUM(SIGN(1-SIGN(y.val-x.val))) = (COUNT(*)+1)/2

Upewnij się, że kolumny są dobrze zindeksowane, A Indeks jest używany do filtrowania i sortowania. / Align = "left" /

select count(*) from table --find the number of rows

Oblicz "medianę" liczby wierszy. Może użyć: median_row = floor(count / 2).

Następnie wybierz go z listy:

select val from table order by val asc limit median_row,1

To powinno zwrócić Ci jeden wiersz z żądaną wartością.

Jakub

 52
Author: TheJacobTaylor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-11 16:20:48

Okazało się, że zaakceptowane rozwiązanie nie działa na mojej instalacji MySQL, zwracając pusty zestaw, ale to zapytanie działało dla mnie we wszystkich sytuacjach, w których go testowałem:

SELECT x.val from data x, data y
GROUP BY x.val
HAVING SUM(SIGN(1-SIGN(y.val-x.val)))/COUNT(*) > .5
LIMIT 1
 26
Author: zookatron,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-04 06:59:33

Niestety, ani odpowiedzi Jacobtaylora, ani velcro nie zwracają dokładnych wyników dla aktualnych wersji MySQL.

ODPOWIEDŹ Velcro z góry jest bliska, ale nie oblicza poprawnie zbiorów wyników o parzystej liczbie wierszy. Średnice są definiowane jako 1) Liczba Środkowa na nieparzystych zestawach liczb lub 2) średnia z dwóch liczb Środkowych na zestawach liczb parzystych.

Oto rozwiązanie velcro, które poradzi sobie zarówno z liczbą nieparzystą, jak i parzystą Zestawy:

SELECT AVG(middle_values) AS 'median' FROM (
  SELECT t1.median_column AS 'middle_values' FROM
    (
      SELECT @row:=@row+1 as `row`, x.median_column
      FROM median_table AS x, (SELECT @row:=0) AS r
      WHERE 1
      -- put some where clause here
      ORDER BY x.median_column
    ) AS t1,
    (
      SELECT COUNT(*) as 'count'
      FROM median_table x
      WHERE 1
      -- put same where clause here
    ) AS t2
    -- the following condition will return 1 record for odd number sets, or 2 records for even number sets.
    WHERE t1.row >= t2.count/2 and t1.row <= ((t2.count/2) +1)) AS t3;

Aby tego użyć, wykonaj następujące 3 proste kroki:

  1. Zastąp " median_table "(2 wystąpienia) w powyższym kodzie nazwą tabeli
  2. Zastąp " median_column "(3 wystąpienia) nazwą kolumny, którą chcesz znaleźć medianę dla
  3. jeśli masz warunek WHERE, zamień "WHERE 1" (2 wystąpienia) na warunek where
 14
Author: bob,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-16 01:42:10

Proponuję szybszy sposób.

Oblicz liczbę wierszy:

SELECT CEIL(COUNT(*)/2) FROM data;

Następnie weź średnią wartość w posortowanym zapytaniu podrzędnym:

SELECT max(val) FROM (SELECT val FROM data ORDER BY val limit @middlevalue) x;

Przetestowałem to z zestawem danych 5x10e6 liczb losowych i znajdzie medianę w mniej niż 10 sekund.

 9
Author: Reggie Edwards,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-05-31 00:05:20

Komentarz do Ta strona w dokumentacji MySQL ma następującą sugestię:

-- (mostly) High Performance scaling MEDIAN function per group
-- Median defined in http://en.wikipedia.org/wiki/Median
--
-- by Peter Hlavac
-- 06.11.2008
--
-- Example Table:

DROP table if exists table_median;
CREATE TABLE table_median (id INTEGER(11),val INTEGER(11));
COMMIT;


INSERT INTO table_median (id, val) VALUES
(1, 7), (1, 4), (1, 5), (1, 1), (1, 8), (1, 3), (1, 6),
(2, 4),
(3, 5), (3, 2),
(4, 5), (4, 12), (4, 1), (4, 7);



-- Calculating the MEDIAN
SELECT @a := 0;
SELECT
id,
AVG(val) AS MEDIAN
FROM (
SELECT
id,
val
FROM (
SELECT
-- Create an index n for every id
@a := (@a + 1) mod o.c AS shifted_n,
IF(@a mod o.c=0, o.c, @a) AS n,
o.id,
o.val,
-- the number of elements for every id
o.c
FROM (
SELECT
t_o.id,
val,
c
FROM
table_median t_o INNER JOIN
(SELECT
id,
COUNT(1) AS c
FROM
table_median
GROUP BY
id
) t2
ON (t2.id = t_o.id)
ORDER BY
t_o.id,val
) o
) a
WHERE
IF(
-- if there is an even number of elements
-- take the lower and the upper median
-- and use AVG(lower,upper)
c MOD 2 = 0,
n = c DIV 2 OR n = (c DIV 2)+1,

-- if its an odd number of elements
-- take the first if its only one element
-- or take the one in the middle
IF(
c = 1,
n = 1,
n = c DIV 2 + 1
)
)
) a
GROUP BY
id;

-- Explanation:
-- The Statement creates a helper table like
--
-- n id val count
-- ----------------
-- 1, 1, 1, 7
-- 2, 1, 3, 7
-- 3, 1, 4, 7
-- 4, 1, 5, 7
-- 5, 1, 6, 7
-- 6, 1, 7, 7
-- 7, 1, 8, 7
--
-- 1, 2, 4, 1

-- 1, 3, 2, 2
-- 2, 3, 5, 2
--
-- 1, 4, 1, 4
-- 2, 4, 5, 4
-- 3, 4, 7, 4
-- 4, 4, 12, 4


-- from there we can select the n-th element on the position: count div 2 + 1 
 7
Author: Sebastian Paaske Tørholm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-18 00:20:48

Budowanie z odpowiedzi velcro, dla tych z Was, którzy muszą zrobić medianę z czegoś, co jest pogrupowane według innego parametru:

SELECT grp_field, t1.val FROM (
   SELECT grp_field, @rownum:=IF(@s = grp_field, @rownum + 1, 0) AS row_number,
   @s:=IF(@s = grp_field, @s, grp_field) AS sec, d.val
  FROM data d,  (SELECT @rownum:=0, @s:=0) r
  ORDER BY grp_field, d.val
) as t1 JOIN (
  SELECT grp_field, count(*) as total_rows
  FROM data d
  GROUP BY grp_field
) as t2
ON t1.grp_field = t2.grp_field
WHERE t1.row_number=floor(total_rows/2)+1;
 4
Author: Doug,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-20 16:46:28

Większość powyższych rozwiązań działa tylko dla jednego pola tabeli, może być konieczne uzyskanie mediany (50 percentyla) dla wielu pól w zapytaniu.

Używam tego:

SELECT CAST(SUBSTRING_INDEX(SUBSTRING_INDEX(
 GROUP_CONCAT(field_name ORDER BY field_name SEPARATOR ','),
  ',', 50/100 * COUNT(*) + 1), ',', -1) AS DECIMAL) AS `Median`
FROM table_name;

Możesz zastąpić "50" w powyższym przykładzie na dowolny percentyl, jest bardzo wydajny.

Upewnij się tylko, że masz wystarczająco dużo pamięci dla GROUP_CONCAT, możesz to zmienić za pomocą:

SET group_concat_max_len = 10485760; #10MB max length

Więcej Szczegółów: http://web.performancerasta.com/metrics-tips-calculating-95th-99th-or-any-percentile-with-single-mysql-query/

 4
Author: Nico,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-13 11:33:31

Mam poniższy kod, który znalazłem na HackerRank i jest dość prosty i działa w każdym przypadku.

SELECT M.MEDIAN_COL FROM MEDIAN_TABLE M WHERE  
  (SELECT COUNT(MEDIAN_COL) FROM MEDIAN_TABLE WHERE MEDIAN_COL < M.MEDIAN_COL ) = 
  (SELECT COUNT(MEDIAN_COL) FROM MEDIAN_TABLE WHERE MEDIAN_COL > M.MEDIAN_COL );
 4
Author: Prashant Srivastav,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-13 13:23:04

Możesz użyć funkcji zdefiniowanej przez użytkownika, która znajduje się tutaj .

 3
Author: Alex Martelli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-18 00:19:52

Dba o nieparzystą liczbę wartości-podaje avg dwóch wartości w środku w tym przypadku.

SELECT AVG(val) FROM
  ( SELECT x.id, x.val from data x, data y
      GROUP BY x.id, x.val
      HAVING SUM(SIGN(1-SIGN(IF(y.val-x.val=0 AND x.id != y.id, SIGN(x.id-y.id), y.val-x.val)))) IN (ROUND((COUNT(*))/2), ROUND((COUNT(*)+1)/2))
  ) sq
 3
Author: Franz K.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-11-17 18:21:29

Mój kod, efektywny bez tabel i dodatkowych zmiennych:

SELECT
((SUBSTRING_INDEX(SUBSTRING_INDEX(group_concat(val order by val), ',', floor(1+((count(val)-1) / 2))), ',', -1))
+
(SUBSTRING_INDEX(SUBSTRING_INDEX(group_concat(val order by val), ',', ceiling(1+((count(val)-1) / 2))), ',', -1)))/2
as median
FROM table;
 2
Author: Oscar Canon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-23 15:31:07

Opcjonalnie można to również zrobić w procedurze składowanej:

DROP PROCEDURE IF EXISTS median;
DELIMITER //
CREATE PROCEDURE median (table_name VARCHAR(255), column_name VARCHAR(255), where_clause VARCHAR(255))
BEGIN
  -- Set default parameters
  IF where_clause IS NULL OR where_clause = '' THEN
    SET where_clause = 1;
  END IF;

  -- Prepare statement
  SET @sql = CONCAT(
    "SELECT AVG(middle_values) AS 'median' FROM (
      SELECT t1.", column_name, " AS 'middle_values' FROM
        (
          SELECT @row:=@row+1 as `row`, x.", column_name, "
          FROM ", table_name," AS x, (SELECT @row:=0) AS r
          WHERE ", where_clause, " ORDER BY x.", column_name, "
        ) AS t1,
        (
          SELECT COUNT(*) as 'count'
          FROM ", table_name, " x
          WHERE ", where_clause, "
        ) AS t2
        -- the following condition will return 1 record for odd number sets, or 2 records for even number sets.
        WHERE t1.row >= t2.count/2
          AND t1.row <= ((t2.count/2)+1)) AS t3
    ");

  -- Execute statement
  PREPARE stmt FROM @sql;
  EXECUTE stmt;
END//
DELIMITER ;


-- Sample usage:
-- median(table_name, column_name, where_condition);
CALL median('products', 'price', NULL);
 2
Author: bob,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-21 23:24:05
SELECT 
    SUBSTRING_INDEX(
        SUBSTRING_INDEX(
            GROUP_CONCAT(field ORDER BY field),
            ',',
            ((
                ROUND(
                    LENGTH(GROUP_CONCAT(field)) - 
                    LENGTH(
                        REPLACE(
                            GROUP_CONCAT(field),
                            ',',
                            ''
                        )
                    )
                ) / 2) + 1
            )),
            ',',
            -1
        )
FROM
    table

Powyższe wydaje się działać dla mnie.

 2
Author: Nochum Sossonko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-22 22:02:27

Użyłem podejścia dwóch zapytań:

  • pierwszy, który otrzyma count, min, max i avg
  • druga (prepared statement) z "LIMIT @count/ 2, 1" i " ORDER BY .."klauzule do uzyskania wartości mediany

Są one zawinięte w funkcję defn, więc wszystkie wartości mogą być zwracane z jednego wywołania.

Jeśli zakresy są statyczne, a dane nie zmieniają się często, bardziej efektywne może być wcześniejsze obliczanie/przechowywanie tych wartości i używanie zapisanych wartości zamiast odpytywania z podrapać za każdym razem.

 1
Author: btk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-07-18 00:15:39

Ponieważ potrzebowałem rozwiązania mediany i percentyla, stworzyłem prostą i dość elastyczną funkcję opartą na ustaleniach w tym wątku. Wiem, że sam jestem szczęśliwy, jeśli znajdę "gotowe" funkcje, które są łatwe do włączenia w moich projektach, więc postanowiłem szybko udostępnić: {]}

function mysql_percentile($table, $column, $where, $percentile = 0.5) {

    $sql = "
            SELECT `t1`.`".$column."` as `percentile` FROM (
            SELECT @rownum:=@rownum+1 as `row_number`, `d`.`".$column."`
              FROM `".$table."` `d`,  (SELECT @rownum:=0) `r`
              ".$where."
              ORDER BY `d`.`".$column."`
            ) as `t1`, 
            (
              SELECT count(*) as `total_rows`
              FROM `".$table."` `d`
              ".$where."
            ) as `t2`
            WHERE 1
            AND `t1`.`row_number`=floor(`total_rows` * ".$percentile.")+1;
        ";

    $result = sql($sql, 1);

    if (!empty($result)) {
        return $result['percentile'];       
    } else {
        return 0;
    }

}

Użycie jest bardzo proste, przykład z mojego obecnego projektu:

...
$table = DBPRE."zip_".$slug;
$column = 'seconds';
$where = "WHERE `reached` = '1' AND `time` >= '".$start_time."'";

    $reaching['median'] = mysql_percentile($table, $column, $where, 0.5);
    $reaching['percentile25'] = mysql_percentile($table, $column, $where, 0.25);
    $reaching['percentile75'] = mysql_percentile($table, $column, $where, 0.75);
...
 1
Author: bezoo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-21 09:19:53

Oto mój sposób . Oczywiście, można umieścić go w procedurze : -)

SET @median_counter = (SELECT FLOOR(COUNT(*)/2) - 1 AS `median_counter` FROM `data`);

SET @median = CONCAT('SELECT `val` FROM `data` ORDER BY `val` LIMIT ', @median_counter, ', 1');

PREPARE median FROM @median;

EXECUTE median;

Możesz uniknąć zmiennej @median_counter, jeśli ją podstawisz:

SET @median = CONCAT( 'SELECT `val` FROM `data` ORDER BY `val` LIMIT ',
                      (SELECT FLOOR(COUNT(*)/2) - 1 AS `median_counter` FROM `data`),
                      ', 1'
                    );

PREPARE median FROM @median;

EXECUTE median;
 1
Author: pucawo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-25 17:06:09

Moje rozwiązanie przedstawione poniżej działa tylko w jednym zapytaniu bez tworzenia tabeli, zmiennej lub nawet sub-zapytania. Dodatkowo, pozwala uzyskać medianę dla każdej grupy w group-by queries (to jest to, czego potrzebowałem !):

SELECT `columnA`, 
SUBSTRING_INDEX(SUBSTRING_INDEX(GROUP_CONCAT(`columnB` ORDER BY `columnB`), ',', CEILING((COUNT(`columnB`)/2))), ',', -1) medianOfColumnB
FROM `tableC`
-- some where clause if you want
GROUP BY `columnA`;

Działa dzięki inteligentnemu użyciu group_concat i substring_index.

Ale, aby zezwolić na duże group_concat, musisz ustawić group_concat_max_len na wyższą wartość(domyślnie znak 1024). Możesz ustawić tak (dla bieżącej sesji sql):

SET SESSION group_concat_max_len = 10000; 
-- up to 4294967295 in 32-bits platform.

Więcej informacje dla group_concat_max_len: https://dev.mysql.com/doc/refman/5.1/en/server-system-variables.html#sysvar_group_concat_max_len

 1
Author: didier2l,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-18 08:45:52

Kolejny riff w odpowiedzi Velcrowa, ale używa pojedynczej tabeli pośredniej i wykorzystuje zmienną używaną do numerowania wierszy, aby uzyskać liczbę, zamiast wykonywać dodatkowe zapytanie, aby ją obliczyć. Również rozpoczyna liczbę tak, że pierwszy wiersz jest wiersz 0, aby umożliwić po prostu za pomocą podłogi i sufitu, aby wybrać środkowy wiersz(s).

SELECT Avg(tmp.val) as median_val
    FROM (SELECT inTab.val, @rows := @rows + 1 as rowNum
              FROM data as inTab,  (SELECT @rows := -1) as init
              -- Replace with better where clause or delete
              WHERE 2 > 1
              ORDER BY inTab.val) as tmp
    WHERE tmp.rowNum in (Floor(@rows / 2), Ceil(@rows / 2));
 1
Author: Steve Cohen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-12 18:30:11

Zainstaluj i skorzystaj z funkcji statystycznych mysql: http://www.xarg.org/2012/07/statistical-functions-in-mysql/

Następnie Oblicz medianę jest łatwe:

Wybierz medianę (x) z t1

 1
Author: Leonardo Nicolas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-12 14:18:03

Ten sposób wydaje się zawierać zarówno parzyste, jak i nieparzyste liczby bez podquery.

SELECT AVG(t1.x)
FROM table t1, table t2
GROUP BY t1.x
HAVING SUM(SIGN(t1.x - t2.x)) = 0
 1
Author: yuhanluo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-01 04:18:47

Często może być konieczne obliczenie mediany nie tylko dla całej tabeli, ale dla agregatów w odniesieniu do naszego ID. Innymi słowy, Oblicz medianę dla każdego ID w naszej tabeli, gdzie każdy ID ma wiele rekordów. (dobra wydajność i działa w wielu SQL + naprawia problem parzystości i kursów, więcej o wydajności różnych metod mediany https://sqlperformance.com/2012/08/t-sql-queries/median )

SELECT our_id, AVG(1.0 * our_val) as Median
FROM
( SELECT our_id, our_val, 
  COUNT(*) OVER (PARTITION BY our_id) AS cnt,
  ROW_NUMBER() OVER (PARTITION BY our_id ORDER BY our_val) AS rn
  FROM our_table
) AS x
WHERE rn IN ((cnt + 1)/2, (cnt + 2)/2) GROUP BY our_id;

Hope it helps

 1
Author: Danylo Zherebetskyy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-21 22:06:41

Jeśli MySQL ma numer ROW_NUMBER, to mediana wynosi (zainspiruj się tym zapytaniem SQL Server):

WITH Numbered AS 
(
SELECT *, COUNT(*) OVER () AS Cnt,
    ROW_NUMBER() OVER (ORDER BY val) AS RowNum
FROM yourtable
)
SELECT id, val
FROM Numbered
WHERE RowNum IN ((Cnt+1)/2, (Cnt+2)/2)
;

In jest używany w przypadku, gdy masz parzystą liczbę wpisów.

Jeśli chcesz znaleźć medianę dla grupy, po prostu podziel na grupy w klauzulach nad.

Rob

 0
Author: Rob Farley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-12 19:27:05

Po przeczytaniu wszystkich poprzednich nie pasowały do moich rzeczywistych wymagań, więc zaimplementowałem swój własny, który nie wymaga żadnej procedury ani skomplikowania instrukcji, po prostu ja GROUP_CONCAT wszystkie wartości z kolumny chciałem uzyskać medianę i stosując COUNT DIV przez 2 wyodrębniam wartość ze środka listy tak jak następujące zapytanie:

(POS to nazwa kolumny, którą chcę uzyskać medianę)

(query) SELECT
SUBSTRING_INDEX ( 
   SUBSTRING_INDEX ( 
       GROUP_CONCAT(pos ORDER BY CAST(pos AS SIGNED INTEGER) desc SEPARATOR ';') 
    , ';', COUNT(*)/2 ) 
, ';', -1 ) AS `pos_med`
FROM table_name
GROUP BY any_criterial

Mam nadzieję, że to może być przydatne dla kogoś w sposób wielu inne komentarze były dla mnie z tej strony.

 0
Author: ggarri,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-28 08:38:29

Znając dokładną liczbę wierszy możesz użyć tego zapytania:

SELECT <value> AS VAL FROM <table> ORDER BY VAL LIMIT 1 OFFSET <half>

Gdzie <half> = ceiling(<size> / 2.0) - 1

 0
Author: ZhekaKozlov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-02 09:45:09

Mam bazę danych zawierającą około 1 miliarda wierszy, które potrzebujemy do określenia mediany wieku w zbiorze. Sortowanie miliardów wierszy jest trudne, ale jeśli połączysz różne wartości, które można znaleźć( w przedziale wiekowym od 0 do 100), możesz posortować tę listę i użyć magii arytmetycznej, aby znaleźć dowolny percentyl w następujący sposób:

with rawData(count_value) as
(
    select p.YEAR_OF_BIRTH
        from dbo.PERSON p
),
overallStats (avg_value, stdev_value, min_value, max_value, total) as
(
  select avg(1.0 * count_value) as avg_value,
    stdev(count_value) as stdev_value,
    min(count_value) as min_value,
    max(count_value) as max_value,
    count(*) as total
  from rawData
),
aggData (count_value, total, accumulated) as
(
  select count_value, 
    count(*) as total, 
        SUM(count(*)) OVER (ORDER BY count_value ROWS UNBOUNDED PRECEDING) as accumulated
  FROM rawData
  group by count_value
)
select o.total as count_value,
  o.min_value,
    o.max_value,
    o.avg_value,
    o.stdev_value,
    MIN(case when d.accumulated >= .50 * o.total then count_value else o.max_value end) as median_value,
    MIN(case when d.accumulated >= .10 * o.total then count_value else o.max_value end) as p10_value,
    MIN(case when d.accumulated >= .25 * o.total then count_value else o.max_value end) as p25_value,
    MIN(case when d.accumulated >= .75 * o.total then count_value else o.max_value end) as p75_value,
    MIN(case when d.accumulated >= .90 * o.total then count_value else o.max_value end) as p90_value
from aggData d
cross apply overallStats o
GROUP BY o.total, o.min_value, o.max_value, o.avg_value, o.stdev_value
;

To zapytanie zależy od twoich funkcji db obsługujących okno (w tym wierszy niepodzielonych poprzedzających), ale jeśli nie masz, to jest to prosta sprawa do połącz aggData CTE ze sobą i połącz wszystkie wcześniejsze sumy w kolumnę 'accumulated', która jest używana do określenia, która wartość zawiera określony precentyl. Powyższa próbka oblicza p10, p25 ,P50 (mediana), p75 i p90.

-Chris

 0
Author: Chris Knoll,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-17 04:53:16

Wzięte z: http://mdb-blog.blogspot.com/2015/06/mysql-find-median-nth-element-without.html

Proponuję inny sposób, bez join , ale praca z strings

Nie sprawdzałem tabel z dużymi danymi, ale małe / średnie tabele to działa dobrze.

Dobrze, że działa również grupując , aby móc zwrócić medianę dla kilku pozycji.

Oto kod do testu Tabela:

DROP TABLE test.test_median
CREATE TABLE test.test_median AS
SELECT 'book' AS grp, 4 AS val UNION ALL
SELECT 'book', 7 UNION ALL
SELECT 'book', 2 UNION ALL
SELECT 'book', 2 UNION ALL
SELECT 'book', 9 UNION ALL
SELECT 'book', 8 UNION ALL
SELECT 'book', 3 UNION ALL

SELECT 'note', 11 UNION ALL

SELECT 'bike', 22 UNION ALL
SELECT 'bike', 26 

Oraz kod do ustalenia mediany dla każdej grupy:

SELECT grp,
         SUBSTRING_INDEX( SUBSTRING_INDEX( GROUP_CONCAT(val ORDER BY val), ',', COUNT(*)/2 ), ',', -1) as the_median,
         GROUP_CONCAT(val ORDER BY val) as all_vals_for_debug
FROM test.test_median
GROUP BY grp

Wyjście:

grp | the_median| all_vals_for_debug
bike| 22        | 22,26
book| 4         | 2,2,3,4,7,8,9
note| 11        | 11
 0
Author: mr.baby123,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-19 12:03:47

W niektórych przypadkach mediana jest obliczana w następujący sposób:

"mediana" jest "środkową" wartością na liście liczb, gdy są one uporządkowane według wartości. Dla parzystych zestawów liczb, mediana jest średnią z dwóch średnich wartości . Stworzyłem do tego prosty kod:

$midValue = 0;
$rowCount = "SELECT count(*) as count {$from} {$where}";

$even = FALSE;
$offset = 1;
$medianRow = floor($rowCount / 2);
if ($rowCount % 2 == 0 && !empty($medianRow)) {
  $even = TRUE;
  $offset++;
  $medianRow--;
}

$medianValue = "SELECT column as median 
               {$fromClause} {$whereClause} 
               ORDER BY median 
               LIMIT {$medianRow},{$offset}";

$medianValDAO = db_query($medianValue);
while ($medianValDAO->fetch()) {
  if ($even) {
    $midValue = $midValue + $medianValDAO->median;
  }
  else {
    $median = $medianValDAO->median;
  }
}
if ($even) {
  $median = $midValue / 2;
}
return $median;

$mediana zwracana będzie wymaganym wynikiem: -)

 0
Author: jitendrapurohit,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-31 05:42:52

Mediany pogrupowane według wymiaru:

SELECT your_dimension, avg(t1.val) as median_val FROM (
SELECT @rownum:=@rownum+1 AS `row_number`,
   IF(@dim <> d.your_dimension, @rownum := 0, NULL),
   @dim := d.your_dimension AS your_dimension,
   d.val
   FROM data d,  (SELECT @rownum:=0) r, (SELECT @dim := 'something_unreal') d
  WHERE 1
  -- put some where clause here
  ORDER BY d.your_dimension, d.val
) as t1
INNER JOIN  
(
  SELECT d.your_dimension,
    count(*) as total_rows
  FROM data d
  WHERE 1
  -- put same where clause here
  GROUP BY d.your_dimension
) as t2 USING(your_dimension)
WHERE 1
AND t1.row_number in ( floor((total_rows+1)/2), floor((total_rows+2)/2) )

GROUP BY your_dimension;
 0
Author: Vladimir_M,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-04 16:18:36

Bazując na odpowiedzi @ Boba, uogólnia to zapytanie, aby miało możliwość zwracania wielu medianów, pogrupowanych według niektórych kryteriów.

Pomyśl, np., mediana ceny sprzedaży używanych samochodów w partii samochodów, pogrupowane według roku-miesiąca.

SELECT 
    period, 
    AVG(middle_values) AS 'median' 
FROM (
    SELECT t1.sale_price AS 'middle_values', t1.row_num, t1.period, t2.count
    FROM (
        SELECT 
            @last_period:=@period AS 'last_period',
            @period:=DATE_FORMAT(sale_date, '%Y-%m') AS 'period',
            IF (@period<>@last_period, @row:=1, @row:=@row+1) as `row_num`, 
            x.sale_price
          FROM listings AS x, (SELECT @row:=0) AS r
          WHERE 1
            -- where criteria goes here
          ORDER BY DATE_FORMAT(sale_date, '%Y%m'), x.sale_price
        ) AS t1
    LEFT JOIN (  
          SELECT COUNT(*) as 'count', DATE_FORMAT(sale_date, '%Y-%m') AS 'period'
          FROM listings x
          WHERE 1
            -- same where criteria goes here
          GROUP BY DATE_FORMAT(sale_date, '%Y%m')
        ) AS t2
        ON t1.period = t2.period
    ) AS t3
WHERE 
    row_num >= (count/2) 
    AND row_num <= ((count/2) + 1)
GROUP BY t3.period
ORDER BY t3.period;
 0
Author: Ariel Allon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-23 01:31:45