Получите хотя бы недавно арендованные фильмы в Джанго - PullRequest
1 голос
/ 13 января 2012

Итак, представьте, что у вас есть следующие две таблицы:

CREATE movies (
    id int,
    name varchar(255),
    ...
    PRIMARY KEY (id)
);

CREATE movieRentals (
    id int,
    movie_id int,
    customer varchar(255),
    dateRented datetime,
    ...
    PRIMARY KEY (id)
    FOREIGN KEY (movie_id) REFERENCES movies(id)
);

С SQL напрямую, я бы подошел к этому запросу как:

(
    SELECT movie_id, count(movie_id) AS rent_count
    FROM movieRentals
    WHERE  dateRented > [TIME_ARG_HERE]
    GROUP BY movie_id
)
UNION
(
    SELECT id AS movie_id, 0 AS rent_count
    FROM movie
    WHERE movie_id NOT IN
    (
        SELECT movie_id
        FROM movieRentals
        WHERE dateRented > [TIME_ARG_HERE]
        GROUP BY movie_id
    )
)

(Получить количество всех прокатов фильмов по идентификатору с указанной даты)

Очевидно, что Django-версия этих таблиц - простые модели:

class Movies(models.Model):
    name = models.CharField(max_length=255, unique=True)

class MovieRentals(models.Model):
    customer = models.CharField(max_length=255)
    dateRented = models.DateTimeField()
    movie = models.ForeignKey(Movies)

Однако преобразование этого запроса в эквивалентный запрос представляется трудным:

timeArg = datetime.datetime.now() - datetime.timedelta(7,0)
queryset = models.MovieRentals.objects.all()
queryset = queryset.filter(dateRented__gte=timeArg)
queryset = queryset.annotate(rent_count=Count('movies'))

querysetTwo = models.Movies.objects.all()
querysetTwo = querysetTwo.filter(~Q(id__in=[val["movie_id"] for val in queryset.values("movie_id")]))
# Somehow need to set the 0 count. For now force it with Extra:
querysetTwo.extra(select={"rent_count": "SELECT 0 AS rent_count FROM app_movies LIMIT 1"})

# Now union these - for some reason this doesn't work:
# return querysetOne | querysetTwo
# so instead
set1List = [_getMinimalDict(model) for model in queryset]
# Where getMinimalDict just extracts the values I am interested in.
set2List = [_getMinimalDict(model) for model in querysetTwo]
return sorted(set1List + set2List, key=lambda x: x['rent_count'])

Однако, хотя этот метод работает, он невероятно медленный. Есть ли лучший способ, которого мне не хватает?

Ответы [ 2 ]

1 голос
/ 13 февраля 2012

С прямым SQL это было бы намного проще выразить так:

SELECT movie.id, count(movieRentals.id) as rent_count
FROM movie
LEFT JOIN movieRentals ON (movieRentals.movie_id = movie.id AND dateRented > [TIME_ARG_HERE])
GROUP BY movie.id

При левом соединении будет создаваться отдельная строка для каждого фильма, не показанного после [TIME_ARG_HERE], но в этих строках столбец movieRentals.id будет иметь значение NULL.

Затем COUNT(movieRentals.id) будет считать все арендные платы там, где они существуют, и вернет 0, если было только значение NULL.

1 голос
/ 13 января 2012

Я, должно быть, упускаю что-то очевидное.Почему не сработает следующее:

queryset = models.MovieRentals.filter(dateRented__gte=timeArg).values('movies').annotate(Count('movies')).aggregate(Min('movies__count'))

Кроме того, предложения могут быть объединены в цепочку (как показано в коде выше), поэтому нет необходимости постоянно устанавливать переменную queryset для промежуточных наборов запросов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...