Postgres中的快速随机行选择


95

我在postgres中有一张表格,其中包含数百万行。我已经检查了互联网,发现以下内容

SELECT myid FROM mytable ORDER BY RANDOM() LIMIT 1;

它可以工作,但速度确实很慢...还有另一种方法可以进行该查询,还是直接选择随机行而不读取所有表?顺便说一句,“ myid”是一个整数,但它可以是一个空字段。


1
如果要选择多个随机行,请参见以下问题:stackoverflow.com/q/8674718/247696
Flimm,

Answers:


97

您可能想尝试使用OFFSET,例如

SELECT myid FROM mytable OFFSET floor(random()*N) LIMIT 1;

N是行数mytable。您可能需要先执行a SELECT COUNT(*)来确定的值N

更新(由安东尼·哈奇金斯撰写)

您必须floor在这里使用:

SELECT myid FROM mytable OFFSET floor(random()*N) LIMIT 1;

考虑一个两行的表;random()*N生成0 <= x < 2SELECT myid FROM mytable OFFSET 1.7 LIMIT 1;返回0行,因为隐式舍入到最接近的int。


使用N小于SELECT COUNT(*)?是有意义的,我的意思是,不是使用表中的所有值,而是仅使用其中的一部分?
胡安

@Juan取决于您的要求。
NPE

使用EXPLAIN SELECT ...具有不同N的值给查询提供相同的开销,那么我想最好选择N的最大值
Juan Juan

3
在下面的答案中看到错误修正
Antony Hatchkins 2012年

2
这有一个错误。它永远不会返回第一行,并且会生成错误1 / COUNT(*),因为它将尝试返回最后一行之后的行。
2014年

59

PostgreSQL 9.5引入了一种新方法,可以更快地选择样本:TABLESAMPLE

语法是

SELECT * FROM my_table TABLESAMPLE BERNOULLI(percentage);
SELECT * FROM my_table TABLESAMPLE SYSTEM(percentage);

如果只希望选择一行,这不是最佳解决方案,因为您需要知道表的COUNT才能计算出准确的百分比。

为避免计数缓慢,并对从1行到数十亿行的表使用快速TABLESAMPLE,可以执行以下操作:

 SELECT * FROM my_table TABLESAMPLE SYSTEM(0.000001) LIMIT 1;
 -- if you got no result:
 SELECT * FROM my_table TABLESAMPLE SYSTEM(0.00001) LIMIT 1;
 -- if you got no result:
 SELECT * FROM my_table TABLESAMPLE SYSTEM(0.0001) LIMIT 1;
 -- if you got no result:
 SELECT * FROM my_table TABLESAMPLE SYSTEM(0.001) LIMIT 1;
 ...

这看起来可能不太优雅,但可能比其他任何答案都快。

要确定是否要使用BERNULLI oder SYSTEM,请访问http://blog.2ndquadrant.com/tablesample-in-postgresql-9-5-2/了解有关差异的信息


2
这比其他任何答案都更快,更容易-这个答案应该放在顶部。
Hayden Schiff

1
为什么不能仅使用子查询来获取计数?SELECT * FROM my_table TABLESAMPLE SYSTEM(SELECT 1/COUNT(*) FROM my_table) LIMIT 1;
machineghost

2
@machineghost“为避免计数缓慢...” ...如果您的数据太小,可以在合理的时间内计算,那就去吧!:-)
alfonx

2
@machineghost SELECT reltuples FROM pg_class WHERE relname = 'my_table'用于计数估计。
Hynek -Pichi- Vychodil

@ Hynek-Pichi-Vychodil很好的输入!为了确保估计值不会过时,必须最近对VACUUM ANALYZEd进行评估。但是无论如何,都应该对一个好的数据库进行适当的分析。而这一切都取决于特定的用例。通常,大桌子的增长不会那么快...谢谢!
alfonx

34

我尝试了一个子查询,它工作正常。偏移量,至少在Postgresql v8.4.4中可以正常工作。

select * from mytable offset random() * (select count(*) from mytable) limit 1 ;

实际上,v8.4对于此功能至关重要,对于<= 8.3而言不起作用。
安东尼·哈奇金斯

1
在下面的答案中看到错误修正
Antony Hatchkins 2012年

30

您需要使用floor

SELECT myid FROM mytable OFFSET floor(random()*N) LIMIT 1;

考虑一个两行的表;random()*N生成0 <= x <2,并且例如SELECT myid FROM mytable OFFSET 1.7 LIMIT 1;由于隐式舍入到最接近的int而返回0行。
安东尼·哈奇金斯

不幸的是,如果您想使用更高的LIMIT,这是行不通的。我需要获得3个项目,因此我需要使用ORDER BY RANDOM()语法。
亚历克西斯·威尔克

1
连续三个查询仍将比一个查询快order by random(),大约是3*O(N) < O(NlogN)-实际数字由于索引而略有不同。
安东尼·哈奇金斯

