FTS funktioniert nicht wie erwartet mit E-Mails mit Punkten

9

Wir entwickeln eine Suche als Teil eines größeren Systems.

Wir haben Microsoft SQL Server 2014 - 12.0.2000.8 (X64) Standard Edition (64-bit)mit diesem Setup:

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 ist eine strukturierte durch Kommas getrennte Ziffernfolge wie "77777777777, 88888888888"
  2. Emailist eine strukturierte E-Mail-Zeichenfolge mit Kommas wie "[email protected], [email protected]"(oder ohne Kommas wie "[email protected]")
  3. Contacts1, Contacts2, Contacts3, Contacts4sind Textfelder, in denen Benutzer Kontaktdaten in freier Form angeben können. Wie "John Smith +1 202 555 0156"oder "Bob, +1-999-888-0156, [email protected]". Diese Felder können E-Mails und Telefone enthalten, nach denen wir weiter suchen möchten.

Hier erstellen wir Volltextmaterial

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

Hier ist ein Datenbeispiel

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)

Tatsächlich haben wir ungefähr 100.000 solcher Aufzeichnungen.

Wir erwarten, dass Benutzer einen Teil der E-Mail wie "@ gmail.com" angeben können. Dies sollte alle Zeilen mit Google Mail-E-Mail-Adressen in einem der Email, Contacts1, Contacts2, Contacts3, Contacts4Felder zurückgeben.

Gleiches gilt für Telefonnummern. Benutzer können nach einem Muster wie "70283" suchen, und eine Abfrage sollte Telefone mit diesen Ziffern zurückgeben. Es ist sogar für Freiformfelder, Contacts1, Contacts2, Contacts3, Contacts4in denen wir wahrscheinlich zuerst alle außer Ziffern und Leerzeichen entfernen sollten, bevor wir suchen.

Früher haben LIKEwir für die Suche verwendet, als wir ungefähr 1500 Datensätze hatten und es hat gut funktioniert, aber jetzt haben wir viele Datensätze und die LIKESuche dauert unendlich, um Ergebnisse zu erhalten.

So versuchen wir, Daten von dort zu erhalten:

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
quelle
5
Warum sind alle Ihre Spalten nvarchar(MAX)hier? Ich habe noch nie von jemandem gehört oder jemanden getroffen, dessen Name 1 Milliarde Zeichen lang ist. Laut dieser Antwort darf eine E-Mail-Adresse nicht länger als 254 Zeichen sein. Sie haben dort also auch 1 Milliarde ~ verschwendete Charaktere.
Larnu
2
Klingt so, als würden Sie mit den Wortbrechern der Volltextsuche kämpfen. Es ist unwahrscheinlich, dass Sie etwas finden, das @gmail.comals Suchbegriff verwendet wird, da der @Charakter ein Wortbrecher ist. Mit anderen Worten, je die Version von SQL Server Sie haben, Worte im Index für [email protected]werden entweder (A) user, gmailund comoder (B) user, [email protected], gmailund com. REF: Verhaltensänderungen bei der Volltextsuche
AlwaysLearning
1
"aber ich möchte in diesen Feldern nur nach E-Mails und Telefonen suchen", dann sollten sie in einer entsprechenden Spalte gespeichert werden, wie ich bereits sagte. Sie haben Spalten für diese Daten, die normalisiert werden sollten. Word Breaker werden auf Instanz- / Datenbankebene festgelegt. Es wäre also eine bedeutende bahnbrechende Änderung, sie zu entfernen ..
Larnu
1
Sie möchten entweder die Tabellen für alle Telefon-, E-Mail- usw. Datensätze auf 1-M normalisieren. Die zweite Option besteht darin, die Spalten zu teilen (verwenden Sie string_split (E-Mail, ',') in Kombination mit Outer Apply. Sie müssten Geben Sie eine theoretische Grenze für die Anzahl der E-Mails an, die ein Benutzer haben kann. Schreiben Sie dann eine Suche wie 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+'%')
folgt
2
@TheDudeWithHat Nicht zu gehen, heißt nicht, dass das nicht sollte. Der Grund, warum das OP das Problem hat, das sie sind, ist die fehlende Normalisierung.
Larnu

Antworten:

2

Eigentlich Anfragen

SELECT [...] CONTAINS ([...], '"6662211 *"') - bekommt nichts

gegen 'Call only at weekends +7-999-666-22-11' und

SELECT [...] CONTAINS (Name, '"zimuth *"') - bekommt nichts

gegen 'PJSC Azimuth'

