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