如何在SQL中请求随机行?

时间:2008-08-21 06:28:50

标签: sql random

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

30 个答案:

答案 0 :(得分:683)

请参阅此帖:SQL to Select a random row from a database table。它通过在MySQL,PostgreSQL,Microsoft SQL Server,IBM DB2和Oracle中执行此操作的方法(以下内容从该链接复制):

使用MySQL选择一个随机行:

SELECT column FROM table
ORDER BY RAND()
LIMIT 1

使用PostgreSQL选择一个随机行:

SELECT column FROM table
ORDER BY RANDOM()
LIMIT 1

使用Microsoft SQL Server选择随机行:

SELECT TOP 1 column FROM table
ORDER BY NEWID()

使用IBM DB2选择随机行

SELECT column, RAND() as IDX 
FROM table 
ORDER BY IDX FETCH FIRST 1 ROWS ONLY

使用Oracle选择随机记录:

SELECT column FROM
( SELECT column FROM table
ORDER BY dbms_random.value )
WHERE rownum = 1

答案 1 :(得分:172)

Jeremies等解决方案:

SELECT * FROM table ORDER BY RAND() LIMIT 1

工作,但是他们需要对所有表进行顺序扫描(因为需要计算与每行关联的随机值 - 以便可以确定最小的一行),这对于中等大小的表来说可能非常慢。我的建议是使用某种索引数字列(许多表将这些作为主键),然后编写如下内容:

SELECT * FROM table WHERE num_value >= RAND() * 
    ( SELECT MAX (num_value ) FROM table ) 
ORDER BY num_value LIMIT 1

如果索引num_value,则无论表大小如何,这都以对数时间工作。需要注意的是:这假设num_value0..MAX(num_value)范围内均匀分布。如果您的数据集严重偏离此假设,您将得到偏差的结果(某些行将比其他行更频繁地出现)。

答案 2 :(得分:59)

我不知道这有多高效,但我之前使用过它:

SELECT TOP 1 * FROM MyTable ORDER BY newid()

因为GUID非常随机,所以排序意味着你得到一个随机行。

答案 3 :(得分:26)

ORDER BY NEWID()

需要7.4 milliseconds

WHERE num_value >= RAND() * (SELECT MAX(num_value) FROM table)

需要0.0065 milliseconds

我肯定会采用后一种方法。

答案 4 :(得分:13)

您没有说明您正在使用哪台服务器。在旧版本的SQL Server中,您可以使用:

select top 1 * from mytable order by newid()

在SQL Server 2005及更高版本中,您可以使用TABLESAMPLE获取可重复的随机样本:

SELECT FirstName, LastName
FROM Contact 
TABLESAMPLE (1 ROWS) ;

答案 5 :(得分:10)

对于SQL Server

newid()/ order by将起作用,但对于大型结果集来说非常昂贵,因为它必须为每一行生成一个id,然后对它们进行排序。

从性能的角度来看,

TABLESAMPLE()是好的,但你会得到结果的结果(页面上的所有行都会被返回)。

对于性能更好的真随机样本,最好的方法是随机过滤掉行。我在SQL Server联机丛书文章 Limiting Results Sets by Using TABLESAMPLE 中找到了以下代码示例:

  

如果你真的想要随机抽样   单个行,修改您的查询   随机过滤掉行,而不是   使用TABLESAMPLE。例如,   以下查询使用NEWID   函数返回大约一个   行的百分比   Sales.SalesOrderDetail表:

SELECT * FROM Sales.SalesOrderDetail
WHERE 0.01 >= CAST(CHECKSUM(NEWID(),SalesOrderID) & 0x7fffffff AS float)
              / CAST (0x7fffffff AS int)
     

