Forzar la unión interna con una relación de muchos a muchos en sqlalchemy

Estoy usando declarative_base() y tratando de descubrir cómo hacer cumplir una unión interna perezosa cuando uso una relación de muchos a muchos.

Tengo tablas en MySQL e InnoDB con restricciones de clave externa definidas.

users: userid, name
permissions: permissionid, name
users_permissions: userid, permissionid

Estoy usando metadata.reflect() para cargar mi base de datos.


class User_Perm(Base):
    __table__ = Base.metadata.tables['users_permissions']

class User(Base):
    __table__ = Base.metadata.tables['users']

    permissions = orm.relationship('Permission',
            secondary=User_Perm.__table__,
            order_by='Perm.name',
            innerjoin=True,
            lazy=True,
            )

class Permission(Base):
    __table__ = Base.metadata.tables['permissions']

Siempre que selecciono

u = Session().query(User).filter(User.name == 'myuser').first()
u.permissions

La consulta recibida por el servidor MySQL es:

SELECT permissions.permissionid AS permissions_permissionid,
     permissions.name AS permissions_name
FROM permissions, users_permissions 
WHERE ? = users_permissions.userid
    AND permissions.permissionid = users_permissions.permissionid
ORDER BY permissions.name

Como podemos ver, el FROM permissions, users_permissions no es una unión interna. ¿Puedo forzar esto sin necesidad de usar lazy=False porque si lo hago, el efecto cascada será cargar demasiada información como permissions también están en relación con otra tabla (no mencionada en el ejemplo) y users también está relacionado con otras tablas (nuevamente, no se menciona en el ejemplo). Me gustaría usar la misma plantilla de relación para todas mis clases.

EDIT: CONTEXTO Estoy tratando de replicar todas las consultas SQL para que coincidan con las del sistema actual. Estoy tratando de migrar de oursql a sqlalchemy orm.

preguntado el 03 de mayo de 12 a las 18:05

si necesita una coincidencia de carácter por carácter entre dos aplicaciones, una que utiliza consultas escritas a mano y la otra un ORM, se sentirá decepcionado. El ORM de SQLAlchemy ciertamente puede reproducir consultas que son estructuralmente muy similares, pero algunas características, como la relación de carga diferida, por su naturaleza, no tienen espacio para que la consulta se adapte por completo. Como se mencionó, querrá comparar la salida EXPLAIN de MySQL con INNER JOIN frente a la combinación implícita anterior para verificar un rendimiento equivalente. -

1 Respuestas

MySQL is haciendo una unión interna; es decir, la consulta que ha mostrado es una implementación de una combinación interna.

Desde el documentos:

innerjoin=False: cuando es verdadero, las cargas ansiosas unidas usarán una unión interna para unirse a las tablas relacionadas en lugar de una unión externa. El propósito de esta opción es estrictamente de rendimiento, ya que las uniones internas generalmente funcionan mejor que las uniones externas. Esta marca se puede establecer en True cuando la relación hace referencia a un objeto a través de muchos a uno utilizando claves externas locales que no admiten valores NULL, o cuando la referencia es uno a uno o una colección que tiene garantizado tener uno o al menos una entrada

El punto clave es unión interna frente a unión externa. La sintaxis es irrelevante.

Tenga en cuenta que su consulta se puede convertir en una consulta utilizando el inner join forma sintáctica que tiene exactamente el mismo significado:

SELECT permissions.permissionid AS permissions_permissionid,
     permissions.name AS permissions_name
FROM permissions
INNER JOIN users_permissions 
    ON permissions.permissionid = users_permissions.permissionid
WHERE ? = users_permissions.userid
ORDER BY permissions.name

MySQL generalmente generará un plan de ejecución idéntico para las dos formas.

contestado el 23 de mayo de 17 a las 13:05

¿Está diciendo que detrás de escena, tengo la garantía de que MySQL tratará este tipo de consultas como una unión interna? Entiendo que el conjunto de resultados es el mismo, pero no pensé que el optimizador siempre lo garantizaría. - Danosaurio

@Danosaure, digo que tu consulta es siempre lógicamente equivalente a una consulta escrita usando inner join -- es decir, devolverá el exacto mismos datos. Además, el plan de ejecución que genera MySQL suele ser idéntico, como se puede comprobar con explain. - mate fenwick

@Danosaure Es posible que haya malinterpretado un poco su problema. ¿Podría aclarar? ¿No es suficiente que las consultas sean lógicamente equivalentes? - mate fenwick

Mi preocupación era el "normalmente". Lo que entiendo de su respuesta es que sqlalchemy da esta declaración de selección porque sabe que usa MySQL y que MySQL "optimizará" la consulta para el plan de ejecución. Si es así, ¿no hubiera sido más simple usar siempre la sintaxis de unión de SQL-92? - Danosaurio

Lazyloading no usa una unión primero porque en la gran mayoría de los casos se carga desde exactamente una tabla. en el caso menos común de que esté presente una tabla "secundaria", utiliza una combinación natural que simplifica las cosas. no debería haber diferencias significativas de optimización. ejecutar EXPLAIN es su mejor opción. - zzzeek

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.