MySQL로 중앙값을 계산하는 간단한 방법


207

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되므로 중앙값은 4SELECT AVG(val)= = 5입니다.


71
MySQL에 중간 값을 계산하는 기능이 없다는 사실 때문에 메스 꺼운 사람입니까? 어리석은.
Monica Heddneck

3
버전 10.3 이후의 MariaDB에는 하나가 있습니다. mariadb.com/kb/en/library/median
berturion

답변:


224

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+에는 MEDIAN 함수가 포함되어 있습니다


4
그룹 값을 표시하는 방법이 있습니까? 좋아요 : 해당 장소의 장소 / 중앙값 ... 장소 선택, 테이블의 중앙값 _ 값과 같은 방법 ... 감사
saulob

2
@rowNum은 실행이 끝날 때 '총 수'를 갖습니다. 따라서 '모두 계산'을 다시하지 않아도되는 경우 (내 쿼리가 그렇게 간단하지 않기 때문에 제 경우였습니다)
Ahmed-Anas

하나의 문장을 갖는 논리 : (floor ((total_rows + 1) / 2), floor ((total_rows + 2) / 2)) 중간 값에 필요한 행을 계산하는 것이 좋습니다! 어떻게 생각했는지 잘 모르겠지만 훌륭합니다. 내가 따르지 않는 부분은 (SELECT @rownum : = 0) r-이것이 어떤 목적을 달성합니까?
Shanemeister

첫 번째 WHERE 1로 변경하여 행을 WHERE d.val IS NOT NULL제외 NULL하여이 메소드를 기본에 맞게 유지하십시오.AVG
chiliNUT

1
내 값은 두 테이블 조인에서 나왔으므로 조인 후 행 순서가 올바른지 확인하기 위해 다른 하위 쿼리를 추가해야했습니다! 구조는 일종이었다select avg(value) from (select value, row_number from (select a - b as value from a_table join b_table order by value))
Daniel Buckmaster

62

방금 주석에서 온라인으로 다른 답변을 찾았습니다 .

거의 모든 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

열의 색인이 잘 작성되고 색인이 필터링 및 정렬에 사용되는지 확인하십시오. Explain 계획으로 확인하십시오.

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

"중간"행 번호를 계산하십시오. 아마도 사용하십시오 : median_row = floor(count / 2).

그런 다음 목록에서 선택하십시오.

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

원하는 값으로 하나의 행을 반환해야합니다.

야곱


6
@rob 편집을 도와주세요. 아니면 그냥 벨크로 솔루션으로 절해야합니까? (실제로 다른 솔루션을 연기하는 방법을 확실하지 않음) 감사합니다. Jacob
TheJacobTaylor

1
"교차 조인"을 수행하므로 큰 테이블의 경우 매우 느립니다.
Rick James

1
이 대답은 아무것도 반환 을위한 심지어 행의 수입니다.
kuttumiah '

이 답변은 일부 데이터 세트 (예 : 0.1, 0.1, 0.1, 2의 사소한 데이터 세트)에서는 전혀 작동하지 않습니다. 모든 값이 고유 한 경우에는 작동하지만 값인 경우에만 작동합니다.
Kem Mason

32

수용 된 솔루션이 MySQL 설치에서 작동하지 않고 빈 세트를 반환한다는 것을 알았지 만이 쿼리는 테스트 한 모든 상황에서 저에게 효과적이었습니다.

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

1
절대적으로 정확하고 인덱스 된 테이블에서 완벽하고 매우 빠르게 작동합니다.
Rob

2
이 여기에 모든 해답의 MySQL을 밖으로 가장 빠른 해결책이 될 것으로 보인다 단지 짧은 테이블 만 기록과 200 밀리

3
@ FrankConijn : 한 테이블에서 두 번 선택합니다. 테이블의 이름입니다 data및 두 개의 이름으로 사용되고있다 xy.
Brian

