【问题标题】:Improve performance of SQL Query with dynamic like使用动态类似提高 SQL 查询的性能
【发布时间】:2020-02-07 08:39:43
【问题描述】:

我需要搜索其 FirstName 包含在其他人的 FirstName 中(的子字符串)的人。

SELECT DISTINCT top 10 people.[Id], peopleName.[LastName], peopleName.[FirstName]       
    FROM [dbo].[people] people
    INNER JOIN [dbo].[people_NAME] peopleName on peopleName.[Id] = people.[Id]
    WHERE EXISTS (SELECT * 
                        FROM [dbo].[people_NAME] peopleName2 
                        WHERE peopleName2.[Id] != people.[id] 
                            AND peopleName2.[FirstName] LIKE '%' + peopleName.[FirstName] + '%')

太慢了!我知道这是因为"'%' + peopleName.[FirstName] + '%'",因为如果我用像'%G%' 这样的硬编码值替换它,它会立即运行。

根据我的动态,我的前 10 名需要 10 秒! 我希望能够在更大的数据库上运行它。

我能做什么?

【问题讨论】:

  • 在左侧使用带有通配符的LIKE 时会发生这种情况...您可以尝试在people_NAME(FirstName) 上创建索引,但很可能这不会产生真正的性能差异。
  • 这个查询和这个SELECT DISTINCT top 10 p.[Id], pn.[LastName], pn.[FirstName] FROM [dbo].[people] p INNER JOIN [dbo].[people_NAME] pn on pn.[Id] = p.[Id]是一样的
  • 内连接并存在于同一张表上?
  • 我需要找到至少包含在另一个人的名字中的名字的人。例如,如果我有 Bob 和 Bobby,则 Bob 将是一个结果。我尝试添加一些索引但没有区别:(!
  • 实际上添加硬编码值并不是加速它的原因,like '%anything(以 % 开头)会很慢。您的硬编码值很快的原因是它在扫描早期发现了 10 个匹配项。

标签: sql sql-server tsql


【解决方案1】:

看看我关于使用LIKE操作符here的回答

如果你使用一些技巧,它可能会非常高效

如果你玩排序规则,你可以提高速度,试试这个:

SELECT DISTINCT TOP 10 p.[Id], n.[LastName], n.[FirstName]       
FROM [dbo].[people] p
INNER JOIN [dbo].[people_NAME] n on n.[Id] = p.[Id]
WHERE EXISTS (
    SELECT 'x' x
    FROM [dbo].[people_NAME] n2
    WHERE n2.[Id] != p.[id]     
    AND 
        lower(n2.[FirstName]) collate latin1_general_bin 
        LIKE 
        '%' + lower(n1.[FirstName]) + '%' collate latin1_general_bin
)

如您所见,我们使用二进制比较而不是字符串比较,这样的性能要高得多。

注意,您使用的是人名,因此您可能会遇到特殊的 unicode 字符或奇怪的口音……等等……等等。

通常EXISTS 子句比INNER JOIN 好,但您也在所有列上使用DISTINCT,即GROUP BY.. 那么为什么不使用它呢?

您可以切换到INNER JOIN 并使用GROUP BY 而不是DISTINCT,因此测试COUNT(*)>1 将(非常少)比测试WHERE n2.[Id] != p.[id] 更高效,尤其是当您的TOP 子句提取许多行时.

试试这个:

SELECT TOP 10 p.[Id], n.[LastName], n.[FirstName]
FROM [dbo].[people] p
INNER JOIN [dbo].[people_NAME] n on n.[Id] = p.[Id]
INNER JOIN [dbo].[people_NAME] n2 on 
    lower(n2.[FirstName]) collate latin1_general_bin 
    LIKE 
    '%' + lower(n1.[FirstName]) + '%' collate latin1_general_bin
GROUP BY n1.[Id], n1.[FirstName]
HAVING COUNT(*)>1

这里我们也匹配名称本身,因此我们会为每个名称找到至少一个匹配项。 但我们只需要匹配其他名称的名称,因此我们将只保留匹配计数大于 1 的行(count(*)=1 表示名称仅与自身匹配)。

编辑:我使用包含 100000 行的随机名称表进行了所有测试,发现在这种情况下,LIKE 运算符的正常使用比二进制比较差大约三倍。

【讨论】:

  • 感谢您的回答,我会检查一下。与此同时,我正在关闭这篇文章。我有很多实验要做:)。
【解决方案2】:

这是一个难题。我认为全文索引不会有帮助,因为您想比较两列。

这并没有留下好的选择。一种可能性是实现 ngrams。这些是来自字符串的字符序列(例如,连续 3 个)。从我的名字,你会:

gor
ord
rdo
don

然后您可以使用这些直接匹配另一列。 那么你必须做额外的工作来查看一列的全名是否与另一列匹配。但是 ngram 应该会显着减少工作空间。

此外,实现 ngrams 需要工作。一种方法是使用触发器计算每个名称的 ngram,然后将它们插入到 ngram 表中。

我不确定所有这些工作是否值得为解决您的问题而付出努力。但是可以加快搜索速度。

【讨论】:

  • 这很有趣,但实际上,这对我来说不是一个可能的解决方案。不过谢谢。
【解决方案3】:

你可以这样做,

With CTE as
(                       
    SELECT  top 10 peopleName.[Id], peopleName.[LastName], peopleName.[FirstName]       
    FROM 
    [dbo].[people_NAME] peopleName on peopleName.[Id] = people.[Id]
    WHERE EXISTS (SELECT 1 
                        FROM [dbo].[people_NAME] peopleName2 
                        WHERE peopleName2.[Id] != people.[id] 
                            AND peopleName2.[FirstName] LIKE '%' + peopleName.[FirstName] + '%')
    order by peopleName.[Id]                    
)

//如果需要,请在此处将 CTE 与 people 表连接起来

select * from CTE

如果不需要加入people,则不需要CTE

【讨论】:

  • 感谢您的回复,您的解决方案并不比最初的更快。
【解决方案4】:

您是否尝试过 JOIN 而不是相关查询?

由于无法使用索引,它不会有最佳性能,但它应该比相关子查询好一点。

SELECT DISTINCT top 10 people.[Id], peopleName.[LastName], peopleName.[FirstName]       
FROM [dbo].[people] people
     INNER JOIN [dbo].[people_NAME] peopleName on peopleName.[Id] = people.[Id]
     INNER JOIN [dbo].[people_NAME] peopleName2 on peopleName2.[Id] <> people.[id] AND
                                                   peopleName2.[FirstName] LIKE '%' + peopleName.[FirstName] + '%'

【讨论】:

  • 感谢您的回复。我确实试过了,它甚至更慢,因为有很多匹配项。
猜你喜欢
  • 1970-01-01
  • 2013-04-15
  • 1970-01-01
  • 2017-05-26
  • 1970-01-01
  • 1970-01-01
  • 2016-09-19
相关资源
最近更新 更多