SQL Server tablosundan n rasgele satır seçin


309

İçinde yaklaşık 50.000 satır içeren bir SQL Server tablo var. Bu sıralardan yaklaşık 5.000 tanesini rastgele seçmek istiyorum. Karmaşık bir yol düşündüm, bir "rasgele sayı" sütunu ile bir geçici tablo oluşturmak, bu içine benim tablo kopyalayarak, geçici tablo üzerinden döngü ve her satır güncelleme RAND()ve sonra rasgele sayı sütun < 0.1. Mümkünse tek bir ifadeyle bunu yapmak için daha basit bir yol arıyorum.

Bu makalede , NEWID()işlevinin kullanılması önerilmektedir . Bu umut verici görünüyor, ancak satırların belirli bir yüzdesini nasıl güvenilir bir şekilde seçebileceğimi göremiyorum.

Daha önce bunu yapan var mı? Herhangi bir fikir?


3
MSDN, bu sorunların çoğunu kapsayan iyi bir makaleye sahiptir: Büyük Bir Tablodan Rastgele Satır Seçme
KyleMit

Yanıtlar:


387
select top 10 percent * from [yourtable] order by newid()

Büyük tablolarla ilgili "saf çöp" yorumuna yanıt olarak: performansı artırmak için bunu böyle yapabilirsiniz.

select  * from [yourtable] where [yourPk] in 
(select top 10 percent [yourPk] from [yourtable] order by newid())

Bunun maliyeti, değerlerin anahtar taraması artı küçük bir yüzde seçimi olan büyük bir tabloda makul olması gereken birleştirme maliyeti olacaktır.


1
Bu yaklaşımı referans aldığı makaleyi kullanmaktan çok daha iyi seviyorum.
JoshBerke

14
Newid () 'in gerçekten iyi bir psödondom sayı üreteci olmadığını, en azından rand () kadar iyi olmadığını akılda tutmak her zaman iyidir. Ama sadece bazı rastgele rastgele örneklere ihtiyacınız varsa ve matematiksel nitelikleri ve benzeri şeyleri umursamıyorsanız, yeterince iyi olacaktır. Aksi takdirde ihtiyacınız olanlar: stackoverflow.com/questions/249301/…
user12861

1
Um, bu açıksa üzgünüm .. ama ne anlama [yourPk]geliyor? EDIT: Nvm, anladım ... Birincil Anahtar. Durrr
Snailer

4
newid - guid benzersiz ama rastgele değil olarak
ayrılıyor

2
örneğin 1 milyondan fazla newid()Sıra Tahmini G / Ç maliyeti çok yüksek olacak ve performansı etkileyecektir.
aadi1295

81

İhtiyaçlarınıza bağlı olarak, TABLESAMPLEsizi neredeyse rastgele ve daha iyi performans elde edecektir. MS SQL Server 2005 ve sonraki sürümlerinde kullanılabilir.

TABLESAMPLE rastgele satırlar yerine rastgele sayfalardan veri döndürür ve bu nedenle deos döndürmeyeceği verileri bile almaz.

Çok büyük bir masada test ettim

select top 1 percent * from [tablename] order by newid()

20 dakikadan fazla sürdü.

select * from [tablename] tablesample(1 percent)

2 dakika sürdü.

Performans, daha küçük örneklerde de iyileşir, TABLESAMPLEancak bununla birlikte olmayacaktır newid().

Lütfen bunun bu kadar rastgele olmadığını unutmayın. newid() yöntem ancak size iyi bir örnekleme sağlayacağını .

MSDN sayfasına bakın .


7
Aşağıdaki Rob Boek tarafından belirtildiği gibi, tablo örnekleme sonuçları toplar ve bu nedenle az sayıda rastgele sonuç almak için iyi bir yol değildir
Oskar Austegard

Bunun nasıl çalıştığı sorusuna dikkat edin: newid () [tablename] öğesinde bir sütun olmadığından [tablename] siparişinden newid () tarafından en üst yüzde 1'i * seçin. Sql sunucusu her satırda dahili sütun newid () ekleyip bir sıralama yapıyor mu?
FrenkyB

