获得随机订购的最佳方法是什么?


27

我有一个查询,希望将所得记录随机排序。它使用聚集索引,因此如果我不包括order by它,则可能会按该索引的顺序返回记录。如何确保随机的行顺序?

我知道这可能不会是“真正的”随机,伪随机足以满足我的需求。

Answers:


19

ORDER BY NEWID()将对记录进行随机排序。这里的一个例子

SELECT *
FROM Northwind..Orders 
ORDER BY NEWID()

7
ORDER BY NEWID()实际上是随机的,但不是统计上随机的。差异很小,大多数时候差异并不重要。
mrdenny

4
从性能的角度来看,这非常慢-ORDER BY CHECKSUM(NEWID())可以带来显着的改进
Miles D

1
@mrdenny-您以“非统计随机”为基础吗?这里的答案说,最终它最终被使用CryptGenRandom了。dba.stackexchange.com/a/208069/3690
马丁·史密斯

15

Pradeep Adiga的第一个建议ORDER BY NEWID()很好,这也是我过去使用此建议的原因。

使用时要小心RAND()-在许多情况下,每个语句只执行一次,因此ORDER BY RAND()不会起作用(因为从RAND()中获得的结果对每一行都是相同的)。

例如:

SELECT display_name, RAND() FROM tr_person

返回人员表中的每个名称和一个“随机”数字,每一行都相同。每次运行查询时,数字的确会有所不同,但是每次的行数都是相同的。

为了证明RAND()ORDER BY子句中使用的情况相同,我尝试:

SELECT display_name FROM tr_person ORDER BY RAND(), display_name

结果仍按名称排序,表明较早的排序字段(预期为随机的字段)无效,因此推测总是具有相同的值。

但是排序NEWID()确实可以,因为如果不总是重新评估NEWID(),则在一个状态行中插入许多具有唯一标识符作为关键字的新行时,UUID的用途将被破坏,因此:

SELECT display_name FROM tr_person ORDER BY NEWID()

确实对名称进行“随机”排序。

其他DBMS

以上适用于MSSQL(至少在2005年和2008年,如果我正确地记得2000年)。每次在所有DBMS中都应评估返回新UUID的函数,NEWID()处于MSSQL下,但是值得在文档中和/或通过您自己的测试进行验证。在DBMS之间,其他任意结果函数(例如RAND())的行为更可能会有所不同,因此请再次检查文档。

我还看到在某些情况下,由于UUID值的排序被忽略,因为数据库假定该类型没有有意义的排序。如果您发现这种情况,则在订购子句中将UUID显式转换为字符串类型,或者像CHECKSUM()在SQL Server中一样将其他一些函数包装在字符串上(由于在订购时将进行排序,因此性能也会有很小的差异) 32位值而不是128位值,尽管这样做的好处是否CHECKSUM()首先超过了每个值的运行成本,我将让您进行测试)。

边注

如果您想要一个任意的但有点可重复的排序,请按行本身中一些相对不受控制的数据子集进行排序。例如,或者这些将以任意但可重复的顺序返回名称:

SELECT display_name FROM tr_person ORDER BY CHECKSUM(display_name), display_name -- order by the checksum of some of the row's data
SELECT display_name FROM tr_person ORDER BY SUBSTRING(display_name, LEN(display_name)/2, 128) -- order by part of the name field, but not in any an obviously recognisable order)

任意但可重复的排序在应用程序中通常并不有用,但是如果您想以各种顺序对结果进行测试,但又希望能够以相同的方式重复多次运行(以获得平均时间),则在测试中很有用多次运行的结果,或者测试您对代码进行的修复确实消除了以前由特定输入结果集突出显示的问题或效率低下,或者仅用于测试您的代码“稳定”,因为每次返回的结果相同如果以给定的顺序发送了相同的数据)。

