Méthode Simple pour calculer la médiane avec MySQL
Quelle est la façon la plus simple (et si tout va bien 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 à PHP, en faisant une sorte, puis en choisissant la ligne du milieu, mais sûrement il doit 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
tri sur val
donne 2 2 3 4 7 8 9
, donc la médiane devrait être 4
, contre SELECT AVG(val)
qui = = 5
.
30 réponses
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 souligne qu'après le premier passage, @rownum contiendra le nombre total de lignes. Cela peut être utilisé pour déterminer la médiane, de sorte qu'aucune deuxième passe ou jointure est nécessaire.
aussi AVG(dd.val)
et dd.row_number IN(...)
est utilisé pour produire correctement une médiane lorsqu'il y a 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
I just trouvé une autre réponse en ligne dans les commentaires :
pour les médians dans presque n'importe quel 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 tri. Vérifier auprès de l'expliquer les plans.
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)
.
puis le choisir sur le liste:
select val from table order by val asc limit median_row,1
cela devrait vous retourner une rangée avec juste la valeur que vous voulez.
Jacob
j'ai trouvé que la solution acceptée ne fonctionnait pas sur mon installation MySQL, retournant un jeu vide, mais cette requête a fonctionné pour moi dans toutes les situations où je l'ai testé sur:
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 Jacobtaylor ni celles de velcro ne donnent de 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 pour les ensembles de résultats avec un nombre pair de lignes. Les médianes sont définies comme 1) le nombre moyen sur les ensembles impairs numérotés, ou 2) la moyenne des deux nombres moyens sur les ensembles pairs numérotés.
donc, voici la solution de velcro rapiécée pour gérer les deux impairs et même les séries de nombres:
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 utiliser ceci, suivez ces 3 étapes faciles:
- remplacer" median_table "(2 occurrences) dans le code ci-dessus avec le nom de votre table
- remplacer "median_column" (3 occurrences) par le nom de la colonne que vous souhaitez trouver une médiane pour
- si vous avez un état où, remplacer "où 1" (2 occurrences) par votre état où
je propose un moyen plus rapide.
Obtenir le nombre de ligne:
SELECT CEIL(COUNT(*)/2) FROM data;
puis prendre la valeur du milieu dans un sous-questionnaire trié:
SELECT max(val) FROM (SELECT val FROM data ORDER BY val limit @middlevalue) x;
j'ai testé cela avec un ensemble de données 5x10e6 de nombres aléatoires et il trouvera la médiane en moins de 10 Secondes.
Un commentaire sur sur cette page dans la documentation de 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 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
construire à partir de la réponse de velcro, pour ceux d'entre vous qui doivent faire une médiane à partir de quelque chose qui est 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
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;
la plupart des solutions ci-dessus ne fonctionnent que pour un seul champ de la table, vous pourriez avoir besoin de la médiane (50e percentile) 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 à n'importe quel centile, est très efficace.
assurez-vous juste d'avoir assez de mémoire pour le GROUP_CONCAT, vous pouvez le changer avec:
SET group_concat_max_len = 10485760; #10MB max length
plus de détails: http://web.performancerasta.com/metrics-tips-calculating-95th-99th-or-any-percentile-with-single-mysql-query /
j'ai ce code ci-dessous que j'ai trouvé sur HackerRank et il est assez simple et fonctionne dans chaque 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 );
vous pouvez utiliser la fonction définie par l'utilisateur qui se trouve ici .
s'occupe d'un compte de valeurs impaires - 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
mon code, efficace sans tables ou 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;
vous pouvez aussi 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
ci-dessus semble fonctionner pour moi.
j'ai utilisé une approche de deux requêtes:
- un des premiers à obtenir count, min, max et avg
- deuxième déclaration préparée à l'avance) avec une "LIMITE @count/2, 1" et "ORDER BY .."clauses pour obtenir la valeur médiane
ceux-ci sont enveloppés dans une fonction defn, de sorte que toutes les valeurs peuvent être retournées à partir d'un appel.
Si vos gammes sont statiques et vos données ne changent pas souvent, il peut être plus efficace pour précalculer/stocker ces valeurs et utiliser les valeurs stockées au lieu de demander à partir de zéro à chaque fois.
comme j'avais juste besoin d'une solution médiane et percentile, j'ai fait une fonction simple et assez flexible basée sur les résultats dans ce fil. Je sais que je suis heureux moi-même si je trouve des fonctions "readymade" qui sont faciles à inclure dans mes projets, donc 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 facile, 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 ma façon . Bien sûr, vous pouvez 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 pourriez éviter la variable @median_counter
, si vous lui soumettez:
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 en une seule requête sans création de table, variable ou même sous-requête. De plus, il vous permet d'obtenir la médiane pour chaque groupe dans les requêtes group-by (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 substring_index.
mais, pour autoriser big group_concat, vous devez définir group_concat_max_len à une valeur plus élevée (1024 char par défaut). Vous pouvez le mettre comme cela (pour session sql actuelle):
SET SESSION group_concat_max_len = 10000;
-- up to 4294967295 in 32-bits platform.
pour en savoir plus sur group_concat_max_len: https://dev.mysql.com/doc/refman/5.1/en/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 profite de la variable utilisée pour la numérotation des lignes pour obtenir le nombre, plutôt que d'effectuer une requête supplémentaire pour le calculer. Commence également le compte de sorte que la première rangée est la rangée 0 pour permettre simplement en utilisant le plancher et Ceil pour sélectionner la(les) rangée (s) médiane (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));
installer et utiliser cette mysql fonctions statistiques: http://www.xarg.org/2012/07/statistical-functions-in-mysql/
ensuite, calculer la médiane est facile:
sélectionner la médiane (x ) de t1
de cette façon semble inclure Pair et Impair compte sans subquery.
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 l'ensemble du tableau, mais 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. (bonne performance et fonctionne dans beaucoup de SQL + fixe le problème de pair et de cotes, plus au sujet de la performance des différentes médianes-méthodes 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 ça aide
si MySQL a ROW_NUMBER, alors la médiane est (être inspiré par 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é dans le cas où vous avez un nombre pair d'entrées.
si vous voulez trouver la médiane par groupe, alors divisez simplement par groupe dans vos clauses OVER.
Rob
après avoir lu toutes les précédentes, ils ne correspondent pas à mes besoins réels donc j'ai mis en œuvre mon propre qui n'a pas besoin de procédure ou des déclarations compliquées, juste I GROUP_CONCAT
toutes les valeurs de la colonne je voulais obtenir la médiane et l'application D'un DIV COUNT par 2 j'extrait la valeur du milieu de la liste comme le fait la requête suivante:
(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
I j'espère que cela pourrait être utile pour quelqu'un dans la façon dont beaucoup d'autres commentaires étaient pour moi à partir de ce site web.
connaître le nombre exact de lignes vous pouvez utiliser cette requête:
SELECT <value> AS VAL FROM <table> ORDER BY VAL LIMIT 1 OFFSET <half>
où <half> = ceiling(<size> / 2.0) - 1
j'ai une base de données contenant environ 1 milliard de lignes dont nous avons besoin pour déterminer l'âge médian dans l'ensemble. Trier un milliard de lignes est difficile, mais si vous Agrégez les valeurs distinctes qui peuvent être trouvées( les âges vont de 0 à 100), Vous pouvez trier cette liste, et utiliser une magie arithmétique pour trouver tout centile que vous voulez 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 votre db supportant les fonctions de fenêtre (y compris les lignes non limitées qui précèdent) mais si vous n'avez pas qu'il est simple de joindre aggData CTE avec lui-même et agréger tous les totaux antérieurs dans la colonne "accumulé" qui est utilisée pour déterminer quelle valeur contient le précentile 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 travailler avec cordes
Je ne l'ai pas vérifié avec des tableaux avec de grandes données, mais les petites/moyennes tableaux, il fonctionne très bien.
la bonne chose ici, qu'il fonctionne aussi par groupement de sorte qu'il peut retourner la médiane pour plusieurs articles.
voici le code d'essai pour la table d'essai:
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" de la liste des nombres lorsqu'ils sont ordonnés en valeur. Pour les ensembles de comptage Pair, médiane est moyenne des deux valeurs moyennes . J'ai créé un code simple pour ça :
$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;
$médiane retourné serait le résultat requis :-)
médians groupés 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, ceci généralise la requête pour avoir la possibilité de retourner plusieurs médians, groupés selon certains critères.
penser, par exemple, le prix de vente médian pour les voitures d'occasion dans un lot de voitures, groupé par Année-Mois.
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;