es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

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.

Tags:  , ,

Answer

  1. Avatar for 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:

    parent = session.query(Parent).get(parent_id)
    

    Luego, puedes utilizar populate_existing() para cargar a sus hijos y nietos en una sola consulta:

    session.query(Child).\
        options(selectinload(Child.grandchildren)).\
        join(Child.parent).\
        filter(Parent.id == parent.id).\
        populate_existing()
    

    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 original parent sin emitir consultas adicionales.

    Ten en cuenta que, dado que estás especificando el parámetro parent_id en el método filter(), 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.

Comments are closed.