如何在SQL中请求一个随机行?
我如何在纯SQL中请求一个随机行(或尽可能接近真正的随机)?
看到这篇文章: SQL从数据库表中select一个随机的行 。 它通过在MySQL,PostgreSQL,Microsoft SQL Server,IBM DB2和Oracle中执行此操作的方法(以下内容从该链接中复制):
用MySQLselect一个随机的行:
SELECT column FROM table ORDER BY RAND() LIMIT 1
用PostgreSQLselect一个随机行:
SELECT column FROM table ORDER BY RANDOM() LIMIT 1
用Microsoft SQL Serverselect一个随机行:
SELECT TOP 1 column FROM table ORDER BY NEWID()
用IBM DB2select一个随机行
SELECT column, RAND() as IDX FROM table ORDER BY IDX FETCH FIRST 1 ROWS ONLY
用Oracleselect一个随机logging:
SELECT column FROM ( SELECT column FROM table ORDER BY dbms_random.value ) WHERE rownum = 1
像Jeremies这样的解决scheme:
SELECT * FROM table ORDER BY RAND() LIMIT 1
工作,但是他们需要对所有表格进行顺序扫描(因为需要计算与每行相关的随机值 – 因此可以确定最小的值),即使是中等大小的表格也是如此。 我的build议是使用某种索引数字列(许多表将这些作为主键),然后写下如下所示的内容:
SELECT * FROM table WHERE num_value >= RAND() * ( SELECT MAX (num_value ) FROM table ) ORDER BY num_value LIMIT 1
如果num_value
被编入索引,这将在对数时间内工作,不pipe表大小如何。 一个警告:这假设num_value
是平均分布在范围0..MAX(num_value)
。 如果你的数据集强烈偏离这个假设,你会得到偏斜的结果(有些行会比其他的更频繁出现)。
我不知道这是多高效,但我之前使用过它:
SELECT TOP 1 * FROM MyTable ORDER BY newid()
因为GUID是非常随机的,sorting意味着你得到一个随机的行。
ORDER BY NEWID()
需要7.4 milliseconds
WHERE num_value >= RAND() * (SELECT MAX(num_value) FROM table)
需要0.0065 milliseconds
!
我一定会用后一种方法去。
你没有说你正在使用哪个服务器。 在旧版本的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,然后对它们进行sorting。
从性能的angular度来看,TABLESAMPLE()是很好的,但是你会得到结果的聚集(页面上的所有行将被返回)。
为了更好地执行真正的随机样本,最好的方法是随机过滤行。 我在SQL Server联机丛书文章使用TABLESAMPLE限制结果集中find以下代码示例:
如果您确实需要单个行的随机样本,请修改您的查询以随机筛选出行,而不是使用TABLESAMPLE。 例如,以下查询使用NEWID函数返回Sales.SalesOrderDetail表的大约百分之一的行:
SELECT * FROM Sales.SalesOrderDetail WHERE 0.01 >= CAST(CHECKSUM(NEWID(),SalesOrderID) & 0x7fffffff AS float) / CAST (0x7fffffff AS int)
SalesOrderID列包含在CHECKSUMexpression式中,以便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()上的两个索引效率低下,并创build一个logging号字段。
PREPARE RandomRecord FROM“SELECT * FROM table LIMIT?,1”; SET @ n = FLOOR(RAND()*(SELECT COUNT(*)FROM table)); EXECUTE RandomRecord USING @n;
对于SQL Server 2005和2008,如果我们想要一个随机样本(来自联机丛书 ):
SELECT * FROM Sales.SalesOrderDetail WHERE 0.01 >= CAST(CHECKSUM(NEWID(), SalesOrderID) & 0x7fffffff AS float) / CAST (0x7fffffff AS int)
最好的方法就是将一个随机值放在一个新的列中,然后使用类似下面的代码(pseude code + SQL):
randomNo = random() execSql("SELECT TOP 1 * FROM MyTable WHERE MyTable.Randomness > $randomNo")
这是MediaWiki代码使用的解决scheme。 当然,对较小的值有一些偏差,但是他们发现当没有行被提取时将随机值绕到零就足够了。
newid()解决scheme可能需要全表扫描,以便每行可以分配一个新的GUID,这将是性能低得多。
rand()解决scheme可能根本无法工作(即使用MSSQL),因为该函数将被评估一次,并且每行将被分配相同的“随机”数字。
正如@ BillKarwin对@ cnu答案的评论所指出的那样…
当与LIMIT结合时,我发现它执行得更好(至less在PostgreSQL 9.1中)以随机顺序JOIN,而不是直接sorting实际的行:例如
SELECT * FROM tbl_post AS t join... JOIN(SELECT id,CAST(-2147483648 * RANDOM()AS integer)AS rand 从tbl_post WHERE create_time> = 1349928000 )r ON r.id = t.id WHERE create_time> = 1349928000 AND ... ORDER BY r.rand 限制100
只要确保'r'为与其连接的复杂查询中的每个可能的键值生成一个“rand”值,但是仍然在可能的情况下限制'r'的行数。
CAST as Integer特别有助于PostgreSQL 9.2,它针对整型和单精度浮点types进行了特定的sorting优化。
这里的大多数解决scheme都是为了避免sorting,但是仍然需要在表格上进行顺序扫描。
还有一种方法可以通过切换到索引扫描来避免顺序扫描。 如果您知道随机行的索引值,则可以几乎立即得到结果。 问题是 – 如何猜测索引值。
以下解决scheme适用于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;
我以上的解决scheme,你猜从范围0 .. [ID的最后一个值] 10个不同的随机指标值。
数字10是任意的 – 你可以使用100或1000,因为它(惊人地)对响应时间没有很大的影响。
还有一个问题 – 如果你有稀疏的ID, 你可能会错过 。 解决scheme是有一个备份计划 :)在这种情况下,通过随机()查询纯旧秩序。 当合并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;
不是工会的 ALL子句。 在这种情况下,如果第一部分返回任何数据,第二部分永远不会执行!
最近,但通过谷歌来到这里,为了后代,我会添加一个替代解决scheme。
另一种方法是使用TOP两次,交替的顺序。 我不知道它是否是“纯SQL”,因为它在TOP中使用了一个variables,但它在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()types的查询的执行时间的1/10左右。 YYMV。
你也可以尝试使用new id()
函数。
只需编写一个你的查询,并使用new id()
函数的顺序。 它非常随机。
对于MySQL来获得随机logging
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
还没有完全看到这个答案的变化。 我有一个额外的约束,我需要,给定一个初始种子,每次select相同的一组行。
对于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(*))
慢得多,所以你可能不想用它来处理大的logging集。
初始种子select:
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 */
如果你需要select一个给定的种子,这似乎工作。
在MSSQL(在11.0.5569testing)使用
SELECT TOP 100 * FROM employee ORDER BY CRYPT_GEN_RANDOM(10)
明显快于
SELECT TOP 100 * FROM employee ORDER BY NEWID()
使用RAND(),因为它不被鼓励 ,你可以简单地得到最大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
请注意,它将检查任何等于或高于选定值的行。 也可以在表格中查找下一行,获得与My_Generated_Random相同或更低的ID,然后修改查询,如下所示:
SELECT ID FROM TABLE WHERE ID <= My_Generated_Random ORDER BY ID DESC LIMIT 1
在SQL Server中,您可以将TABLESAMPLE和NEWID()组合起来,以获得相当不错的随机性并且仍然有速度。 如果你真的只想要1或者less量的行,这是特别有用的。
SELECT TOP 1 * FROM [table] TABLESAMPLE (500 ROWS) ORDER BY NEWID()
SELECT * FROM table ORDER BY RAND() LIMIT 1
我必须同意CD-MaN:使用“ORDER BY RAND()”对于小型表格或者只有几次SELECT时才能很好地工作。
我也使用“num_value> = RAND()* …”技术,如果我真的想要随机结果,我有一个特殊的“随机”列,我每天更新一次左右。 这个单独的UPDATE运行需要一些时间(特别是因为你必须在该列上有一个索引),但是比每次运行select时为每行创build随机数要快得多。
要小心,因为TableSample实际上不会返回行的随机样本。 它指导您的查询,查看组成您的行的8KB页面的随机样本。 然后,对这些页面中包含的数据执行查询。 由于数据可能如何在这些页面上分组(插入顺序等),这可能导致数据实际上不是一个随机样本。
请参阅: http : //www.mssqltips.com/tip.asp?tip=1308
TableSample的这个MSDN页面包含了一个如何生成一个实际的随机样本数据的例子。
看来,列出的许多想法仍然使用sorting
但是,如果使用临时表,则可以指定随机索引(如同许多解决scheme所build议的那样),然后抓取第一个大于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;
对于Oracle而言,有更好的解决scheme,而不是使用dbms_random.value,而需要完整扫描才能按dbms_random.value对行进行sorting,而对于大型表而言则相当慢。
用这个代替:
SELECT * FROM employee sample(1) WHERE rownum=1
火鸟:
Select FIRST 1 column from table ORDER BY RAND()