Выбрать случайную строку из таблицы sqlite

119

У меня есть sqliteтаблица со следующей схемой:

CREATE TABLE foo (bar VARCHAR)

Я использую эту таблицу как хранилище для списка строк.

Как выбрать случайную строку из этой таблицы?

Alex_coder
источник
несколько stackoverflow.com/questions/4114940/…
Чиро Сантилли 郝海东 冠状 病 六四 事件 法轮功 06

Ответы:

214

Взгляните на выбор случайной строки из таблицы 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;меня всегда получается один и тот же ряд.
Helmut Grohne
Можно ли засеять случайное число. например, «Книга дня» заполнена unix epoc на сегодняшний день в полдень, поэтому она показывает одну и ту же книгу весь день, даже если запрос выполняется несколько раз. Да, я знаю, что кеширование более эффективно для этого варианта использования, просто для примера.
danielson317
FWIW, на мой вопрос здесь есть ответ. И ответ: вы не можете засеять случайное число. stackoverflow.com/questions/24256258/…
danielson317
31

Следующие ниже решения намного быстрее, чем у anktastic (счетчик (*) стоит дорого, но если вы можете его кэшировать, то разница не должна быть такой большой), что само по себе намного быстрее, чем «порядок случайным ()» когда у вас большое количество рядов, хотя в них есть несколько неудобств.

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

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

Если у вас есть дыры, вы иногда будете пытаться выбрать несуществующий rowid, и выбор вернет пустой набор результатов. Если это неприемлемо, вы можете указать значение по умолчанию, подобное этому:

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;

Это второе решение не идеально: распределение вероятностей выше в последней строке (той, у которой самый высокий rowid), но если вы часто добавляете что-то в таблицу, она станет движущейся целью, и распределение вероятностей должно быть намного лучше.

Еще одно решение: если вы часто выбираете случайный материал из таблицы с большим количеством дыр, вы можете создать таблицу, содержащую строки исходной таблицы, отсортированные в случайном порядке:

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

Вам нужно указать в вашем запросе "order by 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
--
* Каждый раз, когда вы выбираете, возврат будет другим.

Роберто Гоес
источник
Хотя ответы только на код не запрещены, пожалуйста, поймите, что это сообщество вопросов и ответов, а не краудсорсинговое, и что, как правило, если OP понимает, что код публикуется как ответ, он / она подошел бы с аналогичным решением самостоятельно, и вообще не отправил бы вопрос. Таким образом, предоставьте контекст для своего ответа и / или кода, объяснив, как и / или почему это работает.
XenoRo
2
Я предпочитаю это решение, поскольку оно позволяет мне искать n строк. В моем случае мне понадобилось 100 случайных выборок из базы данных - ORDER BY RANDOM () в сочетании с LIMIT 100 делает именно это.
пн
17

Что о:

SELECT COUNT(*) AS n FROM foo;

затем выберите случайное число m в [0, n) и

SELECT * FROM foo LIMIT 1 OFFSET m;

Вы даже можете где-нибудь сохранить первое число ( n ) и обновлять его только при изменении количества в базе данных. Таким образом, вам не нужно каждый раз выполнять SELECT COUNT.

Андрес Киевский
источник
1
Это хороший быстрый метод. Это не очень хорошо подходит для выбора более одной строки, но OP запросил только 1, так что я думаю, что это нормально.
Кен Уильямс
Любопытно отметить, что время, необходимое для поиска, OFFSETкажется, увеличивается в зависимости от размера смещения - строка 2 выполняется быстро, строка 2 миллиона занимает некоторое время, даже если все данные в файле имеют фиксированный размер и должен иметь возможность искать прямо к нему. По крайней мере, так это выглядит в SQLite 3.7.13.
Кен Уильямс
@KenWilliams Практически все базы данных имеют одну и ту же проблему с OFFSET. Это очень неэффективный способ запроса к базе данных, потому что он должен прочитать такое количество строк, даже если он вернет только 1.
Джонатан Аллен,
1
Обратите внимание, что я говорил о / фиксированном размере / записях - должно быть легко сканировать непосредственно до правильного байта в данных ( не считывая так много строк), но им придется реализовать оптимизацию явно.
Кен Уильямс
@KenWilliams: в SQLite нет записей фиксированного размера, он динамически типизируется, и данные не обязательно должны соответствовать заявленному сходству ( sqlite.org/fileformat2.html#section_2_1 ). Все хранится на страницах b-дерева, так что в любом случае он должен выполнять хотя бы поиск b-дерева в направлении листа. Чтобы добиться этого эффективно, необходимо сохранить размер поддерева вместе с каждым дочерним указателем. Это будет слишком много накладных расходов и мало пользы, так как вы все равно не сможете оптимизировать OFFSET для объединений, упорядочивания и т. Д. (И без ORDER BY порядок не определен.)
Яков Галка,
13
SELECT   bar
FROM     foo
ORDER BY Random()
LIMIT    1
Светлозар Ангелов
источник
11
Поскольку сначала будет выбрано все содержимое таблицы, не займет ли это много времени для больших таблиц?
Alex_coder
1
Разве вы не можете просто ограничить объем, используя условие (я) «ГДЕ»?
jldupont
11

Вот модификация решения @ank:

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

Это решение также работает для индексов с пробелами, потому что мы рандомизируем смещение в диапазоне [0, count). MAXиспользуется для обработки случая с пустой таблицей.

Вот простые результаты теста на таблице с 16k строками:

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, чтобы rowid не был равен 0.

Максимум
источник
1
Хорошая попытка, но я не думаю, что это сработает. Что, если строка с rowId = 5 была удалена, но rowIds 1,2,3,4,6,7,8,9,10 все еще существует? Затем, если выбран случайный идентификатор строки 5, этот запрос ничего не вернет.
Calicoder