質問するログイン新規登録

Q&A

解決済

1回答

437閲覧

平均の平均を求めたい。

hotta

総合スコア1615

SQL

SQL(Structured Query Language)は、リレーショナルデータベース管理システム (RDBMS)のデータベース言語です。大きく分けて、データ定義言語(DDL)、データ操作言語(DML)、データ制御言語(DCL)の3つで構成されており、プログラム上でSQL文を生成して、RDBMSに命令を出し、RDBに必要なデータを格納できます。また、格納したデータを引き出すことも可能です。

0グッド

1クリップ

投稿2025/02/26 01:02

0

1

実現したいこと

以下のようなテーブルがあります。

SQL

1DROP TABLE IF EXISTS `logs`; 2CREATE TABLE `logs` ( 3 `task_id` int(11) NOT NULL, 4 `mac` varchar(17) NOT NULL, 5 `collection_time` timestamp NOT NULL DEFAULT current_timestamp(), 6 `mv` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`mv`)), 7 `response` int(11) NOT NULL DEFAULT 0, 8 PRIMARY KEY (`task_id`,`mac`,`collection_time`) 9) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; 10

SQL

1INSERT INTO `logs` VALUES 2(1,'a','2025-02-20 00:00:00','{"20":1}',1), 3(1,'b','2025-02-20 00:00:00','{"20":2, "25":3}', 1), 4(1,'c','2025-02-20 00:00:00','{"20":4, "25":5, "30":6}', 1), 5(1,'d','2025-02-20 00:00:00','{"20":7, "25":8, "30":9, "35":10}', 1), 6(1,'a','2025-02-20 00:01:00','{"20":11}',1), 7(1,'b','2025-02-20 00:01:00','{"20":12, "25":13}', 1), 8(1,'c','2025-02-20 00:01:00','{"20":14, "25":15, "30":16}', 1), 9(1,'d','2025-02-20 00:01:00','{"20":17, "25":18, "30":19, "35":20}', 1); 10

SQL

1SELECT collection_time, mac, mv FROM logs ORDER BY collection_time, mac;

+---------------------+-----+------------------------------------------+
| collection_time | mac | mv |
+---------------------+-----+------------------------------------------+
| 2025-02-20 00:00:00 | a | {"20":1} |
| 2025-02-20 00:00:00 | b | {"20":2, "25":3} |
| 2025-02-20 00:00:00 | c | {"20":4, "25":5, "30":6} |
| 2025-02-20 00:00:00 | d | {"20":7, "25":8, "30":9, "35":10} |
| 2025-02-20 00:01:00 | a | {"20":11} |
| 2025-02-20 00:01:00 | b | {"20":12, "25":13} |
| 2025-02-20 00:01:00 | c | {"20":14, "25":15, "30":16} |
| 2025-02-20 00:01:00 | d | {"20":17, "25":18, "30":19, "35":20} |
+---------------------+-----+------------------------------------------+

このテーブルに対してクエリーを行い、以下のような結果を得たいと思います。

SQL

1SELECT 2 collection_time, 3 LEAST( 4 MIN(CASE WHEN JSON_CONTAINS_PATH(mv, 'one', '$."20"') = 0 THEN 99999 5 ELSE CAST(JSON_UNQUOTE(JSON_EXTRACT(mv, '$."20"')) AS DECIMAL(10, 2)) END) 6, MIN(CASE WHEN JSON_CONTAINS_PATH(mv, 'one', '$."25"') = 0 THEN 99999 7 ELSE CAST(JSON_UNQUOTE(JSON_EXTRACT(mv, '$."25"')) AS DECIMAL(10, 2)) END) 8, MIN(CASE WHEN JSON_CONTAINS_PATH(mv, 'one', '$."30"') = 0 THEN 99999 9 ELSE CAST(JSON_UNQUOTE(JSON_EXTRACT(mv, '$."30"')) AS DECIMAL(10, 2)) END) 10, MIN(CASE WHEN JSON_CONTAINS_PATH(mv, 'one', '$."35"') = 0 THEN 99999 11 ELSE CAST(JSON_UNQUOTE(JSON_EXTRACT(mv, '$."35"')) AS DECIMAL(10, 2)) END) 12 ) as min, 13 GREATEST( 14 MAX(CASE WHEN JSON_CONTAINS_PATH(mv, 'one', '$."20"') = 0 THEN -99999 15 ELSE CAST(JSON_UNQUOTE(JSON_EXTRACT(mv, '$."20"')) AS DECIMAL(10, 2)) END) 16, MAX(CASE WHEN JSON_CONTAINS_PATH(mv, 'one', '$."25"') = 0 THEN -99999 17 ELSE CAST(JSON_UNQUOTE(JSON_EXTRACT(mv, '$."25"')) AS DECIMAL(10, 2)) END) 18, MAX(CASE WHEN JSON_CONTAINS_PATH(mv, 'one', '$."30"') = 0 THEN -99999 19 ELSE CAST(JSON_UNQUOTE(JSON_EXTRACT(mv, '$."30"')) AS DECIMAL(10, 2)) END) 20, MAX(CASE WHEN JSON_CONTAINS_PATH(mv, 'one', '$."35"') = 0 THEN -99999 21 ELSE CAST(JSON_UNQUOTE(JSON_EXTRACT(mv, '$."35"')) AS DECIMAL(10, 2)) END) 22 ) AS max, 23 '???' AS avg 24FROM logs 25GROUP BY collection_time;

