从 sqlite 表中选择随机行

Posted

技术标签:

【中文标题】从 sqlite 表中选择随机行【英文标题】:Select random row from a sqlite table 【发布时间】:2011-01-17 19:01:48 【问题描述】:

我有一个具有以下架构的 sqlite 表:

CREATE TABLE foo (bar VARCHAR)

我将这张表用作字符串列表的存储。

如何从这个表中随机选择一行?

【问题讨论】:

多个***.com/questions/4114940/… 【参考方案1】:

看看Selecting a Random Row from an SQLite Table

SELECT * FROM table ORDER BY RANDOM() LIMIT 1;

【讨论】:

如何将此解决方案扩展到连接?使用SELECT a.foo FROM a JOIN b ON a.id = b.id WHERE b.bar = 2 ORDER BY RANDOM() LIMIT 1; 时,我总是得到相同的行。 是否可以播种随机数。例如今天中午使用 unix epoc 播种的每日图书,因此即使查询多次运行,它也会全天显示同一本书。是的,我知道缓存对于这个用例更有效,这只是一个例子。 FWIW 我的问题实际上在这里得到了回答。答案是你不能播种随机数。 ***.com/questions/24256258/… 链接现在超时。此外,“按 RANDOM() 排序”也很糟糕。很想对这个答案投反对票,但是,不,这甚至不值得。【参考方案2】:
SELECT   bar
FROM     foo
ORDER BY Random()
LIMIT    1

【讨论】:

既然会先选择整个表格内容,那么对于大表格来说是不是很耗时? 你不能只使用“WHERE”条件限制范围吗?【参考方案3】:

怎么样:

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。 请注意,我说的是 /fixed size/ 记录 - 它应该很容易直接扫描到数据中的正确字节(不是读取那么多行),但他们必须明确实施优化。 @KenWilliams:SQLite 中没有固定大小的记录,它是动态类型的,并且数据不必与声明的关联 (sqlite.org/fileformat2.html#section_2_1) 匹配。一切都存储在 b-tree 页面中,因此无论哪种方式,它都必须至少对叶子进行 b-tree 搜索。为了有效地实现这一点,它需要将子树的大小与每个子指针一起存储。这将是太多的开销而没有什么好处,因为您仍然无法优化联接、排序依据等的偏移量......(并且没有 ORDER BY,订单是未定义的。)【参考方案4】:

下面的解决方案比anktastic的快很多(count(*)的开销很大,但是如果能缓存的话,差别应该不会那么大),本身比“order by random”快很多()" 当您有大量行时,尽管它们有一些不便之处。

如果您的 rowid 相当紧凑(即删除很少),那么您可以执行以下操作(使用 (select max(rowid) from foo)+1 而不是 max(rowid)+1 提供更好的性能,如 cmets 中所述):

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 的重新构建是一个维护操作,不太可能经常发生。

然而,我最近在mailing list 上发现的另一种方法是在删除时触发一个触发器,以将具有最大 rowid 的行移动到当前已删除的行中,这样就不会留下任何漏洞。

最后,注意 rowid 和整数主键自增的行为是不一样的(对于 rowid,当插入新行时,选择 max(rowid)+1,而它是 higest-value-ever-seen +1 为主键),因此最后一个解决方案不适用于 random_foo 中的自动增量,但其他方法可以。

【讨论】:

就像我刚刚在邮件列表中看到的那样,您可以使用 rowid >= [random] 而不是 =,而不是使用回退方法(方法 2),但与方法二。 这是一个很好的答案;但是它有一个问题。 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/… 这是一个很好的答案。您可以通过将 % 替换为 ABS(RANDOM() / 9223372036854775808 * ) 来修复分布的随机性,但这不是很便携。 感谢您的回复 - 多年后仍然有用。 rowid >= [random] 的性能与最新版本的 SQLite 中的回退方法一样好。我用本地基准(SQLite 3.34.1)确认每个版本运行 250k 查询EXPLAIN 也确认执行计划是有效的。另外,根据SQLite query optimizer docs,SQLite 现在也优化了SELECT max(row) + 1【参考方案5】:

这里是@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

【讨论】:

【参考方案6】:

您需要在查询中添加 "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
--
*每次选择,回报都会不同。

【讨论】:

虽然不禁止仅使用代码的答案,但请理解这是一个问答社区,而不是众包社区,并且通常,如果 OP 理解作为答案发布的代码,他/她会自己想出一个类似的解决方案,并且不会一开始就发布问题。因此,请通过解释如何和/或为什么工作,为您的答案和/或代码提供上下文 我更喜欢这个解决方案,因为它允许我搜索 n 行。在我的例子中,我需要来自数据库的 100 个随机样本 - ORDER BY RANDOM() 结合 LIMIT 100 正是这样做的。【参考方案7】:

我为大型 sqlite3 数据库提出了以下解决方案:

SELECT * FROM foo WHERE rowid = abs(random()) % (SELECT max(rowid) FROM foo) + 1; 

abs(X) 函数返回数值参数的绝对值 十。

random() 函数返回一个伪随机整数 -9223372036854775808 和 +9223372036854775807。

运算符 % 输出其左操作数取其右操作数的整数值。

最后,您添加 +1 以防止 rowid 等于 0。

【讨论】:

很好的尝试,但我认为这不会奏效。如果删除了 rowId = 5 的行,但 rowIds 1,2,3,4,6,7,8,9,10 仍然存在怎么办?然后,如果选择的随机 rowId 为 5,则此查询将不返回任何内容。

以上是关于从 sqlite 表中选择随机行的主要内容,如果未能解决你的问题,请参考以下文章

如何从 SQL 数据库表中选择随机行? [复制]

从mysql中的大表中快速选择随机行

从mysql中的大表中快速选择随机行

如何在 Postgres 中从具有非均匀分布的表中选择随机行?

MySQL - 从大表中选择随机行

从具有加权行概率的 PostgreSQL 表中选择随机行