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)
);
Phone
é uma sequência estruturada de dígitos separados por vírgula, como"77777777777, 88888888888"
Email
é uma sequência de e-mails estruturados com vírgulas como"[email protected], [email protected]"
(ou sem vírgulas"[email protected]"
)Contacts1, Contacts2, Contacts3, Contacts4
sã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, Contacts4
campos.
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, Contacts4
campos de formulário livre , onde provavelmente devemos remover todos os dígitos, exceto os caracteres de espaço, antes de pesquisar.
Costumávamos usar LIKE
a pesquisa quando tínhamos cerca de 1500 registros e funcionava bem, mas agora temos muitos registros e a LIKE
pesquisa 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
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á.@gmail.com
como 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
,gmail
ecom
ou (B)user
,[email protected]
,gmail
ecom
. REF: Alterações de comportamento na pesquisa de texto completo.
.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+'%')
Respostas:
Realmente solicita
contra
'Call only at weekends +7-999-666-22-11'
econtra
'PJSC Azimuth'
fazer o trabalho como esperado .
Consulte Termo de prefixo . Porque
6662211*
não é um prefixo da+7-999-666-22-11
, bem comozimuth*
não é um prefixo deAzimuth
Quanto a
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
... 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
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
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
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)/2
símbolos para armazenar todos os sufixos - isso é complexidade quadrática ... não é bom ... mas se você já possui100 000
registros 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 de
SQL
Digamos que você tenha 2 linhas
NewCompanies
e 2 strings de texto de forma livre: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 é:
Vamos ver quantos sufixos precisamos agora:
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ãoLIKE '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 linhaNewCompanies
. E não haveria necessidade de pesquisas subseqüentes. E nós podemose acabamos com apenas 4 sufixos
Não posso dizer qual seria a complexidade do espaço nesse caso, mas parece que seria aceitável.
fonte
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.
fonte
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 :
Os pontos no email, conforme indicado pelo título, não são o principal problema. Isso, por exemplo, funciona:
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:
fonte