FTS ne fonctionne pas comme prévu avec les e-mails avec des points

9

Nous développons une recherche dans le cadre d'un système plus vaste.

Nous avons Microsoft SQL Server 2014 - 12.0.2000.8 (X64) Standard Edition (64-bit)avec cette configuration:

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 est une chaîne structurée de chiffres séparés par des virgules comme "77777777777, 88888888888"
  2. Emailest une chaîne d'e-mails structurée avec des virgules comme "[email protected], [email protected]"(ou sans virgule du tout comme "[email protected]")
  3. Contacts1, Contacts2, Contacts3, Contacts4sont des champs de texte où les utilisateurs peuvent spécifier leurs coordonnées sous forme libre. Comme "John Smith +1 202 555 0156"ou "Bob, +1-999-888-0156, [email protected]". Ces champs peuvent contenir des e-mails et des téléphones que nous souhaitons rechercher davantage.

Ici, nous créons des trucs en texte intégral

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

Voici un échantillon de données

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)

En fait, nous avons environ 100 milliers de ces enregistrements.

Nous nous attendons à ce que les utilisateurs puissent spécifier une partie de l'e-mail comme "@ gmail.com" et cela devrait renvoyer toutes les lignes avec les adresses e-mail Gmail dans l'un des Email, Contacts1, Contacts2, Contacts3, Contacts4champs.

Idem pour les numéros de téléphone. Les utilisateurs peuvent rechercher un modèle comme "70283" et une requête doit renvoyer les téléphones contenant ces chiffres. C'est même pour les Contacts1, Contacts2, Contacts3, Contacts4champs de formulaire libre où nous devrions probablement supprimer tout sauf les chiffres et les espaces d'abord avant de rechercher.

Nous avions l'habitude d'utiliser LIKEpour la recherche lorsque nous avions environ 1500 enregistrements et cela fonctionnait bien, mais maintenant nous avons beaucoup d'enregistrements et la LIKErecherche prend une infinité pour obtenir des résultats.

