Cara sederhana untuk menghitung median dengan MySQL

208

Apa cara paling sederhana (dan mudah-mudahan tidak terlalu lambat) untuk menghitung median dengan MySQL? Saya sudah terbiasa AVG(x)mencari mean, tetapi saya kesulitan menemukan cara sederhana menghitung median. Untuk saat ini, saya mengembalikan semua baris ke PHP, melakukan pengurutan, dan kemudian memilih baris tengah, tetapi tentunya harus ada beberapa cara sederhana untuk melakukannya dalam satu permintaan MySQL.

Contoh data:

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

Mengurutkan pada valmemberi 2 2 3 4 7 8 9, jadi median seharusnya 4, versus SELECT AVG(val)yang == 5.

davr
sumber
72
Apakah saya satu-satunya yang mual oleh fakta bahwa MySQL tidak memiliki fungsi untuk menghitung median? Konyol.
Monica Heddneck
3
MariaDB sejak versi 10.3 memilikinya, lihat mariadb.com/kb/en/library/median
berturion

Jawaban:

225

Dalam 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 menunjukkan, bahwa setelah lulus pertama, @rownum akan berisi jumlah total baris. Ini dapat digunakan untuk menentukan median, sehingga tidak perlu melewati kedua atau bergabung.

Juga AVG(dd.val)dan dd.row_number IN(...)digunakan untuk menghasilkan median dengan benar ketika ada bahkan jumlah catatan. Pemikiran:

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

Akhirnya, MariaDB 10.3.3+ berisi fungsi MEDIAN

velcrow
sumber
4
cara apa pun untuk membuatnya menampilkan nilai grup? seperti: tempat / median untuk tempat itu ... seperti tempat pilih, median_value dari tabel ... apa pun? terima kasih
saulob
2
@rowNum akan memiliki 'jumlah total' di akhir eksekusi. Jadi Anda dapat menggunakannya jika Anda ingin menghindari melakukan 'hitung semua' lagi (yang merupakan kasus saya karena permintaan saya tidak begitu sederhana)
Ahmed-Anas
Logika memiliki satu pernyataan: (lantai ((total_rows + 1) / 2), lantai ((total_rows + 2) / 2)) menghitung baris yang diperlukan untuk median luar biasa! Tidak yakin bagaimana Anda memikirkannya, tetapi itu brilian. Bagian yang tidak saya ikuti adalah (SELECT @rownum: = 0) r - tujuan apa yang dilayaninya?
Shanemeister
ubah yang pertama WHERE 1menjadi WHERE d.val IS NOT NULLsehingga tidak termasuk NULLbaris untuk menjaga metode ini selaras dengan asliAVG
chiliNUT
1
Nilai saya berasal dari gabungan dua tabel, jadi saya harus menambahkan subquery lain untuk memastikan urutan baris sudah benar setelah bergabung! Strukturnya sepertiselect 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

Saya baru saja menemukan jawaban lain di komentar :

Untuk median di hampir semua 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

Pastikan kolom Anda diindeks dengan baik dan indeks digunakan untuk memfilter dan menyortir. Verifikasi dengan rencana jelaskan.

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

Hitung nomor baris "median". Mungkin menggunakan: median_row = floor(count / 2).

Kemudian ambil dari daftar:

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

Ini akan mengembalikan Anda satu baris dengan hanya nilai yang Anda inginkan.

Yakub

TheJacobTaylor
sumber
6
@rob, bisakah Anda membantu mengedit? Atau haruskah saya tunduk pada solusi velcrow? (tidak benar-benar yakin bagaimana menunda solusi lain) Terima kasih, Jacob
TheJacobTaylor
1
Perhatikan bahwa ia melakukan "cross join", yang sangat lambat untuk tabel besar.
Rick James
1
Jawaban ini tidak mengembalikan apa pun untuk jumlah baris genap .
kuttumiah
Jawaban ini tidak berfungsi sama sekali untuk beberapa set data, misalnya, set data sepele dengan nilai 0,1, 0,1, 0,1, 2 - ini akan berfungsi jika semua nilai berbeda, tetapi hanya berfungsi jika nilai
Kem Mason
32

