从条件较大的数据库中选择n个随机行

时间:2013-11-04 09:19:55

标签: sql sql-server performance random query-performance

我有一个大约800万行的数据库,我想从中随机选择n行。首先,我在StackOverflowMSDN上的文章中阅读了流行的类似问题,但我觉得答案仍然不符合我的需求。

如果我想在没有额外条件的情况下随机选择一定比例的行,则提供的解决方案效果很好。但是我想要随机选择n行(例如最多5行),所有这些都匹配某个条件。

我的数据库包含包含其词性,标记,引理和令牌等信息的单词。现在我想执行查询以选择5个与查询中的单词类似的随机单词(例如,给我5个单词类似于 fuzzy ),这是通过仅查看具有相同部分的单词来确定的语音和levenshtein距离值超过某个阈值。我在sql server中有一个函数可以计算levenshtein距离。

上述方法的问题是它们要么必须遍历所有记录并计算levenshtein距离(这需要花费很多时间!),或者它们只能让我选择一个百分比而不是n行。 / p>

运行得很好的查询是:

SELECT DISTINCT TOP 5 lower(Words.TOKEN) as LTOKEN, Words.LEMMA, TagSet.POS_Simplified, TagSet.TAG 
FROM Words JOIN TagSet on Words.TAG = TagSet.TAG 
WHERE NOT Words.LEMMA = 'monarchie' AND TagSet.POS_Simplified = 'noun' 
AND TagSet.TAG = 'NOM' AND NOT Words.TOKEN = 'monarchie'
AND [dbo].edit_distance('monarchie', Words.Token) > 0.5

然而,只有顶部我总是得到相同的结果。我需要我的上衣是随机的。像使用NEWID()这样的方法将首先遍历整个数据库,然后随机选择,这不是我想要的行为,因为它们占用的时间太长。

有没有人有想法在庞大的数据库中快速选择n个随机行?


修改

某人(不在StackOverflow上)可能为我提供了一个带有 OPTION 子句和 fast 关键字的解决方案,该关键字检索它找到的前n行。

使用OPTION(快5)我到目前为止获得最佳性能(在800万行表上10秒)。我还将Levenshtein函数从SQL实现更改为c#编写的库实现,大大加快了性能。

Select top 5 * from (
SELECT DISTINCT lower(Words.TOKEN) as LTOKEN, Words.LEMMA, TagSet.POS_Simplified, TagSet.TAG 
FROM Words JOIN TagSet on Words.TAG = TagSet.TAG 
WHERE NOT Words.LEMMA = 'monarchie' AND TagSet.POS_Simplified = 'noun' 
AND TagSet.TAG = 'NOM' AND NOT Words.TOKEN = 'monarchie'
AND [dbo].clrEditDistance('monarchie', Words.Token) > 0.5
) AS T
ORDER BY NEWID()
OPTION(fast 5)

4 个答案:

答案 0 :(得分:1)

避免全面扫描将很困难。如果您有一个可以轻松随机选择的列,比如说您碰巧有一个“密集”的标识列,间隙很小,请用以下修改代替Klark的方法:

declare @results table (id bigint, name varchar(100))

while (select count(*) from @results) < 5
    begin
    insert  @results
            (name)
    select  name
    from    (
            select  *
            from    dbo.Words
            WHERE  IDCOLUMN = CONVERT(INT,RAND()) * APPX_NUMBER_OF_ROWS
            ) as SubQueryAlias          
    where   dbo.edit_distance(left(name,4), 'APRS', 100) < 3
    end  

select  *
from    @results)

答案 1 :(得分:0)

为了获得随机数据,您需要遍历与where子句匹配的所有行。搜索将仅在与where表达式匹配的行上完成,因此它不会是完整的表搜索。如果您有大量与您的搜索匹配的记录,您可以执行以下操作:

select top 5 * from
(
SELECT DISTINCT TOP 1000 lower(Words.TOKEN) as LTOKEN, Words.LEMMA, TagSet.POS_Simplified, TagSet.TAG 
FROM Words JOIN TagSet on Words.TAG = TagSet.TAG 
WHERE NOT Words.LEMMA = 'monarchie' AND TagSet.POS_Simplified = 'noun' 
AND TagSet.TAG = 'NOM' AND NOT Words.TOKEN = 'monarchie'
AND [dbo].edit_distance('monarchie', Words.Token) > 0.5
) order by newid();

但当然,这不会是真正随意的。

答案 2 :(得分:0)

根据您的问题,我假设您知道很多行都符合您的edit_distance > 0.5条件。但SQL Server不知道这一点。与SQL Server共享该信息的一种方法是使用表变量编写更明确的查询。

declare @results table (id bigint, name varchar(100))

while (select count(*) from @results) < 5
    begin
    insert  @results
            (name)
    select  name
    from    (
            select  top 100 *
            from    dbo.Words
            order by
                    newid()
            ) as SubQueryAlias          
    where   dbo.edit_distance(left(name,4), 'APRS', 100) < 3
    end  

select  top 5 *
from    @results

上面的代码段一次选择100个随机行,并将匹配的行插入到结果表中。它循环直到找到5行。最后,它从结果表中选择5行。

如果您有许多匹配的行,这应该更有效,但如果有很少的话,效率会低得多。

答案 3 :(得分:0)

我认为你能以多快的速度完成所需的基本限制。如果要快速从表中选择记录,则需要使用索引的方法。假设您有一个连续的整数列ID并且它是聚集索引:您可以选择具有您生成的不同随机ID值的记录,但您不能保证MIN(ID)和MAX(ID)之间的每个ID都会为您提供一行这个表所以你最终可能会得到比你要求的更少的行。

您可能要做的一件事是获取具有您想要应用条件的查询并添加row_number(请参阅this technet article),从而为您提供一个没有“漏洞”的顺序“密钥”...选择该键范围内的随机整数。然后,您将处理表的一个子集而不是整个表,但我怀疑这可能是您在性能方面做得最好的。

您可以通过编写一个使用循环的表值函数来处理ID中的“漏洞”(只需保持选择随机值,直到获得所需的结果数),但这样做不够优雅并且可能会出现并发问题,具体取决于访问数据库模式。所以这取决于你在这些方面的要求。