ウィンドウ関数 SUM() をわかりやすく解説!SQLの表現力を広げよう
こんにちは!このページでは、少し複雑と感じていた SQL のウィンドウ関数の中から、「SUM()」に注目して、その仕組みや使い方をわかりやすく紹介します。実際に使ってみて気づいた注意点や、つまずきやすいポイントについても整理しています。
試した環境
- PostgreSQL 17.5
複雑と感じた理由
ウィンドウ関数の第一印象
SQLのウィンドウ関数(OVER や PARTITION BY を含む構文)は、初めて触れると「難しそう」「複雑そう」と感じる方も多いのではないでしょうか。私自身、最初に見たときはまったく意味が分からず、構文に対して違和感すら覚えました。
特に、他人が書いたクエリを読み解くとき、見慣れないキーワードが並ぶと戸惑ってしまいます。調べればおおよその処理内容は分かるものの、構文全体の構造や動きがすぐには理解できず、頭に入ってこなかった記憶があります。
なぜウィンドウ関数はとっつきにくいのか?
このように感じるのは、ウィンドウ関数が通常の集計関数とは異なり、独特な挙動を持っているからかもしれません。その仕組みや考え方に慣れるまで時間がかかるのも自然なことかもしれません。
慣れれば強力な武器に
とはいえ、ウィンドウ関数はデータ分析や業務で非常に役立つ、強力な機能です。一度使い方を身につければ、これまで複雑だった処理もシンプルなSQLで表現できるようになります。
ここでは、ウィンドウ関数の中でも特に使用頻度の高い SUM() にフォーカスし、その仕組みと使い方をわかりやすく解説します。
SQLウィンドウ関数とは
ウィンドウ関数の基本的な役割
ウィンドウ関数は、SQLでデータの集計や分析を行う際に、各行を保持したまま集計結果を追加表示できる機能です。従来の GROUP BY を使った集計では、グループごとに1行の結果しか得られませんが、ウィンドウ関数では、元の明細データを壊さずに集計結果を付け加えることができます。
つまり、「GROUP BY句に項目を追加することなく、SELECT句に集計処理の項目を追加できるSQL関数」と理解すれば、わかりやすいのではないでしょうか
GROUP BYとの違い
ウィンドウ関数が理解しにくい理由のひとつに、GROUP BYとは処理の挙動や集計のタイミングが異なる点が挙げられます。
GROUP BY句では、まずWHERE句で対象のデータを絞り込んだあと、絞り込まれた行をグループ化し、各グループに対して集計処理を行います。
このため、SELECT句に指定できるのは、GROUP BYで指定した列、リテラル(定数値)、または集計関数を使用した項目に限られます。
一方でウィンドウ関数も、まずWHERE句によってデータを絞り込む点や、GROUP BY句が指定されていればグループ化と集計が行われる点は、GROUP BYを使った処理と共通しています。
ただし、ウィンドウ関数が処理の対象とするのは、SELECT句に出力される明細行です。この点が、GROUP BYのような句との大きな違いと言えるでしょう。
処理対象がSELECT句の明細行であるため、WHERE句やGROUP BY句の中でウィンドウ関数を使用することはできません。
ウィンドウ関数は、明細行に対して、指定された範囲や条件に基づいて集計処理を独立して適用し、その結果を新たな列としてSELECT句に追加することができます。
このように、ウィンドウ関数は「SELECT句の明細に、集計列をあとから付け加える」ような処理として捉えると、イメージしやすくなります。
この違いにより、ウィンドウ関数を使えば、従来のGROUP BYでは難しかった「集計と明細の同時表示」や「行単位での柔軟な集計処理」が可能になります。
OVER() の役割
ウィンドウ関数を使うには、OVER() キーワードの指定が不可欠です。 逆に言えば、OVER() が使われていれば、「この SELECT 項目はウィンドウ関数による 独立した集計 が行われている」と判断できます。
OVER() の役割は、ウィンドウ関数が適用される 集計や分析の対象範囲(ウィンドウ)を指定すること にあります。 つまり、明細行に対して「どの範囲で」「どの順序で」集計を行うかを、OVER() の中で定義することで、独立した柔軟な集計が可能になります。
具体的には、OVER() の中に以下のようなキーワードを組み合わせて使用します:
・PARTITION BY:データのグループ分けの指定(GROUP BY に相当)
・ORDER BY:グループ内での並び順を指定(累積や順位などに使用)
一見すると複雑に感じられるかもしれませんが、OVER() の基本構造と役割を理解すれば、SQLでの表現力は格段に向上します。 特に、従来はサブクエリや自己結合を必要としていた処理を、よりシンプルに記述できるのが、ウィンドウ関数の大きなメリットです。
ウィンドウ関数は難しくない
ウィンドウ関数にはさまざまな種類がありますが、すべて OVER() 句と組み合わせて使用します。
前述のような基本構造を理解しておけば、各ウィンドウ関数の特性も把握しやすくなり、目的に応じた使い分けも難しくありません。
たとえば、「この明細行ごとの累計金額を出したい」や「商品分類ごとに購入顧客数を集計したい」といったケースでも、ウィンドウ関数を使えば、よりスマートにデータを抽出できます。
SUM()の基本とその動作
今回は、使用頻度の高い SUM() 関数を用いて、基本的な使い方を試してみます。
SUM() は、数値を合計するための集計関数で、 SELECT 句で取得されるすべての行が合計の対象になります。
ただし、PARTITION BY を指定することで、その範囲ごとに合計を計算することが可能です。これにより、全体の合計ではなく、例えば顧客ごとの合計やカテゴリ別の合計といった柔軟な集計ができます。
ORDER BYの役割とその効果
OVER() の中で指定する ORDER BY は、単なる並び替えと捉えられがちですが、ウィンドウ関数のSUM()においてはそれ以上の重要な意味を持ちます。
具体的には、ORDER BY で指定した順序に従ってデータが並べられ、その順番に沿って合計値が計算されることで、「累積合計」として機能します。この累計合計の切り替えタイミングを指定しているといっても過言ではありません
この処理は、コントロールブレークや GROUP BY によるグループ集計に似ていますが、やや直感的に理解しづらい点があります。特に、他のウィンドウ関数では ORDER BY が単なる並び替え指定であることが多いため、SUM() のようなケースは、少し混乱するかもしれません。
サンプルデータで検証
それでは、具体的なデータを使って、SUM() 関数の動作を確認してみましょう。
「購入履歴」テーブルに登録した2025年の購入データを使って、実際にどのような結果が返されるのかを見ていきます。
購入履歴データを表示
購入id | 顧客名 | 購入年 | 購入金額
--------+--------+--------+----------
10 | 高木 | 2025 | 400
11 | 仲本 | 2025 | 300
12 | 仲本 | 2025 | 200
13 | 内村 | 2025 | 100
(4 rows)
SUM(購入金額) OVER()
まずは、PARTITION BY も ORDER BY も指定しない、最も基本的な形のウィンドウ関数を使って、購入金額の合計を求めてみましょう。
この場合、OVER() の中に何も指定されていないため、すべての行を1つのウィンドウ(範囲)として扱い、SELECT句全体の合計金額が各行に表示されます。
スマホでSQLの結果が折り返されて見にくい場合は、スマホを横にして表示すると見やすいです
SQLの結果を表示
SELECT purchase_id as 購入ID ,customer_name as 顧客名 ,purchase_year as 購入年 ,amount as 購入金額
,SUM(amount) OVER () AS 合計金額
FROM purchase_history
WHERE purchase_year = '2025'
;
購入id | 顧客名 | 購入年 | 購入金額 | 合計金額
--------+--------+--------+----------+----------
10 | 高木 | 2025 | 400 | 1000
11 | 仲本 | 2025 | 300 | 1000
12 | 仲本 | 2025 | 200 | 1000
13 | 内村 | 2025 | 100 | 1000
(4 rows)
期待通り、全体の合計金額1000(400+300+200+100)が、各行に表示されました。
SUM(購入金額) OVER(PARTITION BY 顧客名)
次に、PARTITION BY だけを指定したパターンを試してみましょう。今回は、顧客名 を指定して集計を行います。
OVER() の中で PARTITION BY 顧客名 を指定すると、集計対象の範囲が「顧客名」ごとになるため、各行の顧客名で SUM(購入金額) が計算されます。
その結果、同じ顧客名を持つすべての行に、該当する顧客の合計購入金額が表示されます。これは、顧客単位の集計値を明細行に保持しながら出力したい場合に、非常に便利な方法です。
SQLの結果を表示
SELECT purchase_id as 購入ID ,customer_name as 顧客名 ,purchase_year as 購入年 ,amount as 購入金額
,SUM(amount) OVER (PARTITION BY customer_name) AS 顧客毎合計金額
FROM purchase_history
WHERE purchase_year = '2025'
;
購入id | 顧客名 | 購入年 | 購入金額 | 顧客毎合計金額
--------+--------+--------+----------+----------------
11 | 仲本 | 2025 | 300 | 500
12 | 仲本 | 2025 | 200 | 500
13 | 内村 | 2025 | 100 | 100
10 | 高木 | 2025 | 400 | 400
(4 rows)
期待通り、顧客ごとの合計金額(500,100,400)が、それぞれの行に表示されました。
SUM(購入金額) OVER(ORDER BY 顧客名)
次は、ORDER BY だけを指定したパターンを試してみましょう。ここでは 顧客名 を指定して集計を行います。
この例では、PARTITION BY の指定がないため、集計の対象は SELECT 句で取得されるすべての行となります。一方で、ORDER BY 顧客名 が指定されているため、データは顧客名の昇順に並び、その並び順に沿って 、顧客名ごとにSUM(購入金額) が計算され、金額が積み上がっていくような出力になります。
その結果、同じ顧客名を持つ単位で、累計合計がつみあがって表示されます。
このように ORDER BY を使うことで、購入金額が上から順に累積されていく「累積合計」が各行に表示されます。
SQLの結果を表示
SELECT purchase_id as 購入ID ,customer_name as 顧客名 ,purchase_year as 購入年 ,amount as 購入金額
,SUM(amount) OVER (ORDER BY customer_name) AS 顧客累計合計
FROM purchase_history
WHERE purchase_year = '2025'
;
購入id | 顧客名 | 購入年 | 購入金額 | 顧客累計合計
--------+--------+--------+----------+--------------
11 | 仲本 | 2025 | 300 | 500
12 | 仲本 | 2025 | 200 | 500
13 | 内村 | 2025 | 100 | 600
10 | 高木 | 2025 | 400 | 1000
(4 rows)
顧客ごとの合計金額が、それぞれの顧客単位で累計金額として積み上げ表示されました。
SUM(購入金額) OVER(ORDER BY 購入ID)
購入IDのような主キーを指定すると、1行ごとに金額が累計されていくため、よりわかりやすく表示されます。次に、顧客IDを指定して試してみます。
SQLの結果を表示
SELECT purchase_id as 購入ID ,customer_name as 顧客名 ,purchase_year as 購入年 ,amount as 購入金額
,SUM(amount) OVER (ORDER BY purchase_id) AS 購入ID累計
FROM purchase_history
WHERE purchase_year = '2025'
;
購入id | 顧客名 | 購入年 | 購入金額 | 購入id累計
--------+--------+--------+----------+------------
10 | 高木 | 2025 | 400 | 400
11 | 仲本 | 2025 | 300 | 700
12 | 仲本 | 2025 | 200 | 900
13 | 内村 | 2025 | 100 | 1000
(4 rows)
購入IDの昇順で、1行ごとに金額が累計されて表示されました。
主キー以外の指定の場合は、直感的には少しわかりづらいかもしれませんが、基本的な動作を理解してしまえば、それほど難しくはないと思います。
SUM(購入金額) OVER (PARTITION BY 顧客名 ORDER BY 購入ID)
最後に、PARTITION BY と ORDER BY の両方を指定するパターンを試してみましょう。ここでは、PARTITION BY に「顧客名」、ORDER BY に「購入ID」を指定して集計を行います。
この例では、顧客名ごとにデータがグループ化され、各グループ内で購入IDの昇順に並べられた上で、SUM(購入金額) によって累計金額が計算・表示されます。
SQLの結果を表示
SELECT purchase_id as 購入ID ,customer_name as 顧客名 ,purchase_year as 購入年 ,amount as 購入金額
,SUM(amount) OVER (PARTITION BY customer_name ORDER BY purchase_id) AS 顧 客毎合計
FROM purchase_history
WHERE purchase_year = '2025'
;
購入id | 顧客名 | 購入年 | 購入金額 | 顧客毎合計
--------+--------+--------+----------+------------
11 | 仲本 | 2025 | 300 | 300
12 | 仲本 | 2025 | 200 | 500
13 | 内村 | 2025 | 100 | 100
10 | 高木 | 2025 | 400 | 400
(4 rows)
顧客名の範囲で、累計金額が表示されました。
実務での活用例
SUM()関数は、売上明細に累計売上を表示したい場合や、商品分類ごとの合計金額を各行に表示させたい場合など、さまざまな集計処理に活用できます。
工数管理での活用
私が SUM()関数をよく使うのは、工数や予算の管理においてです。たとえば、プロジェクトごとに日単位で作業時間を記録している場合、SUM() OVER()を用いれば、担当者別やタスク別に累計工数を算出できます。これにより、進捗状況やリソース配分の偏りを、SQLのクエリ結果だけで視覚的に把握できます。
よくある方法として、日別の工数データをSQLで抽出し、Excelに貼り付けてから数式で累計を出し、さらにグラフ化するという手順を踏むケースがあります。しかし、SUM() OVER()を使えば、SQLの時点で累計値が得られるため、Excelでの処理工程を一つ省略でき、作業の効率化に大きく貢献します。
予算管理での活用
予算管理においても、SUM()関数は有効です。たとえば、月別の支出データに対して累計支出を算出することで、年間予算に対する使用状況を確認できます。これにより、予算の過不足を早期に把握でき、使いすぎや未消化のリスクを軽減できます。
意思決定への活用
このように、SUM() OVER()を活用すれば、単なる集計にとどまらず、業務上の意思決定に役立つ“気づき”をデータから効率的に引き出すことが可能になります。
📌 今回のまとめ
ウィンドウ関数は一見すると難しそうに見えますが、基本構文とその意味を理解すれば、 SQLの表現力を大きく広げ、作業の効率化、進捗の確認など手放せない機能となります。
中でも SUM() 関数は、PARTITION BY や ORDER BY を組み合わせることで、さまざまな切り口での累計やグループ別集計が可能になります。 ORDER BY の使い方には多少慣れが必要ですが、実際に繰り返し使っていくことで、自然と身についていきます。
ぜひ、日々の業務の中で活用していきましょう。
そのほかのウインドウ関数はこちらで紹介しています