Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

Django views.py Versão do SQL Join com Multi Table Query


Bem, esses são alguns nomes de tabelas e campos pouco claros, mas o melhor que posso dizer é que a consulta seria algo como:
(Restaurant.objects.filter(city=8, 
     cuisine__cuisinetype__cuisine="Italian").distinct().order_by('name')[:20])

Mas, a menos que você esteja preso a esse esquema de banco de dados, seus modelos ficariam melhores como:
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)

Então a consulta seria mais parecida com:
Restaurant.objects.filter(city=8, cuisines__name="Italian").order_by('name')[:20]

OK, vamos percorrer sua consulta, assumindo que não há alterações em seu código. Vamos começar com a subconsulta.
SELECT DISTINCT res_id FROM cuisine 
        JOIN    cuisinetype ON cuisine.cuisineid = cuisinetype.`cuisineid`
        WHERE   cuisinetype.`cuisine` = 'Italian'

Nós olhamos para a cláusula WHERE e vemos que precisamos de um JOIN. Para fazer uma junção, você deve declarar um campo relacional em um dos modelos unidos (o Django adicionará uma relação reversa, que devemos nomear). Então, estamos combinando cuisine.cuisineid com `cuisinetype.cuisineid. Isso é uma nomenclatura horrível.

Essa é uma relação muitos-para-muitos, então precisamos de um ManyToManyField . Bem, olhando para a Cuisine modelo, é realmente a mesa de junção para este M2M. O Django espera que uma tabela de junção tenha duas ForeignKey campos, um apontando para cada lado da articulação. Normalmente, ele criará isso para você salvar a sanidade. Aparentemente você não tem tanta sorte. Então você tem que ligá-lo manualmente.

Parece que o campo "GID" é um campo de ID (inútil) para o registro, então vamos supor que seja um inteiro de incremento automático. (Para ter certeza, verifique os comandos CREATE TABLE.) Agora podemos reescrever o Cuisine modele em algo que se aproxime da sensatez:
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'

Os nomes dos modelos são citados porque os modelos ainda não foram definidos (eles estão mais adiante no arquivo). Agora não há exigência de que os nomes dos campos do Django correspondam aos nomes das colunas, então vamos alterá-los para algo mais legível. O campo de ID do registro geralmente é chamado apenas de id , e as chaves estrangeiras geralmente são nomeadas de acordo com o que se relacionam:
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, terminamos de definir nossa tabela conjunta. Enquanto estamos nisso, vamos aplicar o mesmo ao nosso Cuisinetype modelo. Observe o nome da classe do camel-case corrigido:
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'

Finalmente chegamos ao nosso Restaurant modelo. Observe que o nome é singular; um objeto representa apenas um registro.

Percebo que falta qualquer dp_table ou db_column coisas, então estou me arriscando e achando que o Django está criando isso. Isso significa que podemos deixá-lo criar o id campo para nós e podemos omiti-lo do nosso código. (Se esse não for o caso, então nós apenas adicionamos como com os outros modelos. Mas você realmente não deveria ter um ID de registro anulável.) E é aqui que nosso tipo de cozinha ManyToManyField vidas:
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)

Observe que o nome do campo M2M é plural, pois essa relação leva a vários registros.

Mais uma coisa que queremos adicionar a este modelo são os nomes para os relacionamentos inversos. Em outras palavras, como ir dos outros modelos de volta ao Restaurant . Fazemos isso adicionando related_name parâmetros. Não é incomum que sejam iguais.
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")

Agora estamos finalmente definidos. Então vamos a sua consulta:
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

Como este é FROM restaurants , começaremos com o gerenciador de objetos padrão desse modelo, objects :
Restaurant.objects

O WHERE cláusula neste caso é um filter() call, então adicionamos para o primeiro termo:
Restaurant.objects.filter(city=8)

Você pode ter um valor de chave primária ou uma City objeto no lado direito desse termo. O resto da consulta fica mais complexo, porém, porque precisa do JOIN . Uma junção no Django parece apenas desreferenciar através do campo de relação. Em uma consulta, isso significa unir os nomes de campos relevantes com um sublinhado duplo:
Restaurant.objects.filter(city=8, cuisine_type__name="Italian")

O Django sabe em quais campos se juntar porque isso é declarado no Cuisine tabela que é puxada pelo through=Cuisine parâmetro em cuisine_types . ele também sabe fazer uma subconsulta porque você está passando por uma relação M2M.

Então, isso nos torna 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')

No meio do caminho. Agora precisamos de SELECT DISTINCT para que não tenhamos várias cópias do mesmo registro:
Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()

E você precisa puxar os tipos de cozinha para exibição. Acontece que a consulta que você tem é ineficiente, porque só leva você à tabela de junção e você precisa executar outras consultas para obter o CuisineType relacionado registros. Adivinhe:o Django tem tudo para você.
(Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()
    .prefetch_related("cuisine_types"))

O Django executará duas consultas:uma como a sua para obter os IDs conjuntos e outra para obter o CuisineType relacionado registros. Então os acessos através do resultado da consulta não precisam voltar ao banco de dados.

As duas últimas coisas são a ordenação:
(Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()
    .prefetch_related("cuisine_types").order_by("name"))

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

E há sua consulta (e a consulta relacionada) compactada em duas linhas de Python. Lembre-se, neste ponto, a consulta ainda não foi executada. Você tem que colocá-lo em algo, como um modelo, antes de fazer qualquer coisa:
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])
        })

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