Ben çok büyük bir tablo üzerinde karmaşık bir sorgu yapıyordu, tablesample benim için en iyi cevap oldu. Hiç kuşkusuz hızlı oldu. Bunu birden çok kez çalıştırdığımda döndürülen sayı kayıtlarında bir varyasyon aldım, ancak hepsi kabul edilebilir bir hata payı içinde.
jessier3

38

newid () / order by çalışacak, 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 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 şunlardır:

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.


Bu makaleyi de gördüm ve kodumda deniyorum, öyle değil gibi görünüyor, NewID()satır başına yerine sadece bir kez değerlendirilmiş gibi görünüyor ...
Andrew Mao

23

MSDN'deki Büyük Bir Tablodan Rastgele Sıra Seçimi , büyük ölçekli performans endişelerini gideren basit ve iyi ifade edilmiş bir çözüme sahiptir.

  SELECT * FROM Table1
  WHERE (ABS(CAST(
  (BINARY_CHECKSUM(*) *
  RAND()) as int)) % 100) < 10

Çok ilginç. Makaleyi okuduktan sonra, neden RAND()her satır için aynı değeri döndürmediğini gerçekten anlamıyorum ( BINARY_CHECKSUM()mantığı yener ). Bunun nedeni, SELECT yan tümcesinin parçası olmak yerine başka bir işlevin içinde çağrılması mıdır?
John M Gant

Bu sorgu, bir saniyeden daha kısa sürede 6MM satır içeren bir tablo üzerinde çalıştı.
Mark Melville

2
Bu sorguyu 35 girişli bir tabloda çalıştırdım ve iki tanesini sonuç kümesinde sık sık tuttum. Bu bir sorun rand()veya yukarıdakilerin bir kombinasyonu olabilir - ama bu nedenle bu çözümden döndüm. Ayrıca, sonuç sayısı 1'den 5'e kadar değiştiğinden, bu bazı senaryolarda da kabul edilemeyebilir.
Oliver

RAND () her satır için aynı değeri döndürmez mi?
Sarsaparilla

RAND()her satır için aynı değeri döndürür (bu nedenle bu çözüm hızlıdır). Bununla birlikte, birbirine çok yakın ikili sağlama toplamı olan satırların benzer sağlama toplamı sonuçları oluşturma riski yüksektir ve bu RAND()da küçükken topaklanmaya neden olur . Örneğin, (ABS(CAST((BINARY_CHECKSUM(111,null,null) * 0.1) as int))) % 100== SELECT (ABS(CAST((BINARY_CHECKSUM(113,null,null) * 0.1) as int))) % 100. Verileriniz bu sorundan muzdaripse, BINARY_CHECKSUM9923 ile çarpın .
Brian

12

Bu bağlantının Orderby (NEWID ()) ve 1, 7 ve 13 milyon satırlık tablolar için diğer yöntemleri arasında ilginç bir karşılaştırması vardır.

Genellikle, tartışma gruplarında rastgele satırların nasıl seçileceği hakkında sorular sorulduğunda, NEWID sorgusu önerilir; basit ve küçük tablolar için çok iyi çalışıyor.

SELECT TOP 10 PERCENT *
  FROM Table1
  ORDER BY NEWID()

Ancak, NEWID sorgusu büyük tablolar için kullandığınızda büyük bir dezavantajı vardır. ORDER BY deyimi, tablodaki tüm satırların sıralandığı tempdb veritabanına kopyalanmasına neden olur. Bu iki soruna neden olur:

  1. Ayıklama işleminin genellikle yüksek bir maliyeti vardır. Sıralama çok fazla disk G / Ç kullanabilir ve uzun süre çalışabilir.
  2. En kötü senaryoda tempdb'de yer kalmayabilir. En iyi durumda, tempdb, manuel bir shrink komutu olmadan asla geri kazanılmayacak kadar büyük miktarda disk alanı kaplayabilir.

