it-swarm.com.de

Einfache Methode zur Berechnung des Medians mit MySQL

Was ist der einfachste (und hoffentlich nicht zu langsame) Weg, um den Median mit MySQL zu berechnen? Ich habe AVG(x) verwendet, um den Mittelwert zu finden, aber es fällt mir schwer, einen einfachen Weg zur Berechnung des Medians zu finden. Im Moment stelle ich alle Zeilen an PHP zurück, wähle eine Sortierung aus und wähle dann die mittlere Zeile aus, aber es muss doch eine einfache Methode in einer einzigen MySQL-Abfrage geben.

Beispieldaten:

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

Sortierung nach val ergibt 2 2 3 4 7 8 9, daher sollte der Median 4 sein, im Gegensatz zu SELECT AVG(val) was == 5.

177
davr

In MariaDB/MySQL:

SELECT AVG(dd.val) as median_val
FROM (
SELECT d.val, @rownum:[email protected]+1 as `row_number`, @total_rows:[email protected]
  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 weist darauf hin, dass @rownum nach dem ersten Durchlauf die Gesamtzahl der Zeilen enthält. Dies kann zur Bestimmung des Medianwerts verwendet werden, sodass kein zweiter Durchlauf oder Join erforderlich ist.

Auch AVG(dd.val) und dd.row_number IN(...) wird verwendet, um bei einer geraden Anzahl von Datensätzen einen Median korrekt zu erzeugen. Argumentation:

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

MariaDB 10.3.3+ enthält schließlich eine MEDIAN-Funktion

201
velcrow

Ich habe gerade eine andere Antwort online in den Kommentaren gefunden :

Für Medianer in fast jeder 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

Stellen Sie sicher, dass Ihre Spalten gut indiziert sind und der Index zum Filtern und Sortieren verwendet wird. Überprüfen Sie mit den Erläuterungsplänen.

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

Berechnen Sie die "Median" -Zeilennummer. Vielleicht verwenden Sie: median_row = floor(count / 2).

Dann wählen Sie es aus der Liste aus:

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

Dies sollte eine Zeile mit dem gewünschten Wert zurückgeben.

Jacob

54
TheJacobTaylor

Ich habe festgestellt, dass die akzeptierte Lösung bei meiner MySQL-Installation nicht funktioniert und ein leeres Set zurückgegeben hat. Diese Abfrage funktionierte jedoch in allen Situationen, in denen ich sie getestet habe:

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
28
zookatron

Leider liefern weder die Antworten von TheJacobTaylor noch die Klettverschlüsse genaue Ergebnisse für die aktuellen Versionen von MySQL.

Die Antwort von Velcro von oben ist nahe, berechnet jedoch nicht korrekt für Ergebnissätze mit einer geraden Anzahl von Zeilen. Medianwerte werden definiert als entweder 1) die mittlere Zahl in ungeradzahligen Mengen oder 2) der Durchschnitt der beiden mittleren Zahlen in geraden Zahlenmengen.

Hier ist die Lösung von Velcro-Patches, die sowohl für ungerade als auch für gerade Zahlensätze geeignet ist:

