Mysql
 sql >> Database >  >> RDS >> Mysql

Django views.py Versione di SQL Join con query multitabella

Bene, quelli sono alcuni nomi di tabelle e campi poco chiari, ma meglio che posso dire che la query sarebbe simile a:

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

Ma a meno che tu non sia bloccato in quello schema del database, i tuoi modelli sembrerebbero migliori come:

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)

Quindi la query sarebbe più simile a:

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

OK, esaminiamo la tua query, supponendo che non ci siano modifiche al tuo codice. Inizieremo con la sottoquery.

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

Esaminiamo la clausola WHERE e vediamo che abbiamo bisogno di un JOIN. Per fare un join, devi dichiarare un campo relazionale in uno dei modelli uniti (Django aggiungerà una relazione inversa, che dovremmo nominare). Quindi stiamo abbinando cuisine.cuisineid con `cuisinetype.cuisineid. È una denominazione orribile.

Questa è una relazione molti-a-molti, quindi abbiamo bisogno di un ManyToManyField . Bene, guardando la Cuisine modello, è davvero il tavolo di unione per questo M2M. Django si aspetta che una tabella di unione abbia due ForeignKey campi, uno che punta a ciascun lato del giunto. Normalmente creerà questo per salvare la sanità mentale. A quanto pare non sei così fortunato. Quindi devi collegarlo manualmente.

Sembra che il campo "GID" sia un campo ID (inutile) per il record, quindi supponiamo che sia un intero con incremento automatico. (Per essere sicuri, controlla i comandi CREATE TABLE.) Ora possiamo riscrivere la Cuisine modellare in qualcosa che si avvicina al sano:

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'

I nomi dei modelli sono citati perché i modelli non sono stati ancora definiti (sono più avanti nel file). Ora non è necessario che i nomi dei campi Django corrispondano ai nomi delle colonne, quindi cambiamoli in qualcosa di più leggibile. Il campo dell'ID del record di solito è semplicemente denominato id e le chiavi esterne di solito prendono il nome da ciò a cui si riferiscono:

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, abbiamo finito di definire il nostro tavolo comune. Già che ci siamo, applichiamo le stesse cose al nostro Cuisinetype modello. Nota il nome della classe Camel-case corretto:

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'

Quindi arriviamo finalmente al nostro Restaurant modello. Nota che il nome è singolare; un oggetto rappresenta solo un record.

Noto che manca qualsiasi dp_table o db_column roba, quindi sto andando fuori di testa e indovinando che Django lo sta creando. Ciò significa che possiamo lasciare che crei l'id campo per noi e possiamo ometterlo dal nostro codice. (Se non è così, lo aggiungiamo come con gli altri modelli. Ma non dovresti avere un ID record nullable.) Ed è qui che il nostro tipo di cucina ManyToManyField vive:

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)

Nota che il nome per il campo M2M è plurale, poiché tale relazione porta a più record.

Un'altra cosa che vorremo aggiungere a questo modello sono i nomi per le relazioni inverse. In altre parole, come tornare dagli altri modelli a Restaurant . Lo facciamo aggiungendo related_name parametri. Non è insolito che siano uguali.

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

Ora siamo finalmente a posto. Quindi diamo un'occhiata alla tua domanda:

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

Poiché questo è FROM restaurants , inizieremo con il gestore oggetti predefinito di quel modello, objects :

Restaurant.objects

Il WHERE in questo caso è un filter() call, quindi lo aggiungiamo per il primo termine:

Restaurant.objects.filter(city=8)

Puoi avere un valore di chiave primaria o una City oggetto sul lato destro di quel termine. Il resto della query diventa più complesso, però, perché necessita di JOIN . Un join in Django sembra semplicemente dereferenziare attraverso il campo delle relazioni. In una query, ciò significa unire i nomi dei campi rilevanti con un doppio trattino basso:

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

Django sa a quali campi partecipare perché è dichiarato in Cuisine tabella che viene richiamata da through=Cuisine parametro in cuisine_types . sa anche fare una sottoquery perché stai attraversando una relazione M2M.

Quindi questo ci porta SQL equivalente a:

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

A metà strada. Ora abbiamo bisogno di SELECT DISTINCT quindi non otteniamo più copie dello stesso record:

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

E devi inserire i tipi di cucina per la visualizzazione. Si scopre che la query che hai è inefficiente lì, perché ti porta solo alla tabella di join e devi eseguire ulteriori query per ottenere il relativo CuisineType record. Indovina cosa:Django ti copre.

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

Django eseguirà due query:una come la tua per ottenere gli ID congiunti e un'altra per ottenere il relativo CuisineType record. Quindi gli accessi tramite il risultato della query non devono tornare al database.

Le ultime due cose sono l'ordine:

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

E il LIMIT :

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

E c'è la tua query (e la query correlata) racchiusa in due righe di Python. Intendiamoci, a questo punto, la query non è nemmeno stata eseguita. Devi inserirlo in qualcosa, come un modello, prima che faccia qualsiasi cosa:

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

Modello:

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