我想在PostgreSQL中随机选择一行,我试过这个:
select * from table where random() < 0.01;
但其他一些人建议:
select * from table order by random() limit 1000;
我有一个非常大的表,有5亿行,我希望它快。
哪种方法更好?有什么区别?选择随机行的最佳方法是什么?
答案 0 :(得分:188)
根据您的规格(以及评论中的其他信息),
下面的查询不需要对大表进行顺序扫描,只需要进行索引扫描。
首先,获取主要查询的估计值:
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(*)
(对于巨大的表格)。鉴于上述规格,您不需要它。估算会很好,几乎免费提供(detailed explanation here):
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
加入大表。在索引到位时,这应该非常快。
最后修剪没有被欺骗和间隙吃掉的剩余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空间中相对的间隙,或者在达到限制之前递归可能会干涸 - 或者我们必须从足够大的缓冲区开始,这无法达到优化性能的目的。
rCTE中的UNION
消除了重复项。
外部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
的百分比。 The manual:
BERNOULLI
和SYSTEM
抽样方法均接受单一 参数,即要采样的表的分数,表示为a 0到100之间的百分比。该参数可以是任何real
- 值表达式。
大胆强调我的。它非常快,但结果是不完全随机。再次手册:
SYSTEM
方法明显快于BERNOULLI
方法 当指定小的采样百分比时,它可能返回a 由于聚类效应而导致表的随机样本较少。
返回的行数可能会有很大差异。对于我们的示例,要获得大致 1000行:
SELECT * FROM big TABLESAMPLE SYSTEM ((1000 * 100) / 5100000.0);
相关:
或安装附加模块tsm_system_rows以准确获取所请求的行数(如果有足够数量)并允许更方便的语法:
SELECT * FROM big TABLESAMPLE SYSTEM_ROWS(1000);
有关详细信息,请参阅Evan's answer。
但这仍然不是随机的。
答案 1 :(得分:87)
您可以使用
检查和比较两者的执行计划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
会返回相当多的点击次数。
还有一篇链接文章,其中列出了几种方法:
1 “大”,如“完整的表格不适合记忆”。
答案 2 :(得分:67)
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
答案 3 :(得分:33)
从PostgreSQL 9.5开始,有一种新的语法专用于从表中获取随机元素:
SELECT * FROM mytable TABLESAMPLE SYSTEM (5);
此示例将为您提供mytable
中5%的元素。
在此博客文章中查看更多解释:http://www.postgresql.org/docs/current/static/sql-select.html
答案 4 :(得分:25)
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
的缺点是你会获得可变数量的输出记录。
注意,有一种更好的方法来混洗一组数据,而不是随机排序:The Fisher-Yates Shuffle,它在O(N)
中运行。然而,在SQL中实现shuffle听起来就像是一个挑战。
答案 5 :(得分:13)
这是一个适合我的决定。我想这很容易理解和执行。
SELECT
field_1,
field_2,
field_2,
random() as ordering
FROM
big_table
WHERE
some_conditions
ORDER BY
ordering
LIMIT 1000;
答案 6 :(得分:9)
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);
答案 7 :(得分:6)
如果您只想要一行,则可以使用从offset
派生的计算count
。
select * from table_name limit 1
offset floor(random() * (select count(*) from table_name));
答案 8 :(得分:2)
物化视图的变体&#34;可能的替代品&#34; outlined by 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
表中的其他列。
为了获得(和&#34;消费&#34;)随机值,请在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应该在很少负载的情况下快速执行。它返回一个数据库往返的随机值。符合条件的标准&#34;行可以根据需要复杂。可以随时将新行添加到id_values
表中,只要刷新物化视图(可能在非高峰时间运行),它们就可以被应用程序访问。物化视图的创建和刷新将很慢,但只有在将新的ID添加到id_values
表时才需要执行。
答案 9 :(得分:0)
添加名为r
的列serial
。索引r
。
假设我们有200,000行,我们将生成一个随机数n
,其中0 <0} n
&lt; = 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
&gt;您需要再次执行该语句行数或需要选择多行。
答案 10 :(得分:0)
我知道我参加聚会的时间有点晚了,但我刚刚找到了这个名为pg_sample的精彩工具:
pg_sample
- 从较大的PostgreSQL数据库中提取一个小的样本数据集,同时保持参照完整性。
我尝试使用350M行数据库并且它非常快,不了解随机性。
./pg_sample --limit="small_table = *" --limit="large_table = 100000" -U postgres source_db | psql -U postgres target_db
答案 11 :(得分:0)
根据我的经验教训:
$radioG1 = $('[name="Group1"]')
$cbxG1 = $('#Group1_notRequired')
$cbxG1.change(function() {
$radioG1.prop("required", !this.checked)
})
并不比offset floor(random() * N) limit 1
快。
我认为order by random() limit 1
方法会更快,因为它可以节省在Postgres中进行排序的时间。事实并非如此。