SQLAlchemy: Cargar relaciones en cascada con el menor número de consultas (problema N+1)
Al leer la documentación de SQLAlchemy, estoy teniendo dificultades para encontrar una solución a este problema:
Tengo un modelo de base de datos muy básico, con una entidad Padre que tiene una relación uno a muchos con una entidad Hijo, la cual a su vez tiene una relación uno a muchos con una entidad Nieto. Estas relaciones están definidas utilizando el comportamiento de carga diferida por defecto de SQLAlchemy, ya que el 99% del tiempo tiene sentido retrasar la carga de estas relaciones.
Sin embargo, en ese otro 1% del tiempo, se necesita realizar una operación costosa en una instancia de Padre que requiere cargar todos sus hijos y nietos. Me gustaría hacer esto en la menor cantidad de consultas posible, idealmente solo 2:
SELECT * FROM child WHERE parent_id != ?
SELECT * FROM grandchild WHERE child_id IN (?)
Sé cómo hacer esto cuando estoy construyendo una declaración Query desde cero, encadenando múltiples funciones selectinload a Query.options:
parent = session.query(Parent).options(
selectinload(Parent.children).selectinload(Child.grandchildren)
).get(parent_id)
Sin embargo, si ya tengo la instancia de Padre cargada en la memoria, ¿cómo puedo desencadenar la carga de su relación Padre.hijos e Hijo.nietos de la misma manera que la consulta anterior? Como una forma de anular temporalmente la estrategia de carga definida para la relación, o una forma de especificar el método Query.options (…) anterior, pero sin crear una declaración Query, porque mi punto de partida es una instancia de Padre ya cargada, no un parent_id.
davy.ai
Una posible solución es utilizar el método
populate_existing()
proporcionado por SQLAlchemy. Este método te permite poblar los atributos de relación de un objeto existente sin emitir consultas adicionales a la base de datos.Para utilizar este método, primero puedes cargar la instancia del padre con su comportamiento de carga diferida por defecto:
Luego, puedes utilizar
populate_existing()
para cargar a sus hijos y nietos en una sola consulta:Esta consulta carga a todos los hijos del padre dado e incluye a sus nietos en el conjunto de resultados. Al pasar el resultado a
populate_existing()
, SQLAlchemy pobla las relaciones correspondientes del objeto originalparent
sin emitir consultas adicionales.Ten en cuenta que, dado que estás especificando el parámetro
parent_id
en el métodofilter()
, esta consulta es específica para el padre dado y sus hijos. Si tienes varios objetos padres en memoria de los cuales deseas cargar a sus hijos y nietos, necesitarás repetir este proceso para cada uno.