Sql veritabanından Basit Rastgele Örnekler


93

SQL'de nasıl verimli bir basit rastgele örnek alabilirim? Söz konusu veritabanı MySQL kullanıyor; masam en az 200.000 satır ve yaklaşık 10.000'lik basit rastgele bir örnek istiyorum.

"Açık" cevap şudur:

SELECT * FROM table ORDER BY RAND() LIMIT 10000

Büyük tablolar için, bu çok yavaştır: RAND()her satırı çağırır (zaten onu O (n) 'ye koyar) ve onları sıralar, en iyi ihtimalle O (n lg n) yapar. Bunu O (n) 'den daha hızlı yapmanın bir yolu var mı?

Not : Andrew Mao'nun yorumlarda belirttiği gibi, SQL Server'da bu yaklaşımı kullanıyorsanız, T-SQL işlevini kullanmalısınız NEWID()çünkü RAND () tüm satırlar için aynı değeri döndürebilir .

DÜZENLEME: 5 YIL SONRA

Bu problemle tekrar daha büyük bir tabloyla karşılaştım ve iki ince ayar ile @ ignorant'ın çözümünün bir sürümünü kullandım:

  • İstediğim örnek boyutunun 2-5 katına kadar satırları ucuza örnekleyin ORDER BY RAND()
  • Sonucunu RAND()her ekleme / güncellemede dizine alınmış bir sütuna kaydedin . (Veri kümeniz çok güncel değilse, bu sütunu yeni tutmak için başka bir yol bulmanız gerekebilir.)

Bir tablonun 1000 maddelik bir örneğini almak için, satırları sayıyorum ve sonucu frozen_rand sütunuyla ortalama 10.000 satıra indiriyorum:

SELECT COUNT(*) FROM table; -- Use this to determine rand_low and rand_high

  SELECT *
    FROM table
   WHERE frozen_rand BETWEEN %(rand_low)s AND %(rand_high)s
ORDER BY RAND() LIMIT 1000

(Benim gerçek uygulamam, az örneklemediğimden emin olmak ve rand_high'ı manuel olarak kaydırmak için daha fazla çalışma gerektiriyor, ancak temel fikir "N'nizi rastgele birkaç bine düşürmek".)

Bu bazı fedakarlıklar yaratsa da, veritabanını bir dizin taraması kullanarak ORDER BY RAND()tekrar yeterince küçük olana kadar örneklememe izin veriyor .


3
Bu, SQL sunucusunda bile çalışmaz çünkü RAND()sonraki her çağrıda aynı değeri döndürür.
Andrew Mao

1
İyi bir nokta - SQL Server kullanıcılarının bunun yerine ORDER BY NEWID () kullanması gerektiğine dair bir not ekleyeceğim.
ojrac

Yine de son derece verimsizdir çünkü tüm verileri sıralamak zorundadır. Bir yüzde için rastgele bir örnekleme tekniği daha iyidir, ancak burada bir sürü gönderiyi okuduktan sonra bile, yeterince rastgele olan kabul edilebilir bir çözüm bulamadım.
Andrew Mao

Soruyu okursanız, özellikle soruyorum çünkü RAND İLE SİPARİŞ () O (n lg n) 'dir.
ojrac

RAND () 'ın istatistiksel rastgeleliğine fazla takıntılı değilseniz muposat'ın aşağıdaki cevabı harika.
Josh Greifer

Yanıtlar:


25

Burada bu tür bir sorunla ilgili çok ilginç bir tartışma var: http://www.titov.net/2005/09/21/do-not-use-order-by-rand-or-how-to-get-random-rows-from-table/

Tablo hakkında kesinlikle hiçbir varsayımda bulunmadan O (n lg n) çözümünüzün en iyisi olduğunu düşünüyorum. Aslında iyi bir optimize edici veya biraz farklı bir teknikle listelediğiniz sorgu biraz daha iyi olabilir, O (m * n) burada m, istenen rastgele satır sayısıdır, çünkü tüm büyük diziyi sıralamak zorunda değildir. , sadece en küçük m zamanlarını arayabilir. Ama gönderdiğiniz türden sayılar için, m yine de lg n'den büyük.

Deneyebileceğimiz üç varsayım:

  1. Tabloda benzersiz, dizine alınmış bir birincil anahtar var

  2. Seçmek istediğiniz rastgele satırların sayısı (m) tablodaki satır sayısından (n) çok daha az

  3. benzersiz birincil anahtar, boşluk olmadan 1 ile n arasında değişen bir tamsayıdır

Sadece 1 ve 2 varsayımları ile bunun O (n) 'de yapılabileceğini düşünüyorum, ancak varsayım 3 ile eşleşmek için tabloya tam bir indeks yazmanız gerekecek, bu yüzden zorunlu olarak hızlı bir O (n) değil. Tablo hakkında İLAVE başka güzel bir şey varsayabilirsek, görevi O (m log m) cinsinden yapabiliriz. Varsayım 3, çalışmak için kolay ve güzel bir ek özellik olacaktır. Bir satırda m sayıları oluştururken hiçbir yinelemeyi garanti eden hoş bir rastgele sayı üreteci ile bir O (m) çözümü mümkün olacaktır.

Üç varsayım göz önüne alındığında, temel fikir, 1 ile n arasında m benzersiz rastgele sayı oluşturmak ve ardından tablodan bu anahtarları içeren satırları seçmektir. Şu anda önümde mysql veya başka bir şey yok, bu yüzden biraz sözde kodda bu şöyle görünecektir:


create table RandomKeys (RandomKey int)
create table RandomKeysAttempt (RandomKey int)

-- generate m random keys between 1 and n
for i = 1 to m
  insert RandomKeysAttempt select rand()*n + 1

-- eliminate duplicates
insert RandomKeys select distinct RandomKey from RandomKeysAttempt

-- as long as we don't have enough, keep generating new keys,
-- with luck (and m much less than n), this won't be necessary
while count(RandomKeys) < m
  NextAttempt = rand()*n + 1
  if not exists (select * from RandomKeys where RandomKey = NextAttempt)
    insert RandomKeys select NextAttempt

-- get our random rows
select *
from RandomKeys r
join table t ON r.RandomKey = t.UniqueKey

Verimlilik konusunda gerçekten endişeliyseniz, rastgele anahtar oluşturmayı bir tür prosedürel dilde yapmayı ve sonuçları veritabanına eklemeyi düşünebilirsiniz, çünkü SQL'den başka hemen hemen her şey, muhtemelen gereken döngü ve rastgele sayı oluşturma türünde daha iyi olacaktır. .


Rastgele anahtar seçimine benzersiz bir dizin eklemenizi ve belki de ekteki kopyaları görmezden gelmenizi öneririm, bu durumda farklı şeylerden kurtulabilirsiniz ve birleştirme daha hızlı olacaktır.
Sam Saffron

Rastgele sayı algoritmasının bazı ince ayarlamalar yapabileceğini düşünüyorum - ya belirtildiği gibi bir EŞSİZ kısıtlama ya da sadece 2 * m sayılar ve SELECT DISTINCT, ORDER BY id (ilk gelen ilk hizmet, bu nedenle bu UNIQUE kısıtlamasına indirgenir. ) SINIR m. Bunu sevdim.
ojrac

Rastgele anahtar seçimine benzersiz bir dizin eklemek ve ardından eklemede kopyaları yok saymak konusunda, bunun sizi bir sıralama için O (m lg m) yerine O (m ^ 2) davranışına geri getirebileceğini düşündüm. Her seferinde bir rasgele satır eklerken sunucunun dizini ne kadar verimli tuttuğundan emin değilim.
user12861

2 * m sayı üretme önerilerine gelince, ne olursa olsun çalışması garantili bir algoritma istedim. Her zaman (zayıf) 2 * m rasgele sayılarınızın m'den fazla kopyası olması ihtimali vardır, bu nedenle sorgunuz için yeterli sayıya sahip olmazsınız.
user12861

1
Tablodaki satır sayısını nasıl elde edersiniz?
Awesome-o

54

Bence en hızlı çözüm

select * from table where rand() <= .3

İşte bu yüzden bu işi yapmalı diye düşünüyorum.

  • Her satır için rastgele bir sayı oluşturacaktır. Sayı 0 ile 1 arasında
  • Oluşturulan sayı 0 ile .3 (% 30) arasında ise bu satırın görüntülenip görüntülenmeyeceğini değerlendirir.

Bu, rand () 'un tek tip bir dağılımda sayılar ürettiğini varsayar. Bunu yapmanın en hızlı yolu budur.

Birinin bu çözümü tavsiye ettiğini ve kanıt olmadan vurulduğunu gördüm .. işte buna söyleyeceğim şey -

  • Bu O (n) 'dir ancak sıralama gerekmez, bu nedenle O (n lg n)
  • mysql, her satır için rastgele sayılar üretme yeteneğine sahiptir. Bunu dene -

    INFORMATION_SCHEMA.TABLES limit 10'dan rand () seçin;

Söz konusu veritabanı mySQL olduğu için bu doğru çözümdür.


1
Birincisi, tam olarak istenen sayıda sonuç yerine istenen sayıya yakın, ancak tam olarak bu sayı olması gerekmeyen yarı rastgele sayıda sonuç döndürdüğü için bunun soruyu gerçekten yanıtlamaması sorununa sahipsiniz.
user12861

1
Daha sonra, verimlilikle ilgili olarak, sizinki O (n) 'dır, burada n, tablodaki satır sayısıdır. Bu, yaklaşık O (m log m) kadar iyi değildir, burada m istediğiniz sonuç sayısıdır ve m << n. Yine de pratikte daha hızlı olacağı konusunda haklı olabilirsiniz, çünkü dediğiniz gibi rand () lar oluşturmak ve bunları sabit bir sabitle karşılaştırmak çok hızlı OLABİLİR. Öğrenmek için test etmeniz gerekir. Daha küçük masalarda kazanabilirsiniz. Büyük tablolar ve çok daha az sayıda istenen sonuç nedeniyle bundan şüpheliyim.
user12861

1
@ User12861 tam olarak doğru sayıyı almama konusunda haklıyken, veri kümesini doğru kaba boyuta indirmenin iyi bir yoludur.
ojrac

1
Veritabanı aşağıdaki sorguya nasıl hizmet verir - SELECT * FROM table ORDER BY RAND() LIMIT 10000 ? Öncelikle her satır için rastgele bir sayı oluşturmalı (anlattığım çözümle aynı), sonra sipariş vermeli .. sıralar pahalıdır! Bu nedenle bu çözüm, hiçbir türe ihtiyaç duyulmadığından tarif ettiğimden daha yavaş OLACAKTIR. Tarif ettiğim çözüme bir limit ekleyebilirsiniz ve bu size bu sayıdan fazla satır vermeyecektir. Birinin doğru bir şekilde işaret ettiği gibi, size KESİN örnek boyutu vermez, ancak rastgele örneklerle, TAM, çoğu zaman kesin bir gereklilik değildir.
cahil

Minimum satır sayısını belirlemenin bir yolu var mı?
CMCDragonkai


4

Sadece kullan

WHERE RAND() < 0.1 

kayıtların% 10'unu almak veya

WHERE RAND() < 0.01 

kayıtların% 1'ini vb. almak için


1
Bu, her satır için RAND'yi çağırır ve onu O (n) yapar. Poster bundan daha iyisini arıyordu.
user12861

1
Sadece bu değil, RAND()sonraki çağrılar için de aynı değeri döndürür (en azından MSSQL'de), yani bu olasılıkla tüm tabloyu ya da hiçbirini almayacaksınız.
Andrew Mao

4

RANDA GÖRE SİPARİŞTEN HIZLI ()

Bu yöntemi çok daha hızlı test ettim ORDER BY RAND(), dolayısıyla O (n) zamanında çalışıyor ve çok etkileyici bir şekilde hızlı yapıyor.

Http://technet.microsoft.com/en-us/library/ms189108%28v=sql.105%29.aspx adresinden :

MSSQL olmayan sürüm - Bunu test etmedim

SELECT * FROM Sales.SalesOrderDetail
WHERE 0.01 >= RAND()

MSSQL sürümü:

SELECT * FROM Sales.SalesOrderDetail
WHERE 0.01 >= CAST(CHECKSUM(NEWID(), SalesOrderID) & 0x7fffffff AS float) / CAST (0x7fffffff AS int)

Bu, kayıtların ~% 1'ini seçecektir. Bu nedenle, seçilecek yüzdelerin veya kayıtların tam sayısına ihtiyacınız varsa, bir miktar güvenlik marjı ile yüzdenizi tahmin edin, ardından daha pahalı ORDER BY RAND()yöntemi kullanarak elde edilen kümeden fazla kayıtları rasgele çıkarın .

Daha hızlı

Bu yöntemi daha da geliştirebildim çünkü iyi bilinen bir indekslenmiş sütun değer aralığım vardı.

Örneğin, tekdüze olarak dağıtılmış [0..max] tam sayılarına sahip dizinlenmiş bir sütununuz varsa, bunu rasgele N küçük aralık seçmek için kullanabilirsiniz. Bunu, çalıştırılan her sorgu için farklı bir küme almak üzere programınızda dinamik olarak yapın. Bu alt küme seçimi O (N) olacaktır ve bu, tam veri kümenizden birçok büyüklük sırası daha küçük olabilir.

Testimde 20 (20 mil üzerinden) numune kaydı almak için gereken süreyi 3 dakikadan ORDER BY RAND () kullanarak 0.0 saniyeye düşürdüm !


1

Tüm bu çözümlerin değiştirilmeden numune gibi göründüğünü belirtmek isterim. Rastgele bir sıralamadan en üstteki K satırını seçmek veya rastgele sırayla benzersiz anahtarlar içeren bir tabloya katılmak, değiştirilmeden oluşturulan rastgele bir örnek verecektir.

Örneğinizin bağımsız olmasını istiyorsanız, değiştirme ile örneklemeniz gerekir. Bunun user12861'in çözümüne benzer bir şekilde JOIN kullanarak nasıl yapılacağına ilişkin bir örnek için Soru 25451034'e bakın . Çözüm T-SQL için yazılmıştır, ancak konsept herhangi bir SQL veritabanında çalışır.


0

Bir kümeye bağlı olarak bir tablonun kimliklerini (örn. Sayı 5) alabileceğimiz gözleminden başlayarak:

select *
from table_name
where _id in (4, 1, 2, 5, 3)

Eğer dizgiyi oluşturabilirsek "(4, 1, 2, 5, 3)", bundan daha verimli bir yola sahip olacağımız sonucuna varabiliriz RAND().

Örneğin, Java'da:

ArrayList<Integer> indices = new ArrayList<Integer>(rowsCount);
for (int i = 0; i < rowsCount; i++) {
    indices.add(i);
}
Collections.shuffle(indices);
String inClause = indices.toString().replace('[', '(').replace(']', ')');

Kimlikler boşluklara sahipse, ilk indicesdizi listesi kimliklerdeki bir sql sorgusunun sonucudur.


0

Tam olarak msatırlara ihtiyacınız varsa , gerçekçi bir şekilde kimlik alt kümenizi SQL dışında oluşturursunuz. Çoğu yöntem, bir noktada "n'inci" girişi seçmeyi gerektirir ve SQL tabloları gerçekten de diziler değildir. Sadece 1 ile sayı arasındaki rastgele girişleri birleştirmek için anahtarların ardışık olduğu varsayımı da karşılanması zordur - örneğin MySQL bunu yerel olarak desteklemiyor ve kilit koşulları ... zor .

İşte bir var O(max(n, m lg n))-zaman, O(n)sadece düz btree tuşları varsayarak -space çözümü:

  1. Veri tablosunun anahtar sütununun tüm değerlerini herhangi bir sırayla, en sevdiğiniz kodlama dilindeki bir diziye getirin. O(n)
  2. Bir Fisher-Yates karıştırması gerçekleştirin , mtakaslardan sonra durun ve alt diziyi çıkarın [0:m-1].ϴ(m)
  3. Alt diziyi orijinal veri kümesiyle (ör. SELECT ... WHERE id IN (<subarray>))O(m lg n)

SQL dışında rastgele alt kümeyi oluşturan herhangi bir yöntem, en azından bu karmaşıklığa sahip olmalıdır. Birleştirme O(m lg n)BTREE ile olduğundan daha hızlı olamaz (bu nedenle O(m)iddialar çoğu motor için fantezidir) ve karıştırma aşağıda sınırlandırılmıştır nve m lg nasimptotik davranışı etkilemez.

Pythonic sözde kodunda:

ids = sql.query('SELECT id FROM t')
for i in range(m):
  r = int(random() * (len(ids) - i))
  ids[i], ids[i + r] = ids[i + r], ids[i]

results = sql.query('SELECT * FROM t WHERE id IN (%s)' % ', '.join(ids[0:m-1])

0

Netezza'da 3000 rastgele kayıt seçin:

WITH IDS AS (
     SELECT ID
     FROM MYTABLE;
)

SELECT ID FROM IDS ORDER BY mt_random() LIMIT 3000

Bazı SQL diyalektine özgü notlar eklemekten başka, bunun, 'ORDER BY rand () LIMIT $ 1' olmadan rastgele bir satır örneğinin nasıl sorgulanacağı sorusunu yanıtladığını sanmıyorum.
ojrac

0

Deneyin

SELECT TOP 10000 * FROM table ORDER BY NEWID()

Bu, çok fazla karmaşık olmadan istenen sonuçları verir mi?


Bunun NEWID()T-SQL'e özgü olduğunu unutmayın .
Peter O.

Özür dilerim. Bu. Teşekkürler Yine de buraya birinin daha iyi bir şekilde bakıp gelip gelmediğini ve T-SQL kullanıp kullanmadığını bilmek faydalı olacaktır
Northernlad

ORDER BY NEWID()işlevsel olarak aynıdır ORDER BY RAND()- RAND()kümedeki her satırı çağırır - O (n) - ve sonra her şeyi - O (n lg n) sıralar. Başka bir deyişle, bu sorunun iyileştirmek istediği en kötü durum çözümü budur.
ojrac

0

Microsoft SQL Server, PostgreSQL ve Oracle gibi belirli lehçelerde (ancak MySQL veya SQLite değil), aşağıdaki gibi bir şey yapabilirsiniz:

select distinct top 10000 customer_id from nielsen.dbo.customer TABLESAMPLE (20000 rows) REPEATABLE (123);

Sadece (10000 rows)olmadan yapmamanın topnedeni, TABLESAMPLEmantığın size son derece kesin olmayan satır sayısı vermesidir (bazen bunun% 75'i, bazen bunun% 1.25'i gibi), bu nedenle tam olarak istediğiniz sayıyı aşırı örneklemek ve seçmek istersiniz. REPEATABLE (123)Rasgele bir tohum sağlamak içindir.


-4

Belki yapabilirsin

SELECT * FROM table LIMIT 10000 OFFSET FLOOR(RAND() * 190000)

1
Bu, verilerimin rastgele bir dilimini seçecek gibi görünüyor; Biraz daha karmaşık bir şey arıyorum - rastgele dağıtılmış 10.000 satır.
ojrac

Veritabanında yapmak istiyorsanız tek seçeneğiniz ORDER BY rand () olacaktır.
staticsan
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.