4

SQL テーブルを変更して、わずかに一致しない名前をグループ化し、グループ内のすべての要素に標準化された名前を割り当てる必要があります。

たとえば、最初のテーブルが次のようになっているとします。

Name
--------
Jon Q
John Q
Jonn Q
Mary W
Marie W
Matt H

次のように、新しいテーブルを作成するか、既存のテーブルにフィールドを追加したいと思います。

Name     | StdName
--------------------
Jon Q    | Jon Q
John Q   | Jon Q
Jonn Q   | Jon Q
Mary W   | Mary W
Marie W  | Mary W
Matt H   | Matt H

この場合、最初の名前を「標準化された名前」として割り当てることを選択しましたが、実際にはどちらが選択されてもかまいません。最終的には、最終的な「標準化された名前」が一意の人物 ID にハッシュされます。(数字の ID に直接アクセスする別の解決策も受け入れています。) 照合する生年月日も用意するので、名前の照合の精度は、実際にはそれほど正確である必要はありません。私はこれを少し調べましたが、おそらく Jaro-Winkler アルゴリズムを使用します (たとえば、こちらを参照)。

名前がすべてペアになっていることがわかっていれば、クエリは比較的簡単ですが、同じ名前が任意の数存在する可能性があります。

手続き型言語でこのクエリを実行する方法を簡単に概念化できますが、SQL にはあまり詳しくありません。残念ながら、データに直接アクセスすることはできません。これは機密データであるため、他の誰か (官僚) が実際のクエリを実行する必要があります。具体的な実装は SQL Server ですが、私は実装にとらわれないソリューションを好みます。

編集:

コメントに応えて、私は次の手順のアプローチを念頭に置いていました。これは Python で書かれており、Jaro-Winkler を単に名前の最初の文字を一致させるものに置き換えました。これは、実際のコード例を示すためです。

nameList = ['Jon Q', 'John Q', 'Jonn Q', 'Mary W', 'Marie W', 'Larry H']
stdList = nameList[:]

# loop over all names
for i1, name1 in enumerate(stdList):

  # loop over later names in list to find matches
  for i2, name2 in enumerate(stdList[i1+1:]):

    # If there's a match, replace latter with former.
    if (name1[0] == name2[0]):
      stdList[i1+1+i2] = name1

print stdList

結果は['Jon Q', 'Jon Q', 'Jon Q', 'Mary W', 'Mary W', 'Larry H']です。

4

2 に答える 2

6

ほんの一考ですが、機能を使用できる場合がありSOUNDEX()ます。namesこれにより、類似する の値が作成されます。

次のようなものから始めた場合:

select name, soundex(name) snd,
  row_number() over(partition by soundex(name)
                    order by soundex(name)) rn
from yt;

SQL Fiddle with Demoを参照してください。と同様の各行の結果が得られるためrow_number()、各グループの最初の値のみを返すことができます。たとえば、上記のクエリは次を返します。

|    NAME |  SND | RN |
-----------------------
|   Jon Q | J500 |  1 |
|  John Q | J500 |  2 |
|  Jonn Q | J500 |  3 |
|  Matt H | M300 |  1 |
|  Mary W | M600 |  1 |
| Marie W | M600 |  2 |

row_number()次に、この結果からが 1 に等しいすべての行を選択し、そのsoundex(name)値でメイン テーブルに結合できます。

select t1.name,
  t2.Stdname
from yt t1
inner join
(
  select name as stdName, snd, rn
  from
  (
    select name, soundex(name) snd,
      row_number() over(partition by soundex(name)
                        order by soundex(name)) rn
    from yt
  ) d
  where rn = 1
) t2
  on soundex(t1.name) = t2.snd;

SQL Fiddle with Demoを参照してください。これにより、次の結果が得られます。

|    NAME | STDNAME |
---------------------
|   Jon Q |   Jon Q |
|  John Q |   Jon Q |
|  Jonn Q |   Jon Q |
|  Mary W |  Mary W |
| Marie W |  Mary W |
|  Matt H |  Matt H |
于 2013-05-08T19:12:39.857 に答える
6

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 時間分の処理で行が返されないので、それを強制終了して提出します。幸運を祈ります。

于 2013-05-09T03:29:15.910 に答える