PostgreSQL
 sql >> Teknologi Basis Data >  >> RDS >> PostgreSQL

Dapatkan nilai dari baris pertama dan terakhir per grup

Ada berbagai cara yang lebih sederhana dan lebih cepat.

2x DISTINCT ON

SELECT *
FROM  (
   SELECT DISTINCT ON (name)
          name, week AS first_week, value AS first_val
   FROM   tbl
   ORDER  BY name, week
   ) f
JOIN (
   SELECT DISTINCT ON (name)
          name, week AS last_week, value AS last_val
   FROM   tbl
   ORDER  BY name, week DESC
   ) l USING (name);

Atau lebih pendek:

SELECT *
FROM  (SELECT DISTINCT ON (1) name, week AS first_week, value AS first_val FROM tbl ORDER BY 1,2) f
JOIN  (SELECT DISTINCT ON (1) name, week AS last_week , value AS last_val  FROM tbl ORDER BY 1,2 DESC) l USING (name);

Sederhana dan mudah dimengerti. Juga tercepat dalam tes lama saya. Penjelasan mendetail untuk DISTINCT ON :

  • Pilih baris pertama di setiap grup GROUP BY?

2x fungsi jendela, 1x DISTINCT ON

SELECT DISTINCT ON (name)
       name, week AS first_week, value AS first_val
     , first_value(week)  OVER w AS last_week
     , first_value(value) OVER w AS last_value
FROM   tbl t
WINDOW w AS (PARTITION BY name ORDER BY week DESC)
ORDER  BY name, week;

WINDOW yang eksplisit klausa hanya mempersingkat kode, tidak berpengaruh pada kinerja.

first_value() dari tipe komposit

Fungsi agregat min() atau max() tidak menerima tipe komposit sebagai input. Anda harus membuat fungsi agregat khusus (yang tidak terlalu sulit).
Tetapi fungsi jendela first_value() dan last_value() lakukan . Berdasarkan hal itu, kami dapat merancang solusi sederhana:

Kueri sederhana

SELECT DISTINCT ON (name)
       name, week AS first_week, value AS first_value
     ,(first_value((week, value)) OVER (PARTITION BY name ORDER BY week DESC))::text AS l
FROM   tbl t
ORDER  BY name, week;

Keluaran memiliki semua data, tetapi nilai untuk minggu terakhir dimasukkan ke dalam catatan anonim (secara opsional dilemparkan ke text ). Anda mungkin memerlukan nilai yang telah didekomposisi.

Hasil terurai dengan penggunaan oportunistik jenis tabel

Untuk itu diperlukan jenis komposit yang terkenal. Definisi tabel yang disesuaikan akan memungkinkan penggunaan oportunistik dari jenis tabel itu sendiri secara langsung:

CREATE TABLE tbl (week int, value int, name text);  -- optimized column order

week dan value datang dulu, jadi sekarang kita bisa mengurutkan berdasarkan jenis tabel itu sendiri:

SELECT (l).name, first_week, first_val
     , (l).week AS last_week, (l).value AS last_val
FROM  (
   SELECT DISTINCT ON (name)
          week AS first_week, value AS first_val
        , first_value(t) OVER (PARTITION BY name ORDER BY week DESC) AS l
   FROM   tbl t
   ORDER  BY name, week
   ) sub;

Hasil yang didekomposisi dari jenis baris yang ditentukan pengguna

Itu mungkin tidak mungkin dalam banyak kasus. Daftarkan tipe komposit dengan CREATE TYPE (permanen) atau dengan CREATE TEMP TABLE (selama sesi):

CREATE TEMP TABLE nv(last_week int, last_val int);  -- register composite type
SELECT name, first_week, first_val, (l).last_week, (l).last_val
FROM (
   SELECT DISTINCT ON (name)
          name, week AS first_week, value AS first_val
        , first_value((week, value)::nv) OVER (PARTITION BY name ORDER BY week DESC) AS l
   FROM   tbl t
   ORDER  BY name, week
   ) sub;

Fungsi agregat khusus first() &last()

Buat fungsi dan agregat satu kali per database:

CREATE OR REPLACE FUNCTION public.first_agg (anyelement, anyelement)
  RETURNS anyelement
  LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS
'SELECT $1;'

CREATE AGGREGATE public.first(anyelement) (
  SFUNC = public.first_agg
, STYPE = anyelement
, PARALLEL = safe
);


CREATE OR REPLACE FUNCTION public.last_agg (anyelement, anyelement)
  RETURNS anyelement
  LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS
'SELECT $2';

CREATE AGGREGATE public.last(anyelement) (
  SFUNC = public.last_agg
, STYPE = anyelement
, PARALLEL = safe
);

Kemudian:

SELECT name
     , first(week) AS first_week, first(value) AS first_val
     , last(week)  AS last_week , last(value)  AS last_val
FROM  (SELECT * FROM tbl ORDER BY name, week) t
GROUP  BY name;

Mungkin solusi yang paling elegan. Lebih cepat dengan modul tambahan first_last_agg menyediakan implementasi C.
Bandingkan instruksi di Wiki Postgres.

Terkait:

  • Menghitung pertumbuhan pengikut dari waktu ke waktu untuk setiap influencer

db<>main biola di sini (menampilkan semua)
sqlfiddle lama

Masing-masing kueri ini jauh lebih cepat daripada jawaban yang diterima saat ini dalam tes cepat pada tabel dengan 50 ribu baris dengan EXPLAIN ANALYZE .

Ada lebih banyak cara. Bergantung pada distribusi data, gaya kueri yang berbeda mungkin (jauh) lebih cepat. Lihat:

  • Optimalkan kueri GROUP BY untuk mengambil baris terbaru per pengguna


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Django JSONField di dalam ArrayField

  2. Nilai yang Dihasilkan di Postgres

  3. Normalisasikan subskrip larik untuk larik 1 dimensi sehingga dimulai dengan 1

  4. Cara Memantau PostgreSQL Berjalan Di Dalam Wadah Docker:Bagian Kedua

  5. Bagaimana Anda menemukan jumlah baris untuk semua tabel Anda di Postgres