如何在SQL中请求随机行?


510

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


我以前总是在sql的查询结果之后在php中执行此操作...根据解决方案的限制1附肢,这可能处理起来要快得多
CheeseConQueso


2
似乎没有在每个dbms上运行的“纯SQL”解决方案...每个解决方案都有一个解决方案。
Manu 2014年

Answers:


734

参见这篇文章:SQL从数据库表中选择一个随机行。它介绍了在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

30
-1代表order by rand()所有数据库中的dbs:|。这里也提到过
AD7six 2014年

20
十年前,有人说ORDER BY RAND()错了……
trejder 2015年

在SQL Server上,ORDER BY NEWID()似乎明显较慢。我的查询如下:从客户C内部联接中选择前1000个C.CustomerId,CL.LoginName C.CustomerId = LA.LinkedAccount LA上的内部链接C.CustomerId = CL.CustomerId组的内部联接CustomerLogin CL,由C.CustomerId,CL进行。 LoginName的count(*)> 1个按NEWID()排序的行删除“按NEWID()排序”行返回的结果要快得多。
Ben Power

3
对于SQLite,请使用RANDOM()函数。
2015年

10
这些解决方案无法扩展。它们O(n)n表中的记录数相同。假设您有100万条记录,您真的要生成100万个随机数或唯一ID吗?我宁愿使用它COUNT()并将其包含在LIMIT具有单个随机数的新表达式中。
Christian Hujer '16

174

杰里米(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_value是在范围内平均分配的0..MAX(num_value)。如果您的数据集严重偏离此假设,您将得到歪斜的结果(某些行会比其他行更频繁地出现)。


8
第二个建议不是随机的。您无法预测将要选择的行,但是如果您必须下注,则可以下注第二行。而且您永远不会下注,无论num_value的分布和表的大小如何,都不太可能被选中。
Etienne Racine

1
我知道通常RAND()函数的质量不是很高,但是除此之外,您还可以详细说明为什么选择不是随机的吗?
Gray Panther

13
第一个是SQL Server中的WRONG。每个查询仅调用一次RAND()函数,而不是每行调用一次。因此,它总是选择第一行(尝试)。
杰夫·沃克

3
第二个还假设已考虑所有行:有可能会选择已删除的行。
Sam Rueby '02

3
@ Sam.Rueby实际上,num_value> = RAND()...限制1确保空行将被跳过,直到找到现有行为止。
ghord

62

我不知道这有多有效,但是我以前用过:

SELECT TOP 1 * FROM MyTable ORDER BY newid()

由于GUID非常随机,因此排序意味着您将获得随机行。


1
我正在使用MS SQL Server,SELECT TOP 1 * FROM some_table_name ORDER BY NEWID()对我来说非常有效,感谢您的建议!

这也正是同样的事情ORDER BY RAND() LIMIT 1
肯·布鲁姆

6
这也是非常特定于数据库的,因为它使用TOP 1newid()
灰色,

12
这是一个坏主意。除非每个列都单独索引,否则此方法将不使用索引。拥有1亿条记录的表可能需要很长时间才能获得一条记录。
切换

1
@Switch,您会提出什么解决方案?
Akmal Salikhov '18年

31
ORDER BY NEWID()

需要 7.4 milliseconds

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

需要0.0065 milliseconds

我绝对会选择后一种方法。


2
第二个选项不会选择最后一行。我不知道为什么-只是指出来。
Voldemort

7
@Voldemort:rand()返回一个浮点数n,其中0 < n < 1。假设num_value是整数,则的返回值rand() * max(num_value)也将被强制为整数,因此会在小数点后截断任何内容。因此,rand() * max(num_value)始终小于max(num_value),这就是为什么永远不会选择最后一行的原因。
伊恩·坎普

如果我的数据经常被删除,我将没有效率-如果我发现一个空白,我将不得不重新运行整个查询。
Loic Coenen

1
@IanKemp愚蠢的问题,那么为什么不简单地使用SELECT MAX(num_value)+ 1?由于rand(在大多数情况下为RANDOM)返回[0,1),因此您将获得完整的值范围。另外,是的,您是对的,必须修复查询。
tekHedd

13

您没有说要使用哪个服务器。在旧版本的SQL Server中,可以使用以下命令:

select top 1 * from mytable order by newid()

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

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

9
MSDN表示,对于真正的随机结果,newid()优于tablesample:msdn.microsoft.com/en-us/library/ms189108.aspx
Andrew Hedges

7
@Andrew Hedges:ORDER BY NEWID()的费用太高
AndreiRînea10年

10

对于SQL Server

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

从性能的角度来看,TABLESAMPLE()很好,但是您会得到成堆的结果(将返回页面上的所有行)。

为了获得性能更好的真实随机样本,最好的方法是随机过滤出行。我在SQL Server联机丛书文章“ 使用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应该是最后的选择。


4

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

PREPARE RandomRecord FROM“ SELECT * FROM table LIMIT?,1”;
SET @ n = FLOOR(RAND()*(从表中选择COUNT(*)个表));
使用@n执行RandomRecord;

当上述where子句中使用的索引数字值分布不均时,该解决方案还负责返回随机行。因此,即使与使用where id_value> = RAND()* MAX(id_value)花费几乎相同(恒定)的时间,效果也会更好。
guido

据我所知,它不是在恒定时间内运行,而是在线性时间内运行。在最坏的情况下,@ n等于表中的行数,并且“ SELECT * FROM table LIMIT?,1”计算@n-1行,直到到达最后一行。
Andres Riofrio 2014年

3

最好的方法是为此目的在新列中放入一个随机值,并使用如下代码(伪代码+ SQL):

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

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

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

rand()解决方案可能根本无法工作(例如,使用MSSQL),因为该函数将仅被评估一次,并且每一行将被分配相同的“随机”数字。


1
当您得到0个结果时,四处循环可提供随机证明的样本(不仅仅是“足够好”)。该解决方案几乎可以扩展到多行查询(请考虑“聚会改组”)。问题是结果倾向于在相同的组中重复选择。为了解决这个问题,您需要重新分配刚刚使用的随机数。您可以通过跟踪randomNo并将其设置为结果的max(randomness)作弊,但然后p(查询1的行i和查询2的行i)== 0,这是不公平的。让我做一些数学运算,然后我将以一个真正公平的方案与您联系。
alsuren


3

由于不鼓励使用RAND(),因此您可以简单地获得最大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

如果表中不再存在生成的随机ID,将会发生什么情况?您不想显示给用户的已删除或被动行会造成麻烦。
Ebleme '19

没有。您获得的是最接近的ID号,而不是确切的ID号。如果您认为要删除id = 1,则将1与最小值进行交换。
forsberg

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对于PostgreSQL 9.2特别有用,它对整数和单精度浮点类型进行了特定的排序优化。


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 .. [id的最后值]的10个各种随机索引值。

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

还有一个问题-如果您的ID稀疏,则可能会错过。解决方案是有一个备份计划:)在这种情况下,可以通过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子句。在这种情况下,如果第一部分返回任何数据,则永远不会执行第二部分!


