Postgres'te hızlı rastgele sıra seçimi


98

Postgres'te birkaç milyon satır içeren bir tablom var. İnternetten kontrol ettim ve aşağıdakileri buldum

SELECT myid FROM mytable ORDER BY RANDOM() LIMIT 1;

Çalışıyor, ama gerçekten yavaş ... Bu sorguyu yapmanın başka bir yolu var mı, yoksa tüm tabloyu okumadan rastgele bir satırı seçmenin doğrudan bir yolu var mı? Bu arada, 'myid' bir tamsayıdır, ancak boş bir alan olabilir.


1
Birden çok rastgele satır seçmek istiyorsanız, şu soruya bakın: stackoverflow.com/q/8674718/247696
Flimm

Yanıtlar:


99

Şu şekilde denemek isteyebilirsiniz OFFSET:

SELECT myid FROM mytable OFFSET floor(random()*N) LIMIT 1;

İçindeki Nsatır sayısıdır mytable. SELECT COUNT(*)Değerini bulmak için önce bir yapmanız gerekebilir N.

Güncelleme (Antony Hatchkins tarafından)

floorBurada kullanmalısınız :

SELECT myid FROM mytable OFFSET floor(random()*N) LIMIT 1;

2 satırlık bir tablo düşünün; random()*Nüretir 0 <= x < 2ve örneğin SELECT myid FROM mytable OFFSET 1.7 LIMIT 1;en yakın int'e örtük yuvarlama nedeniyle 0 satır döndürür.


N'den küçük kullanmak mantıklı SELECT COUNT(*), yani tablodaki tüm değerleri değil, sadece bir kısmını mı kullanın?
Juan

@Juan Bu, gereksinimlerinize bağlıdır.
NPE

EXPLAIN SELECT ...farklı N değerleri ile kullanmak sorgu için aynı maliyeti verir, bu durumda maksimum N değerini kullanmak daha iyidir sanırım
Juan


2
Bunda bir hata payı var. Hiçbir zaman ilk satırı döndürmez ve 1 / COUNT (*) hatası oluşturur, çünkü son satırdan sonraki satırı döndürmeye çalışır.
Ian

63

PostgreSQL 9.5, çok daha hızlı örnek seçimi için yeni bir yaklaşım getirdi: TABLESAMPLE

Sözdizimi

SELECT * FROM my_table TABLESAMPLE BERNOULLI(percentage);
SELECT * FROM my_table TABLESAMPLE SYSTEM(percentage);

Tam yüzdeyi hesaplamak için tablonun COUNT'unu bilmeniz gerektiğinden, yalnızca bir satırın seçilmesini istiyorsanız bu en uygun çözüm değildir.

Yavaş COUNT'tan kaçınmak ve 1 satırdan milyarlarca satıra kadar olan tablolarda hızlı TABLESAMPLE kullanmak için şunları yapabilirsiniz:

 SELECT * FROM my_table TABLESAMPLE SYSTEM(0.000001) LIMIT 1;
 -- if you got no result:
 SELECT * FROM my_table TABLESAMPLE SYSTEM(0.00001) LIMIT 1;
 -- if you got no result:
 SELECT * FROM my_table TABLESAMPLE SYSTEM(0.0001) LIMIT 1;
 -- if you got no result:
 SELECT * FROM my_table TABLESAMPLE SYSTEM(0.001) LIMIT 1;
 ...

Bu o kadar zarif görünmeyebilir, ancak muhtemelen diğer yanıtların hepsinden daha hızlıdır.

BERNULLI oder SYSTEM'i kullanmak isteyip istemediğinize karar vermek için, farkı http://blog.2ndquadrant.com/tablesample-in-postgresql-9-5-2/ adresinde okuyun.


2
Bu, diğer yanıtlardan çok daha hızlı ve kolaydır - bu, en üstte olmalıdır.
Hayden Schiff

1
Sayımı almak için neden sadece bir alt sorgu kullanamıyorsunuz? SELECT * FROM my_table TABLESAMPLE SYSTEM(SELECT 1/COUNT(*) FROM my_table) LIMIT 1;?
machineghost

2
@machineghost "Yavaş COUNT'tan kaçınmak için ..." ... Verileriniz makul bir süre içinde sayabileceğiniz kadar küçükse, devam edin! :-)
alfonx

