次のスキーマを持つsqlite
テーブルがあります。
CREATE TABLE foo (bar VARCHAR)
このテーブルを文字列のリストのストレージとして使用しています。
このテーブルからランダムな行を選択するにはどうすればよいですか?
SQLiteテーブルからランダムな行を選択する をご覧ください
SELECT * FROM table ORDER BY RANDOM() LIMIT 1;
次のソリューションは、anktasticのソリューションよりもはるかに高速です(count(*)には多くのコストがかかりますが、キャッシュできる場合、差はそれほど大きくないはずです)。それ自体は「order(random by)」よりもはるかに高速です。いくつかの不便はありますが、多数の行がある場合。
ROWIDがかなりパックされている(つまり、削除が少ない)場合は、次のことができます(コメントで説明されているように、max(rowid)+1
の代わりに(select max(rowid) from foo)+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の再構築はメンテナンス操作であり、あまり頻繁に行われることはありません。
しかし、 メーリングリスト で最近発見した別の方法は、削除にトリガーを設定して、最大のROWIDを持つ行を現在の削除済み行に移動して、ホールが残らないようにすることです。
最後に、rowidと整数の主キーの自動インクリメントの動作は同じではないことに注意してください(rowidでは、新しい行が挿入されると、max(rowid)+1が選択されますが、そのため、最後のソリューションはrandom_fooの自動インクリメントでは機能しませんが、他のメソッドでは機能します。
どうですか:
SELECT COUNT(*) AS n FROM foo;
次に、乱数を選択しますm in [0、n)and
SELECT * FROM foo LIMIT 1 OFFSET m;
最初の数(n)をどこかに保存し、データベースカウントが変更された場合にのみ更新することもできます。そうすれば、毎回SELECT COUNTを行う必要がなくなります。
クエリにput "RANDOM()による順序付け"が必要です。
例:
select * from quest order by RANDOM();
完全な例を見てみましょう
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
--
SELECT bar
FROM foo
ORDER BY Random()
LIMIT 1
@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
大規模なsqlite3データベースに対して次のソリューションを思いつきました:
SELECT * FROM foo WHERE rowid = abs(random()) % (SELECT max(rowid) FROM foo) + 1;
Abs(X)関数は、数値引数Xの絶対値を返します。
Random()関数は、-9223372036854775808から+9223372036854775807までの擬似乱数整数を返します。
演算子%は、右オペランドを法とする左オペランドの整数値を出力します。
最後に、+ 1を追加して、rowidが0にならないようにします。