O STF não funciona conforme o esperado com emails com pontos

9

Estamos desenvolvendo uma pesquisa como parte de um sistema maior.

Temos Microsoft SQL Server 2014 - 12.0.2000.8 (X64) Standard Edition (64-bit)com esta configuração:

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 é uma sequência estruturada de dígitos separados por vírgula, como "77777777777, 88888888888"
  2. Emailé uma sequência de e-mails estruturados com vírgulas como "[email protected], [email protected]"(ou sem vírgulas "[email protected]")
  3. Contacts1, Contacts2, Contacts3, Contacts4são campos de texto em que os usuários podem especificar detalhes de contato de forma livre. Como "John Smith +1 202 555 0156"ou "Bob, +1-999-888-0156, [email protected]". Esses campos podem conter e-mails e telefones que desejamos pesquisar mais.

Aqui criamos material de texto completo

-- 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

Aqui está uma amostra de dados

INSERT INTO NewCompanies(Id, Name, Phone, Email, Contacts1, Contacts2, Contacts3, Contacts4) 
VALUES ('7BA05F18-1337-4AFB-80D9-00001A777E4F', 'PJSC Azimuth', '79001002030, 78005005044', '[email protected], [email protected]', 'John Smith', 'Call only at weekends +7-999-666-22-11', NULL, NULL)

Na verdade, temos cerca de 100 milhares desses registros.

Esperamos que os usuários possam especificar uma parte do email como "@ gmail.com" e isso retorne todas as linhas com endereços de email do Gmail em qualquer um dos Email, Contacts1, Contacts2, Contacts3, Contacts4campos.

O mesmo para números de telefone. Os usuários podem procurar um padrão como "70283" e uma consulta deve retornar telefones com esses dígitos. É mesmo para Contacts1, Contacts2, Contacts3, Contacts4campos de formulário livre , onde provavelmente devemos remover todos os dígitos, exceto os caracteres de espaço, antes de pesquisar.

Costumávamos usar LIKEa pesquisa quando tínhamos cerca de 1500 registros e funcionava bem, mas agora temos muitos registros e a LIKEpesquisa leva infinitos para obter resultados.

É assim que tentamos obter dados a partir daí:

SELECT * FROM NewCompanies WHERE CONTAINS((Email, Contacts1, Contacts2, Contacts3, Contacts4), '"[email protected]*"') -- 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
Kseen
fonte
5
Por que todas as suas colunas estão nvarchar(MAX)aqui? Eu nunca ouvi falar ou conheci alguém com o nome de 1 bilhão de caracteres. E, de acordo com esta resposta , um endereço de email não pode ter mais de 254 caracteres; então você também tem 1 bilhão de caracteres desperdiçados por lá.
Larnu 18/02
2
Parece que você está lutando com os separadores de palavras da pesquisa de texto completo. É improvável que você encontre algo usando @gmail.comcomo termo de pesquisa porque o @caractere é um separador de palavras. Em outras palavras, dependendo da versão do SQL Server que você tem, palavras no índice para [email protected]vai ser (A) user, gmaile comou (B) user, [email protected], gmaile com. REF: Alterações de comportamento na pesquisa de texto completo
AlwaysLearning
11
"mas eu não quero procurar nada além de e-mails e telefones nesses campos" , eles devem ser armazenados em uma coluna apropriada, como eu disse anteriormente. Você tem colunas para esses dados, que devem ser normalizados. Os separadores de palavras são definidos no nível da instância / banco de dados. portanto, seria uma alteração significativa para remover ..
Larnu 18/02
11
Você deseja normalizar as tabelas para 1-M para todos os registros de telefone, email etc. A segunda opção é dividir as colunas (use string_split (email, ','), em combinação com o Apply Externo. especifique um limite teórico para o número de e-mails que um usuário pode 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+'%')
ter.Em
2
@TheDudeWithHat Não vai, não significa que não deveria. A razão pela qual o OP está tendo o problema é devido à falta de normalização.
Larnu 24/02

Respostas:

2

Realmente solicita

