より大きなシステムの一部として検索を開発しています。
この設定では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)
);
Phone
は、"77777777777, 88888888888"
のような構造化されたコンマ区切りの数字列です。Email
は、"[email protected], [email protected]"
のようなコンマを含む(または"[email protected]"
のようなコンマをまったく含まない)構造化メールの文字列です。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
実際にリクエスト
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
_
この場合、スペースの複雑さはどうなるかはわかりませんが、許容できると感じています。
このような場合、全文検索は理想的とは言えません。私はあなたと同じ船に乗っていました。 Like検索は遅すぎます。全文検索では、用語を含むのではなく、用語で始まる単語を検索します。
私たちはいくつかのソリューションを試しましたが、純粋なSQLオプションの1つは、独自のバージョンの全文検索、特に逆索引検索を構築することです。私たちはこれを試しましたが、成功しましたが、多くのスペースを取りました。部分的な検索用語用の2次保持テーブルを作成し、それに全文索引付けを使用しました。ただし、これは同じものの複数のコピーを繰り返し保存したことを意味します。たとえば、「longword」をLongword、ongword、ngword、gword ....などとして格納しました。したがって、含まれるフレーズは常にインデックス付き用語の先頭に置かれます。欠陥の多い恐ろしい解決策ですが、うまくいきました。
次に、ルックアップ用に別のサーバーをホストする方法を検討しました。 Luceneとelastisearchをググリングすると、これらの既成のパッケージに関する優れた情報が得られます。
最終的に、SQLと一緒に実行する独自の自社検索エンジンを開発しました。これにより、音声検索(ダブルメタフォン)を実装し、soundexに沿ってレーベンシュタイン分析を使用して関連性を確立できました。多くのソリューションではやり過ぎですが、私たちのユースケースでは努力する価値があります。今でも、NVIDIA GPUを使用してcuda検索を実行するオプションがありますが、これはまったく新しい一連の頭痛と眠れない夜を表しています。これらすべての関連性は、検索が実行される頻度と、検索をどの程度反応させる必要があるかによって異なります。
フルテキストインデックスには、いくつかの制限があります。インデックスが完全な「部分」であると検出した単語にワイルドカードを使用できますが、それでも単語の最後の部分に制限されます。そのため、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*"')