it-swarm.asia

Apa cara terbaik untuk mendapatkan pemesanan acak?

Saya memiliki permintaan di mana saya ingin catatan yang dihasilkan dipesan secara acak. Ini menggunakan indeks berkerumun, jadi jika saya tidak menyertakan order by kemungkinan besar 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.

29
goric

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

SELECT *
FROM Northwind..Orders 
ORDER BY NEWID()
23
Nomad

Ini adalah pertanyaan lama, tetapi salah satu aspek dari diskusi tersebut hilang, menurut saya - PERFORMANCE. ORDER BY NewId() adalah jawaban umum. Ketika seseorang menyukai mereka menambahkan bahwa Anda harus benar-benar membungkus NewID() in 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 ...

enter image description here

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. Menjalankan Order By newid() pada tabel ini menghasilkan 53.700 membaca 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 metode newid() rusak. Jadi apa yang harus dilakukan anak laki-laki?

Memenuhi TABLESAMPLE ()

Dalam SQL 2005 kemampuan baru yang disebut TABLESAMPLE telah 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 baris aktual 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 Top(). Idenya adalah Anda dapat membuat tabel ginormous Anda tampak lebih kecil prior untuk jenis yang 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 opsi REPEATABLE() di mana Anda dapat memberikan "Seed" untuk pemilihan halaman. Ini akan menghasilkan pemilihan sampel yang stabil.

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

16
EBarr

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

Hati-hati menggunakan Rand() - dalam banyak konteks hanya dieksekusi sekali per pernyataan sehingga ORDER BY Rand() tidak 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 angka "acak", yang sama untuk setiap baris. Jumlahnya bervariasi setiap kali Anda menjalankan kueri, tetapi sama untuk setiap baris setiap kali.

Untuk menunjukkan bahwa hal yang sama berlaku dengan Rand() digunakan dalam klausa ORDER BY, 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 menilai kembali tujuan UUIDs akan rusak ketika memasukkan banyak baris baru dalam satu statemnt dengan pengidentifikasi unik saat dikunci, jadi:

SELECT display_name FROM tr_person ORDER BY NEWID()

tidak memesan nama "secara acak".

DBMS Lainnya

Di atas berlaku untuk MSSQL (setidaknya 2005 dan 2008, dan jika saya ingat juga 2000). Fungsi yang 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 sewenang-wenang lainnya, seperti Rand (), lebih mungkin 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() dalam SQL Server (mungkin ada perbedaan kinerja kecil dari ini juga karena pemesanan akan dilakukan pada nilai 32-bit bukan yang 128-bit, meskipun apakah manfaatnya lebih besar daripada biaya menjalankan CHECKSUM() per nilai pertama saya akan meninggalkan Anda untuk menguji).

Catatan Sisi

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).

Kinerja

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.

16
David Spillett

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;

Range query

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'));

random rows query

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, baca-baca bacaan 408 
 Tabel 'Meja Kerja'. Pindai hitung 0, bacaan logis 0, bacaan fisik 0, bacalah 0 
 Tabel 'Workfile'. Pindai hitungan 0, bacaan logis 0, bacaan fisik 0, baca-depan bacaan 0 
 
 Waktu Eksekusi Server SQL: 
 Waktu CPU = 0 ms, waktu yang berlalu = 9 ms. 

Cobalah di Explorer Data Stack Exchange.

3
Paul White 9

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

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

Jika Anda harus mengocok set hasil yang besar dan membatasinya sesudahnya, maka lebih baik menggunakan SQL Server TABLESAMPLE in SQL Server alih-alih fungsi acak dalam klausa ORDER BY.

Jadi, anggap kita memiliki tabel database berikut:

enter image description here

Dan baris berikut dalam tabel song:

| 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 fungsi NEWID, 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-lagu tersebut terdaftar dalam urutan acak, berkat panggilan fungsi NEWID yang digunakan oleh klausa ORDER BY.

0
Vlad Mihalcea