Pilih baris pertama di masing-masing KELOMPOK DENGAN kelompok?

Seperti judulnya, saya'd suka pilih baris pertama dari setiap baris yang dikelompokkan dengan GROUP BY.

Secara khusus, jika saya've punya pembelian meja yang terlihat seperti ini:

SELECT * FROM purchases;

My Output:

id | pelanggan | total
---+----------+------
1 | Joe | 5
2 | Sally | 3
3 | Joe | 2
4 | Sally | 1

I'd seperti untuk query untuk id dari pembelian terbesar (total) yang dibuat oleh masing-masing pelanggan. Sesuatu seperti ini:

SELECT FIRST(id), customer, FIRST(total)
FROM  purchases
GROUP BY customer
ORDER BY total DESC;

Output Yang Diharapkan:

PERTAMA(id) | pelanggan | PERTAMA(total)
----------+----------+-------------
1 | Joe | 5
2 | Sally | 3
Mengomentari pertanyaan (1)

Di PostgreSQL ini adalah biasanya sederhana dan lebih cepat (lebih optimasi kinerja di bawah ini):

PILIH BERBEDA PADA (pelanggan) 
id, pelanggan, total 
DARI pembelian 
ORDER OLEH pelanggan, total DESC, id;
Atau lebih pendek (jika tidak jelas) dengan nomor urut dari kolom output:
SELECT DISTINCT ON (2)
       id, customer, total
FROM   purchases
ORDER  BY 2, 3 DESC, 1;

Jika total dapat NULL (tidak't menyakiti dengan cara baik, tapi anda'll ingin mencocokkan indeks yang ada):

... 
ORDER OLEH pelanggan, total DESC NULLS TERAKHIR, id;
###Poin utama - [**`BERBEDA`**][1] adalah PostgreSQL ekstensi standar (di mana hanya `BERBEDA` secara keseluruhan `PILIH` daftar didefinisikan). - Daftar nomor ekspresi di `BERBEDA` klausa, gabungan turut mendefinisikan nilai duplikat. [Petunjuk:][2] > Jelas, dua baris yang dianggap berbeda jika mereka berbeda dalam setidaknya > satu kolom nilai. **Nilai-nilai Null dianggap sama dalam perbandingan ini.** Bold penekanan dari saya. - `BERBEDA` dapat dikombinasikan dengan **`ORDER BY`**. Terkemuka ekspresi harus sesuai terkemuka `BERBEDA` ekspresi dalam urutan yang sama. Anda dapat menambahkan *tambahan* ekspresi untuk `ORDER BY` untuk memilih baris tertentu dari masing-masing kelompok teman sebaya. Saya menambahkan `id` sebagai item terakhir untuk memecah ikatan: *"Memilih baris dengan terkecil `id` dari masing-masing kelompok sharing tertinggi `total`."* Untuk memesan hasil dengan cara yang tidak setuju dengan menentukan urutan pertama per kelompok, anda dapat menuliskan pertanyaan di atas di luar query dengan yang lain `ORDER BY`. Seperti: - https://stackoverflow.com/questions/9795660/postgresql-distinct-on-with-different-order-by/9796104#9796104 - Jika `total` dapat NULL, anda *mungkin* ingin turut terbesar dengan nilai non-null. Tambahkan **`NULLS TERAKHIR`** seperti yang ditunjukkan. Rincian: - https://stackoverflow.com/questions/9510509/postgresql-sort-by-datetime-asc-null-first/9511492#9511492 - **`PILIH` daftar** tidak dibatasi oleh ekspresi pada `BERBEDA` atau `ORDER BY` dengan cara apapun. (Tidak diperlukan dalam kasus sederhana di atas): - Anda *don't harus* termasuk salah satu ekspresi dalam `BERBEDA` atau `ORDER BY`. - Anda *bisa* memiliki ekspresi lain di `PILIH` daftar. Ini adalah berperan untuk menggantikan jauh lebih kompleks query dengan subqueries dan agregat / jendela fungsi. - Aku diuji dengan Postgres versi 8.3 – 12. Tetapi fitur ini telah ada setidaknya sejak versi 7.1, jadi pada dasarnya selalu. ##Indeks Yang *ideal* indeks untuk query di atas akan menjadi [multi-kolom index][3] yang mencakup semua tiga kolom dalam pencocokan berurutan dan sesuai dengan urutan:
CREATE INDEX purchases_3c_idx ON purchases (customer, total DESC, id);

