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

Cara terbaik untuk memilih baris acak PostgreSQL

Mengingat spesifikasi Anda (ditambah info tambahan di komentar),

  • Anda memiliki kolom ID numerik (angka bilangan bulat) dengan hanya sedikit (atau sedikit) celah.
  • Jelas tidak ada atau sedikit operasi tulis.
  • Kolom ID Anda harus diindeks! Kunci utama berfungsi dengan baik.

Kueri di bawah ini tidak memerlukan pemindaian berurutan dari tabel besar, hanya pemindaian indeks.

Pertama, dapatkan perkiraan untuk kueri utama:

SELECT count(*) AS ct              -- optional
     , min(id)  AS min_id
     , max(id)  AS max_id
     , max(id) - min(id) AS id_span
FROM   big;

Satu-satunya bagian yang mungkin mahal adalah count(*) (untuk meja besar). Mengingat spesifikasi di atas, Anda tidak membutuhkannya. Perkiraan akan baik-baik saja, tersedia hampir tanpa biaya (penjelasan terperinci di sini):

SELECT reltuples AS ct FROM pg_class
WHERE oid = 'schema_name.big'::regclass;

Selama ct tidak banyak lebih kecil dari id_span , kueri akan mengungguli pendekatan lain.

WITH params AS (
   SELECT 1       AS min_id           -- minimum id <= current min id
        , 5100000 AS id_span          -- rounded up. (max_id - min_id + buffer)
    )
SELECT *
FROM  (
   SELECT p.min_id + trunc(random() * p.id_span)::integer AS id
   FROM   params p
         ,generate_series(1, 1100) g  -- 1000 + buffer
   GROUP  BY 1                        -- trim duplicates
) r
JOIN   big USING (id)
LIMIT  1000;                          -- trim surplus
  • Hasilkan angka acak di id ruang angkasa. Anda memiliki "sedikit celah", jadi tambahkan 10% (cukup untuk menutupi bagian yang kosong dengan mudah) ke jumlah baris yang akan diambil.

  • Setiap id dapat diambil beberapa kali secara kebetulan (meskipun sangat tidak mungkin dengan ruang id yang besar), jadi kelompokkan nomor yang dihasilkan (atau gunakan DISTINCT ).

  • Bergabunglah dengan id s ke meja besar. Ini harus sangat cepat dengan indeks di tempat.

  • Akhirnya potong surplus id s yang belum dimakan oleh dupes dan kesenjangan. Setiap baris memiliki peluang yang sama persis untuk dipilih.

Versi pendek

Anda dapat menyederhanakan permintaan ini. CTE dalam kueri di atas hanya untuk tujuan pendidikan:

SELECT *
FROM  (
   SELECT DISTINCT 1 + trunc(random() * 5100000)::integer AS id
   FROM   generate_series(1, 1100) g
   ) r
JOIN   big USING (id)
LIMIT  1000;

Sempurnakan dengan rCTE

Terutama jika Anda tidak begitu yakin tentang kesenjangan dan perkiraan.

WITH RECURSIVE random_pick AS (
   SELECT *
   FROM  (
      SELECT 1 + trunc(random() * 5100000)::int AS id
      FROM   generate_series(1, 1030)  -- 1000 + few percent - adapt to your needs
      LIMIT  1030                      -- hint for query planner
      ) r
   JOIN   big b USING (id)             -- eliminate miss

   UNION                               -- eliminate dupe
   SELECT b.*
   FROM  (
      SELECT 1 + trunc(random() * 5100000)::int AS id
      FROM   random_pick r             -- plus 3 percent - adapt to your needs
      LIMIT  999                       -- less than 1000, hint for query planner
      ) r
   JOIN   big b USING (id)             -- eliminate miss
   )
TABLE  random_pick
LIMIT  1000;  -- actual limit

Kami dapat bekerja dengan surplus yang lebih kecil dalam kueri dasar. Jika ada terlalu banyak celah sehingga kami tidak menemukan baris yang cukup pada iterasi pertama, rCTE terus melakukan iterasi dengan suku rekursif. Kami masih membutuhkan relatif sedikit celah di ruang ID atau rekursi dapat mengering sebelum batas tercapai - atau kita harus memulai dengan buffer yang cukup besar yang bertentangan dengan tujuan pengoptimalan kinerja.

Duplikat dihilangkan oleh UNION di rCTE.

Bagian luar LIMIT membuat CTE berhenti segera setelah kita memiliki cukup baris.

