Выберите те объекты, чьи идентификаторы связанных объектов * all * в данной строке - PullRequest
1 голос
/ 30 апреля 2010

Я хочу создать веб-интерфейс для базы данных рецептов, которая позволит пользователю искать список рецептов, которые можно готовить с ингредиентами, которые он предоставляет.

У меня есть следующие модели

class Ingredient(models.Model):
    name = models.CharField(max_length=100, unique=True)
    slug = models.SlugField(max_length=100, unique=True)
    importancy = models.PositiveSmallIntegerField(default=4)
    […]

class Amount(models.Model):
    recipe = models.ForeignKey('Recipe')
    ingredient = models.ForeignKey(Ingredient)
    […]

class Recipe(models.Model):
    name = models.CharField(max_length=100)
    slug = models.SlugField()
    instructions = models.TextField()
    ingredients = models.ManyToManyField(Ingredient, through=Amount)
    […]

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

query = "SELECT *, 
    COUNT(amount.zutat_id) AS selected_count_ingredients, 
    (SELECT COUNT(*) 
            FROM amount 
            WHERE amount.recipe_id = amount.id) 
    AS count_ingredients 
    FROM amount LEFT OUTER JOIN amount 
    ON (recipe.id = recipe.recipe_id) 
    WHERE amount.ingredient_id IN (%s) 
    GROUP BY amount.id 
    HAVING count_ingredients=selected_count_ingredients" % 
            ",".join([str(ingredient.id) for ingredient in ingredients])
recipes = Recipe.objects.raw(query)

Теперь я ищу способ, который не опирается на .raw(), поскольку я хотел бы сделать это исключительно с помощью методов набора запросов Django.

Кроме того, было бы замечательно, если бы вы, ребята, знали способ включения важности ингредиента в поиск, чтобы рецепт все еще отображался в результате, даже если один из его ингредиентов (который имеет значение 0) не поставляется пользователем.

Ответы [ 2 ]

1 голос
/ 10 мая 2010

Вы можете использовать возможность объединить набор запросов.

Простейший способ (но не очень эффективный, так как соединение таблицы Amount более одного раза в sql ...):

ingredients = Ingredient.objects.filter(...)
query = Recipe.object.all()
for i in ingredients:
    query = query.filter(ingredients=i)


print query # at this database query will be exceuted 
1 голос
/ 01 мая 2010

Похоже, вы проделали довольно хорошую работу по настройке моделей для этого. Вы можете сделать это в двух очень простых запросах, которые, вероятно, будут выполняться так же быстро, как один запрос с объединением.

amounts = Amount.objects.filter(ingredient__in=ingredients)
rezepte = Rezept.objects.filter(
        pk__in=amounts.values_list('recipe', flat=True)
    ).order_by('importancy')

Вы даже можете сэкономить память, поместив ее в один оператор, если вы предпочитаете:

rezepte = Rezept.objects.filter(
        pk__in=Amount.objects.filter(
                ingredient__in=ingredients
        ).values_list('recipe', flat=True)
    ).order_by('importancy')

Оба запроса должны автоматически получить индекс по умолчанию из django. Вы получите два довольно чистых запроса:

SELECT `yourproject_amount`.`recipe_id` FROM `yourproject_amount`;
SELECT * FROM `yourproject_rezept`
        WHERE `yourproject_rezept`.`id` IN (1, 2, 3, 4)
        ORDER BY `yourproject_rezept`.`importancy`;
...