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;と常に同じ行が表示されます。
Helmut Grohne 2013

乱数をシードすることは可能ですか?たとえば、今日の正午にunix epocがシードされた1日の本なので、クエリが複数回実行されても、同じ本が1日中表示されます。はい、このユースケースのキャッシングは一例にすぎません。
danielson317

FWIW私の質問は実際にここで答えられます。答えは、乱数をシードできないことです。stackoverflow.com/questions/24256258/...
danielson317

31

次の解決策は、antkasticの場合よりもはるかに高速です(count(*)は多くのコストがかかりますが、キャッシュできる場合、その差はそれほど大きくないはずです)。これは、「order by random()」よりはるかに高速ですいくつかの不便がありますが、多数の行がある場合。

ROWIDがかなりパックされている(つまり、削除が少ない)場合は、次の操作を実行できます(コメントで説明されているように、(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;

この2番目のソリューションは完全ではありません。確率の分布は最後の行(ROWIDが最も高い行)の方が高くなりますが、テーブルに項目を頻繁に追加すると、移動ターゲットになり、確率の分布はずっといい。

さらに別の解決策として、穴がたくさんあるテーブルからランダムなものを頻繁に選択する場合は、元のテーブルの行をランダムな順序でソートしたテーブルを作成することができます。

create table random_foo(foo_id);

次に、定期的に、テーブルrandom_fooを再入力します

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

そして、ランダムな行を選択するには、最初の方法を使用できます(ここには穴はありません)。もちろん、この最後の方法にはいくつかの並行性の問題がありますが、random_fooの再構築は、あまり頻繁に発生する可能性が低い保守操作です。

さらに、私が最近メーリングリストで見つけたもう1つの方法は、削除のトリガーを設定して、最大のROWIDを持つ行を現在の削除された行に移動し、穴が残らないようにすることです。

最後に、rowidと整数の主キーの自動インクリメントの動作は同じではないことに注意してください(rowidでは、新しい行が挿入されると、max(rowid)+1が選択されます。つまり、最後のソリューションは、random_fooの自動インクリメントでは機能しませんが、他のメソッドでは機能します。


代わりにフォールバックメソッド(方法2)を有するのメーリングリスト上のIちょうどのこぎり、同様に、あなただけ使用することができROWID> = [ランダム]の代わりに=が、それはslugissingly遅い方法2に比べて実際にある
スザンヌDupéron

3
これは素晴らしい答えです。ただし、1つの問題があります。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
--
*選択するたびに、順序は異なります。

1行だけを返したい場合

select * from quest order by RANDOM() LIMIT 1;
| id |   quest  | resp_id |
   2     256/2       12
--
*選択するたびに、返品は異なります。


コードのみの回答は禁止されていませんが、これはクラウドソーシングコミュニティではなくQ&Aコミュニティであることを理解してください。通常、OPが回答として投稿されているコードをOPが理解していれば、彼/彼女は現れたはずです。彼/彼女自身で同様の解決策を持ち、そもそも質問を投稿しなかったでしょう。そのため、それがどのように、および/またはなぜ機能するを説明することにより、回答および/またはコードにコンテキストを提供してください。
XenoRo

2
n行を検索できるので、この解決策を選びます。私の場合、データベースから100個のランダムなサンプルが必要でした-ORDER BY RANDOM()とLIMIT 100を組み合わせると、まさにそれが可能です。
mnr 2018

17

何について:

SELECT COUNT(*) AS n FROM foo;

次に、[0、n)で乱数mを選択し、

SELECT * FROM foo LIMIT 1 OFFSET m;

最初の数(n)をどこかに保存して、データベース数が変更されたときにのみ更新することもできます。そうすれば、毎回SELECT COUNTを実行する必要がなくなります。


1
それはすばらしく速い方法です。複数の行を選択することはあまり一般化していませんが、OPは1つだけを要求したので、それで問題ないと思います。
ケンウィリアムズ

奇妙なことに注意すべきことはOFFSET、オフセットのサイズに応じてを見つけるために必要な時間が増えるようです-行2は高速で、行200万は時間がかかります。直接それを探すことができるはずです。少なくとも、SQLite 3.7.13ではこのようになっています。
ケンウィリアムズ

@KenWilliamsほとんどすべてのデータベースで、「OFFSET」に関する同じ問題があります。それは、多くの行を読み取る必要があるので、それが唯一の1を返しますにもかかわらず、データベースを照会するために非常に非効率的な方法である
ジョナサン・アレン

1
/ fixed size /レコードについて話していることに注意してください-データの正しいバイトに直接スキャンするのは簡単です(それほど多くの行を読み取らない)べきですが、最適化を明示的に実装する必要があります。
ケンウィリアムズ

@KenWilliams:SQLiteには固定サイズのレコードはなく、動的に型指定され、データは宣言されたアフィニティと一致する必要はありません(sqlite.org/fileformat2.html#section_2_1)。すべてがbツリーページに格納されているため、少なくともいずれかの方法でリーフに向けてbツリー検索を行う必要があります。これを効率的に行うには、サブツリーのサイズを各子ポインタとともに格納する必要があります。それでも、結合や順序付けなどのOFFSETを最適化できないため、オーバーヘッドが多すぎてほとんどメリットがありません(そして、ORDER BYがなければ、順序は定義されていません)
Yakov Galka


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がまだ存在する場合はどうなりますか?次に、選択されたランダムなrowIdが5の場合、このクエリは何も返しません。
Calicoder
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.