【问题标题】:Simple way to calculate median with MySQL用 MySQL 计算中位数的简单方法
【发布时间】:2010-11-20 10:34:43
【问题描述】:

用 MySQL 计算中位数最简单(希望不会太慢)的方法是什么?我使用AVG(x) 来求平均值,但我很难找到一种简单的方法来计算中值。目前,我将所有行返回给 PHP,进行排序,然后选择中间行,但肯定有一些简单的方法可以在单个 MySQL 查询中完成。

示例数据:

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

val 排序得到2 2 3 4 7 8 9,所以中位数应该是4,而SELECT AVG(val) == 5

【问题讨论】:

标签: sql mysql statistics median


【解决方案1】:

您可以使用窗口函数 row_number() 来回答查询以查找媒介

select val 
from (select val, row_number() over (order by val) as rownumber, x.cnt 
from data, (select count(*) as cnt from data) x) abc
where rownumber=ceil(cnt/2);

【讨论】:

    【解决方案2】:

    我在 MySQL 中使用下表作为解决方案:

    CREATE TABLE transactions (
      transaction_id int , user_id int , merchant_name varchar(255), transaction_date date , amount int
    );
    
    INSERT INTO transactions (transaction_id, user_id, merchant_name, transaction_date, amount)  
    VALUES (1, 1 ,'abc', '2015-08-17', 100),(2, 2, 'ced', '2015-2-17', 100),(3, 1, 'def', '2015-2-16', 121),
    (4, 1 ,'ced', '2015-3-17', 110),(5, 1, 'ced', '2015-3-17', 150),(6, 2 ,'abc', '2015-4-17', 130), 
    (7, 3 ,'ced', '2015-12-17', 10),(8, 3 ,'abc', '2015-8-17', 100),(9, 2 ,'abc', '2015-12-17', 140),(10, 1,'abc', '2015-9-17', 100),
    (11, 1 ,'abc', '2015-08-17', 121),(12, 2 ,'ced', '2015-12-23', 130),(13, 1 ,'def', '2015-12-23', 13),(3, 4, 'abc', '2015-2-16', 120),(3, 4, 'def', '2015-2-16', 121),(3, 4, 'ced', '2015-2-16', 121);
    

    计算“金额”列的中位数:

    WITH Numbered AS 
    (
    SELECT *, COUNT(*) OVER () AS TotatRecords,
        ROW_NUMBER() OVER (ORDER BY amount) AS RowNum
    FROM transactions
    )
    SELECT Avg(amount)
    FROM Numbered
    WHERE RowNum IN ( FLOOR((TotatRecords+1)/2), FLOOR((TotatRecords+2)/2) )
    ;
    

    TotalRecords = 16 和 Median = 120.5000

    此查询适用于两种条件,即偶数和奇数记录。

    【讨论】:

      【解决方案3】:

      对于一个表站和列 lat_n,这里是获取中位数的 MySQL 代码:

      set @rows := (select count(1) from station);
      set @v1 := 0;
      set @sql1 := concat('select lat_n into @v1 from station order by lat_n asc limit 1 offset ', ceil(@rows/2) - 1);
      prepare statement1 from @sql1;
      execute statement1;
      set @v2 := 0;
      set @sql2 := concat('select lat_n into @v2 from station order by lat_n asc limit 1 offset ', ceil((@rows + 1)/2) - 1);
      prepare statement2 from @sql2;
      execute statement2;
      select (@v1 + @v2)/2;
      

      【讨论】:

        【解决方案4】:

        如果这是 MySQL,现在有窗口函数,你可以这样做(假设你想四舍五入到最接近的整数 - 否则只需将 ROUND 替换为 CEILFLOOR 或你有什么)。以下解决方案适用于表,无论它们的行数是偶数还是奇数:

        
        WITH CTE AS (
            SELECT val,
                    ROW_NUMBER() OVER (ORDER BY val ASC) AS rn,
                    COUNT(*) OVER () AS total_count
            FROM data
        )
        SELECT ROUND(AVG(val)) AS median
        FROM CTE
        WHERE
            rn BETWEEN
            total_count / 2.0 AND
            total_count / 2.0 + 1;
        
        

        我认为该线程上的一些较新的答案已经采用了这种方法,但似乎人们也想多了,所以考虑这是一个改进的版本。无论 SQL 风格如何,没有理由任何人都应该编写包含多个子查询的大段代码来获得 2021 年的中位数。但是,请注意,上述查询仅在被要求找到中位数的情况下才有效连续系列。当然,不管行数如何,有时人们确实会区分连续序列的所谓离散中位数和所谓的插值中位数 .

        如果要求您找到 离散序列 的中位数,并且该表的行数是偶数,则上述解决方案将不适合您,并且您应该恢复使用其他解决方案之一,例如 TheJacobTaylor 的。

        下面的第二个解决方案是 TheJacobTaylor 的略微修改版本,我明确指出 CROSS JOIN。这也适用于具有奇数行的表,无论您是否被要求找到连续或离散系列的中位数,但当被要求找到离散系列的中位数时,我会特别使用它。否则,使用第一个解决方案。这样一来,您就不必考虑数据是否包含“偶数”或“奇数”个数据点。

        
        SELECT x.val AS median
        FROM data x
        CROSS JOIN data y
        GROUP BY x.val
        HAVING SUM(SIGN(1 - SIGN(y.val - x.val))) = (COUNT(*) + 1) / 2;
        
        

        最后,您可以在 PostgreSQL 中使用内置函数轻松完成此操作。这是一个很好的解释,以及关于离散中位数与插值中位数的有效总结。

        https://leafo.net/guides/postgresql-calculating-percentile.html#calculating-the-median

        【讨论】:

          【解决方案5】:

          ORACLE 的简单解决方案:

          SELECT ROUND(MEDIAN(Lat_N), 4) FROM Station;
          

          易于理解的 MySQL 解决方案:

          select case MOD(count(lat_n),2) 
          when 1 then (select round(S.LAT_N,4) from station S where (select count(Lat_N) from station where Lat_N < S.LAT_N ) = (select count(Lat_N) from station where Lat_N > S.LAT_N))
          else (select round(AVG(S.LAT_N),4) from station S where 1 = (select count(Lat_N) from station where Lat_N < S.LAT_N ) - (select count(Lat_N) from station where Lat_N > S.LAT_N))
          end from station;
          

          说明

          STATION 是表名。 LAT_N 是具有数值的列名

          假设车站表中有101条记录(奇数)。这意味着如果表格按 asc 或 desc 排序,则中位数是第 51 条记录。

          在上面对 S 表的每个 S.LAT_N 的查询中,我正在创建两个表。一个用于 LAT_N 值的数量小于 S.LAT_N,另一个用于 LAT_N 值的数量大于 S.LAT_N。稍后我将比较这两个表,如果它们匹配,那么我将选择 S.LAT_N 值。当我检查第 51 条记录时,有 50 个值小于第 51 条记录,有 50 条记录大于第 51 条记录。如您所见,两个表中有 50 条记录。所以这就是我们的答案。对于每个其他记录,在两个表中创建用于比较的记录数不同。因此,只有第 51 条记录满足条件。

          现在假设 在 station 表中有 100 条记录(偶数)。这意味着如果表格按升序或降序排序,则中位数是第 50 条和第 51 条记录的平均值。

          与奇怪的逻辑相同,我正在创建两个表。一个用于 LAT_N 值的数量小于 S.LAT_N,另一个用于 LAT_N 值的数量大于 S.LAT_N。稍后我将比较这两个表,如果它们的差异等于 1,那么我将选择 S.LAT_N 值并找到平均值。当我检查第 50 条记录时,有 49 个值小于第 50 条记录,有 51 条记录大于第 50 条记录。如您所见,两个表中存在 1 条记录的差异。所以这(第 50 条记录)是我们平均的第 1 条记录。同样,当我检查第 51 条记录时,有 50 个值小于第 51 条记录,有 49 条记录大于第 51 条记录。如您所见,两个表中存在 1 条记录的差异。所以这(第 51 条记录)是我们平均的第二条记录。对于每个其他记录,在两个表中创建用于比较的记录数不同。因此,只有第 50 条和第 51 条记录满足条件。

          【讨论】:

            【解决方案6】:

            mysql中计算中位数最简单快捷的方法。

            select x.col
            from   (select lat_n, 
                           count(1) over (partition by 'A')        as total_rows, 
                           row_number() over (order by col asc) as rank_Order 
                    from   station ft) x 
            where  x.rank_Order = round(x.total_rows / 2.0, 0) 
            

            【讨论】:

              【解决方案7】:

              我没有将此解决方案的性能与此处发布的其他答案进行比较,但我发现这是最容易理解的,并且涵盖了用于计算中位数的 mathematical formula 的全部范围。换句话说,这个解决方案对于偶数和奇数数据集来说足够强大:

              SELECT CASE 
              -- odd-numbered data sets:
              WHEN MOD(COUNT(*), 2) = 1 THEN (SELECT median.<value> AS median
              FROM
              (SELECT t1.<value>
                FROM (SELECT <value>, 
                             ROW_NUMBER() OVER(ORDER BY <value>) AS rownum
                        FROM <data>) t1,
                     (SELECT COUNT(*) AS num_records FROM <data>) t2
               WHERE t1.rownum =(t2.num_records) / 2) as median)
              -- even-numbered data sets:
              ELSE (select (low_bound.<value> + up_bound.<value>) / 2 AS median
              FROM
              (SELECT t1.<value>
                FROM (SELECT <value>, 
                             ROW_NUMBER() OVER(ORDER BY <value>) AS rownum
                        FROM <data>) t1,
                     (SELECT COUNT(*) AS num_records FROM <data>) t2
               WHERE t1.rownum =(t2.num_records - 1) / 2) as low_bound,
               (SELECT t1.<value>
                FROM (SELECT <value>, 
                             ROW_NUMBER() OVER(ORDER BY <value>) AS rownum
                        FROM station) t1,
                     (SELECT COUNT(*) AS num_records FROM data) t2
               WHERE t1.rownum =(t2.num_records + 1) / 2) as up_bound)
              END
              FROM <data>
              

              【讨论】:

                【解决方案8】:

                尝试类似:

                SELECT  
                CAST (AVG(val) AS DECIMAL(10,4))
                FROM
                (
                    SELECT 
                    val,
                    ROW_NUMBER() OVER( ORDER BY val ) -1 AS rn,
                    COUNT(1) OVER () -1 AS cnt
                    FROM STATION
                ) as tmp
                WHERE rn IN (FLOOR(cnt/2),CEILING (cnt/2))
                

                **

                注意:-1 的原因是使其索引为零..即行号 现在从 0 而不是 1 开始

                **

                【讨论】:

                  【解决方案9】:

                  以下查询适用于偶数行或奇数行。在子查询中,我们正在查找在它之前和之后具有相同行数的值。在奇数行的情况下,having 子句将计算为 0(取消符号之前和之后的相同行数)。

                  同样,对于偶数行,对于两行(中间的 2 行),having 子句的计算结果为 1,因为它们(共同)前后的行数相同。

                  在外部查询中,我们将计算单个值(奇数行的情况下)或(偶数行的情况下的两个值)。

                  select avg(val) as median
                  from
                  (
                      select d1.val
                      from data d1 cross join data d2
                      group by d1.val
                      having abs(sum(sign(d1.val-d2.val))) in (0,1)
                  ) sub
                  

                  注意:如果你的表有重复值,上面的having子句应该改为下面的条件。在这种情况下,可能存在原始可能性 0,1 之外的值。以下条件将使此条件动态化,并且在重复的情况下也可以工作。

                  having sum(case when d1.val=d2.val then 1 else 0 end)>=
                  abs(sum(sign(d1.val-d2.val)))
                  

                  【讨论】:

                    【解决方案10】:

                    在 MySQL 中计算中位数的简单方法

                    set @ct := (select count(1) from station);
                    set @row := 0;
                    
                    select avg(a.val) as median from 
                    (select * from  table order by val) a
                    where (select @row := @row + 1)
                    between @ct/2.0 and @ct/2.0 +1;
                    

                    【讨论】:

                      【解决方案11】:

                      单一查询归档完美中位数:

                      SELECT 
                      COUNT(*) as total_rows, 
                      IF(count(*)%2 = 1, CAST(SUBSTRING_INDEX(SUBSTRING_INDEX( GROUP_CONCAT(val ORDER BY val SEPARATOR ','), ',', 50/100 * COUNT(*)), ',', -1) AS DECIMAL), ROUND((CAST(SUBSTRING_INDEX(SUBSTRING_INDEX( GROUP_CONCAT(val ORDER BY val SEPARATOR ','), ',', 50/100 * COUNT(*) + 1), ',', -1) AS DECIMAL) + CAST(SUBSTRING_INDEX(SUBSTRING_INDEX( GROUP_CONCAT(val ORDER BY val SEPARATOR ','), ',', 50/100 * COUNT(*)), ',', -1) AS DECIMAL)) / 2)) as median, 
                      AVG(val) as average 
                      FROM 
                      data
                      

                      【讨论】:

                        【解决方案12】:

                        MySQL 从 8.0 版本开始支持窗口函数,您可以使用 ROW_NUMBERDENSE_RANK请勿使用 RANK,因为它为相同的值分配相同的排名,就像在体育排名中一样):

                        SELECT AVG(t1.val) AS median_val
                          FROM (SELECT val, 
                                       ROW_NUMBER() OVER(ORDER BY val) AS rownum
                                  FROM data) t1,
                               (SELECT COUNT(*) AS num_records FROM data) t2
                         WHERE t1.row_num IN
                               (FLOOR((t2.num_records + 1) / 2), 
                                FLOOR((t2.num_records + 2) / 2));
                        

                        【讨论】:

                          【解决方案13】:

                          不幸的是,TheJacobTaylor 和 velcrow 的回答都没有返回当前 MySQL 版本的准确结果。

                          上面的 Velcro 的答案很接近,但它不能正确计算具有偶数行的结果集。中位数定义为 1) 奇数集的中间数,或 2) 偶数集的两个中间数的平均值。

                          所以,下面是魔术贴的解决方案,可以处理奇数和偶数集:

                          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;
                          

                          要使用它,请按照以下 3 个简单步骤操作:

                          1. 将上述代码中的“median_table”(出现 2 次)替换为您的表名
                          2. 将“median_column”(出现 3 次)替换为您要为其查找中值的列名
                          3. 如果您有 WHERE 条件,请将“WHERE 1”(出现 2 次)替换为您的 where 条件

                          【讨论】:

                          • 还有,你对字符串值的中位数做了什么?
                          【解决方案14】:

                          安装使用这个mysql统计功能:http://www.xarg.org/2012/07/statistical-functions-in-mysql/

                          之后,计算中位数就很容易了:

                          SELECT median(val) FROM data;
                          

                          【讨论】:

                          • 我自己也试过了,因为它的价值,安装它超级快/容易,而且它像宣传的那样工作,包括分组,例如"select name, median(x) FROM t1 group by name" -- github 来源:github.com/infusion/udf_infusion
                          【解决方案15】:

                          我发现这个答案很有帮助 - https://www.eversql.com/how-to-calculate-median-value-in-mysql-using-a-simple-sql-query/

                          SET @rowindex := -1;
                          
                          SELECT
                             AVG(g.grade)
                          FROM
                             (SELECT @rowindex:=@rowindex + 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));
                          

                          【讨论】:

                            【解决方案16】:

                            在 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 指出,在第一遍之后,@rownum 将包含总行数。这可用于确定中位数,因此不需要第二次传递或连接。

                            还有AVG(dd.val)dd.row_number IN(...) 用于在记录数为偶数时正确生成中位数。推理:

                            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+ contains a MEDIAN function

                            【讨论】:

                            • 有什么办法让它显示组值?喜欢:那个地方的地方/中位数......比如选择地方,表格中的median_value......任何方式?谢谢
                            • @rowNum 将在执行结束时获得“总数”。因此,如果您想避免再次进行“全部计数”,则可以使用它(这是我的情况,因为我的查询不是那么简单)
                            • 只有一条语句的逻辑:( floor((total_rows+1)/2), floor((total_rows+2)/2) ) 计算中位数所需的行真是太棒了!不知道你是怎么想的,但它很棒。我不关注的部分是 (SELECT @rownum:=0) r——这有什么作用?
                            • 我的值来自一个两表连接,所以我必须添加另一个子查询,以确保连接后行顺序正确!结构有点像select avg(value) from (select value, row_number from (select a - b as value from a_table join b_table order by value))
                            • 我知道这已经很老了,但由于某种原因,这会产生与将 set @rn:=-1 移动到外部选择而不是在内部选择中在 0 处实例化的结果截然不同的结果。由于某种原因,我无法得到匹配的结果
                            【解决方案17】:

                            以下 SQL 代码将帮助您使用用户定义的变量计算 MySQL 中的中位数。

                            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:=@rowid+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 tbl.rid=@middle_no or tbl.rid=(@middle_no+@odd_even);

                            如果你正在寻找详细的解释,请参考这个blog.

                            【讨论】:

                              【解决方案18】:

                              我在 HackerRank 上找到了以下代码,它非常简单,适用于各种情况。

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

                              【讨论】:

                              • 我相信这仅适用于条目数为奇数的表。对于偶数个条目,这可能会出现问题。
                              • @Y.Chang 你是对的。这对偶数行不返回任何内容
                              【解决方案19】:

                              通常,我们可能不仅需要计算整个表的中位数,还需要计算与 ID 相关的聚合。换句话说,计算我们表中每个 ID 的中位数,其中每个 ID 都有许多记录。 (良好的性能和适用于许多 SQL + 修复偶数和赔率问题,更多关于不同中位数方法的性能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;
                              

                              希望对你有帮助

                              【讨论】:

                              • 这是最好的解决方案。但是,对于大型数据集,它会变慢,因为它会为每个集合中的每个项目重新计数。为了让它更快,把“COUNT(*)”放在单独的子查询中。
                              【解决方案20】:
                              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)
                              

                              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;
                              

                              【讨论】:

                                【解决方案21】:

                                这些方法从同一个表中选择两次。如果源数据来自昂贵的查询,这是一种避免运行两次的方法:

                                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
                                

                                【讨论】:

                                  【解决方案22】:

                                  根据@bob 的回答,这将查询概括为能够返回多个按某些条件分组的中位数。

                                  想想,例如,按年月分组的汽车批次中二手车的中位销售价格。

                                  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;
                                  

                                  【讨论】:

                                    【解决方案23】:
                                    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
                                    

                                    以上似乎对我有用。

                                    【讨论】:

                                    • 它没有返回偶数个值的正确中位数,例如,{98,102,102,98} 的中位数是100,但您的代码给出了102。它适用于奇数。
                                    【解决方案24】:

                                    这种方式似乎包括偶数和奇数,没有子查询。

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

                                    【讨论】:

                                    • 你能告诉t2表是什么吗?
                                    • @xliiv t2 是被选中的第二个表的别名(在示例中名为 table)。这种方法的问题在于它进行了交叉连接,但也不适用于我的简单示例:ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in ("t.score" - "t2.score")
                                    【解决方案25】:
                                    set @r = 0;
                                    
                                    select  
                                        case when mod(c,2)=0 then round(sum(lat_N),4)
                                        else round(sum(lat_N)/2,4) 
                                        end as Med  
                                    from 
                                        (select lat_N, @r := @r+1, @r as id from station order by lat_N) A
                                        cross join
                                        (select (count(1)+1)/2 as c from station) B
                                    where id >= floor(c) and id <=ceil(c)
                                    

                                    【讨论】:

                                      【解决方案26】:

                                      按维度分组的中位数:

                                      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;
                                      

                                      【讨论】:

                                        【解决方案27】:

                                        在某些情况下,中位数的计算方式如下:

                                        当按值排序时,“中位数”是数字列表中的“中间”值。对于偶数集,中位数是两个中间值的平均值。 我为此创建了一个简单的代码:

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

                                        返回的 $median 将是所需的结果 :-)

                                        【讨论】:

                                          【解决方案28】:

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

                                          我会建议另一种方式,不加入, 但使用字符串

                                          我没有用大数据表检查它, 但小型/中型表工作得很好。

                                          这里的好处是,它也可以按 GROUPING 工作,因此它可以返回多个项目的中值。

                                          这里是测试表的测试代码:

                                          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 
                                          

                                          以及查找每组中位数的代码:

                                          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
                                          

                                          输出:

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

                                          【讨论】:

                                          • 你不觉得`{22,26}`的中位数应该是24吗?
                                          【解决方案29】:

                                          我有一个包含大约 10 亿行的数据库,我们需要这些行来确定集合中的年龄中位数。对十亿行进行排序很困难,但如果您汇总可以找到的不同值(年龄范围从 0 到 100),您可以对这个列表进行排序,并使用一些算术魔法来找到您想要的任何百分位数,如下所示:

                                          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
                                          ;
                                          

                                          此查询取决于您的数据库支持窗口函数(包括 ROWS UNBOUNDED PRECEDING),但如果您没有,则将 aggData CTE 与其自身连接并将所有先前的总计聚合到使用的“累积”列中是一件简单的事情以确定哪个值包含指定的百分位数。上述示例计算了 p10、p25、p50(中位数)、p75 和 p90。

                                          -克里斯

                                          【讨论】:

                                            【解决方案30】:

                                            知道确切的行数,您可以使用此查询:

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

                                            在哪里&lt;half&gt; = ceiling(&lt;size&gt; / 2.0) - 1

                                            【讨论】:

                                              猜你喜欢
                                              • 2022-12-11
                                              • 1970-01-01
                                              • 1970-01-01
                                              • 1970-01-01
                                              • 1970-01-01
                                              相关资源
                                              最近更新 更多