+---------------------+-------+-------+-----+
| collection_time | min | max | avg |
+---------------------+-------+-------+-----+
| 2025-02-20 00:00:00 | 1.00 | 10.00 | ??? |
| 2025-02-20 00:01:00 | 11.00 | 20.00 | ??? |
+---------------------+-------+-------+-----+

この、avg の取得方法についてご教示ください。
avg の計算方法は以下の通りです。

SQL

1SELECT 2 collection_time, mac, 3 CASE WHEN JSON_CONTAINS_PATH(mv, 'one', '$.20') = 1 4 THEN JSON_EXTRACT(mv, '$.20') ELSE '' END + 5 CASE WHEN JSON_CONTAINS_PATH(mv, 'one', '$.25') = 1 6 THEN JSON_EXTRACT(mv, '$.25') ELSE '' END + 7 CASE WHEN JSON_CONTAINS_PATH(mv, 'one', '$.30') = 1 8 THEN JSON_EXTRACT(mv, '$.30') ELSE '' END + 9 CASE WHEN JSON_CONTAINS_PATH(mv, 'one', '$.35') = 1 10 THEN JSON_EXTRACT(mv, '$.35') ELSE '' END AS sum, 11 (JSON_CONTAINS_PATH(mv, 'one', '$.20')) + 12 (JSON_CONTAINS_PATH(mv, 'one', '$.25')) + 13 (JSON_CONTAINS_PATH(mv, 'one', '$.30')) + 14 (JSON_CONTAINS_PATH(mv, 'one', '$.35')) AS count, 15 ( 16 CASE WHEN JSON_CONTAINS_PATH(mv, 'one', '$.20') = 1 17 THEN JSON_EXTRACT(mv, '$.20') ELSE '' END + 18 CASE WHEN JSON_CONTAINS_PATH(mv, 'one', '$.25') = 1 19 THEN JSON_EXTRACT(mv, '$.25') ELSE '' END + 20 CASE WHEN JSON_CONTAINS_PATH(mv, 'one', '$.30') = 1 21 THEN JSON_EXTRACT(mv, '$.30') ELSE '' END + 22 CASE WHEN JSON_CONTAINS_PATH(mv, 'one', '$.35') = 1 23 THEN JSON_EXTRACT(mv, '$.35') ELSE '' END 24 ) / ( 25 (JSON_CONTAINS_PATH(mv, 'one', '$.20')) + 26 (JSON_CONTAINS_PATH(mv, 'one', '$.25')) + 27 (JSON_CONTAINS_PATH(mv, 'one', '$.30')) + 28 (JSON_CONTAINS_PATH(mv, 'one', '$.35')) 29 ) AS avg 30FROM logs 31ORDER BY collection_time, mac 32;

