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, इसलिए माध्यक होना चाहिए 4, SELECT AVG(val)जो कि == 5


71
क्या मैं केवल इस तथ्य से रुका हुआ हूं कि MySQL के पास माध्यिका की गणना करने का कोई कार्य नहीं है? हास्यास्पद।
मोनिका हेडडेक

3
MariaDB संस्करण 10.3 के बाद से एक है, देखें 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) );

स्टीव कोहेन बताते हैं, कि पहली पास के बाद, @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+ में एक मीडिया फ़ंक्शन होता है


4
समूह मान दिखाने के लिए इसे बनाने का कोई तरीका? जैसे: जगह / उस जगह के लिए मंझला ... चुनिंदा जगह की तरह, मेज से मध्ययुगीन ... कोई रास्ता? धन्यवाद
saulob

2
@rowNum के निष्पादन के अंत में 'कुल गणना' होगी। इसलिए आप इसका उपयोग कर सकते हैं कि यदि आप फिर से 'सभी को गिनने' से बचना चाहते हैं (जो मेरा मामला था क्योंकि मेरी क्वेरी इतनी आसान नहीं थी)
अहमद-अनस

एक कथन होने का तर्क: (मंजिल ((total_rows + 1) / 2), floor ((total_rows + 2) / 2)) मंझला के लिए आवश्यक पंक्तियों की गणना भयानक है! यकीन नहीं होता कि आपने ऐसा कैसे सोचा, लेकिन यह शानदार है। मैं जिस भाग का अनुसरण नहीं करता हूं वह है (SELECT @rownum: = 0) r - यह किस उद्देश्य से कार्य करता है?
शमनीिस्टर

पहले 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))
डैनियल बकमास्टर

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

सुनिश्चित करें कि आपके कॉलम अच्छी तरह से अनुक्रमित हैं और सूचकांक का उपयोग फ़िल्टरिंग और सॉर्टिंग के लिए किया जाता है। व्याख्या योजनाओं के साथ सत्यापित करें।

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
@ क्या आप कृपया संपादन में मदद कर सकते हैं? या मुझे बस वेल्क्रो समाधान के लिए झुकना चाहिए? (वास्तव में कैसे एक और समाधान के लिए स्थगित करने के लिए निश्चित नहीं) धन्यवाद, याकूब
TheJacobTaylor

1
ध्यान दें कि यह एक "क्रॉस जॉइन" करता है, जो बड़ी तालिकाओं के लिए बहुत धीमा है।
रिक जेम्स

1
यह उत्तर पंक्तियों की संख्या के लिए भी कुछ नहीं देता है
कुट्टुमायह

यह उत्तर कुछ डेटा सेटों के लिए बिल्कुल भी काम नहीं करता है, उदाहरण के लिए, 0.1, 0.1, 0.1, 2 - मानों के साथ सेट किए गए तुच्छ डेटा - यह काम करेगा यदि सभी मान अलग-अलग हैं, लेकिन केवल तभी काम करता है जब मान
केएम मेसन

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
बिल्कुल सही, मेरी अनुक्रमित तालिकाओं पर पूरी तरह से और बहुत तेजी से काम करता है
रोब

2
यह सब जवाबों से बाहर mysql पर सबसे तेज़ समाधान प्रतीत होता है, 200 मिलियन टेबल में सिर्फ एक लाख रिकॉर्ड के साथ
रोब

3
@FrankConijn: यह एक तालिका से दो बार चयन करता है। तालिका का नाम है dataऔर इसका उपयोग दो नामों के साथ किया जा रहा है, xऔर y
ब्रायन

3
सिर्फ यह कहते हुए कि मैंने अपने mysqld को 33k पंक्तियों वाली तालिका पर इस सटीक क्वेरी के साथ रोक दिया ...
Xenonite

1
यह क्वेरी पंक्तियों की संख्या के लिए भी गलत उत्तर देती है
कुटुमिया

26

दुर्भाग्य से, न तो TheJacobTaylor के और न ही वेल्क्रो के उत्तरों ने MySQL के वर्तमान संस्करणों के लिए सटीक परिणाम दिए हैं।

ऊपर से वेल्क्रो का जवाब करीब है, लेकिन यह समान पंक्तियों के साथ परिणाम सेट के लिए सही गणना नहीं करता है। 2 या तो औसत संख्याओं पर मध्य संख्या को परिभाषित किया जाता है, या 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 1" (2 घटनाएं) को अपनी स्थिति के साथ बदलें

और, आप स्ट्रिंग के मूल्यों के मेडियन के लिए क्या करते हैं?
रिक जेम्स

12

मैं एक तेज़ तरीका प्रस्तावित करता हूँ।

पंक्ति गणना प्राप्त करें:

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

फिर छाँटे गए उपकेंद्र में मध्य मान लें:

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

मैंने यादृच्छिक संख्याओं के 5x10e6 डेटासेट के साथ इसका परीक्षण किया और यह 10 सेकंड के भीतर माध्यिका को खोज लेगा।


