web-dev-qa-db-ja.com

FTSがドットのあるメールで期待どおりに機能しない

より大きなシステムの一部として検索を開発しています。

この設定ではMicrosoft SQL Server 2014 - 12.0.2000.8 (X64) Standard Edition (64-bit)があります:

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は、"77777777777, 88888888888"のような構造化されたコンマ区切りの数字列です。
  2. Emailは、"[email protected], [email protected]"のようなコンマを含む(または"[email protected]"のようなコンマをまったく含まない)構造化メールの文字列です。
  3. Contacts1, Contacts2, Contacts3, Contacts4は、ユーザーが連絡先の詳細を自由形式で指定できるテキストフィールドです。 "John Smith +1 202 555 0156"または"Bob, +1-999-888-0156, [email protected]"のように。これらのフィールドには、さらに検索するメールと電話を含めることができます。

ここでフルテキストのものを作成します

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

ここにデータサンプルがあります

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)

実際、そのような記録は約10万件あります。

ユーザーは "@ gmail.com"のようなメールの一部を指定できると想定しています。これにより、Email, Contacts1, Contacts2, Contacts3, Contacts4フィールドのいずれかにGmailのメールアドレスが含まれるすべての行が返されます。

電話番号についても同様です。ユーザーは「70283」のようなパターンを検索でき、クエリはこれらの数字を含む電話を返します。自由形式のContacts1, Contacts2, Contacts3, Contacts4フィールドでも、数字とスペース以外の文字をすべて削除してから検索する必要があります。

約1500レコードの場合、以前はLIKEを使用して検索を行いましたが、正常に機能しましたが、現在は多数のレコードがあり、LIKE検索は結果を取得するために無限にかかります。

これは、そこからデータを取得する方法です。

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

実際にリクエスト

SELECT [...] CONTAINS([...]、 '"6662211 *"')-何も取得しません

_'Call only at weekends +7-999-666-22-11'_および

SELECT [...] CONTAINS(Name、 '"zimuth *"')-何も取得しません

_'PJSC Azimuth'_に対して

期待どおりに動作します
プレフィックス用語 を参照してください。 _6662211*_は_+7-999-666-22-11_のprefixではなく、_zimuth*_もprefixof Azimuth

はどうかと言うと

SELECT [...] CONTAINS([...]、 '"[email protected]*"')-これは行を取得しません

alwayslearning がコメントで指摘されているため、これはおそらくワードブレーカーが原因です。 Word-breakers を参照

全文検索があなたの仕事に適用できるとは思いません。

LIKE演算子が使用されるのとまったく同じタスクでFTSを使用する理由LIKEクエリにより適切なインデックスタイプがある場合...完全に異なるテクノロジーや構文ではなく、より適切なインデックスタイプが存在します。
そして、決して_"6662211*"_を "666some any char22任意の文字11 "。
全文検索は正規表現についてではありません(そして_"6662211*"_はジョブの正しい表現でさえありません-「いくつかの任意の文字」の部分については何もありません)同義語、Wordフォームなどについてです。

しかし、部分文字列を効果的に検索することはまったく可能ですか?

はい、そうです。独自の検索エンジンを作成するような見込み客は別として、SQL内で何ができますか?

まず第一に-あなたのデータをクリーンアップすることが不可欠です!ユーザーに入力した正確な文字列をユーザーに返したい場合

ユーザーは連絡先の詳細を自由形式で指定できます

...そのまま保存して、そのままにしておくことができます。
次に、フリーフォームテキストからデータを抽出して(メールや電話番号の場合はそれほど難しくありません)、データを保存する必要がありますいくつかの標準形で。メールの場合、本当に必要なのは唯一のことです-それらをすべて小文字にするか大文字にするか(重要ではありません)、多分次に_@_で分割します。しかし、電話番号では数字だけを残す必要があります
(...さらに、numbersとして保存することもできます。これにより、スペースを節約できますと時間ですが、検索は異なります...今のところ、文字列を使用したよりシンプルでユニバーサルなソリューションに飛び込みましょう。)

MatthewBaker 言及 として、接尾辞のテーブルを作成できます。次に、そのように検索できます

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

ワイルドカード_%_は最後にのみ配置する必要があります。または、サフィックステーブルからのメリットはありません。

たとえば電話番号を考えてみましょう

+ 7-999-666-22-11

それに含まれる廃物を取り除くと、11桁になります。つまり、1つの電話番号に11のサフィックスが必要です。

_           1
          11
         211
        2211
       62211
      662211
     6662211
    96662211
   996662211
  9996662211
 79996662211
_