Sie funktionieren wie erwartet .
Siehe Präfixbegriff . Weil 6662211*ist kein Präfix von +7-999-666-22-11sowie zimuth*ist kein Präfix vonAzimuth

Wie für

SELECT [...] CONTAINS ([...], '"[email protected]*"') - dies bekommt die Zeile nicht

Dies ist wahrscheinlich auf Wortbrecher zurückzuführen , wie in Kommentaren immer erwähnt wurde. Siehe Wortbrecher

Ich denke nicht, dass die Volltextsuche für Ihre Aufgabe geeignet ist.

Warum für FTS genau dieselben Aufgaben verwenden, für die der LIKE-Operator verwendet wird? Wenn es einen besseren Indextyp für LIKE-Abfragen gäbe ... dann gäbe es den besseren Indextyp , nicht die völlig andere Technologie und Syntax.
Und in keiner Weise wird es Ihnen helfen, "6662211*"gegen "666 irgendein beliebiges Zeichen 22 etwas beliebiges Zeichen 11" abzugleichen .
Bei der Volltextsuche geht es nicht um Regex-es (und es "6662211*"ist nicht einmal ein korrekter Ausdruck für den Job - es gibt nichts über "irgendeinen beliebigen Zeichen" -Teil), sondern um Synonyme, Wortformen usw.

Aber ist es überhaupt möglich, effektiv nach Teilzeichenfolgen zu suchen?

Ja, so ist es. Was können wir tun, wenn wir potenzielle Kunden wie das Schreiben einer eigenen Suchmaschine außer Acht lassen SQL?

Zunächst einmal - es ist unbedingt erforderlich, Ihre Daten zu bereinigen! Wenn Sie den Benutzern die genauen Zeichenfolgen zurückgeben möchten, die sie eingegeben haben

Benutzer können Kontaktdaten in freier Form angeben

... Sie können sie so speichern, wie sie sind ... und sie mitnehmen.
Dann müssen Sie Daten aus dem Freiformtext extrahieren (es ist nicht so schwer für E-Mails und Telefonnummern) und die Daten in einer kanonischen Form speichern. Für E-Mails ist das einzige, was Sie wirklich tun müssen - machen Sie sie alle in Klein- oder Großbuchstaben (spielt keine Rolle) und teilen Sie sie dann vielleicht beim @Singen auf. Aber in Telefonnummern müssen Sie nur Ziffern hinterlassen
(... und dann können Sie sie sogar als Nummern speichern . Das kann Ihnen Platz und Zeit sparen. Aber die Suche wird anders sein ... Lassen Sie uns jetzt in eine einfachere eintauchen und universelle Lösung mit Strings.)

Wie MatthewBaker erwähnt hat, können Sie eine Tabelle mit Suffixen erstellen. Dann können Sie so suchen

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

Sie sollten den Platzhalter %nur am Ende platzieren . Oder die Suffixe-Tabelle bietet keine Vorteile.

Nehmen wir zum Beispiel eine Telefonnummer

+ 7-999-666-22-11

Nachdem wir Abfallzeichen entfernt haben, wird es 11 Ziffern haben. Das heißt, wir benötigen 11 Suffixe für eine Telefonnummer

           1
          11
         211
        2211
       62211
      662211
     6662211
    96662211
   996662211
  9996662211
 79996662211

Die räumliche Komplexität für diese Lösung ist also linear ... nicht so schlimm, würde ich sagen ... Aber warten Sie, es ist die Komplexität in der Anzahl der Datensätze. Aber in Symbolen ... brauchen wir N(N+1)/2Symbole, um alle Suffixe zu speichern - das ist quadratische Komplexität ... nicht gut ... aber wenn Sie jetzt 100 000Aufzeichnungen haben und in naher Zukunft keine Pläne für Millionen haben - können Sie damit weitermachen Lösung.

Können wir die Raumkomplexität reduzieren?

Ich werde nur die Idee beschreiben, deren Umsetzung einige Anstrengungen erfordern wird. Und wahrscheinlich müssen wir die Grenzen von überschreitenSQL

Angenommen, Sie haben 2 Zeilen NewCompaniesund 2 Zeichenfolgen Freiformtext:

    aaaaa
    11111

Wie groß sollte der Suffix-Tisch sein? Offensichtlich brauchen wir nur 2 Datensätze.

Nehmen wir ein anderes Beispiel. Auch 2 Zeilen, 2 freie Textzeichenfolgen zu suchen. Aber jetzt ist es:

    aa11aa
    cc11cc