SalesOrderID列包含在中   CHECKSUM表达式   NEWID()每行评估一次   实现每行采样。   表达式CAST(CHECKSUM(NEWID(),   SalesOrderID)& 0x7fffffff AS float /   CAST(0x7fffffff AS int)求值为   随机浮点值介于0和1之间。

对具有1,000,000行的表运行时,以下是我的结果:

SET STATISTICS TIME ON
SET STATISTICS IO ON

/* newid()
   rows returned: 10000
   logical reads: 3359
   CPU time: 3312 ms
   elapsed time = 3359 ms
*/
SELECT TOP 1 PERCENT Number
FROM Numbers
ORDER BY newid()

/* TABLESAMPLE
   rows returned: 9269 (varies)
   logical reads: 32
   CPU time: 0 ms
   elapsed time: 5 ms
*/
SELECT Number
FROM Numbers
TABLESAMPLE (1 PERCENT)

/* Filter
   rows returned: 9994 (varies)
   logical reads: 3359
   CPU time: 641 ms
   elapsed time: 627 ms
*/    
SELECT Number
FROM Numbers
WHERE 0.01 >= CAST(CHECKSUM(NEWID(), Number) & 0x7fffffff AS float) 
              / CAST (0x7fffffff AS int)

SET STATISTICS IO OFF
SET STATISTICS TIME OFF

如果您可以使用TABLESAMPLE,它将为您提供最佳性能。否则使用newid()/ filter方法。如果你有一个大的结果集,newid()/ order by应该是最后的选择。

答案 6 :(得分:4)

如果可能,使用存储语句来避免RND()上的两个索引效率低下并创建记录号字段。

PREPARE RandomRecord FROM "SELECT * FROM table LIMIT ?,1";
SET @n=FLOOR(RAND()*(SELECT COUNT(*) FROM table));
EXECUTE RandomRecord USING @n;

答案 7 :(得分:3)

最好的方法是将一个随机值放在一个新列中,仅用于此目的,并使用类似的东西(pseude code + SQL):

randomNo = random()
execSql("SELECT TOP 1 * FROM MyTable WHERE MyTable.Randomness > $randomNo")

这是MediaWiki代码使用的解决方案。当然,对较小的值存在一些偏差,但他们发现在没有获取行时将随机值包装为零就足够了。

newid()解决方案可能需要进行全表扫描,以便为每一行分配一个新的guid,这样性能会低得多。

rand()解决方案可能根本不起作用(即使用MSSQL),因为该函数只会被评估一次,每个行将被分配相同的“随机”数字。

答案 8 :(得分:3)

对于SQL Server 2005和2008,如果我们想要随机抽取单个行(来自Books Online):

SELECT * FROM Sales.SalesOrderDetail
WHERE 0.01 >= CAST(CHECKSUM(NEWID(), SalesOrderID) & 0x7fffffff AS float)
/ CAST (0x7fffffff AS int)

答案 9 :(得分:3)

Ins using RAND(), as it is not encouraged,您可能只会获得最大ID(=最大):

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

请注意,它将检查Ids等于或高于所选值的任何行。 也可以在表中查找行,并获得与My_Generated_Random相等或更低的ID,然后像这样修改查询:

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

答案 10 :(得分:2)

正如@ BillKarwin对@​​ cnu回答的评论所指出的那样......

当与LIMIT结合使用时,我发现它使用随机排序对JOIN执行得更好(至少使用PostgreSQL 9.1),而不是直接对实际行进行排序:例如

SELECT * FROM tbl_post AS t
JOIN ...
JOIN ( SELECT id, CAST(-2147483648 * RANDOM() AS integer) AS rand
       FROM tbl_post
       WHERE create_time >= 1349928000
     ) r ON r.id = t.id
WHERE create_time >= 1349928000 AND ...
ORDER BY r.rand
LIMIT 100

确保'r'为复杂查询中的每个可能的键值生成一个'rand'值,该值与之连接但仍然限制了'r'的行数。

CAST as Integer对PostgreSQL 9.2特别有用,后者对整数和单精度浮点类型进行了特定的排序优化。

答案 11 :(得分:1)

还没有在答案中看到这种变化。在给定初始种子时,我需要一个额外的约束,每次都选择相同的行集。

对于MS SQL:

最小例子:

select top 10 percent *
from table_name
order by rand(checksum(*))

标准化执行时间:1.00

NewId()示例:

select top 10 percent *
from table_name
order by newid()

标准化执行时间:1.02

NewId()明显慢于rand(checksum(*)),因此您可能不希望将其用于大型记录集。

选择初始种子:

declare @seed int
set @seed = Year(getdate()) * month(getdate()) /* any other initial seed here */

select top 10 percent *
from table_name
order by rand(checksum(*) % seed) /* any other math function here */

如果您需要选择给定种子的同一组,这似乎有效。

答案 12 :(得分:1)

在MSSQL中(使用

在11.0.5569上测试)
SELECT TOP 100 * FROM employee ORDER BY CRYPT_GEN_RANDOM(10)

明显快于

SELECT TOP 100 * FROM employee ORDER BY NEWID()

答案 13 :(得分:1)

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

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

答案 14 :(得分:1)

让MySQL获取随机记录

 SELECT name
  FROM random AS r1 JOIN
       (SELECT (RAND() *
                     (SELECT MAX(id)
                        FROM random)) AS id)
        AS r2
 WHERE r1.id >= r2.id
 ORDER BY r1.id ASC
 LIMIT 1

更多细节http://jan.kneschke.de/projects/mysql/order-by-rand/

答案 15 :(得分:1)

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

如果不需要真正的采样,生成一个随机值 [0, max_rows) 并使用 ORDER BY..OFFSET..FETCH from SQL Server 2012+

如果 COUNTORDER 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 并且需要“常规行示例”方法..

注意:这是对on a SQL Server specific question about fetching a sample of rows 答案的改编。 它是根据上下文量身定制的。

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


这是一种更新和改进的对一定百分比的行进行采样的形式。它基于使用 CHECKSUM / BINARY_CHECKSUM 和模数的其他一些答案的相同概念。

  • 在庞大的数据集上相对较快并且可以有效地用于派生查询中/与派生查询一起使用。数以百万计的预过滤行可以在几秒钟内采样而无需使用 tempdb,如果与查询的其余部分保持一致,则开销通常很小。

  • 在运行数据时不会遇到 CHECKSUM(*) / BINARY_CHECKSUM(*) 问题。使用 CHECKSUM(*) 方法时,行可以在“块”中选择,而不是“随机”!这是因为CHECKSUM 更喜欢速度而不是分发

  • 导致稳定/可重复行选择,并且可以简单地更改以在后续查询执行中生成不同的行。使用 NEWID() 的方法永远不会稳定/可重复。

  • 不使用 ORDER BY NEWID() 整个输入集,因为排序可能成为大型输入集的重大瓶颈。< /strong> 避免不必要的排序还可以减少内存和临时数据库的使用

  • 不使用 TABLESAMPLE,因此与 WHERE 预过滤器一起使用。

这是要点。 See this answer for additional details and notes

天真的尝试:

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()

答案 16 :(得分:1)

您也可以尝试使用new id()功能。

只需编写您的查询并按new id()功能使用订单即可。这很随机。

答案 17 :(得分:1)

最近,但是通过谷歌来到这里,所以为了后代,我将添加一个替代解决方案。

另一种方法是使用TOP两次,交替订单。我不知道它是否是“纯SQL”,因为它在TOP中使用了一个变量,但它在SQL Server 2008中有效。如果我想要一个随机单词,这是我用来对付字典单词表的一个例子。 / p>

SELECT TOP 1
  word
FROM (
  SELECT TOP(@idx)
    word 
  FROM
    dbo.DictionaryAbridged WITH(NOLOCK)
  ORDER BY
    word DESC
) AS D
ORDER BY
  word ASC

当然,@ idx是一些随机生成的整数,在目标表上的范围从1到COUNT(*),包括在内。如果您的列已编入索引,您也将从中受益。另一个优点是您可以在函数中使用它,因为不允许使用NEWID()。

最后,上面的查询运行在同一个表上的NEWID()类型的查询的执行时间的大约1/10。 YYMV。

答案 18 :(得分:1)

这里的大多数解决方案都是为了避免排序,但是他们仍然需要在桌面上进行顺序扫描。

还有一种方法可以通过切换到索引扫描来避免顺序扫描。如果您知道随机行的索引值,则几乎可以实际获得结果。问题是 - 如何猜测索引值。

以下解决方案适用于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;

我在上面的解决方案中你猜测范围0的10个各种随机索引值.. [id的最后一个值]。

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

还有一个问题 - 如果你有稀疏的ids 你可能会错过。解决方案是有一个备份计划 :)在这种情况下,通过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;