3
33k 개의 행이있는 테이블에서이 정확한 쿼리를 사용하여 mysqld를 중단했습니다 ...
Xenonite

1
이 쿼리는 짝수 행에 대해 잘못된 답변 을 반환합니다 .
kuttumiah

26

불행히도 TheJacobTaylor와 velcrow의 답변은 현재 버전의 MySQL에 대한 정확한 결과를 반환하지 않습니다.

위에서 벨크로의 대답은 가깝지만 짝수 행의 결과 집합에 대해서는 올바르게 계산되지 않습니다. 중앙값은 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 조건으로 바꾸십시오.

그리고 문자열 값의 중앙값에 대해 무엇을합니까?
Rick James

12

더 빠른 방법을 제안합니다.

행 개수를 가져옵니다.

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

그런 다음 정렬 된 하위 쿼리에서 중간 값을 가져옵니다.

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

나는 이것을 무작위 숫자의 5x10e6 데이터 세트로 테스트했으며 10 초 안에 중앙값을 찾을 것입니다.


3
이유 : SELECT VAL FROM data ORDER BY val limit @middlevalue, 1
Bryan

1
첫 번째 코드 블록의 변수 출력을 두 번째 코드 블록으로 어떻게 가져 옵니까?
여행

3
마찬가지로 @middlevalue는 어디에서 왔습니까?
여행

@Bryan-나는 당신에게 동의합니다. 그렇게하지 않는 이유를 찾은 적이 있습니까?
Shane N

5
limit 절에서 변수를 사용할 수 없으므로 작동하지 않습니다.
codepk

8

에 대한 의견 MySQL의 문서에서이 페이지는 다음과 같은 제안을 가지고 :

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

IMHO, 이것은 복잡한 부분 집합에서 중간 값이 필요한 상황에서 분명히 가장 좋습니다 (많은 데이터 부분 집합의 별도의 중간 값을 계산해야했습니다)
mblackwell8

나를 위해 잘 작동합니다. 5.6.14 MySQL 커뮤니티 서버. 11M 레코드가있는 테이블 (디스크에서 약 20Gb)에는 두 개의 기본 인덱스 (model_id, price)가 없습니다. 테이블 (여과 후)에는 중앙값을 계산할 500K 레코드가 있습니다. 결과적으로 30K 레코드 (model_id, median_price)가 있습니다. 쿼리 지속 시간은 1.5-2 초입니다. 속도가 빠릅니다.
Mikl


6

위의 대부분의 솔루션은 테이블의 한 필드에 대해서만 작동하므로 쿼리의 많은 필드에 대한 중앙값 (50 번째 백분위 수)을 가져와야합니다.

나는 이것을 사용한다 :

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;

위의 예에서 "50"을 백분위 수로 바꿀 수 있으며 매우 효율적입니다.

GROUP_CONCAT에 충분한 메모리가 있는지 확인하십시오. 다음과 같이 변경할 수 있습니다.

SET group_concat_max_len = 10485760; #10MB max length

자세한 내용은 http://web.performancerasta.com/metrics-tips-calculating-95th-99th-or-any-percentile-with-single-mysql-query/


알아두기 : 짝수의 값은 두 개의 중간 값보다 높습니다. 승산 값의 경우 중앙값 다음으로 높은 값을 갖습니다.
giordano

6

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

2
나는 이것이 엔트리 수가 이상한 테이블에서만 작동한다고 생각합니다. 짝수의 항목에 대해서는 문제가있을 수 있습니다.
Y. Chang

4

다른 매개 변수로 그룹화 된 무언가를 중간에서 수행 해야하는 사람들을 위해 벨크로의 대답을 바탕으로하십시오.