Saya menemukan solusi yang diterima tidak berfungsi pada instalasi MySQL saya, mengembalikan set kosong, tetapi kueri ini bekerja untuk saya dalam semua situasi yang saya uji pada:

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
zookatron
sumber
1
benar-benar benar, bekerja dengan sempurna dan sangat cepat di tabel saya yang diindeks
Rob
2
ini tampaknya menjadi solusi tercepat di mysql dari semua jawaban di sini, 200 ms dengan hanya kurang dari satu juta catatan dalam tabel
Rob
3
@ Frankconijn: Ini memilih dari satu tabel dua kali. Nama tabel adalah datadan sedang digunakan dengan dua nama, xdan y.
Brian
3
hanya mengatakan saya menghentikan mysqld saya dengan permintaan yang tepat ini di atas meja dengan baris 33k ...
Xenonite
1
Kueri ini mengembalikan jawaban yang salah untuk jumlah baris genap .
kuttumiah
26

Sayangnya, jawaban TheJacobTaylor maupun velcrow tidak memberikan hasil yang akurat untuk versi MySQL saat ini.

Jawaban Velcro dari atas sudah dekat, tetapi tidak menghitung dengan benar untuk hasil set dengan jumlah baris genap. Median didefinisikan sebagai 1) nomor tengah pada set bernomor ganjil, atau 2) rata-rata dari dua nomor tengah pada set angka genap.

Jadi, inilah solusi velcro yang ditambal untuk menangani set angka ganjil dan genap:

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;

Untuk menggunakan ini, ikuti 3 langkah mudah ini:

  1. Ganti "median_table" (2 kemunculan) dalam kode di atas dengan nama tabel Anda
  2. Ganti "median_column" (3 kemunculan) dengan nama kolom yang ingin Anda cari mediannya
  3. Jika Anda memiliki kondisi WHERE, ganti "WHERE 1" (2 kejadian) dengan kondisi di mana Anda
bob
sumber
Dan, apa yang Anda lakukan untuk Median nilai string?
Rick James
12

Saya mengusulkan cara yang lebih cepat.

Dapatkan jumlah baris:

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

Kemudian ambil nilai tengah dalam subquery yang diurutkan:

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

Saya menguji ini dengan dataset acak angka 5x10e6 dan ia akan menemukan median dalam waktu kurang dari 10 detik.

Reggie Edwards
sumber
3
Mengapa tidak: SELECT val DARI data ORDER BY val limit @middlevalue, 1
Bryan
1
Bagaimana Anda menarik output variabel dari blok kode pertama Anda ke dalam blok kode kedua Anda?
Perjalanan
3
Seperti di, dari mana @middlevalue berasal?
Perjalanan
@Bryan - Saya setuju dengan Anda, itu jauh lebih masuk akal bagi saya. Apakah Anda pernah menemukan alasan untuk tidak melakukannya dengan cara itu?
Shane N
5
Ini tidak berfungsi sebagai variabel tidak dapat digunakan dalam klausa batas.
codepk
8

Komentar pada halaman ini dalam dokumentasi MySQL memiliki saran berikut:

-- (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 
Sebastian Paaske Tørholm
sumber
IMHO, ini jelas yang terbaik untuk situasi di mana Anda memerlukan median dari subset yang rumit (saya perlu menghitung median terpisah dari sejumlah besar subset data)
mblackwell8
Bekerja dengan baik untuk saya. 5.6.14 Server Komunitas MySQL. Tabel dengan catatan 11M (sekitar 20Gb pada disk), memiliki dua bukan indeks utama (model_id, harga). Dalam tabel (setelah filtrasi) kami memiliki catatan 500K untuk menghitung median. Sebagai hasilnya, kami memiliki catatan 30K (model_id, median_price). Durasi permintaan adalah 1,5-2 detik. Kecepatan cepat bagi saya.
Mikl
6

Sebagian besar solusi di atas hanya berfungsi untuk satu bidang tabel, Anda mungkin perlu mendapatkan median (persentil ke-50) untuk banyak bidang di kueri.

Saya menggunakan ini:

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;

Anda dapat mengganti "50" dalam contoh di atas untuk persentil apa pun, sangat efisien.

Pastikan Anda memiliki cukup memori untuk GROUP_CONCAT, Anda dapat mengubahnya dengan:

SET group_concat_max_len = 10485760; #10MB max length

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

Nico
sumber
Sadarilah: Untuk jumlah nilai genap, dibutuhkan dua nilai tengah yang lebih tinggi. Untuk jumlah odds nilai dibutuhkan nilai lebih tinggi berikutnya setelah median.
giordano
6

Saya memiliki kode di bawah ini yang saya temukan di HackerRank dan sangat sederhana dan berfungsi di setiap kasus.

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 );
Prashant Srivastav
sumber
2
Saya percaya ini hanya berfungsi dengan tabel yang memiliki jumlah entri aneh. Untuk jumlah entri yang genap, ini mungkin memiliki masalah.
Y. Chang
4

