FTS无法正常处理带有点的电子邮件


9

我们正在将搜索作为更大系统的一部分进行开发。

我们有Microsoft SQL Server 2014 - 12.0.2000.8 (X64) Standard Edition (64-bit)这个设置:

CREATE TABLE NewCompanies(
    [Id] [uniqueidentifier] NOT NULL,
    [Name] [nvarchar](400) NOT NULL,
    [Phone] [nvarchar](max) NULL,
    [Email] [nvarchar](max) NULL,
    [Contacts1] [nvarchar](max) NULL,
    [Contacts2] [nvarchar](max) NULL,
    [Contacts3] [nvarchar](max) NULL,
    [Contacts4] [nvarchar](max) NULL,
    [Address] [nvarchar](max) NULL,
    CONSTRAINT PK_Id PRIMARY KEY (Id)
);
  1. Phone 是用逗号分隔的结构化数字字符串,例如 "77777777777, 88888888888"
  2. Email是结构化的电子邮件字符串,带有类似逗号 "email1@gmail.com, email2@gmail.com"(或根本没有逗号 "email1@gmail.com"
  3. Contacts1, Contacts2, Contacts3, Contacts4是文本字段,用户可以在其中以自由格式指定联系人详细信息。喜欢"John Smith +1 202 555 0156""Bob, +1-999-888-0156, bob@company.com"。这些字段可以包含我们要进一步搜索的电子邮件和电话。

在这里我们创建全文

-- FULL TEXT SEARCH
CREATE FULLTEXT CATALOG NewCompanySearch AS DEFAULT;  
CREATE FULLTEXT INDEX ON NewCompanies(Name, Phone, Email, Contacts1, Contacts2, Contacts3, Contacts4, Address)
KEY INDEX PK_Id

这是一个数据样本

INSERT INTO NewCompanies(Id, Name, Phone, Email, Contacts1, Contacts2, Contacts3, Contacts4) 
VALUES ('7BA05F18-1337-4AFB-80D9-00001A777E4F', 'PJSC Azimuth', '79001002030, 78005005044', 'regular@hotmail.com, s.m.s@gmail.com', 'John Smith', 'Call only at weekends +7-999-666-22-11', NULL, NULL)

实际上,我们大约有10万条这样的记录。

我们希望用户可以指定电子邮件的一部分,例如“ @ gmail.com”,并且这应返回任何包含Gmail电子邮件地址的行。 Email, Contacts1, Contacts2, Contacts3, Contacts4字段中。

电话号码也一样。用户可以搜索“ 70283”之类的模式,查询应返回其中包含这些数字的电话。甚至对于自由格式的Contacts1, Contacts2, Contacts3, Contacts4字段,在搜索之前,我们可能应该首先删除除数字和空格字符之外的所有字符。

LIKE当我们有大约1500条记录时,我们曾经使用它进行搜索,但是效果很好,但是现在我们有很多记录,并且LIKE搜索需要无限次才能获得结果。

这是我们尝试从那里获取数据的方式:

SELECT * FROM NewCompanies WHERE CONTAINS((Email, Contacts1, Contacts2, Contacts3, Contacts4), '"s.m.s@gmail.com*"') -- this doesn't get the row
SELECT * FROM NewCompanies WHERE CONTAINS((Phone, Contacts1, Contacts2, Contacts3, Contacts4), '"6662211*"') -- doesn't get anything
SELECT * FROM NewCompanies WHERE CONTAINS(Name, '"zimuth*"') -- doesn't get anything

5
为什么您的所有专栏都在nvarchar(MAX)这里?我从未听说过或遇到过一个名字长10亿个字符的人。并且,根据此答案,电子邮件地址不能超过254个字符;所以那里也有10亿个〜浪费的字符。
拉努

2
听起来您正在与全文搜索的分词系统作斗争。您不太可能找到@gmail.com用作搜索字词的内容,因为该@字符是断字器。换句话说,这取决于SQL Server的版本你有,在索引字user@gmail.com将是(A) usergmail以及com或(B) ,,user 和。REF:全文搜索的行为更改user@gmail.comgmailcom
AlwaysLearning

1
“但是我不想在这些字段中搜索电子邮件和电话以外的任何内容”,然后将它们存储在适当的列中,就像我之前说的那样。您有该数据的列,应将其标准化。分词器在实例/数据库级别设置。因此,删除将是一个重大的重大更改.
拉努

1
您可能想将所有电话,电子邮件等记录的表标准化为1-M。第二种选择是将列拆分(使用string_split(email,','),与外部应用结合使用。指定用户可以接收的电子邮件数量的理论上限,然后进行如下搜索:SELECT * FROM NewCompanies WHERE Id IN (SELECT ID from .... where MyOuterApply.EmailCol1 LIKE '%'+@SearchString+'%') OR Id IN (SELECT ID from .... where MyOuterApply.EmailCol2 LIKE '%'+@SearchString+'%')。在每个字段上创建大约五个单独的索引并包括主键
starbyone

2
@TheDudeWithHat不参加,并不意味着不应该。OP出现问题的原因是由于缺乏标准化。
Larnu

Answers:


2

实际要求

SELECT [...] CONTAINS([...],'“ 6662211 *”')-什么也没得到

反对'Call only at weekends +7-999-666-22-11'

SELECT [...] CONTAINS(Name,'“ zimuth *”')-什么也没得到

反对 'PJSC Azimuth'

预期的工作
请参阅前缀术语。因为6662211*不是一个前缀+7-999-666-22-11,以及zimuth*是不是一个前缀Azimuth

至于

选择[...] CONTAINS([...],'“ sms@gmail.com*”')-这不会得到该行

这可能是由于注释中的Alwayslearning经常出现断字现象。见断字

我认为全文搜索不适用于您的任务。

为什么要在与LIKE运算符完全相同的任务中使用FTS?如果对于LIKE查询有更好的索引类型...那么将有更好的索引类型,而不是完全不同的技术和语法。
并且绝不会帮助您与"6662211*"“ 666 一些任意字符 22 一些任意字符 11” 匹配。
全文搜索与正则表达式无关(并且"6662211*"无关(甚至不是该工作的正确表达-与“任意字符”部分无关),它与同义词,词形等有关。

但是,是否有可能有效地搜索子字符串?

是的。除了编写自己的搜索引擎这样的前景外,我们还能做SQL什么?

首先-必须清理您的数据!如果您想向用户返回他们输入的确切字符串

用户可以自由指定联系方式

...您可以按原样保存它们...并保留它们。
然后,您需要从自由格式的文本中提取数据(对于电子邮件和电话号码来说并不难),然后以某种规范的形式保存数据。对于电子邮件,您真正需要做的唯一一件事-将它们全部转换为小写或大写(没关系),然后拆分然后再@唱歌。但是在电话号码中,您只需要保留数字
(...然后您甚至可以将它们存储为数字。这样可以节省一些空间和时间。但是搜索将有所不同...现在,让我们深入探讨一下更简单的方法和使用字符串的通用解决方案。)

正如MatthewBaker所提到的,您可以创建一个后缀表。然后你可以像这样搜索

SELECT DISTINCT * FROM NewCompanies JOIN Sufficies ON NewCompanies.Id = Sufficies.Id WHERE Sufficies.sufficies LIKE 'some text%'

%只能将通配符放在最后。否则后缀表将没有任何好处。

以电话号码为例

+ 7-999-666-22-11

在我们清除掉多余的字符后,它将有11位数字。这意味着一个电话号码需要11个后缀

           1
          11
         211
        2211
       62211
      662211
     6662211
    96662211
   996662211
  9996662211
 79996662211

因此,此解决方案的空间复杂度是线性的……我想还不错…… 但是请耐心等待记录数量的复杂性。但是在符号中...我们需要N(N+1)/2符号来存储所有后缀-这是二次复杂性...不好...但是如果您现在有了100 000记录并且在不久的将来没有数百万的计划-您可以使用它解。

我们可以降低空间复杂度吗?

我只会描述这个想法,要实现它需要一些努力。可能我们需要跨越SQL

假设您有2行,NewCompanies其中包含2个自由格式文本字符串:

    aaaaa
    11111

后缀表应该有多大?显然,我们只需要2条记录。

让我们再举一个例子。还要搜索2行,2个自由文本字符串。但是现在是:

    aa11aa
    cc11cc

让我们看看我们现在需要多少个后缀:

         a // no need, LIKE `a%`  will match against 'aa' and 'a11aa' and 'aa11aa'
        aa // no need, LIKE `aa%` will match against 'aa11aa'
       1aa
      11aa
     a11aa
    aa11aa
         c // no need, LIKE `c%`  will match against 'cc' and 'c11cc' and 'cc11cc'
        cc // no need, LIKE `cc%` will match against 'cc11cc'
       1cc
      11cc
     c11cc
    cc11cc

没那么糟,但也没有那么好。

我们还能做什么?

假设用户输入"c11"了搜索字段。然后LIKE 'c11%'需要' c11 cc'后缀才能成功。但是,如果不是"c11"先搜索"c%",而是先搜索,然后搜索"c1%"等等?第一次搜索将提供来自的一行NewCompanies。并且将不需要后续搜索。我们可以

       1aa // drop this as well, because LIKE '1%' matches '11aa'
      11aa
     a11aa // drop this as well, because LIKE 'a%' matches 'aa11aa'
    aa11aa
       1cc // same here
      11cc
     c11cc // same here
    cc11cc

最后只有四个后缀

      11aa
    aa11aa
      11cc
    cc11cc

我不能说这种情况下的空间复杂度如何,但是感觉可以接受。


1

在这种情况下,全文搜索不理想。我和你在同一条船上。就像搜索太慢一样,全文搜索将搜索以术语开头而不是包含术语的单词。

我们尝试了几种解决方案,其中一个纯SQL选项是构建您自己的全文搜索版本,尤其是反向索引搜索。我们尝试了一下,但成功了,但占用了很多空间。我们为部分搜索项创建了一个辅助保存表,并在其上使用了全文索引。但是,这意味着我们重复存储同一事物的多个副本。例如,我们将“ longword”存储为Longword,ongword,ngword,gword ...等。因此,任何包含的短语始终位于索引词的开头。可怕的解决方案,充满缺陷,但它确实有效。

然后,我们考虑托管一个单独的服务器以进行查找。谷歌搜索Lucene和elastisearch将为您提供有关这些现成软件包的良好信息。

最终,我们开发了自己的内部搜索引擎,该引擎沿SQL端运行。这使我们能够实施语音搜索(双音素),然后在旁音伴奏中使用levenshtein计算来建立相关性。对于许多解决方案来说,它们是过分杀伤力的,但是在我们的用例中值得付出努力。现在,我们甚至可以选择利用Nvidia GPU进行cuda搜索,但这代表了全新的头痛和不眠之夜。所有这些的相关性将取决于您看到搜索执行的频率以及需要它们进行反应的程度。


1

全文索引有许多限制。您可以对索引找到的完整单词“部分”使用通配符,但即使那样,您也只能使用单词的结尾部分。这就是为什么您可以使用CONTAINS(Name, '"Azimut*"')但不能使用的原因CONTAINS(Name, '"zimuth*"')

从Microsoft 文档

当前缀术语是短语时,组成该短语的每个标记都被视为一个单独的前缀术语。将返回所有具有以前缀开头的单词的行。例如,前缀术语“轻面包*”将找到带有“轻面包”,“轻面包”或“轻面包”文本的行,但不会返回“轻面包”。

电子邮件中的点(如标题所示)不是主要问题。例如,这可以工作:

SELECT * FROM NewCompanies 
WHERE CONTAINS((Email, Contacts1, Contacts2, Contacts3, Contacts4), 's.m.s@gmail.com') 

在这种情况下,索引会将整个电子邮件字符串以及“ gmail”和“ gmail.com”标识为有效。仅“ sms”无效。

最后一个例子是相似的。电话号码的各个部分都已编入索引(例如666-22-11和999-666-22-11),但是删除连字符不是索引将要知道的字符串。否则,这确实可行:

SELECT * FROM NewCompanies 
WHERE CONTAINS((Phone, Contacts1, Contacts2, Contacts3, Contacts4), '"666-22-11*"')
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.