SSC からjaro-winklerの実装をコピーして貼り付けると仮定すると(登録が必要です)、次のコードが機能します。そのための SQLFiddle を構築しようとしましたが、スキーマを構築しているときに腹が立ち続けました。
この実装にはチートがあります---私はカーソルを使用しています。一般に、カーソルはパフォーマンスに貢献しませんが、この場合、セットをそれ自体と比較できる必要があります。宣言されたカーソルを排除するための適切な数値/集計表のアプローチがおそらくあります。
DECLARE @SRC TABLE
(
source_string varchar(50) NOT NULL
, ref_id int identity(1,1) NOT NULL
);
-- Identify matches
DECLARE @WORK TABLE
(
source_ref_id int NOT NULL
, match_ref_id int NOT NULL
);
INSERT INTO
@src
SELECT 'Jon Q'
UNION ALL SELECT 'John Q'
UNION ALL SELECT 'JOHN Q'
UNION ALL SELECT 'Jonn Q'
-- Oops on matching joan to jon
UNION ALL SELECT 'Joan Q'
UNION ALL SELECT 'june'
UNION ALL SELECT 'Mary W'
UNION ALL SELECT 'Marie W'
UNION ALL SELECT 'Matt H';
-- 2 problems to address
-- duplicates in our inbound set
-- duplicates against a reference set
--
-- Better matching will occur if names are split into ordinal entities
-- Splitting on whitespace is always questionable
--
-- Mat, Matt, Matthew
DECLARE CSR CURSOR
READ_ONLY
FOR
SELECT DISTINCT
S1.source_string
, S1.ref_id
FROM
@SRC AS S1
ORDER BY
S1.ref_id;
DECLARE @source_string varchar(50), @ref_id int
OPEN CSR
FETCH NEXT FROM CSR INTO @source_string, @ref_id
WHILE (@@fetch_status <> -1)
BEGIN
IF (@@fetch_status <> -2)
BEGIN
IF NOT EXISTS
(
SELECT * FROM @WORK W WHERE W.match_ref_id = @ref_id
)
BEGIN
INSERT INTO
@WORK
SELECT
@ref_id
, S.ref_id
FROM
@src S
-- If we have already matched the value, skip it
LEFT OUTER JOIN
@WORK W
ON W.match_ref_id = S.ref_id
WHERE
-- Don't match yourself
S.ref_id <> @ref_id
-- arbitrary threshold, will need to examine this for sanity
AND dbo.fn_calculateJaroWinkler(@source_string, S.source_string) > .95
END
END
FETCH NEXT FROM CSR INTO @source_string, @ref_id
END
CLOSE CSR
DEALLOCATE CSR
-- Show me the list of all the unmatched rows
-- plus the retained
;WITH MATCHES AS
(
SELECT
S1.source_string
, S1.ref_id
, S2.source_string AS match_source_string
, S2.ref_id AS match_ref_id
FROM
@SRC S1
INNER JOIN
@WORK W
ON W.source_ref_id = S1.ref_id
INNER JOIN
@SRC S2
ON S2.ref_id = W.match_ref_id
)
, UNMATCHES AS
(
SELECT
S1.source_string
, S1.ref_id
, NULL AS match_source_string
, NULL AS match_ref_id
FROM
@SRC S1
LEFT OUTER JOIN
@WORK W
ON W.source_ref_id = S1.ref_id
LEFT OUTER JOIN
@WORK S2
ON S2.match_ref_id = S1.ref_id
WHERE
W.source_ref_id IS NULL
and s2.match_ref_id IS NULL
)
SELECT
M.source_string
, M.ref_id
, M.match_source_string
, M.match_ref_id
FROM
MATCHES M
UNION ALL
SELECT
M.source_string
, M.ref_id
, M.match_source_string
, M.match_ref_id
FROM
UNMATCHES M;
-- To specifically solve your request
SELECT
S.source_string AS Name
, COALESCE(S2.source_string, S.source_string) As StdName
FROM
@SRC S
LEFT OUTER JOIN
@WORK W
ON W.match_ref_id = S.ref_id
LEFT OUTER JOIN
@SRC S2
ON S2.ref_id = W.source_ref_id
クエリ出力 1
source_string ref_id match_source_string match_ref_id
Jon Q 1 John Q 2
Jon Q 1 JOHN Q 3
Jon Q 1 Jonn Q 4
Jon Q 1 Joan Q 5
june 6 NULL NULL
Mary W 7 NULL NULL
Marie W 8 NULL NULL
Matt H 9 NULL NULL
クエリ出力 2
Name StdName
Jon Q Jon Q
John Q Jon Q
JOHN Q Jon Q
Jonn Q Jon Q
Joan Q Jon Q
june june
Mary W Mary W
Marie W Marie W
Matt H Matt H
ドラゴンがいる
SuperUser で、人とのマッチングの経験について話しました。このセクションでは、注意すべき点をいくつかリストします。
スピード
マッチングの一環として、マッチング プロセスを強化するための誕生日があるという点で万歳。実際には、最初に生年月日のみに基づいて一致を生成することをお勧めします。これは完全一致であり、適切なインデックスを使用すると、SQL Server は行をすばやく含めたり除外したりできます。必要になるからです。TSQL の実装は非常に遅いです。私は、28,000 名の名前 (会議出席者としてリストされていた名前) のデータセットに対して、同等の照合を実行してきました。そこにはある程度のオーバーラップがあるはずです.@srcにデータを入力しましたが、それはそれが意味するすべてのテーブル変数ですが、現在15分間実行されており、まだ完了していません.
いくつかの理由で遅いですが、私が飛び出したのは、関数内のすべてのループと文字列操作です。それは SQL Server が優れているところではありません。これをたくさん行う必要がある場合は、それらを CLR メソッドに変換することをお勧めします。これにより、少なくとも一部の操作で .NET ライブラリの強みを活用できるようになります。
私たちが使用していた一致の 1 つはDouble Metaphoneで、名前の可能な音声解釈のペアを生成します。毎回計算するのではなく、一度計算して名前と一緒に保存します。これは、マッチングの一部を高速化するのに役立ちます。残念ながら、JWがそのように分解するのに適しているようには見えません.
反復も見てください。最初に、高速であることがわかっている alg を試しました。'John' = 'John' なので、大げさなことをする必要はありません。そのため、ストレートな名前チェックの最初のパスを試してみます。一致するものが見つからない場合は、もっと頑張ります。希望は、マッチングでさまざまなスワイプを行うことで、簡単に達成できる成果をできるだけ早く取得し、後で難しいマッチングについて心配することでした.
名前
SU の回答とコードのコメントで、ニックネームについて言及しています。ビルとビリーがマッチする。ビリー、リアム、ウィリアムは、たとえ同一人物であっても絶対に一致しません。ニックネームとフルネームの間の翻訳を提供するために、このようなリストを見たいと思うかもしれません. 提供された名前で一連の一致を実行した後、可能なルート名に基づいて一致を探してみます。
明らかに、このアプローチには欠点があります。たとえば、義理の祖父はマックスです。ただマックス。マキシミリアン、マキシマス、またはあなたが気にするかもしれない他のものではありません。
指定された名前は、最初と最後が連結されているように見えます。将来の読者の皆さん、名前の個々の部分をキャプチャする機会があれば、ぜひそうしてください. 名前を分割し、それらをディレクトリと照合して、名前/ミドルネームまたは姓のいずれであるかを推測しようとする製品がありますが、「Robar Mike」のような人がいます。そこにその名前を見たら、Robar は苗字だと思い、「強盗」のように発音するでしょう。代わりに、Robar (フランス語訛りで言う) が彼の名前で、Mike が姓です。いずれにせよ、先攻と後攻をそれぞれ別のフィールドに分けて、それぞれのピースを合わせてマッチングできるようになれば、マッチングはより快適になると思います。姓が完全に一致し、名が部分的に一致していれば十分です。特に、法的に「Franklin Roosevelt」であり、「F. Roosevelt」の候補者がいる場合は、最初の文字が一致するという規則がある可能性があります。または、そうではありません。
ノイズ - JW の投稿と私の回答で参照されているように、一致する目的でがらくた (句読点、ストップ ワードなど) を取り除きます。また、敬称 (phd、jd など) と世代 (II、III、JR、SR) にも注意してください。私たちのルールは、世代の有無にかかわらず候補であり、反対の状態 (Bob Jones Jr == Bob Jones) に一致するか、世代 (Bob Jones Sr = Bob Jones Sr) と完全に一致する可能性がありますが、両方のレコードがそれらを提供し、競合していました (Bob Jones Sr != Bob Jones Jr)。
大文字と小文字を区別します。常にデータベースと tempdb をチェックして、大文字と小文字を区別しない一致を作成していないことを確認してください。もしそうなら、マッチングのためにすべてを上下に変換しますが、付属のケーシングを捨てないでください. latessa が Latessa であるべきか、LaTessa なのか、それとも何か他のものであるべきかを判断しようとして頑張ってください。
私のクエリは 1 時間分の処理で行が返されないので、それを強制終了して提出します。幸運を祈ります。