Pilih n baris acak dari tabel SQL Server


309

Saya punya tabel SQL Server dengan sekitar 50.000 baris di dalamnya. Saya ingin memilih sekitar 5.000 baris itu secara acak. Saya telah memikirkan cara yang rumit, membuat tabel temp dengan kolom "angka acak", menyalin tabel saya ke dalamnya, memutar melalui tabel temp dan memperbarui setiap baris dengan RAND(), dan kemudian memilih dari tabel itu di mana kolom angka acak < 0,1. Saya mencari cara yang lebih sederhana untuk melakukannya, dalam satu pernyataan jika memungkinkan.

Artikel ini menyarankan untuk menggunakan NEWID()fungsi ini. Itu terlihat menjanjikan, tetapi saya tidak bisa melihat bagaimana saya dapat memilih persentase baris dengan andal.

Adakah yang pernah melakukan ini sebelumnya? Ada ide?


3
MSDN memiliki artikel bagus yang membahas banyak masalah ini: Memilih Baris Secara Acak dari Tabel Besar
KyleMit

Jawaban:


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

Menanggapi komentar "sampah murni" tentang tabel besar: Anda bisa melakukannya seperti ini untuk meningkatkan kinerja.

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

Biaya ini akan menjadi pemindaian kunci nilai plus biaya bergabung, yang pada tabel besar dengan pemilihan persentase kecil harus masuk akal.


1
Saya suka pendekatan ini jauh lebih baik daripada menggunakan artikel yang dirujuknya.
JoshBerke

14
Selalu baik untuk diingat bahwa newid () bukan generator nomor pseudorandom yang benar-benar bagus, setidaknya tidak sebagus rand (). Tetapi jika Anda hanya perlu beberapa sampel acak yang samar-samar dan tidak peduli dengan kualitas matematika dan semacamnya, itu akan cukup baik. Kalau tidak, Anda perlu: stackoverflow.com/questions/249301/…
user12861

1
Mm, maaf jika ini jelas .. tapi apa yang dimaksud [yourPk]? EDIT: Nvm, berhasil ... Kunci Utama. Durrr
Snailer

4
Newid - guid dianggap unik tetapi tidak acak .. pendekatan yang salah
Brans Ds

2
dengan jumlah baris yang besar misalnya lebih dari 1 juta newid()Sort Estimasi Biaya I / O akan sangat tinggi dan akan mempengaruhi kinerja.
aadi1295

81

Tergantung pada kebutuhan Anda, Anda TABLESAMPLEakan mendapatkan kinerja yang hampir sama acak dan lebih baik. ini tersedia di MS SQL server 2005 dan yang lebih baru.

TABLESAMPLE akan mengembalikan data dari halaman acak alih-alih baris acak dan karenanya deos bahkan tidak mengambil data yang tidak akan kembali.

Di atas meja yang sangat besar saya uji

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

butuh lebih dari 20 menit.

select * from [tablename] tablesample(1 percent)

butuh 2 menit.

Kinerja juga akan meningkat pada sampel yang lebih kecil TABLESAMPLEpadahal tidak newid().

Harap diingat bahwa ini tidak acak seperti newid() metode ini tetapi akan memberi Anda sampling yang layak.

Lihat halaman MSDN .


7
Seperti yang ditunjukkan oleh Rob Boek di bawah ini, tablesampling hasil rumpun, dan karena itu bukan cara yang baik untuk mendapatkan sejumlah kecil hasil acak
Oskar Austegard

Anda keberatan dengan pertanyaan bagaimana ini bekerja: pilih top 1 persen * dari urutan [tablename] oleh newid () karena newid () bukan kolom di [tablename]. Apakah sql server menambahkan kolom newid () secara internal pada setiap baris dan kemudian membuat semacam?
FrenkyB

Tableample adalah jawaban terbaik bagi saya karena saya melakukan kueri kompleks pada tabel yang sangat besar. Tidak ada pertanyaan bahwa itu sangat cepat. Saya memang mendapatkan variasi dalam jumlah catatan yang dikembalikan saat saya menjalankan ini beberapa kali tetapi semuanya berada dalam margin kesalahan yang dapat diterima.
jessier3