Kueri ini dirancang dengan hati-hati untuk menggunakan indeks yang tersedia, menghasilkan baris yang benar-benar acak dan tidak berhenti sampai kami memenuhi batas (kecuali rekursi berjalan kering). Ada sejumlah jebakan di sini jika Anda ingin menulis ulang.

Bungkus ke dalam fungsi

Untuk penggunaan berulang dengan parameter yang bervariasi:

CREATE OR REPLACE FUNCTION f_random_sample(_limit int = 1000, _gaps real = 1.03)
  RETURNS SETOF big
  LANGUAGE plpgsql VOLATILE ROWS 1000 AS
$func$
DECLARE
   _surplus  int := _limit * _gaps;
   _estimate int := (           -- get current estimate from system
      SELECT c.reltuples * _gaps
      FROM   pg_class c
      WHERE  c.oid = 'big'::regclass);
BEGIN
   RETURN QUERY
   WITH RECURSIVE random_pick AS (
      SELECT *
      FROM  (
         SELECT 1 + trunc(random() * _estimate)::int
         FROM   generate_series(1, _surplus) g
         LIMIT  _surplus           -- hint for query planner
         ) r (id)
      JOIN   big USING (id)        -- eliminate misses

      UNION                        -- eliminate dupes
      SELECT *
      FROM  (
         SELECT 1 + trunc(random() * _estimate)::int
         FROM   random_pick        -- just to make it recursive
         LIMIT  _limit             -- hint for query planner
         ) r (id)
      JOIN   big USING (id)        -- eliminate misses
   )
   TABLE  random_pick
   LIMIT  _limit;
END
$func$;

Telepon:

SELECT * FROM f_random_sample();
SELECT * FROM f_random_sample(500, 1.05);

Anda bahkan dapat membuat generik ini berfungsi untuk tabel apa pun:Ambil nama kolom PK dan tabel sebagai tipe polimorfik dan gunakan EXECUTE ... Tapi itu di luar cakupan pertanyaan ini. Lihat:

  • Memfaktorkan ulang fungsi PL/pgSQL untuk mengembalikan output dari berbagai kueri SELECT

Alternatif yang memungkinkan

JIKA persyaratan Anda mengizinkan set identik untuk diulang panggilan (dan kita berbicara tentang panggilan berulang) Saya akan mempertimbangkan tampilan yang terwujud . Jalankan kueri di atas sekali dan tulis hasilnya ke tabel. Pengguna mendapatkan pilihan acak semu dengan kecepatan kilat. Segarkan pilihan acak Anda pada interval atau acara yang Anda pilih.

Postgres 9.5 memperkenalkan TABLESAMPLE SYSTEM (n)

Dimana n adalah persentase. Panduan:

BERNOULLI dan SYSTEM metode pengambilan sampel masing-masing menerima argumen tunggal yang merupakan pecahan dari tabel untuk sampel, dinyatakan sebagaipersentase antara 0 dan 100 . Argumen ini dapat berupa real -ekspresi bernilai.

Penekanan saya yang berani. Ini sangat cepat , tetapi hasilnya tidak sepenuhnya acak . Manual lagi:

SYSTEM metode ini secara signifikan lebih cepat daripada BERNOULLI metode ketika persentase pengambilan sampel kecil ditentukan, tetapi mungkin mengembalikan sampel tabel yang tidak terlalu acak sebagai akibat dari efek pengelompokan.

Jumlah baris yang dikembalikan dapat sangat bervariasi. Untuk contoh kita, untuk mendapatkan secara kasar 1000 baris:

SELECT * FROM big TABLESAMPLE SYSTEM ((1000 * 100) / 5100000.0);

Terkait:

  • Cara cepat untuk menemukan jumlah baris tabel di PostgreSQL

Atau instal modul tambahan tsm_system_rows untuk mendapatkan jumlah baris yang diminta dengan tepat (jika ada cukup) dan memungkinkan sintaks yang lebih nyaman:

SELECT * FROM big TABLESAMPLE SYSTEM_ROWS(1000);

Lihat jawaban Evan untuk detailnya.

Tapi itu masih belum sepenuhnya acak.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Bagaimana cara menambahkan jumlah hari di datetime postgresql

  2. Batasan unik Postgres vs indeks

  3. Bagaimana cara mengupdate semua kolom dengan INSERT...ON CONFLICT...?

  4. Evolusi Fault Tolerance di PostgreSQL:Fase Replikasi

  5. Ekspor dan impor tabel dump (.sql) menggunakan pgAdmin