Mungkin terlalu khusus. Tapi menggunakannya jika baca kinerja untuk query tertentu adalah sangat penting. Jika anda memiliki DESC NULLS TERAKHIR dalam query, menggunakan yang sama dalam indeks sehingga urutan pertandingan dan indeks berlaku.

Efektivitas / Kinerja optimasi

Menimbang biaya dan manfaat sebelum menciptakan disesuaikan indeks untuk setiap query. Potensi di atas indeks sebagian besar tergantung pada data distribusi. Indeks ini digunakan karena memberikan pre-data diurutkan. Di Postgres 9.2 atau lambat query juga bisa mendapatkan keuntungan dari aplikasi indeks hanya scan jika indeks lebih kecil dari tabel yang mendasari. Indeks harus dipindai secara keseluruhan, meskipun.

Komentar (7)
Larutan

Pada Oracle 9.2+ (tidak 8i+ sebagai awalnya dinyatakan), SQL Server 2005+, PostgreSQL 8.4+, DB2, Firebird 3.0+, Teradata, Sybase, Vertica:

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

Didukung oleh database:

Tapi anda perlu untuk menambahkan logika untuk memecahkan ikatan:

  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
Komentar (7)

Acuan

Pengujian yang paling menarik calon dengan Postgres 9.4 dan 9.5 dengan setengah realistis tabel 200k baris di pembelian dan 10k berbeda customer_id (avg. 20 baris per pelanggan).

Untuk Postgres 9.5 aku berlari 2 tes dengan efektif 86446 berbeda pelanggan. Lihat di bawah ini (avg. 2.3 baris per pelanggan).

Setup

Tabel utama

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
);

Saya menggunakan serial (PK kendala ditambahkan di bawah) dan integer customer_id karena itu's yang lebih khas setup. Juga ditambahkan some_column untuk menebus biasanya lebih kolom.

Data Dummy, PK, indeks khas meja juga memiliki beberapa mati tupel:

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;

pelanggan table - untuk segala query

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;

Saya tes kedua untuk 9.5 saya menggunakan setup yang sama, tetapi dengan random() * 100000 menghasilkan customer_id untuk mendapatkan hanya beberapa baris per customer_id.

Objek ukuran untuk tabel pembelian

Dihasilkan dengan query ini.

               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 |              |

Pertanyaan

1. row_number() di CTE, (lihat jawaban lain)

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;

2. row_number() dalam subquery (saya optimasi)

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;

3. BERBEDA (lihat jawaban lain)

SELECT DISTINCT ON (customer_id)
       id, customer_id, total
FROM   purchases
ORDER  BY customer_id, total DESC, id;

4. rCTE dengan LATERAL subquery (lihat di sini)

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;

5. pelanggan tabel dengan LATERAL (lihat di sini)

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;

6. array_agg() dengan ORDER BY (lihat jawaban lain)

SELECT (array_agg(id ORDER BY total DESC))[1] AS id
     , customer_id
     , max(total) AS total
FROM   purchases
GROUP  BY customer_id;

Hasil

Waktu eksekusi untuk di atas query dengan JELASKAN MENGANALISIS (dan semua pilihan off), terbaik dari 5 berjalan.

Semua query yang digunakan Indeks Hanya Memindai pada purchases2_3c_idx (di antara langkah-langkah lain). Beberapa dari mereka hanya untuk ukuran yang lebih kecil dari index, yang lain lebih efektif.

A. Postgres 9.4 dengan 200k baris dan ~ 20 per customer_id

1. 273.274 ms  
2. 194.572 ms  
3. 111.067 ms  
4.  92.922 ms  
5.  37.679 ms  -- winner
6. 189.495 ms

B. sama dengan Postgres 9.5

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  

C. Sama seperti B., tapi dengan ~ 2.3 baris per customer_id

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

Terkait tolok ukur

Berikut ini adalah baru dengan "ogr" pengujian dengan 10M baris dan 60k unik "pelanggan" di Postgres 11.5 (saat ini sebagai Sep. 2019). Hasil tersebut masih sejalan dengan apa yang telah kita lihat sejauh ini:

Asli (usang) acuan dari 2011

Aku berlari tiga tes dengan PostgreSQL 9.1 pada kehidupan nyata tabel 65579 baris dan satu kolom btree indeks pada masing-masing dari tiga kolom yang terlibat dan mengambil yang terbaik waktu pelaksanaan 5 berjalan. Membandingkan @OMGPonies' pertama query (A) untuk di atas BERBEDA solusi (B):

  1. Pilih seluruh tabel, hasil di 5958 baris dalam kasus ini.

