Spesifikasyonlarınız verildiğinde (artı yorumlarda ek bilgi),
- Çok az (veya orta derecede az) boşluk içeren bir sayısal kimlik sütununuz (tamsayı sayılar) var.
- Açıkçası hiç veya az sayıda yazma işlemi.
- Kimlik sütununuz dizine eklenmelidir! Birincil anahtar güzel hizmet eder.
Aşağıdaki sorgu büyük tablonun sıralı olarak taranmasını gerektirmez, sadece bir indeks taraması gerektirir.
İlk olarak, ana sorgu için tahminler alın:
SELECT count(*) AS ct -- optional
, min(id) AS min_id
, max(id) AS max_id
, max(id) - min(id) AS id_span
FROM big;
Muhtemelen pahalı kısmı count(*)
(büyük tablolar için) 'dir. Yukarıdaki özellikler göz önüne alındığında, buna ihtiyacınız yoktur. Bir tahmin iyi olur, neredeyse ücretsiz olarak kullanılabilir ( ayrıntılı açıklama burada ):
SELECT reltuples AS ct FROM pg_class WHERE oid = 'schema_name.big'::regclass;
Çok küçük ct
olmadığı sürece , sorgu diğer yaklaşımlardan daha iyi performans gösterecektir. id_span
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
id
Uzayda rastgele sayılar üretin . "Birkaç boşluk" var, bu yüzden alınacak satır sayısına% 10 (boşlukları kolayca kaplayacak kadar) ekleyin.
Her biri id
şans eseri birden çok kez seçilebilir (ancak büyük bir kimlik alanına sahip olma ihtimali düşüktür), bu nedenle üretilen numaraları gruplandırın (veya kullanın DISTINCT
).
id
Büyük masaya s katılın . Bu, dizin hazır olduğunda çok hızlı olmalıdır.
Son id
olarak çiftler ve boşluklar tarafından yenilmeyen fazlaları düzeltin . Her sıranın seçilme şansı tamamen eşittir .
Kısa versiyon
Şunları yapabilirsiniz basitleştirmek bu sorguyu. Yukarıdaki sorgudaki CTE sadece eğitim amaçlıdır:
SELECT *
FROM (
SELECT DISTINCT 1 + trunc(random() * 5100000)::integer AS id
FROM generate_series(1, 1100) g
) r
JOIN big USING (id)
LIMIT 1000;
RCTE ile hassaslaştırın
Özellikle boşluklar ve tahminler hakkında o kadar emin değilseniz.
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
Temel sorguda daha küçük bir artı ile çalışabiliriz . Çok fazla boşluk varsa, bu nedenle ilk yinelemede yeterli satır bulamazsak, rCTE özyinelemeli terimle yinelemeye devam eder. Kimlik alanında nispeten daha az boşluğa ihtiyacımız var veya sınıra ulaşılmadan tekrarlama kuru olabilir - ya da performansı optimize etme amacına meydan okuyan yeterince büyük bir tamponla başlamalıyız.
UNION
RCTE içindeki kopyalar elimine edilir .
Dış LIMIT
kısım, yeterli satırımız olduğunda CTE'yi durdurur.
Bu sorgu, mevcut dizini kullanmak, gerçekten rastgele satırlar oluşturmak ve sınırı karşılayana kadar (özyineleme kuru çalışmadığı sürece) durmamak için dikkatle tasarlanmıştır. Yeniden yazacaksanız burada birtakım tuzaklar var.
İşlevine sarın
Değişken parametrelerle tekrarlanan kullanım için:
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;
Aramak:
SELECT * FROM f_random_sample();
SELECT * FROM f_random_sample(500, 1.05);
Bu jeneriki herhangi bir tablo için çalışacak şekilde bile yapabilirsiniz: PK sütununun ve tablonun adını polimorfik tip olarak kullanın ve kullanın EXECUTE
... Ama bu, bu sorunun kapsamı dışında. Görmek:
Olası alternatif
Gereksinimleriniz tekrarlanan çağrılar için özdeş setlere izin veriyorsa (ve tekrarlanan çağrılardan bahsediyoruz) somutlaştırılmış bir görünüm düşünürüm . Yukarıdaki sorguyu bir kez yürütün ve sonucu bir tabloya yazın. Kullanıcılar aydınlatma hızında yarı rastgele bir seçim alır. Seçtiğiniz aralıklarla veya olaylarla rastgele seçiminizi yenileyin.
n
Yüzde nerede . Kullanım kılavuzu:
BERNOULLI
Ve SYSTEM
örnekleme yöntemleri her biri bir şekilde ifade edilen numuneye tablonun fraksiyon, tek bir bağımsız değişken kabul
0 ile 100 arasında yüzde . Bu argüman herhangi bir real
değerli ifade olabilir.
Cesur vurgu benim. Bu var çok hızlı , ama sonuç tam olarak rastgele değil . Kılavuz tekrar:
SYSTEM
Yöntem daha hızlı önemli olan BERNOULLI
küçük numune alma yüzdeleri belirtilen yöntem, ancak etkileri kümeleme sonucu tablo daha az rastgele örnek döndürebilir.
Döndürülen satır sayısı çılgınca değişebilir. Örneğimiz için, yaklaşık 1000 satır almak için :
SELECT * FROM big TABLESAMPLE SYSTEM ((1000 * 100) / 5100000.0);
İlişkili:
Veya ek modül yüklemek tsm_system_rows (yeterli olup olmadığını) tam olarak istenen satır sayısını almak ve daha uygun sözdizimi için izin vermek:
SELECT * FROM big TABLESAMPLE SYSTEM_ROWS(1000);
Ayrıntılar için Evan'ın cevabına bakınız.
Ama bu hala tam olarak rastgele değil.