+---------------------+-----+------+-------+------+
| collection_time | mac | sum | count | avg |
+---------------------+-----+------+-------+------+
| 2025-02-20 00:00:00 | a | 1 | 1 | 1 |
| 2025-02-20 00:00:00 | b | 5 | 2 | 2.5 |
| 2025-02-20 00:00:00 | c | 15 | 3 | 5 |
| 2025-02-20 00:00:00 | d | 34 | 4 | 8.5 |
| 2025-02-20 00:01:00 | a | 11 | 1 | 11 |
| 2025-02-20 00:01:00 | b | 25 | 2 | 12.5 |
| 2025-02-20 00:01:00 | c | 45 | 3 | 15 |
| 2025-02-20 00:01:00 | d | 74 | 4 | 18.5 |
+---------------------+-----+------+-------+------+

1つの collection_time に対して、avg が mac の個数分作られています。
で、最終的に求めたい avg は、 (1 + 2.5 + 5 + 8.5 ) / 4 = 4.25 と (11 + 12.5 + 15 + 17.5) / 4 = 14 となります。

以上、よろしくお願いします。

発生している問題・分からないこと

平均の平均を求める方法を知りたい。

該当のソースコード

特になし

試したこと・調べたこと

  • teratailやGoogle等で検索した
  • ソースコードを自分なりに変更した
  • 知人に聞いた
  • その他
上記の詳細・結果

同様の質問を見つけることができなかった。

補足

テーブルはかなり省略して書いてありますが、実際にはJSONフィールドにキーが200以上あります。
レコード数が非常に多いため(JSONにしても、論理的には1億行オーバー)、このような構成になっています。データベースはMariaDB-10.11.9です。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

yambejp

2025/02/26 01:14 編集

レコード数が多いのであれば集計に不利なJSON型での管理はやめたほうがよいかと 1億行となると更に特殊なチューニングが必要でしょう
hotta

2025/02/26 01:45

時系列で取得するデータ自体が割と頻繁に変わるので、スキーマの変更が不要なこの形に落ち着いています。同じシステムの別テーブルでは10億行オーバーもありますが、パーティショニング等でどうにか動いています。
yambejp

2025/02/26 01:52 編集

基本的にログデータは検索さえできればいいのでJSON型でも構いませんが集計には向いていません。 集計を前提とするなら定期的に必要な集計データした状態で別途専用のテーブルに流し込んでおき、リアルタイムでの集計は諦めたほうがよいでしょう。(数万件であればリアルタイム処理も可能でしょうけど)
utm.

2025/02/26 02:38