Membangun dari jawaban velcro, bagi Anda yang harus melakukan median dari sesuatu yang dikelompokkan oleh parameter lain:

SELECT grp_field , t1 . val FROM ( SELECT grp_field , @ rownum : = IF (@ s = grp_field , @ rownum + 1 , 0 ) AS , @ s : = IF (@ s = grp_field , @ s , grp_field ) AS dtk , d . val
   data FROM d , PILIH 
         row_number
        ( @ rownum : = 0 , @ s : = 0 ) r
   ORDER OLEH grp_field , d . val
 ) sebagai t1 GABUNG ( SELECT grp_field , hitung (*) sebagai total_rows
   DARI data d
   GROUP BY grp_field
 ) sebagai t2
 ON t1 . grp_field = t2 . grp_field
 WHERE t1 . = lantai    
     row_number ( total_rows / 2 ) +1 ;

Doug
sumber
3

Anda bisa menggunakan fungsi yang ditentukan pengguna yang ditemukan di sini .

Alex Martelli
sumber
3
Ini terlihat paling berguna, tetapi saya tidak ingin menginstal perangkat lunak alpha yang tidak stabil yang dapat menyebabkan mysql crash ke server produksi saya :(
davr
6
Jadi pelajari sumber-sumber mereka untuk fungsi yang menarik, perbaiki atau modifikasi sesuai kebutuhan, dan instal versi "milik Anda" dan non-alpha begitu Anda membuatnya - bagaimana itu lebih buruk daripada mengubah-ubah saran kode yang kurang terbukti sama Anda mendapatkan SO? -)
Alex Martelli
3

Merawat hitungan nilai ganjil - berikan rata-rata dari dua nilai di tengah dalam kasus itu.

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
Franz K.
sumber
2

Kode saya, efisien tanpa tabel atau variabel tambahan:

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;
Oscar Canon
sumber
3
Ini akan gagal pada sejumlah besar data karena GROUP_CONCATterbatas pada 1023 karakter, bahkan ketika digunakan di dalam fungsi lain seperti ini.
Rob Van Dam
2

Secara opsional, Anda juga bisa melakukan ini dalam prosedur tersimpan:

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);
bob
sumber
Terima kasih untuk ini! Pengguna harus menyadari bahwa nilai yang hilang (NULL) dianggap sebagai nilai. untuk menghindari masalah ini tambahkan 'x BUKAN NULL di mana kondisinya.
giordano
1
@giordano Di baris kode mana yang x IS NOT NULLharus ditambahkan?
Przemyslaw Remin
1
@ PrzemyslawRemin Maaf, saya tidak jelas dalam pernyataan saya dan saya menyadari sekarang bahwa SP sudah mempertimbangkan kasus nilai yang hilang. SP harus disebut dengan cara ini: CALL median("table","x","x IS NOT NULL").
giordano
2

Solusi saya yang disajikan di bawah ini berfungsi hanya dalam satu kueri tanpa membuat tabel, variabel atau bahkan sub-kueri. Plus, ini memungkinkan Anda untuk mendapatkan median untuk setiap grup dalam permintaan grup-oleh (inilah yang saya butuhkan!):

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

