8

我有一个大约 800 万行的数据库,我想从中随机选择 n 行。首先,我已经阅读了StackOverflow上的流行和类似问题以及MSDN上的文章,但是我觉得答案仍然不适合我的需要。

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

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

上述方法的问题在于,他们要么必须遍历所有记录并计算 levenshtein 距离(这会占用大量时间!),要么他们只让我选择一个百分比而不是 n 行。

一个运行良好的查询是:

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

但是,只有 top 我总是得到相同的结果。我需要我的上衣是随机的。使用 NEWID() 之类的方法将首先遍历整个数据库,然后随机选择,这不是我的预期行为,因为它们花费的时间太长。

有没有人想在一个巨大的数据库上快速选择 n 个随机行?


编辑:

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

使用 OPTION(fast 5) 我获得了迄今为止最好的性能(在超过 800 万行的表上需要 10 秒)。我还将 Levenshtein 函数从 SQL 实现更改为 ac# 编写的库实现,这大大提高了性能。

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

4 回答 4

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)
于 2014-01-21T02:42:55.187 回答
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();

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

于 2013-11-04T09:29:47.713 回答
0

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

您可能会做的一件事是获取具有您想要应用的条件的查询并添加一个 row_number(请参阅此 technet 文章),从而为您提供一个没有“孔”的顺序“键”......在该键的范围。然后,您将处理表的子集而不是整个表,但我怀疑这可能是您在性能方面可以做到的最好的。

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

于 2013-12-19T17:39:05.830 回答
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 行。

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

于 2013-11-04T10:03:25.693 回答