Apa cara terbaik untuk mendapatkan pemesanan acak?

27

Saya memiliki permintaan di mana saya ingin catatan yang dihasilkan dipesan secara acak. Ini menggunakan indeks berkerumun, jadi jika saya tidak menyertakan order byitu kemungkinan akan mengembalikan catatan dalam urutan indeks itu. Bagaimana saya bisa memastikan urutan baris acak?

Saya mengerti bahwa kemungkinan tidak akan "benar-benar" acak, pseudo-acak cukup baik untuk kebutuhan saya.

goric
sumber

Jawaban:

19

ORDER BY NEWID () akan mengurutkan catatan secara acak. Contohnya di sini

SELECT *
FROM Northwind..Orders 
ORDER BY NEWID()
Pengembara
sumber
7
ORDER BY NEWID () efektif acak, tetapi tidak secara statistik acak. Ada perbedaan kecil, dan sebagian besar waktu perbedaan itu tidak masalah.
mrdenny
4
Dari sudut pandang kinerja, ini sangat lambat - Anda bisa mendapatkan peningkatan yang signifikan dengan ORDER BY CHECKSUM (NEWID ())
Miles D
1
@ Mrdenny - Apa yang Anda mendasarkan "tidak secara statistik acak" pada? Jawabannya di sini mengatakan akhirnya digunakan CryptGenRandompada akhirnya. dba.stackexchange.com/a/208069/3690
Martin Smith
15

Saran pertama Pradeep Adiga ORDER BY NEWID(),, baik-baik saja dan sesuatu yang saya gunakan di masa lalu karena alasan ini.

Berhati-hatilah dengan menggunakan RAND()- dalam banyak konteks hanya dieksekusi sekali per pernyataan sehingga tidak ORDER BY RAND()akan berpengaruh (karena Anda mendapatkan hasil yang sama dari RAND () untuk setiap baris).

Contohnya:

SELECT display_name, RAND() FROM tr_person

mengembalikan setiap nama dari tabel orang kami dan nomor "acak", yang sama untuk setiap baris. Jumlahnya bervariasi setiap kali Anda menjalankan kueri, tetapi sama untuk setiap baris setiap kali.

Untuk menunjukkan bahwa sama halnya dengan RAND()yang digunakan dalam ORDER BYklausa, saya mencoba:

SELECT display_name FROM tr_person ORDER BY RAND(), display_name

Hasilnya masih dipesan dengan nama yang menunjukkan bahwa bidang pengurutan sebelumnya (yang diharapkan acak) tidak berpengaruh sehingga mungkin selalu memiliki nilai yang sama.

Memesan dengan NEWID()tidak berfungsi, karena jika NEWID () tidak selalu dinilai ulang tujuan UUIDs akan rusak ketika memasukkan banyak baris baru dalam satu statemnt dengan pengidentifikasi unik sebagai kuncinya, jadi:

SELECT display_name FROM tr_person ORDER BY NEWID()

tidak memesan nama-nama "secara acak".

DBMS lainnya

Di atas berlaku untuk MSSQL (setidaknya 2005 dan 2008, dan jika saya ingat juga 2000). Fungsi mengembalikan UUID baru harus dievaluasi setiap kali di semua DBMS NEWID () berada di bawah MSSQL tetapi perlu memverifikasi ini dalam dokumentasi dan / atau dengan tes Anda sendiri. Perilaku fungsi hasil arbitrer lainnya, seperti RAND (), lebih cenderung bervariasi di antara DBMS, jadi sekali lagi periksa dokumentasi.

Saya juga melihat pemesanan dengan nilai-nilai UUID diabaikan dalam beberapa konteks karena DB mengasumsikan bahwa tipe tidak memiliki urutan yang berarti. Jika Anda menemukan ini adalah kasus yang secara eksplisit melemparkan UUID ke tipe string dalam klausa pemesanan, atau membungkus beberapa fungsi lain di sekitarnya seperti CHECKSUM()di SQL Server (mungkin ada perbedaan kinerja yang kecil dari ini juga karena pemesanan akan dilakukan pada nilai 32-bit bukan 128-bit, meskipun apakah manfaatnya lebih besar daripada biaya menjalankan CHECKSUM()per nilai, saya akan meninggalkan Anda untuk menguji).