A: ms 567.218 B: 386.673 ms

  1. Menggunakan kondisi DIMANA pelanggan ANTARA x DAN y yang mengakibatkan 1000 baris.

A: ms 249.136 B: 55.111 ms

  1. Pilih satu pelanggan dengan di MANA pelanggan = x.

A: ms 0.143 B: 0.072 ms

Tes yang sama berulang-ulang dengan indeks dijelaskan dalam jawaban lain

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
Komentar (7)

Ini adalah umum [tag:terbesar-n-per-group] masalah ini, yang telah diuji dengan baik dan sangat dioptimalkan solusi. Secara pribadi saya lebih suka kiri bergabung solusi oleh Bill Karwin (asli posting dengan banyak solusi lain).

Perhatikan bahwa banyak solusi untuk masalah umum ini dapat mengejutkan ditemukan di salah satu dari sebagian besar sumber-sumber resmi, MySQL manual! Lihat contoh-Contoh Pertanyaan Umum :: Baris Holding Kelompok-bijak Maksimum dari sebuah Kolom Tertentu.

Komentar (6)

Di Postgres anda dapat menggunakan array_agg seperti ini:

SELECT  customer,
        (array_agg(id ORDER BY total DESC))[1],
        max(total)
FROM purchases
GROUP BY customer

Ini akan memberikan anda id masing-masing pelanggan's pembelian terbesar.

Beberapa hal yang perlu diperhatikan:

  • array_agg adalah sebuah fungsi agregat, sehingga bekerja dengan GROUP BY.
  • array_agg memungkinkan anda menentukan pemesanan scoped hanya itu sendiri, jadi itu tidak jadi't membatasi struktur keseluruhan query. Ada juga sintaks untuk cara mengurutkan NULLs, jika anda perlu untuk melakukan sesuatu yang berbeda dari default.
  • Setelah kita membangun array, kita ambil elemen pertama. (Postgres array 1-diindeks, bukan 0-diindeks).
  • Anda bisa menggunakan array_agg dengan cara yang sama untuk ketiga kolom output, tetapi max(total) lebih sederhana.
  • Tidak seperti BERBEDA, menggunakan array_agg memungkinkan anda tetap GROUP BY, jika anda ingin bahwa untuk alasan lain.
Komentar (0)

Solusi ini sangat tidak efisien seperti yang ditunjukkan oleh Erwin, karena kehadiran SubQs

select * from purchases p1 where total in
(select max(total) from purchases where p1.customer=customer) order by total desc;
Komentar (4)

Sangat cepat solusi

SELECT a.* 
FROM
    purchases a 
    JOIN ( 
        SELECT customer, min( id ) as id 
        FROM purchases 
        GROUP BY customer 
    ) b USING ( id );

dan benar-benar sangat cepat jika tabel diindeks oleh id:

create index purchases_id on purchases (id);
Komentar (3)

Saya menggunakan cara ini (postgresql saja): 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
);

Kemudian misalnya anda harus bekerja hampir seperti ini:

SELECT FIRST(id), customer, FIRST(total)
FROM  purchases
GROUP BY customer
ORDER BY FIRST(total) DESC;

PERINGATAN: mengabaikan's NULL baris


Edit 1 - Menggunakan postgres ekstensi bukan

Sekarang saya menggunakan cara ini: http://pgxn.org/dist/first_last_agg/

Untuk install di 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'

It's postgres ekstensi yang memberikan anda pertama dan terakhir fungsi; rupanya lebih cepat dari cara di atas.


Edit 2 - Pengurutan dan penyaringan

Jika anda menggunakan fungsi agregat (seperti ini), anda dapat memesan hasil, tanpa perlu memiliki data-data yang sudah terurut:

http://www.postgresql.org/docs/current/static/sql-expressions.html#SYNTAX-AGGREGATES

Sehingga setara dengan contoh, dengan memesan akan menjadi sesuatu seperti:

SELECT first(id order by id), customer, first(total order by id)
  FROM purchases
 GROUP BY customer
 ORDER BY first(total);

Tentu saja anda dapat memesan dan filter seperti yang anda anggap cocok dalam agregat; it's sangat kuat sintaks.

Komentar (1)

Gunakan ARRAY_AGG fungsi untuk PostgreSQL, U-SQL, IBM DB2, dan Google BigQuery SQL:

SELECT customer, (ARRAY_AGG(id ORDER BY total DESC))[1], MAX(total)
FROM purchases
GROUP BY customer
Komentar (0)

Query:

