sql >> Base de Datos >  >> RDS >> Mysql

Django views.py Versión de SQL Join con Multi Table Query

Bueno, esos son algunos nombres de tablas y campos poco claros, pero lo mejor que puedo decir es que la consulta se vería así:

(Restaurante.objetos.filtro(ciudad=8, cocina__cocinatipo__cocina="Italiana").distinct().order_by('nombre')[:20]) 

Pero a menos que esté bloqueado en ese esquema de base de datos, sus modelos se verían mejor como:

class Tipo de Cocina(modelos.Modelo):nombre =modelos.CharField(longitud_máxima=50) clase Meta:db_table ='tipo de cocina'clase Restaurantes(modelos.Modelo):ciudad =modelos.ForeignKey("Ciudad", null=Verdadero, en blanco=Verdadero) # Aparentemente definido en otro lugar. ¿Debe ser parte de la ubicación? name =models.CharField(max_length=50) location =models.ForeignKey("Location", null=True, blank=True) # Aparentemente definido en otro lugar. cocinas =modelos.ManyToManyField(CuisineType) 

Entonces la consulta sería más como:

Restaurante.objetos.filtro(ciudad=8, cocina__nombre="Italiano").order_by('nombre')[:20] 

Bien, analicemos su consulta, suponiendo que no haya cambios en su código. Comenzaremos con la subconsulta.

SELECCIONA DISTINCT res_id FROM cuisine ÚNETE a cookingtype ON cooking.cuisineid =cookingtype.`cuisineid` WHERE cookingtype.`cuisine` ='Italiano' 