38

newid () / order by akan bekerja, tetapi akan sangat mahal untuk set hasil besar karena harus menghasilkan id untuk setiap baris, dan kemudian mengurutkannya.

TABLESAMPLE () bagus dari sudut pandang kinerja, tetapi Anda akan mendapatkan hasil yang berkelompok (semua baris pada halaman akan dikembalikan).

Untuk sampel acak benar yang berkinerja lebih baik, cara terbaik adalah menyaring baris secara acak. Saya menemukan contoh kode berikut dalam artikel SQL Server Books Online yang Membatasi Set Hasil dengan Menggunakan TABLESAMPLE :

Jika Anda benar-benar ingin sampel acak baris individual, ubah kueri Anda untuk memfilter baris secara acak, alih-alih menggunakan TABLESAMPLE. Misalnya, kueri berikut ini menggunakan fungsi NEWID untuk mengembalikan sekitar satu persen dari baris tabel Sales.SalesOrderDetail:

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

Kolom SalesOrderID termasuk dalam ekspresi CHECKSUM sehingga NEWID () mengevaluasi satu kali per baris untuk mencapai pengambilan sampel berdasarkan per baris. Ekspresi CAST (CHECKSUM (NEWID (), SalesOrderID) & 0x7fffffff AS float / CAST (0x7fffffff AS int) mengevaluasi nilai float acak antara 0 dan 1.

Saat dijalankan melawan tabel dengan 1.000.000 baris, berikut ini adalah hasil saya:

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

Jika Anda bisa menggunakan TABLESAMPLE, itu akan memberi Anda kinerja terbaik. Kalau tidak, gunakan metode newid () / filter. newid () / order oleh harus menjadi pilihan terakhir jika Anda memiliki hasil yang besar.


Saya melihat artikel itu juga dan mencobanya pada kode saya, tampaknya itu NewID()hanya dievaluasi sekali, bukan per baris, yang saya tidak suka ...
Andrew Mao

23

Memilih Baris Secara Acak dari Tabel Besar di MSDN memiliki solusi sederhana yang diartikulasikan dengan baik yang mengatasi masalah kinerja skala besar.

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

Sangat menarik. Setelah membaca artikel itu, saya tidak begitu mengerti mengapa RAND()tidak mengembalikan nilai yang sama untuk setiap baris (yang akan mengalahkan BINARY_CHECKSUM()logika). Apakah itu karena itu disebut di dalam fungsi lain daripada menjadi bagian dari klausa SELECT?
John M Gant

Kueri ini berjalan di atas meja dengan baris 6MM dalam waktu kurang dari satu detik.
Mark Melville

2
Saya telah menjalankan kueri ini di atas meja dengan 35 entri dan terus memiliki dua dari mereka di set hasil yang sangat sering. Ini mungkin masalah rand()atau kombinasi di atas - tetapi saya berpaling dari solusi ini karena alasan itu. Juga jumlah hasil bervariasi dari 1 hingga 5 jadi ini mungkin juga tidak dapat diterima dalam beberapa skenario.
Oliver

Tidakkah RAND () mengembalikan nilai yang sama untuk setiap baris?
Sarsaparilla

RAND()mengembalikan nilai yang sama untuk setiap baris (itulah sebabnya solusi ini cepat). Namun, baris dengan checksum biner yang sangat berdekatan memiliki risiko tinggi untuk menghasilkan hasil checksum yang serupa, menyebabkan penggumpalan ketika RAND()kecil. Misalnya, (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. Jika data Anda menderita masalah ini, kalikan BINARY_CHECKSUMdengan 9923.
Brian

12

Tautan ini memiliki perbandingan yang menarik antara Orderby (NEWID ()) dan metode lain untuk tabel dengan 1, 7, dan 13 juta baris.

Seringkali, ketika pertanyaan tentang bagaimana memilih baris acak ditanyakan dalam kelompok diskusi, permintaan NEWID diajukan; itu sederhana dan bekerja sangat baik untuk meja kecil.

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

Namun, kueri NEWID memiliki kelemahan besar ketika Anda menggunakannya untuk tabel besar. Klausa ORDER BY menyebabkan semua baris dalam tabel untuk disalin ke database tempdb, di mana mereka diurutkan. Ini menyebabkan dua masalah:

  1. Operasi penyortiran biasanya memiliki biaya tinggi yang terkait dengannya. Penyortiran dapat menggunakan banyak I / O disk dan dapat berjalan untuk waktu yang lama.
  2. Dalam skenario terburuk, tempdb dapat kehabisan ruang. Dalam skenario kasus terbaik, tempdb dapat mengambil sejumlah besar ruang disk yang tidak akan pernah dapat direklamasi tanpa perintah menyusut manual.

Yang Anda butuhkan adalah cara untuk memilih baris secara acak yang tidak akan menggunakan tempdb dan tidak akan menjadi lebih lambat karena tabel semakin besar. Berikut adalah ide baru tentang cara melakukannya:

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

Gagasan dasar di balik kueri ini adalah bahwa kami ingin menghasilkan angka acak antara 0 dan 99 untuk setiap baris dalam tabel, dan lalu memilih semua baris yang angka acaknya kurang dari nilai persen yang ditentukan. Dalam contoh ini, kami ingin sekitar 10 persen dari baris dipilih secara acak; oleh karena itu, kami memilih semua baris yang nomor acaknya kurang dari 10.

Silakan baca artikel selengkapnya di MSDN .


2
Hai Deumber, senang ditemukan, Anda mungkin menyempurnakannya karena hanya tautan jawaban yang kemungkinan akan dihapus.
bummi

1
@bummi Saya mengubahnya untuk menghindari jawaban hanya tautan :)
QMaster

Ini jawaban terbaik. 'ORDER BY NEWID ()' berfungsi dalam banyak kasus (tabel yang lebih kecil), tetapi karena tolok ukur dalam tautan yang direferensikan dengan jelas menunjukkan bahwa tabel tersebut tertinggal saat meja tumbuh
pedram bashiri

10

Jika Anda (tidak seperti OP) membutuhkan sejumlah catatan tertentu (yang membuat pendekatan CHECKSUM sulit) dan menginginkan sampel yang lebih acak daripada yang disediakan oleh TABLESAMPLE sendiri, dan juga menginginkan kecepatan yang lebih baik daripada CHECKSUM, Anda dapat melakukan penggabungan dengan Metode TABLESAMPLE dan NEWID (), seperti ini:

DECLARE @sampleCount int = 50
SET STATISTICS TIME ON

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

SET STATISTICS TIME OFF

Dalam kasus saya ini adalah kompromi paling langsung antara keacakan (itu tidak benar-benar, saya tahu) dan kecepatan. Variasikan TABLESAMPLE persentase (atau baris) yang sesuai - semakin tinggi persentase, semakin acak sampel, tetapi mengharapkan penurunan linear dalam kecepatan. (Perhatikan bahwa TABLESAMPLE tidak akan menerima variabel)


9

Cukup pesan tabel dengan nomor acak dan dapatkan 5.000 baris pertama menggunakan TOP.

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

MEMPERBARUI

Hanya mencobanya dan newid()panggilan sudah cukup - tidak perlu untuk semua pemain dan semua matematika.


10
Alasan bahwa 'semua pemain dan semua matematika' digunakan adalah untuk kinerja yang lebih baik.
hkf

6

Ini adalah kombinasi dari ide benih awal dan sebuah checksum, yang bagi saya memberikan hasil acak yang benar tanpa biaya NEWID ():

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

3

Di MySQL Anda dapat melakukan ini:

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

3
Ini tidak akan berfungsi. Karena pernyataan yang dipilih adalah atom, itu hanya mengambil satu nomor acak dan menduplikatnya untuk setiap baris. Anda harus memasang kembali di setiap baris untuk memaksanya berubah.
Tom H

4
Mmm ... suka perbedaan vendor. Pilih atom pada MySQL, tapi saya rasa dengan cara yang berbeda. Ini akan bekerja di MySQL.
Jeff Ferland

2

Belum melihat variasi ini dalam jawaban. Saya memiliki kendala tambahan di mana saya perlu, diberikan benih awal, untuk memilih set baris yang sama setiap kali.

Untuk MS SQL:

Contoh minimum:

select top 10 percent *
from table_name
order by rand(checksum(*))

Waktu pelaksanaan normal: 1,00

Contoh NewId ():

select top 10 percent *
from table_name
order by newid()

Waktu pelaksanaan normal: 1.02

NewId()lebih lambat dari biasanya rand(checksum(*)), jadi Anda mungkin tidak ingin menggunakannya melawan set rekaman besar.

Pilihan dengan Benih Awal:

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 */

Jika Anda perlu memilih set yang sama dengan seed, ini sepertinya berhasil.


Apakah ada keuntungan menggunakan @seed khusus terhadap RAND ()?
QMaster

tentu saja, Anda menggunakan parameter seed dan mengisinya dengan parameter date, fungsi RAND () melakukan hal yang sama kecuali menggunakan nilai waktu lengkap, saya ingin tahu apakah ada manfaat menggunakan parameter yang dibuat seperti seed di atas RAND () atau tidak?
QMaster

Ah!. OKE, ini adalah persyaratan proyek. Saya perlu membuat daftar baris n-acak dengan cara deterministik. Pada dasarnya kepemimpinan ingin mengetahui baris "acak" apa yang akan kami pilih beberapa hari sebelum baris dipilih dan diproses. Dengan membangun nilai seed berdasarkan tahun / bulan saya bisa menjamin panggilan ke kueri tahun itu akan mengembalikan daftar "acak" yang sama. Saya tahu, itu aneh dan mungkin ada cara yang lebih baik tetapi berhasil ...
klyd

HAHA :) Saya mengerti, tapi saya pikir arti umum dari catatan yang dipilih secara acak bukanlah catatan yang sama pada permintaan yang berbeda.
QMaster


