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 .

169
demandé sur Török Gábor 2009-08-18 04:13:49

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

enfin, MariaDB 10.3.3 + contient une fonction médiane

194
répondu velcrow 2018-08-27 20:00:02

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

52
répondu TheJacobTaylor 2010-03-11 16:20:48

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
26
répondu zookatron 2012-06-04 06:59:33

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:

  1. remplacer" median_table "(2 occurrences) dans le code ci-dessus avec le nom de votre table
  2. remplacer "median_column" (3 occurrences) par le nom de la colonne que vous souhaitez trouver une médiane pour
  3. si vous avez un état où, remplacer "où 1" (2 occurrences) par votre état où
14
répondu bob 2018-05-16 01:42:10

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.

9
répondu Reggie Edwards 2011-05-31 00:05:20

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 
7
répondu Sebastian Paaske Tørholm 2009-08-18 00:20:48

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;
4
répondu Doug 2012-12-20 16:46:28

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 /

4
répondu Nico 2013-08-13 11:33:31

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 );
4
répondu Prashant Srivastav 2017-06-13 13:23:04

vous pouvez utiliser la fonction définie par l'utilisateur qui se trouve ici .

3
répondu Alex Martelli 2009-08-18 00:19:52

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
3
répondu Franz K. 2010-11-17 18:21:29

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;
2
répondu Oscar Canon 2013-04-23 15:31:07

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);
2
répondu bob 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

ci-dessus semble fonctionner pour moi.

2
répondu Nochum Sossonko 2016-11-22 22:02:27

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.

1
répondu btk 2010-07-18 00:15:39

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);
...
1
répondu bezoo 2013-06-21 09:19:53

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;
1
répondu pucawo 2014-03-25 17:06:09

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

1
répondu didier2l 2014-04-18 08:45:52

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));
1
répondu Steve Cohen 2014-08-12 18:30:11

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

1
répondu Leonardo Nicolas 2014-09-12 14:18:03

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
1
répondu yuhanluo 2016-11-01 04:18:47

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

1
répondu Danylo Zherebetskyy 2017-04-21 22:06:41

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

0
répondu Rob Farley 2011-10-12 19:27:05

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.

0
répondu ggarri 2014-07-28 08:38:29

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>

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

0
répondu ZhekaKozlov 2014-09-02 09:45:09

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

0
répondu Chris Knoll 2015-06-17 04:53:16

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
0
répondu mr.baby123 2015-06-19 12:03:47

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

0
répondu jitendrapurohit 2015-07-31 05:42:52

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;
0
répondu Vladimir_M 2015-08-04 16:18:36

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;
0
répondu Ariel Allon 2016-11-23 01:31:45