Skip to content

Instantly share code, notes, and snippets.

@robertowest
Last active May 31, 2021 20:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save robertowest/4b7170ba82db003322c0a579cd20dc81 to your computer and use it in GitHub Desktop.
Save robertowest/4b7170ba82db003322c0a579cd20dc81 to your computer and use it in GitHub Desktop.
relaciones con django orm

Django ORM: select_related y prefetch_related

select_related() "sigue" relaciones de clave externa, seleccionando datos adicionales de objetos relacionados cuando ejecuta su consulta.

prefetch_related() realiza una búsqueda por separado para cada relación y realiza la "unión" en Python.

Utiliza select_related cuando el objeto que va a seleccionar es un solo objeto, por lo que OneToOneField o ForeignKey. Utiliza prefetch_related cuando vas a obtener un "conjunto" de cosas, así que ManyToManyFields o inviertes ForeignKeys. Solo para aclarar lo que quiero decir con "revertir ForeignKeys" aquí hay un ejemplo:

class ModelA(models.Model):
    pass

class ModelB(models.Model):
    a = ForeignKey(ModelA)

ModelB.objects.select_related('a').all()            # Forward ForeignKey relationship
ModelA.objects.prefetch_related('modelb_set').all() # Reverse ForeignKey relationship

La diferencia es que select_related realiza una unión SQL y, por lo tanto, recupera los resultados como parte de la tabla del servidor SQL. Por otro lado, prefetch_related ejecuta otra consulta y, por lo tanto, reduce las columnas redundantes en el objeto original (ModelA en el ejemplo anterior). Puede usar prefetch_related para cualquier cosa que pueda usar select_related para.

Las ventajas y desventajas son que prefetch_related tiene que crear y enviar una lista de ID para seleccionar de nuevo al servidor, esto puede llevar un tiempo. No estoy seguro de si hay una buena forma de hacer esto en una transacción, pero tengo entendido que Django siempre envía una lista y dice SELECCIONAR ... DÓNDE ENTRAR pk (..., ..., ...) básicamente. En este caso, si los datos precargados son escasos (digamos los objetos del estado de los EE. UU. Vinculados a las direcciones de las personas), esto puede ser muy bueno; sin embargo, si está más cerca de uno a uno, puede desperdiciar muchas comunicaciones. En caso de duda, pruebe ambos y vea cuál funciona mejor.

Todo lo discutido anteriormente es básicamente sobre las comunicaciones con la base de datos. En el lado de Python, sin embargo, prefetch_related tiene el beneficio adicional de que se utiliza un solo objeto para representar cada objeto en la base de datos. Con select_related se crearán objetos duplicados en Python para cada objeto "padre". Como los objetos en Python tienen una sobrecarga de memoria decente, esto también puede ser una consideración.


Ambos métodos logran el mismo propósito, para evitar consultas db innecesarias. Pero utilizan diferentes enfoques para la eficiencia.

La única razón para usar cualquiera de estos métodos es cuando una sola consulta grande es preferible a muchas consultas pequeñas. Django usa la consulta grande para crear modelos en memoria de forma preventiva en lugar de realizar consultas a pedido en la base de datos.

select_related realiza una combinación con cada búsqueda, pero extiende la selección para incluir las columnas de todas las tablas unidas. Sin embargo, este enfoque tiene una advertencia.

Las combinaciones tienen el potencial de multiplicar el número de filas en una consulta. Cuando realiza una unión sobre una clave externa o un campo de uno a uno, el número de filas no aumentará. Sin embargo, las uniones de muchos a muchos no tienen esta garantía. Entonces, Django restringe select_related a relaciones que no resultarán inesperadamente en una unión masiva.

El "join in python" para prefetch_related es un poco más alarmante de lo que debería ser. Crea una consulta separada para cada tabla a unir. Filtra cada una de estas tablas con una cláusula WHERE IN, como:

SELECT "credential"."id",
       "credential"."uuid",
       "credential"."identity_id"
FROM   "credential"
WHERE  "credential"."identity_id" IN
    (84706, 48746, 871441, 84713, 76492, 84621, 51472);

En lugar de realizar una única unión con potencialmente demasiadas filas, cada tabla se divide en una consulta separada.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment