タイトルが示すように、GROUP BY
でグループ化された各行セットの最初の行を選択したいと思います。
具体的に言うと、purchases
テーブルがこのようになっているとします。
SELECT * FROM purchases;
私の出力:
id |お客様total --- + ---------- + ------ 1 |ジョー5 2 |サリー3 3 |ジョー2 4 |サリー1
id
ごとに行われた最大の購入額(total
)のcustomer
を照会します。このようなもの:
SELECT FIRST(id), customer, FIRST(total)
FROM purchases
GROUP BY customer
ORDER BY total DESC;
予想される出力:
FIRST(id)|お客様最初(合計) ---------- + ---------- + ------------- 1 |ジョー5 2 |サリー3
WITH summary AS (
SELECT p.id,
p.customer,
p.total,
ROW_NUMBER() OVER(PARTITION BY p.customer
ORDER BY p.total DESC) AS rk
FROM PURCHASES p)
SELECT s.*
FROM summary s
WHERE s.rk = 1
しかし、関係を破るためのロジックを追加する必要があります。
SELECT MIN(x.id), -- change to MAX if you want the highest
x.customer,
x.total
FROM PURCHASES x
JOIN (SELECT p.customer,
MAX(total) AS max_total
FROM PURCHASES p
GROUP BY p.customer) y ON y.customer = x.customer
AND y.max_total = x.total
GROUP BY x.customer, x.total
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
と組み合わせることができます。先頭の式は、先頭のDISTINCT ON
式と同じ順序で一致する必要があります。additional式をORDER BY
に追加して、ピアの各グループから特定の行を選択できます。タイを壊す最後の項目としてid
を追加しました。
"最高のid
を共有する各グループから最小のtotal
の行を選択します。"
グループごとに最初を決定する並べ替え順序と一致しない方法で結果を並べ替えるには、別のORDER BY
を使用して外側のクエリで上記のクエリをネストできます。のような:
total
がNULLになる可能性がある場合は、mostおそらくnull以外の値が最大の行が必要です。示されているようにNULLS LAST
を追加します。詳細:
SELECT
list は、DISTINCT ON
またはORDER BY
の式による制約を受けません。 (上記の単純なケースでは必要ありません):
toにDISTINCT ON
またはORDER BY
の式を含める必要はありません。
canは、SELECT
リストに他の式を含めます。これは、より複雑なクエリをサブクエリと集計/ウィンドウ関数で置き換えるための手段です。
Postgresバージョン8.3〜12でテストしましたが、この機能は少なくともバージョン7.1以降に存在しているため、基本的には常にです。
上記のクエリのperfectインデックスは、 マルチカラムインデックス マッチングシーケンスとマッチングソート順で3つのカラムすべてにまたがります。
CREATE INDEX purchases_3c_idx ON purchases (customer, total DESC, id);
専門的すぎるかもしれません。ただし、特定のクエリの読み取りパフォーマンスが重要な場合に使用します。クエリにDESC NULLS LAST
がある場合は、インデックスで同じものを使用して、ソート順が一致し、インデックスが適用されるようにします。
クエリごとにカスタマイズされたインデックスを作成する前に、コストとメリットを検討してください。上記のインデックスの可能性は、データ分布に大きく依存します。
事前にソートされたデータを配信するため、インデックスが使用されます。 Postgres 9.2以降では、クエリがインデックスのみスキャンの恩恵を受けることもできます。インデックスが基になるテーブルより小さい場合。ただし、インデックス全体をスキャンする必要があります。
few顧客ごとの行(列customer
のカーディナリティが高い)の場合、これは非常に効率的です。とにかくソートされた出力が必要な場合はさらにそうです。顧客あたりの行数が増えると、メリットは小さくなります。
理想的には、RAMの関連するソート手順を処理し、ディスクに流出しないために十分な work_mem
があります。ただし、一般的にwork_mem
tooを高く設定すると、悪影響が生じる可能性があります。非常に大きなクエリの場合はSET LOCAL
を検討してください。 EXPLAIN ANALYZE
で必要な量を見つけてください。ソート手順で「Disk:」と記載されている場合は、さらに必要があることを示しています。
many顧客ごとの行(列customer
のカーディナリティが低い)の場合、 loose index scan (別名「スキップスキャン」)は(はるかに)より効率的ですが、Postgres 12まで実装されていません(インデックスのみのスキャンの実装はPostgres 13で開発中です。 here および here 。
今のところ、より高速なクエリ手法がこれに代わるものです。特に、一意の顧客を保持する別のテーブルがある場合は、これが一般的な使用例です。しかし、そうしない場合も:
ここには、今では時代遅れの簡単なベンチマークがありました。 この個別の回答 の詳細なベンチマークに置き換えました。
Postgresを使用した最も興味深い候補のテスト9.4および9.5200k行の中間的な現実的なテーブルin purchases
および10k個別customer_id
(avg。顧客ごとに20行)。
Postgres 9.5の場合、事実上86446人の顧客を対象に2回目のテストを実行しました。下記を参照してください(avg。顧客あたり2.3行)。
メインテーブル
CREATE TABLE purchases (
id serial
, customer_id int -- REFERENCES customer
, total int -- could be amount of money in Cent
, some_column text -- to make the row bigger, more realistic
);
serial
(以下に追加されたPK制約)と整数customer_id
を使用します。これはより一般的なセットアップだからです。また、通常より多くの列を補うためにsome_column
を追加しました。
ダミーデータ、PK、インデックス-典型的なテーブルにもいくつかのデッドタプルがあります:
INSERT INTO purchases (customer_id, total, some_column) -- insert 200k rows
SELECT (random() * 10000)::int AS customer_id -- 10k customers
, (random() * random() * 100000)::int AS total
, 'note: ' || repeat('x', (random()^2 * random() * random() * 500)::int)
FROM generate_series(1,200000) g;
ALTER TABLE purchases ADD CONSTRAINT purchases_id_pkey PRIMARY KEY (id);
DELETE FROM purchases WHERE random() > 0.9; -- some dead rows
INSERT INTO purchases (customer_id, total, some_column)
SELECT (random() * 10000)::int AS customer_id -- 10k customers
, (random() * random() * 100000)::int AS total
, 'note: ' || repeat('x', (random()^2 * random() * random() * 500)::int)
FROM generate_series(1,20000) g; -- add 20k to make it ~ 200k
CREATE INDEX purchases_3c_idx ON purchases (customer_id, total DESC, id);
VACUUM ANALYZE purchases;
customer
テーブル-優れたクエリ用
CREATE TABLE customer AS
SELECT customer_id, 'customer_' || customer_id AS customer
FROM purchases
GROUP BY 1
ORDER BY 1;
ALTER TABLE customer ADD CONSTRAINT customer_customer_id_pkey PRIMARY KEY (customer_id);
VACUUM ANALYZE customer;
私の2番目のテスト9.5では同じセットアップを使用しましたが、customer_id
ごとに数行しか取得しないようにrandom() * 100000
を使用してcustomer_id
を生成しました。
purchases
のオブジェクトサイズこのクエリ で生成されます。
what | bytes/ct | bytes_pretty | bytes_per_row
-----------------------------------+----------+--------------+---------------
core_relation_size | 20496384 | 20 MB | 102
visibility_map | 0 | 0 bytes | 0
free_space_map | 24576 | 24 kB | 0
table_size_incl_toast | 20529152 | 20 MB | 102
indexes_size | 10977280 | 10 MB | 54
total_size_incl_toast_and_indexes | 31506432 | 30 MB | 157
live_rows_in_text_representation | 13729802 | 13 MB | 68
------------------------------ | | |
row_count | 200045 | |
live_tuples | 200045 | |
dead_tuples | 19955 | |
row_number()
、( 他の回答を参照 )WITH cte AS (
SELECT id, customer_id, total
, row_number() OVER(PARTITION BY customer_id ORDER BY total DESC) AS rn
FROM purchases
)
SELECT id, customer_id, total
FROM cte
WHERE rn = 1;
row_number()
(私の最適化)SELECT id, customer_id, total
FROM (
SELECT id, customer_id, total
, row_number() OVER(PARTITION BY customer_id ORDER BY total DESC) AS rn
FROM purchases
) sub
WHERE rn = 1;
DISTINCT ON
( 他の回答を参照 )SELECT DISTINCT ON (customer_id)
id, customer_id, total
FROM purchases
ORDER BY customer_id, total DESC, id;
LATERAL
サブクエリを使用したrCTE( こちらを参照 )WITH RECURSIVE cte AS (
( -- parentheses required
SELECT id, customer_id, total
FROM purchases
ORDER BY customer_id, total DESC
LIMIT 1
)
UNION ALL
SELECT u.*
FROM cte c
, LATERAL (
SELECT id, customer_id, total
FROM purchases
WHERE customer_id > c.customer_id -- lateral reference
ORDER BY customer_id, total DESC
LIMIT 1
) u
)
SELECT id, customer_id, total
FROM cte
ORDER BY customer_id;
customer
を含むLATERAL
テーブル( ここを参照 )SELECT l.*
FROM customer c
, LATERAL (
SELECT id, customer_id, total
FROM purchases
WHERE customer_id = c.customer_id -- lateral reference
ORDER BY total DESC
LIMIT 1
) l;
array_agg()
with ORDER BY
( 他の回答を参照 )SELECT (array_agg(id ORDER BY total DESC))[1] AS id
, customer_id
, max(total) AS total
FROM purchases
GROUP BY customer_id;
EXPLAIN ANALYZE
(およびすべてのオプションoff)、ベストオブ5ランを使用した上記のクエリの実行時間。
Allクエリは、Index Only Scanon purchases2_3c_idx
(他の手順の中で)を使用しました。インデックスのサイズを小さくするためのものもあれば、より効果的なものもあります。
customer_id
あたり20個のPostgres 9.41. 273.274 ms
2. 194.572 ms
3. 111.067 ms
4. 92.922 ms
5. 37.679 ms -- winner
6. 189.495 ms
1. 288.006 ms
2. 223.032 ms
3. 107.074 ms
4. 78.032 ms
5. 33.944 ms -- winner
6. 211.540 ms
customer_id
あたり約2.3行1. 381.573 ms
2. 311.976 ms
3. 124.074 ms -- winner
4. 710.631 ms
5. 311.976 ms
6. 421.679 ms
私はPostgreSQLで3つのテストを実行しました9.165579行の実生活のテーブルと、関連する3つの列のそれぞれの単一列btreeインデックスで、最高の実行時間を取りました5回実行します。
比較 @ OMGPonies ' 最初のクエリ(A
)と 上記DISTINCT ON
ソリューション (B
):
テーブル全体を選択すると、この場合は5958行になります。
A: 567.218 ms
B: 386.673 ms
条件WHERE customer BETWEEN x AND y
を使用すると、1000行になります。
A: 249.136 ms
B: 55.111 ms
WHERE customer = x
を持つ単一の顧客を選択します。
A: 0.143 ms
B: 0.072 ms
他の回答で説明されているインデックスで同じテストが繰り返されました
CREATE INDEX purchases_3c_idx ON purchases (customer, total DESC, id);
1A: 277.953 ms
1B: 193.547 ms
2A: 249.796 ms -- special index not used
2B: 28.679 ms
3A: 0.120 ms
3B: 0.048 ms
これは一般的な グループあたりの最大数 問題で、すでによくテストされており、非常に高い{ 最適化されたソリューション です。私は個人的には Bill Karwinによる左結合ソリューション ( 他の多くの解決策を含むオリジナルの投稿 )を好みます。
この一般的な問題に対する解決策の束は、驚くべきことに、最も公式な情報源の1つ、 MySQLマニュアル !にあります。 一般的なクエリの例::特定の列のグループごとの最大値を保持する行 を参照してください。
Postgresでは、次のようにarray_agg
を使うことができます。
SELECT customer,
(array_agg(id ORDER BY total DESC))[1],
max(total)
FROM purchases
GROUP BY customer
これはあなたに各顧客の最大購入のid
を与えるでしょう。
注意すべき点がいくつかあります。
array_agg
は集約関数なので、GROUP BY
と連携します。array_agg
を使用すると、それ自体を範囲とする順序付けを指定できます。したがって、クエリ全体の構造に制約はありません。デフォルトとは異なる処理を行う必要がある場合は、NULLのソート方法に関する構文もあります。array_agg
を使用できますが、max(total)
のほうが簡単です。DISTINCT ON
とは異なり、array_agg
を使用すると、他の理由でGROUP BY
を保持できます。SubQが存在するため、Erwinが指摘したように、解はあまり効率的ではありません
select * from purchases p1 where total in
(select max(total) from purchases where p1.customer=customer) order by total desc;
私はこの方法を使います(postgresqlのみ): https://wiki.postgresql.org/wiki/First/last_%28aggregate%29
-- Create a function that always returns the first non-NULL item
CREATE OR REPLACE FUNCTION public.first_agg ( anyelement, anyelement )
RETURNS anyelement LANGUAGE sql IMMUTABLE STRICT AS $$
SELECT $1;
$$;
-- And then wrap an aggregate around it
CREATE AGGREGATE public.first (
sfunc = public.first_agg,
basetype = anyelement,
stype = anyelement
);
-- Create a function that always returns the last non-NULL item
CREATE OR REPLACE FUNCTION public.last_agg ( anyelement, anyelement )
RETURNS anyelement LANGUAGE sql IMMUTABLE STRICT AS $$
SELECT $2;
$$;
-- And then wrap an aggregate around it
CREATE AGGREGATE public.last (
sfunc = public.last_agg,
basetype = anyelement,
stype = anyelement
);
それならあなたの例は ほぼ のように動作するはずです:
SELECT FIRST(id), customer, FIRST(total)
FROM purchases
GROUP BY customer
ORDER BY FIRST(total) DESC;
警告:NULL行を無視します
今私はこのように使用します: http://pgxn.org/dist/first_last_agg/
Ubuntu 14.04にインストールするには:
apt-get install postgresql-server-dev-9.3 git build-essential -y
git clone git://github.com/wulczer/first_last_agg.git
cd first_last_app
make && Sudo make install
psql -c 'create extension first_last_agg'
それはあなたに最初と最後の機能を与えるpostgresの拡張です。上記の方法よりも明らかに速いです。
(これらのように)集約関数を使用する場合は、データをすでに順序付けしておく必要なく、結果を順序付けることができます。
http://www.postgresql.org/docs/current/static/sql-expressions.html#SYNTAX-AGGREGATES
そのため、順序付きの同等の例は次のようになります。
SELECT first(id order by id), customer, first(total order by id)
FROM purchases
GROUP BY customer
ORDER BY first(total);
もちろん、あなたが集合体に収まると思うように、あなたは順序付けしてフィルターをかけることができます。とても強力な構文です。
非常に速い解決策
SELECT a.*
FROM
purchases a
JOIN (
SELECT customer, min( id ) as id
FROM purchases
GROUP BY customer
) b USING ( id );
テーブルがidでインデックスされているなら、本当にとても速いです
create index purchases_id on purchases (id);
クエリ:
SELECT purchases.*
FROM purchases
LEFT JOIN purchases as p
ON
p.customer = purchases.customer
AND
purchases.total < p.total
WHERE p.total IS NULL
どのように動作しますか。 (私はそこに行ったことがある)
私達は私達が私達が各購入のための最も高い合計だけがあることを確かめたいと思います。
理論上のもの (クエリを理解したいだけの場合はこの部分を飛ばしてください)
Totalを関数T(customer、id)とします。ここで、与えられた合計(T(customer、id))が最高であることを証明するには、どちらかを証明したい
OR
最初のアプローチでは、その名前のレコードをすべて入手する必要がありますが、これはあまり好きではありません。
2番目のものはこれより高い記録がないことを言うために賢い方法を必要とするでしょう。
SQLに戻る
名前を付けてテーブルを結合したままにし、合計が結合したテーブルより少ない場合:
LEFT JOIN purchases as p
ON
p.customer = purchases.customer
AND
purchases.total < p.total
同じユーザーの合計が大きい別のレコードを持つすべてのレコードが結合されるようにします。
purchases.id, purchases.customer, purchases.total, p.id, p.customer, p.total
1 , Tom , 200 , 2 , Tom , 300
2 , Tom , 300
3 , Bob , 400 , 4 , Bob , 500
4 , Bob , 500
5 , Alice , 600 , 6 , Alice , 700
6 , Alice , 700
これは、グループ化を必要とせずに、購入ごとに最高の合計を絞り込むのに役立ちます。
WHERE p.total IS NULL
purchases.id, purchases.name, purchases.total, p.id, p.name, p.total
2 , Tom , 300
4 , Bob , 500
6 , Alice , 700
そしてそれが私たちが必要とする答えです。
PostgreSQL 、 U-SQL 、 IBM DB2 、および Google BigQuery SQL :にARRAY_AGG
関数を使用します。
SELECT customer, (ARRAY_AGG(id ORDER BY total DESC))[1], MAX(total)
FROM purchases
GROUP BY customer
承認されたOMG Poniesの "Supported by any database"ソリューションは私のテストではスピードが速いです。
ここで私は同じアプローチを提供しますが、より完全でクリーンなany-databaseソリューションを提供します。同順位が考慮され(各顧客に対して1行のみ、顧客ごとの最大合計に対して複数のレコードを取得することを望む)、他の購入フィールド(例えば、purchase_payment_id)が購入テーブルの実際に一致する行に対して選択される。
どのデータベースでもサポートされています。
select * from purchase
join (
select min(id) as id from purchase
join (
select customer, max(total) as total from purchase
group by customer
) t1 using (customer, total)
group by customer
) t2 using (id)
order by customer
特に購入テーブルに(customer、total)のような複合インデックスがある場合、このクエリはかなり高速です。
リマーク:
t1、t2はデータベースによっては削除できる副照会の別名です。
警告 :2017年1月のこの編集時点では、using (...)
句はMS-SQLとOracle dbでは現在サポートされていません。 on t2.id = purchase.id
などUSING構文はSQLite、MySQLそしてPostgreSQLで動作します。
SQL Serverでは、これを実行できます。
SELECT *
FROM (
SELECT ROW_NUMBER()
OVER(PARTITION BY customer
ORDER BY total DESC) AS StRank, *
FROM Purchases) n
WHERE StRank = 1
説明:ここで グループ化 は顧客に基づいて行われ、それから合計でそれを注文します、そしてそのような各グループはStRankとして通し番号を与えられて、StRankが1である最初の1人の顧客を取り出します。
SQl Serverの最も効率的な方法は次のとおりです。
with
ids as ( --condition for split table into groups
select i from (values (9),(12),(17),(18),(19),(20),(22),(21),(23),(10)) as v(i)
)
,src as (
select * from yourTable where <condition> --use this as filter for other conditions
)
,joined as (
select tops.* from ids
cross apply --it`s like for each rows
(
select top(1) *
from src
where CommodityId = ids.i
) as tops
)
select * from joined
そして使用された列のためのクラスタ化インデックスを作成することを忘れないで
集約行のセットから(特定の条件によって)任意の行を選択したい場合。
sum/avg
に加えて別の(max/min
)集計関数を使いたい場合そのため、DISTINCT ON
で手がかりを使うことはできません
次の副問い合わせを使うことができます。
SELECT
(
SELECT **id** FROM t2
WHERE id = ANY ( ARRAY_AGG( tf.id ) ) AND amount = MAX( tf.amount )
) id,
name,
MAX(amount) ma,
SUM( ratio )
FROM t2 tf
GROUP BY name
amount = MAX( tf.amount )
は、1つの制限付きで任意の条件に置き換えることができます。この副問合せは、複数の行を返さないでください。
しかし、そのようなことをしたいのなら、おそらく ウィンドウ関数を探しています