Un moyen simple de calculer la médiane avec MySQL

Quelle est la manière la plus simple (et sans doute pas trop lente) de calculer la médiane avec MySQL? J’ai utilisé AVG(x) pour trouver la moyenne, mais j’ai du mal à trouver un moyen simple de calculer la médiane. Pour l’instant, je retourne toutes les lignes dans PHP, en faisant un sorting, puis en sélectionnant la ligne du milieu, mais il doit sûrement y avoir un moyen simple de le faire dans une seule requête MySQL.

Exemple de données:

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

Le sorting sur val donne 2 2 3 4 7 8 9 , donc la médiane devrait être 4 , contre SELECT AVG(val) qui == 5 .

Dans 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 souligne qu’après le premier passage, @rownum contiendra le nombre total de lignes. Cela peut être utilisé pour déterminer la médiane, donc aucune seconde passe ou jointure n’est nécessaire.

De même, AVG(dd.val) et dd.row_number IN(...) sont utilisés pour produire correctement une médiane lorsqu’il existe un nombre pair d’enregistrements. Raisonnement:

 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 

Enfin, MariaDB 10.3.3+ contient une fonction MEDIAN

Je viens de trouver une autre réponse en ligne dans les commentaires :

Pour les médianes dans presque tous les 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 

Assurez-vous que vos colonnes sont bien indexées et que l’index est utilisé pour le filtrage et le sorting. Vérifiez avec les plans explicatifs.

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

Calculez le numéro de ligne “médian”. Peut-être utiliser: median_row = floor(count / 2) .

Ensuite, retirez-le de la liste:

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

Cela devrait vous retourner une ligne avec la valeur que vous voulez.

Jacob

J’ai trouvé que la solution acceptée ne fonctionnait pas sur mon installation MySQL, renvoyant un ensemble vide, mais cette requête a fonctionné pour moi dans toutes les situations où je l’ai testée:

 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 

Malheureusement, ni les réponses de TheJacobTaylor ni celles de velcro ne donnent des résultats précis pour les versions actuelles de MySQL.

La réponse de Velcro ci-dessus est proche, mais elle ne calcule pas correctement les ensembles de résultats avec un nombre pair de lignes. Les médianes sont définies comme étant 1) le chiffre du milieu sur les séries impaires, ou 2) la moyenne des deux nombres moyens sur les jeux de nombres pairs.

Donc, voici la solution de velcro corrigée pour gérer à la fois les jeux de nombres pairs et impairs:

 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; 

Pour l'utiliser, suivez ces trois étapes simples:

  1. Remplacez "median_table" (2 occurrences) dans le code ci-dessus par le nom de votre table
  2. Remplacez "median_column" (3 occurrences) par le nom de la colonne que vous souhaitez trouver une médiane pour
  3. Si vous avez une condition WHERE, remplacez "WHERE 1" (2 occurrences) par votre condition where

Je propose un moyen plus rapide.

Obtenez le nombre de lignes:

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

Prenez ensuite la valeur intermédiaire dans une sous-requête sortingée:

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

Je l’ai testé avec un jeu de données 5x10e6 de nombres aléatoires et il trouvera la médiane en moins de 10 secondes.

Un commentaire sur cette page dans la documentation MySQL a la suggestion suivante:

 -- (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 oc AS shifted_n, IF(@a mod oc=0, oc, @a) AS n, o.id, o.val, -- the number of elements for every id oc 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 

En vous appuyant sur la réponse de Velcro, pour ceux d’entre vous qui ont à faire une médiane de quelque chose regroupé par un autre paramètre:

 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
   ORDRE PAR grp_field, d.val
 ) comme t1 JOIN (
   SELECT grp_field, compte (*) comme total_rows
   FROM données d
   GROUP BY grp_field
 ) comme t2
 ON t1.grp_field = t2.grp_field
 WHERE t1.row_number = floor (total_rows / 2) +1;

La plupart des solutions ci-dessus ne fonctionnent que pour un seul champ de la table, vous devrez peut-être obtenir la médiane (50e centile) pour de nombreux champs de la requête.

J’utilise ceci:

 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; 

Vous pouvez remplacer le “50” dans l’exemple ci-dessus par n’importe quel centile, c’est très efficace.

Assurez-vous que vous avez suffisamment de mémoire pour GROUP_CONCAT, vous pouvez le changer avec:

 SET group_concat_max_len = 10485760; #10MB max length 

Plus de détails: http://web.performancerasta.com/mesortingcs-tips-calculating-95th-99th-or-any-percentile-with-single-mysql-query/

Vous pouvez utiliser la fonction définie par l’utilisateur qui se trouve ici .

Prend soin d’un compte de valeur impair – donne la moyenne des deux valeurs au milieu dans ce cas.

 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 

J’ai ce code ci-dessous que j’ai trouvé sur HackerRank et c’est assez simple et fonctionne dans tous les cas.

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

Mon code, efficace sans tables ni variables supplémentaires:

 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; 

Si vous le souhaitez, vous pouvez également le faire dans une procédure stockée:

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

Ce qui précède semble fonctionner pour moi.

J’ai utilisé une approche à deux requêtes:

  • le premier pour obtenir le compte, min, max et avg
  • deuxième (déclaration préparée) avec les clauses “LIMIT @ count / 2, 1” et “ORDER BY ..” pour obtenir la valeur médiane

Celles-ci sont enveloppées dans une fonction defn, de sorte que toutes les valeurs peuvent être renvoyées d’un appel.

Si vos plages sont statiques et que vos données ne changent pas souvent, il serait peut-être plus efficace de précalculer / stocker ces valeurs et d’utiliser les valeurs stockées au lieu d’interroger à partir de rien à chaque fois.