したがって、このソリューションのスペースの複雑さは線形です...それほど悪くはない、と私は言います...しかし待ってくださいそれはレコード数の複雑さです。しかし、シンボルでは...すべてのサフィックスを格納するためにN(N+1)/2シンボルが必要です。これは2次の複雑さ...良くありません...しかし、_100 000_レコードがあり、計画がない場合近い将来に何百万人も-このソリューションで行くことができます。

スペースの複雑さを軽減できますか?

アイデアについてのみ説明します。実装には多少の労力が必要です。そしておそらく私たちはSQLの境界を越える必要があるでしょう

NewCompaniesに2つの行があり、その中に自由形式のテキストの2つの文字列があるとします。

_    aaaaa
    11111
_

サフィックステーブルはどのくらいの大きさにする必要がありますか?明らかに、必要なレコードは2つだけです。

別の例を見てみましょう。また、2行、検索する2つのフリーテキスト文字列。しかし今それは:

_    aa11aa
    cc11cc
_

今必要なサフィックスの数を見てみましょう:

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

それほど悪くはないが、あまり良くない。

他に何ができますか?

たとえば、ユーザーが検索フィールドに_"c11"_と入力したとします。次に、_LIKE 'c11%'_が成功するには、 'c11cc'サフィックスが必要です。しかし、_"c11"_を検索する代わりに、最初に_"c%"_を検索し、次に_"c1%"_などを検索する場合は、最初の検索では、NewCompaniesから1行がonlyとして返されます。そして、その後の検索は必要ありません。そして私たちはできる

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

そして、私たちはたった4つのサフィックスで終わります

_      11aa
    aa11aa
      11cc
    cc11cc
_

この場合、スペースの複雑さはどうなるかはわかりませんが、許容できると感じています。

2
x00

このような場合、全文検索は理想的とは言えません。私はあなたと同じ船に乗っていました。 Like検索は遅すぎます。全文検索では、用語を含むのではなく、用語で始まる単語を検索します。

私たちはいくつかのソリューションを試しましたが、純粋なSQLオプションの1つは、独自のバージョンの全文検索、特に逆索引検索を構築することです。私たちはこれを試しましたが、成功しましたが、多くのスペースを取りました。部分的な検索用語用の2次保持テーブルを作成し、それに全文索引付けを使用しました。ただし、これは同じものの複数のコピーを繰り返し保存したことを意味します。たとえば、「longword」をLongword、ongword、ngword、gword ....などとして格納しました。したがって、含まれるフレーズは常にインデックス付き用語の先頭に置かれます。欠陥の多い恐ろしい解決策ですが、うまくいきました。

次に、ルックアップ用に別のサーバーをホストする方法を検討しました。 Luceneとelastisearchをググリングすると、これらの既成のパッケージに関する優れた情報が得られます。

最終的に、SQLと一緒に実行する独自の自社検索エンジンを開発しました。これにより、音声検索(ダブルメタフォン)を実装し、soundexに沿ってレーベンシュタイン分析を使用して関連性を確立できました。多くのソリューションではやり過ぎですが、私たちのユースケースでは努力する価値があります。今でも、NVIDIA GPUを使用してcuda検索を実行するオプションがありますが、これはまったく新しい一連の頭痛と眠れない夜を表しています。これらすべての関連性は、検索が実行される頻度と、検索をどの程度反応させる必要があるかによって異なります。

1
Matthew Baker

フルテキストインデックスには、いくつかの制限があります。インデックスが完全な「部分」であると検出した単語にワイルドカードを使用できますが、それでも単語の最後の部分に制限されます。そのため、CONTAINS(Name, '"Azimut*"')は使用できますが、CONTAINS(Name, '"zimuth*"')は使用できません

Microsoftから ドキュメント

接頭辞の用語が句である場合、その句を構成する各トークンは個別の接頭辞の用語と見なされます。単語prefix termsで始まるを含むすべての行が返されます。たとえば、「軽いパン*」という接頭辞は、「軽いパン」、「軽くパン」、「軽いパン」のテキストを含む行を検索しますが、「軽くトーストしたパン」を返しません。

タイトルに示されているメールのドットは、主要な問題ではありません。たとえば、これは機能します:

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

この場合、インデックスはメール文字列全体、および「gmail」と「gmail.com」を有効として識別します。 "s.m.s"だけは無効です。

最後の例も同様です。電話番号の一部にはインデックスが付けられます(たとえば、666-22-11および999-666-22-11)。ただし、ハイフンの削除は、インデックスが認識する文字列ではありません。それ以外の場合、これは機能します:

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