不是联盟 所有子句。在这种情况下,如果第一部分返回任何数据,则第二部分永远不会被执行!

答案 19 :(得分:0)

似乎列出的许多想法仍然使用排序

但是,如果使用临时表,则可以分配随机索引(就像许多已建议​​的解决方案一样),然后抓取第一个大于0到1之间的任意数字的索引。

例如(对于DB2):

WITH TEMP AS (
SELECT COMLUMN, RAND() AS IDX FROM TABLE)
SELECT COLUMN FROM TABLE WHERE IDX > .5
FETCH FIRST 1 ROW ONLY

答案 20 :(得分:0)

http://akinas.com/pages/en/blog/mysql_random_row/

的简单有效方法
SET @i = (SELECT FLOOR(RAND() * COUNT(*)) FROM table); PREPARE get_stmt FROM 'SELECT * FROM table LIMIT ?, 1'; EXECUTE get_stmt USING @i;

答案 21 :(得分:0)

对于Oracle而言,有更好的解决方案,而不是使用dbms_random.value,而它需要完全扫描以按dbms_random.value对行进行排序,而且对于大型表来说这是非常慢的。

请改用:

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

答案 22 :(得分:0)

对于Firebird:

Select FIRST 1 column from table ORDER BY RAND()

答案 23 :(得分:0)

