如何在纯SQL中请求随机行(或尽可能接近真正的随机行)?
如何在纯SQL中请求随机行(或尽可能接近真正的随机行)?
Answers:
参见这篇文章: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
order by rand()
所有数据库中的dbs:|。这里也提到过。
ORDER BY RAND()
错了……
O(n)
与n
表中的记录数相同。假设您有100万条记录,您真的要生成100万个随机数或唯一ID吗?我宁愿使用它COUNT()
并将其包含在LIMIT
具有单个随机数的新表达式中。
杰里米(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)
。如果您的数据集严重偏离此假设,您将得到歪斜的结果(某些行会比其他行更频繁地出现)。
我不知道这有多有效,但是我以前用过:
SELECT TOP 1 * FROM MyTable ORDER BY newid()
由于GUID非常随机,因此排序意味着您将获得随机行。
ORDER BY RAND() LIMIT 1
TOP 1
和newid()
。
ORDER BY NEWID()
需要 7.4 milliseconds
WHERE num_value >= RAND() * (SELECT MAX(num_value) FROM table)
需要0.0065 milliseconds
!
我绝对会选择后一种方法。
rand()
返回一个浮点数n
,其中0 < n < 1
。假设num_value
是整数,则的返回值rand() * max(num_value)
也将被强制为整数,因此会在小数点后截断任何内容。因此,rand() * max(num_value)
将始终小于max(num_value)
,这就是为什么永远不会选择最后一行的原因。
您没有说要使用哪个服务器。在旧版本的SQL Server中,可以使用以下命令:
select top 1 * from mytable order by newid()
在SQL Server 2005及更高版本中,您可以TABLESAMPLE
用来获取可重复的随机样本:
SELECT FirstName, LastName
FROM Contact
TABLESAMPLE (1 ROWS) ;
对于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应该是最后的选择。
如果可能,请使用存储的语句来避免RND()上的两个索引都无效,并创建记录号字段。
PREPARE RandomRecord FROM“ SELECT * FROM table LIMIT?,1”; SET @ n = FLOOR(RAND()*(从表中选择COUNT(*)个表)); 使用@n执行RandomRecord;
最好的方法是为此目的在新列中放入一个随机值,并使用如下代码(伪代码+ SQL):
randomNo = random()
execSql("SELECT TOP 1 * FROM MyTable WHERE MyTable.Randomness > $randomNo")
这是MediaWiki代码采用的解决方案。当然,对于较小的值存在一些偏差,但是他们发现,在没有获取任何行的情况下,将随机值包装为零就足够了。
newid()解决方案可能需要全表扫描,以便可以为每行分配一个新的guid,这将大大降低性能。
rand()解决方案可能根本无法工作(例如,使用MSSQL),因为该函数将仅被评估一次,并且每一行将被分配相同的“随机”数字。
对于SQL Server 2005和2008,如果我们想要一个随机的单个行样本(来自联机丛书):
SELECT * FROM Sales.SalesOrderDetail
WHERE 0.01 >= CAST(CHECKSUM(NEWID(), SalesOrderID) & 0x7fffffff AS float)
/ CAST (0x7fffffff AS int)
由于不鼓励使用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
正如@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特别有用,它对整数和单精度浮点类型进行了特定的排序优化。
此处大多数解决方案旨在避免排序,但是它们仍然需要对表进行顺序扫描。
还有一种方法可以通过切换到索引扫描来避免顺序扫描。如果您知道随机行的索引值,则几乎可以立即获得结果。问题是-如何猜测索引值。
以下解决方案适用于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子句。在这种情况下,如果第一部分返回任何数据,则永远不会执行第二部分!
最近,但是是通过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。
您也可以尝试使用new id()
功能。
只需编写查询并按new id()
功能使用订单即可。它相当随机。
让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
还没有在答案中看到这种变化。我有一个额外的约束,需要给定初始种子,每次选择相同的行集。
对于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 */
如果需要给定种子选择同一组,这似乎可行。
在SQL Server中,可以将TABLESAMPLE与NEWID()结合使用以获得很好的随机性,并且仍然具有速度。如果您确实只需要1行或少量行,则此功能特别有用。
SELECT TOP 1 * FROM [table]
TABLESAMPLE (500 ROWS)
ORDER BY NEWID()
使用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子句的索引,它就可以处理表数据中的孔。这对于随机性也非常好-当您努力使自己通过时,却没有其他方法的麻烦。此外,性能非常好,在较小的数据集上,它可以很好地保持性能,尽管我没有尝试对几百万行进行严格的性能测试。
注意,因为TableSample实际上不会返回行的随机样本。它指示您的查询查看构成行的8KB页面的随机样本。然后,针对这些页面中包含的数据执行查询。由于在这些页面上数据的分组方式(插入顺序等),这可能导致数据实际上不是随机样本。
请参阅:http : //www.mssqltips.com/tip.asp?tip=1308
这个用于TableSample的MSDN页面包括一个如何生成实际随机数据样本的示例。
似乎列出的许多想法仍然使用排序
但是,如果使用临时表,则可以分配一个随机索引(如许多解决方案所建议的那样),然后获取第一个大于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
来自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;
对于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)
)
来自sql的随机函数可能会有所帮助。另外,如果您只想限制一行,那么只需在最后添加一行即可。
SELECT column FROM table
ORDER BY RAND()
LIMIT 1