sql >> Database >  >> RDS >> Mysql

Django views.py Versie van SQL Join met Multi Table Query

Nou, dat zijn wat onduidelijke tabel- en veldnamen, maar ik kan het beste zien dat die query er ongeveer zo uit zou zien:

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

Maar tenzij u vastzit aan dat databaseschema, zien uw modellen er beter uit als:

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)

Dan zou de vraag meer zijn als:

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

OK, laten we uw vraag doornemen, ervan uitgaande dat er geen wijzigingen in uw code zijn. We beginnen met de subquery.

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

We kijken naar de WHERE-clausule en zien dat we een JOIN nodig hebben. Om een ​​join te doen, moet je een relationeel veld declareren in een van de samengevoegde modellen (Django zal een omgekeerde relatie toevoegen, die we een naam moeten geven). Dus we matchen cuisine.cuisineid met `cuisinetype.cuisineid. Dat is een vreselijke naamgeving.

Dat is een veel-op-veel-relatie, dus we hebben een ManyToManyField nodig . Nou, kijkend naar de Cuisine model, het is echt de verbindingstafel voor deze M2M. Django verwacht dat een samenvoegtafel twee ForeignKey . heeft velden, één naar elke kant van het gewricht. Normaal gesproken zal het dit voor u maken om uw gezond verstand te redden. Blijkbaar heb je niet zoveel geluk. Je moet hem dus handmatig aansluiten.

Het lijkt erop dat het "GID"-veld een (nutteloos) ID-veld is voor de record, dus laten we aannemen dat het een auto-increment integer is. (Controleer voor de zekerheid de CREATE TABLE-commando's.) Nu kunnen we de Cuisine herschrijven modelleren in iets dat bijna gezond is:

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'

De modelnamen worden geciteerd omdat de modellen nog niet zijn gedefinieerd (ze staan ​​later in het bestand). Nu is er geen vereiste dat de Django-veldnamen overeenkomen met de kolomnamen, dus laten we ze veranderen in iets dat leesbaarder is. Het record-ID-veld heet meestal gewoon id , en buitenlandse sleutels worden meestal genoemd naar waar ze mee te maken hebben:

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, we zijn klaar met het definiëren van onze gezamenlijke tafel. Nu we toch bezig zijn, laten we hetzelfde toepassen op ons Cuisinetype model. Let op de gecorrigeerde camel-case klassenaam:

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'

Dus we komen eindelijk aan bij ons Restaurant model. Merk op dat de naam enkelvoud is; een object vertegenwoordigt slechts één record.

Ik merk dat er geen dp_table . is of db_column dingen, dus ik ga op een ledemaat en gok dat Django het creëert. Dat betekent dat we het de id . kunnen laten maken veld voor ons en we kunnen het weglaten uit onze code. (Als dat niet het geval is, dan voegen we het gewoon toe zoals bij de andere modellen. Maar je zou eigenlijk geen nullable record-ID moeten hebben.) En dit is waar ons keukentype ManyToManyField leeft:

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)

Merk op dat de naam voor het M2M-veld meervoud is, aangezien die relatie tot meerdere records leidt.

Nog iets dat we aan dit model willen toevoegen, zijn namen voor de omgekeerde relaties. Met andere woorden, hoe ga je van de andere modellen terug naar Restaurant . We doen dit door related_name . toe te voegen parameters. Het is niet ongebruikelijk dat ze hetzelfde zijn.

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")

Nu zijn we eindelijk klaar. Laten we eens kijken naar uw vraag:

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

Aangezien dit FROM restaurants . is , we beginnen met de standaard objectmanager van dat model, objects :

Restaurant.objects

De WHERE clausule is in dit geval een filter() aanroepen, dus we voegen het toe voor de eerste term:

Restaurant.objects.filter(city=8)

U kunt een primaire sleutelwaarde of een City . hebben object aan de rechterkant van die term. De rest van de query wordt echter complexer, omdat de JOIN . nodig is . Een join in Django lijkt gewoon op dereferentie via het relatieveld. In een zoekopdracht betekent dit dat de relevante veldnamen worden samengevoegd met een dubbel onderstrepingsteken:

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

Django weet aan welke velden hij moet deelnemen, want dat is aangegeven in de Cuisine tafel die wordt binnengehaald door de through=Cuisine parameter in cuisine_types . het weet ook dat het een subquery moet doen omdat je een M2M-relatie hebt.

Dus dat levert ons een SQL-equivalent op van:

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')

Halverwege. Nu hebben we SELECT DISTINCT nodig zodat we niet meerdere exemplaren van hetzelfde record krijgen:

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

En je moet de keukentypes binnenhalen om te laten zien. Blijkt dat de query die u heeft daar inefficiënt is, omdat u alleen naar de join-tabel gaat en u verdere query's moet uitvoeren om het gerelateerde CuisineType te krijgen verslagen. Raad eens:Django heeft je gedekt.

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

Django zal twee zoekopdrachten uitvoeren:een zoals die van jou om de gezamenlijke ID's te krijgen en nog een om het gerelateerde CuisineType te krijgen. verslagen. Dan hoeven toegangen via het zoekresultaat niet terug te gaan naar de database.

De laatste twee dingen zijn de volgorde:

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

En de LIMIT :

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

En daar is je query (en de gerelateerde query) verpakt in twee regels van Python. Let wel, op dit moment is de query nog niet eens uitgevoerd. Je moet het ergens in stoppen, zoals een sjabloon, voordat het iets doet:

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])
        })

Sjabloon:

{% 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 %}



  1. Somwaarden van multidimensionale array per sleutel zonder lus

  2. Het opgegeven wachtwoord voor gebruikersaccount 'root' is niet geldig of kan geen verbinding maken met de databaseserver

  3. MYSQL CONCAT MAX LENGTE

  4. Log querytijd in SQLite op Android