Saf SQL'de nasıl rastgele bir satır (veya mümkün olduğu kadar gerçekten rastgele yakın) isteyebilirim?
Saf SQL'de nasıl rastgele bir satır (veya mümkün olduğu kadar gerçekten rastgele yakın) isteyebilirim?
Yanıtlar:
Şu gönderiye bakın: SQL - Veritabanı tablosundan rastgele bir satır seçmek . Bunu MySQL, PostgreSQL, Microsoft SQL Server, IBM DB2 ve Oracle'da yapmak için yöntemlerden geçer (bu bağlantıdan aşağıdakiler kopyalanır):
MySQL ile rastgele bir satır seçin:
SELECT column FROM table
ORDER BY RAND()
LIMIT 1
PostgreSQL ile rastgele bir satır seçin:
SELECT column FROM table
ORDER BY RANDOM()
LIMIT 1
Microsoft SQL Server ile rastgele bir satır seçin:
SELECT TOP 1 column FROM table
ORDER BY NEWID()
IBM DB2 ile rastgele bir satır seçin
SELECT column, RAND() as IDX
FROM table
ORDER BY IDX FETCH FIRST 1 ROWS ONLY
Oracle ile rastgele bir kayıt seçin:
SELECT column FROM
( SELECT column FROM table
ORDER BY dbms_random.value )
WHERE rownum = 1
order by rand()
Tüm dbs'ye güvenmek veya eşdeğerleri için -1 : |. burada da bahsedildi .
O(n)
ile n
tablodaki kayıtların sayısı olmak. 1 milyon kaydınız olduğunu düşünün, gerçekten 1 milyon rastgele numara veya benzersiz kimlik oluşturmak istiyor musunuz? Bunu tek bir rastgele sayıyla COUNT()
yeni bir LIMIT
ifadeye kullanmayı tercih ederim .
Jeremies gibi çözümler:
SELECT * FROM table ORDER BY RAND() LIMIT 1
ancak tüm tabloların sıralı olarak taranması gerekir (çünkü her satırla ilişkili rastgele değerin hesaplanması gerekir - böylece en küçük olanı belirlenebilir), bu da orta boy tablolar için oldukça yavaş olabilir. Benim tavsiyem bir çeşit indekslenmiş sayısal sütun kullanmak (birçok tablonun bunlar birincil anahtarları olarak var) ve daha sonra şöyle bir şey yazmanız:
SELECT * FROM table WHERE num_value >= RAND() *
( SELECT MAX (num_value ) FROM table )
ORDER BY num_value LIMIT 1
num_value
İndekslenmişse , tablo boyutundan bağımsız olarak logaritmik zamanda çalışır . Bir uyarı: bu num_value
, aralıkta eşit olarak dağıtıldığını varsayar 0..MAX(num_value)
. Veri kümeniz bu varsayımdan güçlü bir şekilde saparsa, çarpık sonuçlar alırsınız (bazı satırlar diğerlerinden daha sık görünür).
Bunun ne kadar verimli olduğunu bilmiyorum, ama daha önce kullandım:
SELECT TOP 1 * FROM MyTable ORDER BY newid()
GUID'ler oldukça rasgele olduğundan, sıralama rastgele bir satır elde edeceğiniz anlamına gelir.
ORDER BY RAND() LIMIT 1
TOP 1
ve veritabanını kullandığından veritabanına özeldir newid()
.
ORDER BY NEWID()
alır 7.4 milliseconds
WHERE num_value >= RAND() * (SELECT MAX(num_value) FROM table)
alır 0.0065 milliseconds
!
Kesinlikle ikinci yöntemle gideceğim.
rand()
bir kayan nokta sayısı döndürür . Bir tamsayı varsayarsak , dönüş değeri de bir tamsayıya zorlanır, böylece ondalık noktadan sonra herhangi bir şeyi keser. Bu nedenle, daima daha az olacaktır , bu yüzden son satır asla seçilmeyecektir. n
0 < n < 1
num_value
rand() * max(num_value)
rand() * max(num_value)
max(num_value)
Hangi sunucuyu kullandığınızı söylemediniz. SQL Server'ın eski sürümlerinde şunları kullanabilirsiniz:
select top 1 * from mytable order by newid()
SQL Server 2005 ve sonraki TABLESAMPLE
sürümlerde, tekrarlanabilir rastgele bir örnek almak için kullanabilirsiniz :
SELECT FirstName, LastName
FROM Contact
TABLESAMPLE (1 ROWS) ;
SQL Server için
newid () / order by işe yarayacaktır, ancak her satır için bir kimlik oluşturması ve ardından bunları sıralaması gerektiği için büyük sonuç kümeleri için çok pahalı olacaktır.
TABLESAMPLE (), performans açısından iyidir, ancak sonuçların toplanmasını sağlarsınız (sayfadaki tüm satırlar döndürülür).
Daha iyi performans gösteren gerçek bir rastgele örnek için en iyi yol, satırları rastgele filtrelemektir. Aşağıdaki kod örneğini TABLESAMPLE kullanarak sonuç kümelerini sınırlama SQL Server Books Online'da buldum :
Gerçekten tek tek sıraların rastgele bir örneğini istiyorsanız, TABLESAMPLE yerine satırları rastgele filtrelemek için sorgunuzu değiştirin. Örneğin, aşağıdaki sorgu, Sales.SalesOrderDetail tablosundaki satırların yaklaşık yüzde birini döndürmek için NEWID işlevini kullanır:
SELECT * FROM Sales.SalesOrderDetail WHERE 0.01 >= CAST(CHECKSUM(NEWID(),SalesOrderID) & 0x7fffffff AS float) / CAST (0x7fffffff AS int)
SalesOrderID sütunu CHECKSUM ifadesine dahil edilir, böylece NEWID () satır başına örnekleme elde etmek için satır başına bir kez değerlendirilir. CAST (CHECKSUM (NEWID (), SalesOrderID) & 0x7fffffff AS float / CAST (0x7fffffff AS int) ifadesi 0 ile 1 arasında rastgele bir float değeri olarak değerlendirilir.
1.000.000 sıralı bir tabloya karşı çalıştırıldığında, sonuçlarım şöyle:
SET STATISTICS TIME ON
SET STATISTICS IO ON
/* newid()
rows returned: 10000
logical reads: 3359
CPU time: 3312 ms
elapsed time = 3359 ms
*/
SELECT TOP 1 PERCENT Number
FROM Numbers
ORDER BY newid()
/* TABLESAMPLE
rows returned: 9269 (varies)
logical reads: 32
CPU time: 0 ms
elapsed time: 5 ms
*/
SELECT Number
FROM Numbers
TABLESAMPLE (1 PERCENT)
/* Filter
rows returned: 9994 (varies)
logical reads: 3359
CPU time: 641 ms
elapsed time: 627 ms
*/
SELECT Number
FROM Numbers
WHERE 0.01 >= CAST(CHECKSUM(NEWID(), Number) & 0x7fffffff AS float)
/ CAST (0x7fffffff AS int)
SET STATISTICS IO OFF
SET STATISTICS TIME OFF
TABLESAMPLE'ı kullanmaktan kurtulabilirseniz, size en iyi performansı verecektir. Aksi takdirde newid () / filter yöntemini kullanın. büyük bir sonuç kümeniz varsa newid () / order by son çare olmalıdır.
Mümkünse, RND () üzerindeki her iki dizinin verimsizliğini ve kayıt numarası alanı oluşturmayı önlemek için saklı ifadeleri kullanın.
"SELECT * tablo LIMIT?, 1" 'den RandomRecord HAZIRLAYIN; SET @ n = ZEMİN (RAND () * (Tablodan COUNT (*) SEÇ)); YÜRÜT Rastgele Kayıt @ n;
En iyi yol, sadece bu amaçla yeni bir sütuna rastgele bir değer koymak ve bunun gibi bir şey kullanmaktır (sahte kod + SQL):
randomNo = random()
execSql("SELECT TOP 1 * FROM MyTable WHERE MyTable.Randomness > $randomNo")
Bu MediaWiki kodu tarafından kullanılan çözümdür. Elbette, daha küçük değerlere karşı bazı önyargılar vardır, ancak hiçbir satır getirilmediğinde rastgele değerin sıfıra sarılmasının yeterli olduğunu bulmuşlardır.
newid () çözümü tam tablo taraması gerektirebilir, böylece her satıra daha az performans gösterecek yeni bir kılavuz atanabilir.
rand () çözümü hiç çalışmayabilir (yani MSSQL ile), çünkü fonksiyon sadece bir kez değerlendirilecek ve her satıra aynı "rastgele" sayı atanacaktır.
SQL Server 2005 ve 2008 için, münferit satırların rastgele bir örneğini istiyorsak ( Books Online'dan ):
SELECT * FROM Sales.SalesOrderDetail
WHERE 0.01 >= CAST(CHECKSUM(NEWID(), SalesOrderID) & 0x7fffffff AS float)
/ CAST (0x7fffffff AS int)
Teşvik edilmediğinden, RAND () kullanıldığında ısrarlı bir şekilde maksimum ID (= Maks) alabilirsiniz:
SELECT MAX(ID) FROM TABLE;
1.Maks (= My_Generated_Random) arasında rastgele
My_Generated_Random = rand_in_your_programming_lang_function(1..Max);
ve sonra bu SQL'i çalıştırın:
SELECT ID FROM TABLE WHERE ID >= My_Generated_Random ORDER BY ID LIMIT 1
Kimlikleri seçilen değerden EQUAL veya HIGHER olan satırları kontrol edeceğini unutmayın. Tablodaki satır için avlamak ve My_Generated_Random'dan eşit veya daha düşük bir kimlik almak ve ardından sorguyu şu şekilde değiştirmek de mümkündür:
SELECT ID FROM TABLE WHERE ID <= My_Generated_Random ORDER BY ID DESC LIMIT 1
@ BillKarwin'in @ cnu'nun cevabı hakkındaki yorumunda belirtildiği gibi ...
Bir LIMIT ile birleştirirken, gerçek satırları doğrudan sipariş etmek yerine rastgele bir siparişle JOIN'e (en azından PostgreSQL 9.1 ile) çok daha iyi performans gösterdiğini gördüm:
SELECT * FROM tbl_post AS t
JOIN ...
JOIN ( SELECT id, CAST(-2147483648 * RANDOM() AS integer) AS rand
FROM tbl_post
WHERE create_time >= 1349928000
) r ON r.id = t.id
WHERE create_time >= 1349928000 AND ...
ORDER BY r.rand
LIMIT 100
'R' değerinin, birleştirilen karmaşık sorgudaki olası her anahtar değer için bir 'rand' değeri oluşturduğundan emin olun, ancak yine de mümkünse 'r' satır sayısını sınırlandırın.
Tamsayı olarak CAST, tamsayı ve tek duyarlıklı kayan türler için özel sıralama optimizasyonuna sahip PostgreSQL 9.2 için özellikle yararlıdır.
Buradaki çözümlerin çoğu sıralamadan kaçınmayı amaçlamaktadır, ancak yine de bir tablo üzerinde sıralı bir tarama yapmaları gerekir.
İndeks taramaya geçerek sıralı taramayı önlemenin bir yolu da vardır. Rastgele satırınızın dizin değerini biliyorsanız, sonucu neredeyse anında alabilirsiniz. Sorun - bir dizin değeri nasıl tahmin edilir.
Aşağıdaki çözüm PostgreSQL 8.4 üzerinde çalışır:
explain analyze select * from cms_refs where rec_id in
(select (random()*(select last_value from cms_refs_rec_id_seq))::bigint
from generate_series(1,10))
limit 1;
Ben çözüm yukarıdaki 0 aralığı 10 farklı rasgele dizin değerleri tahmin .. .. id son değeri].
10 sayısı isteğe bağlıdır - 100 veya 1000 kullanabilirsiniz (şaşırtıcı bir şekilde) yanıt süresi üzerinde büyük bir etkisi yoktur.
Ayrıca bir sorun var - seyrek kimlikleri varsa, kaçırabilirsiniz . Çözüm bir yedekleme planına sahip olmaktır :) Bu durumda random () sorgu ile saf eski bir düzen. Birleşik kimlik şöyle göründüğünde:
explain analyze select * from cms_refs where rec_id in
(select (random()*(select last_value from cms_refs_rec_id_seq))::bigint
from generate_series(1,10))
union all (select * from cms_refs order by random() limit 1)
limit 1;
Değil birlik TÜM fıkra. Bu durumda, ilk bölüm herhangi bir veri döndürürse, ikinci bölüm ASLA yürütülmez!
Geç kaldım, ancak Google üzerinden buraya geldim, bu yüzden gelecek kuşaklar için alternatif bir çözüm ekleyeceğim.
Başka bir yaklaşım, TOP'u iki kez alternatif sırayla kullanmaktır. "Saf SQL" olup olmadığını bilmiyorum, çünkü TOP bir değişken kullanıyor, ancak SQL Server 2008'de çalışıyor. İşte rastgele bir kelime istiyorsanız, sözlük kelimelerin bir tablo karşı kullandığım bir örnek.
SELECT TOP 1
word
FROM (
SELECT TOP(@idx)
word
FROM
dbo.DictionaryAbridged WITH(NOLOCK)
ORDER BY
word DESC
) AS D
ORDER BY
word ASC
Tabii ki, @idx, hedef tablodaki 1 ile COUNT (*) arasında değişen rasgele oluşturulmuş bir tamsayıdır. Sütununuz dizine eklenmişse, bundan da yararlanacaksınız. Başka bir avantaj da NEWID () işlevine izin verilmediğinden, onu bir işlevde kullanabilmenizdir.
Son olarak, yukarıdaki sorgu aynı tabloda bir NEWID () tipi sorgu türünün yürütme süresinin yaklaşık 1 / 10'unda çalışır. YYMV.
Ayrıca new id()
işlevi kullanmayı deneyebilirsiniz .
Sadece sorgunuzu yazın ve new id()
fonksiyona göre sıralamayı kullanın . Oldukça rastgele.
MySQL'in rastgele kayıt alması
SELECT name
FROM random AS r1 JOIN
(SELECT (RAND() *
(SELECT MAX(id)
FROM random)) AS id)
AS r2
WHERE r1.id >= r2.id
ORDER BY r1.id ASC
LIMIT 1
Daha fazla ayrıntı http://jan.kneschke.de/projects/mysql/order-by-rand/
Cevaplarda bu varyasyonu henüz görmedim. Her seferinde aynı satır kümesini seçmek için bir başlangıç tohumu verildiğinde ek bir kısıtlama vardı.
MS SQL için:
Minimum örnek:
select top 10 percent *
from table_name
order by rand(checksum(*))
Normalleştirilmiş yürütme süresi: 1.00
NewId () örneği:
select top 10 percent *
from table_name
order by newid()
Normalleştirilmiş yürütme süresi: 1.02
NewId()
önemsiz derecede yavaş olduğundan rand(checksum(*))
büyük kayıt kümelerine karşı kullanmak istemeyebilirsiniz.
İlk Tohumlu Seçim:
declare @seed int
set @seed = Year(getdate()) * month(getdate()) /* any other initial seed here */
select top 10 percent *
from table_name
order by rand(checksum(*) % seed) /* any other math function here */
Bir tohum verilen aynı seti seçmeniz gerekiyorsa, bu işe yarıyor gibi görünüyor.
MSSQL'de (11.0.5569'da test edilmiştir)
SELECT TOP 100 * FROM employee ORDER BY CRYPT_GEN_RANDOM(10)
önemli ölçüde daha hızlı
SELECT TOP 100 * FROM employee ORDER BY NEWID()
SQL Server'da, oldukça iyi bir rastgelelik elde etmek ve hala hız elde etmek için TABLESAMPLE komutunu NEWID () ile birleştirebilirsiniz. Bu, özellikle yalnızca 1 veya az sayıda satır istiyorsanız faydalıdır.
SELECT TOP 1 * FROM [table]
TABLESAMPLE (500 ROWS)
ORDER BY NEWID()
SQL Server 2012+ ile bunu tek bir rastgele satır için yapmak için OFFSET FETCH sorgusunu kullanabilirsiniz
select * from MyTable ORDER BY id OFFSET n ROW FETCH NEXT 1 ROWS ONLY
burada id bir kimlik sütunudur ve n, istediğiniz satırdır - tablonun 0 ile count () - 1 arasında rastgele bir sayı olarak hesaplanır (0 değeri, her şeyden önce ilk satırdır)
ORDER BY deyimi için çalışmak için bir dizin olduğu sürece, tablo verilerinde deliklerle çalışır. Rastgele olma için de çok iyidir - kendinizi dışarı aktarmak için çalışırken, ancak diğer yöntemlerde niggles mevcut değildir. Buna ek olarak, performans oldukça iyi, daha küçük bir veri kümesinde iyi duruyor, ancak birkaç milyon satıra karşı ciddi performans testleri denemedim.
SELECT * FROM table ORDER BY RAND() LIMIT 1
CD-MaN ile anlaşmak zorundayım: "ORDER BY RAND ()" kullanarak küçük tablolar için veya SELECT'inizi sadece birkaç kez yaptığınızda iyi çalışır.
Ayrıca "num_value> = RAND () * ..." tekniğini kullanıyorum ve gerçekten rastgele sonuçlar elde etmek istiyorsam, günde bir kez güncellediğim tabloda özel bir "rastgele" sütun var. Bu tek UPDATE çalıştırması biraz zaman alacaktır (özellikle bu sütunda bir dizin olması gerektiğinden), ancak seçim her çalıştırıldığında her satır için rastgele sayılar oluşturmaktan çok daha hızlıdır.
Dikkatli olun çünkü TableSample aslında rastgele bir satır örneği döndürmez. Sorgunuzu, satırınızı oluşturan 8 KB'lik sayfaların rastgele bir örneğine bakmaya yönlendirir. Ardından, sorgunuz bu sayfalarda bulunan verilere karşı yürütülür. Verilerin bu sayfalarda nasıl gruplanabileceğinden (ekleme sırası vb.), Bu aslında rastgele bir örnek olmayan verilere yol açabilir.
Bkz. Http://www.mssqltips.com/tip.asp?tip=1308
TableSample için bu MSDN sayfası, gerçekte rastgele bir veri örneğinin nasıl oluşturulacağına ilişkin bir örnek içerir.
Listelenen fikirlerin çoğu hala sipariş kullanıyor
Ancak, geçici bir tablo kullanırsanız, rastgele bir dizin atayabilirsiniz (çözümlerin çoğunun önerdiği gibi) ve sonra 0 ile 1 arasında rasgele bir sayıdan büyük olan ilk tabloyu alabilirsiniz.
Örneğin (DB2 için):
WITH TEMP AS (
SELECT COMLUMN, RAND() AS IDX FROM TABLE)
SELECT COLUMN FROM TABLE WHERE IDX > .5
FETCH FIRST 1 ROW ONLY
Http://akinas.com/pages/en/blog/mysql_random_row/ adresinden basit ve etkili bir yol
SET @i = (SELECT FLOOR(RAND() * COUNT(*)) FROM table); PREPARE get_stmt FROM 'SELECT * FROM table LIMIT ?, 1'; EXECUTE get_stmt USING @i;
SQL Server 2005 ve üstü için, num_value
sürekli değerleri olmayan durumlarda @ GreyPanther'ın cevabını genişletmek . Bu, veri kümelerini eşit olarak dağıtmadığımız ve num_value
bir sayı değil, benzersiz bir tanımlayıcı olduğunda da işe yarar .
WITH CTE_Table (SelRow, num_value)
AS
(
SELECT ROW_NUMBER() OVER(ORDER BY ID) AS SelRow, num_value FROM table
)
SELECT * FROM table Where num_value = (
SELECT TOP 1 num_value FROM CTE_Table WHERE SelRow >= RAND() * (SELECT MAX(SelRow) FROM CTE_Table)
)
Sql'den rastgele işlev yardımcı olabilir. Ayrıca yalnızca bir satırla sınırlamak istiyorsanız, bunu sonuna ekleyin.
SELECT column FROM table
ORDER BY RAND()
LIMIT 1