Виберіть випадковий рядок із таблиці sqlite


119

У мене є sqliteтаблиця з наступною схемою:

CREATE TABLE foo (bar VARCHAR)

Я використовую цю таблицю як сховище для списку рядків.

Як вибрати випадковий рядок із цієї таблиці?


Відповіді:


213

Погляньте на вибір випадкових рядків із таблиці SQLite

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

1
Як поширити це рішення на приєднання? Під час використання SELECT a.foo FROM a JOIN b ON a.id = b.id WHERE b.bar = 2 ORDER BY RANDOM() LIMIT 1;я завжди отримую один і той же рядок.
Гельмут Гроне

Чи можна насіти випадкове число. наприклад, Книга дня, засіяна Unix epoc, сьогодні опівдні, тому вона показує одну і ту ж книгу протягом усього дня, навіть якщо запит виконується кілька разів. Так, я знаю, що кешування є більш ефективним для цього випадку використання лише прикладом.
danielson317

FWIW на моє питання насправді тут відповіли. І відповідь - ви не можете насіти випадкове число. stackoverflow.com/questions/24256258 / ...
danielson317

31

Наступні рішення набагато швидше, ніж антистатичні (count (*) коштує багато, але якщо ви можете кешувати це, то різниця не повинна бути такою великою), що саме по собі набагато швидше, ніж "порядок випадково ()" коли у вас велика кількість рядків, хоча вони мають кілька незручностей.

Якщо ваші ряди досить упаковані (тобто кілька видалень), ви можете зробити наступне (використовуючи (select max(rowid) from foo)+1замість того, щоб max(rowid)+1покращити ефективність, як пояснено в коментарях):

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

Якщо у вас є отвори, іноді ви намагатиметеся вибрати неіснуючий рядин, і вибір поверне порожній набір результатів. Якщо це неприйнятно, ви можете надати таке значення за замовчуванням, як це:

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;

Це друге рішення не є ідеальним: розподіл ймовірностей вищий на останньому рядку (той, що має найвищий рядід), але якщо ви часто додаєте речі до таблиці, він стане рухомою ціллю, і розподіл ймовірностей повинен бути набагато краще.

Ще одне рішення: якщо ви часто вибираєте випадкові речі з таблиці з великою кількістю отворів, то, можливо, вам захочеться створити таблицю, яка містить рядки оригінальної таблиці, відсортовані у випадковому порядку:

create table random_foo(foo_id);

Потім, періодично, заповніть таблицю random_foo

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

І щоб вибрати випадковий рядок, ви можете скористатися моїм першим методом (тут немає дірок). Звичайно, цей останній метод має певні проблеми з одночасністю, але відновлення random_foo - це операція технічного обслуговування, яка, швидше за все, не трапляється дуже часто.

Але ще один спосіб, який я нещодавно знайшов у списку розсилки , - це зробити тригер для видалення, щоб перемістити рядок з найбільшим рядком у поточний видалений рядок, щоб не залишилося жодних отворів.

Нарешті, зауважте, що поведінка rowid та автоматичного посилення первинного ключа не є тотожним (при rowid, коли вставляється новий рядок, вибирається max (rowid) +1, якщо це найбільше значення, яке коли-небудь бачили + 1 для первинний ключ), тож останнє рішення не працюватиме з автоматичним збільшенням у random_foo, але інші методи будуть.


Як я щойно бачила у списку розсилки, замість того, щоб використовувати метод резервного копіювання (метод 2), ви можете просто використовувати rowid> = [random] замість =, але насправді це повільно повільно порівняно зі способом 2.
Сюзанна Дуперон,

3
Це чудова відповідь; однак у нього є одна проблема. SELECT max(rowid) + 1буде повільний запит - він вимагає повного сканування таблиці. sqlite лише оптимізує запит SELECT max(rowid). Таким чином, ця відповідь буде покращена: select * from foo where rowid = (abs(random()) % (select (select max(rowid) from foo)+1)); Дивіться це для отримання додаткової інформації: sqlite.1065341.n5.nabble.com/…
dasl

19

Вам потрібно поставити "замовлення на RANDOM ()" у вашому запиті.

Приклад:

select * from quest order by RANDOM();

Подивимось повний приклад

  1. Створіть таблицю:
CREATE TABLE  quest  (
    id  INTEGER PRIMARY KEY AUTOINCREMENT,
    quest TEXT NOT NULL,
    resp_id INTEGER NOT NULL
);

Вставлення деяких значень:

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

Вибір за замовчуванням:

select * from quest;

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

Вибір випадкових випадків:

