次の表_t1
_があるとします。
================= |タグ| val | -+簡略化のため、valはNULL以外です ================= | a1 | v1 | | a1 | v2 | | a1 | v3 | | a1 | v4 | | a1 | v5 | | a2 | v6 | | a2 | v7 | | a2 | v8 | | a2 | v9 | | ... | ... | =================
MySQLで以下のスクリプトを実行すると、
_SELECT `tag`, AVG(`val`) FROM `t1` GROUP BY `tag`
_
列tag
でグループ化された平均値を取得します。
================= |タグ| AVG()| ================= | a1 | avg1 | | a2 | avg2 | | a3 | avg3 | | a4 | avg4 | | ... | ... | =================
MySQLには、AVG()
の他に、集計値を計算するための組み込み関数がいくつかあります(例:SUM()
、MAX()
、COUNT()
、およびSTD()
)前述のスクリプトと同じ方法で使用できます。ただし、medianの組み込み関数はありません。
この問題はすでにSEで何度か発生しています。ただし、それらのほとんどは_GROUP BY
_のないテーブルに関連しています。 _GROUP BY
_のあるものは MySql:Count median grouped by day のようです。ただし、スクリプトは複雑すぎるようです。
この中央値を計算する簡単で簡単な方法(可能な場合)は何ですか?
受け入れられた答えを補完する優れた記事:
http://danielsetzermann.com/howto/how-to-calculate-the-median-per-group-with-mysql/
このクエリはあなたの質問に答えることができます:medianvalue andgroup by
SELECT tag, AVG(val) as median
FROM
(
SELECT tag, val,
(SELECT count(*) FROM median t2 WHERE t2.tag = t3.tag) as ct,
seq,
(SELECT count(*) FROM median t2 WHERE t2.tag < t3.tag) as delta
FROM (SELECT tag, val, @rownum := @rownum + 1 as seq
FROM (SELECT * FROM median ORDER BY tag, val) t1
ORDER BY tag, seq
) t3 CROSS JOIN (SELECT @rownum := 0) x
HAVING (ct%2 = 0 and seq-delta between floor((ct+1)/2) and floor((ct+1)/2) +1)
or (ct%2 <> 0 and seq-delta = (ct+1)/2)
) T
GROUP BY tag
ORDER BY tag;
私はこのデータセットで試しました(主に here から):
+------+------+
| tag | val |
+------+------+
| 1 | 3 |
| 1 | 13 |
...(以下の説明を参照)
| 3 | 12 |
| 3 | 43 |
| 3 | 15 |
+------+------+
そして結果は:
+------+---------+
| tag | median |
+------+---------+
| 1 | 23.0000 |
| 2 | 22.0000 |
| 3 | 15.0000 |
+------+---------+
内部サブクエリが最初に計算されます。シーケンスは(1)(2)(3)(4)です。
-(4)(2行または1行の)平均を計算します
SELECT tag, AVG(val) as median
FROM
(
-(3)中央値を計算するための線を取得します
SELECT tag, val,
(SELECT count(*) FROM median t2 -- +number of lines for the current tag value as ct
WHERE t2.tag = t3.tag) as ct,
seq,
(SELECT count(*) FROM median t2 -- +number of lines before the current tag value as delta
WHERE t2.tag < t3.tag) as delta -- to compute the starting line number of a tag
FROM (
-(2)タグとシーケンスでデータセットをソートする
SELECT tag, val,
@rownum := @rownum + 1 as seq -- +@rownum enable to create a sequence from 0 by 1
FROM (
-(1)タグと値でデータセットをソート
SELECT * FROM median
ORDER BY tag, val) t1
-(2)ここに続く
ORDER BY tag, seq
) t3 CROSS JOIN (SELECT @rownum := 0) x -- +use to set @rownum to 0 (no data)
-(3)ここに続く
HAVING (ct%2 = 0 -- +when ct is even, select the two lines around the middle
and seq-delta between floor((ct+1)/2)
and floor((ct+1)/2) +1)
or (ct%2 <> 0 -- +when ct is odd, select the one line in the middle
and seq-delta = (floor(ct+1)/2))
) T
-(4)ここに続く
GROUP BY tag
ORDER BY tag;
データセット:
after (1) after (2) processing (3)
+------+------+
| tag | val | ct delta seq seq-delta
+------+------+
| 1 | 3 | 15 0 1 1 ct : odd ct%2 <> 0
| 1 | 5 | 15 0 2 2 floor((ct+1)/2) : 8
| 1 | 7 | 15 0 3 3
| 1 | 12 | 15 0 4 4
| 1 | 13 | 15 0 5 5
| 1 | 14 | 15 0 6 6
| 1 | 21 | 15 0 7 7
| 1 | 23 | 15 0 8 8 ---> keep this line
| 1 | 23 | 15 0 9 9
| 1 | 23 | 15 0 10 10
| 1 | 23 | 15 0 11 11
| 1 | 29 | 15 0 12 12
| 1 | 39 | 15 0 13 13
| 1 | 40 | 15 0 14 14
| 1 | 56 | 15 0 15 15
| 2 | 3 | 14 15 16 1 ct : even (ct%2 = 0 )
| 2 | 5 | 14 15 17 2 floor((ct+1)/2) : 7
| 2 | 7 | 14 15 18 3 floor((ct+1)/2)+1 : 8
| 2 | 12 | 14 15 19 4
| 2 | 13 | 14 15 20 5
| 2 | 14 | 14 15 21 6
| 2 | 21 | 14 15 22 7 ---> keep this line
| 2 | 23 | 14 15 23 8 ---> keep this line
| 2 | 23 | 14 15 24 9
| 2 | 23 | 14 15 25 10
| 2 | 23 | 14 15 26 11
| 2 | 29 | 14 15 27 12
| 2 | 40 | 14 15 28 13
| 2 | 56 | 14 15 29 14
| 3 | 12 | 3 29 30 1 ct : odd ct%2 <> 0
| 3 | 15 | 3 29 31 2 ---> keep floor((ct+1)/2) : 2
| 3 | 43 | 3 29 32 3
+------+------+
(3)の後のデータセット
+------+------+------+------+-------+
| tag | val | ct | seq | delta |
+------+------+------+------+-------+
| 1 | 23 | 15 | 8 | 0 |
| 2 | 21 | 14 | 22 | 15 |
| 2 | 23 | 14 | 23 | 15 |
| 3 | 15 | 3 | 31 | 29 |
+------+------+------+------+-------+
外部クエリは、タグ値によってavg(val)グループを計算します。
お役に立てれば。
しかし、null値がある場合の中央値計算についてはどうでしょうか?以下のEDIT2を参照してください
DELIMITER //
CREATE FUNCTION median(pTag int)
RETURNS real
READS SQL DATA
DETERMINISTIC
BEGIN
DECLARE r real; -- result
SELECT AVG(val) INTO r
FROM
(
SELECT val,
(SELECT count(*) FROM median WHERE tag = pTag) as ct,
seq
FROM (SELECT val, @rownum := @rownum + 1 as seq
FROM (SELECT * FROM median WHERE tag = pTag ORDER BY val ) t1
ORDER BY seq
) t3
CROSS JOIN (SELECT @rownum := 0) x
HAVING (ct%2 = 0 and seq between floor((ct+1)/2) and floor((ct+1)/2) +1)
or (ct%2 <> 0 and seq = (ct+1)/2)
) T;
return r;
END//
DELIMITER ;
しかし、関数は行ごとに呼び出されます。
SELECT tag, median(tag) FROM median; -- my test table is 'median' too...
このクエリは「より良い」でしょう:
select tag, median(tag)
from (select distinct tag from median) t;
私ができることはそれだけです!それが役に立てば幸い!
行をカウントする2つのサブクエリとデータを取得するサブクエリの両方で、WHERE句:WHERE val IS NOT NULL
を使用して、null値がソースデータから省略されていることを示します。
クエリの実行で最も早く宣言できるように、最も深いレベルに置く必要があります。
DELIMITER //
CREATE FUNCTION median(pTag int)
RETURNS real
READS SQL DATA
DETERMINISTIC
BEGIN
DECLARE r real; -- result
SELECT AVG(val) INTO r
FROM
(
SELECT val,
(SELECT count(*) FROM median WHERE tag = pTag and val is not null) as ct,
seq
FROM (SELECT val, @rownum := @rownum + 1 as seq
FROM (SELECT * FROM median
CROSS JOIN (SELECT @rownum := 0) x -- INIT @rownum here
WHERE tag = pTag and val is not null ORDER BY val
) t1
ORDER BY seq
) t3
HAVING (ct%2 = 0 and seq between floor((ct+1)/2) and floor((ct+1)/2) +1)
or (ct%2 <> 0 and seq = (ct+1)/2)
) T;
return r;
END//
DELIMITER ;
これはクエリでも同じです。
さらに2つのデータセットでテストする:
| 4 | NULL |
| 4 | 10 |
| 4 | 15 |
| 4 | 20 |
| 5 | NULL |
| 5 | NULL |
| 5 | NULL |
+------+------+
39行セット(0.00秒)
+------+--------------+
| tag | median2(tag) |
+------+--------------+
| 1 | 23 |
| 2 | 22 |
| 3 | 15 |
| 4 | 15 |
| 5 | NULL |
+------+--------------+
5 rows in set (0.08 sec)
ここに質問に関する優れた記事があります: http://danielsetzermann.com/howto/how-to-calculate-the-median-per-group-with-mysql/
記録のためだけに...
@ PatrickDezecacheの受け入れられた回答に基づいて(マイナーな変更を加えて)、MySQLサーバーで使用しているコードは次のとおりです。
SELECT `tag`, avg(`val`) as `median`
FROM
(
SELECT
`tag`,
`val`,
(SELECT count(*) FROM `t1` `t2` WHERE `t2`.`tag` = `t3`.`tag`) as `ct`,
`seq`,
(SELECT count(*) FROM `t1` `t2` WHERE `t2`.`tag` < `t3`.`tag`) as `delta`
FROM
(SELECT `tag`, `val`, @rownum := @rownum + 1 as `seq`
FROM (SELECT `tag`, `val` FROM `t1` ORDER BY `tag`, `val`) as `t2`
CROSS JOIN (SELECT @rownum := 0) as `x`
ORDER BY `tag`, `seq`) as `t3`
HAVING
(`ct`%2 = 0 and `seq`-`delta` between floor((`ct`+1)/2) and floor((`ct`+1)/2) +1)
or (`ct`%2 <> 0 and `seq`-`delta` = (`ct`+1)/2)
) as `t`
GROUP BY `tag`
ORDER BY `tag`;