Voici comment nous essayons d'obtenir des données à partir de là:

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
la source
5
Pourquoi toutes vos colonnes sont-elles nvarchar(MAX)ici? Je n'ai jamais entendu parler ou rencontré quelqu'un dont le nom contient 1 milliard ~ caractères. Et, selon cette réponse , une adresse e-mail ne peut pas contenir plus de 254 caractères; vous avez donc également 1 milliard ~ de personnages perdus.
Larnu
2
On dirait que vous vous battez avec les séparateurs de mots de la recherche en texte intégral. Il est peu probable que vous trouviez quoi @gmail.comque ce soit en tant que terme de recherche, car le @personnage est un séparateur de mots. En d' autres termes, selon la version de SQL Server que vous avez, mots l'indice [email protected]seront soit (A) user, gmailet com(B) user, [email protected], gmailet com. REF: changements de comportement à la recherche en texte
intégral
1
"mais je ne veux pas chercher autre chose que des e-mails et des téléphones dans ces champs" alors ils devraient être stockés dans une colonne appropriée, comme je l'ai dit précédemment. Vous avez des colonnes pour ces données, qui devraient être normalisées. Les séparateurs de mots sont définis au niveau de l'instance / de la base de données. ce serait donc un changement de rupture important à supprimer ..
Larnu
1
Vous souhaitez soit normaliser les tableaux à 1-M pour tous les enregistrements de téléphone, e-mail, etc. La deuxième option consiste à diviser les colonnes (utilisez string_split (e-mail, ','), en combinaison avec Outer Apply. Vous devez spécifiez une limite théorique sur le nombre d'e-mails qu'un utilisateur peut avoir. Ensuite, écrivez une recherche comme celleSELECT * FROM NewCompanies WHERE Id IN (SELECT ID from .... where MyOuterApply.EmailCol1 LIKE '%'+@SearchString+'%') OR Id IN (SELECT ID from .... where MyOuterApply.EmailCol2 LIKE '%'+@SearchString+'%')
:.
2
@TheDudeWithHat Ne va pas, ne signifie pas que cela ne devrait pas. La raison pour laquelle le PO rencontre le problème, c'est à cause du manque de normalisation.
Larnu

Réponses:

2

Demande réellement

SELECT [...] CONTAINS ([...], '"6662211 *"') - n'obtient rien

contre 'Call only at weekends +7-999-666-22-11' et

SELECT [...] CONTAINS (Name, '"zimuth *"') - n'obtient rien

contre 'PJSC Azimuth'

faire le travail comme prévu .
Voir Terme de préfixe . Parce que 6662211*n'est pas un préfixe de +7-999-666-22-11ainsi que zimuth*n'est pas un préfixe deAzimuth

Pour ce qui est de

SELECT [...] CONTAINS ([...], '"[email protected]*"') - ceci n'obtient pas la ligne

Cela est probablement dû à des briseurs de mots, comme toujours indiqué dans les commentaires. Voir les briseurs de mots

Je ne pense pas que la recherche en texte intégral soit applicable à votre tâche.

Pourquoi utiliser pour FTS dans les mêmes tâches que pour lesquelles l'opérateur LIKE est utilisé? S'il y avait un meilleur type d'index pour les requêtes LIKE ... alors il y aurait le meilleur type d'index , pas la technologie et la syntaxe totalement différentes.
Et en aucun cas cela ne vous aidera à faire la comparaison "6662211*"avec "666 certains caractères arbitraires 22 certains caractères arbitraires 11".
La recherche en texte intégral ne concerne pas les expressions rationnelles (et "6662211*"n'est même pas une expression correcte pour le travail - il n'y a rien sur la partie "certains caractères arbitraires"), elle concerne les synonymes, les formes de mots, etc.

Mais est-il possible de rechercher efficacement des sous-chaînes?

Oui, ça l'est. Laissant de côté des perspectives telles que la rédaction de votre propre moteur de recherche, que pouvons-nous faire à l'intérieur SQL?

Tout d'abord - il est impératif de nettoyer vos données! Si vous souhaitez rendre aux utilisateurs les chaînes exactes qu'ils ont entrées

les utilisateurs peuvent spécifier leurs coordonnées sous forme libre

... vous pouvez les enregistrer tels quels ... et les laisser.
Ensuite, vous devez extraire les données du texte du formulaire libre (ce n'est pas si difficile pour les e-mails et les numéros de téléphone) et enregistrer les données sous une forme canonique. Pour les e-mails, la seule chose que vous devez vraiment faire - les mettre tous en minuscules ou en majuscules (peu importe), et peut-être les diviser ensuite en @chantant. Mais dans les numéros de téléphone, vous ne devez laisser que des chiffres
(... Et puis vous pouvez même les stocker sous forme de numéros . Cela peut vous faire économiser de l'espace et du temps. Mais la recherche sera différente ... Pour l'instant, plongons dans une approche plus simple et solution universelle utilisant des chaînes.)

Comme MatthewBaker l'a mentionné, vous pouvez créer une table de suffixes. Ensuite, vous pouvez rechercher comme ça

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

Vous ne devez placer le caractère générique %qu'à la fin . Ou il n'y aurait aucun avantage de la table des suffixes.

Prenons par exemple un numéro de téléphone

+ 7-999-666-22-11

Une fois que nous nous serons débarrassés des déchets, il contiendra 11 chiffres. Cela signifie que nous aurons besoin de 11 suffixes pour un numéro de téléphone

           1
          11
         211
        2211
       62211
      662211
     6662211
    96662211
   996662211
  9996662211
 79996662211

Donc, la complexité de l'espace pour cette solution est linéaire ... pas si mal, je dirais ... Mais attendez, c'est la complexité du nombre d'enregistrements. Mais dans les symboles ... nous avons besoin de N(N+1)/2symboles pour stocker tous les suffixes - c'est la complexité quadratique ... pas bon ... mais si vous avez maintenant des 100 000enregistrements et que vous n'avez pas de plans pour des millions dans un avenir proche - vous pouvez aller avec cela Solution.

Pouvons-nous réduire la complexité de l'espace?

Je ne décrirai que l'idée, sa mise en œuvre prendra un certain effort. Et nous devrons probablement traverser les frontières deSQL

Supposons que vous ayez 2 lignes NewCompanieset 2 chaînes de texte de forme libre:

    aaaaa
    11111

Quelle doit être la taille de la table des suffixes? Évidemment, nous n'avons besoin que de 2 enregistrements.

Prenons un autre exemple. Aussi 2 lignes, 2 chaînes de texte libres à rechercher. Mais maintenant c'est:

    aa11aa
    cc11cc

Voyons de combien de suffixes avons-nous besoin maintenant:

         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

Pas si mal, mais pas si bien non plus.

Que pouvons-nous faire d'autre?

Disons que l'utilisateur entre "c11"dans le champ de recherche. A ensuite LIKE 'c11%'besoin du suffixe « c11 cc» pour réussir. Mais si au lieu de chercher, "c11"nous cherchons d'abord "c%", alors "c1%"et ainsi de suite? La première recherche donnera une seule ligne de NewCompanies. Et il n'y aurait pas besoin de recherches ultérieures. Et nous pouvons

       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

et on se retrouve avec seulement 4 suffixes

      11aa
    aa11aa
      11cc
    cc11cc

Je ne peux pas dire quelle serait la complexité de l'espace dans ce cas, mais il semble que ce serait acceptable.

x00
la source
1

Dans des cas comme celui-ci, la recherche en texte intégral n'est pas idéale. J'étais dans le même bateau que toi. Comme les recherches sont trop lentes, et les recherches en texte intégral recherchent des mots commençant par un terme plutôt que contenant un terme.

Nous avons essayé plusieurs solutions, une option SQL pure est de construire votre propre version de recherche en texte intégral, en particulier une recherche d'index inversé. Nous avons essayé cela, et cela a réussi, mais a pris beaucoup de place. Nous avons créé une table d'attente secondaire pour les termes de recherche partielle et utilisé l'indexation en texte intégral à ce sujet. Cependant, cela signifie que nous avons stocké à plusieurs reprises plusieurs copies de la même chose. Par exemple, nous avons stocké "longword" comme Longword, ongword, ngword, gword .... etc. Ainsi, toute phrase contenue serait toujours au début du terme indexé. Une solution horrible, pleine de défauts, mais cela a fonctionné.

Nous avons ensuite envisagé d'héberger un serveur distinct pour les recherches. Googler Lucene et elastisearch vous donneront de bonnes informations sur ces packages prêts à l'emploi.

Finalement, nous avons développé notre propre moteur de recherche interne, qui fonctionne avec SQL. Cela nous a permis d'implémenter des recherches phonétiques (double métaphone) puis d'utiliser des calculs de levenshtein le long de soundex pour établir la pertinence. Trop pour beaucoup de solutions, mais en vaut la peine dans notre cas d'utilisation. Nous avons même maintenant la possibilité d'utiliser les GPU Nvidia pour les recherches de cuda, mais cela représentait un tout nouvel ensemble de maux de tête et de nuits blanches. La pertinence de tout cela dépendra de la fréquence à laquelle vous voyez vos recherches effectuées et de la réactivité dont vous avez besoin.

Matthew Baker
la source
1

Les index de recherche en texte intégral ont un certain nombre de limitations. Vous pouvez utiliser des caractères génériques sur les mots que l'index trouve sont des «parties» entières, mais même alors, vous êtes contraint à la partie finale du mot. C'est pourquoi vous pouvez utiliser CONTAINS(Name, '"Azimut*"')mais pasCONTAINS(Name, '"zimuth*"')

De la documentation Microsoft :

Lorsque le terme préfixe est une phrase, chaque jeton constituant la phrase est considéré comme un terme préfixe distinct. Toutes les lignes dont les mots commencent par les termes préfixes seront renvoyées. Par exemple, le terme préfixe "pain léger *" trouvera des lignes avec le texte de "pain léger", "légèrement pané" ou "pain léger", mais il ne renverra pas "pain légèrement grillé".

Les points dans l'e-mail, comme indiqué par le titre, ne sont pas le problème principal. Cela, par exemple, fonctionne:

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

Dans ce cas, l'index identifie la chaîne de messagerie entière comme valide, ainsi que «gmail» et «gmail.com». Mais "sms" n'est pas valide.

Le dernier exemple est similaire. Les parties du numéro de téléphone sont indexées (666-22-11 et 999-666-22-11 par exemple), mais la suppression des tirets n'est pas une chaîne que l'index va connaître. Sinon, cela fonctionne:

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