SELECT purchases.*
FROM purchases
LEFT JOIN purchases as p 
ON 
  p.customer = purchases.customer 
  AND 
  purchases.total < p.total
WHERE p.total IS NULL

BAGAIMANA ITU BEKERJA! (Saya've telah ada)

Kami ingin memastikan bahwa kami hanya memiliki total tertinggi untuk setiap pembelian.


Beberapa Teori Barang (melewatkan bagian ini jika anda hanya ingin memahami query)

Biarkan Total menjadi sebuah fungsi T(pelanggan,id) di mana ia mengembalikan nilai yang diberikan nama dan id Untuk membuktikan bahwa yang diberikan total (T(pelanggan,id)) adalah yang tertinggi yang kita miliki untuk membuktikan bahwa Kami ingin membuktikan baik

  • ∀x T(pelanggan,id) > T(pelanggan,x) (jumlah ini lebih tinggi daripada semua yang lain total untuk pelanggan)

ATAU

  • ¬∃x T(pelanggan, id) < T(pelanggan, x) (tidak ada tidak ada yang lebih tinggi total bahwa pelanggan)

Pendekatan pertama akan membutuhkan kami untuk mendapatkan semua catatan untuk nama itu yang saya tidak suka.

Yang kedua akan memerlukan sebuah cara cerdas untuk mengatakan tidak ada catatan lebih tinggi dari yang satu ini.


Kembali ke SQL

Jika kita meninggalkan bergabung pada tabel nama dan jumlah yang kurang dari bergabung dengan tabel:

      LEFT JOIN purchases as p 
      ON 
      p.customer = purchases.customer 
      AND 
      purchases.total < p.total

kami memastikan bahwa semua catatan yang memiliki rekor lain dengan total lebih tinggi untuk pengguna yang sama untuk bergabung:

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

Yang akan membantu kita filter untuk total tertinggi untuk setiap pembelian dengan tidak ada pengelompokan yang dibutuhkan:

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

Dan yang's jawaban yang kita butuhkan.

Komentar (0)

Di SQL Server anda dapat melakukan ini:

SELECT *
FROM (
SELECT ROW_NUMBER()
OVER(PARTITION BY customer
ORDER BY total DESC) AS StRank, *
FROM Purchases) n
WHERE StRank = 1

Penjelasan:di Sini Group by dilakukan atas dasar pelanggan dan kemudian memesannya dengan total kemudian masing-masing kelompok tersebut diberikan nomor seri seperti StRank dan kami mengambil keluar pertama 1 pelanggan dan StRank 1

Komentar (1)

Diterima OMG Kuda', "Didukung oleh database" solusi memiliki kecepatan yang baik dari pengujian saya.

Di sini saya menyediakan satu pendekatan, tetapi lebih lengkap dan bersih-solusi database. Ikatan dianggap (misalnya keinginan untuk mendapatkan hanya satu baris untuk setiap pelanggan, bahkan beberapa catatan untuk max total per pelanggan), dan lain pembelian bidang (misalnya purchase_payment_id) akan dipilih untuk baris yang cocok dalam pembelian meja.

Didukung oleh database:

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

Permintaan ini cukup cepat terutama ketika ada composite seperti (pelanggan, total) pada pembelian meja.

Komentar:

  1. t1, t2 adalah subquery alias yang bisa dilepas tergantung pada database.

  2. Peringatan: menggunakan (...) klausul ini saat ini tidak didukung di MS-SQL dan Oracle db seperti ini edit pada Januari 2017. Anda harus memperluas diri untuk misal pada t2.id = pembelian.id dll. MENGGUNAKAN sintaks bekerja di SQLite, MySQL dan PostgreSQL.

Komentar (0)

Untuk SQl Server cara yang paling efisien adalah:

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   --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

dan don't lupa untuk membuat indeks berkerumun untuk digunakan kolom

Komentar (0)
  • Jika anda ingin memilih salah (dengan beberapa syarat tertentu) baris dari set agregat baris.

  • Jika anda ingin menggunakan yang lain (jumlah/rata) agregasi fungsi selain untuk max/min. Dengan demikian anda tidak dapat menggunakan petunjuk dengan BERBEDA

Anda dapat menggunakan subquery:

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

Anda dapat mengganti jumlah = MAX( tf.jumlah ) dengan kondisi apapun yang anda inginkan dengan satu batasan: Ini subquery tidak harus kembali lebih dari satu baris

Tetapi jika anda ingin untuk melakukan hal-hal seperti itu anda mungkin mencari fungsi window

Komentar (0)