Chọn hàng ngẫu nhiên từ bảng sqlite


Câu trả lời:


213

Xem phần Chọn hàng ngẫu nhiên từ bảng SQLite

SELECT * FROM table ORDER BY RANDOM() LIMIT 1;

1
Làm thế nào để mở rộng giải pháp này để tham gia? Khi sử dụng SELECT a.foo FROM a JOIN b ON a.id = b.id WHERE b.bar = 2 ORDER BY RANDOM() LIMIT 1;tôi luôn nhận được cùng một hàng.
Helmut Grohne

Có thể gieo số ngẫu nhiên. ví dụ: Sách trong ngày được tạo bằng unix epoc cho ngày hôm nay vào buổi trưa để nó hiển thị cùng một cuốn sách cả ngày ngay cả khi truy vấn được chạy nhiều lần. Có, tôi biết bộ nhớ đệm hiệu quả hơn cho trường hợp sử dụng này chỉ là một ví dụ.
danielson317

FWIW câu hỏi của tôi thực sự đã được trả lời ở đây. Và câu trả lời là bạn không thể gieo số ngẫu nhiên. stackoverflow.com/questions/24256258/…
danielson317

31

Các giải pháp sau đây nhanh hơn nhiều so với anktastic (số lượng (*) tốn rất nhiều, nhưng nếu bạn có thể lưu vào bộ nhớ cache, thì sự khác biệt sẽ không quá lớn), bản thân nó nhanh hơn nhiều so với "order by random ()" khi bạn có một số lượng lớn các hàng, mặc dù chúng có một vài điểm bất tiện.

Nếu các rowid của bạn khá được đóng gói (tức là ít lần xóa), thì bạn có thể làm như sau (sử dụng (select max(rowid) from foo)+1thay vì max(rowid)+1mang lại hiệu suất tốt hơn, như được giải thích trong phần nhận xét):

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

Nếu bạn có lỗ hổng, đôi khi bạn sẽ cố gắng chọn một rowid không tồn tại và lựa chọn sẽ trả về một tập hợp kết quả trống. Nếu điều này không được chấp nhận, bạn có thể cung cấp giá trị mặc định như sau:

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;

Giải pháp thứ hai này không hoàn hảo: phân phối xác suất cao hơn trên hàng cuối cùng (hàng có hàng cao nhất), nhưng nếu bạn thường xuyên thêm nội dung vào bảng, nó sẽ trở thành mục tiêu di động và phân phối xác suất sẽ tốt hơn nhiều.

Tuy nhiên, một giải pháp khác, nếu bạn thường chọn nội dung ngẫu nhiên từ một bảng có nhiều lỗ, thì bạn có thể muốn tạo một bảng chứa các hàng của bảng ban đầu được sắp xếp theo thứ tự ngẫu nhiên:

create table random_foo(foo_id);

Sau đó, định kỳ, điền lại bảng random_foo

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

Và để chọn một hàng ngẫu nhiên, bạn có thể sử dụng phương pháp đầu tiên của tôi (không có lỗ nào ở đây). Tất nhiên, phương pháp cuối cùng này có một số vấn đề về đồng thời, nhưng việc xây dựng lại random_foo là một hoạt động bảo trì không có khả năng xảy ra thường xuyên.

Tuy nhiên, một cách khác, mà tôi đã tìm thấy gần đây trên danh sách gửi thư , là đặt kích hoạt xóa để di chuyển hàng có rowid lớn nhất vào hàng đã xóa hiện tại, để không còn lỗ nào.

Cuối cùng, lưu ý rằng hành vi của rowid và tự động gia tăng khóa chính của một số nguyên không giống nhau (với rowid, khi một hàng mới được chèn, max (rowid) +1 được chọn, khi đó nó là cao nhất-giá trị-từng thấy + 1 cho khóa chính), vì vậy giải pháp cuối cùng sẽ không hoạt động với autoincrement trong random_foo, nhưng các phương thức khác sẽ làm được.


Giống như tôi chỉ thấy trên một mailing list, thay vì phải (phương pháp 2) phương pháp dự phòng, bạn chỉ có thể sử dụng rowid> = [ngẫu nhiên] thay vì =, nhưng nó thực sự slugissingly chậm so với phương pháp 2.
Suzanne Dupéron

3
Đây là một câu trả lời tuyệt vời; tuy nhiên nó có một vấn đề. SELECT max(rowid) + 1sẽ là một truy vấn chậm - nó yêu cầu quét toàn bộ bảng. sqlite chỉ tối ưu hóa truy vấn SELECT max(rowid). Do đó, câu trả lời này sẽ được cải thiện bằng cách: select * from foo where rowid = (abs(random()) % (select (select max(rowid) from foo)+1)); Xem phần này để biết thêm thông tin: sqlite.1065341.n5.nabble.com/…
dasl Ngày

19

Bạn cần đặt "order by RANDOM ()" vào truy vấn của mình.

Thí dụ:

select * from quest order by RANDOM();

Hãy xem một ví dụ hoàn chỉnh

  1. Tạo bảng:
CREATE TABLE  quest  (
    id  INTEGER PRIMARY KEY AUTOINCREMENT,
    quest TEXT NOT NULL,
    resp_id INTEGER NOT NULL
);

Chèn một số giá trị:

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

Một lựa chọn mặc định:

select * from quest;

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

Một lựa chọn ngẫu nhiên:

select * from quest order by RANDOM();
| id |   quest  | resp_id |
   3     128/1       24
   1     1024/4       6
   2     256/2       12
--
* Mỗi lần bạn chọn, thứ tự sẽ khác nhau.

Nếu bạn chỉ muốn trả lại một hàng

