我需要将冷线索与客户数据库相匹配。
销售线索来自第三方提供商(数千条记录),销售人员要求我们(用他们的话说)“过滤掉我们的客户”,这样他们就不会尝试将我们的服务卖给老牌客户。
显然,线索中有拼写错误。查尔斯变成了查理,约瑟夫变成了乔,等等。所以我不能真正做一个过滤器比较lead_first_name和client_first_name等。
我需要使用某种string similarity机制。
现在我正在使用可爱的difflib将潜在客户的名字和姓氏与使用Client.objects.all()
生成的列表进行比较。它有效,但由于客户端的数量往往很慢。
我知道大多数sql数据库都有soundex和差异函数。请参阅下面的更新中我对它的测试 - 它不像difflib那样有用。
还有其他解决方案吗?有更好的解决方案吗?
修改
Soundex,至少在我的数据库中,表现不如difflib。
这是一个简单的测试 - 在包含“Joseph Lopes”的表格中查找“Joe Lopes”:
with temp (first_name, last_name) as (
select 'Joseph', 'Lopes'
union
select 'Joe', 'Satriani'
union
select 'CZ', 'Lopes'
union
select 'Blah', 'Lopes'
union
select 'Antonio', 'Lopes'
union
select 'Carlos', 'Lopes'
)
select first_name, last_name
from temp
where difference(first_name+' '+last_name, 'Joe Lopes') >= 3
order by difference(first_name+' '+last_name, 'Joe Lopes')
以上返回“Joe Satriani”作为唯一的比赛。即使将相似性阈值降低到2,也不会将“Joseph Lopes”作为潜在匹配。
但是difflib做得更好:
difflib.get_close_matches('Joe Lopes', ['Joseph Lopes', 'Joe Satriani', 'CZ Lopes', 'Blah Lopes', 'Antonio Lopes', 'Carlos Lopes'])
['Joseph Lopes', 'CZ Lopes', 'Carlos Lopes']
在gruszczy的回复后编辑:
在写我自己之前,我找了found a T-SQL implementation of Levenshtein Distance in the repository of all knowledge.
在测试中,它仍然不会比difflib做更好的匹配工作。
这让我研究了difflib背后的算法。它似乎是modified version算法的Ratcliff-Obershelp。
不幸的是,我似乎找不到其他已经基于difflib创建了T-SQL实现的灵魂......我会尽力尝试。
如果在接下来的几天内没有其他人能得到更好的答案,我会将其授予gruszczy。谢谢,亲切的先生。
答案 0 :(得分:2)
soundex
对你不会有帮助,因为它是一种语音算法。乔和约瑟夫在语音上并不相似,所以soundex不会将它们标记为相似。
您可以尝试在PostgreSQL中实现的Levenshtein distance。也许在您的数据库中,如果没有,您应该能够编写一个存储过程,它将计算两个字符串之间的距离并在计算中使用它。
答案 1 :(得分:2)
自Django 1.10以来trigram_similar
次查找可能,请参阅PostgreSQL specific lookups和Full text search的文档
答案 2 :(得分:0)
如果你需要使用django和postgres到达那里并且不想使用1.10 trigram-similarity https://docs.djangoproject.com/en/2.0/ref/contrib/postgres/lookups/#trigram-similarity中的介绍,你可以像这样使用Levensthein来实现:
扩展需要fuzzystrmatch
你需要在psql中为你的数据库添加postgres扩展名:
CREATE EXTENSION fuzzystrmatch;
让我们定义自定义函数,我们可以为查询集添加注释。它只需要一个参数search_term并使用postgres levenshtein函数(参见docs):
from django.db.models import Func
class Levenshtein(Func):
template = "%(function)s(%(expressions)s, '%(search_term)s')"
function = "levenshtein"
def __init__(self, expression, search_term, **extras):
super(Levenshtein, self).__init__(
expression,
search_term=search_term,
**extras
)
然后在项目的任何其他地方我们只导入定义的Levenshtein和F来传递django字段。
from django.db.models import F
Spot.objects.annotate(
lev_dist=Levenshtein(F('name'), 'Kfaka')
).filter(
lev_dist__lte=2
)
答案 3 :(得分:0)
根据 andilabs 的回答,您可以使用 Levenshtein 函数来创建您的自定义函数。 Postgres doc 表示 Levenshtein 函数如下:
levenshtein(text source, text target, int ins_cost, int del_cost, int sub_cost) returns int
levenshtein(text source, text target) returns int
andilabs 答案只能使用第二个函数。如果您想要更高级的带有插入/删除/替换成本的搜索,您可以像这样重写函数:
from django.db.models import Func
class Levenshtein(Func):
template = "%(function)s(%(expressions)s, '%(search_term)s', %(ins_cost)d, %(del_cost)d, %(sub_cost)d)"
function = 'levenshtein'
def __init__(self, expression, search_term, ins_cost=1, del_cost=1, sub_cost=1, **extras):
super(Levenshtein, self).__init__(
expression,
search_term=search_term,
ins_cost=ins_cost,
del_cost=del_cost,
sub_cost=sub_cost,
**extras
)
并调用函数:
from django.db.models import F
Spot.objects.annotate(
lev_dist=Levenshtein(F('name'), 'Kfaka', 3, 3, 1) # ins = 3, del = 3, sub = 1
).filter(
lev_dist__lte=2
)