SELECT CONTAINS ([...], '"6662211 *"') - não recebe nada

contra 'Call only at weekends +7-999-666-22-11' e

SELECT [...] CONTAINS (Nome, '"zimuth *"') - não recebe nada

contra 'PJSC Azimuth'

fazer o trabalho como esperado .
Consulte Termo de prefixo . Porque 6662211*não é um prefixo da +7-999-666-22-11, bem como zimuth*não é um prefixo deAzimuth

Quanto a

SELECT [...] CONTAINS ([...], '"[email protected]*"') - isso não aparece na linha

Provavelmente, isso ocorre devido à quebra de palavras, como o aprendizado sempre apontado nos comentários. Veja quebra-palavras

Não acho que a pesquisa de texto completo seja aplicável à sua tarefa.

Por que usar o STF nas mesmas tarefas exatamente para as quais o operador LIKE é usado? Se houvesse um melhor tipo de índice para consultas LIKE ... então haveria o melhor tipo de índice , não a tecnologia e a sintaxe totalmente diferentes.
E de nenhuma maneira isso ajudará você a comparar "6662211*"com "666 alguns caracteres arbitrários 22 outros caracteres arbitrários 11".
A pesquisa de texto completo não é sobre expressões regulares (e "6662211*"nem sequer é uma expressão correta para o trabalho - não há nada sobre a parte "algum caracter arbitrário"), é sobre sinônimos, formas de palavras etc.

Mas é possível procurar substrings de maneira eficaz?

Sim, ele é. Deixando de lado perspectivas como escrever seu próprio mecanismo de pesquisa, o que podemos fazer dentro SQL?

Primeiro de tudo - é imperativo limpar seus dados! Se você deseja retornar aos usuários as strings exatas que eles inseriram

os usuários podem especificar detalhes de contato de forma livre

... você pode salvá-los como estão ... e deixá-los junto.
Então você precisa extrair dados do texto de formulário livre (não é tão difícil para emails e números de telefone) e salvar os dados de alguma forma canônica. Para o e-mail, a única coisa que você realmente precisa fazer - torná-las todas em minúsculas ou maiúsculas (não importa), e talvez dividir em seguida no @canto. Mas nos números de telefone você precisa deixar apenas dígitos
(... E então você pode até armazená-los como números . Isso pode economizar um pouco de espaço e tempo. Mas a pesquisa será diferente ... Por enquanto, vamos nos aprofundar em um assunto mais simples e solução universal usando cadeias.)

Como MatthewBaker mencionou, você pode criar uma tabela de sufixos. Então você pode pesquisar assim

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

Você deve colocar o curinga %apenas no final . Ou não haveria benefícios na tabela Sufixos.

Vamos pegar, por exemplo, um número de telefone

+ 7-999-666-22-11

Depois de nos livrarmos dos resíduos de caracteres, ele terá 11 dígitos. Isso significa que precisaremos de 11 sufixos para um número de telefone

           1
          11
         211
        2211
       62211
      662211
     6662211
    96662211
   996662211
  9996662211
 79996662211

Portanto, a complexidade do espaço para esta solução é linear ... não é tão ruim, eu diria ... Mas espere , é complexidade no número de registros. Mas em símbolos ... precisamos de N(N+1)/2símbolos para armazenar todos os sufixos - isso é complexidade quadrática ... não é bom ... mas se você já possui 100 000registros e não tem planos para milhões em um futuro próximo - pode seguir com isso solução.

Podemos reduzir a complexidade do espaço?

Descreverei apenas a ideia, implementá-la exigirá algum esforço. E provavelmente precisaremos cruzar as fronteiras deSQL

Digamos que você tenha 2 linhas NewCompaniese 2 strings de texto de forma livre:

    aaaaa
    11111

Qual deve ser o tamanho da tabela de sufixos? Obviamente, precisamos de apenas 2 registros.

Vamos dar outro exemplo. Também 2 linhas, 2 cadeias de texto livre para pesquisar. Mas agora é:

    aa11aa
    cc11cc

