在Django 1.8中创建自定义查询

时间:2016-12-30 11:01:03

标签: python mysql django django-1.8

请考虑以下示例:

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'开头的客户)。 / p>

我尝试过的事情:

聚合:

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

但是这会返回一个包含Car个对象主键的dict。我也需要来自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

1 个答案:

答案 0 :(得分:0)

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

您应该将您的租借数据normalize放入另一张表格中。

我还将Color和Brand标准化为他们自己的模型(以及表格),因为您可能希望通过它来查找并在UI中显示可用颜色和品牌的列表。如果您允许用户在野外输入颜色,您会发现自己在Car模型中以各种错误的方式拼写了1000种“蓝色”变体。想象一下从“蓝色”,“蓝色”,“蓝调”,“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一起使用,如果您要构建一个。