Виберіть випадковий рядок із таблиці 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.