Mysql
 sql >> Base de données >  >> RDS >> Mysql

Django views.py Version de jointure SQL avec requête multi-tables

Eh bien, ce sont des noms de table et de champ peu clairs, mais le mieux que je puisse dire, c'est que la requête ressemblerait à :

(Restaurant.objects.filter(city=8, 
     cuisine__cuisinetype__cuisine="Italian").distinct().order_by('name')[:20])

Mais à moins que vous ne soyez enfermé dans ce schéma de base de données, vos modèles auraient une meilleure apparence :

class CuisineType(models.Model):
    name = models.CharField(max_length=50)
    class Meta:
        db_table = 'cuisinetype'

class Restaurants(models.Model):
    city = models.ForeignKey("City", null=True, blank=True) # Apparently defined elsewhere. Should be part of location?
    name = models.CharField(max_length=50)
    location = models.ForeignKey("Location", null=True, blank=True) # Apparently defined elsewhere.
    cuisines = models.ManyToManyField(CuisineType)

La requête ressemblerait alors davantage à :

Restaurant.objects.filter(city=8, cuisines__name="Italian").order_by('name')[:20]

OK, passons en revue votre requête, en supposant qu'aucune modification n'est apportée à votre code. Nous allons commencer par la sous-requête.

SELECT DISTINCT res_id FROM cuisine 
        JOIN    cuisinetype ON cuisine.cuisineid = cuisinetype.`cuisineid`
        WHERE   cuisinetype.`cuisine` = 'Italian'

