从 sqlite 表中选择随机行

问题描述 投票:0回答:8

我有一个

sqlite
表,其架构如下:

CREATE TABLE foo (bar VARCHAR)

我使用此表作为字符串列表的存储。

如何从此表中选择随机行?

sqlite random row
8个回答
314
投票

看看从 SQLite 表中选择随机行

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

35
投票

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

如果你的 rowids 相当拥挤(即很少删除),那么你可以执行以下操作(使用

(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会返回一个空的结果集。如果这是不可接受的,您可以提供如下默认值:

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

然而,我最近在邮件列表上发现的另一种方法是在删除上放置触发器,将具有最大 rowid 的行移动到当前删除的行中,这样就不会留下任何漏洞。

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


27
投票

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


22
投票

关于:

SELECT COUNT(*) AS n FROM foo;

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

SELECT * FROM foo LIMIT 1 OFFSET m;

您甚至可以将第一个数字 (n) 保存在某处,并且仅在数据库计数发生变化时更新它。这样您就不必每次都执行 SELECT COUNT。


15
投票

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

12
投票
SELECT   bar
FROM     foo
ORDER BY Random()
LIMIT    1

4
投票

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

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

abs(X) 函数返回数字参数的绝对值 X.

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

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

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


0
投票

这两步操作将解决OP关于如何随机查找单行的问题。

首先,计算表中有多少行,并获取随机偏移量。这将是您想要的记录编号:

SELECT abs( random() % COUNT(1) ) AS RandomOffset FROM your_table

其次,使用随机偏移量来选择特定行:

SELECT * FOM your_table LIMIT 1 OFFSET RandomOffset

很多情况下我想通过查看多个记录来抽查我的数据。这将返回行的随机子集,我们假设所需的行数小于表中的总行数。

首先,我计算表中的行数,并将其除以我想要返回的大致行数。然后我请求随机数除以我的 TargetRows 的所有行。

WITH RowCount as (
    SELECT COUNT(1) / 400 as TargetRows
    FROM your_table
    )
SELECT *
FROM your_table
WHERE RANDOM() % ( 
    SELECT CEILING( TargetRows )
    FROM RowCount
    ) = 0

在这两种情况下,我都使用

count(1)
,因为它的性能比
count(*)
更好。

© www.soinside.com 2019 - 2024. All rights reserved.