HOWTO · Django

Método select_related en Django

El artículo explica qué es un QuerySet, cómo trabajar con él y cómo usar el método select_related() en Django para cargar un objeto relacionado junto con la consulta, sin filtrar filas.

En esta página

Usa select_related() cuando un QuerySet de Django vaya a leer un objeto relacionado de valor único, como el departamento de un empleado. Carga ese objeto junto con la consulta, de modo que los accesos posteriores puedan evitar consultas separadas para obtener objetos relacionados; no filtra filas.

select_related() es un método de QuerySet que indica a Django que recupere los objetos relacionados en la misma consulta a la base de datos que los objetos del QuerySet. Resulta útil cuando el código va a leer una relación de valor único, como el departamento de un empleado. Django combina la tabla relacionada y carga el objeto relacionado, por lo que acceder a él no requiere una consulta aparte por cada empleado.

Este método cambia cómo se cargan los datos relacionados; no filtra empleados ni departamentos. Para elegir filas, usa filtros normales de QuerySet, como filter(). El nombre select_related() describe cómo seguir una relación entre modelos al seleccionar objetos, no cómo restringir las filas relacionadas que coinciden.

Entender el problema de las consultas a objetos relacionados

Supongamos que un proyecto de Django tiene los modelos Department y Employee, y que Employee.department es una ForeignKey a Department. Un QuerySet como Employee.objects.all() recupera inicialmente los empleados. Si después un bucle lee employee.department.name, Django puede emitir otra consulta para cargar el departamento de un empleado cuando ese objeto aún no está disponible en la instancia del modelo.

Los accesos repetidos pueden crear el patrón de consultas N+1: una consulta recupera los empleados y, luego, se ejecutan consultas adicionales para obtener los departamentos relacionados a medida que se accede a ellos. El total exacto depende de qué relaciones se consultan, de si los objetos ya están cargados o se reutilizan y del comportamiento del resto de la consulta. No es correcto prometer un número fijo, como 101, para cualquier conjunto de 100 empleados. Varios empleados pueden pertenecer al mismo departamento y el total depende de cómo se usen el QuerySet y sus objetos.

Para la clave externa asumida, añade al QuerySet el nombre del campo de relación. En una vista de Django, la expresión pertinente es Employee.objects.all().select_related("department"). Después, recorre los empleados devueltos y lee employee.department.name cuando lo necesites. Django obtiene los datos del departamento junto con cada fila de empleado mediante una combinación SQL.

La relación entre modelos debe existir, y la cadena que se pasa a select_related() debe ser un campo de relación, no un atributo de presentación como department.name. Si el campo ForeignKey tiene un nombre personalizado, usa el nombre de ese campo. También puedes encadenar el método con otros métodos de QuerySet; devuelve un QuerySet, así que puedes combinarlo con filtros y ordenamientos.

El siguiente ejemplo independiente de Python representa la diferencia en el número de búsquedas para cuatro empleados y dos departamentos. Solo ilustra la idea: no ejecuta Django ni mide consultas a la base de datos. Ejecuta este ejemplo:

El código y las etiquetas o salidas visibles de esta sección se mantienen en inglés.

employees = [
    {"name": "Jane", "department_id": 1},
    {"name": "John", "department_id": 2},
    {"name": "Sam", "department_id": 1},
    {"name": "Lee", "department_id": 2},
]
departments = {1: "HR", 2: "Accounts"}

separate_lookups = 1 + len(employees)
joined_lookups = 1
print(f"Separate related lookups: {separate_lookups}")
print(f"Related data loaded with employee query: {joined_lookups}")
print(f"First employee's department: {departments[employees[0]['department_id']]}")

En la aplicación real, usa el método de QuerySet de Django en lugar de esta ilustración en memoria. La ilustración cuenta deliberadamente una búsqueda relacionada por empleado en el caso de carga por separado; el número real de consultas puede variar, ya que la caché de relaciones de Django y el patrón de acceso del código afectan a lo que se obtiene.

Comprobar el comportamiento y el número de consultas

Inspecciona el SQL generado para un QuerySet con su atributo query; por ejemplo, print(Employee.objects.select_related("department").query). Esto sirve para comprobar si la tabla relacionada aparece en la consulta, pero muestra la representación del SQL, no el número de consultas ejecutadas durante una solicitud completa.

Para medir las consultas de una solicitud en un proyecto de desarrollo, usa Django Debug Toolbar o utilidades de prueba de Django, como assertNumQueries(), alrededor del código que quieras probar. Compara la misma operación antes y después de añadir select_related(), con los mismos datos en la base de datos y el mismo patrón de acceso. Al interpretar el total, ten en cuenta la configuración de la base de datos, el middleware y las demás consultas de la solicitud. La mejora esperada es reducir las consultas independientes para obtener objetos relacionados cuando el código sigue la relación de valor único seleccionada; el método no garantiza que toda la solicitud use una sola consulta.

Elegir el método adecuado para cargar relaciones

select_related() está pensado para relaciones de valor único: rutas ForeignKey y OneToOneField. Combina esas filas relacionadas en la consulta SQL. Es posible seguir varias relaciones de valor único indicando la ruta, por ejemplo select_related("department__manager"), siempre que esos campos existan en los modelos.

Para relaciones con colecciones, como las claves externas inversas y los campos de muchos a muchos, usa prefetch_related(). Este método obtiene las filas relacionadas mediante consultas separadas y las combina en Python, evitando un resultado combinado grande con datos del objeto principal repetidos. Elige según la relación y los datos que vaya a consultar el código: usa select_related() para el único departamento de un empleado y prefetch_related() cuando un empleado necesite una colección, como varios proyectos. Ambos métodos ayudan a evitar cargas repetidas e ineficientes, pero emplean estrategias de consulta distintas.