Nous regardons la clause WHERE et voyons que nous avons besoin d'un JOIN. Pour faire une jointure, il faut déclarer un champ relationnel dans un des modèles joints (Django ajoutera une relation inverse, qu'il faudra nommer). Nous faisons donc correspondre cuisine.cuisineid avec `cuisinetype.cuisineid. C'est un nom horrible.

C'est une relation plusieurs à plusieurs, nous avons donc besoin d'un ManyToManyField . Eh bien, en regardant la Cuisine modèle, c'est vraiment la table de jonction pour ce M2M. Django s'attend à ce qu'une table de jointure ait deux ForeignKey champs, un pointant de chaque côté du joint. Normalement, il créera ceci pour vous afin de sauver votre santé mentale. Apparemment, tu n'as pas cette chance. Vous devez donc le brancher manuellement.

Il semble que le champ "GID" soit un champ ID (inutile) pour l'enregistrement, supposons donc qu'il s'agit d'un entier à incrémentation automatique. (Pour être sûr, vérifiez les commandes CREATE TABLE.) Nous pouvons maintenant réécrire la Cuisine modèle en quelque chose d'approchant sain d'esprit :

class Cuisine(models.Model):
    cuisinegid = models.AutoField(primary_key=True, db_column='CuisineGID')
    cuisineid = models.ForeignKey("Cuisinetype", null=True, 
        db_column='CuisineID', blank=True)
    res_id = models.ForeignKey("Restaurant", null=True, db_column='Res_ID', 
        blank=True)
    class Meta:
        db_table = 'cuisine'

Les noms de modèles sont entre guillemets car les modèles n'ont pas encore été définis (ils sont plus tard dans le fichier). Maintenant, il n'y a aucune exigence que les noms de champs Django correspondent aux noms de colonnes, alors changeons-les en quelque chose de plus lisible. Le champ d'ID d'enregistrement est généralement simplement nommé id , et les clés étrangères sont généralement nommées d'après ce à quoi elles se rapportent :

class Cuisine(models.Model):
    id = models.AutoField(primary_key=True, db_column='CuisineGID')
    cuisine_type = models.ForeignKey("CuisineType", null=True, 
        db_column='CuisineID', blank=True)
    restaurant = models.ForeignKey("Restaurant", null=True, db_column='Res_ID', 
        blank=True)
    class Meta:
        db_table = 'cuisine'

OK, nous avons fini de définir notre table commune. Pendant que nous y sommes, appliquons la même chose à notre Cuisinetype maquette. Notez le nom de classe corrigé en cas de chameau :

class CuisineType(models.Model):
    id = models.AutoField(primary_key=True, db_column='CuisineID')
    name = models.CharField(max_length=50, db_column='Cuisine', blank=True)
    class Meta:
        db_table = 'cuisinetype'

Nous arrivons donc enfin à notre Restaurant maquette. Notez que le nom est au singulier ; un objet ne représente qu'un seul enregistrement.

Je remarque qu'il manque tout dp_table ou db_column trucs, donc je vais sur une branche et deviner que Django le crée. Cela signifie que nous pouvons le laisser créer le id champ pour nous et nous pouvons l'omettre de notre code. (Si ce n'est pas le cas, nous l'ajoutons simplement comme avec les autres modèles. Mais vous ne devriez vraiment pas avoir d'ID d'enregistrement nullable.) Et c'est là que notre type de cuisine ManyToManyField vit :

class Restaurants(models.Model):
    city_id = models.ForeignKey(null=True, blank=True)
    name = models.CharField(max_length=50, blank=True)
    location = models.ForeignKey(null=True, blank=True)
    cuisine_types = models.ManyToManyField(CuisineType, through=Cuisine,
        null=True, blank=True)

Notez que le nom du champ M2M est au pluriel, car cette relation conduit à plusieurs enregistrements.

Une autre chose que nous voudrons ajouter à ce modèle est les noms des relations inverses. En d'autres termes, comment passer des autres modèles au Restaurant . Nous faisons cela en ajoutant related_name paramètres. Il n'est pas rare qu'ils soient les mêmes.

class Restaurant(models.Model):
    city_id = models.ForeignKey(null=True, blank=True, 
        related_name="restaurants")
    name = models.CharField(max_length=50, blank=True)
    location = models.ForeignKey(null=True, blank=True, 
        related_name="restaurants")
    cuisine_types = models.ManyToManyField(CuisineType, through=Cuisine,
        null=True, blank=True, related_name="restaurants")

Maintenant, nous sommes enfin prêts. Examinons donc votre requête :

SELECT  restaurants.`name`, restaurants.`address`, cuisinetype.`cuisine`
FROM    restaurants
JOIN    cuisinetype ON cuisinetype.cuisineid = restaurants.`cuisine`
WHERE   city_id = 8 AND restaurants.id IN (
        SELECT DISTINCT res_id FROM cuisine 
        JOIN    cuisinetype ON cuisine.cuisineid = cuisinetype.`cuisineid`
        WHERE   cuisinetype.`cuisine` = 'Italian')
ORDER BY restaurants.`name`
LIMIT 20

Comme il s'agit de FROM restaurants , nous commencerons par le gestionnaire d'objets par défaut de ce modèle, objects :

Restaurant.objects

Le WHERE la clause dans ce cas est un filter() call, donc on l'ajoute pour le premier terme :

Restaurant.objects.filter(city=8)

Vous pouvez avoir une valeur de clé primaire ou une City objet sur le côté droit de ce terme. Le reste de la requête devient cependant plus complexe, car il nécessite le JOIN . Une jointure dans Django ressemble à un déréférencement à travers le champ de relation. Dans une requête, cela signifie joindre les noms de champs pertinents avec un double trait de soulignement :

Restaurant.objects.filter(city=8, cuisine_type__name="Italian")

Django sait sur quels champs se joindre car cela est déclaré dans le Cuisine table qui est extraite par le through=Cuisine paramètre dans cuisine_types . il sait aussi faire une sous-requête car vous passez par une relation M2M.

Cela nous donne donc l'équivalent SQL de :

SELECT  restaurants.`name`, restaurants.`address`
FROM    restaurants
WHERE   city_id = 8 AND restaurants.id IN (
        SELECT res_id FROM cuisine 
        JOIN    cuisinetype ON cuisine.cuisineid = cuisinetype.`cuisineid`
        WHERE   cuisinetype.`cuisine` = 'Italian')

À mi-chemin. Maintenant, nous avons besoin de SELECT DISTINCT nous n'obtenons donc pas plusieurs copies du même enregistrement :

Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()

Et vous devez afficher les types de cuisine. Il s'avère que la requête que vous avez est inefficace là-bas, car elle ne vous amène qu'à la table de jointure et vous devez exécuter d'autres requêtes pour obtenir le CuisineType associé enregistrements. Devinez quoi :Django a ce qu'il vous faut.

(Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()
    .prefetch_related("cuisine_types"))

Django exécutera deux requêtes :une comme la vôtre pour obtenir les identifiants communs, et une autre pour obtenir le CuisineType associé enregistrements. Ensuite, les accès via le résultat de la requête n'ont pas besoin de revenir à la base de données.

Les deux dernières choses sont la commande :

(Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()
    .prefetch_related("cuisine_types").order_by("name"))

Et la LIMIT :

(Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()
    .prefetch_related("cuisine_types").order_by("name")[:20])

Et il y a votre requête (et la requête associée) emballée dans deux lignes de Python. Attention, à ce stade, la requête n'a même pas été exécutée. Vous devez le mettre dans quelque chose, comme un modèle, avant qu'il ne fasse quoi que ce soit :

def cuisinesearch(request, cuisine):
    return render_to_response('cuisinesearch.html', {
        'restaurants': (Restaurant.objects.filter(city=8, 
             cuisine_type__name="Italian").distinct()
             .prefetch_related("cuisine_types").order_by("name")[:20])
        })

Modèle :

{% for restaurant in cuisinesearch %}
<h2>{{ restaurant.name }}</h2>
<div class="location">{{ restaurant.location }}</div>
<h3>Cuisines:</h3>
<ul class="cuisines">{% for ct in restaurant.cuisine_types.all %}
<li>{{ ct.name }}</li>{% endfor %}
</ul>
{% endfor %}