web-dev-qa-db-ja.com

同様の文字列値を持つ行を検索する

Microsoft SQL Server 2012データベーステーブルで、約700万のクラウドソースレコードがあり、主に文字列名の値といくつかの関連する詳細が含まれています。ほぼすべてのレコードについて、12の類似したタイプミスレコードがあるようです。私は、 "Apple"、 "Aple"、 "Apples"、 "Spple"などのレコードグループを識別するためにファジーマッチングを実行しようとしています。これらの名前は、複数の単語の間にスペースを入れます。

String1からstring2への変換に必要なキーストロークの数を返す編集距離スカラー関数を使用し、その関数を使用してテーブルをそれ自体に結合するソリューションを考え出しました。ご想像のとおり、これは結合を評価するために何百万回も関数を実行する必要があるため、うまく機能しません。

それをカーソルに置いて、一度に少なくとも1つのstring1のみが評価されるようにします。これにより、少なくとも結果が出ますが、数週間実行した後、150,000レコードを評価するだけで完了しました。 700万件の評価があるので、自分の方法にかかる時間はないと思います。

文字列名にフルテキストインデックスを付けましたが、検索している静的な値がなかったため、フルテキスト述語を使用する方法を実際に見つけることができませんでした。

実行に数か月もかからない方法で、次のようなことをどのように実行できるかについてのアイデアはありますか?

  SELECT t1.name, t2.name
  FROM names AS t1
  INNER JOIN names AS t2
       ON EditDistance(t1.name,t2.name) = 1
       AND t1.id != t2.id

私はsoundexを試しましたが、名前にはスペースと値ごとに複数の単語を含めることができるので、誤検出が多すぎて確実に使用できません。

4
kscott

この問題に対処した後、最も効果的でパフォーマンスの高い方法は、レーベンシュタイン距離を計算するCLR関数を作成することです。アセンブリをSAFEとしてマークすることができ(セキュリティにまったく不安がある場合)、SOUNDEX()または組み込みのSQL Server関数よりもはるかに高速に実行されます。

これは、データベースにアセンブリと関数を設定するコードと、C#で実装されたレーベンシュタイン距離アルゴリズムの基本バージョンです https://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance #C#

C#:

using System;
using System.Security.Cryptography;

namespace LevenshteinDistance
{
    public class LevenshteinDistance
    {
        private int LevenshteinDistance(string a, string b)
        {
            if (string.IsNullOrEmpty(a))
            {
                if (!string.IsNullOrEmpty(b))
                {
                    return b.Length;
                }
                return 0;
            }

            if (string.IsNullOrEmpty(b))
            {
                if (!string.IsNullOrEmpty(a))
                {
                    return a.Length;
                }
                return 0;
            }

            int cost;
            int[,] d = new int[a.Length + 1, b.Length + 1];
            int min1;
            int min2;
            int min3;

            for (int i = 0; i <= d.GetUpperBound(0); i += 1)
            {
                d[i, 0] = i;
            }

            for (int i = 0; i <= d.GetUpperBound(1); i += 1)
            {
                d[0, i] = i;
            }

            for (int i = 1; i <= d.GetUpperBound(0); i += 1)
            {
                for (int j = 1; j <= d.GetUpperBound(1); j += 1)
                {
                    cost = (a[i-1] != b[j-1])? 1 : 0; 

                    min1 = d[i - 1, j] + 1;
                    min2 = d[i, j - 1] + 1;
                    min3 = d[i - 1, j - 1] + cost;
                    d[i, j] = Math.Min(Math.Min(min1, min2), min3);
                }
            }
            return d[d.GetUpperBound(0), d.GetUpperBound(1)];
        }        
    }
}

T-SQL:

use [master];
go

exec sp_configure 'clr enabled', 1;
go
reconfigure with override;
go

use [database_name];
go

-- Drop the function...
if exists (select 1 from sys.objects so where so.[name] = 'LevenshteinDistance')
    drop function dbo.LevenshteinDistance;
go

-- ...then the Assembly
if exists (select 1 from sys.assemblies sa where sa.[name] = 'LevenshteinDistance')
    drop Assembly [LevenshteinDistance];
go

-- Now load the Assembly from an appropriately accessible location
create Assembly [LevenshteinDistance]
from
    'd:\LevenshteinDistance.dll'
with
    permission_set = safe;
go

-- Create an asymmetric key from the Assembly file
use [master];
go

if not exists (select 1 from sys.asymmetric_keys ak where ak.[name] = 'LevenshteinDistanceKey')
begin
    create asymmetric key LevenshteinDistanceKey
    from executable file = 'd:\LevenshteinDistance.dll';
end
go

-- Create a user to associate with the Assembly from the asymmetric key, and then
-- revoke connect access. The login is used to execute the Assembly.
use [master];
go

if not exists (select 1 from sys.server_principals sp where sp.[name] = 'LevenshteinDistanceKeyUser')
begin
    create login LevenshteinDistanceKeyUser from asymmetric key LevenshteinDistanceKey;
    revoke connect sql from LevenshteinDistanceKeyUser;
end
go

grant external access Assembly to LevenshteinDistanceKeyUser;
go

use [database_name];
go
alter Assembly [LevenshteinDistance] with permission_set = safe;
go

-- Create the SQL function which will be called
create function [dbo].LevenshteinDistance
(
    @string1 nvarchar(2048)
    ,@string2 nvarchar(2048)
)
returns nvarchar(max)
as
    external name LevenshteinDistance.[LevenshteinDistance.LevenshteinDistance].LevenshteinDistance;
go
2
Stephen Falken