select * from quest order by RANDOM();
| id |   quest  | resp_id |
   3     128/1       24
   1     1024/4       6
   2     256/2       12
--
* Щоразу, коли ви вибираєте, порядок буде різним.

Якщо ви хочете повернути лише один ряд

select * from quest order by RANDOM() LIMIT 1;
| id |   quest  | resp_id |
   2     256/2       12
--
* Кожен раз, коли ви вибираєте, повернення буде різним.


Хоча відповіді, що стосуються лише коду, не заборонені, будь ласка, розумійте, що це спільнота з питань питань, а не натовп, і що, якби ОР розуміла, що код розміщується як відповідь, він / вона придумав би з подібним рішенням самостійно і не ставив би питання в першу чергу. Будь ласка, надайте контекст вашої відповіді та / або коду, пояснивши, як і / або чому він працює.
XenoRo

2
Я вважаю за краще це рішення, оскільки воно дозволяє мені шукати n рядків. У моєму випадку мені знадобилося 100 випадкових вибірок із бази даних - ЗАМОВИТИ ЗА РАНДОМ () у поєднанні з LIMIT 100 робить саме це.
mnr

17

А як на рахунок:

SELECT COUNT(*) AS n FROM foo;

потім виберіть випадкове число m в [0, n) і

SELECT * FROM foo LIMIT 1 OFFSET m;

Ви навіть можете зберегти перше число ( n ) десь і лише оновити його, коли зміниться кількість баз даних. Таким чином, вам не потрібно робити ВИБІР КОЛЕТИ кожен раз.


1
Це приємний швидкий метод. Це не дуже добре узагальнює вибір більше 1 ряду, але ОП попросив лише 1, тож я думаю, це добре.
Кен Вільямс

Цікаво зауважити, що час, необхідний для пошуку, OFFSETздається, збільшується залежно від розміру зміщення - рядок 2 швидкий, 2 мільйони рядків займає деякий час, навіть коли всі дані у фіксованому розмірі і це повинні мати можливість безпосередньо звертатися до цього. Принаймні, так виглядає в SQLite 3.7.13.
Кен Вільямс

@KenWilliams Досить багато всіх баз даних мають однакову проблему з `OFFSET``. Це дуже неефективний спосіб запиту до бази даних, оскільки йому потрібно прочитати стільки рядків, хоча вона повернеться лише 1.
Джонатан Аллен

1
Зауважте, що я говорив про / фіксований розмір / записи, хоча - слід легко сканувати безпосередньо правильний байт даних ( не читаючи стільки рядків), але оптимізацію доведеться чітко реалізувати.
Кен Вільямс

@KenWilliams: у SQLite немає записів фіксованого розміру, він динамічно набирається, і дані не повинні відповідати заявленим спорідненостям ( sqlite.org/fileformat2.html#section_2_1 ). Все зберігається на сторінках b-дерева, так що в будь-якому випадку це потрібно зробити хоча б b-деревський пошук у напрямку до листа. Для того, щоб досягти цього ефективно, потрібно було б зберігати розмір піддерева разом із кожним дочірнім вказівником. Це було б занадто великим накладом витрат для невеликої вигоди, оскільки ви все одно не зможете оптимізувати OFFSET для приєднання, замовлення тощо ... (і без ЗАМОВЛЕННЯ ЗА замовленням не визначено)
Яків Галка

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

11
Оскільки вона спочатку вибере весь вміст таблиці, чи не буде це дуже трудомістким для великих таблиць?
Alex_coder

1
Ви не можете просто обмежити сферу застосування, використовуючи умови "WHERE"?
jldupont

11

Ось модифікація рішення @ ank:

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

Це рішення також працює для індексів з пробілами, оскільки ми рандомізуємо зміщення в діапазоні [0, підрахунок). MAXвикористовується для обробки справи з порожнім столом.

Ось прості результати тесту на таблиці з 16-ти рядковими рядками:

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

Я створив таке рішення для великих баз даних sqlite3 :

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

Функція abs (X) повертає абсолютне значення числового аргументу X.

Функція random () повертає псевдовипадкове ціле число між -9223372036854775808 та +9223372036854775807.

Оператор% виводить ціле значення лівого операнда по модулю його правого операнда.

Нарешті, ви додаєте +1, щоб запобігти рівню 0.


1
Добре спробуйте, але я не думаю, що це спрацює. Що робити, якщо рядок з rowId = 5 було видалено, але rowIds 1,2,3,4,6,7,8,9,10 все ще існують? Тоді, якщо обраний випадковий rowId дорівнює 5, цей запит нічого не поверне.
Calicoder
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.