確かにあいまいなテキスト検索をどこでも見たことがあるでしょう。たとえば、「stck」と入力すると、実際には「スタック」を意味します! これがどのように機能するのか疑問に思ったことはありませんか?
ファジー テキスト マッチングを行うアルゴリズムはたくさんありますが、それぞれに長所と短所があります。最も有名なものは、編集距離と qgram です。今日は qgram に焦点を当て、サンプルを実装したいと思います。
基本的に、qgram はリレーショナル データベースに最も適したファジー文字列マッチング アルゴリズムです。とてもシンプルです。qgram の「q」は、2-gram や 3-gram、さらには 4-gram などの数字に置き換えられます。
2 グラムとは、すべての単語が 2 つの文字グラムのセットに分割されることを意味します。"Stack" は {"st", "ta", "ac", "ck"} のセットに分割されるか、"database" は {"da","at","ta","ab に分割されます"、"ba"、"as"、"se"}.
単語が 2 グラムに分割されると、1 つの文字列ではなく一連の値をデータベースで検索できます。たとえば、ユーザーが「stck」と入力を間違えた場合、「a」が欠落しているため、「stck」の検索は「stack」と一致しませんが、2 グラム セット {"st","tc","ck"} には 2 つの行があります。スタックの2グラムセットと共通!ビンゴはかなり近い試合を見つけました。データベースの 2 グラム セットと共通点はなく、「stat」の 2 グラム セットとの共通点は 1 つだけなので、最初に「stack」または 2 番目に「star」と入力するつもりだったことをユーザーに簡単に示唆できます。 "。
それでは、Sql Server を使用して実装してみましょう。仮説的な単語データセットを想定します。2gram と単語の間に多対多の関係が必要です。
CREATE TABLE Grams(twog char(2), wordId int, PRIMARY KEY (twog, wordId))
Grams テーブルは、最初の 2 つの g でクラスター化し、次にパフォーマンスのために wordId でクラスター化する必要があります。単語 (例: スタック) をクエリするときは、グラムを一時テーブルに入れます。まず、数百万のダミー レコードを作成します。
--make millions of 2grams
DECLARE @i int =0
WHILE (@i<5000000)
BEGIN
-- a random 2gram
declare @rnum1 char = CHAR(CAST(RAND()*28 AS INT)+97)
declare @rnum2 char = CHAR(CAST(RAND()*28 AS INT)+97)
INS... INTO Grams (twog, wordId) VALUES ( @rnum1 + @rnum2, CAST(RAND()*100000 AS int))
END
ここで、「stack」という単語をクエリしてみましょう。これは {'st','ta','ac','ck'} 2 グラムに分割されます。
DECLARE @word TABLE(twog char(2)) -- 'stack'
INS... INTO @word VALUES ('st'), ('ta'), ('ac'), ('ck')
select wordId, count(*) from @word w inner join Grams g ON w.twog = g.twog
GROUP BY wordId
このクエリを実行するために、Sql Server が一連のクラスター化されたインデックス シーク (またはルックアップ) を使用していることを確認する必要があります。これは当然の選択ですが、統計が破損していたり、古くなっている場合があり、SqlServer がフル スキャンの方が安価であると判断する場合があります。これは通常、左側のテーブルのカーディナリティがわからない場合に発生します。たとえば、SqlServer は @word テーブルが巨大であり、数百万回のルックアップが完全なインデックス スキャンよりもコストがかかると想定する場合があります。