Postgres中的快速随机行选择

时间:2011-03-14 10:33:38

标签: postgresql random-access

我在postgres中有一个包含数百万行的表。我在网上查了一下,发现了以下内容

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

它有效,但它真的很慢......是否有另一种方法来进行查询,或者直接选择随机行而不读取所有表格?顺便说一下'myid'是一个整数,但它可以是一个空字段。

感谢

7 个答案:

答案 0 :(得分:92)

您可能希望尝试使用OFFSET,例如

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

Nmytable中的行数。您可能需要先SELECT COUNT(*)计算N的值。

更新(由Antony Hatchkins提供)

您必须在此处使用floor

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

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

答案 1 :(得分:42)

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

语法是

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

如果您只想选择一行,这不是最佳解决方案,因为您需要知道表的COUNT来计算确切的百分比。

为了避免缓慢的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 :(得分:34)

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

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

答案 3 :(得分:29)

您需要使用floor

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

答案 4 :(得分:14)

检查此链接以获取一些不同的选项。 http://www.depesz.com/index.php/2007/09/16/my-thoughts-on-getting-random-row/

更新: (A.Hatchkins)

(非常)长篇文章的摘要如下。

作者列出了四种方法:

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

2)ORDER BY id where id>=random()*N LIMIT 1 - 如果存在空白则不均匀

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

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

并建议使用

改进方法#2

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

答案 5 :(得分: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)

答案 6 :(得分:1)

获取随机行最简单,最快的方法是使用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