0

Tampaknya newid () tidak dapat digunakan di mana klausa, jadi solusi ini memerlukan kueri batin:

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

0

Saya menggunakannya di subquery dan mengembalikan saya baris yang sama di subquery

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

maka saya diselesaikan dengan memasukkan variabel tabel induk di mana

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

Perhatikan kondisi di mana


0

Bahasa pemrosesan sisi server yang digunakan (misalnya PHP, .net, dll) tidak ditentukan, tetapi jika itu PHP, ambil nomor yang diperlukan (atau semua catatan) dan alih-alih mengacak dalam kueri gunakan fungsi acak PHP. Saya tidak tahu apakah .net memiliki fungsi yang setara tetapi jika tidak maka gunakan itu jika Anda menggunakan .net

ORDER BY RAND () dapat memiliki penalti kinerja yang cukup, tergantung pada berapa banyak catatan yang terlibat.


Saya tidak ingat persis apa yang saya gunakan ini pada saat itu, tapi saya mungkin bekerja di C #, mungkin di server, atau mungkin dalam aplikasi klien, tidak yakin. C # tidak memiliki apa pun yang secara langsung dapat dibandingkan dengan PHP shuffle afaik, tetapi dapat dilakukan dengan menerapkan fungsi dari objek Acak dalam operasi Pilih, memesan hasilnya, dan kemudian mengambil sepuluh persen teratas. Tetapi kita harus membaca seluruh tabel dari disk pada server DB dan mengirimkannya melalui jaringan, hanya untuk membuang 90% dari data itu. Memprosesnya langsung dalam DB hampir pasti lebih efisien.
John M Gant

-2

Ini bekerja untuk saya:

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

9
@ user537824, apakah Anda mencobanya di SQL Server? RANDOM bukan fungsi dan LIMIT bukan kata kunci. Sintaks SQL Server untuk apa yang Anda lakukan adalah select top 10 percent from table_name order by rand(), tetapi itu juga tidak berfungsi karena rand () mengembalikan nilai yang sama pada semua baris.
John M Gant
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.