Vamos ver quantos sufixos precisamos agora:

         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

Não é tão ruim, mas também não é tão bom.

O que mais podemos fazer?

Digamos, o usuário entra "c11"no campo de pesquisa. Então LIKE 'c11%'precisa do sufixo ' c11 cc' para ter sucesso. Mas se, em vez de procurar "c11", primeiro procuramos "c%", então "c1%"e assim por diante? A primeira pesquisa fornecerá apenas uma linha NewCompanies. E não haveria necessidade de pesquisas subseqüentes. E nós podemos

       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

e acabamos com apenas 4 sufixos

      11aa
    aa11aa
      11cc
    cc11cc

Não posso dizer qual seria a complexidade do espaço nesse caso, mas parece que seria aceitável.

x00
fonte
1

Em casos como esse, a pesquisa de texto completo é inferior ao ideal. Eu estava no mesmo barco que você. As pesquisas semelhantes são muito lentas e as pesquisas de texto completo pesquisam palavras que começam com um termo e não contêm um termo.

Tentamos várias soluções, uma opção pura do SQL é criar sua própria versão da pesquisa de texto completo, em particular uma pesquisa de índice invertida. Tentamos isso e foi bem-sucedido, mas ocupou muito espaço. Criamos uma tabela de retenção secundária para termos de pesquisa parciais e usamos a indexação de texto completo. No entanto, isso significa que repetidamente armazenamos várias cópias da mesma coisa. Por exemplo, armazenamos "longword" como Longword, ongword, ngword, gword .... etc. Portanto, qualquer frase contida sempre estará no início do termo indexado. Uma solução horrenda, cheia de falhas, mas funcionou.

Em seguida, analisamos a hospedagem de um servidor separado para pesquisas. Pesquisando no Lucene e na elastisearch, você obterá boas informações sobre esses pacotes prontos para uso.

Eventualmente, desenvolvemos nosso próprio mecanismo de pesquisa interno, que roda ao lado do SQL. Isso nos permitiu implementar pesquisas fonéticas (metafone duplo) e, em seguida, usar os cálculos de levenshtein ao longo do soundex lateral para estabelecer relevância. Exagere em muitas soluções, mas vale o esforço no nosso caso de uso. Até agora temos a opção de alavancar as GPUs da Nvidia para buscas cuda, mas isso representa um novo conjunto de dores de cabeça e noites sem dormir. A relevância de tudo isso dependerá da frequência com que você vê suas pesquisas sendo executadas e da reatividade necessária.

Matthew Baker
fonte
1

Os índices de texto completo têm várias limitações. Você pode usar curingas em palavras que o índice encontra como "partes" inteiras, mas mesmo assim você fica restrito à parte final da palavra. É por isso que você pode usar, CONTAINS(Name, '"Azimut*"')mas nãoCONTAINS(Name, '"zimuth*"')

Na documentação da Microsoft :

Quando o termo do prefixo é uma frase, cada token que compõe a frase é considerado um termo de prefixo separado. Todas as linhas com palavras que começam com os termos do prefixo serão retornadas. Por exemplo, o prefixo "pão leve *" encontrará linhas com o texto "empanado leve", "empanado leve" ou "pão leve", mas não retornará "pão torrado levemente".

Os pontos no email, conforme indicado pelo título, não são o principal problema. Isso, por exemplo, funciona:

SELECT * FROM NewCompanies 
WHERE CONTAINS((Email, Contacts1, Contacts2, Contacts3, Contacts4), '[email protected]') 

Nesse caso, o índice identifica toda a cadeia de email como válida, além de "gmail" e "gmail.com". Apenas "sms", porém, não é válido.

O último exemplo é semelhante. As partes do número de telefone são indexadas (666-22-11 e 999-666-22-11, por exemplo), mas remover os hífens não é uma sequência que o índice conhecerá. Caso contrário, isso funciona:

SELECT * FROM NewCompanies 
WHERE CONTAINS((Phone, Contacts1, Contacts2, Contacts3, Contacts4), '"666-22-11*"')
smoore4
fonte