此技巧还可以用于从函数中获取更多任意结果,这些函数不允许在其体内进行非确定性调用,例如NEWID()。再说一次,这在现实世界中可能不会经常有用,但是如果您希望函数返回随机的东西并且“ random-ish”就足够了,那么它可能会派上用场(但是要小心记住确定规则评估用户定义的函数时(即通常每行仅评估一次,否则结果可能不符合您的期望/要求)。

性能

正如EBarr指出的那样,上述任何一项都可能存在性能问题。对于多于几行的数据,您几乎肯定会看到输出以正确顺序读回请求的行数之前已被缓存到tempdb,这意味着即使您要查找前十行,您也可能会找到完整的索引扫描(或更糟糕的是,表扫描)与对tempdb的大量写入一起发生。因此,与大多数事情一样,在生产中使用实际数据进行基准测试至关重要。


14

这是一个古老的问题,但是我认为讨论的一个方面缺失-性能。 ORDER BY NewId()是一般的答案。当别人那里的幻想,他们补充说,你真的应该换NewID()CheckSum(),你知道的,性能!

这种方法的问题在于,仍然可以保证对索引进行完整的扫描,然后对数据进行完整的排序。如果您处理的数据量很大,则可能很快变得昂贵。查看这个典型的执行计划,并注意排序如何占用您96%的时间...

在此处输入图片说明

为了让您了解它的扩展方式,我将为您提供一个来自我使用的数据库的示例。

  • TableA-在2500个数据页中有50,000行。随机查询在42毫秒内产生145次读取。
  • 表B-在114,000个数据页中有120万行。Order By newid()在此表上运行将产生53,700次读取,并花费16秒。

这个故事的寓意是,如果您有大表(想想数十亿行)或需要频繁运行此查询,则该newid()方法会崩溃。那么男孩要做什么?

认识TABLESAMPLE()

在SQL 2005中TABLESAMPLE,创建了一个新功能。我只看过一篇文章讨论它的用途 ...应该有更多。MSDN 文档在这里。首先是一个例子:

SELECT Top (20) *
FROM Northwind..Orders TABLESAMPLE(20 PERCENT)
ORDER BY NEWID()

表样本背后的想法是给您大约所需的子集大小。SQL为每个数据页编号,然后选择这些页的X%。您返回的实际行数可能会因所选页面中存在的内容而异。

那么我该如何使用呢?选择一个子集大小,使其超过您需要的行数,然后添加Top()。我们的想法是,你可以让你极大的相表显得更小之前昂贵的排序来。

我个人一直在使用它来实际上限制我的桌子的大小。因此,在该百万行表上执行top(20)...TABLESAMPLE(20 PERCENT)查询的操作在1600毫秒内下降到5600次读取。还有一个REPEATABLE()选项,您可以在其中传递“种子”来进行页面选择。这将导致稳定的样品选择。

无论如何,只是认为应该将其添加到讨论中。希望它可以帮助某人。


能够编写可伸缩的随机顺序查询不仅会扩大规模,而且可以处理较小的数据集,这将是很好的。听起来您必须TABLESAMPLE()根据所拥有的数据手动在拥有和不拥有之间进行切换。我认为这TABLESAMPLE(x ROWS)甚至不能确保至少 x返回行,因为文档中说“返回的实际行数可能会有很大差异。如果指定一个小数目,如5,您可能无法获得的样本中的结果。” -这样的ROWS语法真的还只是一个蒙面PERCENT里面?
binki 2014年

当然,自动魔术很好。在实践中,我很少见过5行表可扩展到数百万行,而无需另行通知。TABLESAMPLE()似乎是基于对表中页数的选择,因此给定的行大小会影响返回的内容。至少在我看来,表样本的要点是为您提供一个很好的子集,您可以从中选择子集-有点像派生表。
EBarr 2014年

3

许多表都有一个相对密集的(缺失值很少)索引的数字ID列。

这使我们能够确定现有值的范围,并使用该范围内随机生成的ID值选择行。当要返回的行数相对较小并且ID值的范围被密集填充(因此,生成缺失值的机会足够小)时,这种方法最有效。

为了说明这一点,下面的代码从“用户堆栈溢出”表中选择100个不同的随机用户,该表有8,123,937行。

第一步是确定ID值的范围,这是由于索引而导致的有效操作:

DECLARE 
    @MinID integer,
    @Range integer,
    @Rows bigint = 100;

--- Find the range of values
SELECT
    @MinID = MIN(U.Id),
    @Range = 1 + MAX(U.Id) - MIN(U.Id)
FROM dbo.Users AS U;

范围查询

该计划从索引的每一端读取一行。

现在,我们生成范围内的100个不同的随机ID(在users表中具有匹配的行),并返回这些行:

WITH Random (ID) AS
(
    -- Find @Rows distinct random user IDs that exist
    SELECT DISTINCT TOP (@Rows)
        Random.ID
    FROM dbo.Users AS U
    CROSS APPLY
    (
        -- Random ID
        VALUES (@MinID + (CONVERT(integer, CRYPT_GEN_RANDOM(4)) % @Range))
    ) AS Random (ID)
    WHERE EXISTS
    (
        SELECT 1
        FROM dbo.Users AS U2
            -- Ensure the row continues to exist
            WITH (REPEATABLEREAD)
        WHERE U2.Id = Random.ID
    )
)
SELECT
    U3.Id,
    U3.DisplayName,
    U3.CreationDate
FROM Random AS R
JOIN dbo.Users AS U3
    ON U3.Id = R.ID
-- QO model hint required to get a non-blocking flow distinct
OPTION (MAXDOP 1, USE HINT ('FORCE_LEGACY_CARDINALITY_ESTIMATION'));

随机行查询

该计划显示,在这种情况下,需要601个随机数才能找到100个匹配的行。很快:

表“用户”。扫描计数1,逻辑读1937,物理读2,预读408
表“工作表”。扫描计数0,逻辑读取0,物理读取0,预读读取0
表“工作文件”。扫描计数0,逻辑读取0,物理读取0,预读读取0

 SQL Server执行时间:
   CPU时间= 0毫秒,经过时间= 9毫秒。

在Stack Exchange Data Explorer上尝试一下。


0

正如我在本文中所解释的,为了重新整理SQL结果集,您需要使用特定于数据库的函数调用。

请注意,使用RANDOM函数对大型结果集进行排序可能会非常缓慢,因此请确保对小型结果集执行此操作。

如果你有洗牌大型结果集,并随后限制它,那么它是更好地使用SQL Server TABLESAMPLE的SQL Server在ORDER,而不是随机函数BY子句。

因此,假设我们有以下数据库表:

在此处输入图片说明

以及song表中的以下行:

| id | artist                          | title                              |
|----|---------------------------------|------------------------------------|
| 1  | Miyagi & Эндшпиль ft. Рем Дигга | I Got Love                         |
| 2  | HAIM                            | Don't Save Me (Cyril Hahn Remix)   |
| 3  | 2Pac ft. DMX                    | Rise Of A Champion (GalilHD Remix) |
| 4  | Ed Sheeran & Passenger          | No Diggity (Kygo Remix)            |
| 5  | JP Cooper ft. Mali-Koa          | All This Love                      |

在SQL Server上,您需要使用该NEWID功能,如以下示例所示:

SELECT
    CONCAT(CONCAT(artist, ' - '), title) AS song
FROM song
ORDER BY NEWID()

在SQL Server上运行上述SQL查询时,我们将获得以下结果集:

| song                                              |
|---------------------------------------------------|
| Miyagi & Эндшпиль ft. Рем Дигга - I Got Love      |
| JP Cooper ft. Mali-Koa - All This Love            |
| HAIM - Don't Save Me (Cyril Hahn Remix)           |
| Ed Sheeran & Passenger - No Diggity (Kygo Remix)  |
| 2Pac ft. DMX - Rise Of A Champion (GalilHD Remix) |

请注意,由于NEWIDORDER BY子句使用了函数调用,因此歌曲以随机顺序列出。

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.