3
क्यों न करें: डेटा से वैल वैल से ORDER का चयन करें सीमा @middlevalue, 1
ब्रायन

1
आप अपने पहले कोड ब्लॉक के चर आउटपुट को अपने दूसरे कोड ब्लॉक में कैसे खींचते हैं?
ट्रिप

3
जैसे, @middlevalue कहाँ से आता है?
यात्रा

@ ब्रायन - मैं आपसे सहमत हूं, इससे मुझे बहुत ज्यादा समझ में आता है। क्या आपको कभी ऐसा करने का कारण नहीं मिला?
शेन एन

5
यह चर के रूप में काम नहीं करता है, इसका उपयोग सीमा खंड में नहीं किया जा सकता है।
कोडपेक

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 सेकंड है। मेरे लिए स्पीड फास्ट है।
मिकाल

7

स्थापित करें और इस mysql सांख्यिकीय कार्यों का उपयोग करें: http://www.xarg.org/2012/07/statutic-functions-in-mysql/

उसके बाद, माध्य की गणना करना आसान है:

SELECT median(val) FROM data;

1
मैंने इसे स्वयं आज़माया, और इसके लायक क्या है, इसे स्थापित करना सुपर फास्ट / आसान था, और इसने विज्ञापन के रूप में काम किया, जिसमें समूहीकरण शामिल है, जैसे "नाम से चयन करें, माध्य (x) F1 से t1 समूह" - github स्रोत यहां: github.com/infusion/udf_infusion
केएम मेसन

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/


जागरूक रहें: मानों की संख्या के लिए भी यह दो मध्य मानों से अधिक होता है। मूल्यों की संख्या के लिए यह मंझला के बाद अगला उच्च मूल्य लेता है।
जियोर्दानो

6

मेरे पास यह नीचे कोड है जो मुझे हैकररैंक पर मिला है और यह बहुत सरल है और प्रत्येक मामले में काम करता है।

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
मेरा मानना ​​है कि यह केवल एक तालिका के साथ काम करता है जिसमें प्रविष्टियों की संख्या विषम है। प्रविष्टियों की संख्या के लिए भी, यह एक समस्या हो सकती है।
वाई। चांग

4

वेल्क्रो के उत्तर का निर्माण, आप में से उन लोगों के लिए जो किसी अन्य पैरामीटर द्वारा समूहीकृत किए गए किसी चीज़ का एक मध्य भाग करना चाहते हैं:

Grp_field , t1 का चयन करें वैल से ( चयन grp_field , @ ROWNUM : = IF (@ रों = grp_field , @ ROWNUM + 1 , 0 ) के रूप में , @ रों : = IF (@ रों = grp_field , @ रों , grp_field ) के रूप में सेकंड , वैल
   से डेटा डी , का 
         row_number
        (चयनपंक्ति संख्या @ rownum : = 0 , @ s : = 0 ) r
   ORDER BY grp_field , d वैल
 ) के रूप में t1 शामिल हों ( चयन grp_field , गिनती (*) के रूप में TOTAL_ROWS
   से डेटा d
   ग्रुप द्वारा grp_field
 ) के रूप में t2
 पर t1 grp_field = t2 grp_field
 कहां t1     
     = मंजिल( कुल_रो / 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_CONCAT1023 वर्णों तक सीमित है, तब भी जब इस तरह के एक अन्य फ़ंक्शन के अंदर उपयोग किया जाता है।
रॉब वैन डैम

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 NULL NULL कहाँ पर स्थित करें।
जिओरडानो

1
@giordano कोड की किस पंक्ति में x IS NOT NULLजोड़ा जाना चाहिए?
प्रेज़मिसलाव रिमिन

1
@PrzemyslawRemin क्षमा करें, मैं अपने बयान में स्पष्ट नहीं था और मुझे अब महसूस हुआ कि सपा पहले से ही लापता मूल्यों के मामले पर विचार करती है। सपा को इस तरह से बुलाया जाना चाहिए: CALL median("table","x","x IS NOT NULL")
जिओरडनो

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 को उच्च मान (डिफ़ॉल्ट रूप से 1024 वर्ण) पर सेट करना होगा। आप इसे इस तरह सेट कर सकते हैं (वर्तमान एसक्यूएल सत्र के लिए):

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

वेल्क्रो के जवाब पर एक और दरार, लेकिन एक एकल मध्यवर्ती तालिका का उपयोग करता है और गणना करने के लिए एक अतिरिक्त क्वेरी करने के बजाय, गिनती प्राप्त करने के लिए पंक्ति संख्या के लिए उपयोग किए गए चर का लाभ उठाता है। गिनती भी शुरू करता है ताकि पहली पंक्ति पंक्ति 0 हो, ताकि मंझला पंक्ति (ओं) का चयन करने के लिए केवल फर्श और छत का उपयोग किया जा सके।

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}, 100लेकिन आपका कोड देता है 102। इसने विषम संख्या में काम किया।
नॉमिलुक्स

