根据另一个相关模型过滤相关字段';Django的s M2M关系
所以我有一个预订系统。代理商(提交预订的人员和组织)只允许在我们分配给他们的类别中进行预订。许多代理可以分配到相同的类别。这是一个简单的多对多。以下是这些模型的外观:根据另一个相关模型过滤相关字段';Django的s M2M关系,django,django-models,django-queryset,Django,Django Models,Django Queryset,所以我有一个预订系统。代理商(提交预订的人员和组织)只允许在我们分配给他们的类别中进行预订。许多代理可以分配到相同的类别。这是一个简单的多对多。以下是这些模型的外观: class Category(models.Model): pass class Agent(models.Model): categories = models.ManyToManyField('Category') class Booking(models.Model): agent = model
class Category(models.Model):
pass
class Agent(models.Model):
categories = models.ManyToManyField('Category')
class Booking(models.Model):
agent = models.ForeignKey('Agent')
category = models.ForeignKey('Category')
因此,当收到预订时,我们会根据可供代理使用的类别动态分配类别。代理通常不指定
我可以选择Booking.agent.categories中没有Booking.categories的预订吗?
我们刚刚注意到,由于一个愚蠢的管理员错误,一些代理被允许提交任何类别的预订。它给我们留下了成千上万的错误地点的预订
我可以解决此问题,但我只能通过嵌套查找使其工作:
for agent in Agent.objects.all():
for booking in Booking.objects.filter(agent=agent):
if booking.category not in agent.categories.all():
# go through the automated allocation logic again
这是可行的,但速度非常慢。大量数据在数据库和Django之间传输。这也不是一次性的。我想定期审核新预订,以确保它们位于正确的位置。似乎不可能出现另一个管理问题,因此在检查代理数据库后,我想查询不属于其代理类别的预订。
同样,嵌套查询将不起作用,但随着我们的数据集增长到数百万(甚至更多),我希望能更有效地完成这项工作
我觉得通过F()
查找应该可以做到这一点,比如:
from django.db.models import F
bad = Booking.objects.exclude(category__in=F('agent__categories'))
但这不起作用:TypeError:“Col”对象不可编辑
我还尝试了.exclude(category=F('agent\uu categories'))
,虽然它的语法比较好,但它并不排除“正确”的预订
在M2M上执行这种F()
查询的秘密公式是什么
帮助我在设置(和一些数据)后准确地确定我的目标。请使用它们来编写查询。目前唯一的答案也是我在“真实”数据上看到的
git clone https://github.com/oliwarner/djangorelquerytest.git
cd djangorelquerytest
python3 -m venv venv
. ./venv/bin/activate
pip install ipython Django==1.9a1
./manage.py migrate
./manage.py shell
在炮弹中,开火:
from django.db.models import F
from querytest.models import Category, Agent, Booking
Booking.objects.exclude(agent__categories=F('category'))
那是虫子吗?有没有合适的方法来实现这一点?我有可能是错的,但我认为反过来应该可以做到:
bad=Booking.objects.exclude(agent\u categories=F('category'))
编辑
如果上述方法不起作用,这里有另一个想法。我在我的设置上尝试过类似的逻辑,它似乎很有效。尝试为ManyToManyField
添加中间模型:
class Category(models.Model):
pass
class Agent(models.Model):
categories = models.ManyToManyField('Category', through='AgentCategory')
class AgentCategory(models.Model):
agent = models.ForeignKey(Agent, related_name='agent_category_set')
category = models.ForeignKey(Category, related_name='agent_category_set')
class Booking(models.Model):
agent = models.ForeignKey('Agent')
category = models.ForeignKey('Category')
然后您可以执行查询:
bad = Booking.objects.exclude(agent_category_set__category=F('category'))
当然,指定中间模型有其自身的含义,但我相信您可以处理它们 这可能会加快速度
for agent in Agent.objects.iterator():
agent_categories = agent.categories.all()
for booking in agent.bookings.iterator():
if booking.category not in agent_categories:
# go through the automated allocation logic again
这可能不是您想要的,但您可以使用原始查询。我不知道是否可以完全在ORM中完成,但这在您的github repo中起作用:
Booking.objects.raw("SELECT id \
FROM querytest_booking as booking \
WHERE category_id NOT IN ( \
SELECT category_id \
FROM querytest_agent_categories as agent_cats \
WHERE agent_cats.agent_id = booking.agent_id);")
我假设您的表名会有所不同,除非您的应用程序名为
querytest
。但无论哪种方式,您都可以重复此过程,将自定义逻辑插入其中。您就快到了。首先,让我们创建两个预订元素:
# b1 has a "correct" agent
b1 = Booking.objects.create(agent=Agent.objects.create(), category=Category.objects.create())
b1.agent.categories.add(b1.category)
# b2 has an incorrect agent
b2 = Booking.objects.create(agent=Agent.objects.create(), category=Category.objects.create())
这是所有错误预订的查询集(即:[b2]
):
请注意,根据我的经验,以下查询不会产生任何错误,但由于未知原因,结果也不正确:
Booking.objects.exclude(category__in=F('agent__categories'))
[]
通常在处理m2m关系时,我采用混合方法。我将把问题分为两部分,python和sql部分。我发现这大大加快了查询速度,而且不需要任何复杂的查询 您要做的第一件事是获取代理到类别的映射,然后使用该映射确定不在分配中的类别
def get_agent_to_cats():
# output { agent_id1: [ cat_id1, cat_id2, ], agent_id2: [] }
result = defaultdict(list)
# get the relation using the "through" model, it is more efficient
# this is the Agent.categories mapping
for rel in Agent.categories.through.objects.all():
result[rel.agent_id].append(rel.category_id)
return result
def find_bad_bookings(request):
agent_to_cats = get_agent_to_cats()
for (agent_id, cats) in agent_to_cats.items():
# this will get all the bookings that NOT belong to the agent's category assignments
bad_bookings = Booking.objects.filter(agent_id=agent_id)
.exclude(category_id__in=cats)
# at this point you can do whatever you want to the list of bad bookings
bad_bookings.update(wrong_cat=True)
return HttpResponse('Bad Bookings: %s' % Booking.objects.filter(wrong_cat=True).count())
以下是我在服务器上运行测试时的一些统计信息:
10000名特工
500类
2479839代理到类别分配
500万次预订
2509161次不良预订。总持续时间149秒
解决方案1:
您可以使用此查询找到好的预订
good = Booking.objects.filter(category=F('agent__categories'))
您可以为此检查sql查询
print Booking.objects.filter(category=F('agent__categories')).query
因此,您可以从所有预订中排除好的预订。
解决办法是:
Booking.objects.exclude(id__in=Booking.objects.filter(category=F('agent__categories')).values('id'))
它将创建一个MySql嵌套查询,这是针对这个问题最优化的MySql查询(据我所知)
这个MySql查询会有点重,因为您的数据库很大,但它只会命中数据库一次,而不是您第一次尝试的循环命中预订*代理\类别次数
此外,如果您正在存储数据集,并且在错误的预订开始时具有近似值,则可以通过使用日期筛选来减少数据集
您可以定期使用上述命令检查不一致的预订。
但我建议您在预订时翻阅管理员表格并检查类别是否正确。
此外,您还可以使用一些javascript仅添加管理员表单中的类别,这些类别在当时为所选/登录的代理提供
解决方案2:
使用预回迁,这将大大减少您的时间,因为数据库命中率非常低
请在此处阅读:
如果是
bad=Booking.objects.exclude(agent\u category\u set\u contains=F('category'))
@jcplower Nope,这将是一个错误,因为使用此查询您将比较不同的表agent\u category\u set
是AgentCategory
,而category
是category
模型。此外,它将为您提供:TypeError:Related Field get invalid lookup:contains
,用于此查询。
Booking.objects.exclude(id__in=Booking.objects.filter(category=F('agent__categories')).values('id'))
for agent in Agent.objects.all().prefetch_related('bookings, categories'):
for booking in Booking.objects.filter(agent=agent):
if booking.category not in agent.categories.all():