Maneira simples de calcular mediana com o MySQL

Qual é a maneira mais simples (e esperançosamente não muito lenta) de calcular a mediana com o MySQL? Eu usei o AVG(x) para encontrar a média, mas estou tendo dificuldades para encontrar uma maneira simples de calcular a mediana. Por enquanto, estou retornando todas as linhas para o PHP, fazendo uma sorting e, em seguida, escolhendo a linha do meio, mas certamente deve haver alguma maneira simples de fazer isso em uma única consulta do MySQL.

Exemplo de dados:

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

A sorting em val2 2 3 4 7 8 9 , então a mediana deve ser 4 , versus SELECT AVG(val) que == 5 .

    No 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 ressalta que após o primeiro passo, @rownum irá conter o número total de linhas. Isso pode ser usado para determinar a mediana, portanto, não é necessário passar em segundo ou juntar.

    Também AVG(dd.val) e dd.row_number IN(...) é usado para produzir corretamente uma mediana quando há um número par de registros. Raciocínio:

     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 

    Finalmente, o MariaDB 10.3.3+ contém uma function MEDIAN

    Acabei de encontrar outra resposta online nos comentários :

    Para medianas em praticamente qualquer 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 

    Verifique se suas colunas estão bem indexadas e se o índice é usado para filtragem e sorting. Verifique com os planos de explicação.

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

    Calcular o número da linha “mediana”. Talvez use: median_row = floor(count / 2) .

    Então, selecione-o da lista:

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

    Isso deve retornar uma linha apenas com o valor desejado.

    Jacob

    Eu encontrei a solução aceita não funcionou na minha instalação do MySQL, retornando um conjunto vazio, mas essa consulta funcionou para mim em todas as situações em que eu testei:

     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 

    Infelizmente, nem as respostas do TheJacobTaylor nem do velcro retornam resultados precisos para as versões atuais do MySQL.

    A resposta de Velcro acima é próxima, mas não é calculada corretamente para conjuntos de resultados com um número par de linhas. As medianas são definidas como 1) o número do meio em conjuntos com números ímpares, ou 2) a média dos dois números do meio em conjuntos de números pares.

    Então, aqui está a solução do velcro corrigida para lidar com conjuntos de números pares e ímpares:

     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; 

    Para usar isso, siga estas 3 etapas simples:

    1. Substitua "median_table" (2 ocorrências) no código acima pelo nome da sua tabela
    2. Substitua "median_column" (3 ocorrências) pelo nome da coluna que você gostaria de encontrar uma mediana para
    3. Se você tiver uma condição WHERE, substitua "WHERE 1" (2 ocorrências) por sua condição where

    Eu proponho um caminho mais rápido.

    Obter a contagem de linhas:

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

    Em seguida, pegue o valor do meio em uma subconsulta classificada:

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

    Eu testei isso com um dataset de números randoms de 5x10e6 e ele encontrará a mediana em menos de 10 segundos.

    Um comentário nesta página na documentação do MySQL tem a seguinte sugestão:

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

    Com base na resposta do velcro, para aqueles que têm que fazer uma mediana de algo agrupado por outro parâmetro:

     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
       DOS dados d, (SELECT @rownum: = 0, @s: = 0) r
       ORDER BY grp_field, d.val
     ) como t1 JOIN (
       SELECT grp_field, count (*) como total_rows
       DOS dados d
       GRUPO POR grp_field
     ) como t2
     ON t1.grp_field = t2.grp_field
     WHERE t1.row_number = floor (total_rows / 2) +1;
    

    A maioria das soluções acima funciona apenas para um campo da tabela, talvez seja necessário obter a mediana (percentil 50) para muitos campos na consulta.

    Eu uso isso:

     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; 

    Você pode replace o “50” no exemplo acima para qualquer percentil, é muito eficiente.

    Apenas certifique-se de ter memory suficiente para o GROUP_CONCAT, você pode alterá-lo com:

     SET group_concat_max_len = 10485760; #10MB max length 

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

    Você pode usar a function definida pelo usuário que é encontrada aqui .

    Toma conta de uma contagem de valores ímpares – fornece a média dos dois valores no meio nesse caso.

     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 

    Eu tenho este código abaixo que eu encontrei no HackerRank e é bastante simples e funciona em todos os casos.

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

    Meu código, eficiente sem tabelas ou variables ​​adicionais:

     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; 

    Opcionalmente, você também pode fazer isso em um procedimento armazenado:

     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 

    O acima parece funcionar para mim.

    Eu usei uma abordagem de duas consultas:

    • primeiro um para obter contagem, min, max e avg
    • segundo (declaração preparada) com cláusulas “LIMIT @ count / 2, 1” e “ORDER BY ..” para obter o valor mediano

    Estes são envolvidos em uma function defn, então todos os valores podem ser retornados de uma chamada.

    Se seus intervalos forem estáticos e seus dados não forem alterados com frequência, talvez seja mais eficiente pré-compilar / armazenar esses valores e usar os valores armazenados em vez de consultar do zero todas as vezes.

    Como eu só precisava de uma solução mediana e percentual, fiz uma function simples e bastante flexível com base nos resultados deste segmento. Sei que estou feliz se encontrar funções “prontas” fáceis de include nos meus projetos, por isso decidi compartilhar rapidamente:

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

    O uso é muito fácil, exemplo do meu projeto atual:

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

    Aqui está o meu caminho. Claro, você poderia colocá-lo em um procedimento 🙂

     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; 

    Você poderia evitar a variável @median_counter , se você substituiu isto:

     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; 

    Minha solução apresentada abaixo funciona em apenas uma consulta sem criação de tabela, variável ou mesmo subconsulta. Além disso, ele permite que você obtenha mediana para cada grupo em consultas agrupadas (isto é o que eu precisava!):

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

    Funciona devido ao uso inteligente de group_concat e substring_index.

    Mas, para permitir o group_concat grande, você precisa configurar group_concat_max_len para um valor mais alto (1024 char por padrão). Você pode configurá-lo assim (para a session SQL atual):

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

    Mais infos para group_concat_max_len: https://dev.mysql.com/doc/refman/5.1/en/server-system-variables.html#sysvar_group_concat_max_len

    Outro comentário sobre a resposta de Velcrow, mas usa uma única tabela intermediária e aproveita a variável usada para numeração de linhas para obter a contagem, em vez de executar uma consulta extra para calculá-la. Também inicia a contagem para que a primeira linha seja linha 0 para permitir simplesmente usar Floor e Ceil para selecionar a (s) linha (s) mediana (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)); 

    Instale e use as funções statistics do mysql: http://www.xarg.org/2012/07/statistical-functions-in-mysql/

    Depois disso, calcule a mediana é fácil:

    SELECT mediana (x) FROM t1

    Desta forma, parece include a contagem par e ímpar sem subconsulta.

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

    Frequentemente, podemos precisar calcular a Mediana não apenas para a tabela inteira, mas para agregados com relação à nossa ID. Em outras palavras, calcule a mediana para cada ID em nossa tabela, onde cada ID tem muitos registros. (bom desempenho e funciona em muitos problemas de correções de SQL + de vantagens e desvantagens, mais sobre o desempenho de diferentes methods de Mediana 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; 

    Espero que ajude

    Se o MySQL tem ROW_NUMBER, então o MEDIAN é (seja inspirado por esta consulta do 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) ; 

    O IN é usado no caso de você ter um número par de inputs.

    Se você quiser encontrar a mediana por grupo, apenas o grupo PARTITION BY nas cláusulas OVER.

    Roubar

    Depois de ler todos os anteriores, eles não corresponderam ao meu requisito real, então eu implementei o meu próprio, que não precisa de nenhum procedimento ou complicar as instruções, apenas eu GROUP_CONCAT todos os valores da coluna Eu queria obter o MEDIAN e aplicar um COUNT DIV Por 2 eu extraio o valor do meio da lista como a seguinte consulta:

    (POS é o nome da coluna que eu quero obter sua mediana)

     (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 

    Espero que isso possa ser útil para alguém do modo como muitos outros comentários foram feitos para mim neste site.

    Conhecendo a contagem exata de linhas, você pode usar esta consulta:

     SELECT  AS VAL FROM  ORDER BY VAL LIMIT 1 OFFSET 

    Onde = ceiling( / 2.0) - 1

    Eu tenho um database contendo cerca de 1 bilhão de linhas que precisamos para determinar a idade média no conjunto. A sorting de um bilhão de linhas é difícil, mas se você agregar os valores distintos que podem ser encontrados (faixa etária de 0 a 100), você pode classificar esta lista e usar alguma magia aritmética para encontrar qualquer percentil desejado da seguinte forma:

     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 ; 

    Esta consulta depende do seu database suportando funções da janela (incluindo ROWS UNBOUNDED PRECEDING), mas se você não tem que é simples juntar o aggData CTE e agregar todos os totais anteriores na coluna ‘acumulada’ que é usada para determinar qual value contém o precentile especificado. A amostra acima calcula p10, p25, p50 (mediana), p75 e p90.

    -Chris

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

    Eu sugeriria outra maneira, sem entrar , mas trabalhando com strings

    Eu não verifiquei com tabelas com grandes dados, mas pequenas / médias tabelas funciona muito bem.

    A coisa boa aqui, que funciona também por GROUPING , pode retornar a mediana para vários itens.

    aqui está o código de teste para a tabela de teste:

     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 

    e o código para encontrar a mediana para cada grupo:

     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 

    Saída:

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

    Em alguns casos, a mediana é calculada da seguinte forma:

    A “mediana” é o valor “intermediário” na lista de números quando eles são ordenados por valor. Para conjuntos de contagem pares, a mediana é a média dos dois valores médios . Eu criei um código simples para isso:

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

    O $ mediano retornado seria o resultado requerido 🙂

    Medianas agrupadas por dimensão:

     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; 

    Com base na resposta de @ bob, isso generaliza a consulta para ter a capacidade de retornar várias medianas, agrupadas por alguns critérios.

    Pense, por exemplo, no preço médio de venda de carros usados ​​em um lote de carros, agrupado por ano-mês.

     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;