Pilih baris acak dari tabel sqlite

119

Saya memiliki sqlitetabel dengan skema berikut:

CREATE TABLE foo (bar VARCHAR)

Saya menggunakan tabel ini sebagai tempat penyimpanan daftar string.

Bagaimana cara memilih baris acak dari tabel ini?

Alex_coder
sumber
multiple stackoverflow.com/questions/4114940/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Jawaban:

213

Lihat Memilih Baris Acak dari Tabel SQLite

SELECT * FROM table ORDER BY RANDOM() LIMIT 1;
Adriaan Stander
sumber
1
Bagaimana cara memperluas solusi ini untuk bergabung? Saat menggunakan SELECT a.foo FROM a JOIN b ON a.id = b.id WHERE b.bar = 2 ORDER BY RANDOM() LIMIT 1;saya selalu mendapatkan baris yang sama.
Helmut Grohne
Apakah mungkin untuk menyemai nomor acak. mis. Book of the day diunggulkan dengan unix epoc untuk hari ini pada siang hari sehingga menampilkan buku yang sama sepanjang hari meskipun kueri dijalankan beberapa kali. Ya, saya tahu caching lebih efisien untuk kasus penggunaan ini hanya sebagai contoh.
danielson317
FWIW pertanyaan saya sebenarnya terjawab disini. Dan jawabannya adalah Anda tidak dapat menyemai nomor acak. stackoverflow.com/questions/24256258/…
danielson317
31

Solusi berikut jauh lebih cepat daripada solusi anktastic (penghitungan (*) menghabiskan banyak biaya, tetapi jika Anda dapat menyimpannya dalam cache, maka perbedaannya tidak boleh sebesar itu), yang dengan sendirinya jauh lebih cepat daripada "pesan secara acak ()" bila Anda memiliki banyak baris, meskipun ada beberapa ketidaknyamanan.

Jika rowid Anda agak padat (mis. Beberapa penghapusan), maka Anda dapat melakukan hal berikut (menggunakan (select max(rowid) from foo)+1alih-alih max(rowid)+1memberikan kinerja yang lebih baik, seperti yang dijelaskan dalam komentar):

select * from foo where rowid = (abs(random()) % (select (select max(rowid) from foo)+1));

Jika Anda memiliki lubang, Anda kadang-kadang akan mencoba untuk memilih rowid yang tidak ada, dan pemilihan tersebut akan mengembalikan kumpulan hasil kosong. Jika ini tidak dapat diterima, Anda dapat memberikan nilai default seperti ini:

select * from foo where rowid = (abs(random()) % (select (select max(rowid) from foo)+1)) or rowid = (select max(rowid) from node) order by rowid limit 1;

Solusi kedua ini tidak sempurna: distribusi probabilitas lebih tinggi pada baris terakhir (yang memiliki rowid tertinggi), tetapi jika Anda sering menambahkan barang ke tabel, itu akan menjadi target bergerak dan distribusi probabilitas harus jauh lebih baik.

Namun solusi lain, jika Anda sering memilih barang acak dari tabel dengan banyak lubang, maka Anda mungkin ingin membuat tabel yang berisi baris tabel asli yang diurutkan secara acak:

create table random_foo(foo_id);

Kemudian secara berkala, isi kembali tabel random_foo

delete from random_foo;
insert into random_foo select id from foo;

Dan untuk memilih baris acak, Anda dapat menggunakan metode pertama saya (tidak ada lubang di sini). Tentu saja, metode terakhir ini memiliki beberapa masalah konkurensi, tetapi pembangunan kembali random_foo adalah operasi pemeliharaan yang tidak mungkin terjadi terlalu sering.

Namun, ada cara lain yang baru-baru ini saya temukan di milis , adalah dengan memasang pemicu pada delete untuk memindahkan baris dengan rowid terbesar ke baris yang saat ini dihapus, sehingga tidak ada lubang yang tersisa.

