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)
);
Phone
est une chaîne structurée de chiffres séparés par des virgules comme"77777777777, 88888888888"
Email
est une chaîne d'e-mails structurée avec des virgules comme"[email protected], [email protected]"
(ou sans virgule du tout comme"[email protected]"
)Contacts1, Contacts2, Contacts3, Contacts4
sont 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, Contacts4
champs.
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, Contacts4
champs 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 LIKE
pour la recherche lorsque nous avions environ 1500 enregistrements et cela fonctionnait bien, mais maintenant nous avons beaucoup d'enregistrements et la LIKE
recherche 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
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.@gmail.com
que 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
,gmail
etcom
(B)user
,[email protected]
,gmail
etcom
. REF: changements de comportement à la recherche en texte.
.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+'%')
Réponses:
Demande réellement
contre
'Call only at weekends +7-999-666-22-11'
etcontre
'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-11
ainsi quezimuth*
n'est pas un préfixe deAzimuth
Pour ce qui est de
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
... 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
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
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
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)/2
symboles pour stocker tous les suffixes - c'est la complexité quadratique ... pas bon ... mais si vous avez maintenant des100 000
enregistrements 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 de
SQL
Supposons que vous ayez 2 lignes
NewCompanies
et 2 chaînes de texte de forme libre: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:
Voyons de combien de suffixes avons-nous besoin maintenant:
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 ensuiteLIKE '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 deNewCompanies
. Et il n'y aurait pas besoin de recherches ultérieures. Et nous pouvonset on se retrouve avec seulement 4 suffixes
Je ne peux pas dire quelle serait la complexité de l'espace dans ce cas, mais il semble que ce serait acceptable.
la source
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.
la source
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 :
Les points dans l'e-mail, comme indiqué par le titre, ne sont pas le problème principal. Cela, par exemple, fonctionne:
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:
la source