grp_field , t1을 선택 하십시오 . val FROM ( SELECT grp_field , @ rownum : = IF (@ s = grp_field , @ rownum + 1 , 0 ) AS , @ s : = IF (@ s @ rownum : = 0 , @ s : = 0 ) r
   ORDER BY grp_field , d . val
 ) 
         row_number
    = grp_field , @ s , grp_field ) AS sec , d . val
   FROM 데이터 d , (     선택    같은 T1이 JOIN ( SELECT grp_field를 , 카운트 (*) TOTAL_ROWS
   FROM 데이터 D
   
    GROUP BY의 grp_field
 ) T2
 ON T1을 . grp_field = t2 . grp_field
 어디서 t1 .   row_number= (총 _ 행수 / 2 ) +1 ;


3

여기에 있는 사용자 정의 함수를 사용할 수 있습니다 .


3
이것은 가장 유용하게 보이지만 불안정한 알파 소프트웨어를 설치하고 싶지 않아 불안정한 알파 소프트웨어를 설치하여 mysql이 내 프로덕션 서버에 충돌 할 수 있습니다. (
davr

6
따라서 관심있는 기능에 대한 소스를 연구하고, 필요에 따라 수정하거나 수정하고, "자신의"안정적인 알파 버전이 아닌 버전을 설치하십시오. 당신이 SO에 얻을 -?)
알렉스 마르 텔리

3

홀수 값 수를 처리합니다.이 경우 중간에있는 두 값의 평균을 제공합니다.

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

2

테이블이나 추가 변수없이 효율적으로 내 코드 :

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;

3
이와 GROUP_CONCAT같은 다른 함수 내에서 사용되는 경우에도 1023 자로 제한 되므로 상당한 양의 데이터에서 실패 합니다.
Rob Van Dam

2

선택적으로 스토어드 프로 시저에서이를 수행 할 수도 있습니다.

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

감사합니다! 결 측값 (NULL)은 값으로 간주됩니다. 이 문제를 피하려면 조건에 'x IS NOT NULL을 추가하십시오.
giordano

1
@giordano 코드의 어느 줄에 x IS NOT NULL추가해야합니까?
Przemyslaw 레민

1
@PrzemyslawRemin 죄송합니다. 본인의 진술이 명확하지 않아서 SP가 이미 결 측값을 고려하고 있음을 깨달았습니다. SP는 다음과 같이 호출해야합니다 CALL median("table","x","x IS NOT NULL").
giordano

2

아래 제시된 솔루션은 테이블, 변수 또는 하위 쿼리를 만들지 않고 하나의 쿼리에서만 작동합니다. 또한 그룹별로 쿼리하여 각 그룹의 중앙값을 얻을 수 있습니다 (필자가 필요합니다!).

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

group_concat 및 substring_index를 현명하게 사용하기 때문에 작동합니다.

그러나 큰 group_concat을 허용하려면 group_concat_max_len을 더 높은 값 (기본적으로 1,024 자)으로 설정해야합니다. 다음과 같이 설정할 수 있습니다 (현재 SQL 세션의 경우).

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

group_concat_max_len에 대한 추가 정보 : https://dev.mysql.com/doc/refman/5.1/en/server-system-variables.html#sysvar_group_concat_max_len


2

Velcrow의 대답에 대한 또 다른 리프이지만 단일 중간 테이블을 사용하고 계산을 위해 추가 쿼리를 수행하는 대신 행 번호 매기기에 사용되는 변수를 활용합니다. 또한 첫 번째 행이 행 0이되도록 카운트를 시작하여 Floor 및 Ceil을 사용하여 중간 행을 간단히 선택할 수 있도록합니다.

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

2
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}is 100이지만 코드는 제공합니다 102. 홀수에 대해서는 잘 작동했습니다.
Nomiluks

1

두 가지 쿼리 접근 방식을 사용했습니다.

  • 카운트, 최소, 최대 및 평균을 얻는 첫 번째
  • 중간 값을 얻기 위해 "LIMIT @ count / 2, 1"및 "ORDER BY .."절이있는 두 번째 (준비된 명령문)