SELECT AVG(middle_values) AS 'median' FROM (
  SELECT t1.median_column AS 'middle_values' FROM
    (
      SELECT @row:[email protected]+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;

Um dies zu verwenden, folgen Sie diesen 3 einfachen Schritten:

  1. Ersetzen Sie "median_table" (2 Vorkommen) im obigen Code durch den Namen Ihrer Tabelle
  2. Ersetzen Sie "median_column" (3 Vorkommen) durch den Spaltennamen, für den Sie einen Median suchen möchten
  3. Wenn Sie eine WHERE-Bedingung haben, ersetzen Sie "WHERE 1" (2 Vorkommen) durch Ihre Where-Bedingung
16
bob

Ich schlage einen schnelleren Weg vor.

Holen Sie sich die Zeilenanzahl:

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

Dann nimm den mittleren Wert in einer sortierten Unterabfrage:

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

Ich habe dies mit einem 5x10e6-Datensatz von Zufallszahlen getestet und es wird den Median in weniger als 10 Sekunden finden.

9
Reggie Edwards

Ein Kommentar zu dieser Seite in der MySQL-Dokumentation hat folgenden Vorschlag:

-- (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 

Aufbauend auf der Antwort des Klettverschlusses, für diejenigen von Ihnen, die einen Medianwert aus etwas erstellen müssen, das nach einem anderen Parameter gruppiert ist:

 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 Daten d, (SELECT @rownum: = 0, @s: = 0) r ORDER BY grp_field, d.val ) as t1 JOIN ( SELECT grp_field, count (*) als total_rows FROM Daten d GROUP BY grp_field ) als t2 ON t1.grp_field = t2.grp_field WHERE t1. Reihennummer = Etage (Gesamtanzahl/2) +1; 

4
Doug

Ich habe diesen Code, den ich auf HackerRank gefunden habe, und er ist ziemlich einfach und funktioniert in jedem Fall.

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

Die meisten der oben genannten Lösungen funktionieren nur für ein Feld der Tabelle. Möglicherweise müssen Sie für viele Felder in der Abfrage den Medianwert (50. Perzentil) ermitteln. 

Ich benutze das: 

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;

Sie können die "50" im obigen Beispiel durch ein beliebiges Perzentil ersetzen, das ist sehr effizient.

Stellen Sie nur sicher, dass Sie über genügend Speicher für GROUP_CONCAT verfügen. Sie können ihn mit folgenden Einstellungen ändern:

SET group_concat_max_len = 10485760; #10MB max length

Weitere Informationen: http://web.performancerasta.com/metrics-tips-calculating-95th-99th-or-any-percentile-with-single-mysql-query/

4
Nico

Kümmert sich um eine ungerade Anzahl von Werten - gibt in diesem Fall den Durchschnitt der beiden Werte in der Mitte an.

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
Franz K.

Sie können die benutzerdefinierte Funktion hier verwenden.

3
Alex Martelli
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

Das Obige scheint für mich zu funktionieren.

2
Nochum Sossonko

Optional können Sie dies auch in einer gespeicherten Prozedur tun:

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:[email protected]+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
bob

Installieren und verwenden Sie diese statistischen Funktionen für MySQL: http://www.xarg.org/2012/07/statistical-functions-in-mysql/

Danach ist der Medianwert einfach zu berechnen:

SELECT Median (x) FROM t1

2

Mein Code, effizient ohne Tabellen oder zusätzliche Variablen:

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
Oscar Canon

Ein weiteres Riff auf Velcrows Antwort, verwendet jedoch eine einzige Zwischentabelle und nutzt die für die Zeilennummerierung verwendete Variable, um die Anzahl zu ermitteln, anstatt eine zusätzliche Abfrage durchzuführen, um diese zu berechnen. Startet die Zählung auch so, dass die erste Zeile Zeile 0 ist, sodass Sie einfach Floor und Ceil verwenden können, um die Median-Zeile (n) auszuwählen.

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
Steve Cohen

Dieser Weg scheint sowohl die gerade als auch die ungerade Zählung ohne Unterabfrage zu umfassen.

SELECT AVG(t1.x)
FROM table t1, table t2
GROUP BY t1.x
HAVING SUM(SIGN(t1.x - t2.x)) = 0
1
yuhanluo

Meine unten dargestellte Lösung funktioniert in nur einer Abfrage, ohne dass eine Tabelle, eine Variable oder sogar eine Unterabfrage erstellt werden muss. Außerdem können Sie für jede Gruppe einen Medianwert in Gruppenanfragen ermitteln (dies ist das, was ich brauchte!):

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`;

Es funktioniert aufgrund einer intelligenten Verwendung von group_concat und substring_index.

Um big group_concat zuzulassen, müssen Sie jedoch group_concat_max_len auf einen höheren Wert setzen (standardmäßig 1024 Zeichen) .. __ Sie können es so einstellen (für die aktuelle SQL-Sitzung): 

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

Weitere Informationen zu group_concat_max_len: https://dev.mysql.com/doc/refman/5.1/de/server-system-variables.html#sysvar_group_concat_max_len

1
didier2l

da ich nur eine mittlere und prozentuale Lösung brauchte, erstellte ich eine einfache und recht flexible Funktion basierend auf den Ergebnissen in diesem Thread. Ich weiß, dass ich selbst glücklich bin, wenn ich "fertige" Funktionen finde, die leicht in meine Projekte aufgenommen werden können. Daher habe ich mich entschlossen, schnell Folgendes zu teilen:

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

    $sql = "
            SELECT `t1`.`".$column."` as `percentile` FROM (
            SELECT @rownum:[email protected]+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;
    }

}

Die Verwendung ist sehr einfach, Beispiel aus meinem aktuellen Projekt:

...
$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
bezoo

Ich habe einen Ansatz mit zwei Abfragen verwendet:

  • erstes, um Count, Min, Max und Avg zu erhalten
  • zweite (vorbereitete Anweisung) mit den Klauseln "LIMIT @ count/2, 1" und "ORDER BY ..", um den Mittelwert zu erhalten

Diese werden in eine Funktion defn eingeschlossen, sodass alle Werte von einem Aufruf zurückgegeben werden können.

Wenn Ihre Bereiche statisch sind und sich Ihre Daten nicht häufig ändern, ist es möglicherweise effizienter, diese Werte vorzugeben/zu speichern und die gespeicherten Werte zu verwenden, anstatt jedes Mal von Grund auf neu zu fragen.

1
btk

Oft müssen wir den Median nicht nur für die gesamte Tabelle berechnen, sondern für Aggregate in Bezug auf unsere ID. Mit anderen Worten, berechnen Sie den Median für jede ID in unserer Tabelle, wobei jede ID viele Datensätze enthält. (Gute Performance und Arbeit in vielen SQL + behebt Problem von Even und Odds, mehr zur Performance verschiedener Median-Methoden 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;

Ich hoffe es hilft

1

Hier ist mein Weg. Natürlich könnte man es in eine Prozedur einarbeiten :-)

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;

Sie könnten die Variable @median_counter vermeiden, wenn Sie sie ersetzen:

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
pucawo

Wenn MySQL ROW_NUMBER hat, dann ist der MEDIAN (lassen Sie sich von dieser SQL Server-Abfrage inspirieren):

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)
;

Das IN wird verwendet, wenn Sie eine gerade Anzahl von Einträgen haben.

Wenn Sie den Median pro Gruppe ermitteln möchten, dann nur die Gruppe PARTITION BY in Ihren OVER-Klauseln.

Rauben

0
Rob Farley

Ich habe eine Datenbank mit etwa 1 Milliarde Zeilen, die wir benötigen, um das Durchschnittsalter in der Gruppe zu bestimmen. Das Sortieren einer Milliarde Zeilen ist schwierig, aber wenn Sie die verschiedenen Werte zusammenfassen, die gefunden werden können (Alter zwischen 0 und 100), können Sie DIESE Liste sortieren und mit arithmetischer Magie das gewünschte Perzentil wie folgt ermitteln:

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
;

Diese Abfrage hängt von Ihrer db-unterstützenden Fensterfunktion ab (einschließlich ROWS UNBOUNDED PRECEDING). Wenn Sie jedoch nicht wissen, dass es einfach ist, aggData CTE mit sich selbst zu verknüpfen und alle vorherigen Summen in der Spalte "angesammelt" zu aggregieren, um zu ermitteln, welche value enthält das angegebene Precentile. Die obige Probe berechnet p10, p25, p50 (Median), p75 und p90.

-Chris

0
Chris Knoll

Nachdem ich alle vorherigen gelesen hatte, stimmten sie nicht mit meiner tatsächlichen Anforderung überein. Daher implementierte ich meine eigene, die keine Prozedur oder komplizierten Anweisungen benötigte. Ich GROUP_CONCAT alle Werte aus der Spalte, in der ich den MEDIAN abrufen wollte, und einen COUNT DIV BY 2 Ich extrahiere den Wert aus der Mitte der Liste wie die folgende Abfrage: 

(POS ist der Name der Spalte, deren Median ich erhalten möchte)

(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

Ich hoffe, dass dies für jemanden nützlich sein könnte, so wie viele andere Kommentare von mir auf dieser Website. 

0
Gabriel G.
create table med(id integer);
insert into med(id) values(1);
insert into med(id) values(2);
insert into med(id) values(3);
insert into med(id) values(4);
insert into med(id) values(5);
insert into med(id) values(6);

select (MIN(count)+MAX(count))/2 from 
(select case when (select count(*) from 
med A where A.id<B.id)=(select count(*)/2 from med) OR 
(select count(*) from med A where A.id>B.id)=(select count(*)/2 
from med) then cast(B.id as float)end as count from med B) C;

 ?column? 
----------
  3.5
(1 row)

OR

select cast(avg(id) as float) from 
(select t1.id from med t1 JOIN med t2 on t1.id!= t2.id 
group by t1.id having ABS(SUM(SIGN(t1.id-t2.id)))=1) A;
0
Dwipam Katariya

Der folgende SQL-Code hilft Ihnen bei der Berechnung des Medians in MySQL anhand benutzerdefinierter Variablen.

create table employees(salary int);

insert into employees values(8);
insert into employees values(23);
insert into employees values(45);
insert into employees values(123);
insert into employees values(93);
insert into employees values(2342);
insert into employees values(2238);

select * from employees;

Select salary from employees  order by salary;

set @rowid=0;
set @cnt=(select count(*) from employees);
set @middle_no=ceil(@cnt/2);
set @odd_even=null;

select AVG(salary) from 
(select salary,@rowid:[email protected]+1 as rid, (CASE WHEN(mod(@cnt,2)=0) THEN @odd_even:=1 ELSE @odd_even:=0 END) as odd_even_status  from employees  order by salary) as tbl where [email protected]_no or tbl.rid=(@[email protected]_even);

Wenn Sie nach detaillierten Erklärungen suchen, lesen Sie bitte diesen Blog.

0

Ich fand diese Antwort sehr hilfreich - https://www.eversql.com/how-to-calculate-median-value-in-mysql- using-asimple-sql-query/

SET @rowindex := -1;

SELECT
   AVG(g.grade)
FROM
   (SELECT @rowindex:[email protected] + 1 AS rowindex,
       grades.grade AS grade
    FROM grades
    ORDER BY grades.grade) AS g
WHERE
g.rowindex IN (FLOOR(@rowindex / 2) , CEIL(@rowindex / 2));
0
Kwex

Diese Methoden wählen zweimal aus derselben Tabelle. Wenn die Quelldaten von einer teuren Abfrage stammen, können Sie vermeiden, dass die Quelldaten zweimal ausgeführt werden:

select KEY_FIELD, AVG(VALUE_FIELD) MEDIAN_VALUE
from (
    select KEY_FIELD, VALUE_FIELD, RANKF
    , @rownumr := IF(@prevrowidr=KEY_FIELD,@rownumr+1,1) RANKR
    , @prevrowidr := KEY_FIELD
    FROM (
        SELECT KEY_FIELD, VALUE_FIELD, RANKF
        FROM (
            SELECT KEY_FIELD, VALUE_FIELD 
            , @rownumf := IF(@prevrowidf=KEY_FIELD,@rownumf+1,1) RANKF
            , @prevrowidf := KEY_FIELD     
            FROM (
                SELECT KEY_FIELD, VALUE_FIELD 
                FROM (
                    -- some expensive query
                )   B
                ORDER BY  KEY_FIELD, VALUE_FIELD
            ) C
            , (SELECT @rownumf := 1) t_rownum
            , (SELECT @prevrowidf := '*') t_previd
        ) D
        ORDER BY  KEY_FIELD, RANKF DESC
    ) E
    , (SELECT @rownumr := 1) t_rownum
    , (SELECT @prevrowidr := '*') t_previd
) F
WHERE RANKF-RANKR BETWEEN -1 and 1
GROUP BY KEY_FIELD
0
RobbertNix

In einigen Fällen wird der Median wie folgt berechnet:

Der "Median" ist der "mittlere" Wert in der Liste der Zahlen, wenn sie nach Wert geordnet sind. Bei geraden Count-Sets ist Median der Durchschnitt der zwei mittleren Werte . Ich habe einen einfachen Code dafür erstellt:

$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;

Der zurückgegebene Median $ wäre das geforderte Ergebnis :-)

0
jitendrapurohit

Aus: http://mdb-blog.blogspot.com/2015/06/mysql-find-median-nth-element-without.html

Ich würde einen anderen Weg vorschlagen, ohne join, Aber mit strings arbeiten

ich habe es nicht mit Tabellen mit großen Daten überprüft, aber kleine/mittlere Tabellen funktionieren gut.

Das Gute hier ist, dass es auch by GROUPING funktioniert, damit es den Median für mehrere Elemente zurückgeben kann.

hier ist der Testcode für die Testtabelle:

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 

und den Code zum Ermitteln des Medians für jede Gruppe:

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

Ausgabe:

grp | the_median| all_vals_for_debug
bike| 22        | 22,26
book| 4         | 2,2,3,4,7,8,9
note| 11        | 11
0
mr.baby123

Basierend auf der Antwort von @ bob wird die Abfrage so verallgemeinert, dass mehrere Medianwerte zurückgegeben werden können, die nach bestimmten Kriterien gruppiert sind.

Denken Sie beispielsweise an den mittleren Verkaufspreis für Gebrauchtwagen in einem PKW, gruppiert nach Jahr und Monat.

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:[email protected] AS 'last_period',
            @period:=DATE_FORMAT(sale_date, '%Y-%m') AS 'period',
            IF (@period<>@last_period, @row:=1, @row:[email protected]+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
Ariel Allon

Wenn Sie die genaue Zeilenanzahl kennen, können Sie diese Abfrage verwenden:

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

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

0
ZhekaKozlov

Medianer nach Dimension:

SELECT your_dimension, avg(t1.val) as median_val FROM (
SELECT @rownum:[email protected]+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
Vladimir_M