基本上,我需要根据ManyToManyField中的多个值进行排序。
所以我想要实现的是将具有最多质疑值的对象放在顶部,继续使用具有较少质疑值的对象。继续使用不具备任何这些值的对象。
模特:
class Color(models.Model):
name = models.CharField(max_length=200)
class Item(models.Model):
surface_color = models.ManyToManyField(Color)
创建的实例基于以上模型:
现在我需要根据多种颜色订购:
虚假查询:Item.objects.all().order_by(surface_color_id=[1, 3])
查询应具有以下结果:
单个查询集可以实现吗?或者我是否需要为每个组合垃圾邮件多次查询?
我在互联网上找到的唯一内容是订购多个字段,这是关于值。
感谢任何帮助。
答案 0 :(得分:2)
这可以给你你想要的东西:
list(Item.objects.filter(surface_color__in=[1,3]).distinct().annotate(num_colors=Count('surface_color')).order_by('-num_colors')) + list(Item.objects.exclude(surface_color__in=[1,3]).distinct())
它需要两个查询,但您不需要为每个项目单独查询。
# Make sure you import Count somewhere in the file first
from django.db.models import Count
# id of Color objects to match
color_ids_to_match = [1,3]
#-----------------------------------------------------------------------------------------------------------------------
# Item objects with Color objects matching `color_ids_to_match`
# - The `surface_color` field has **at least one** Color object with a matching id from `color_ids_to_match`
# - This matches Items #4, #1, #2 from your sample data
items_with_matching_color = Item.objects.filter(surface_color__in=color_ids_to_match).distinct()
# Adds a select field to the query to track the number of surface_color objects matched
# - **NOT the total** number of Color objects associated through `surface_color`
items_with_matching_color = items_with_matching_color.annotate(num_colors=Count('surface_color'))
# Order by that field in descending order
# - Note that the order is undetermined between Item objects with the same `num_colors` value
items_with_matching_color = items_with_matching_color.order_by('-num_colors')
#-----------------------------------------------------------------------------------------------------------------------
# Item objects **NOT** associated with any Color objects with a id in `color_ids_to_match`
# - This matches Item #3 from your sample data
items_without_matching_color = Item.objects.exclude(surface_color__in=color_ids_to_match).distinct()
# Optional - Sets the num_colors field to 0 for this queryset in case you need this information
from django.db.models.expressions import RawSQL
items_without_matching_color = items_without_matching_color.annotate(num_colors=RawSQL('0', ()))
#-----------------------------------------------------------------------------------------------------------------------
# Convert the two querysets to lists and concatenate them
# - This is necessary because a simple queryset union such as `items_with_matching_color | items_without_matching_color`
# does not maintain the order between the two querysets
ordered_items = list(items_with_matching_color) + list(items_without_matching_color)
>>> ordered_items
[<Item: 1: <QuerySet [<Color: 1>, <Color: 2>, <Color: 3>]>>, <Item: 4: <QuerySet [<Color: 1>, <Color: 3>]>>, <Item: 2: <QuerySet [<Color: 1>, <Color: 2>]>>, <Item: 3: <QuerySet [<Color: 2>]>>]
请注意,此处,第1项在第4项之前。您提到两者之间的顺序无关紧要,因为它们都匹配相同数量的Color对象。您可以向order_by
添加另一个参数,以进一步完善您的需求排序。
<Item: 1: <QuerySet [<Color: 1>, <Color: 2>, <Color: 3>]>>
>>> ordered_items[0].num_colors
2
>>> ordered_items[1]
<Item: 4: <QuerySet [<Color: 1>, <Color: 3>]>>
>>> ordered_items[1].num_colors
2
>>> ordered_items[2]
<Item: 2: <QuerySet [<Color: 1>, <Color: 2>]>>
>>> ordered_items[2].num_colors
1
>>> ordered_items[3]
<Item: 3: <QuerySet [<Color: 2>]>>
>>> ordered_items[3].num_colors
0
答案 1 :(得分:0)
假设你有一个名为items
,colors
和item_surface_colors
的Postgres表,你可以使用这样的查询:
SELECT items.*
FROM items
LEFT OUTER JOIN item_surface_colors isc
ON isc.item_id = items.id
AND isc.surface_color_id IN (1,3)
GROUP BY items.id
ORDER BY COUNT(DISTINCT isc.surface_color_id) DESC
我可以告诉你如何在ActiveRecord中执行该查询,但是对于Django我恐怕你自己就是这样。 : - )
有raw(...)
方法来执行原始SQL查询,但请确保参数化输入以防止SQL注入(我这里没有这样做)。阅读更多关于在Django中执行原始SQL查询的信息:https://docs.djangoproject.com/en/1.9/topics/db/sql/
Item.objects.raw("SELECT items.* \
FROM items \
LEFT OUTER JOIN item_surface_colors isc \
ON isc.item_id = items.id \
AND isc.surface_color_id IN (1,3) \
GROUP BY items.id \
ORDER BY COUNT(DISTINCT isc.surface_color_id) DESC")
答案 2 :(得分:0)
也许你可以总结它遇到的任何ID。它不会为更大的ID集合工作,但如果它只是这些(我怀疑)......
from django.db.models import Sum
Item.objects.annotate(my_order=Sum('surface_color__id')).order_by('-my_order')
答案 3 :(得分:0)
对于您要订购的每个color_id
,您需要制作subquery
并标记True/False
该行是否具有此color_id
。
假设你有一个ItemQuerySet
类,那么你可以用以下方式抽象你的订单逻辑:
class ItemQuerySet(models.QuerySet):
def order_by_colors(self, color_ids):
extra_kwargs = {
'select': {},
'select_params' = [],
'order_by': []
}
for (index, color_id) in enumerate(color_ids):
order_by_key = 'color_%s' % index
extra_kwargs['select'][order_by_key] = """EXISTS(
SELECT 1 FROM myapp_item_colors
WHERE myapp_item_colors.color_id = %s
AND myapp_item_colors.item_id = myapp_item.id)"""
extra_kwargs['select_params'].append(color_id)
extra_kwargs['order_by'].append('-%s' % order_by_key)
return self.extra(**extra_kwargs)
#usage
Item.objects.all().order_by_colors([1, 3])