从sqlite表中选择随机行


Answers:


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播种的当天的书,因此即使查询多次运行,它也全天显示同一本书。是的,我知道对于此用例而言,缓存更有效,仅是示例。
danielson317

FWIW我的问题实际上在这里得到了回答。答案是您不能播种随机数。stackoverflow.com/questions/24256258/...
danielson317

31

以下解决方案比anktastic的解决方案要快得多(count(*)花费很多,但如果可以对其进行缓存,则差异不应该那么大),其本身比“ order by random()”要快得多。当您有很多行时,尽管它们有一些不便。

如果您的行标识符比较紧凑(即删除了很少的内容),则可以执行以下操作(使用(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,对于主键),因此最后一个解决方案无法在random_foo中使用自动递增功能,但其他方法则可以。


就像我刚刚在邮件列表上锯,而不是具有备用方法(方法2),您可以只使用ROWID> = [随机]代替=,但它实际上是slugissingly缓慢相比,方法2
苏珊Dupéron

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相结合就可以做到这一点。
mnr

17

关于什么:

SELECT COUNT(*) AS n FROM foo;

然后在[0,n)中选择一个随机数m

SELECT * FROM foo LIMIT 1 OFFSET m;

您甚至可以将第一个数字(n)保存在某个地方,并且仅在数据库计数更改时才对其进行更新。这样,您不必每次都执行SELECT COUNT。


1
那是一个很好的快速方法。选择不止1个行并不能很好地泛化,但是OP只要求1行,所以我想这很好。
肯·威廉姆斯

需要注意的是,找到OFFSET偏移量所需的时间似乎取决于偏移量的大小-第2行很快,第2百万行花费了一段时间,即使其中的所有数据都是固定大小并且应该能够直接寻求它。至少,这就是SQLite 3.7.13中的样子。
肯·威廉姆斯

@KenWilliams几乎所有数据库都存在“ OFFSET”相同的问题。这是查询数据库,因为它需要读取多行,即使它只会返回1.一种非常低效的方式
乔纳森·艾伦

1
请注意,尽管我在谈论/ fixed size /记录-应该很容易直接扫描到数据中的正确字节(读取那么多行),但是它们必须显式实现优化。
肯·威廉斯

@KenWilliams:SQLite中没有固定大小的记录,它是动态键入的,数据不必与声明的关联性匹配(sqlite.org/fileformat2.html#section_2_1)。一切都存储在b树页面中,因此无论哪种方式,它都必须至少对叶子进行b树搜索。为了有效地完成此操作,将需要将子树的大小与每个子指针一起存储。这将带来太多的开销,却收效甚微,因为您仍然无法优化连接,排序依据等的偏移量(如果没有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的行,但rowId 1,2,3,4,6,7,8,9,10仍然存在怎么办?然后,如果选择的随机rowId为5,则此查询将不返回任何内容。
Calicoder
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.