所以我有一个预订系统。代理商(提交预订的人员和组织)仅允许在我们分配的类别中进行预订。许多代理可以分配到相同的类别。这是一个简单的多对多。这里是模型的概念:
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')
因此,当预订进入时,我们会根据代理商可用的类别动态分配类别。代理人通常没有指定。
我们刚刚注意到 - 由于一个愚蠢的管理员错误的优雅 - 一些代理人被允许提交任何类别的预订。它让我们在错误的地方有成千上万的预订。
我可以解决这个问题,但我只能通过嵌套查找来实现它:
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之间传递了大量数据。这也不是一次性的。我想定期审核新的预订,以确保它们在正确的位置。在检查代理数据库之后,似乎不可能发生另一个管理问题,我想查询其代理商类别中没有的预订。 < / p>
同样,嵌套查询不会起作用,但随着我们的数据集增长到数百万(甚至更多),我希望更有效地做到这一点。
我觉得应该可以使用F()
查找来执行此操作,如下所示:
from django.db.models import F
bad = Booking.objects.exclude(category__in=F('agent__categories'))
但这不起作用:TypeError: 'Col' object is not iterable
我也试过.exclude(category=F('agent__categories'))
虽然它对语法更满意,但它并没有排除&#34;正确的&#34;担保。
在M2M上进行此类F()
查询的秘诀是什么?
在我设置change the Sentry configuration file(以及一些数据)之后,确切地说明了我的确切内容。请用它们来编写查询。目前唯一的回答点击和问题我在我的&#34;真实&#34;数据也是。
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
在shell中,点火:
from django.db.models import F
from querytest.models import Category, Agent, Booking
Booking.objects.exclude(agent__categories=F('category'))
这是一个错误吗?有没有正确的方法来实现这一目标?
答案 0 :(得分:6)
我可能错了,但我认为反过来应该这样做:
bad = Booking.objects.exclude(agent__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'))
当然,指定一个中间模型有它自己的含义,但我相信你可以处理它们。
答案 1 :(得分:1)
通常在处理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())
当我在服务器上运行测试时,这是一些小的统计信息: 10,000个代理商 500类别 2,479,839代理到类别分配 5,000,000次预订
2,509,161 Bad Bookings。总持续时间149秒
答案 2 :(得分:1)
解决方案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查询会有点沉重,因为你的数据库很大但它只会打到数据库一次,而不是你第一次尝试循环,这将会预订* agent_categories次。
此外,如果您要存储这些数据,则可以通过使用日期过滤来减少数据集,并且在错误预订开始时您有近似值。
您可以定期使用上述命令检查预订是否不一致。 但我建议过度使用管理表格,并在预订时检查类别是否正确。 此外,您可以使用某些javascript仅添加管理表单中的类别,这些类别在当时为选定/登录代理提供。
解决方案2:
使用prefetch_related,这将大大减少您的时间,因为数据库命中次数非常少。
在此处阅读:https://docs.djangoproject.com/en/1.8/ref/models/querysets/
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():
答案 3 :(得分:0)
这可能加快它的速度......
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
答案 4 :(得分:0)
这可能不是您正在寻找的内容,但您可以使用原始查询。我不知道它是否完全可以在ORM中完成,但这可以在你的github回购中使用:
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
,否则表格名称会有所不同。但无论哪种方式,都可以迭代,以便将自定义逻辑插入。
答案 5 :(得分:0)
你快到了。首先,让我们创建两个预订元素:
# 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]
):
# The following requires a single query because
# the Django ORM is pretty smart
[b.id for b in Booking.objects.exclude(
id__in=Booking.objects.filter(
category__in=F('agent__categories')
)
)]
[2]
请注意,根据我的经验,以下查询不会产生任何错误,但由于某些未知原因,结果也不正确:
Booking.objects.exclude(category__in=F('agent__categories'))
[]