Ini berfungsi karena penggunaan cerdas dari group_concat dan substring_index.

Tapi, untuk mengizinkan group_concat besar, Anda harus mengatur group_concat_max_len ke nilai yang lebih tinggi (1024 char secara default). Anda dapat mengaturnya seperti itu (untuk sesi sql saat ini):

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

Lebih banyak info untuk group_concat_max_len: https://dev.mysql.com/doc/refman/5.1/en/server-system-variables.html#sysvar_group_concat_max_len

didier2l
sumber
2

Riff lain pada jawaban Velcrow, tetapi menggunakan tabel perantara tunggal dan mengambil keuntungan dari variabel yang digunakan untuk penomoran baris untuk mendapatkan hitungan, daripada melakukan kueri tambahan untuk menghitungnya. Juga memulai penghitungan sehingga baris pertama adalah baris 0 untuk memungkinkan cukup menggunakan Lantai dan Ceil untuk memilih baris median.

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));
Steve Cohen
sumber
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

Di atas sepertinya bekerja untuk saya.

Nochum Sossonko
sumber
Itu tidak mengembalikan median yang benar untuk jumlah nilai genap, Misalnya, median {98,102,102,98}adalah 100tetapi kode Anda berikan 102. Ini bekerja dengan baik untuk angka ganjil.
Nomiluks
1

Saya menggunakan pendekatan dua permintaan:

  • yang pertama mendapatkan hitungan, min, maks, dan rata-rata
  • yang kedua (pernyataan yang disiapkan) dengan klausa "LIMIT @ count / 2, 1" dan "ORDER BY .." untuk mendapatkan nilai median

Ini dibungkus dengan fungsi defn, sehingga semua nilai dapat dikembalikan dari satu panggilan.

Jika rentang Anda statis dan data Anda tidak sering berubah, mungkin lebih efisien untuk melakukan precompute / menyimpan nilai-nilai ini dan menggunakan nilai yang disimpan daripada meminta kueri dari awal setiap kali.

btk
sumber
1

karena saya hanya membutuhkan solusi median DAN persentil, saya membuat fungsi sederhana dan cukup fleksibel berdasarkan temuan di utas ini. Saya tahu bahwa saya sendiri bahagia jika saya menemukan fungsi "readymade" yang mudah dimasukkan dalam proyek saya, jadi saya memutuskan untuk segera membagikan:

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

}

Penggunaannya sangat mudah, contoh dari proyek saya saat ini:

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

Ini jalan saya. Tentu saja, Anda bisa memasukkannya ke dalam prosedur :-)

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;

Anda dapat menghindari variabel @median_counter, jika Anda menambahkannya :

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;
pucawo
sumber
1

Cara ini tampaknya termasuk hitungan genap dan ganjil tanpa subquery.

SELECT AVG(t1.x)
FROM table t1, table t2
GROUP BY t1.x
HAVING SUM(SIGN(t1.x - t2.x)) = 0
yuhanluo
sumber
Bisakah Anda tahu apa tabel t2?
xliiv
1

Berdasarkan jawaban @ bob, ini menggeneralisasikan kueri untuk memiliki kemampuan untuk mengembalikan beberapa median, dikelompokkan berdasarkan beberapa kriteria.

Pikirkan, misalnya, harga jual rata-rata untuk mobil bekas di tempat parkir, dikelompokkan berdasarkan tahun-bulan.

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;
Ariel Allon
sumber
1

Seringkali, kita mungkin perlu menghitung Median tidak hanya untuk seluruh tabel, tetapi untuk agregat sehubungan dengan ID kami. Dengan kata lain, hitung median untuk setiap ID di tabel kami, di mana setiap ID memiliki banyak catatan. (Kinerja bagus dan berfungsi di banyak SQL + memperbaiki masalah genap dan ganjil, lebih lanjut tentang kinerja berbagai metode Median 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;

Semoga ini bisa membantu

Danylo Zherebetskyy
sumber
Itu adalah solusi terbaik. Namun, untuk set data besar, ia akan melambat karena menghitung ulang untuk setiap item dalam setiap set. Untuk membuatnya lebih cepat, masukkan "COUNT (*)" ke sub-kueri yang terpisah.
Slava Murygin
1

MySQL telah mendukung fungsi-fungsi jendela sejak versi 8.0, Anda dapat menggunakan ROW_NUMBERatau DENSE_RANK( JANGAN menggunakannya RANKkarena memberikan peringkat yang sama ke nilai yang sama, seperti dalam peringkat olahraga):

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));
rhanqtl
sumber
0