2
@machineghost SELECT reltuples FROM pg_class WHERE relname = 'my_table'Sayım tahmini için kullanın .
Hynek -Pichi- Vychodil

@ Hynek-Pichi-Vychodil çok iyi girdi! Tahminin güncelliğini yitirmediğinden emin olmak için, yakın zamanda VAKUM ANALİZİ yapılması gerekiyor .. ama yine de iyi bir veri tabanı düzgün bir şekilde analiz edilmelidir .. Ve hepsi özel kullanım durumuna bağlıdır. Genellikle büyük masalar bu kadar hızlı büyümez ... Teşekkürler!
alfonx

34

Bunu bir alt sorgu ile denedim ve iyi çalıştı. Ofset, en azından Postgresql v8.4.4'te iyi çalışıyor.

select * from mytable offset random() * (select count(*) from mytable) limit 1 ;

Aslında, bunun çalışması için v8.4 gereklidir, <= 8.3 için çalışmaz.
Antony Hatchkins


32

Kullanmanız gerekenler floor:

SELECT myid FROM mytable OFFSET floor(random()*N) LIMIT 1;

2 satırlık bir tablo düşünün; random()*N0 <= x <2 üretir ve örneğin SELECT myid FROM mytable OFFSET 1.7 LIMIT 1;, en yakın int'e örtük yuvarlama nedeniyle 0 satır döndürür.
Antony Hatchkins

Maalesef daha yüksek bir LİMİT kullanmak istiyorsanız bu işe yaramaz ... 3 öğe almam gerekiyor, bu nedenle ORDER BY RANDOM () sözdizimini kullanmam gerekiyor.
Alexis Wilke

1
Art arda üç sorgu yine de birden hızlı olacaktır order by random(), yaklaşık olarak gerçek 3*O(N) < O(NlogN)hayattaki rakamlar endeksler nedeniyle biraz farklı olacaktır.
Antony Hatchkins

Benim sorunum 3 ürün farklı ve bir olmak gerektiğidir WHERE myid NOT IN (1st-myid)ve WHERE myid NOT IN (1st-myid, 2nd-myid)karar OFFSET tarafından yapılır beri çalışmalarını olmaz. Hmmm ... Sanırım ikinci ve üçüncü SEÇİM'de N'yi 1 ve 2 azaltabilirim.
Alexis Wilke

Siz veya herhangi biri bu yanıtı neden kullanmam gerektiğine dair bir yanıtla genişletebilir floor()mi? Ne avantajı var?
ADTC

14

Bazı farklı seçenekler için bu bağlantıya göz atın. http://www.depesz.com/index.php/2007/09/16/my-inglyts-on-getting-random-row/

Güncelleme: (A. Hatchkins)

(Çok) uzun makalenin özeti aşağıdaki gibidir.

Yazar dört yaklaşımı listeler:

1) ORDER BY random() LIMIT 1; - yavaş

2) ORDER BY id where id>=random()*N LIMIT 1- boşluklar varsa üniform olmayan

3) rastgele sütun - ara sıra güncellenmesi gerekiyor

4) özel rastgele toplama - kurnaz yöntem yavaş olabilir: random () N kez üretilmelidir

ve 2. yöntemi kullanarak iyileştirmeyi önerir.

5) ORDER BY id where id=random()*N LIMIT 1 sonuç boşsa sonraki taleplerle.


OFFSET'i neden kapatmadıklarını merak ediyorum? SİPARİŞ kullanmak, rastgele bir sıra elde etmek için söz konusu değildir. Neyse ki, OFFSET cevaplarda iyi bir şekilde yer almaktadır.
androidguy

4

Rastgele satırı getirmenin en kolay ve en hızlı yolu, şu tsm_system_rowsuzantıyı kullanmaktır :

CREATE EXTENSION IF NOT EXISTS tsm_system_rows;

Daha sonra tam istediğiniz satır sayısını seçebilirsiniz:

SELECT myid  FROM mytable TABLESAMPLE SYSTEM_ROWS(1);

Bu, PostgreSQL 9.5 ve sonrasında mevcuttur.

Bakınız: https://www.postgresql.org/docs/current/static/tsm-system-rows.html


1
Adil uyarı, bu tamamen rastgele değil. Daha küçük tablolarda, her zaman ilk satırları sırayla döndürdüm.
Ben Aubin

