如何在纯SQL中请求随机行(或尽可能接近真正的随机)?


当前回答

这里的大多数解决方案都旨在避免排序,但它们仍然需要对表进行顺序扫描。

还有一种方法可以通过切换到索引扫描来避免顺序扫描。如果你知道你随机行的下标值,你几乎可以立即得到结果。问题是——如何猜测一个索引值。

以下方案适用于PostgreSQL 8.4版本:

explain analyze select * from cms_refs where rec_id in 
  (select (random()*(select last_value from cms_refs_rec_id_seq))::bigint 
   from generate_series(1,10))
  limit 1;

我上面的解决方案你猜10个不同的随机指标值从范围0 ..[id的最后一个值]。

数字10是任意的-你可以使用100或1000,因为它(令人惊讶的是)对响应时间没有太大的影响。

还有一个问题-如果你有稀疏的id,你可能会错过。解决方案是有一个备份计划:)在这种情况下,一个纯旧order by random()查询。当合并id看起来像这样:

explain analyze select * from cms_refs where rec_id in 
    (select (random()*(select last_value from cms_refs_rec_id_seq))::bigint 
     from generate_series(1,10))
    union all (select * from cms_refs order by random() limit 1)
    limit 1;

不是union ALL条款。在这种情况下,如果第一部分返回任何数据,那么第二部分将永远不会执行!

其他回答

Oracle有更好的解决方案,而不是使用dbms_random。值,而它需要完全扫描dbms_random来排序行。值,对于大表来说非常慢。

用这个代替:

SELECT *
FROM employee sample(1)
WHERE rownum=1

我不得不同意CD-MaN:使用“ORDER BY RAND()”将很好地用于小表或当你只做几次SELECT时。

我还使用“num_value >= RAND() *…”技术,如果我真的想获得随机结果,我在表中有一个特殊的“随机”列,我大约每天更新一次。单个UPDATE运行将花费一些时间(特别是因为必须在该列上建立索引),但它比每次运行select时为每一行创建随机数快得多。

在SQL Server中,您可以将TABLESAMPLE与NEWID()结合使用,以获得相当好的随机性,并且仍然具有速度。如果您真的只想要1行或少量的行,这尤其有用。

SELECT TOP 1 * FROM [table] 
TABLESAMPLE (500 ROWS) 
ORDER BY NEWID()

而不是使用RAND(),因为它是不鼓励的,你可以简单地得到max ID (= max):

SELECT MAX(ID) FROM TABLE;

在1..Max (= My_Generated_Random)

My_Generated_Random = rand_in_your_programming_lang_function(1..Max);

然后运行SQL:

SELECT ID FROM TABLE WHERE ID >= My_Generated_Random ORDER BY ID LIMIT 1

注意,它将检查id等于或高于所选值的任何行。 也可以在表中寻找行,并获得一个等于或低于My_Generated_Random的ID,然后修改查询如下:

SELECT ID FROM TABLE WHERE ID <= My_Generated_Random ORDER BY ID DESC LIMIT 1

对于SQL Server和需要“单个随机行”..

如果不需要真采样,生成一个随机值[0,max_rows)并使用ORDER BY..OFFSET..从SQL Server 2012+获取。

如果COUNT和ORDER BY在适当的索引上,这是非常快的——这样数据就已经沿着查询行“排序”了。如果涵盖了这些操作,那么它就是一个快速请求,并且不会受到使用ORDER BY NEWID()或类似方法的可怕可伸缩性的影响。显然,这种方法在非索引的HEAP表上不能很好地伸缩。

declare @rows int
select @rows = count(1) from t

-- Other issues if row counts in the bigint range..
-- This is also not 'true random', although such is likely not required.
declare @skip int = convert(int, @rows * rand())

select t.*
from t
order by t.id -- Make sure this is clustered PK or IX/UCL axis!
offset (@skip) rows
fetch first 1 row only

确保使用了适当的事务隔离级别和/或考虑0结果。


对于SQL Server,需要一个“一般行样本”的方法..

注意:这是一个在SQL Server上找到的关于获取行样本的特定问题的答案的改编。它是根据上下文量身定制的。

虽然这里应该谨慎使用一般抽样方法,但对于其他答案(以及关于非伸缩和/或有问题的实现的重复建议),它仍然是潜在的有用信息。如果目标是找到“单个随机行”,那么这种抽样方法的效率低于所示的第一个代码,并且容易出错。


这是一个更新和改进的对行百分比进行抽样的形式。它基于与其他一些使用CHECKSUM / BINARY_CHECKSUM和modulus的答案相同的概念。

It is relatively fast over huge data sets and can be efficiently used in/with derived queries. Millions of pre-filtered rows can be sampled in seconds with no tempdb usage and, if aligned with the rest of the query, the overhead is often minimal. Does not suffer from CHECKSUM(*) / BINARY_CHECKSUM(*) issues with runs of data. When using the CHECKSUM(*) approach, the rows can be selected in "chunks" and not "random" at all! This is because CHECKSUM prefers speed over distribution. Results in a stable/repeatable row selection and can be trivially changed to produce different rows on subsequent query executions. Approaches that use NEWID() can never be stable/repeatable. Does not use ORDER BY NEWID() of the entire input set, as ordering can become a significant bottleneck with large input sets. Avoiding unnecessary sorting also reduces memory and tempdb usage. Does not use TABLESAMPLE and thus works with a WHERE pre-filter.

这是要点。有关更多细节和注意事项,请参阅这个答案。

Naï亿一下:

declare @sample_percent decimal(7, 4)
-- Looking at this value should be an indicator of why a
-- general sampling approach can be error-prone to select 1 row.
select @sample_percent = 100.0 / count(1) from t

-- BAD!
-- When choosing appropriate sample percent of "approximately 1 row"
-- it is very reasonable to expect 0 rows, which definitely fails the ask!
-- If choosing a larger sample size the distribution is heavily skewed forward,
-- and is very much NOT 'true random'.
select top 1
    t.*
from t
where 1=1
    and ( -- sample
        @sample_percent = 100
        or abs(
            convert(bigint, hashbytes('SHA1', convert(varbinary(32), t.rowguid)))
        ) % (1000 * 100) < (1000 * @sample_percent)
    )

这可以在很大程度上通过混合抽样和ORDER by从小得多的样本集中选择的混合查询来补救。这将排序操作限制为样本大小,而不是原始表的大小。

-- Sample "approximately 1000 rows" from the table,
-- dealing with some edge-cases.
declare @rows int
select @rows = count(1) from t

declare @sample_size int = 1000
declare @sample_percent decimal(7, 4) = case
    when @rows <= 1000 then 100                              -- not enough rows
    when (100.0 * @sample_size / @rows) < 0.0001 then 0.0001 -- min sample percent
    else 100.0 * @sample_size / @rows                        -- everything else
    end

-- There is a statistical "guarantee" of having sampled a limited-yet-non-zero number of rows.
-- The limited rows are then sorted randomly before the first is selected.
select top 1
    t.*
from t
where 1=1
    and ( -- sample
        @sample_percent = 100
        or abs(
            convert(bigint, hashbytes('SHA1', convert(varbinary(32), t.rowguid)))
        ) % (1000 * 100) < (1000 * @sample_percent)
    )
-- ONLY the sampled rows are ordered, which improves scalability.
order by newid()