在关于行顺序的多个字段中搜索

时间:2017-11-24 22:27:22

标签: python django django-queryset django-orm

我有一个如下模型:

res/layout/

填充了一些数据:

/res/layout-??/

我需要将它与一个集合(不是查询集)合并/加入:

class Foo(models.Model):
    fruit = models.CharField(max_length=10)
    stuff = models.CharField(max_length=10)
    color = models.CharField(max_length=10)
    owner = models.CharField(max_length=20)
    exists = models.BooleanField()
    class Meta:
        unique_together = (('fruit', 'stuff', 'color'), )

因此,当我使用此元组列表搜索此模型时,基本上应返回行0和2。

目前我的解决方法是将fruit stuff color owner exists Apple Table Blue abc True Pear Book Red xyz False Pear Phone Green xyz False Apple Phone Blue abc True Pear Table Green abc True 读入DataFrame并与元组列表合并,并将ID传递给[('Apple', 'Table', 'Blue'), ('Pear', 'Phone', 'Green')] 。我也尝试迭代列表并在每个元组上调用Foo.objects.all(),但它非常慢。名单很大。

当我按照当前答案的建议尝试链接Q时,它引发了一个OperationalError(太多的SQL变量)。

我的主要目标如下:

从模型中可以看出,这三个字段共同构成了我的主键。该表包含大约15k个条目。当我从另一个源获取数据时,我需要检查数据是否已经存在于我的表中并相应地创建/更新/删除(新数据最多可包含15k个条目)。是否有一种干净有效的方法来检查这些记录是否已经在我的表中?

注意:元组列表不必是那种形状。我可以修改它,将其转换为另一个数据结构或转置它。

5 个答案:

答案 0 :(得分:5)

您有('fruit', 'stuff', 'color')字段唯一

因此,如果您的搜索元组是('Apple', 'Table', 'Blue')并且我们连接它,那么它也将是一个唯一的字符串

f = [('Apple', 'Table', 'Blue'), ('Pear', 'Phone', 'Green')]
c = [''.join(w) for w in f]
# Output: ['AppleTableBlue', 'PearPhoneGreen']

因此,我们可以在 annotations 上过滤查询集,并使用 Concat

Foo.objects.annotate(u_key=Concat('fruit', 'stuff', 'color', output_field=CharField())).filter(u_key__in=c)
# Output: <QuerySet [<Foo: #0row >, <Foo: #2row>]>
  

这适用于元组列表

转置案例

案例1:

如果输入是2元组的列表:

[('Apple', 'Table', 'Blue'), ('Pear', 'Phone', 'Green')]
转置输入后

将是:

transpose_input = [('Apple', 'Pear'), ('Table', 'Phone'), ('Blue', 'Green')]
  

我们可以通过计算each_tuple_size和input_list_size轻松识别   输入是转置的。所以我们可以使用 zip 来   再次转置,上述解决方案将按预期工作

if each_tuple_size == 2 and input_list_size == 3:
    transpose_again = list(zip(*transpose_input))
    #  use *transpose_again* variable further

案例2:

如果输入是3元组的列表:

[('Apple', 'Table', 'Blue'), ('Pear', 'Phone', 'Green'), ('Pear', 'Book', 'Red')]

转置输入后将是:

transpose_input = [('Apple', 'Pear', 'Pear'), ('Table', 'Phone', 'Book'), ('Blue', 'Green', 'Red')]
  

因此无法确定输入是否为每个输入转换    n*n 矩阵及以上解决方案将失败

答案 1 :(得分:2)

这是正确的查询:

q = Foo.objects.filter(
    Q(fruit='Apple', stuff='Table', color='Blue') |
    Q(fruit='Pear', stuff='Phone', color='Green')
)

这个查询也会起作用(如果你不喜欢Q):

q = Foo.objects.filter(
    fruit='Apple', stuff='Table', color='Blue'
) | Foo.objects.filter(
    fruit='Pear', stuff='Phone', color='Green'
)

答案 2 :(得分:2)

如果您知道这些字段构成您的自然键并且您必须对它们进行大量查询,请将此自然键添加为正确的字段并采取措施进行维护:

class FooQuerySet(models.QuerySet):
    def bulk_create(self, objs, batch_size=None):
        objs = list(objs)
        for obj in objs:
            obj.natural_key = Foo.get_natural_key(obj.fruit, obj.stuff, obj.color)
        return super(FooQuerySet, self).bulk_create(objs, batch_size=batch_size)

    # you might override update(...) with proper F and Value expressions, 
    # but I assume the natural key does not change

class FooManager(models.Manager):
    def get_queryset(self):
        return FooQuerySet(self.model, using=self._db)

class Foo(models.Model):
    NK_SEP = '|||'  # sth unlikely to occur in the other fields

    fruit = models.CharField(max_length=10)
    stuff = models.CharField(max_length=10)
    color = models.CharField(max_length=10)
    natural_key = models.CharField(max_length=40, unique=True, db_index=True)

    @staticmethod
    def get_natural_key(*args):
        return Foo.NK_SEP.join(args) 

    def save(self, *args, **kwargs):
        self.natural_key = Foo.get_natural_key(self.fruit, self.stuff, self.color)
        Super(Foo, self).save(*args, **kwargs)

    objects = FooManager()

    class Meta:
        unique_together = (('fruit', 'stuff', 'color'), )

现在您可以查询:

from itertools import starmap

lst = [('Apple', 'Table', 'Blue'), ('Pear', 'Phone', 'Green')]
existing_foos = Foo.objects.filter(natural_key__in=list(starmap(Foo.get_natural_key, lst)))

并批量创建:

Foo.objects.bulk_create(
    [
        Foo(fruit=x[0], stuff=x[1], color=x[2]) 
        for x in lst 
        if x not in set(existing_foos.values_list('fruit', 'stuff', 'color'))
    ]
)

答案 3 :(得分:0)

您在所有Q声明

之间AND所做的是where in

您想要实现的是OR所有Q与元组属性设置如下

Foo.objects.filter(Q(fruit='Apple',stuff='Pear',color='Blue)|Q...

要执行此程序化,您可以执行以下操作:

tuple = [('Apple', 'Table', 'Blue'), ('Pear', 'Phone', 'Green')]

query = reduce(lambda q,value: q|Q(fruit=value[0], stuff=value[1], color=value[2]), tuple, Q())  

Foo.objects.filter(query)

答案 4 :(得分:0)

这个问题很可能是X / Y问题的表现。而不是询问你的问题X,你问的是你想出的解决方案。

你为什么要保留一个反击场?我的意思是,为什么不删除计数字段并使用以下方法查询:

Foo.objects.order_by('fruit', 'stuff', 'color')\
           .values('fruit', 'stuff', 'color')\
           .annotate(count=Count('*'))

或保留它,但改为使用计数总和:

Foo.objects.order_by('fruit', 'stuff', 'color')\
           .values('fruit', 'stuff', 'color')\
           .annotate(total=Sum('count'))

如果删除unique_together约束,那么为了合并数据集,您只需要在数据库中插入新条目:

for fruit, stuff, color in collection:
    Foo.objects.update_or_create(fruit=fruit, stuff=stuff, color=color)

或者假设收集是密钥和计数的字典:

for fruit, stuff, color in collection:
    Foo.objects.update_or_create(
         fruit=fruit, 
         stuff=stuff, 
         color=color,
         count=F('count') + collection[(fruit, stuff, color)],
    )

请不要回答&#34;这是出于性能原因&#34;除非你描述了这两种方法 - 在我不那么谦虚的意见中,保持分数是数据库的工作。如果您尝试并且确实发现了性能问题,那么合格的DBA将提出一个解决方案(在极少数情况下,它可能涉及通过使用数据库触发器来保持辅助表的计数)。

我的观点是,保留一个可由数据库计算的值是一个值得怀疑的设计。你必须有充分的理由,并且你必须分析“让数据库计算它&#39;首先接近 - 否则你会因为虚构的性能原因而使你的设计复杂化。

无论如何,我无法想到任何可以比O(n)更好的策略 - n是要合并的数据集中的条目数。

然后我可能已经猜到你的原始问题是错误的,所以如果是这样的话请告诉我们。