İhtiyacınız olan şey, tempdb kullanmayacak ve tablo büyüdükçe çok daha yavaş olmayacak sıraları rastgele seçmenin bir yoludur. İşte bunun nasıl yapılacağı hakkında yeni bir fikir:

SELECT * FROM Table1
  WHERE (ABS(CAST(
  (BINARY_CHECKSUM(*) *
  RAND()) as int)) % 100) < 10

Bu sorgunun arkasındaki temel fikir, tablodaki her satır için 0 ile 99 arasında rastgele bir sayı oluşturmak ve ardından rastgele sayısı belirtilen yüzde değerinden daha az olan tüm satırları seçmek istememizdir. Bu örnekte, satırların yaklaşık yüzde 10'unun rastgele seçilmesini istiyoruz; bu nedenle rastgele sayısı 10'dan küçük olan tüm satırları seçiyoruz.

Lütfen MSDN'deki makalenin tamamını okuyun .


2
Merhaba Deumber, güzel bulundu, bağlantı sadece cevapların silinmesi muhtemel olduğu için bunu yapabilirsiniz.
bummi

1
@bummi Ben bağlantı sadece cevap olmaktan kaçınmak için değiştirdim :)
QMaster

Bu en iyi cevap. 'ORDER BY NEWID ()' çoğu durumda çalışır (daha küçük tablolar), ancak yenilenen bağlantıdaki kriterler tablo büyüdükçe geride kaldığını açıkça gösterir
pedram bashiri

10

(OP'nin aksine) belirli sayıda kayda ihtiyacınız varsa (CHECKSUM yaklaşımını zorlaştırır) ve TABLESAMPLE tarafından sağlanandan daha rastgele bir örnek istiyorsanız ve CHECKSUM'dan daha iyi bir hız istiyorsanız, TABLESAMPLE ve NEWID () yöntemleri, şöyle:

DECLARE @sampleCount int = 50
SET STATISTICS TIME ON

SELECT TOP (@sampleCount) * 
FROM [yourtable] TABLESAMPLE(10 PERCENT)
ORDER BY NEWID()

SET STATISTICS TIME OFF

Benim durumumda bu, rasgelelik (gerçekten, biliyorum) ve hız arasındaki en basit uzlaşmadır. TABLESAMPLE yüzdesini (veya satırları) uygun şekilde değiştirin - yüzde ne kadar yüksek olursa, örnek o kadar rasgele olur, ancak hızda doğrusal bir düşüş beklenir. (TABLESAMPLE öğesinin bir değişkeni kabul etmeyeceğini unutmayın)


9

Tabloyu rastgele bir sayı ile sıralayın ve kullanarak ilk 5.000 satırı elde edin TOP.

SELECT TOP 5000 * FROM [Table] ORDER BY newid();

GÜNCELLEME

Sadece denedim ve bir newid()çağrı yeterli - tüm oyuncular ve tüm matematiklere gerek yok.


10
'Tüm oyuncular ve tüm matematiklerin' kullanılmasının nedeni daha iyi performans içindir.
HKF

6

Bu, ilk tohum fikri ve bir sağlama toplamı birleşimidir ve bana NEWID () maliyeti olmadan düzgün rasgele sonuçlar vermemi ister:

SELECT TOP [number] 
FROM table_name
ORDER BY RAND(CHECKSUM(*) * RAND())

3

MySQL'de şunları yapabilirsiniz:

SELECT `PRIMARY_KEY`, rand() FROM table ORDER BY rand() LIMIT 5000;

3
Bu işe yaramayacak. Select deyimi atomik olduğundan, yalnızca bir rasgele sayı alır ve her satır için çoğaltır. Değişmeye zorlamak için her bir satırda yeniden boyutlandırmanız gerekir.
Tom H

4
Mmm ... satıcı farklılıklarını seviyorum. Seçim MySQL'de atomik, ama sanırım farklı bir şekilde. Bu MySQL'de çalışacaktır.
Jeff Ferland

2

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.


RAND () 'a karşı özel @ tohum kullanmanın herhangi bir avantajı var mı?
QMaster

kesinlikle, Sen tohum parametresini kullandınız ve tarih parametresi ile doldurun, RAND () fonksiyonu tam zaman değerini kullanmak dışında aynısını yapmak, bilmek istiyorum RAND () üzerinde tohum gibi kullanışlı oluşturulan parametre kullanmanın herhangi bir avantajı değil mi?
QMaster

Ah!. Tamam, bu projenin bir gereğiydi. Belirleyici bir şekilde n-rastgele satırların bir listesini oluşturmam gerekiyordu. Temel olarak liderlik, satırların seçilmesinden ve işlenmesinden birkaç gün önce hangi "rastgele" satırları seçeceğimizi bilmek istedi. Yıl / ay bazında bir tohum değeri oluşturarak, o yıl aynı "rastgele" listeyi döndürecek olan sorguya yapılan herhangi bir çağrıyı garanti edebilirim. Biliyorum, garipti ve muhtemelen daha iyi yollar vardı ama işe yaradı ...
klyd

HAHA :) Anlıyorum, ama rastgele seçilmiş kayıtların genel anlamı farklı çalışan sorgudaki aynı kayıtlar değil bence.
QMaster