Comme j’avais juste besoin d’une solution médiane ET percentile, j’ai créé une fonction simple et assez flexible basée sur les résultats de ce fil. Je sais que je suis heureux moi-même si je trouve des fonctions “readymade” faciles à inclure dans mes projets, alors j’ai décidé de partager rapidement:

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

L’utilisation est très simple, exemple de mon projet actuel:

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

Voici mon chemin Bien sûr, vous pourriez le mettre dans une procédure 🙂

 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; 

Vous pouvez éviter la variable @median_counter , si vous la substituez:

 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; 

Ma solution présentée ci-dessous fonctionne dans une seule requête sans création de table, variable ou même sous-requête. De plus, cela vous permet d’obtenir une médiane pour chaque groupe dans les requêtes groupées (c’est ce dont j’avais besoin!):

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

Cela fonctionne grâce à une utilisation intelligente de group_concat et de subssortingng_index.

Cependant, pour autoriser big group_concat, vous devez définir group_concat_max_len sur une valeur supérieure (1024 caractères par défaut). Vous pouvez le définir comme ça (pour la session SQL actuelle):

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

Plus d’infos pour group_concat_max_len: https://dev.mysql.com/doc/refman/5.1/fr/server-system-variables.html#sysvar_group_concat_max_len

Un autre riff sur la réponse de Velcrow, mais utilise une seule table intermédiaire et tire parti de la variable utilisée pour la numérotation des lignes pour obtenir le compte, au lieu d’effectuer une requête supplémentaire pour le calculer. Commence également le décompte de sorte que la première ligne soit la ligne 0 pour permettre simplement d’utiliser Floor et Ceil pour sélectionner la ou les lignes médianes.

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

Installez et utilisez ces fonctions statistiques mysql: http://www.xarg.org/2012/07/statistical-functions-in-mysql/

Après cela, calculer la médiane est facile:

SELECT médiane (x) DE t1

Cette façon de faire semble inclure à la fois le compte pair et le nombre impair sans sous-requête.

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

Souvent, nous pouvons avoir besoin de calculer la médiane non seulement pour la table entière, mais aussi pour les agrégats par rapport à notre ID. En d’autres termes, calculez la médiane pour chaque ID dans notre tableau, où chaque ID a de nombreux enregistrements. (de bonnes performances et fonctionne dans de nombreux SQL + corrige un problème de pair et de cotes, plus sur les performances des différentes méthodes Median 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; 

J’espère que cela aide

Si MySQL a ROW_NUMBER, alors le MEDIAN est inspiré de cette requête 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) ; 

Le IN est utilisé si vous avez un nombre pair d’entrées.

Si vous souhaitez trouver la médiane par groupe, alors juste PARTITION BY groupe dans vos clauses OVER.

Rob

Après avoir lu tous les précédents, ils ne correspondaient pas à mes besoins réels, alors j’ai implémenté mon propre programme, qui ne nécessite aucune procédure ou instruction compliquée, juste I GROUP_CONCAT toutes les valeurs de la colonne que je voulais obtenir et appliquer un COUNT DIV BY 2 J’extrais la valeur du milieu de la liste comme la requête suivante fait:

(POS est le nom de la colonne que je veux obtenir sa médiane)

 (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 

J’espère que cela pourrait être utile pour quelqu’un comme beaucoup d’autres commentaires étaient pour moi sur ce site.

Connaissant le nombre de lignes exact, vous pouvez utiliser cette requête:

 SELECT  AS VAL FROM  ORDER BY VAL LIMIT 1 OFFSET 

= ceiling( / 2.0) - 1

J’ai une firebase database contenant environ 1 milliard de lignes dont nous avons besoin pour déterminer l’âge médian dans l’ensemble. Le sorting d’un milliard de lignes est difficile, mais si vous agrégez les valeurs distinctes pouvant être trouvées (les âges vont de 0 à 100), vous pouvez sortinger cette liste et utiliser un peu de magie arithmétique pour trouver un percentile comme suit:

 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 ; 

Cette requête dépend de vos fonctions de fenêtre supportant la firebase database (y compris ROWS UNBOUNDED PRECEDING), mais si vous ne l’avez pas, il est facile de joindre aggData CTE et d’agréger tous les totaux précédents dans la colonne “accumulée”. valeur contient le précurseur spécifié. L’échantillon ci-dessus calcule p10, p25, p50 (médiane), p75 et p90.

-Chris

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

Je suggérerais une autre façon, sans rejoindre , mais en travaillant avec des chaînes

Je ne l’ai pas vérifié avec des tables avec de grandes données, mais les petites / moyennes tables ça marche très bien.

La bonne chose ici, c’est que cela fonctionne aussi avec GROUPING afin qu’il puisse retourner la médiane pour plusieurs articles.

Voici le code de test pour la table de test:

 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 

et le code pour trouver la médiane pour chaque groupe:

 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 

Sortie:

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

Dans certains cas, la médiane est calculée comme suit:

La “médiane” est la valeur “médiane” dans la liste des nombres lorsqu’ils sont classés par valeur. Pour les ensembles de comptes pairs, la médiane est la moyenne des deux valeurs moyennes . J’ai créé un code simple pour cela:

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

La médiane retournée serait le résultat requirejs 🙂

Médianes regroupées par dimension:

 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; 

Basé sur la réponse de @ bob, cela généralise la requête pour avoir la possibilité de retourner plusieurs médianes, regroupés par critères.

Pensez, par exemple, au prix de vente médian des voitures d’occasion dans un parc automobile, regroupé par mois de l’année.

 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;