1
evet bu, belgelerde açıkça açıklanmıştır (yukarıdaki bağlantı): «Yerleşik SİSTEM örnekleme yöntemi gibi, SYSTEM_ROWS blok düzeyinde örnekleme gerçekleştirir, böylece örnek tamamen rastgele olmaz, ancak özellikle küçükse kümeleme etkilerine maruz kalabilir satır sayısı isteniyor. ». Küçük bir veri kümeniz varsa, ORDER BY random() LIMIT 1;yeterince hızlı olmalıdır.
daamien

Bunu gördüm. Bağlantıya tıklamayan veya bağlantının gelecekte öleceğini herkese açıklığa kavuşturmak istedim.
Ben Aubin

1
Ayrıca, bir sorgu çalıştırma ve ardından rastgele bir veya birkaç kayıt seçmenin aksine, bunun yalnızca bir tablodan rastgele satırları seçmek ve SONRA filtrelemek için işe yarayacağını belirtmek gerekir.
nomen

3

Olmadan çok hızlı bir çözüm buldum TABLESAMPLE. Daha hızlı OFFSET random()*N LIMIT 1. Masa sayımı bile gerektirmez.

Buradaki fikir, örneğin rastgele ancak öngörülebilir verilerle bir ifade dizini oluşturmaktır md5(primary key).

İşte 1 milyon satırlık örnek veriye sahip bir test:

create table randtest (id serial primary key, data int not null);

insert into randtest (data) select (random()*1000000)::int from generate_series(1,1000000);

create index randtest_md5_id_idx on randtest (md5(id::text));

explain analyze
select * from randtest where md5(id::text)>md5(random()::text)
order by md5(id::text) limit 1;

Sonuç:

 Limit  (cost=0.42..0.68 rows=1 width=8) (actual time=6.219..6.220 rows=1 loops=1)
   ->  Index Scan using randtest_md5_id_idx on randtest  (cost=0.42..84040.42 rows=333333 width=8) (actual time=6.217..6.217 rows=1 loops=1)
         Filter: (md5((id)::text) > md5((random())::text))
         Rows Removed by Filter: 1831
 Total runtime: 6.245 ms

Bu sorgu bazen (yaklaşık 1 / Satır_sayısı olasılığı ile) 0 satır döndürebilir, bu nedenle kontrol edilmesi ve yeniden çalıştırılması gerekir. Ayrıca olasılıklar tam olarak aynı değildir - bazı satırlar diğerlerinden daha olasıdır.

Karşılaştırma için:

explain analyze SELECT id FROM randtest OFFSET random()*1000000 LIMIT 1;

Sonuçlar çok çeşitlidir, ancak oldukça kötü olabilir:

 Limit  (cost=1442.50..1442.51 rows=1 width=4) (actual time=179.183..179.184 rows=1 loops=1)
   ->  Seq Scan on randtest  (cost=0.00..14425.00 rows=1000000 width=4) (actual time=0.016..134.835 rows=915702 loops=1)
 Total runtime: 179.211 ms
(3 rows)

2
Hızlı, evet. Gerçekten rastgele, hayır. Başka bir mevcut değerden sonra bir sonraki daha büyük değer olan bir md5 değerinin seçilme şansı çok düşükken, sayı uzayında büyük bir boşluktan sonraki değerlerin çok daha büyük bir şansı vardır (aradaki olası değerlerin sayısı kadar daha büyük) . Ortaya çıkan dağılım rastgele değildir.
Erwin Brandstetter

çok ilginç, piyango benzeri bir sorgunun kullanım durumunda işe yarayabilir mi: Sorgu mevcut tüm biletlere bakmalı ve rastgele yalnızca tek bir bilet döndürmelidir. Ayrıca tekniğinizle karamsar bir kilit (güncelleme için seçin ...) kullanabilir miyim?
Mathieu

Piyango ile ilgili herhangi bir şey için gerçekten adil ve kriptografik olarak güvenli rastgele örnekleme kullanmalısınız - örneğin mevcut kimliği bulana kadar 1 ile maks (id) arasında rastgele bir sayı seçin. Bu cevabın yöntemi ne adil ne de güvenli - hızlı. 'Bir şeyi test etmek için satırların% 1'ini rastgele al' veya 'rastgele 5 giriş göster' gibi şeyler için kullanılabilir.
Tometzky
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.