Jika MySQL memiliki ROW_NUMBER, maka MEDIAN adalah (terinspirasi oleh permintaan SQL Server ini):

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 digunakan jika Anda memiliki jumlah entri yang genap.

Jika Anda ingin menemukan median per grup, maka cukup PARTITION BY grup di OVER klausa Anda.

rampok

Rob Farley
sumber
1
Tidak, tidak ROW_NUMBER OVER, tidak PARTISI DENGAN, tidak ada itu; ini MySql, bukan mesin DB nyata seperti PostgreSQL, IBM DB2, MS SQL Server, dan sebagainya ;-).
Alex Martelli
0

Setelah membaca semua yang sebelumnya mereka tidak cocok dengan persyaratan saya yang sebenarnya, jadi saya menerapkannya sendiri yang tidak memerlukan prosedur atau pernyataan yang rumit, hanya saja saya GROUP_CONCAT semua nilai dari kolom saya ingin mendapatkan MEDIAN dan menerapkan COUNT DIV BY 2 Saya mengekstrak nilai dari tengah daftar seperti yang dilakukan oleh kueri berikut:

(POS adalah nama kolom yang ingin saya dapatkan mediannya)

(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

Saya harap ini dapat bermanfaat bagi seseorang dalam cara banyak komentar lain bagi saya dari situs web ini.

Gabriel G.
sumber
0

Mengetahui jumlah baris yang tepat Anda dapat menggunakan kueri ini:

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

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

ZhekaKozlov
sumber
0

Saya memiliki database yang berisi sekitar 1 miliar baris yang kami perlukan untuk menentukan usia rata-rata di set. Menyortir satu miliar baris sulit, tetapi jika Anda mengumpulkan nilai berbeda yang dapat ditemukan (rentang usia 0 hingga 100), Anda dapat mengurutkan daftar INI, dan menggunakan beberapa sihir aritmatika untuk menemukan persentil yang Anda inginkan sebagai berikut:

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
;

Kueri ini tergantung pada fungsi jendela pendukung db Anda (termasuk ROWS UNBOUNDED PRECEDING) tetapi jika Anda tidak memilikinya, bergabunglah dengan agData CTE dengan mudah dan agregat semua total sebelumnya ke dalam kolom 'akumulasi' yang digunakan untuk menentukan mana nilai berisi precentile yang ditentukan. Sampel di atas menghitung p10, p25, p50 (median), p75, dan p90.

-Chris

Chris Knoll
sumber
0

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

Saya akan menyarankan cara lain, tanpa bergabung , tetapi bekerja dengan string

saya tidak memeriksanya dengan tabel dengan data besar, tetapi tabel kecil / sedang berfungsi dengan baik.

Hal yang baik di sini, bahwa ia bekerja juga dengan MENGELOLA sehingga dapat mengembalikan median untuk beberapa item.

di sini adalah kode tes untuk tabel tes:

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 

dan kode untuk menemukan median untuk setiap grup:

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

Keluaran:

grp | the_median| all_vals_for_debug
bike| 22        | 22,26
book| 4         | 2,2,3,4,7,8,9
note| 11        | 11
mr.baby123
sumber
Tidakkah Anda berpikir median `{22,26}` seharusnya 24?
Nomiluks
0

Dalam beberapa kasus median dihitung sebagai berikut:

"Median" adalah nilai "tengah" dalam daftar angka ketika mereka dipesan berdasarkan nilai. Untuk set hitung genap, median adalah rata-rata dari dua nilai tengah . Saya telah membuat kode sederhana untuk itu:

$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 yang dikembalikan akan menjadi hasil yang diperlukan :-)

jitendrapurohit
sumber