Terakhir, perhatikan bahwa perilaku rowid dan autoincrement kunci primer integer tidak identik (dengan rowid, ketika baris baru dimasukkan, maks (rowid) +1 dipilih, sedangkan nilai tertinggi-yang pernah dilihat + 1 untuk kunci utama), jadi solusi terakhir tidak akan berfungsi dengan peningkatan otomatis di random_foo, tetapi metode lain akan berfungsi.

Suzanne Dupéron
sumber
Seperti yang baru saja saya lihat di milis, alih-alih memiliki metode fallback (metode 2), Anda bisa menggunakan rowid> = [random] daripada =, tetapi sebenarnya sangat lambat dibandingkan dengan metode 2.
Suzanne Dupéron
3
Ini adalah jawaban yang bagus; bagaimanapun itu memiliki satu masalah. SELECT max(rowid) + 1akan menjadi kueri yang lambat - ini membutuhkan pemindaian tabel lengkap. sqlite hanya mengoptimalkan kueri SELECT max(rowid). Jadi, jawaban ini akan diperbaiki dengan: select * from foo where rowid = (abs(random()) % (select (select max(rowid) from foo)+1)); Lihat ini untuk info lebih lanjut: sqlite.1065341.n5.nabble.com/…
dasl
19

Anda perlu menempatkan "order by ACAK ()" pada kueri Anda.

Contoh:

select * from quest order by RANDOM();

Mari kita lihat contoh lengkapnya

  1. Buat tabel:
CREATE TABLE  quest  (
    id  INTEGER PRIMARY KEY AUTOINCREMENT,
    quest TEXT NOT NULL,
    resp_id INTEGER NOT NULL
);

Memasukkan beberapa nilai:

insert into quest(quest, resp_id) values ('1024/4',6), ('256/2',12), ('128/1',24);

Pilihan default:

select * from quest;

| id |   quest  | resp_id |
   1     1024/4       6
   2     256/2       12
   3     128/1       24
--

Acak pilih:

select * from quest order by RANDOM();
| id |   quest  | resp_id |
   3     128/1       24
   1     1024/4       6
   2     256/2       12
--
* Setiap kali Anda memilih, urutannya akan berbeda.

Jika Anda ingin mengembalikan hanya satu baris

select * from quest order by RANDOM() LIMIT 1;
| id |   quest  | resp_id |
   2     256/2       12
--
* Setiap kali Anda memilih, hasil akan berbeda.

Roberto Góes
sumber
Meskipun jawaban hanya kode tidak dilarang, harap dipahami bahwa ini adalah komunitas Tanya Jawab, bukan komunitas sumber, dan biasanya, jika OP memahami kode yang diposting sebagai jawaban, dia akan muncul. dengan solusi serupa sendiri, dan tidak akan memposting pertanyaan sejak awal. Karena itu, berikan konteks pada jawaban dan / atau kode Anda dengan menjelaskan bagaimana dan / atau mengapa itu berhasil.
XenoRo
2
Saya lebih suka solusi ini, karena memungkinkan saya untuk mencari n baris. Dalam kasus saya, saya membutuhkan 100 sampel acak dari database - ORDER BY RANDOM () dikombinasikan dengan LIMIT 100 melakukan hal itu.
mnr
17

Bagaimana dengan:

SELECT COUNT(*) AS n FROM foo;

lalu pilih nomor acak m di [0, n) dan

SELECT * FROM foo LIMIT 1 OFFSET m;

Anda bahkan dapat menyimpan angka pertama ( n ) di suatu tempat dan hanya memperbaruinya ketika jumlah database berubah. Dengan begitu Anda tidak perlu melakukan PILIHAN MENGHITUNG setiap waktu.

