有没有办法根据字符串相似性过滤django查询集(la python difflib)?

时间:2010-07-29 19:57:27

标签: django django-queryset similarity

我需要将冷线索与客户数据库相匹配。

销售线索来自第三方提供商(数千条记录),销售人员要求我们(用他们的话说)“过滤掉我们的客户”,这样他们就不会尝试将我们的服务卖给老牌客户。

显然,线索中有拼写错误。查尔斯变成了查理,约瑟夫变成了乔,等等。所以我不能真正做一个过滤器比较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。谢谢,亲切的先生。

4 个答案:

答案 0 :(得分:2)

soundex对你不会有帮助,因为它是一种语音算法。乔和约瑟夫在语音上并不相似,所以soundex不会将它们标记为相似。

您可以尝试在PostgreSQL中实现的Levenshtein distance。也许在您的数据库中,如果没有,您应该能够编写一个存储过程,它将计算两个字符串之间的距离并在计算中使用它。

答案 1 :(得分:2)

自Django 1.10以来trigram_similar次查找可能,请参阅PostgreSQL specific lookupsFull 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
)