2011-01-06 1 views
5

Sono un nuovo arrivato a SQLAlchemy ORM e sto faticando a realizzare query ish complesse su più tabelle - query che trovo relativamente semplici da fare in Doctrine DQL.Come interrogare più tabelle in SQLAlchemy ORM

Ho oggetti dati di Città, che appartengono a Paesi. Alcune città hanno anche un ID di contea, ma non tutte. Oltre alle chiavi primarie e straniere necessarie, ogni record ha anche un text_string_id, che si collega a una tabella TextStrings che memorizza il nome della città/contea/nazione in diverse lingue. La tabella TextStrings MySQL si presenta così:

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`) 
) 

Voglio costruire un breadcrumb per ogni città, della forma:

country_en_name> city_en_name O

country_en_name> county_en_name> city_en_name,

a seconda che sia impostato o meno un attributo County per questa città. In Dottrina questo sarebbe relativamente semplice:

$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, sto lottando per ottenere la stessa cosa. Credo Ho installato gli oggetti in modo corretto - in forma ad esempio:

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)) 

Il mio problema è nella interrogazione - ho provato vari approcci per la generazione del breadcrumb, ma nulla sembra funzionare. Alcune osservazioni:

Forse usare cose come CONCAT e IF inline nella query non è molto pitone (è anche possibile con l'ORM?) - quindi ho provato a eseguire queste operazioni al di fuori di SQLAlchemy, in un loop Python del record. Comunque qui ho faticato ad accedere ai singoli campi - ad esempio, i model accessors non sembrano andare in profondità n livelli, ad es. City.counties.text_strings.language non esiste.

Ho anche sperimentato con l'utilizzo di tuple - la più vicina che ho avuto modo di farlo funzionare è stato suddividendo fuori in due query:

# 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 

ho diviso fuori in due query perché non potevo capire come rendere la Tuta unirsi opzionale in una sola query. Ma questo approccio è ovviamente terribile e peggio la seconda query non ha funzionato al 100% - non si stava unendo a tutte le diverse city.text_strings per il successivo filtraggio.

Quindi sono perplesso! Sarebbe molto gradito qualsiasi aiuto che tu possa darmi nel percorso giusto per eseguire questo tipo di query ish complesse in SQLAlchemy ORM.

risposta

5

La mappatura per Suit non è presente ma in base alla query propel suppongo che abbia un attributo text_strings.

La parte rilevante della documentazione SQLAlchemy descrivere gli alias con join e ':

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

generazione di funzioni e':

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)) 

anche se vorrei che sia un diamine molto più semplice da dire:

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

Se si mette un descrittore sulla Città, Completo:

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

allora si potrebbe dire city.text_string.

+0

Enorme, grazie Mike per questa risposta: avrei dovuto sapere usare gli alias! Una volta aggiornato il mio SQLAlchemy a una versione più recente il tuo codice ha funzionato bene. Alla fine ho adattato leggermente il tuo codice - incollerò il mio codice qui sotto come risposta separata nel caso qualcuno volesse vederlo. Un ultimo punto: tu dici: "Penso che sia molto più semplice solo per dire: city.text_strings.text_string ..." Ho provato a fare qualcosa di simile, ma questa sintassi non sembrava rispettare i join esterni - cioè le proprietà text_string erano per lingua == 'de' invece della lingua == 'en'. Non sono sicuro se stavo facendo qualcosa di sbagliato! –

0

Solo per la cronaca, ecco il codice che ho finito usando. La risposta di Mike (zzzeek) rimane la risposta corretta e definitiva perché questo è solo un suo adattamento, che è stata la svolta per me.

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