要小心,因为TableSample实际上并不返回随机的行样本。它会指示您的查询查看构成行的8KB页面的随机样本。然后,针对这些页面中包含的数据执行查询。由于数据如何在这些页面上分组(插入顺序等),这可能导致数据实际上不是随机样本。

请参阅:http://www.mssqltips.com/tip.asp?tip=1308

TableSample的这个MSDN页面包含一个如何生成实际随机数据样本的示例。

http://msdn.microsoft.com/en-us/library/ms189108.aspx

答案 24 :(得分:0)

我必须同意CD-MaN:使用“ORDER BY RAND()”可以很好地用于小型表格,或者只用几次SELECT。

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

答案 25 :(得分:0)

使用SQL Server 2012+,您可以使用OFFSET FETCH query为单个随机行执行此操作

select  * from MyTable ORDER BY id OFFSET n ROW FETCH NEXT 1 ROWS ONLY

其中id是一个标识列,n是你想要的行 - 计算为0和count()之间的随机数 - 表中的1(偏移0是毕竟的第一行)

这适用于表数据中的漏洞,只要您有一个用于ORDER BY子句的索引即可。它也非常适合随机性 - 因为你自己努力传递但其他方法中的小问题不存在。此外,性能相当不错,在较小的数据集上表现良好,但我没有尝试过针对数百万行的严格性能测试。

答案 26 :(得分:0)

对于SQL Server 2005及更高版本,扩展@ GreyPanther对num_value没有连续值的情况的答案。这也适用于我们没有均匀分布数据集且num_value不是数字而是唯一标识符的情况。

WITH CTE_Table (SelRow, num_value) 
AS 
(
    SELECT ROW_NUMBER() OVER(ORDER BY ID) AS SelRow, num_value FROM table
) 

SELECT * FROM table Where num_value = ( 
    SELECT TOP 1 num_value FROM CTE_Table  WHERE SelRow >= RAND() * (SELECT MAX(SelRow) FROM CTE_Table)
)

答案 27 :(得分:0)

select r.id, r.name from table AS r
INNER JOIN(select CEIL(RAND() * (select MAX(id) from table)) as id) as r1
ON r.id >= r1.id ORDER BY r.id ASC LIMIT 1

这将需要较少的计算时间

答案 28 :(得分:0)

 SELECT * FROM table ORDER BY RAND() LIMIT 1

答案 29 :(得分:-1)

sql中的

随机函数可能会有所帮助。另外,如果您只想限制一行,只需在最后添加一行即可。

SELECT column FROM table
ORDER BY RAND()
LIMIT 1