select * from quest order by RANDOM() LIMIT 1;
| id |   quest  | resp_id |
   2     256/2       12
--
* Mỗi lần bạn chọn, kết quả trả về sẽ khác nhau.


Mặc dù các câu trả lời chỉ có mã không bị cấm, nhưng hãy hiểu rằng đây là một cộng đồng Hỏi & Đáp, chứ không phải là cộng đồng tìm nguồn cung ứng từ cộng đồng và thông thường, nếu OP hiểu mã được đăng dưới dạng câu trả lời, anh ấy / cô ấy sẽ đưa ra với một giải pháp tương tự của riêng anh ấy / cô ấy và sẽ không đăng câu hỏi ngay từ đầu. Do đó, vui lòng cung cấp ngữ cảnh cho câu trả lời và / hoặc mã của bạn bằng cách giải thích cách thức và / hoặc tại sao nó hoạt động.
XenoRo

2
Tôi thích giải pháp này hơn, vì nó cho phép tôi tìm kiếm n dòng. Trong trường hợp của tôi, tôi cần 100 mẫu ngẫu nhiên từ cơ sở dữ liệu - ORDER BY RANDOM () kết hợp với LIMIT 100 thực hiện chính xác điều đó.
mnr

17

Thế còn:

SELECT COUNT(*) AS n FROM foo;

sau đó chọn một số ngẫu nhiên m trong [0, n) và

SELECT * FROM foo LIMIT 1 OFFSET m;

Bạn thậm chí có thể lưu số đầu tiên ( n ) ở đâu đó và chỉ cập nhật nó khi số lượng cơ sở dữ liệu thay đổi. Bằng cách đó, bạn không phải thực hiện SELECT COUNT mỗi lần.


1
Đó là một phương pháp nhanh chóng tốt đẹp. Nó không khái quát lắm khi chọn nhiều hơn 1 hàng, nhưng OP chỉ yêu cầu 1 hàng, vì vậy tôi đoán điều đó ổn.
Ken Williams

Một điều thú vị cần lưu ý là thời gian cần thiết để tìm giá trị OFFSETdường như tăng lên tùy thuộc vào kích thước của khoảng chênh lệch - hàng 2 là nhanh, hàng 2 triệu mất một lúc, ngay cả khi tất cả dữ liệu trong đó là kích thước cố định và nên có thể trực tiếp tìm kiếm nó. Ít nhất, đó là những gì nó trông giống như trong SQLite 3.7.13.
Ken Williams

@KenWilliams Khá nhiều cơ sở dữ liệu có cùng vấn đề với `OFFSET ''. Đó là một cách rất hiệu quả để truy vấn cơ sở dữ liệu bởi vì nó cần để đọc mà nhiều hàng mặc dù nó sẽ chỉ trả lại 1.
Jonathan Allen

1
Lưu ý rằng tôi đã nói về / kích thước cố định / bản ghi - sẽ dễ dàng quét trực tiếp đến đúng byte trong dữ liệu ( không đọc nhiều hàng như vậy), nhưng chúng phải thực hiện tối ưu hóa một cách rõ ràng.
Ken Williams

@KenWilliams: không có bản ghi có kích thước cố định trong SQLite, nó được nhập động và dữ liệu không phải khớp với các sở thích đã khai báo ( sqlite.org/fileformat2.html#section_2_1 ). Mọi thứ đều được lưu trữ trong các trang b-tree, vì vậy dù theo cách nào thì nó cũng phải thực hiện ít nhất một tìm kiếm b-tree về phía chiếc lá. Để thực hiện điều này một cách hiệu quả, nó sẽ cần phải lưu trữ kích thước của cây con cùng với mỗi con trỏ con. Nó sẽ là quá nhiều chi phí cho một chút lợi ích, vì bạn vẫn sẽ không thể tối ưu hóa OFFSET cho các lần tham gia, đặt hàng theo, v.v. (và không có ĐẶT HÀNG THEO đơn hàng là không xác định.)
Yakov Galka

13
SELECT   bar
FROM     foo
ORDER BY Random()
LIMIT    1

11
Vì nó sẽ chọn toàn bộ nội dung bảng trước, điều này sẽ rất tốn thời gian đối với các bảng lớn phải không?
Alex_coder

1
Bạn không thể chỉ giới hạn phạm vi bằng cách sử dụng (các) điều kiện "WHERE"?
jldupont

11

Đây là một sửa đổi của giải pháp @ ank:

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

Giải pháp này cũng hoạt động đối với các chỉ số có khoảng trống, bởi vì chúng tôi ngẫu nhiên hóa một phần bù trong phạm vi [0, đếm). MAXđược sử dụng để xử lý một trường hợp có bảng trống.

Dưới đây là kết quả kiểm tra đơn giản trên bảng có 16k hàng:

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

4

Tôi đã đưa ra giải pháp sau cho cơ sở dữ liệu sqlite3 lớn :

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

Hàm abs (X) trả về giá trị tuyệt đối của đối số số X.

Hàm random () trả về một số nguyên giả ngẫu nhiên giữa -9223372036854775808 và +9223372036854775807.

Toán tử% xuất ra giá trị nguyên của modul toán hạng bên trái cho toán hạng bên phải của nó.

Cuối cùng, bạn thêm +1 để ngăn rowid bằng 0.


1
Cố gắng tốt nhưng tôi không nghĩ điều này sẽ hiệu quả. Điều gì sẽ xảy ra nếu một hàng có rowId = 5 bị xóa, nhưng rowIds 1,2,3,4,6,7,8,9,10 vẫn tồn tại? Sau đó, nếu rowId ngẫu nhiên được chọn là 5, truy vấn này sẽ không trả về kết quả nào.
Calicoder
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.