【问题标题】:Create a custom Query in Django 1.8在 Django 1.8 中创建自定义查询
【发布时间】:2017-05-14 16:35:19
【问题描述】:

考虑下面的例子:

class Customer(models.Model):
    first_name = models.CharField()
    last_name = models.CharField()
    rental_date = models.DateTimeField() 
    rented_car = models.ForeignKey(Car)

class Car(models.Model):
    color = models.CharField()
    reg_no = models.IntegerField()

我想按汽车对所有租赁进行分组(假设客户不能租赁超过一辆车,但客户可以多次租赁汽车),并且对于每个组,仅返回最近 rental_date 的租赁并访问客户和汽车的名称reg_no。

SQL 看起来像这样:

SELECT * FROM Customer c where rental_date = (SELECT max(rental_date) FROM Customer WHERE rented_car_id = c.rented_car_id);

或者像这样:

SELECT *, max(rental_date) from Customer group by rented_car;

然后,生成的查询集将按客户 first_name 或 car reg_no 排序,并且可以应用其他过滤器(例如,仅获取蓝色汽车或名称以“A”开头的客户)。

我尝试过的事情:

聚合:

from django.db.models Max
Customer.objects.values('rented_car').annotate(max_start_date=Max('rental_date'))

但这会返回一个包含Car 对象主键的字典。我也需要来自Customer 的值。修改查询以包含来自 Customer (.values('rented_car', 'first_name')) 的字段将更改 SQL 并改变最终结果。

我使用的第二种方法是raw:

Customer.objects.raw("""SELECT * FROM Customer c where rental_date = (SELECT max(rental_date) FROM Customer WHERE rented_car_id = c.rented_car_id)""")

但这会返回一个不允许进一步过滤或排序的 RawQuerySet 实例。此外,Customer.objects.filter(...).raw(...).order_by(...) 将不起作用,因为 raw 方法将覆盖过滤。

另一个可以返回查询集并允许额外过滤的方法是extra:

Customer.objects.filter(...).extra(select={
    'customer_instances': """SELECT *, max(rental_date) from Customer group by rented_car"""
})

但他总是会返回错误(1241, 'Operand should contain 1 column(s)')。另外,从discussion 我发现 QuerySet.extra(...) 将不再有支持,应该改用aggregation。

【问题讨论】:

  • 您的模型/架构对我来说似乎很奇怪 - 我会使用中间的 Rent 模型,在 Car 和 Customer 上带有日期和外键。
  • 感谢您的回复。但是,这类似于我正在从事的项目中的情况。由于 NDA 协议,我无法提供更多细节,但是,拥有其他型号并不合适。
  • 为什么您的项目不接受理智的数据库设计?此外,如果您受到严格的 NDA 之类的限制,那么就会涉及到金钱,所以请聘请一位熟悉 Django 和 SQL 的顾问,不要来这里要求其他人免费完成您的工作。
  • 感谢您的回复!我很欣赏你的方法和奉献精神。关于数据库设计的合理性,请让我假设您也是一名具有复杂项目经验的开发人员,因此知道许多项目都会遭受修改,尤其是在开发的最后阶段。这是其中一种情况。另外,我并不是要求任何人免费完成我的工作,而是我提出了一个极端案例,在这种情况下 Django 不是一个很好的伴侣,也许在开发复杂的应用程序时找到一种解决方法会帮助许多其他人。
  • Ergo,边缘案例:拥有两个相关模型(ModelA 和 ModelB),我如何通过 ModelB 实例对 ModelA 进行分组,然后在不使用自定义 SQL 并且能够进一步过滤结果(这意味着返回一个 Django QuerySet,而不是一些 RawQuerySet 或字典中的一些值)?

标签: python mysql django django-1.8


【解决方案1】:

您通常不需要在 Django 中自定义 SQL。如果您确实发现使用 ORM 制定数据库查询时遇到问题,请考虑您的数据库架构是否可以采用一种可以让您轻松(且高效)获取数据的形式。

您可能应该将您的租金数据normalize 放到另一个表中。

我还在此处将颜色和品牌规范化为它们自己的模型(因此也是表格),因为您可能希望通过它来查找并在您的 UI 中显示可用颜色和品牌的列表。如果您允许您的用户随意输入颜色,您会发现您的汽车模型中有 1000 种“蓝色”变体,拼写错误。想象一下从“Blue”、“blue”、“blues”、“bLue”等中搜索并向您的用户展示这些可怕的东西。呃……

您的汽车类型(轿车、皮卡...)也可能是一张桌子。

class Color(models.Model):
    name = models.CharField(max_length=16)
    hex = models.CharField(max_length=16)

class Brand(models.Model):
    name = models.CharField(max_length=16)

class Car(models.Model): 
    # make sure your database constraints are thought-out
    reg_no = models.CharField(max_length=16, unique=True)
    year = models.DateField()
    color = models.ForeignKey(Color)
    brand = models.ForeignKey(Brand)

    class Meta:
        order_by = ('reg_no', )

class Customer(models.Model): 
    first_name = models.CharField(max_length=64)
    last_name = models.CharField(max_length=64)

    class Meta:
        order_by = ('first_name', 'last_name', 'pk')

class Rental(models.Model):
    date = models.DateField()  # if it's a date, use a DateField
    car = models.ForeignKey(Car)
    customer = models.ForeignKey(Customer)

    class Meta:
        # Let's not allow duplicate rentals for the same day
        unique_together = ('date', 'car')
        order_by = ('date', 'car', 'customer')

# Since your customers are already sorted by names,
# and your cars by 'reg_no', they follow that sorting.
# Your rentals are ordered by 'date' and therefore have the
# most recent date first in the table, so you can just select
# the most recent 'car' instances, which results in you getting
# the most recent 'car' rentals sorted by your dates,
# nice and easy:

Rental.objects.all().distinct('car')

# Pretty easy to search your rentals now.
# If you want to filter just use something like:

Rental.objects.filter(
    date__range=(datetime(2016, 1, 1), datetime(2017, 1, 1),
    car__brand__icontains='Toyota',
    customer__first_name__icontains='John'
)

在 Django 中,您通常可以通过查看您的产品并找出您拥有的抽象类型来确定您的架构应该是什么样子。您似乎在这里至少有用户、汽车、租赁和颜色。

您可能还会发现django-filter 包在这里很有用。如果您要构建一个,它与您的 UI 和可能的 API 配合得很好。

【讨论】:

    猜你喜欢
    • 2014-09-06
    • 1970-01-01
    • 1970-01-01
    • 2014-02-16
    • 2011-01-13
    • 1970-01-01
    • 2020-08-12
    • 2011-03-11
    • 1970-01-01
    相关资源
    最近更新 更多