我有一个sqlite
具有以下架构的表:
CREATE TABLE foo (bar VARCHAR)
我使用这个表作为字符串列表的存储。
如何从此表中选择随机行?
SELECT * FROM table ORDER BY RANDOM() LIMIT 1;
下面的解决方案比anktastic的要快很多(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;
第二种解决方案并不完美:最后一行(具有最高 rowid 的那一行)的概率分布更高,但如果您经常向表中添加东西,它将成为移动目标,概率分布应该是好多了。
另一种解决方案,如果您经常从有很多孔的表中选择随机的东西,那么您可能希望创建一个包含以随机顺序排序的原始表的行的表:
create table random_foo(foo_id);
然后,定期重新填充表 random_foo
delete from random_foo;
insert into random_foo select id from foo;
而要随机选择一行,可以使用我的第一种方法(这里没有洞)。当然,这最后一种方法存在一些并发问题,但是 random_foo 的重新构建是一种维护操作,不太可能经常发生。
然而,我最近在邮件列表中发现的另一种方法是在 delete 上放置一个触发器,以将具有最大 rowid 的行移动到当前已删除的行中,这样就不会留下任何漏洞。
最后,注意 rowid 和整数主键自增的行为是不一样的(对于 rowid,当插入新行时,选择 max(rowid)+1,而它是 higest-value-ever-seen+1主键),因此最后一个解决方案不适用于 random_foo 中的自动增量,但其他方法可以。
您需要在查询中添加“order by 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
--
*每次选择,顺序都会不同。
如果你只想返回一行
select * from quest order by RANDOM() LIMIT 1;
| id | quest | resp_id |
2 256/2 12
--
*每次选择,回报都会不同。
关于什么:
SELECT COUNT(*) AS n FROM foo;
然后在 [0, n) 中选择一个随机数m和
SELECT * FROM foo LIMIT 1 OFFSET m;
您甚至可以将第一个数字 ( n ) 保存在某处,并且仅在数据库计数更改时更新它。这样您就不必每次都执行 SELECT COUNT。
这是@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
SELECT bar
FROM foo
ORDER BY Random()
LIMIT 1
我为大型 sqlite3 数据库提出了以下解决方案:
SELECT * FROM foo WHERE rowid = abs(random()) % (SELECT max(rowid) FROM foo) + 1;
abs(X) 函数返回数值参数 X 的绝对值。
random() 函数返回一个介于 -9223372036854775808 和 +9223372036854775807 之间的伪随机整数。
运算符 % 输出其左操作数模其右操作数的整数值。
最后,您添加 +1 以防止 rowid 等于 0。