我的问题是,这3个项目必须是不同的,并且a WHERE myid NOT IN (1st-myid)WHERE myid NOT IN (1st-myid, 2nd-myid)将不起作用,因为该决定是由OFFSET决定的。嗯...我想我可以在第二个和第三个SELECT中将N减少1和2。
亚历克西斯·威尔克

您或任何人都可以用我为什么要使用的答案来扩展此答案floor()吗?它提供什么优势?
ADTC 2014年

14

请查看此链接以了解其他选项。 http://www.depesz.com/index.php/2007/09/16/my-thoughts-on-getting-random-row/

更新: (哈奇金斯)

(很长)文章的摘要如下。

作者列出了四种方法:

1)ORDER BY random() LIMIT 1; -慢

2)ORDER BY id where id>=random()*N LIMIT 1-如果有差距则不一致

3)随机列-需要不时更新

4)自定义随机聚合 -狡猾的方法,可能会很慢:random()需要生成N次

并建议通过使用以下方法改进方法2

5)ORDER BY id where id=random()*N LIMIT 1 如果结果为空,则进行后续查询。


我想知道为什么他们没有覆盖抵消?使用ORDER只是为了获得随机行是不可能的。幸运的是,答案中涵盖了OFFSET。
androidguy

4

提取随机行最简单,最快的方法是使用tsm_system_rows扩展名:

CREATE EXTENSION IF NOT EXISTS tsm_system_rows;

然后,您可以选择所需的确切行数:

SELECT myid  FROM mytable TABLESAMPLE SYSTEM_ROWS(1);

这在PostgreSQL 9.5和更高版本中可用。

参见:https : //www.postgresql.org/docs/current/static/tsm-system-rows.html


1
合理的警告,这不是完全随机的。在较小的表上,我总是按顺序返回第一行。
本·奥宾

1
是的,在文档(上面的链接)中对此进行了清楚的解释:«像内置的SYSTEM采样方法一样,SYSTEM_ROWS会执行块级采样,因此该采样不是完全随机的,但可能会受到聚类影响,尤其是在只有很小的情况下。请求的行数。»。如果您的数据集很小,则ORDER BY random() LIMIT 1;速度应该足够快。
daamien

我看见了。只是想向未单击该链接或该链接将来消失的任何人明确。
本·奥宾

1
还值得注意的是,这仅适用于从表中选择随机行并进行THEN过滤,这与运行查询相反/相比,然后随机选择一个或一些记录。
nomen

3

我想出了一个非常快速的解决方案,而没有TABLESAMPLE。比快得多OFFSET random()*N LIMIT 1。它甚至不需要表计数。

这个想法是用例如随机但可预测的数据创建一个表达索引md5(primary key)

这是一个具有1M行样本数据的测试:

create table randtest (id serial primary key, data int not null);

insert into randtest (data) select (random()*1000000)::int from generate_series(1,1000000);

create index randtest_md5_id_idx on randtest (md5(id::text));

explain analyze
select * from randtest where md5(id::text)>md5(random()::text)
order by md5(id::text) limit 1;

结果:

 Limit  (cost=0.42..0.68 rows=1 width=8) (actual time=6.219..6.220 rows=1 loops=1)
   ->  Index Scan using randtest_md5_id_idx on randtest  (cost=0.42..84040.42 rows=333333 width=8) (actual time=6.217..6.217 rows=1 loops=1)
         Filter: (md5((id)::text) > md5((random())::text))
         Rows Removed by Filter: 1831
 Total runtime: 6.245 ms

该查询有时(大约有1 / Number_of_rows个概率)返回0行,因此需要检查并重新运行。概率也不完全相同-有些行比另一些行更可能。

为了比较:

explain analyze SELECT id FROM randtest OFFSET random()*1000000 LIMIT 1;

结果差异很大,但可能会很糟糕:

 Limit  (cost=1442.50..1442.51 rows=1 width=4) (actual time=179.183..179.184 rows=1 loops=1)
   ->  Seq Scan on randtest  (cost=0.00..14425.00 rows=1000000 width=4) (actual time=0.016..134.835 rows=915702 loops=1)
 Total runtime: 179.211 ms
(3 rows)

2
很快,是的。真的随机,不。md5值恰好是在另一个现有值之后的下一个更大值,被选择的机会很小,而在数字空间中有很大差距之后的值则有更大的机会(中间的值更大) 。结果分布不是随机的。
Erwin Brandstetter 2015年

非常有趣,它是否可以在类似彩票的查询用例中使用:查询必须查看所有可用票证,并且仅随机返回一张票证。我还可以对您的技术使用悲观锁(选择...进行更新)吗?
Mathieu

对于与彩票相关的任何事情,您都应该真正使用公平且具有加密安全性的随机抽样-例如,选择1到max(id)之间的随机数,直到找到现有的ID。此答案提供的方法既不公平也不安全-快速。可用于“随机获取1%的行以测试某些内容”或“显示随机5个条目”之类的内容。
Tometzky
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.