SQLのウィンドウ関数「FIRST_VALUE()」の使い方と実務での活用例
こんにちは!このページでは、SQLのウィンドウ関数の中の「FIRST_VALUE()」に注目し、その挙動を確認していきます。実際にどのような場面で活用しているのか、についても紹介しています。
試した環境
- PostgreSQL 17.5
FIRST_VALUE()とは?
FIRST_VALUE() は、SQLのウィンドウ関数のひとつで、「あるグループや並び順の中で最初に現れる値」を取得できます。この関数を使うと、最初の情報を各行に表示できるため、初期状態との比較や変化の検出などに非常に便利です。
FIRST_VALUE()の基本とその動作
FIRST_VALUE(列名) は、指定した列の値のうち、「最初の行」の値を返します。ここでの「最初の行」とは、OVER() 内の ORDER BY で指定された並び順において最初の行を指します。ORDER BY を指定しない場合は行の順序が保証されないため、同じクエリでも実行のたびに異なる結果になる可能性があります。この点には注意が必要です。そのため、ORDER BY による並び順の指定は必須です。
また、PARTITION BY 列名 を指定すると、指定されたパーティション(グループ)ごとに「最初の行」の値が返されます。ここでの「最初」も同様に、ORDER BY で定義された並び順に基づいて決まります。
実務での活用例
FIRST_VALUE() は、日常的に頻繁に使われる関数ではありませんが、特定の場面では非常に効果的に機能します。私自身、実務の中でその有用性を実感することがあります。
当初予定との比較に活用
たとえば、タスクごとに日単位で作業完了の見込み時間を記録している場合、FIRST_VALUE() OVER() を使うことで、担当者ごとに「当初予定との差異」を一覧で可視化することが可能になります。
この可視化によって、進捗の推移と当初予定を並べて表示できるため、実際の進捗率と当初予定の進捗率を比較しやすくなります。これにより、予定とのギャップを発見し、作業計画の見直しなどの対応をとれるため、 問題点の把握と対応の迅速化といった点で、FIRST_VALUE() を含むSQLクエリが有効な分析ツールとして機能します。
サンプルデータで検証
それでは、具体的なデータを使って、FIRST_VALUE() 関数の動作を確認してみましょう。
タスクの作業時間管理のデータを用意し、実際にどのような結果が返されるのかを見ていきます。
作業時間データを表示
select * from task_log;
person_id | task_id | log_date | planned_hours | daily_actual_hours
-----------+---------+------------+---------------+--------------------
A001 | 設計 | 2025-07-01 | 45 | 8
A001 | 設計 | 2025-07-02 | 30 | 7
A001 | 設計 | 2025-07-03 | 21 | 6
A001 | 製造 | 2025-07-03 | 30 | 2
B001 | 製造 | 2025-07-01 | 38 | 8
B001 | 製造 | 2025-07-02 | 35 | 8
B001 | 製造 | 2025-07-03 | 36 | 8
(7 rows)
person_idは、担当者のIDです。ここでは、A001とB001の2名のメンバーがいます。task_idは、設計と製造の2つのIDがあります。log_dateは、作業日です。planned_hoursは、そのタスクにかかる総時間の見込みです。daily_actual_hoursはその日、そのタスクにつかった時間です。このデータをつかって、当初の予定も表示してあわせて進捗率も表示するようなSQLをFIRST_VALUE()を用いて、実行してみます。
person_id は担当者のIDで、この例では A001 と B001 の2名のメンバーがいます。task_id はタスクの種類を表し、「設計」と「製造」の2つのIDがあります。log_date は作業日、planned_hours は各タスクにかかる総見込み時間、そして daily_actual_hours はその日ごとに実際に費やした作業時間を示します。
これらのデータをもとに、FIRST_VALUE() を活用して、当初予定の表示と進捗率の可視化を行うSQLを実行してみます。
スマホでSQLの結果が折り返されて見にくい場合は、スマホを横にして表示すると見やすいです
SQLの結果を表示
select *
, round( 累積時間 / 計画 * 100, 1 ) as 進捗率
, round( 累積時間 / 当初計画 * 100, 1 ) as 当初進捗率
from ( select person_id as person, task_id as task, log_date, planned_hours as 計画
, first_value(planned_hours) over(partition by person_id, task_id order by log_date) as 当初計画
, sum(daily_actual_hours) over(partition by person_id, task_id order by log_date) as 累積時間
from task_log
) a
order by 1, 2, 3
;
person | task | log_date | 計画 | 当初計画 | 累積時間 | 進捗率 | 当初進捗率
--------+------+------------+------+----------+----------+--------+------------
A001 | 製造 | 2025-07-03 | 30 | 30 | 2 | 6.7 | 6.7
A001 | 設計 | 2025-07-01 | 45 | 45 | 8 | 17.8 | 17.8
A001 | 設計 | 2025-07-02 | 30 | 45 | 15 | 50.0 | 33.3
A001 | 設計 | 2025-07-03 | 21 | 45 | 21 | 100.0 | 46.7
B001 | 製造 | 2025-07-01 | 38 | 38 | 8 | 21.1 | 21.1
B001 | 製造 | 2025-07-02 | 35 | 38 | 16 | 45.7 | 42.1
B001 | 製造 | 2025-07-03 | 36 | 38 | 24 | 66.7 | 63.2
(7 rows)
planned_hours は列名として少し長いため、ここでは「計画」という別名に変更しています。そして、当初(07月01日時点)の各タスクに対する総見込み時間を、「当初計画」として、すべての行に表示しました。累積時間の表示にはSUM()OVER()を用いてます。
このように当初の計画時間を明示的に表示することで、進捗状況との比較が視覚的にわかりやすくなり、そこから多様な分析や気づきにつなげることが可能になります。
📌 今回のまとめ
FIRST_VALUE() は、使用頻度が高い関数ではないかもしれません。しかし、だからこそ、この関数を「知っているかどうか」で、特定の作業における効率や柔軟性に大きな差が生まれます。
利用時の重要な注意点として、ORDER BY を必ず指定する必要があります。指定しなくてもエラーにはなりませんが、意図しない結果になる場合があり、一見正しく見えても実際には誤ったデータを取得していることがあります。
特定の分析や進捗管理など、用途がはっきりしている場面では非常に効果を発揮する関数なので、ぜひ習得しておきたい機能のひとつです。
そのほかのウインドウ関数はこちらで紹介しています