こんにちは、@knsk765 です。
データ分析をしていると、「移動平均」を計算する場面がよくあります。たとえば、売上データのトレンドを把握したり、ノイズを減らしてスムーズなデータ傾向を見たいときに便利です。
この記事では、SQLのウィンドウ関数を使って移動平均を計算する方法を、初心者にもわかりやすく解説します。
サンプルコードと具体例を交えて、すぐに実務で使える知識をお届けします!
目次
移動平均とは、一定期間のデータの平均値を計算し、時間とともに「窓」をずらしながら計算を繰り返す手法です。
たとえば、7日間の移動平均なら、過去7日間のデータを平均して1つの値を作り、次の日ではその次の7日間の平均を計算します。
これにより、データの短期的な変動を抑え、長期的な傾向を把握しやすくなります。特に、外れ値(異常な高値や低値)が含まれるデータでは、移動平均の平滑化効果が顕著に現れます。
SQLのウィンドウ関数を使うと、こうした計算が驚くほど簡単になります。それでは、さっそく具体例を見ていきましょう!
ウィンドウ関数は、データベース内の行に対して「窓(ウィンドウ)」を定義し、その範囲内で計算を行うSQLの機能です。GROUP BY とは異なり、ウィンドウ関数は元のデータを集約せず、各行に対して計算結果を付加します。移動平均を計算するには、AVG() 関数をウィンドウ関数として使うのが一般的です。
基本的な構文は以下のとおり
AVG(列名) OVER ( PARTITION BY グループ分けする列(オプション) ORDER BY 順序を決める列 ROWS BETWEEN 範囲指定 )
PARTITION BY:データをグループ分け(例:店舗ごと、商品ごとなど)。省略可ORDER BY:データの並び順を指定(通常は日付や時系列)。ROWS BETWEEN:計算対象の範囲(例:直前の2行など)を指定。
これを移動平均にどう活かすか、具体例で見ていきましょう。
サンプルデータ
以下のような売上データがあるとします。テーブル名は sales で、構造は以下の通りです。今回は31レコード(2025-08-01 から 08-31 までの31日分)用意しました。
外れ値(例:8/6 に 5000)を意図的に入れており、平均化による平滑化の効果を強調しています。
| date | store_id | sales_amount |
|---|---|---|
| 2025-08-01 | A | 902 |
| 2025-08-02 | A | 1148 |
| 2025-08-03 | A | 1070 |
| 2025-08-04 | A | 906 |
| 2025-08-05 | A | 871 |
| 2025-08-06 | A | 5000 |
| 2025-08-07 | A | 820 |
| 2025-08-08 | A | 902 |
| 2025-08-09 | A | 921 |
| 2025-08-10 | A | 1014 |
| 2025-08-11 | A | 200 |
| 2025-08-12 | A | 887 |
| 2025-08-13 | A | 1172 |
| 2025-08-14 | A | 899 |
| 2025-08-15 | A | 1159 |
| 2025-08-16 | A | 951 |
| 2025-08-17 | A | 930 |
| 2025-08-18 | A | 949 |
| 2025-08-19 | A | 1108 |
| 2025-08-20 | A | 1057 |
| 2025-08-01 | B | 860 |
| 2025-08-02 | B | 1013 |
| 2025-08-03 | B | 721 |
| 2025-08-04 | B | 952 |
| 2025-08-05 | B | 935 |
| 2025-08-06 | B | 1044 |
| 2025-08-07 | B | 748 |
| 2025-08-08 | B | 4000 |
| 2025-08-09 | B | 869 |
| 2025-08-10 | B | 887 |
| 2025-08-11 | B | 970 |
| 2025-08-12 | B | 889 |
| 2025-08-13 | B | 874 |
| 2025-08-14 | B | 750 |
| 2025-08-15 | B | 1063 |
| 2025-08-16 | B | 100 |
| 2025-08-17 | B | 943 |
| 2025-08-18 | B | 1019 |
| 2025-08-19 | B | 830 |
| 2025-08-20 | B | 1006 |
チャートで見るとこんな感じです。