0

Newid () ifadesi nerede deyiminde kullanılamaz, bu nedenle bu çözüm bir iç sorgu gerektirir:

SELECT *
FROM (
    SELECT *, ABS(CHECKSUM(NEWID())) AS Rnd
    FROM MyTable
) vw
WHERE Rnd % 100 < 10        --10%

0

Alt sorguda kullanıyordum ve alt sorguda bana aynı satırları döndürdü

 SELECT  ID ,
            ( SELECT TOP 1
                        ImageURL
              FROM      SubTable 
              ORDER BY  NEWID()
            ) AS ImageURL,
            GETUTCDATE() ,
            1
    FROM    Mytable

sonra üst tablo değişkeni nereye dahil ile çözüldü

SELECT  ID ,
            ( SELECT TOP 1
                        ImageURL
              FROM      SubTable 
              Where Mytable.ID>0
              ORDER BY  NEWID()
            ) AS ImageURL,
            GETUTCDATE() ,
            1
    FROM    Mytable

Nerede yoğuşmaya dikkat edin


0

Kullanılan sunucu tarafı işleme dili (örn. PHP, .net, vb.) Belirtilmez, ancak PHP ise gerekli sayıyı (veya tüm kayıtları) yakalayın ve sorguda rasgele kullanmak yerine PHP'nin shuffle işlevini kullanın. .Net eşdeğer bir işlevi olup olmadığını bilmiyorum ama eğer .net kullanıyorsanız bunu kullanın

ORDER BY RAND (), kaç kaydın bulunduğuna bağlı olarak oldukça yüksek bir performans cezasına sahip olabilir.


Tam olarak ne için o zaman kullandığımı hatırlamıyorum, ama muhtemelen C #, belki bir sunucu veya belki bir istemci uygulamasında çalışıyordum, emin değilim. C #, PHP'nin shuffle afaik ile doğrudan karşılaştırılabilir bir şey içermez, ancak bir Select işlemi içinde Random nesnesinden işlevler uygulanarak, sonuç sipariş edilerek ve ardından ilk yüzde on alınarak yapılabilir. Ancak tüm tabloyu DB sunucusundaki diskten okumalı ve ağ üzerinden iletmeliyiz, sadece bu verilerin% 90'ını atmak zorundayız. Doğrudan DB'de işlenmesi neredeyse kesinlikle daha verimlidir.
John M Gant

-2

Bu benim için çalışıyor:

SELECT * FROM table_name
ORDER BY RANDOM()
LIMIT [number]

9
@ user537824, SQL Server'da denediniz mi? RANDOM bir işlev değildir ve LIMIT bir anahtar kelime değildir. Yaptığınız şeyin SQL Server sözdizimi olurdu select top 10 percent from table_name order by rand(), ancak rand () tüm satırlarda aynı değeri döndürdüğü için bu da çalışmaz.
John M Gant
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.