2016-12-30 10 views
0

Рассмотрим ниже пример:Создание пользовательского запроса в 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 клиента или автомобиля reg_no и другие фильтры могут быть (например, получение только синих автомобилей или клиентов, чье имя начинается с «A»).

Вещи, которые я пытался:

Aggregation:

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

но это возвращает Dict, содержащий первичные ключи 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.

+0

Ваша модель/схема кажется мне довольно странной - я бы использовал промежуточную модель «Rent» с датой и внешними ключами на «Car» и «Customer». –

+0

Благодарим за отзыв. Однако это аналогия ситуации, которую я имею в проекте, над которым я работаю. В связи с соглашениями NDA, я не могу дать более подробную информацию, однако, если бы другая модель не была подходящей. –

+0

Почему ваш проект не принял разумный дизайн базы данных? Кроме того, если вы находитесь под строгими NDA и т. Д., Тогда есть деньги, поэтому нанимайте консультанта, который знает свой путь вокруг Django и SQL, и не приходят сюда, прося других людей сделать ваши вакансии бесплатно. –

ответ

0

Обычно вам не нужен пользовательский SQL в Django. Если вы обнаружите, что у вас возникли проблемы с составлением запросов к базе данных с помощью ORM, подумайте, может ли ваша схема базы данных быть в форме, которая упрощает (и эффективно) получение ваших данных.

Вы должны, вероятно, указать normalize данные о аренде в другой таблице.

Я также нормализовал цвет и бренд в свои собственные модели (и, следовательно, таблицы) здесь, потому что вы, вероятно, захотите его искать и отобразить список доступных цветов и брендов в пользовательском интерфейсе. Если вы позволите своим пользователям вводить цвета в дикой природе, вы обнаружите, что у вас есть 1000 вариантов «Синего», записанных во всех видах неправильных способов в вашей модели автомобиля. Представьте себе поиск из «Синего», «Синего», «Блюза», «bLue» и т. Д. И отображения этих ужасных вещей для ваших пользователей. Eh ...

Ваш тип автомобиля (седан, пикап, ...) также, вероятно, будет одним столом.

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. Он хорошо сочетается с вашим пользовательским интерфейсом и возможным API, если вы его построите.