このデータを使って、直近7日間の移動平均を計算してみましょう。外れ値があることで、売上額の急変動が移動平均でどう抑えられるかがわかります。
サンプルデータテーブルを作るクエリは以下です。
CREATE TABLE sales ( date DATE NOT NULL, store_id VARCHAR(1) NOT NULL, sales_amount INTEGER NOT NULL ); INSERT INTO sales (date, store_id, sales_amount) VALUES ('2025-08-01', 'A', 902), ('2025-08-02', 'A', 1148), ('2025-08-03', 'A', 1070), ('2025-08-04', 'A', 906), ('2025-08-05', 'A', 871), ('2025-08-06', 'A', 5000), ('2025-08-07', 'A', 820), ('2025-08-08', 'A', 902), ('2025-08-09', 'A', 921), ('2025-08-10', 'A', 1014), ('2025-08-11', 'A', 200), ('2025-08-12', 'A', 887), ('2025-08-13', 'A', 1172), ('2025-08-14', 'A', 899), ('2025-08-15', 'A', 1159), ('2025-08-16', 'A', 951), ('2025-08-17', 'A', 930), ('2025-08-18', 'A', 949), ('2025-08-19', 'A', 1108), ('2025-08-20', 'A', 1057), ('2025-08-01', 'B', 860), ('2025-08-02', 'B', 1013), ('2025-08-03', 'B', 721), ('2025-08-04', 'B', 952), ('2025-08-05', 'B', 935), ('2025-08-06', 'B', 1044), ('2025-08-07', 'B', 748), ('2025-08-08', 'B', 4000), ('2025-08-09', 'B', 869), ('2025-08-10', 'B', 887), ('2025-08-11', 'B', 970), ('2025-08-12', 'B', 889), ('2025-08-13', 'B', 874), ('2025-08-14', 'B', 750), ('2025-08-15', 'B', 1063), ('2025-08-16', 'B', 100), ('2025-08-17', 'B', 943), ('2025-08-18', 'B', 1019), ('2025-08-19', 'B', 830), ('2025-08-20', 'B', 1006);
7日間移動平均のSQL
以下のクエリで、店舗ごとの7日間移動平均を計算できます
SELECT date, store_id, sales_amount, ROUND(AVG(sales_amount) OVER ( PARTITION BY store_id ORDER BY date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW )) AS moving_average FROM sales ORDER BY store_id, date;
クエリの解説
PARTITION BY store_id:
店舗ごとにデータをグループ化。店舗Aと店舗Bの計算は独立して行われます。
ORDER BY date:
日付順にデータを並べ、時系列での計算を保証。
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW:
直前の6行と現在の行(合計7行)を計算対象に指定。
AVG(sales_amount):
指定した範囲のsales_amountの平均を計算。
結果例
上記のクエリを実行すると、以下のような結果が得られます。
外れ値(例:Aの5000や200)があっても、移動平均は急激な変動を抑え、トレンドを安定させてくれます。
店舗A:
| date | store_id | sales_amount | moving_average |
|---|---|---|---|
| 2025-08-01 | A | 902 | 902 |
| 2025-08-02 | A | 1148 | 1025 |
| 2025-08-03 | A | 1070 | 1040 |
| 2025-08-04 | A | 906 | 1007 |
| 2025-08-05 | A | 871 | 979 |
| 2025-08-06 | A | 5000 | 1650 |
| 2025-08-07 | A | 820 | 1531 |
| 2025-08-08 | A | 902 | 1531 |
| 2025-08-09 | A | 921 | 1499 |
| 2025-08-10 | A | 1014 | 1491 |
| 2025-08-11 | A | 200 | 1390 |
| 2025-08-12 | A | 887 | 1392 |
| 2025-08-13 | A | 1172 | 845 |
| 2025-08-14 | A | 899 | 856 |
| 2025-08-15 | A | 1159 | 893 |
| 2025-08-16 | A | 951 | 897 |
| 2025-08-17 | A | 930 | 885 |
| 2025-08-18 | A | 949 | 992 |
| 2025-08-19 | A | 1108 | 1024 |
| 2025-08-20 | A | 1057 | 1008 |
8/6 の 5000(高外れ値)で移動平均が一時的に上がるが、次の日以降で徐々に平滑化。
8/11 の 200(低外れ値)で平均が下がるが、周囲のデータで緩和される。
店舗B:
| date | store_id | sales_amount | moving_average |
|---|---|---|---|
| 2025-08-01 | B | 860 | 860 |
| 2025-08-02 | B | 1013 | 937 |
| 2025-08-03 | B | 721 | 865 |
| 2025-08-04 | B | 952 | 887 |
| 2025-08-05 | B | 935 | 896 |
| 2025-08-06 | B | 1044 | 921 |
| 2025-08-07 | B | 748 | 896 |
| 2025-08-08 | B | 4000 | 1345 |
| 2025-08-09 | B | 869 | 1324 |
| 2025-08-10 | B | 887 | 1348 |
| 2025-08-11 | B | 970 | 1350 |
| 2025-08-12 | B | 889 | 1344 |
| 2025-08-13 | B | 874 | 1320 |
| 2025-08-14 | B | 750 | 1320 |
| 2025-08-15 | B | 1063 | 900 |
| 2025-08-16 | B | 100 | 790 |
| 2025-08-17 | B | 943 | 798 |
| 2025-08-18 | B | 1019 | 805 |
| 2025-08-19 | B | 830 | 797 |
| 2025-08-20 | B | 1006 | 816 |
8/8 の 4000 で平均が跳ね上がるが、以降で抑えられる。
8/16 の 100 で平均が下がるが、平滑化効果でトレンドが崩れにくい。
データ量が多いと、こうした外れ値の影響が全体トレンドの中でどう吸収されるかがよくわかります。チャートにするとこうですね。

