关于python:使用多对多成员的属性查询对象

Querying objects using attribute of member of many-to-many

我有以下型号:

1
2
3
4
5
6
7
8
9
10
class Member(models.Model):
    ref = models.CharField(max_length=200)
    # some other stuff
    def __str__(self):
        return self.ref

class Feature(models.Model):
    feature_id = models.BigIntegerField(default=0)
    members = models.ManyToManyField(Member)
    # some other stuff

成员基本上只是指向某个特性的指针。所以假设我有以下特点:

  • 特征_id=2,成员=1,2
  • StuturyID=4
  • StuturyID=3

那么成员将是:

  • ID=1,REF=4
  • ID=2,REF=3

我想从"确定成员"列表中查找包含一个或多个成员的所有功能。当前我的查询如下所示:

1
2
3
4
5
6
7
# ndtmp is a query set of member-less Features which Members can point to
sids = [str(i) for i in list(ndtmp.values('feature_id'))]
# now make a query set that contains all rels and ways with at least one member with an id in sids
okmems = Member.objects.filter(ref__in=sids)
relsways = Feature.geoobjects.filter(members__in=okmems)
# now combine with nodes
op = relsways | ndtmp

这速度太慢了,我甚至不确定它是否起作用。我尝试使用print语句进行调试,只是为了确保实际正在分析任何内容,我得到了以下信息:

1
2
3
4
5
print(ndtmp.count())
>>> 12747
print(len(sids))
>>> 12747
print(okmems.count())

…然后代码就挂了几分钟,最后我放弃了它。我认为我只是过度简化了这个查询,但我不确定如何最好地简化它。我应该:

  • 迁移功能以使用charfield而不是bigintegerfield?我没有真正的理由使用bigintegerfield,我之所以这样做是因为我开始这个项目时遵循了一个教程。我尝试了一个简单的迁移,只是在models.py中对其进行了更改,在postgresql的列中得到了一个格式为"decimal:(the id)"的"numeric"值,但可能有某种方法可以强制它将ID推入字符串。

  • 使用一些我不知道的多对多字段的特性来更有效地检查匹配

  • 计算每个特征的边界框,并将其存储在另一列中,这样我就不必每次查询数据库时都进行计算(因此,只需迁移时的单一固定计算成本+每次添加新特征或修改现有特征时的计算成本)?

  • 或者别的什么?如果有帮助的话,这是我正在进行的OpenStreetmap相关项目的服务器端脚本,您可以在这里看到正在进行的工作。

    编辑-我认为获得ndids的更快方法如下:

    1
    ndids = ndtmp.values_list('feature_id', flat=True)

    这样可以产生一组非空的ID。不幸的是,我对如何得到Okmems仍然一无所知。我试过:

    1
    okmems = Member.objects.filter(ref__in=str(ndids))

    但它返回一个空查询集。我可以通过以下测试确认参考点是正确的:

    1
    2
    3
    4
    Member.objects.values('ref')[:1]
    >>> [{'ref': '2286047272'}]
    Feature.objects.filter(feature_id='2286047272').values('feature_id')[:1]
    >>> [{'feature_id': '2286047272'}]


    最后,我在一个表中使用数字ID,在另一个表中使用文本类型ID来设置数据库是错误的。我对迁移还不是很熟悉,但在某种程度上,我必须深入研究这个世界,并找出如何迁移数据库,以便在两者上都使用数字。目前,这是可行的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # ndtmp is a query set of member-less Features which Members can point to
    # get the unique ids from ndtmp as strings
    strids = ndtmp.extra({'feature_id_str':"CAST( \
        feature_id AS VARCHAR)"
    }).order_by( \
        '-feature_id_str').values_list('feature_id_str',flat=True).distinct()
    # find all members whose ref values can be found in stride
    okmems = Member.objects.filter(ref__in=strids)
    # find all features containing one or more members in the accepted members list
    relsways = Feature.geoobjects.filter(members__in=okmems)
    # combine that with my existing list of allowed member-less features
    op = relsways | ndtmp
    # prove that this set is not empty
    op.count()
    # takes about 10 seconds
    >>> 8997148 # looks like it worked!

    基本上,我正在创建一个由feature_ids(数字)组成的查询集,并将其转换为文本类型(varchar字段值)的查询集。然后,我使用values_list使它只包含这些字符串ID值,然后我找到refID在允许功能列表中的所有成员。现在我知道哪些成员是允许的,所以我可以过滤掉所有包含该允许列表中一个或多个成员的特性。最后,我将这个包含成员的允许功能的查询集与我原来的不包含成员的允许功能的查询集ndtmp结合起来。


    你应该看看annotate

    1
    2
    3
    okmems = Member.objects.annotate(
        feat_count=models.Count('feature')).filter(feat_count__gte=1)
    relsways = Feature.geoobjects.filter(members__in=okmems)