※ この記事にはアフィリエイトリンクが含まれます
PostgreSQLでグループごとの最小値を取りたいけれど、どうやって書けばよいか分からないことはありませんか?
- GROUP BYと集約関数の使い方が分からない
- 最小値を持つ行全体を取得したいが書き方が分からない
- NULLや同値があると期待通りに結果が出ない
結論から言うと、PostgreSQLではMIN()やウィンドウ関数、DISTINCT ONなどを使えば目的を達成できます。
- 集約関数MIN()とGROUP BYでグループごとの最小値を取得する
- 最小値を持つ行全体を取得するにはJOIN、DISTINCT ON、またはウィンドウ関数を使う
- NULLや同値の扱いには注意し、必要ならCOALESCEや追加条件で調整する
この記事を読むことで、基本的な書き方から実務でよく使う応用パターン、注意点まで分かります。
PostgreSQL グループごとの最小値を求めるための実践的なクエリ例と説明を、初心者向けに丁寧に解説します。
グループごとの最小値とは何か
まずは概念を整理しましょう。グループごとの最小値とは、あるキーごとに集合を作り、その集合内で最も小さい値を取得する処理です。
たとえば販売テーブルで商品ごとの最安値や、ユーザーごとの最短応答時間を求める場面で使います。
PostgreSQLでは集約関数MIN()を使うことで簡単に求められますが、行全体を取りたい場合は別の手法が必要になります。
基本:GROUP BYとMIN()を使う
最も基本的なのはGROUP BYとMIN()を組み合わせる方法です。これは集約結果だけが欲しい場合に非常に有効です。
以下はサンプルのテーブル定義とデータ挿入です。最小値の出し方も示します。
サンプルデータの作成
CREATE TABLE items (
id serial PRIMARY KEY,
group_id int,
value int
);
INSERT INTO items (group_id, value) VALUES
(1, 100),
(1, 200),
(1, 50),
(2, 300),
(2, 150),
(2, NULL);
単純なGROUP BYとMIN()の例
SELECT group_id, MIN(value) AS min_value
FROM items
GROUP BY group_id
ORDER BY group_id;
このクエリは各group_idごとにvalueの最小値を返します。NULLはMIN()では無視される点に注意してください。
最小値を持つ「行全体」を取得する方法
集約結果だけでなく、最小値を持つ行そのもの(たとえばidや他カラムも含む)を取得したい場合は別の方法が必要です。
代表的な3つの方法を紹介します。それぞれ長所と短所があるので用途に応じて選びます。
方法1:サブクエリとJOINで結合する
まずは集約結果をサブクエリで取得し、本体テーブルと結合する方法です。理解しやすく実務でもよく使われます。
SELECT t.*
FROM items t
JOIN (
SELECT group_id, MIN(value) AS min_value
FROM items
GROUP BY group_id
) m
ON t.group_id = m.group_id
AND t.value = m.min_value;
この方法は、最小値が複数行ある場合すべての該当行を返します。インデックスが効く場面も多く、比較的高速です。
方法2:DISTINCT ONを使う
PostgreSQL固有のDISTINCT ONは、あるカラムごとに1行だけ取り出したいときに便利です。ただしORDER BYの順序に依存します。
SELECT DISTINCT ON (group_id) *
FROM items
ORDER BY group_id, value ASC;
このクエリはgroup_idごとにvalueが最小となる1行を返します。複数の同値がある場合はORDER BYでさらに条件を指定して制御できます。
方法3:ウィンドウ関数(ROW_NUMBERなど)を使う
ウィンドウ関数を使うと、各行に順位を付けて最小値の行だけを抽出できます。柔軟かつ表現力が高い方法です。
SELECT id, group_id, value
FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY group_id ORDER BY value ASC NULLS LAST) AS rn
FROM items
) s
WHERE rn = 1;
ROW_NUMBERだと同値があっても1行だけ返ります。全ての同値を返したいならRANK()やDENSE_RANK()を使います。
NULLや同値への対応方法
MIN()はNULLを無視しますが、実務ではNULLを特別扱いしたい場面があります。COALESCEを使って代替値を与える方法が一般的です。
-- NULLを大きい値に置き換えて無視したい場合
SELECT group_id, MIN(COALESCE(value, 999999)) AS min_value
FROM items
GROUP BY group_id;
また、最小値が複数存在する場合の挙動を明確にするために、どの行を返すか設計段階で決めておくとよいです。
パフォーマンスの考え方とインデックス
大規模データでグループごとの最小値を取るときはパフォーマンスが重要です。インデックスが役に立ちます。
たとえばgroup_idとvalueに複合インデックスを張ると、最小値の検索が速くなる場合があります。適切なインデックス設計を検討しましょう。
CREATE INDEX idx_items_group_value ON items (group_id, value);
ただし、GROUP BYやウィンドウ関数は内部でソートや集約を行うため、インデックスが常に効くとは限りません。実行計画(EXPLAIN ANALYZE)で確認することが重要です。
よくある間違いと対処例
初心者が詰まりやすいポイントを挙げ、それぞれの対処方法を説明します。原因を知れば簡単に修正できます。
- GROUP BYに非集約カラムを含めてしまいエラーになる:非集約カラムはGROUP BYに入れるか集約関数で包む必要があります。
- 最小値の行が欲しいのに
MIN()だけを書いてしまう:ROWやDISTINCT ON、JOINを使って行を取得します。 - NULLの扱いで期待と違う結果になる:COALESCEやIS NULLチェックで明示的に扱う。
- 同値が複数ある場合の取り方が曖昧:RANKやDENSE_RANK、あるいは条件で絞る。
発生した問題はまず小さなサンプルで再現し、EXPLAINで実行計画を確認することをおすすめします。
実践的な例:売上テーブルで商品ごとの最安販売日を取得する
実務でよくあるシナリオを使って、どの方法を選ぶべきか示します。ポイントは何を「最小」と定義するかです。
売上テーブル sales(product_id, sold_at, price) があり、商品ごとの最安値とその販売日を取得したいとします。
-- 最小価格だけを取得
SELECT product_id, MIN(price) AS min_price
FROM sales
GROUP BY product_id;
-- 最小価格の行(販売日なども含む)を取得(ウィンドウ関数)
SELECT product_id, price, sold_at
FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY product_id ORDER BY price ASC, sold_at ASC) AS rn
FROM sales
) t
WHERE rn = 1;
この例では、価格が同じ場合はsold_atで並べ替えてどの行を優先するかを決めています。仕様に合わせてORDER BYを調整してください。
まとめ
PostgreSQLでグループごとの最小値を求める基本はMIN()とGROUP BYです。集約結果だけが欲しいならこれで十分です。
最小値を含む行全体が必要な場合は、サブクエリとJOIN、DISTINCT ON、ウィンドウ関数のいずれかを使います。それぞれ用途と特性が違います。
NULLや同値への対応、パフォーマンス面の配慮も忘れずに。実行計画を確認して最適化を行ってください。
この記事を参考にして、まずは小さなサンプルで試し、実データに適用していきましょう。PostgreSQL グループごとの最小値の扱いに自信が持てるようになります。
PostgreSQLは、現場でも広く使われている信頼性の高いデータベースです。もしこれから本格的に学び、実務で通用する力をつけたい方には、RareTECHをチェックしてみてください。実案件ベースのカリキュラムで、あなたのスキルを次のステージへ引き上げてくれるはずです。
なんくる「本当にエンジニアとしてやっていけるか不安…」という方も、実践的な開発に関わることで、転職後の働き方を事前に体感できますよ。
実務で使えるDBスキルとともに、プログラミングスキルをちゃんと身につけたいなら、
RareTECHの無料カウンセリングで、学ぶ目的やゴールをプロと一緒に明確にしてみましょう。独学では得られない「実践的な成長の道筋」が見えてきます。


もしこの内容を通して、PostgreSQLについてさらに理解を深めたいと感じられたなら、信頼できる講座や書籍を紹介した別記事をご覧いただくのも良いかと思います。ご自身の学びに、きっとお役立ていただけるはずです。










コメント