Catatan Samping

Jika Anda menginginkan pemesanan yang sewenang-wenang tetapi agak dapat diulang, pesanlah dengan subset data yang relatif tidak terkontrol di baris itu sendiri. Misalnya salah satu atau ini akan mengembalikan nama dalam urutan yang sewenang-wenang tetapi berulang:

SELECT display_name FROM tr_person ORDER BY CHECKSUM(display_name), display_name -- order by the checksum of some of the row's data
SELECT display_name FROM tr_person ORDER BY SUBSTRING(display_name, LEN(display_name)/2, 128) -- order by part of the name field, but not in any an obviously recognisable order)

Pemesanan sewenang-wenang tetapi berulang tidak sering berguna dalam aplikasi, meskipun dapat berguna dalam pengujian jika Anda ingin menguji beberapa kode pada hasil dalam berbagai pesanan tetapi ingin dapat mengulangi setiap menjalankan dengan cara yang sama beberapa kali (untuk mendapatkan waktu rata-rata hasil lebih dari beberapa kali berjalan, atau pengujian bahwa perbaikan yang Anda lakukan pada kode tidak menghilangkan masalah atau ketidakefisienan yang sebelumnya disorot oleh inputet hasil tertentu, atau hanya untuk menguji bahwa kode Anda "stabil" yang mengembalikan hasil yang sama setiap kali jika mengirim data yang sama dalam urutan tertentu).

Trik ini juga dapat digunakan untuk mendapatkan hasil yang lebih sewenang-wenang dari fungsi, yang tidak memungkinkan panggilan non-deterministik seperti NEWID () di dalam tubuh mereka. Sekali lagi, ini bukan sesuatu yang mungkin sering berguna di dunia nyata tetapi bisa berguna jika Anda ingin fungsi mengembalikan sesuatu yang acak dan "acak-ish" cukup baik (tapi hati-hati mengingat aturan yang menentukan ketika fungsi yang ditentukan pengguna dievaluasi, yaitu biasanya hanya sekali per baris, atau hasil Anda mungkin tidak seperti yang Anda harapkan / butuhkan).

Performa

Seperti yang ditunjukkan EBarr, mungkin ada masalah kinerja dengan salah satu di atas. Untuk lebih dari beberapa baris Anda hampir dijamin untuk melihat output spooled ke tempdb sebelum jumlah baris yang diminta dibaca kembali dalam urutan yang benar, yang berarti bahwa bahkan jika Anda mencari 10 besar, Anda mungkin menemukan indeks lengkap pemindaian (atau lebih buruk, pemindaian tabel) terjadi bersamaan dengan blok penulisan yang sangat besar ke tempdb. Karenanya sangat penting, seperti halnya kebanyakan hal, untuk melakukan tolok ukur dengan data realistis sebelum menggunakan ini dalam produksi.

David Spillett
sumber
14

Ini adalah pertanyaan lama, tetapi salah satu aspek dari diskusi tersebut hilang, menurut saya - PERFORMANCE ORDER BY NewId()adalah jawaban umum. Ketika mewah seseorang mendapatkan, mereka menambahkan bahwa Anda harus benar-benar membungkus NewID()di CheckSum(), Anda tahu, untuk kinerja!

Masalah dengan metode ini, adalah Anda masih dijamin pemindaian indeks lengkap dan kemudian semacam data lengkap. Jika Anda pernah bekerja dengan volume data serius apa pun, ini bisa dengan cepat menjadi mahal. Lihatlah rencana eksekusi yang khas ini, dan perhatikan bagaimana prosesnya memakan waktu 96% dari waktu Anda ...

masukkan deskripsi gambar di sini

Untuk memberi Anda gambaran bagaimana skala ini, saya akan memberi Anda dua contoh dari database yang saya gunakan.

  • TableA - memiliki 50.000 baris di 2500 halaman data. Kueri acak menghasilkan 145 dibaca dalam 42ms.
  • Tabel B - memiliki 1,2 juta baris di 114.000 halaman data. Berjalan Order By newid()di tabel ini menghasilkan 53.700 bacaan dan membutuhkan waktu 16 detik.

