假设您从 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 实现很慢。我一直在对包含 28k 个姓名(已列为会议参加者的姓名)的数据集进行等效匹配。那里应该有一些很好的重叠,虽然我确实用数据填充了@src,但它是一个包含所有含义的表变量,但它现在已经运行了 15 分钟,但仍未完成。
由于多种原因,它很慢,但我突然想到的是函数中的所有循环和字符串操作。这不是 SQL Server 的亮点。如果您需要做很多这样的事情,最好将它们转换为 CLR 方法,这样至少您可以利用 .NET 库的优势进行一些操作。
我们曾经使用的匹配项之一是双变音位,它会生成一对可能的名称语音解释。与其每次都计算,不如计算一次并将其存储在名称旁边。这将有助于加快一些匹配。不幸的是,看起来 JW 不适合那样分解它。
看看迭代。我们会首先尝试我们知道速度很快的算法。'John' = 'John' 所以没有必要拿出大手笔,所以我们会尝试第一次直接名称检查。如果我们没有找到匹配项,我们会更加努力。希望通过在匹配中进行各种滑动,我们可以尽快获得低悬的果实,并担心以后更难的匹配。
名称
在我的 SU 答案和代码注释中,我提到了昵称。比尔和比利将匹配。比利、利亚姆和威廉绝对不会匹配,即使他们可能是同一个人。您可能希望查看这样的列表,以提供昵称和全名之间的翻译。在对提供的名称运行一组匹配之后,也许我们会尝试根据可能的根名称来寻找匹配。
显然,这种方法存在缺陷。例如,我的祖父是 Max。只是麦克斯。不是马克西米利安、马克西姆斯或任何其他你可能喜欢的东西。
您提供的名称看起来像是第一个和最后一个连接在一起的。未来的读者,如果您有机会捕捉名称的各个部分,请这样做。有些产品会拆分名称并尝试将它们与目录匹配,以尝试猜测某些东西是名字/中间名还是姓氏,但是你有像“Robar Mike”这样的人。如果你在那里看到这个名字,你会认为 Robar 是一个姓氏,你也会把它读成“强盗”。相反,Robar(用法国口音说)是他的名字,而迈克是他的姓氏。无论如何,我认为如果您可以先和后拆分到单独的字段并将各个部分匹配在一起,您将获得更好的匹配体验。精确的姓氏匹配加上部分名字的匹配可能就足够了,特别是在法律上他们是“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 或其他东西。
我的查询即将进行一个小时的处理,没有返回任何行,所以我要杀死它并上交。祝你好运,匹配愉快。