計算方法は以下を参考にしました。 > 1つの collection_time に対して、avg が mac の個数分作られています。 > で、最終的に求めたい avg は、 (1 + 2.5 + 5 + 8.5 ) / 4 = 4.25 と (11 + 12.5 + 15 + 17.5) / 4 = 14 となります。 以下の方法ではだめという質問趣旨ですか? DROP TABLE IF EXISTS `logs`; CREATE TABLE `logs` ( `task_id` int(11) NOT NULL, `mac` varchar(17) NOT NULL, `collection_time` timestamp NOT NULL DEFAULT current_timestamp(), `mv` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`mv`)), `response` int(11) NOT NULL DEFAULT 0, PRIMARY KEY (`task_id`,`mac`,`collection_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; INSERT INTO `logs` VALUES (1,'a','2025-02-20 00:00:00','{"20":1}',1), (1,'b','2025-02-20 00:00:00','{"20":2, "25":3}', 1), (1,'c','2025-02-20 00:00:00','{"20":4, "25":5, "30":6}', 1), (1,'d','2025-02-20 00:00:00','{"20":7, "25":8, "30":9, "35":10}', 1), (1,'a','2025-02-20 00:01:00','{"20":11}',1), (1,'b','2025-02-20 00:01:00','{"20":12, "25":13}', 1), (1,'c','2025-02-20 00:01:00','{"20":14, "25":15, "30":16}', 1), (1,'d','2025-02-20 00:01:00','{"20":17, "25":18, "30":19, "35":20}', 1); SELECT SUM(subquery.avg) / COUNT(subquery.avg) AS column_averages, SUM(subquery.avg) as s, COUNT(subquery.avg) as c FROM ( SELECT collection_time, mac, CASE WHEN JSON_CONTAINS_PATH(mv, 'one', '$.20') = 1 THEN JSON_EXTRACT(mv, '$.20') ELSE '' END + CASE WHEN JSON_CONTAINS_PATH(mv, 'one', '$.25') = 1 THEN JSON_EXTRACT(mv, '$.25') ELSE '' END + CASE WHEN JSON_CONTAINS_PATH(mv, 'one', '$.30') = 1 THEN JSON_EXTRACT(mv, '$.30') ELSE '' END + CASE WHEN JSON_CONTAINS_PATH(mv, 'one', '$.35') = 1 THEN JSON_EXTRACT(mv, '$.35') ELSE '' END AS sum, (JSON_CONTAINS_PATH(mv, 'one', '$.20')) + (JSON_CONTAINS_PATH(mv, 'one', '$.25')) + (JSON_CONTAINS_PATH(mv, 'one', '$.30')) + (JSON_CONTAINS_PATH(mv, 'one', '$.35')) AS count, ( CASE WHEN JSON_CONTAINS_PATH(mv, 'one', '$.20') = 1 THEN JSON_EXTRACT(mv, '$.20') ELSE '' END + CASE WHEN JSON_CONTAINS_PATH(mv, 'one', '$.25') = 1 THEN JSON_EXTRACT(mv, '$.25') ELSE '' END + CASE WHEN JSON_CONTAINS_PATH(mv, 'one', '$.30') = 1 THEN JSON_EXTRACT(mv, '$.30') ELSE '' END + CASE WHEN JSON_CONTAINS_PATH(mv, 'one', '$.35') = 1 THEN JSON_EXTRACT(mv, '$.35') ELSE '' END ) / ( (JSON_CONTAINS_PATH(mv, 'one', '$.20')) + (JSON_CONTAINS_PATH(mv, 'one', '$.25')) + (JSON_CONTAINS_PATH(mv, 'one', '$.30')) + (JSON_CONTAINS_PATH(mv, 'one', '$.35')) ) AS avg FROM logs ORDER BY collection_time, mac ) subquery GROUP BY subquery.collection_time ;
hotta

2025/02/26 02:59

はい、これで求めたい avg は合っているようです。 最終的に求めたい出力は、以下となります。 +---------------------+-------+-------+-----+ | collection_time | min | max | avg | +---------------------+-------+-------+-----+ | 2025-02-20 00:00:00 | 1.00 | 10.00 | 4.25 | | 2025-02-20 00:01:00 | 11.00 | 20.00 | 14.25 | +---------------------+-------+-------+-----+
yambejp

2025/02/26 04:00

想定するavgが私の解と違うようですが、大丈夫でしょうか? 2025-02-20 00:00:00は1から10までの平均で5.5 2025-02-20 00:01:00は11から20までの平均で15.5
yambejp

2025/02/26 05:18 編集

よく読み返してみました、平均値を平均するんですね。それってなにか意味があるのでしょうか? (一応調整版もあげてあります)
utm.

2025/02/26 06:43

丸投げかい
guest

回答1

0

ベストアンサー

一応こんな感じで

SQL

1select collection_time, 2min(value) as min, 3max(value) as max, 4avg(value) as avg 5from ( 6select collection_time,value 7 from 8( 9select 10collection_time, 11replace(group_concat(JSON_EXTRACT(mv,'$.*')),'],[',',') as val 12from logs 13group by collection_time 14) as t1,JSON_TABLE(val, '$[*]' COLUMNS (value INT PATH '$')) AS j 15) as t2 16group by collection_time

調整版

各データごとに平均を出して、そのデータを平均する

sql

1select collection_time, 2min(min) as min, 3max(max) as max, 4avg(avg) as avg 5from( 6select collection_time,mac, 7min(value) as min, 8max(value) as max, 9avg(value) as avg 10from ( 11select collection_time,mac,value 12 from 13( 14select 15collection_time,mac, 16replace(group_concat(JSON_EXTRACT(mv,'$.*')),'],[',',') as val 17from logs 18group by collection_time,mac 19) as t1,JSON_TABLE(val, '$[*]' COLUMNS (value INT PATH '$')) AS j 20) as t2 21group by collection_time,mac 22) as t3 23group by collection_time

投稿2025/02/26 03:57

編集2025/02/26 05:18
yambejp

総合スコア118300

hotta

2025/02/26 06:41

調整版の方をいただきました。ありがとうございました。 平均の平均を取る理由は、、、ちょっと説明が難しいです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.29%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問