Moral dari cerita ini adalah bahwa jika Anda memiliki tabel besar (pikirkan miliaran baris) atau perlu menjalankan kueri ini sering newid()metode rusak. Jadi apa yang harus dilakukan anak laki-laki?

Memenuhi TABLESAMPLE ()

Dalam SQL 2005 kemampuan baru yang disebut TABLESAMPLEtelah dibuat. Saya hanya melihat satu artikel yang membahas penggunaannya ... seharusnya ada lebih banyak. MSDN Documents di sini . Pertama sebuah contoh:

SELECT Top (20) *
FROM Northwind..Orders TABLESAMPLE(20 PERCENT)
ORDER BY NEWID()

Gagasan di balik sampel tabel adalah memberi Anda kira - kira ukuran subset yang Anda minta. SQL memberi nomor pada setiap halaman data dan memilih X persen dari halaman tersebut. Jumlah aktual baris yang Anda dapat kembali dapat bervariasi berdasarkan pada apa yang ada di halaman yang dipilih.

Jadi bagaimana saya menggunakannya? Pilih ukuran subset yang lebih dari mencakup jumlah baris yang Anda butuhkan, lalu tambahkan a Top(). Idenya adalah Anda dapat membuat meja ginormous Anda tampak lebih kecil sebelum jenis mahal.

Secara pribadi saya telah menggunakannya untuk membatasi ukuran meja saya. Jadi pada tabel sejuta baris yang melakukan top(20)...TABLESAMPLE(20 PERCENT)kueri turun menjadi 5.600 dibaca dalam 1600ms. Ada juga REPEATABLE()opsi di mana Anda dapat melewati "Benih" untuk pemilihan halaman. Ini akan menghasilkan pemilihan sampel yang stabil.

Pokoknya, anggap saja ini harus ditambahkan ke diskusi. Semoga ini bisa membantu seseorang.

EBarr
sumber
Akan menyenangkan untuk dapat menulis permintaan pemesanan acak scalable yang tidak hanya meningkatkan skala tetapi bekerja dengan set data kecil. Sepertinya Anda harus beralih secara manual antara memiliki dan tidak TABLESAMPLE()berdasarkan berapa banyak data yang Anda miliki. Saya tidak berpikir itu TABLESAMPLE(x ROWS)akan memastikan bahwa setidaknya x baris dikembalikan karena dokumentasi mengatakan “Jumlah aktual baris yang dikembalikan dapat sangat bervariasi. Jika Anda menentukan jumlah kecil, seperti 5, Anda mungkin tidak menerima hasil dalam sampel. "- jadi ROWSsintaksnya masih benar-benar hanya bertopeng PERCENTdi dalam?
binki
Tentu, sihir otomatis itu bagus. Dalam praktiknya, saya jarang melihat skala tabel 5 baris hingga jutaan baris tanpa pemberitahuan. TABLESAMPLE () tampaknya mendasarkan pemilihan jumlah halaman dalam sebuah tabel, sehingga ukuran baris yang diberikan memengaruhi apa yang muncul kembali. Maksud dari contoh tabel, setidaknya seperti yang saya lihat, adalah untuk memberi Anda sub-set yang baik dari mana Anda dapat memilih - seperti tabel yang diturunkan.
EBarr
3

Banyak tabel memiliki kolom ID numerik terindeks yang relatif padat (beberapa nilai yang hilang).

Ini memungkinkan kami untuk menentukan rentang nilai yang ada, dan memilih baris menggunakan nilai ID yang dibuat secara acak dalam rentang itu. Ini bekerja paling baik ketika jumlah baris yang akan dikembalikan relatif kecil, dan kisaran nilai ID padat penduduk (sehingga kemungkinan menghasilkan nilai yang hilang cukup kecil).

Sebagai ilustrasi, kode berikut memilih 100 pengguna acak berbeda dari tabel Stack Overflow pengguna, yang memiliki 8.123.937 baris.

Langkah pertama adalah menentukan rentang nilai ID, operasi yang efisien karena indeks:

DECLARE 
    @MinID integer,
    @Range integer,
    @Rows bigint = 100;

--- Find the range of values
SELECT
    @MinID = MIN(U.Id),
    @Range = 1 + MAX(U.Id) - MIN(U.Id)
FROM dbo.Users AS U;

Rentang kueri

Rencana membaca satu baris dari setiap ujung indeks.