Andres Kievsky
sumber
1
Itu metode cepat yang bagus. Ini tidak menggeneralisasi dengan baik untuk memilih lebih dari 1 baris, tetapi OP hanya meminta 1, jadi saya rasa tidak apa-apa.
Ken Williams
Hal yang menarik untuk diperhatikan adalah bahwa waktu yang diperlukan untuk menemukan OFFSETtampaknya naik tergantung pada ukuran offset - baris 2 cepat, baris 2 juta membutuhkan waktu, bahkan ketika semua data dalam ukuran tetap dan itu harus bisa mencari langsung ke sana. Setidaknya, seperti itulah tampilannya di SQLite 3.7.13.
Ken Williams
@KenWiams Hampir semua database memiliki masalah yang sama dengan OFFSET. Ini adalah cara yang sangat tidak efisien untuk mengkueri database karena ia perlu membaca banyak baris meskipun hanya akan mengembalikan 1.
Jonathan Allen
1
Perhatikan bahwa saya berbicara tentang / ukuran tetap / catatan meskipun - seharusnya mudah untuk memindai langsung ke byte yang benar dalam data ( tidak membaca banyak baris), tetapi mereka harus menerapkan pengoptimalan secara eksplisit.
Ken Williams
@KenWiams: tidak ada record berukuran tetap di SQLite, ia diketik secara dinamis dan datanya tidak harus cocok dengan afinitas yang dideklarasikan ( sqlite.org/fileformat2.html#section_2_1 ). Semuanya disimpan di halaman b-tree, jadi bagaimanapun caranya setidaknya harus melakukan pencarian b-tree ke arah daun. Untuk mencapai hal ini secara efisien, perlu menyimpan ukuran subpohon bersama dengan setiap penunjuk anak. Ini akan menjadi overhead yang terlalu banyak untuk sedikit manfaat, karena Anda masih tidak akan dapat mengoptimalkan OFFSET untuk gabungan, diurutkan berdasarkan, dll ... (dan tanpa ORDER BY pesanan tidak ditentukan.)
Yakov Galka
13
SELECT   bar
FROM     foo
ORDER BY Random()
LIMIT    1
Svetlozar Angelov
sumber
11
Karena ini akan memilih seluruh isi tabel terlebih dahulu, bukankah ini akan sangat memakan waktu untuk tabel yang besar?
Alex_coder
1
Tidak bisakah Anda membatasi cakupan menggunakan ketentuan "DI MANA"?
jldupont
11

Berikut modifikasi solusi @ ank:

SELECT * 
FROM table
LIMIT 1 
OFFSET ABS(RANDOM()) % MAX((SELECT COUNT(*) FROM table), 1)

Solusi ini juga berfungsi untuk indeks dengan celah, karena kami mengacak offset dalam rentang [0, hitung). MAXdigunakan untuk menangani kasus dengan tabel kosong.

Berikut hasil tes sederhana pada tabel dengan 16k baris:

sqlite> .timer on
sqlite> select count(*) from payment;
16049
Run Time: real 0.000 user 0.000140 sys 0.000117

sqlite> select payment_id from payment limit 1 offset abs(random()) % (select count(*) from payment);
14746
Run Time: real 0.002 user 0.000899 sys 0.000132
sqlite> select payment_id from payment limit 1 offset abs(random()) % (select count(*) from payment);
12486
Run Time: real 0.001 user 0.000952 sys 0.000103

sqlite> select payment_id from payment order by random() limit 1;
3134
Run Time: real 0.015 user 0.014022 sys 0.000309
sqlite> select payment_id from payment order by random() limit 1;
9407
Run Time: real 0.018 user 0.013757 sys 0.000208
vokilam.dll
sumber
4

Saya datang dengan solusi berikut untuk database sqlite3 yang besar :

SELECT * FROM foo WHERE rowid = abs(random()) % (SELECT max(rowid) FROM foo) + 1; 

Fungsi abs (X) mengembalikan nilai absolut dari argumen numerik X.

Fungsi random () mengembalikan bilangan bulat pseudo-random antara -9223372036854775808 dan +9223372036854775807.

Operator% mengeluarkan nilai integer dari operan kirinya modulo operan kanannya.

Terakhir, Anda menambahkan +1 untuk mencegah rowid sama dengan 0.

Max
sumber
1
Usaha yang bagus tapi saya rasa ini tidak akan berhasil. Bagaimana jika baris dengan rowId = 5 dihapus, tetapi rowIds 1,2,3,4,6,7,8,9,10 masih ada? Kemudian, jika rowId acak yang dipilih adalah 5, kueri ini tidak akan menghasilkan apa pun.
Calicoder