最好的方法来select随机行PostgreSQL
我想在PostgreSQL中随机select行,我试过这个:
select * from table where random() < 0.01;
但其他一些build议:
select * from table order by random() limit 1000;
我有一张有5亿行的非常大的桌子,我希望它快。
哪种方法更好? 有什么区别? 什么是select随机行的最佳方式?
给你的规格(加上额外的信息在评论中),
- 你有一个数字ID列(整数),只有less数(或less量)的差距。
- 显然没有或很less写操作。
- 您的ID列必须被编入索引! 主键很好地服务。
下面的查询不需要大表的顺序扫描,只需要索引扫描。
首先,得到主查询的估计值:
SELECT count(*) AS ct -- optional , min(id) AS min_id , max(id) AS max_id , max(id) - min(id) AS id_span FROM big;
count(*)
(对于巨大的表格)唯一可能是昂贵的部分。 鉴于以上规格,你不需要它。 一个估计会做得很好,几乎可以免费获得( 详细解释在这里 ):
SELECT reltuples AS ct FROM pg_class WHERE oid = 'schema_name.big'::regclass;
只要ct
不比id_span
小很多,查询就会比其他方法id_span
。
WITH params AS ( SELECT 1 AS min_id -- minimum id <= current min id , 5100000 AS id_span -- rounded up. (max_id - min_id + buffer) ) SELECT * FROM ( SELECT p.min_id + trunc(random() * p.id_span)::integer AS id FROM params p ,generate_series(1, 1100) g -- 1000 + buffer GROUP BY 1 -- trim duplicates ) r JOIN big USING (id) LIMIT 1000; -- trim surplus
-
在
id
空间中生成随机数字。 你有“less量空白”,所以添加10%(足以轻松覆盖空白)到要检索的行数。 -
每个
id
可以偶然选取多次(尽pipe不太可能有大的id空间),所以将生成的数字分组(或使用DISTINCT
)。 -
join
id
到大表。 这个索引应该是非常快的。 -
最后修剪没有被愚弄和缺口吃掉的剩余
id
。 每一行都有完全相同的select机会 。
短版
你可以简化这个查询。 以上查询中的CTE仅用于教育目的:
SELECT * FROM ( SELECT DISTINCT 1 + trunc(random() * 5100000)::integer AS id FROM generate_series(1, 1100) g ) r JOIN big USING (id) LIMIT 1000;
用rCTE进行优化
特别是如果你不确定差距和估计。
WITH RECURSIVE random_pick AS ( SELECT * FROM ( SELECT 1 + trunc(random() * 5100000)::int AS id FROM generate_series(1, 1030) -- 1000 + few percent - adapt to your needs LIMIT 1030 -- hint for query planner ) r JOIN big b USING (id) -- eliminate miss UNION -- eliminate dupe SELECT b.* FROM ( SELECT 1 + trunc(random() * 5100000)::int AS id FROM random_pick r -- plus 3 percent - adapt to your needs LIMIT 999 -- less than 1000, hint for query planner ) r JOIN big b USING (id) -- eliminate miss ) SELECT * FROM random_pick LIMIT 1000; -- actual limit
我们可以在基本查询中使用更less的剩余 。 如果间隙太多,所以我们在第一次迭代中没有find足够的行,rCTE继续迭代recursion项。 在ID空间中我们仍然需要相对较less的空白,否则recursion在达到极限之前可能会干涸 – 或者我们必须从一个足够大的缓冲区开始,这个缓冲区无法达到优化性能的目的。
联合体在rCTE中消除了重复。
一旦有足够的行,外部LIMIT
使CTE停止。
这个查询是仔细草拟使用可用的索引,生成实际上随机的行,并不停止,直到我们达到极限(除非recursion干运行)。 如果你要重写它,这里有一些陷阱。
包装成function
用不同的参数重复使用:
CREATE OR REPLACE FUNCTION f_random_sample(_limit int = 1000, _gaps real = 1.03) RETURNS SETOF big AS $func$ DECLARE _surplus int := _limit * _gaps; _estimate int := ( -- get current estimate from system SELECT c.reltuples * _gaps FROM pg_class c WHERE c.oid = 'big'::regclass); BEGIN RETURN QUERY WITH RECURSIVE random_pick AS ( SELECT * FROM ( SELECT 1 + trunc(random() * _estimate)::int FROM generate_series(1, _surplus) g LIMIT _surplus -- hint for query planner ) r (id) JOIN big USING (id) -- eliminate misses UNION -- eliminate dupes SELECT * FROM ( SELECT 1 + trunc(random() * _estimate)::int FROM random_pick -- just to make it recursive LIMIT _limit -- hint for query planner ) r (id) JOIN big USING (id) -- eliminate misses ) SELECT * FROM random_pick LIMIT _limit; END $func$ LANGUAGE plpgsql VOLATILE ROWS 1000;
呼叫:
SELECT * FROM f_random_sample(); SELECT * FROM f_random_sample(500, 1.05);
你甚至可以使这个generics适用于任何表:将PK列和表的名称作为多态types并使用EXECUTE
…但是这超出了这个问题的范围。 看到:
- 重构PL / pgSQL函数以返回各种SELECT查询的输出
可能的select
如果您的要求允许重复呼叫的相同集 (我们正在谈论重复呼叫),我会考虑一个物化视图 。 执行上述查询一次,并将结果写入表中。 用户可以以轻松的速度进行准随机select。 刷新你的随机select间隔或您select的事件。
Postgres 9.5引入了TABLESAMPLE SYSTEM (n)
速度非常快 ,但结果并不完全是随机的 。 手册:
当指定小的抽样百分比时,
SYSTEM
方法比BERNOULLI
方法快得多,但是由于聚类效应,它可能会返回表的随机抽样。
而且返回的行数可能会大相径庭。 对于我们的例子,要获得大约 1000行,请尝试:
SELECT * FROM big TABLESAMPLE SYSTEM ((1000 * 100) / 5100000.0);
其中n是一个百分比。 手册:
BERNOULLI
和SYSTEM
抽样方法每个都接受一个参数,它是要抽样的表的一部分,表示为0到100之间的百分比 。 这个论点可以是任何real
expression。
大胆重视我的。
有关:
- 快速发现PostgreSQL中表格的行数
或者安装额外的模块tsm_system_rows来精确地获取请求的行数(如果有足够的)并且允许更方便的语法:
SELECT * FROM big TABLESAMPLE SYSTEM_ROWS(1000);
详情请参阅埃文的答案 。
但是这还不是完全随机的。
你可以通过使用来检查和比较两者的执行计划
EXPLAIN select * from table where random() < 0.01; EXPLAIN select * from table order by random() limit 1000;
对大表1的快速testing显示, ORDER BY
首先对整个表进行sorting,然后选取前1000个项目。 对大表进行sorting不仅可以读取该表,还可以读取和写入临时文件。 where random() < 0.1
只扫描一次完整的表。
对于大型表,这可能不是你想要的,因为即使是一次完整的表扫描也可能花费很长时间。
第三个build议是
select * from table where random() < 0.01 limit 1000;
一find1000行就停止表扫描,因此会更快地返回。 当然,这会使随机性有点下降,但是对于你的情况来说也许这已经足够了。
编辑:除了这个考虑,你可以看看这个已经提出的问题。 使用查询[postgresql] random
返回相当多的命中。
- Postgres中的快速随机行select
- 如何从postgreSQL表中检索随机数据行?
- postgres:从表中获得随机条目 – 太慢了
另外还有一篇相关文章,概述了几种方法:
1 “大”,如“完整的表格不适合内存”。
postgresql按random()顺序sorting,按随机顺序select行:
select your_columns from your_table ORDER BY random()
postgresql按随机()sorting:
select * from (select distinct your_columns from your_table) table_alias ORDER BY random()
postgresql按顺序随机限制一行:
select your_columns from your_table ORDER BY random() limit 1
ORDER BY的那个将会是较慢的那个。
select * from table where random() < 0.01;
通过logging进行logging,并决定随机过滤或不logging。 这将是O(N)
因为它只需要检查每个logging一次。
select * from table order by random() limit 1000;
要sorting整个表格,然后select第一个1000.除了幕后的巫术魔法之外,顺序依次是O(N * log N)
。
random() < 0.01
的缺点是你会得到可变数量的输出logging。
注意,有一个更好的方法来整理一组数据,而不是随机sorting:在O(N)
运行的Fisher-Yates Shuffle 。 但是,在SQL中实现shuffle听起来像是相当的挑战。
从PostgreSQL 9.5开始,有一个新的语法来从表中获取随机元素:
SELECT * FROM mytable TABLESAMPLE SYSTEM (5);
这个例子会给你5%的mytable
元素。
请参阅以下博客文章的更多解释: http : //www.postgresql.org/docs/current/static/sql-select.html
如果只需要一行,则可以使用从count
导出的计算offset
。
select * from table_name limit 1 offset floor(random() * (select count(*) from table_name));
select * from table order by random() limit 1000;
如果您知道需要多less行,请查看tsm_system_rows
。
tsm_system_rows
模块提供了表格采样方法SYSTEM_ROWS,它可以在SELECT命令的TABLESAMPLE子句中使用。
此表采样方法接受一个整数参数,即读取的最大行数。 生成的示例将始终包含那么多行,除非表中没有足够的行,在这种情况下整个表被选中。 与内置的SYSTEM采样方法一样,SYSTEM_ROWS执行块级采样,因此样本不是完全随机的,但可能会受到聚类效应的影响,特别是只需要less量的行时。
首先安装扩展
CREATE EXTENSION tsm_system_rows;
然后你的查询,
SELECT * FROM table TABLESAMPLE SYSTEM_ROWS(1000);
这是一个适合我的决定。 我想这很容易理解和执行。
SELECT field_1, field_2, field_2, random() as ordering FROM big_table WHERE some_conditions ORDER BY ordering LIMIT 1000;
Erwin Brandstetter概括的物化视图“可能的select”的变化是可能的。
比方说,你不想在返回的随机值中有重复。 所以你需要在包含你的(非随机)值的主表上设置一个布尔值。
假设这是input表:
id_values id | used ----+-------- 1 | FALSE 2 | FALSE 3 | FALSE 4 | FALSE 5 | FALSE ...
根据需要填充ID_VALUES
表。 然后,如Erwin所述,创build一个随机化ID_VALUES
表的ID_VALUES
化视图:
CREATE MATERIALIZED VIEW id_values_randomized AS SELECT id FROM id_values ORDER BY random();
请注意,物化视图不包含使用的列,因为这将很快变得过时。 该视图也不需要包含可能在id_values
表中的其他列。
为了获得(和“消费”)随机值,对id_values
使用UPDATE- id_values
,使用join来从id_values_randomized
selectid_values
,并应用所需的标准来获得相关的可能性。 例如:
UPDATE id_values SET used = TRUE WHERE id_values.id IN (SELECT i.id FROM id_values_randomized r INNER JOIN id_values i ON i.id = r.id WHERE (NOT i.used) LIMIT 5) RETURNING id;
根据需要更改LIMIT
– 如果您一次只需要一个随机值,请将LIMIT
更改为1
。
有了id_values
上适当的索引,我相信UPDATE-RETURNING应该可以在很小的负载下很快地执行。 它返回一个数据库往返随机值。 “合格”行的标准可以根据需要复杂。 新行可以随时添加到id_values
表中,一旦id_values
化视图刷新(可能在非高峰时间运行),它们将变为应用程序可访问的。 物化视图的创build和刷新将会很慢,但是只需要在id_values
表中添加新的id时执行。
在被陷入思维的SQL模式之后,我意识到,对于我的用例,下面的方法应该工作。
为了达到我的目的,我想从“过去一个月”(可能是300-400个元素的子集 – 随着服务器活动增加而不会增加的数目)中select一个指定数量(大约10-20个)的随机元素。 Mickaël的解决scheme几乎符合条例草案,但似乎不能在WHERE
子句之后使用TABLESAMPLE
。
以下是我遇到的解决scheme:
首先,我运行了一个简单的查询:
SELECT id FROM table WHERE "timestamp" > now()::Date - 30
一旦结果返回到我的程序,我select了一个随机样本的ID。 然后我只是跑了:
SELECT * FROM table WHERE id IN (1,2,3)
(其中(1,2,3)
是我随机select的样本)。
我意识到这不是一个严格的PostgreSQL解决scheme,但它很好,简单,只要你介意缩放限制,应该工作得很好。 希望这将是适合处于类似位置的人的正确select。
添加一个types为serial
名为r
的列。 索引r
。
假设我们有20万行,我们将生成一个随机数n
,其中0 < n
<= 200,000。
selectr > n
行,将它们sorting为ASC
并select最小的行。
码:
select * from YOUR_TABLE where r > ( select ( select reltuples::bigint AS estimate from pg_class where oid = 'public.YOUR_TABLE'::regclass) * random() ) order by r asc limit(1);
代码是不言自明的。 中间的子查询用于从https://stackoverflow.com/a/7945274/1271094快速估算表格行数。;
在应用程序级别,如果n
>行数或需要select多行,则需要再次执行语句。