1

मैंने दो क्वेरी दृष्टिकोण का उपयोग किया:

  • गिनती, न्यूनतम, अधिकतम और औसत प्राप्त करने के लिए पहला
  • "लिमिट @ काउंट / 2, 1" और "ORDER BY .." के साथ दूसरा एक (तैयार किया गया स्टेटमेंट) माध्य मान प्राप्त करने के लिए क्लॉस

ये एक फ़ंक्शन डिफेन में लिपटे हुए हैं, इसलिए सभी मूल्यों को एक कॉल से वापस किया जा सकता है।

यदि आपकी सीमाएं स्थिर हैं और आपका डेटा अक्सर नहीं बदलता है, तो इन मानों को प्री-कंपोज़ / स्टोर करना और संग्रहीत मानों का उपयोग हर बार स्क्रैच से क्वेरी करने के बजाय अधिक कुशल हो सकता है।


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

@ बॉब के उत्तर के आधार पर, यह क्वेरी को कुछ मानदंडों द्वारा समूहीकृत कई मध्यस्थों को वापस करने की क्षमता का सामान्यीकरण करता है।

सोचिए, उदाहरण के लिए, एक कार लॉट में इस्तेमाल की गई कारों के लिए औसत बिक्री मूल्य, वर्ष-महीने के हिसाब से समूहीकृत।

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

अक्सर, हमें न केवल पूरी मेज के लिए, बल्कि हमारी आईडी के संबंध में समुच्चय के लिए मेडियन की गणना करने की आवश्यकता हो सकती है। दूसरे शब्दों में, हमारी तालिका में प्रत्येक आईडी के लिए माध्यिका की गणना करें, जहां प्रत्येक आईडी के कई रिकॉर्ड हैं। (अच्छा प्रदर्शन और कई एसक्यूएल में काम करता है + सम और बाधाओं की समस्या को हल करता है, विभिन्न मेडियन-तरीकों के प्रदर्शन के बारे में 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 (*)" डालें।
स्लाव मृगिन

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 ​​सर्वर क्वेरी से प्रेरित हो):

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 क्लॉस में केवल समूह द्वारा भाग दें।

लूटना


1
नहीं, नहीं ROW_NUMBER OVER, कोई भी विभाजन नहीं है; यह MySql है, PostgreSQL, IBM DB2, MS SQL सर्वर, और इसके आगे ;-) जैसे वास्तविक DB इंजन नहीं।
एलेक्स मार्टेली

0

पिछले सभी को पढ़ने के बाद वे मेरी वास्तविक आवश्यकता के साथ मेल नहीं खाते थे, इसलिए मैंने अपना एक लागू किया, जिसे किसी भी प्रक्रिया की आवश्यकता नहीं है या केवल बयानों को जटिल करना GROUP_CONCAT उस कॉलम से सभी मूल्यों को प्राप्त करना चाहता हूं जो मैं प्राप्त करना चाहता था और एक COINT DIV द्वारा आवेदन करना चाहता था। 2 मैं सूची के बीच से मूल्य निकालता हूं जैसे कि निम्नलिखित क्वेरी करता है:

(पीओएस उस कॉलम का नाम है जिसे मैं उसका माध्य प्राप्त करना चाहता हूं)

(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

मेरे पास लगभग 1 बिलियन पंक्तियों वाला एक डेटाबेस है जिसे हमें सेट में औसत आयु निर्धारित करने की आवश्यकता है। एक अरब पंक्तियों को क्रमबद्ध करना कठिन है, लेकिन यदि आप उन भिन्न मूल्यों को एकत्र करते हैं, जिन्हें पाया जा सकता है (आयु सीमा 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 को ज्वाइन करें और सभी पूर्व कुल योगों को 'संचित' कॉलम में एकत्रित करें जो यह निर्धारित करने के लिए उपयोग किया जाता है कि मान में निर्दिष्ट प्रीसेंटाइल होता है। उपरोक्त नमूना p10, p25, p50 (माध्यिका), p75 और p90 को शांत करता है।

-क्रिस


0

से लिया गया: http://mdb-blog.blogspot.com/2015/06/mysql-find-median-nth-element-without.html

मैं एक और तरीका सुझाऊंगा, बिना शामिल हुए , लेकिन तार के साथ काम करना

मैंने इसे बड़े डेटा वाले तालिकाओं के साथ नहीं जांचा, लेकिन छोटे / मध्यम तालिकाओं में यह ठीक काम करता है।

यहाँ अच्छी बात यह है कि यह ग्रुपिंग के द्वारा भी काम करता है ताकि यह कई वस्तुओं के लिए माध्य वापस कर सके।

यहाँ परीक्षण तालिका के लिए परीक्षण कोड है:

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 होना चाहिए?
नॉमिलुक्स

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;

$ मंझला लौटा आवश्यक परिणाम होगा :-)

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.