Sekarang kami menghasilkan 100 ID acak berbeda dalam rentang (dengan baris yang cocok di tabel pengguna) dan mengembalikan baris itu:

WITH Random (ID) AS
(
    -- Find @Rows distinct random user IDs that exist
    SELECT DISTINCT TOP (@Rows)
        Random.ID
    FROM dbo.Users AS U
    CROSS APPLY
    (
        -- Random ID
        VALUES (@MinID + (CONVERT(integer, CRYPT_GEN_RANDOM(4)) % @Range))
    ) AS Random (ID)
    WHERE EXISTS
    (
        SELECT 1
        FROM dbo.Users AS U2
            -- Ensure the row continues to exist
            WITH (REPEATABLEREAD)
        WHERE U2.Id = Random.ID
    )
)
SELECT
    U3.Id,
    U3.DisplayName,
    U3.CreationDate
FROM Random AS R
JOIN dbo.Users AS U3
    ON U3.Id = R.ID
-- QO model hint required to get a non-blocking flow distinct
OPTION (MAXDOP 1, USE HINT ('FORCE_LEGACY_CARDINALITY_ESTIMATION'));

permintaan baris acak

Rencana tersebut menunjukkan bahwa dalam hal ini 601 angka acak diperlukan untuk menemukan 100 baris yang cocok. Cukup cepat:

Tabel 'Pengguna'. Pindai hitungan 1, bacaan logis 1937, bacaan fisik 2, bacalah 408
Tabel 'Meja Kerja'. Pindai hitungan 0, bacaan logis 0, bacaan fisik 0, baca-depan dibaca 0
Tabel 'Workfile'. Pindai hitungan 0, bacaan logis 0, bacaan fisik 0, baca-depan dibaca 0

 Waktu Eksekusi SQL Server:
   Waktu CPU = 0 ms, waktu yang berlalu = 9 ms.

Cobalah di Explorer Data Stack Exchange.

Paul White mengatakan GoFundMonica
sumber
0

Seperti yang saya jelaskan dalam artikel ini , untuk mengocok set hasil SQL, Anda perlu menggunakan panggilan fungsi database-spesifik.

Perhatikan bahwa mengurutkan set hasil besar menggunakan fungsi RANDOM mungkin menjadi sangat lambat, jadi pastikan Anda melakukannya pada set hasil kecil.

Jika Anda harus mengocok set hasil yang besar dan membatasi setelahnya, maka lebih baik menggunakan SQL Server TABLESAMPLEdi SQL Server daripada fungsi acak di klausa ORDER BY.

Jadi, anggap kita memiliki tabel database berikut:

masukkan deskripsi gambar di sini

Dan baris-baris berikut dalam songtabel:

| id | artist                          | title                              |
|----|---------------------------------|------------------------------------|
| 1  | Miyagi & Эндшпиль ft. Рем Дигга | I Got Love                         |
| 2  | HAIM                            | Don't Save Me (Cyril Hahn Remix)   |
| 3  | 2Pac ft. DMX                    | Rise Of A Champion (GalilHD Remix) |
| 4  | Ed Sheeran & Passenger          | No Diggity (Kygo Remix)            |
| 5  | JP Cooper ft. Mali-Koa          | All This Love                      |

Pada SQL Server, Anda perlu menggunakan NEWIDfungsi, seperti yang diilustrasikan oleh contoh berikut:

SELECT
    CONCAT(CONCAT(artist, ' - '), title) AS song
FROM song
ORDER BY NEWID()

Saat menjalankan kueri SQL yang disebutkan di SQL Server, kita akan mendapatkan set hasil berikut:

| song                                              |
|---------------------------------------------------|
| Miyagi & Эндшпиль ft. Рем Дигга - I Got Love      |
| JP Cooper ft. Mali-Koa - All This Love            |
| HAIM - Don't Save Me (Cyril Hahn Remix)           |
| Ed Sheeran & Passenger - No Diggity (Kygo Remix)  |
| 2Pac ft. DMX - Rise Of A Champion (GalilHD Remix) |

Perhatikan bahwa lagu sedang terdaftar dalam urutan acak, berkat NEWIDpanggilan fungsi yang digunakan oleh klausa ORDER BY.

Vlad Mihalcea
sumber