2011-01-06 34 views
5

Soy un recién llegado al SQLAlchemy ORM y tengo dificultades para realizar consultas complejas en varias tablas, consultas que me resulta relativamente sencillo de hacer en Doctrine DQL.Cómo consultar varias tablas en SQLAlchemy ORM

Tengo objetos de datos de Ciudades, que pertenecen a Países. Algunas ciudades también tienen un conjunto de identificación del condado, pero no todas. Además de las claves primarias y externas necesarias, cada registro también tiene un text_string_id, que se vincula a una tabla TextStrings que almacena el nombre de la ciudad/condado/país en diferentes idiomas. La tabla TextStrings MySQL se ve así:

CREATE TABLE IF NOT EXISTS `text_strings` (
    `id` INT UNSIGNED NOT NULL, 
    `language` VARCHAR(2) NOT NULL, 
    `text_string` varchar(255) NOT NULL, 
    PRIMARY KEY (`id`, `language`) 
) 

Quiero construir una ruta de navegación para cada ciudad, de la forma:

country_en_name> city_en_name O

country_en_name> county_en_name> city_en_name,

dependiendo de si un atributo del Condado está establecido o no para esta ciudad. En Doctrina esto sería relativamente sencillo:

$query = Doctrine_Query::create() 
       ->select('ci.id, CONCAT(cyts.text_string, \'> \', IF(cots.text_string is not null, CONCAT(cots.text_string, \'> \', \'\'), cits.text_string) as city_breadcrumb') 
       ->from('City ci') 
       ->leftJoin('ci.TextString cits') 
       ->leftJoin('ci.Country cy') 
       ->leftJoin('cy.TextString cyts') 
       ->leftJoin('ci.County co') 
       ->leftJoin('co.TextString cots') 
       ->where('cits.language = ?', 'en') 
       ->andWhere('cyts.language = ?', 'en') 
       ->andWhere('(cots.language = ? OR cots.language is null)', 'en'); 

Con SQLAlchemy ORM, estoy luchando para lograr lo mismo. Creo que he configurar correctamente los objetos - en forma por ejemplo:

class City(Base): 
    __tablename__ = "cities" 

    id = Column(Integer, primary_key=True) 
    country_id = Column(Integer, ForeignKey('countries.id')) 
    text_string_id = Column(Integer, ForeignKey('text_strings.id')) 
    county_id = Column(Integer, ForeignKey('counties.id')) 

    text_strings = relation(TextString, backref=backref('cards', order_by=id)) 
    country = relation(Country, backref=backref('countries', order_by=id)) 
    county = relation(County, backref=backref('counties', order_by=id)) 

Mi problema está en la consulta - He intentado varios enfoques para la generación de la miga de pan, pero nada parece funcionar. Algunas observaciones:

Quizás usar cosas como CONCAT e IF en línea en la consulta no es muy pitónico (¿es posible con el ORM?) - así que he intentado realizar estas operaciones fuera de SQLAlchemy, en un bucle de Python del archivos. Sin embargo, aquí he tenido problemas para acceder a los campos individuales; por ejemplo, los usuarios de los modelos no parecen tener niveles n profundos, p. City.counties.text_strings.language no existe.

también he experimentado con el uso tuplas - el más cercano Tengo que hacerlo funcionar fue dividiendo a cabo en dos consultas:

# For cities without a county 
for city, country in session.query(City, Country).\ 
    filter(Country.id == City.country_id).\ 
    filter(City.county_id == None).all(): 

    if city.text_strings.language == 'en': 
    # etc 

# For cities with a county 
for city, county, country in session.query(City, County, Country).\ 
    filter(and_(City.county_id == County.id, City.country_id == Country.id)).all(): 

    if city.text_strings.language == 'en': 
    # etc 

que se dividió en dos consultas a cabo porque no podía averiguar cómo hacer que la combinación de Trajes sea opcional en solo una consulta. Pero este enfoque es, por supuesto, terrible y peor, la segunda consulta no funcionó al 100%: no se unió a todas las diferentes cadenas de ciudades.Características para el filtrado posterior.

¡Así que estoy perplejo! Me agradecería cualquier ayuda que pueda brindarme, configurándome en el camino correcto para realizar este tipo de consultas complejas en SQLAlchemy ORM.

Respuesta

5

La asignación para Suit no está presente, pero en función de la consulta de propel supongo que tiene un atributo text_strings.

La parte pertinente de la documentación SQLAlchemy describir alias con las combinaciones esté en:

http://www.sqlalchemy.org/docs/orm/tutorial.html#using-aliases

generación de funciones está en:

http://www.sqlalchemy.org/docs/core/tutorial.html#functions

cyts = aliased(TextString) 
cits = aliased(TextString) 
cots = aliased(TextString) 
cy = aliased(Suit) 
co = aliased(Suit) 

session.query(
      City.id, 
      (
       cyts.text_string + \ 
       '> ' + \ 
       func.if_(cots.text_string!=None, cots.text_string + '> ', cits.text_string) 
      ).label('city_breadcrumb') 
      ).\ 
      outerjoin((cits, City.text_strings)).\ 
      outerjoin((cy, City.country)).\ 
      outerjoin((cyts, cy.text_strings)).\ 
      outerjoin((co, City.county))\ 
      outerjoin((cots, co.text_string)).\ 
      filter(cits.langauge=='en').\ 
      filter(cyts.langauge=='en').\ 
      filter(or_(cots.langauge=='en', cots.language==None)) 

aunque yo creo que es una muchísimo más simple decir:

city.text_strings.text_string + " > " + city.country.text_strings.text_string + " > " city.county.text_strings.text_string 

Si pones un descriptor en la Ciudad, Traje:

class City(object): 
    # ... 
    @property 
    def text_string(self): 
     return self.text_strings.text_string 

entonces usted podría decir city.text_string.

+0

Muchísimas gracias Mike por esta respuesta - ¡Debería haber sabido usar alias! Una vez que actualicé mi SQLAlchemy a una versión más nueva, tu código funcionó bien. Al final, he adaptado tu código un poco: pegaré mi código a continuación como una respuesta separada en caso de que alguien quiera verlo. Un último punto: dices: "Creo que es muchísimo más simple decir: city.text_strings.text_string ..." Intenté hacer algo como esto, pero esta sintaxis no parecía respetar los enlaces externos: es decir, las propiedades text_string fueron para el lenguaje == 'de' en lugar del idioma == 'en'. ¡No estoy seguro de si estaba haciendo algo mal! –

0

Solo para el registro, este es el código que terminé usando. La respuesta de Mike (zzzeek) permanece como la respuesta correcta y definitiva porque esta es solo una adaptación de la suya, que fue el gran avance para mí.

cits = aliased(TextString) 
cyts = aliased(TextString) 
cots = aliased(TextString) 

for (city_id, country_text, county_text, city_text) in \ 
    session.query(City.id, cyts.text_string, cots.text_string, cits.text_string).\ 
    outerjoin((cits, and_(cits.id==City.text_string_id, cits.language=='en'))).\ 
    outerjoin((County, City.county)).\ 
    outerjoin((cots, and_(cots.id==County.text_string_id, cots.language=='en'))).\ 
    outerjoin((Country, City.country)).\ 
    outerjoin((cyts, and_(cyts.id==Country.text_string_id, cyts.language=='en'))): 

    # Python to construct the breadcrumb, checking county_text for None-ness 
Cuestiones relacionadas