Miramos la cláusula WHERE y vemos que necesitamos un JOIN. Para hacer una combinación, debe declarar un campo relacional en uno de los modelos combinados (Django agregará una relación inversa, que debemos nombrar). Así que estamos emparejando cuisine.cuisineid con `cuisinetype.cuisineid. Ese es un nombre horrible.

Esa es una relación de muchos a muchos, por lo que necesitamos un ManyToManyField . Bueno, mirando la Cocina modelo, es realmente la mesa de unión para este M2M. Django espera que una tabla de unión tenga dos ForeignKey campos, uno apuntando a cada lado de la articulación. Normalmente creará esto para que guardes la cordura. Aparentemente no tienes tanta suerte. Así que tienes que conectarlo manualmente.

Parece que el campo "GID" es un campo de identificación (inútil) para el registro, así que supongamos que es un entero de incremento automático. (Para estar seguro, verifique los comandos CREATE TABLE). Ahora podemos reescribir la Cuisine modelo en algo cercano a la cordura:

class Cocina(modelos.Modelo):cocinagid =modelos.AutoField(primary_key=True, db_column='CuisineGID') cocinaid =modelos.ForeignKey("Tipo de cocina", null=Verdadero, db_column='CocinaID', blank=True) res_id =models.ForeignKey("Restaurante", null=True, db_column='Res_ID', blank=True) class Meta:db_table ='cocina' 

Los nombres de los modelos se citan porque los modelos aún no se han definido (están más adelante en el archivo). Ahora no hay ningún requisito de que los nombres de los campos de Django coincidan con los nombres de las columnas, así que cambiémoslos a algo más legible. El campo de ID de registro generalmente se llama simplemente id , y las claves foráneas suelen recibir el nombre de su relación:

class Cocina(modelos.Modelo):id =modelos.AutoField(primary_key=True, db_column='CuisineGID') cooking_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 ='cocina' 

Bien, hemos terminado de definir nuestra mesa conjunta. Mientras estamos en esto, apliquemos lo mismo a nuestro Cuisinetype modelo. Tenga en cuenta el nombre de clase camel-case corregido:

class Tipo de Cocina(modelos.Modelo):id =modelos.AutoField(primary_key=True, db_column='CuisineID') nombre =modelos.CharField(max_length=50, db_column='Cuisine', blank=True) clase Meta:db_table ='tipo de cocina' 

Finalmente llegamos a nuestro Restaurante modelo. Tenga en cuenta que el nombre es singular; un objeto solo representa un registro.

Me doy cuenta de que carece de cualquier dp_table o db_column cosas, así que me arriesgo y adivino que Django lo está creando. Eso significa que podemos dejar que cree el id campo para nosotros y podemos omitirlo de nuestro código. (Si ese no es el caso, simplemente lo agregamos como con los otros modelos. Pero realmente no debería tener una identificación de registro anulable). Y aquí es donde nuestro tipo de cocina ManyToManyField vidas:

clase Restaurantes(modelos.Modelo):ciudad_id =modelos.ForeignKey(null=Verdadero, en blanco=Verdadero) nombre =modelos.CharField(max_length=50, en blanco=Verdadero) ubicación =modelos.ForeignKey(null=Verdadero, en blanco=Verdadero) tipos_de_cocina =modelos.ManyToManyField(CuisineType, through=Cuisine, null=Verdadero, en blanco=Verdadero) 

Tenga en cuenta que el nombre del campo M2M es plural, ya que esa relación genera varios registros.

Una cosa más que queremos agregar a este modelo son los nombres de las relaciones inversas. En otras palabras, cómo volver de los otros modelos a Restaurante . Hacemos esto agregando related_name parámetros No es raro que sean iguales.

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

Ahora finalmente estamos listos. Así que echemos un vistazo a su consulta:

SELECCIONE restaurantes.`nombre`, restaurantes.`dirección`, tipo de cocina.`cuisine`DE restaurantes ÚNASE a tipo de cocina ON tipo de cocina.cuisineid =restaurantes.`cuisine`WHERE city_id =8 AND restaurants.id IN ( SELECCIONE DISTINCT res_id DESDE cocina ÚNETE a tipo de cocina EN cocina.cuisineid =tipo de cocina.`cuisineid` DONDE tipo de cocina.`cuisine` ='Italiano') ORDENAR POR restaurants.`name`LIMITE 20 

Ya que esto es DE restaurantes , comenzaremos con el administrador de objetos predeterminado de ese modelo, objects :

Restaurante.objetos 

El DÓNDE cláusula en este caso es un filter() call, por lo que lo sumamos para el primer término:

Restaurante.objetos.filtro(ciudad=8) 

Puede tener un valor de clave principal o una City objeto en el lado derecho de ese término. Sin embargo, el resto de la consulta se vuelve más compleja porque necesita JOIN . Una unión en Django simplemente parece una desreferenciación a través del campo de relación. En una consulta, eso significa unir los nombres de los campos relevantes con un doble guión bajo:

Restaurante.objetos.filtro(ciudad=8, tipo_de_cocina__nombre="Italiano") 

Django sabe en qué campos unirse porque eso está declarado en Cuisine mesa que es arrastrada por el through=Cuisine parámetro en cuisine_types . también sabe hacer una subconsulta porque estás pasando por una relación M2M.

Entonces eso nos da SQL equivalente a:

SELECCIONE restaurants.`name`, restaurants.`address`FROM restaurantsWHERE city_id =8 Y restaurants.id IN ( SELECCIONE res_id FROM cuisine ÚNASE a cookingtype ON cooking.cuisineid =cookingtype.`cuisineid` WHERE cookingtype.`cuisine ` ='italiano') 

A mitad de camino. Ahora necesitamos SELECT DISTINCT para que no obtengamos varias copias del mismo registro:

Restaurante.objetos.filtro(ciudad=8, tipo_cocina__nombre="Italiano").distinct() 

Y debe seleccionar los tipos de cocina para mostrarlos. Resulta que la consulta que tiene es ineficiente allí, porque solo lo lleva a la tabla de unión y necesita ejecutar más consultas para obtener el CuisineType relacionado registros. Adivina qué:Django te tiene cubierto.

(Restaurante.objetos.filter(ciudad=8, tipo_de_cocina__nombre="Italiano").distinct() .prefetch_related("tipos_de_cocina")) 

Django ejecutará dos consultas:una como la suya para obtener los ID conjuntos y otra más para obtener el CuisineType relacionado. registros. Luego, los accesos a través del resultado de la consulta no necesitan volver a la base de datos.

Las dos últimas cosas son el orden:

(Restaurante.objetos.filter(ciudad=8, tipo_de_cocina__nombre="Italiano").distinct() .prefetch_related("tipos_de_cocina").order_by("nombre")) 

Y el LIMIT :

(Restaurante.objetos.filter(ciudad=8, tipo_de_cocina__nombre="Italiano").distinct() .prefetch_related("tipos_de_cocina").order_by("nombre")[:20]) 

Y ahí está su consulta (y la consulta relacionada) empaquetada en dos líneas de Python. Eso sí, en este punto, la consulta ni siquiera se ha ejecutado. Tienes que ponerlo en algo, como una plantilla, antes de que haga algo:

. prefetch_related("tipos_de_cocina").order_by("nombre")[:20]) })

Plantilla:

{% para restaurante en búsqueda de cocina %}

{{ restaurante.nombre }}

{{ restaurante.ubicación }}

Tipos de cocina:

    {% for ct in restaurant.cuisine_types.all %}
  • {{ ct.name }}
  • {% endfor %}
{ % fin de %}