同じ3行から、平均150msと400msが出る理由。
100ms、200ms、900ms。全体の平均は400msです。それでも「平均150ms」が正しくなる条件があります。SQLのどこを読むと、その違いが分かるのでしょう。

この記事の目次
平均150msも400msも、計算ミスとは限らない
説明用の記録を三行だけ並べてみます。応答時間は100ms、200ms、900ms。最初の二件は成功、最後の一件は失敗でした。全部なら平均400ms、成功だけなら150ms。どちらも計算は合っているのに、答えは違う。「平均を教えて」だけでは、ここが決まらないんですね。
集計の前に絞るか、集計ごとに条件を置くか
SQLのSELECTはデータを取り出す指定、WHEREは条件に合う行へ絞る指定です。この例でWHERE status=200を付けると、成功の二行が対象に。平均を求めるavg(duration_ms)を使えば150、全三行なら400。計算へ入る前に、使う行を選んでいるわけです。[1][2]
count(*)も一緒に出せば、使った行数が見えます。150だけでは気づきにくい違いも、「2件の平均150」と「3件の平均400」なら話しやすくなります。実データの成功条件は業務によって決める必要がありますが、その条件を確認できる形にするのが最初の一歩です。[2]
FILTERなら、同じ行から別の集計を並べられる
PostgreSQLのFILTERは、指定した集計へ入れる行を絞ります。たとえばcount(*)は全行、avg(duration_ms) FILTER (WHERE status=200)は成功だけ、というように一つの結果に並べられます。WHEREをクエリ全体へ付けた場合と違い、ほかの集計は全行を受け取れるのがポイントです。[2]
下の実践では、全件のcountとavg、成功だけのcountとavgを横に並べます。期待する値は3、400、2、150。どの数字がどの条件に対応するかを追ってみてください。SQLを速く書くことより、条件を読んで「この平均の対象はここだ」と指せることが、今回の発見です。[2]
要は、WHEREは集計する前の行を絞り、FILTERはその集計だけに条件を掛けるという違いです。全件の平均と成功だけの平均を並べたいときは、この掛ける場所を読めると、なぜ400と150が同時に出るかが分かります。
NULLが入ると、件数と平均の意味も変わる
では、記録できなかった時間があったらどうでしょう。値が入っていないことを表すNULLが出てきます。行の数と、時間を記録できた数が同じとは限らなくなるので、先ほどの三行を少し変えてみましょう。[3]
ここからの式はPostgreSQL向けです。SQLには製品ごとの違いもあるので、手元で試すときは同じ製品の案内を使ってください。まずは値がない一行を、平均がどう扱うかに注目します。[2]
説明用に、応答時間が100、200、NULLの三行を考えます。count(*)は三行、count(duration_ms)は値のある二行。avg(duration_ms)はNULL以外の値を使うので150です。100と200にゼロを足して三で割る計算とは違います。[3]
つまり、平均へ添える件数にも、何を数えたかが必要なんです。「全行は三件、時間を記録できたのは二件」と並べれば、数字が食い違う理由を話せます。NULLを本当のゼロへ置き換えてよいかは、データの意味から別に決めます。
同じ三行でも、数える対象を読む
| 式 | 説明用データでの値 | 何を見たか |
|---|---|---|
| count(*) | 3 | すべての行 |
| count(duration_ms) | 2 | 時間の値がNULLでない行 |
| avg(duration_ms) | 150 | 100と200の平均 |
AIの式を採点するなら、先に三行の正解を作っておく
AIへ式を頼むとき、白紙から動く答えが出てくるだけで、少しうれしくなりますよね。そこで「実行できたから合っている」と判断する前に、三行なら手で計算できることを使いましょう。全件なら400、成功だけなら150。この二つを、AIの式を見る前にメモしておきます。
依頼も、平均だけから一段具体的にします。「全件の件数と平均、成功した件数と平均を同じ結果へ出したい。成功はstatus=200。この三行なら3件・400、2件・150になる」。どの数字が欲しいかに加え、どの行を使うかまで渡せました。
もし返った式にWHERE status=200があり、全件のcount(*)まで2になったら。構文は動いていても、「全件」を集計する前に一行が消えています。先ほどのFILTERなら、成功だけの集計へ条件を付け、ほかの集計へは全行を渡せます。間違いを見つけた場所と、直す場所が対応するんですね。[1][2]
ここで「FILTERが正しい呪文」と覚えるより、ほしい結果を先に言えることが大事です。最初から成功行だけを調べたいなら、WHEREで絞る目的もあります。何を比較したいかが変われば、同じ条件を掛ける場所も変わるわけです。[1][2]
一度期待値と一致したら、今度は成功した200msを300msへ変える。成功だけの平均は200、全件は約433.33になるはずです。一つ値を変えて予想した方向へ動くかを見ると、たまたま元の三行だけで合った式も、もう一度問い直せます。
いま200msを300msへ変えた後のデータは、100・300・900の三行です。ここから900msをNULLへ変えると、100・300・NULLになり、平均は200。前の節の図は、変更前の100・200・NULLで平均150でした。三行目がNULLでも、二行目の値が違うので平均も変わるんですね。[3]
同じ100・300・NULLで、今度は件数も比べます。count(*)は三行、count(duration_ms)は値がある二件。avg(duration_ms)は後者の二つの値を使います。NULLをゼロへ変えて三で割ると約133.33になるので、記録がないことと、本当のゼロは分けて扱います。[3]
要は、AIより速くSQLを書く競争をしなくても、式が答える問いを確かめる仕事はできるんです。小さなデータ、先に書いた期待値、条件を一つ変えた結果。この三つを持っておくと、「数字が出た」から「この数字になる理由を説明できる」へ進めます。
全件と成功だけの集計を、同じ結果で比べる
自分が使ってよいPostgreSQL環境で行います。手元になければ記事末尾の「環境の準備に使う公式ガイド」から、OSに合った導入方法を確認してください。この記事では新しいDBの契約や構築は不要です。まず架空の3行を紙に書き、全件と成功だけを分ければ、同じ違いを確かめられます。
WITH sample(status, duration_ms) AS (VALUES (200,100),(200,200),(500,900))
SELECT count(*) AS all_count, avg(duration_ms) AS all_avg,
count(*) FILTER (WHERE status=200) AS success_count,
avg(duration_ms) FILTER (WHERE status=200) AS success_avg
FROM sample;準備と、確かめる順番
全件は3件で400、成功だけは2件で150。これは式から求めた期待値です。手元で実行した結果と比べてみてください。表示する小数点の桁は環境で異なります。
FILTERが付いた二つの集計と、付いていない二つを指で追います。なぜ同じSELECTの中で対象が変わるのか、「FILTERなら、同じ行から別の集計を並べられる」の説明と照合します。
成功行の200msを300msへ変えたらどうなるか、実行前に予想します。全件の平均は約433.33、成功だけは200です。
次に「平均」を見たら、対象と件数も確かめる。そのために読めるSQLを増やす続きが、DBの学習です。
研究の補足と、よくある疑問
PostgreSQLが手元にない場合は?
PostgreSQL公式サイトにOS別のダウンロードとインストール案内があります。環境を用意する作業と、今回の集計を理解する作業は分けられます。所属先の学習用環境がある場合はそちらのルールを確認し、実務のDBへ試しに接続することは避けてください。
WHEREとFILTERは、同じ結果なら交換してよい?
すべての集計を同じ行へ絞る場合は同じ結果になることがあります。ただ、今回のように全件と成功だけを並べたい場合、WHEREで先に成功だけへ絞ると全件を数える集計にもその絞り込みが及びます。どの集計に条件を掛けたいかを先に決めます。
この3件から、サービスが速いか判断できる?
できません。今回は対象条件の違いを学ぶための架空データです。性能を判断するには、測定期間や負荷、失敗率、分布なども必要になります。この例で覚えるのは、数字を評価する前に集計した対象を確かめることです。
出典と確認した範囲(5資料)
出典と、読み取れる範囲
技術仕様と操作マニュアルを参照しています。それぞれの資料が説明する仕様と、その使い方を記事で結びつけています。
限界:SELECTとWHEREによる行の取得。記事の式はPostgreSQL向けで、Oracle等への無変更の適用は保証しない。
確認した範囲
2026-10-25に公開資料の該当箇所を確認。全マニュアルの精読・全環境での再現ではない。 確認日:2026-10-25
限界:count・sum・avgなどの集計。母集団や業務上の意味は利用者が定義する。
確認した範囲
2026-10-25に公開資料の該当箇所を確認。全マニュアルの精読・全環境での再現ではない。 確認日:2026-10-25
限界:count(*)、count(式)、avgのNULLの扱い。件数と平均の対象を区別する。
確認した範囲
2026-10-26に公開本文の該当仕様を確認。実機での手順再現とは別。 確認日:2026-10-26
限界:行取得と句の仕様。製品独自の拡張があり、SQLを全製品で同じと扱わない。
確認した範囲
2026-10-25に公開資料の該当箇所を確認。全マニュアルの精読・全環境での再現ではない。 確認日:2026-10-25
限界:SQLとCLI固有のドットコマンドを区別。この記事は既存DBの変更や削除を指示しない。
確認した範囲
2026-10-25に公開資料の該当箇所を確認。全マニュアルの精読・全環境での再現ではない。 確認日:2026-10-25
操作例は解説用です。製品・版・権限で表示や利用できる機能が異なります。マニュアルは操作の仕様を確かめる資料として参照しています。