이들은 defn 함수로 래핑되므로 한 번의 호출로 모든 값을 반환 할 수 있습니다.

범위가 정적이고 데이터가 자주 변경되지 않는 경우 이러한 값을 미리 계산 / 저장하고 매번 처음부터 쿼리하는 대신 저장된 값을 사용하는 것이 더 효율적일 수 있습니다.


1

중간 및 백분위 수 솔루션이 필요했기 때문에이 스레드의 결과를 기반으로 간단하고 매우 유연한 기능을 만들었습니다. 프로젝트에 쉽게 포함 할 수있는 "준비된"기능을 찾으면 나 자신이 행복하다는 것을 알고 있으므로 다음과 같이 빠르게 공유하기로 결정했습니다.

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

}

현재 프로젝트의 예와 같이 사용법이 매우 쉽습니다.

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

여기 내 방법이 있습니다. 물론, 당신은 그것을 절차에 넣을 수 있습니다 :-)

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;

미묘한 변수는 피할 수 @median_counter있습니다.

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

이 방법에는 하위 쿼리가없는 짝수와 홀수가 모두 포함됩니다.

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

1

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

1

종종 전체 테이블뿐만 아니라 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 (*)"를 넣으십시오.
Slava Murygin

1

MySQL은 버전 8.0부터 창 기능을 지원하므로, ROW_NUMBER또는 DENSE_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));

0

MySQL에 ROW_NUMBER가 있으면 MEDIAN은이 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)
;

IN은 짝수의 항목이있는 경우에 사용됩니다.

그룹당 중앙값을 찾으려면 OVER 절에서 PARTITION BY 그룹 만 찾으십시오.


1
아니, 아니, ROW_NUMBER OVERPARTITION BY, 그 어느 것도 아니다; 이것은 PostgreSQL, IBM DB2, MS SQL Server 등과 같은 실제 DB 엔진이 아닌 MySql입니다 .-).
Alex Martelli

0

이전의 모든 것을 읽은 후에는 실제 요구 사항과 일치하지 않으므로 절차 나 복잡한 진술이 필요없는 내 자신의 것을 구현했습니다. 나는 GROUP_CONCATMEDIAN을 얻고 COUNT DIV BY를 적용하려는 열의 모든 값 2 다음 쿼리와 같이 목록 중간에서 값을 추출합니다.

(POS는 중앙값을 얻으려는 열의 이름입니다)

(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

이 웹 사이트에서 다른 많은 의견이 나에게 도움이되는 방식으로 누군가에게 유용 할 수 있기를 바랍니다.


0

정확한 행 수를 알고 있으면이 쿼리를 사용할 수 있습니다.

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

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


0

세트의 중간 나이를 결정하는 데 필요한 약 10 억 개의 행이 포함 된 데이터베이스가 있습니다. 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
;

이 쿼리는 DB 지원 창 함수 (ROWS UNBOUNDED PRECEDING 포함)에 따라 다르지만이를 보유하고 있지 않은 경우 aggData CTE를 자체적으로 결합하고 이전의 모든 총계를 '누적 된'열로 집계하는 것이 간단합니다. value는 지정된 precentile을 포함합니다. 상기 샘플은 p10, p25, p50 (중앙값), p75 및 p90을 계산한다.

크리스


0

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

join없이 다른 방법을 제안 하지만 문자열 작업

나는 큰 데이터가있는 테이블로 확인하지 않았지만 작은 테이블은 정상적으로 작동합니다.

여기서 좋은 점 은 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 여야한다고 생각하지 않습니까?
Nomiluks

0

경우에 따라 중앙값은 다음과 같이 계산됩니다.

"중간 값"은 숫자 순으로 값을 정렬 할 때 숫자 목록의 "중간"값입니다. 짝수 세트의 경우 중앙값은 두 중간 값의 평균입니다 . 나는 그것을 위해 간단한 코드를 만들었습니다.

$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은 필수 결과입니다 :-)

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.