我想要在PostgreSQL中随机选择行,我尝试了以下方法:
select * from table where random() < 0.01;
但是其他一些建议:
select * from table order by random() limit 1000;
我有一个很大的表,有5亿行,我希望它能很快。
哪种方法更好?有什么区别?选择随机行的最佳方法是什么?
我想要在PostgreSQL中随机选择行,我尝试了以下方法:
select * from table where random() < 0.01;
但是其他一些建议:
select * from table order by random() limit 1000;
我有一个很大的表,有5亿行,我希望它能很快。
哪种方法更好?有什么区别?选择随机行的最佳方法是什么?
Answers:
根据您的要求(加上注释中的其他信息),
下面的查询不需要大表的顺序扫描,只需要索引扫描。
首先,获取主要查询的估算值:
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
,查询会优于其他方法。
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
空间中生成随机数。您的差距很小,因此要检索的行数增加10%(足以轻松覆盖空白)。
每个都id
可以被偶然选择多次(尽管ID空间很大,这是不太可能的),因此请对生成的数字进行分组(或使用DISTINCT
)。
将id
s 加入大表。有了适当的索引,这应该非常快。
最后,修剪id
未被骗子和缺口吃掉的剩余食物。每行都有完全相等的机会被选中。
您可以简化此查询。上面查询中的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;
特别是如果您不确定差距和估算值。
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
我们可以在基本查询中使用较小的盈余。如果间隙太多,那么在第一次迭代中我们找不到足够的行,则rCTE会继续使用递归项进行迭代。我们仍然需要在ID空间中保持相对较少的间隙,否则在达到限制之前递归可能会枯竭-否则我们必须从足够大的缓冲区开始,这不利于优化性能。
重复项UNION
在rCTE中被消除。
LIMIT
一旦我们有足够的行,外部将使CTE停止。
该查询经过精心设计,可以使用可用的索引,生成实际上是随机的行,并且直到我们达到限制后才会停止(除非递归运行干了)。如果要重写它,这里有很多陷阱。
重复使用不同的参数:
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);
您甚至可以使此泛型适用于任何表:将PK列和表的名称作为多态类型并使用EXECUTE
...但这超出了此问题的范围。看到:
如果您的要求允许重复调用使用相同的集合(并且我们正在谈论重复调用),我将考虑实例化视图。一次执行上述查询,然后将结果写入表中。用户以闪电般的速度获得准随机选择。在您选择的时间间隔或事件中刷新您的随机选择。
TABLESAMPLE SYSTEM (n)
n
百分比在哪里。手册:
的
BERNOULLI
和SYSTEM
采样方法的每一个接受单个参数,它是表格样本的分数,表示为 0到100之间的百分比。此参数可以是任何real
值的表达式。
大胆强调我的。这是非常快的,但结果并非完全是随机的。再次手册:
该
SYSTEM
方法明显快于BERNOULLI
指定较小采样百分比时的方法,但是由于聚类效果,它可能会返回表中的随机性较低的样本。
返回的行数可以有很大的不同。对于我们的示例,要获得大约 1000行:
SELECT * FROM big TABLESAMPLE SYSTEM ((1000 * 100) / 5100000.0);
有关:
或安装其他模块tsm_system_rows以获得确切的请求行数(如果有的话),并允许使用更方便的语法:
SELECT * FROM big TABLESAMPLE SYSTEM_ROWS(1000);
有关详细信息,请参见Evan的答案。
但这还不是完全随机的。
JOIN bigtbl t
,是的缩写JOIN bigtbl AS t
。t
是表别名为bigtbl
。其目的是缩短语法,但在此特定情况下不需要。我在答案中简化了查询,并添加了一个简单的版本。
您可以使用以下方法检查和比较两者的执行计划
EXPLAIN select * from table where random() < 0.01;
EXPLAIN select * from table order by random() limit 1000;
对大型表1进行的快速测试显示,ORDER BY
首先对整个表进行排序,然后再选择前1000个项目。对大型表进行排序不仅可以读取该表,还可以读取和写入临时文件。该where random() < 0.1
只扫描整个表一次。
对于大型表,这可能不是您想要的,因为即使是一次完整的表扫描也可能需要很长时间。
第三个建议是
select * from table where random() < 0.01 limit 1000;
一旦找到1000行,该表就会停止表扫描,因此会更快返回。当然,这会降低随机性,但是对于您的情况,这也许就足够了。
编辑:除了这些注意事项,您可能会为此查看已问到的问题。使用查询会[postgresql] random
返回很多匹配。
Depez的链接文章概述了其他几种方法:
1 “大”,如“完整表将不适合内存”。
random() < 0.02
,然后再整理那个列表,然后limit 1000
!这种排序在几千行上会更便宜(lol)。
select your_columns from your_table ORDER BY random()
select * from
(select distinct your_columns from your_table) table_alias
ORDER BY random()
select your_columns from your_table ORDER BY random() limit 1
select your_columns from your_table ORDER BY random() limit 1
拿〜2分钟EXEC上45mil行
从PostgreSQL 9.5开始,有一种新语法专门用于从表中获取随机元素:
SELECT * FROM mytable TABLESAMPLE SYSTEM (5);
本示例将为您提供的5%的元素mytable
。
请参阅此博客文章的更多说明:http : //www.postgresql.org/docs/current/static/sql-select.html
TABLESAMPLE SYSTEM_ROWS(400)
用来获取400个随机行的样本。您需要启用内置tsm_system_rows
扩展才能使用此语句。
带有ORDER BY的将是速度较慢的一个。
select * from table where random() < 0.01;
逐条记录,并决定是否对其进行随机过滤。这是O(N)
因为它只需要检查每个记录一次。
select * from table order by random() limit 1000;
将对整个表进行排序,然后选择第一个1000。除了幕后的任何伏都教徒魔法外,顺序是O(N * log N)
。
缺点random() < 0.01
是您将获得可变数量的输出记录。
请注意,与随机排序相比,有一种改组数据的更好的方法:Fisher-Yates Shuffle,在中运行O(N)
。但是,在SQL中实现混洗听起来确实是个挑战。
select * from table order by random() limit 1000;
如果您知道要多少行,请签出tsm_system_rows
。
模块提供表采样方法SYSTEM_ROWS,可在SELECT命令的TABLESAMPLE子句中使用。
此表采样方法接受单个整数参数,该参数是要读取的最大行数。除非表没有足够的行,否则结果样本将始终恰好包含那么多的行,在这种情况下,将选择整个表。像内置的SYSTEM采样方法一样,SYSTEM_ROWS执行块级采样,因此采样不是完全随机的,但是可能会受到聚类的影响,尤其是在仅要求少量行的情况下。
首先安装扩展
CREATE EXTENSION tsm_system_rows;
然后查询
SELECT *
FROM table
TABLESAMPLE SYSTEM_ROWS(1000);
SYSTEM
方法的显着改进。
如果只需要一行,则可以使用offset
从导出的计算值count
。
select * from table_name limit 1
offset floor(random() * (select count(*) from table_name));
Erwin Brandstetter概述的物化视图“可能的选择”的变体是可能的。
例如,假设您不希望重复的随机值重复。因此,您将需要在包含您的(非随机)值集的主表上设置一个布尔值。
假设这是输入表:
id_values id | used
----+--------
1 | FALSE
2 | FALSE
3 | FALSE
4 | FALSE
5 | FALSE
...
ID_VALUES
根据需要填充表。然后,按照Erwin的描述,创建一个实例化视图,该视图将ID_VALUES
表随机化一次:
CREATE MATERIALIZED VIEW id_values_randomized AS
SELECT id
FROM id_values
ORDER BY random();
请注意,实例化视图不包含已使用的列,因为这将很快过时。视图也不需要包含表中可能存在的其他列id_values
。
为了获得(并“消费”)随机值,请在上使用UPDATE-RETURNING id_values
,id_values
从id_values_randomized
连接中选择,然后应用所需的条件仅获得相关的可能性。例如:
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添加到id_values
表中时才需要执行该视图。
添加名为r
type 的列serial
。索引r
。
假设我们有200,000行,我们将生成一个随机数n
,其中0 n
<<== 200,000。
用选择行r > n
,对行进行排序,ASC
然后选择最小的行。
码:
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
>行数或需要选择多行,则需要再次执行该语句。