応用例:30日間移動平均や全期間平均
30日間移動平均:ROWS BETWEEN 29 PRECEDING AND CURRENT ROWに変更。外れ値の影響がさらに薄まる。
全期間の平均:ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROWで、開始日から現在までの平均を計算。
中心移動平均(前後3日の平均):ROWS BETWEEN 3 PRECEDING AND 3 FOLLOWINGで、前後3日を含む7日間の平均を計算。
NULL値の扱い:
データに欠損がある場合、AVG() は NULL を無視します。必要に応じて COALESCE などで補完を。
パフォーマンス:
大きなデータセットでは、PARTITION BY や範囲指定がパフォーマンスに影響します。インデックスを適切に設定しましょう。
データベースの互換性:
ウィンドウ関数は PostgreSQL、MySQL(8.0以降)、SQL Server、Oracle などで使えますが、構文やサポート範囲が異なる場合があります。使用するDBのドキュメントを確認してください。
外れ値の考慮:
外れ値を含むデータでは、移動平均の窓幅を調整して影響をコントロール。狭い窓だと変動が残り、広い窓だと平滑化が強まります。
売上分析:日次売上のブレ(外れ値含む)を抑えてトレンドを把握。
在庫管理:需要予測のために過去の出荷データの移動平均を計算。
センサーデータ:IoTデバイスのノイズを平滑化して異常検知に活用。
SQLのウィンドウ関数を使えば、移動平均の計算が驚くほど簡単にできます。
AVG()とOVER句を組み合わせるだけで、柔軟にデータ範囲を指定して分析が可能。
今回の例のように、外れ値を入れてみると、平均化の効果が実感しやすいですよ!ぜひ自分のデータで試してみてください。
ウィンドウ関数に興味を持たれた方はこちらの記事もぜひ。
コメント