Django两个查询集在相似属性上的交集

3
在Django中我有两个模型,不确定如何编写它们(是使用抽象模型和继承等方式还是有两个不同的模型)。而实际上,我的两种对象A和B完全相同,因为它们都是项目。它们都具有以下属性:名称、价格。现在我想比较A和B中所有相似的项目(相似的项目是指具有相同名称的项目),并查看它们之间价格的差异(注意:假定没有重复项,但假设交集包含可能不在A或B中的项目,这意味着A和B不是相同的数据集,因此A可能有20个项目,但B可能有643个)。请问如何在Django中使用模型等来实现这一点。

你如何定义“集合A和B中的共同项”? - Anentropic
抱歉,我已经更新了原始帖子,基本上相似/常见的项目是具有相同名称的项目。因此,在集合A中,如果我有一个项目(名称=“apple”),在集合B中我有另一个项目(名称=“apple”),那么它们是相似的。它们的价格可能不同,因此我需要比较a.price和b.price。我想要的第一件事是A和B中的共同项目,然后是它们的价格差异。 - user3413046
A和B都有价格,它们听起来像商店里的产品...是否存在一种情况,您希望列出或搜索“所有产品”?即不仅仅是“A产品”或“B产品”,而是有时将它们视为同一件事物。 - Anentropic
是的,您可以假设有不同的供应商销售完全相同但价格不同的产品。因此,ShopA有“MacBook”商品,而Shop B也有完全相同的MacBook。因此,我想比较它们的价格。但问题在于我想要全部,所以我想要所有在商店A和B之间共同的产品的比较。 - user3413046
3个回答

2

根据您的评论,我认为您只需要一个模型,并使用vendor字段来区分它们。

class Product(models.Model):
    VENDOR_CHOICES = (
        ('a', 'Vendor A'),
        ('b', 'Vendor B')
    )
    # you may find it more appropriate to have a ForeignKey to
    # a Vendor model instead of this choice field:
    vendor = models.CharField(max_length=16, choices=VENDOR_CHOICES)
    name = models.CharField(max_length=255)
    price = models.DecimalField(max_digits=6, decimal_places=2)

然后您可以获取任意两个供应商之间的价格差异(Django 1.8):

from django.db.models import Func, F

prices = (
    Product.objects
    .filter(vendor__in=('a', 'b'))
    .order_by('name', 'vendor')  # order rows within each group
    .values('name')  # GROUP BY `name`
    .annotate(
        first_vendor_price=Func(F('price'), function='FIRST'),
        second_vendor_price=Func(F('price'), function='LAST')
    )
)

price_diffs = {
    group['name']: group['first_vendor_price'] - group['second_vendor_price']
    for group in prices
}

你为什么不注释这个差异呢? - DylanYoung
1
当我写这篇答案时,要么我没有想到,要么Django的表达式系统无法实现。但是,是的,那将是一个好主意! - Anentropic

0

我不确定这是否正是您所询问的,但我可以想到两种查询共享字段的方法。

选项1

首先,您可以通过使用模型继承来获取包含两种模型类型对象的单个QuerySet。

代码:

class ModelBase(models.Model):
    name = models.CharField(max_length=10)
    price = models.FloatField()

class ModelA(ModelBase):
    field_a = models.BooleanField()

class ModelB(ModelBase):
    field_b = models.BooleanField()

测试一下:

在[4]中:从test_app.models导入ModelA、ModelB、ModelBase

在[5]中:matches = ModelBase.objects.filter(name='Product1')

在[6]中:matches.count()

Out[6]:2

在[7]中:对于m in matches:

...: 打印 "%s - %s" % (m.name, m.price)

...:

Product1 - 10.5

Product1 - 3.5

选项2

或者,如果你不想使用继承,并且不介意评估整个QuerySet,可以使用itertools。

代码

class ModelX(models.Model):
    name = models.CharField(max_length=10)
    price = models.FloatField()
    field_x = models.BooleanField()

class ModelY(models.Model):
    name = models.CharField(max_length=10)
    price = models.FloatField()
    field_y = models.BooleanField()

测试一下

from test_app.models import ModelX, ModelY

from itertools import chain

full_set = chain(ModelX.objects.filter(name='Product2'), Model.Y.objects.filter(name='Product2'))

In [5]: full_set = chain(ModelX.objects.filter(name='Product2'), > ModelY.objects.filter(name='Product2'))

In [6]: for m in full_set:

...: print "%s - %s" % (m.name, m.price)

...:

Product2 - 14.0

Product2 - 30.0


选项1正是我需要的,只是我需要将这行代码应用于所有产品matches = ModelBase.objects.filter(name='Product1')并通过迭代对每个产品进行相同的价格比较。因此,我正在寻找集合A和集合B之间的交集,它们仅具有共同的产品,并且我想要对它们进行迭代...类似于这个列表[ [{'name':'apple', 'price':1.99}, {'name':'apple', 'price':2.49}], [{'name':'orange', 'price':13.99}, {'name':'orange', 'price':12.49}], [{'name':'pear', 'price':6.99}, {'name':'pear', 'price':7.49}] ] - user3413046
先计算交集,然后使用 __in 将其传递给过滤函数。 - Dwight Gunning

0

我会选择使用抽象类,并实现多态关系;通过Django的ContentType框架进行查询。

这个解决方案假设模型A具有可与模型B区分开来的属性,否则一个带有ChoiceField的单一模型是最合适的。

Django中的多态性

    ModelManager(models.Manager):
        def get_intersection_on_name_for(self, name):
            model = Model.objects.filter(model_object__name=name).distinct()
            return model


    Model(models.Model):
        content_type = models.ForeignKey(ContentType, null=True)
        object_id = models.PositiveIntegerField(null=True)
        model_object = generic.GenericForeignKey('content_type', 'object_id') # this gets the actual field obj

        objects = ModelManager()


    ModelBase(models.Model):
        name = models.CharField(max_length=255)
        price = models.CharField(max_length=255)

        class Meta:
            abstract = True

    ModelA(ModelBase):
        weight = models.CharField(max_length=255)


    ModelB(ModelBase):
        url = models.UrlField(max_length=255)

正在使用中:

    name = "Test Name"
    Model.objects.get_intersection_on_name_for(name)

参考资料:

Polymorphism: http://en.wikipedia.org/wiki/Polymorphism_(computer_science)

子类型多态http://en.wikipedia.org/wiki/Subtyping

Django内容类型框架https://docs.djangoproject.com/en/1.8/ref/contrib/contenttypes/

Django自定义管理器https://docs.djangoproject.com/en/1.8/topics/db/managers/

Django Queryset .distinct()https://docs.djangoproject.com/en/1.8/ref/models/querysets/#django.db.models.query.QuerySet.distinct

要了解更强大的Django多态性,请查看django-polymorphic


从性能角度来看,与下面的解决方案相比,这个解决方案是否可以实现最快的速度? - user3413046
@user3413046 这是一个很好的答案,它是对Dwightgunning“选项1”的改进。但是,它们都比我建议的“单一模型”选项慢,因为您需要跨越不同的模型表进行连接... 我建议除非您真的需要A和B产品具有非常不同的行为,否则应避免使用这种方法。 - Anentropic
1
这个解决方案确实进行了联接操作,这比在单个表上执行操作本质上要慢。话虽如此,在没有显著规模的情况下,您可能不会注意到速度问题。@Anentropic提到了这个解决方案可能不是您问题的答案;他重新强调了我第二段中的免责声明。 - pygeek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接