如何在SQL请求一个随机行?

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

543204 次浏览

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

SELECT TOP 1 * FROM MyTable ORDER BY newid()

因为guid是非常随机的,所以顺序意味着您得到的是随机行。

 SELECT * FROM table ORDER BY RAND() LIMIT 1

你没说你用的是哪台服务器。在旧版本的SQL Server中,你可以使用这个:

select top 1 * from mytable order by newid()

在SQL Server 2005及以上版本中,你可以使用TABLESAMPLE来获得一个可重复的随机样本:

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

看看这个帖子:从数据库表中随机选择一行。它介绍了在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

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

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

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

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

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

像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)范围内均匀分布。如果您的数据集严重偏离这个假设,您将得到倾斜的结果(一些行会比其他行出现得更频繁)。

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

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

对于SQL Server 2005和2008,如果我们想要一个随机的个别行样本(从图书在线):

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

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

看到:# EYZ0

该表的MSDN页面包含了如何生成实际随机数据样本的示例。

# EYZ0

SQL Server

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

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

为了获得更好的真实随机样本,最好的方法是随机过滤掉行。我在SQL Server书籍在线文章 limitesresultssets by Using TABLESAMPLE中找到了以下代码示例:

如果你真的想要一个随机样本 将查询修改为单独的行 随机过滤行,而不是 使用TABLESAMPLE。例如, 下面的查询使用newwid 函数返回大约1 行的百分比 销售。SalesOrderDetail表:< / p >

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,它将为您提供最佳性能。否则使用newwid ()/filter方法。如果结果集很大,Newid ()/order by应该是最后的选择。

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

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

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

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

晚了,但通过谷歌到达这里,所以为了子孙后代,我将添加一个替代解决方案。

另一种方法是使用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。

ORDER BY NEWID()

需要# EYZ0

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

需要# EYZ0 !

我肯定会选择后一种方法。

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

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

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

但是,如果使用临时表,则可以分配一个随机索引(就像许多解决方案所建议的那样),然后获取第一个大于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

您也可以尝试使用new id()函数。

只需编写您的查询和使用顺序由new id()函数。它是随机的。

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

当与LIMIT结合时,我发现它执行得更好(至少与PostgreSQL 9.1),以随机顺序连接,而不是直接对实际行进行排序: <代码> < >之前 SELECT * FROM tbl_post AS t 加入…… JOIN (SELECT id, CAST(-2147483648 * RANDOM() AS integer) AS rand 从tbl_post WHERE create_time >= 1349928000 ) r ON r.id = t.d id WHERE create_time >= 1349928000 AND… ORDER BY r.r rand 限制100 < / pre > < /代码> < / p >

只要确保“r”为与它连接的复杂查询中的每个可能的键值生成一个“rand”值,但仍然尽可能限制“r”的行数。

CAST as Integer对于PostgreSQL 9.2特别有用,它对整型和单精度浮点类型有特定的排序优化。

用于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/

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;

我还没看出来答案有什么不同。我有一个额外的约束条件,给定一个初始种子,每次都要选择相同的行集。

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

如果给定一个种子,你需要选择相同的集合,这似乎是可行的。

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

用这个代替:

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

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

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

明显快于

SELECT TOP 100 * FROM employee ORDER BY NEWID()

火鸟:

Select FIRST 1 column from table ORDER BY RAND()

而不是使用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中,您可以将TABLESAMPLE与NEWID()结合使用,以获得相当好的随机性,并且仍然具有速度。如果您真的只想要1行或少量的行,这尤其有用。

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

在SQL Server 2012+中,您可以使用偏移获取查询对单个随机行执行此操作

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

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

这适用于表数据中的漏洞,只要您有一个用于ORDER BY子句的索引。它对随机性也很有好处——因为你可以自己计算出来传递进来,但其他方法中的琐碎问题是不存在的。此外,性能非常好,在一个较小的数据集上,它表现得很好,尽管我没有尝试过针对数百万行进行认真的性能测试。

对于SQL Server 2005及以上版本,在num_value没有连续值的情况下扩展@GreyPanther的答案。当数据集分布不均匀,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)
)

sql中的随机函数可能会有所帮助。此外,如果您想限制为一行,只需在最后添加它。

SELECT column FROM table
ORDER BY RAND()
LIMIT 1
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

这将需要更少的计算时间

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

如果不需要真采样,则生成一个随机值[0, max_rows)并使用ORDER BY . .抵消. .从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,需要一个“一般行样本”;方法…< / >强

注意:这是对在SQL Server特定的问题中获取一行样本找到的答案的改编。# EYZ1

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


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

  • 它是相对快速的巨大数据集可以有效地用于/与派生查询。数百万个预先过滤的行可以在几秒钟内采样没有使用tempdb,如果与查询的其余部分对齐,开销通常是最小的。

  • < p > # EYZ3

  • 结果在稳定/可重复行选择,并且可以在后续查询执行时进行简单更改以生成不同的行。使用NEWID()的方法永远不可能稳定/可重复。

  • 不使用整个输入集的ORDER BY NEWID() ,因为排序可以成为大输入集的显著瓶颈避免不必要的排序也避免减少内存和tempdb的使用排序。

  • < p > # EYZ2

这是要点。# EYZ0。

天真的尝试:

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