1

最近,但是是通过Google到达的,因此为了后代,我将添加一个替代解决方案。

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

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。


1

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

只需编写查询并按new id()功能使用订单即可。它相当随机。


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/


在测试了许多答案之后,我相信这是最好的答案。它看起来很快,每次都选择一个好的随机数。似乎类似于@GreyPanther的第二个建议,但此答案选择了更多随机数。
杰夫·贝克

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 */

如果需要给定种子选择同一组,这似乎可行。


1

在MSSQL中(在11.0.5569上测试)使用

SELECT TOP 100 * FROM employee ORDER BY CRYPT_GEN_RANDOM(10)

明显比

SELECT TOP 100 * FROM employee ORDER BY NEWID()

1

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

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

1

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

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

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

只要您有一个用于ORDER BY子句的索引,它就可以处理表数据中的孔。这对于随机性也非常好-当您努力使自己通过时,却没有其他方法的麻烦。此外,性能非常好,在较小的数据集上,它可以很好地保持性能,尽管我没有尝试对几百万行进行严格的性能测试。



0

我必须同意CD-MaN:使用“ ORDER BY RAND()”对于小型表或仅执行几次SELECT时将很好地工作。

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


0

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

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

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

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


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

2
在考虑了这种解决方案之后,我发现我的逻辑存在一个根本缺陷。这将一致地在表的开头附近返回相同的较小的设置值,因为我假设如果0和1之间存在均匀分布,则第一行有50%的机会满足该条件。
DAVID


0

对于Oracle,有一个更好的解决方案,而不是使用dbms_random.value,尽管它需要对dbms_random.value进行完全扫描以对行进行排序,并且对于大型表而言它的速度相当慢。

使用此代替:

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


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

-1

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

SELECT column FROM table
ORDER BY RAND()
LIMIT 1
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.