DISTINCT ON
PostgreSQL では、通常、これが最も簡単で最速です。 。
(特定のワークロードのパフォーマンスの最適化については、以下を参照してください。)
SELECT DISTINCT ON (customer)
id, customer, total
FROM purchases
ORDER BY customer, total DESC, id;
または、出力列の序数で(明確でない場合は)短くします:
SELECT DISTINCT ON (2)
id, customer, total
FROM purchases
ORDER BY 2, 3 DESC, 1;
total
の場合 NULLにすることができます(どちらの方法でも問題はありませんが、既存のインデックスと一致させる必要があります):
...
ORDER BY customer, total DESC NULLS LAST, id;
主なポイント
DISTINCT ON
は標準のPostgreSQL拡張機能です(DISTINCT
のみ) 全体としてSELECT
リストが定義されています。
DISTINCT ON
に任意の数の式をリストします 句では、結合された行の値が重複を定義します。マニュアル:
明らかに、少なくとも1つの列の値が異なる場合、2つの行は別個のものと見なされます。 この比較では、ヌル値は等しいと見なされます。
大胆な強調鉱山。
DISTINCT ON
ORDER BY
と組み合わせることができます 。 ORDER BY
の先頭の式 DISTINCT ON
の式のセットに含まれている必要があります 、ただし、それらの間で自由に順序を並べ替えることができます。例。
追加追加できます ORDER BY
への式 ピアの各グループから特定の行を選択します。または、マニュアルに記載されているように:
DISTINCT ON
式は、左端のORDER BY
と一致する必要があります 式。ORDER BY
句には通常、各DISTINCT ON
内の行の望ましい優先順位を決定する追加の式が含まれます グループ。
id
を追加しました 結びつきを断ち切る最後の項目として:
"id
が最小の行を選択してください 最高のtotal
を共有する各グループから 。"
グループごとの最初の並べ替え順序と一致しない方法で結果を並べ替えるには、別のORDER BY
を使用して外部クエリのクエリの上にネストします。 。例。
total
の場合 NULLにすることもできますが、おそらく null以外の値が最大の行が必要です。 NULLS LAST
を追加します 示されているように。参照:
- 列ASCで並べ替えますが、最初にNULL値を使用しますか?
SELECT
リスト DISTINCT ON
の式による制約を受けません またはORDER BY
とにかく。 (上記の単純なケースでは必要ありません):
-
する必要はありません
DISTINCT ON
に任意の式を含めます またはORDER BY
。 -
できます
SELECT
に他の式を含める リスト。これは、はるかに複雑なクエリをサブクエリや集計/ウィンドウ関数に置き換えるのに役立ちます。
私はPostgresバージョン8.3– 13でテストしましたが、この機能は少なくともバージョン7.1から存在しているので、基本的には常にそうです。
インデックス
完璧 上記のクエリのインデックスは、一致する順序で、一致する並べ替え順序で3つの列すべてにまたがる複数列のインデックスになります。
CREATE INDEX purchases_3c_idx ON purchases (customer, total DESC, id);
専門的すぎるかもしれません。ただし、特定のクエリの読み取りパフォーマンスが重要な場合に使用してください。 DESC NULLS LAST
がある場合 クエリでは、同じものをインデックスで使用して、並べ替え順序が一致し、インデックスが適用されるようにします。
有効性/パフォーマンスの最適化
クエリごとに調整されたインデックスを作成する前に、コストとメリットを比較検討してください。上記のインデックスの可能性は、データ分散に大きく依存します 。
インデックスは、事前にソートされたデータを提供するために使用されます。 Postgres 9.2以降では、クエリはインデックスのみのスキャンの恩恵を受けることもできます インデックスが基になるテーブルよりも小さい場合。ただし、インデックス全体をスキャンする必要があります。
少数の場合 顧客あたりの行数 (列customer
のカーディナリティが高い )、これは非常に効率的です。とにかくソートされた出力が必要な場合はさらにそうです。顧客あたりの行数が増えると、メリットは減少します。
理想的には、十分な work_mem
があります。 関係するソートステップをRAMで処理し、ディスクに流出しないようにします。ただし、通常はwork_mem
を設定します あまりにも 高は悪影響を与える可能性があります。 SET LOCAL
を検討してください 非常に大きなクエリの場合。 EXPLAIN ANALYZE
で必要な金額を見つけてください 。 「ディスク:」の言及 「並べ替え手順で、さらに必要なことを示します:
- Linux上のPostgreSQLの構成パラメーターwork_mem
- ORDERBYの日付とテキストを使用して単純なクエリを最適化する
多くの場合 顧客あたりの行数 (列customer
のカーディナリティが低い )、ルーズインデックススキャン (別名「スキップスキャン」)は(はるかに)効率的ですが、Postgres 14までは実装されていません(インデックスのみのスキャンの実装はPostgres 15用に開発中です。こことここを参照してください。)
For現在、より高速なクエリ手法があります これの代わりに。特に、固有の顧客を保持する別のテーブルがある場合は、これが一般的なユースケースです。ただし、そうでない場合も:
- SELECT DISTINCTは、PostgreSQLのテーブルで予想よりも遅いです
- GROUP BYクエリを最適化して、ユーザーごとに最新の行を取得します
- グループごとの最大クエリを最適化する
- 行ごとに最後のN個の関連行をクエリする
ベンチマーク
別の回答を参照してください。