Dengan spesifikasi Anda (ditambah info tambahan di komentar),
- Anda memiliki kolom ID numerik (angka integer) dengan hanya sedikit (atau sedikit) kesenjangan.
- Jelas tidak ada atau sedikit operasi penulisan.
- 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 taksiran 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;
Bagian hanya mungkin mahal adalah count(*)
(untuk meja besar). Dengan spesifikasi di atas, Anda tidak memerlukannya. 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 jauh lebih kecil dari itu 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. Anda memiliki "beberapa celah", jadi tambahkan 10% (cukup untuk menutupi dengan mudah) ke jumlah baris yang akan diambil.
Masing id
- masing dapat dipilih beberapa kali secara kebetulan (meskipun sangat tidak mungkin dengan ruang id besar), jadi kelompokkan angka yang dihasilkan (atau gunakan DISTINCT
).
Bergabunglah dengan id
s ke meja besar. Ini harus sangat cepat dengan indeks di tempat.
Akhirnya pangkas kelebihan id
yang belum dimakan oleh dupes dan celah. Setiap baris memiliki peluang yang sepenuhnya sama untuk dipetik.
Versi pendek
Anda dapat menyederhanakan pertanyaan 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;
Saring 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
)
SELECT *
FROM random_pick
LIMIT 1000; -- actual limit
Kami dapat bekerja dengan surplus yang lebih kecil dalam kueri basis. Jika ada terlalu banyak celah sehingga kami tidak menemukan cukup baris di iterasi pertama, rCTE terus beralih dengan istilah rekursif. Kita masih membutuhkan celah yang relatif sedikit di ruang ID atau rekursi bisa mengering sebelum batas tercapai - atau kita harus mulai dengan buffer yang cukup besar yang menentang tujuan mengoptimalkan kinerja.
Duplikat dihilangkan oleh UNION
di rCTE.
Bagian luar LIMIT
membuat CTE berhenti segera setelah kami memiliki cukup baris.
Permintaan 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 menjadi kering). Ada sejumlah jebakan di sini jika Anda ingin menulis ulang.
Bungkus ke dalam fungsi
Untuk penggunaan berulang dengan berbagai parameter:
CREATE OR REPLACE FUNCTION f_random_sample(_limit int = 1000, _gaps real = 1.03)
RETURNS SETOF big 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
)
SELECT *
FROM random_pick
LIMIT _limit;
END
$func$ LANGUAGE plpgsql VOLATILE ROWS 1000;
Panggilan:
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:
Alternatif yang mungkin
JIKA persyaratan Anda memungkinkan set yang sama untuk panggilan berulang (dan kita berbicara tentang panggilan berulang), saya akan mempertimbangkan pandangan terwujud . Jalankan query di atas satu kali dan tulis hasilnya ke sebuah tabel. Pengguna mendapatkan pilihan acak semu dengan kecepatan tinggi. Segarkan pilihan acak Anda pada interval atau acara yang Anda pilih.
Dimana n
persentasenya. Manual:
Metode BERNOULLI
dan SYSTEM
sampling masing-masing menerima argumen tunggal yang merupakan sebagian kecil dari tabel untuk sampel, dinyatakan sebagai
persentase antara 0 dan 100 . Argumen ini bisa berupa real
ekspresi bernilai apa pun .
Penekanan berani saya. Ini sangat cepat , tetapi hasilnya tidak sepenuhnya acak . Manual lagi:
The SYSTEM
Metode secara signifikan lebih cepat daripada BERNOULLI
metode ketika persentase contoh kecil yang ditentukan, tetapi mungkin kembali sampel kurang-acak meja sebagai akibat dari efek klastering.
Jumlah baris yang dikembalikan dapat sangat bervariasi. Sebagai contoh kami, untuk mendapatkan sekitar 1000 baris:
SELECT * FROM big TABLESAMPLE SYSTEM ((1000 * 100) / 5100000.0);
Terkait:
Atau instal modul tambahan tsm_system_rows untuk mendapatkan jumlah baris yang diminta secara 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.