Mal sehen, wie viele Suffixe wir jetzt brauchen:

         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

Nein, so schlecht, aber auch nicht so gut.

Was können wir sonst noch tun?

Angenommen, der Benutzer gibt "c11"das Suchfeld ein. Dann LIKE 'c11%'braucht ‚ c11 cc‘ Suffix erfolgreich zu sein. Aber wenn "c11"wir nicht suchen "c%", sondern zuerst suchen , dann nach "c1%"und so weiter? Die erste Suche ergibt nur eine Zeile von NewCompanies. Und es würde keine Notwendigkeit für nachfolgende Suchen geben. Und wir können

       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

und wir haben nur 4 Suffixe

      11aa
    aa11aa
      11cc
    cc11cc

Ich kann nicht sagen, wie hoch die Raumkomplexität in diesem Fall sein würde, aber es scheint akzeptabel zu sein.

x00
quelle
1

In solchen Fällen ist die Volltextsuche nicht ideal. Ich war im selben Boot wie Sie. Ebenso ist die Suche zu langsam und die Volltextsuche sucht nach Wörtern, die mit einem Begriff beginnen und keinen Begriff enthalten.

Wir haben verschiedene Lösungen ausprobiert. Eine reine SQL-Option besteht darin, eine eigene Version der Volltextsuche zu erstellen, insbesondere eine invertierte Indexsuche. Wir haben es versucht und es war erfolgreich, hat aber viel Platz in Anspruch genommen. Wir haben eine sekundäre Haltetabelle für teilweise Suchbegriffe erstellt und die Volltextindizierung verwendet. Dies bedeutet jedoch, dass wir wiederholt mehrere Kopien derselben Sache gespeichert haben. Zum Beispiel haben wir "longword" als Longword, ongword, ngword, gword ... usw. gespeichert. Jede enthaltene Phrase steht also immer am Anfang des indizierten Begriffs. Eine schreckliche Lösung voller Fehler, aber es hat funktioniert.

Wir haben uns dann angesehen, einen separaten Server für Lookups zu hosten. Wenn Sie Lucene und elastisearch googeln, erhalten Sie gute Informationen zu diesen Standardverpackungen.

Schließlich haben wir unsere eigene Suchmaschine entwickelt, die neben SQL läuft. Dies hat es uns ermöglicht, phonetische Suchen (Doppelmetaphon) zu implementieren und dann Levenshtein-Berechnungen neben Soundex zu verwenden, um die Relevanz festzustellen. Overkill für viele Lösungen, aber die Mühe lohnt sich in unserem Anwendungsfall. Wir haben sogar jetzt die Möglichkeit, Nvidia-GPUs für die Cuda-Suche zu nutzen, aber dies war eine ganz neue Reihe von Kopfschmerzen und schlaflosen Nächten. Die Relevanz all dieser Faktoren hängt davon ab, wie oft Ihre Suchvorgänge ausgeführt werden und wie reaktiv Sie sie benötigen.

Matthew Baker
quelle
1

Volltextindizes weisen eine Reihe von Einschränkungen auf. Sie können Platzhalter für Wörter verwenden, bei denen der Index feststellt, dass sie ganze "Teile" sind, aber selbst dann sind Sie auf den Endteil des Wortes beschränkt. Deshalb können Sie CONTAINS(Name, '"Azimut*"')aber nicht verwendenCONTAINS(Name, '"zimuth*"')

Aus der Microsoft- Dokumentation :

Wenn der Präfixbegriff eine Phrase ist, wird jedes Token, aus dem die Phrase besteht, als separater Präfixbegriff betrachtet. Alle Zeilen, deren Wörter mit den Präfixbegriffen beginnen , werden zurückgegeben. Beispielsweise findet der Präfixbegriff "leichtes Brot *" Zeilen mit dem Text "leicht paniert", "leicht paniert" oder "leichtes Brot", gibt jedoch nicht "leicht geröstetes Brot" zurück.

Die Punkte in der E-Mail, wie im Titel angegeben, sind nicht das Hauptproblem. Dies funktioniert zum Beispiel:

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

In diesem Fall identifiziert der Index die gesamte E-Mail-Zeichenfolge als gültig sowie "gmail" und "gmail.com". Nur "SMS" ist jedoch nicht gültig.

Das letzte Beispiel ist ähnlich. Die Teile der Telefonnummer sind indiziert (z. B. 666-22-11 und 999-666-22-11), aber das Entfernen der Bindestriche ist keine Zeichenfolge, über die der Index Bescheid wissen wird. Ansonsten funktioniert das:

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