2011-01-06 58 views
5

我是SQLAlchemy ORM的新手,我正努力在多個表上完成複雜的查詢 - 我發現在Doctrine DQL中執行相對簡單的查詢。如何在SQLAlchemy中查詢多個表ORM

我有屬於國家的城市的數據對象。一些城市也有一個縣的ID設置,但不是全部。除了必要的主鍵和外鍵以外,每條記錄還有一個text_string_id,它鏈接到用不同語言存儲城市/縣/國家名稱的TextStrings表。該TextStrings MySQL表看起來是這樣的:

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

我想構建一個麪包屑爲每個城市,下面的形式:

country_en_name> city_en_name OR

country_en_name> county_en_name> city_en_name,

取決於是否爲該城市設置了縣屬性。在學說這將是相對簡單的:

$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'); 

與SQLAlchemy ORM,我努力實現同樣的事情。我相信我正確地已經設置的對象 - 形式如:

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

我的問題是在查詢 - 我已經試過各種方法來產生的痕跡,但似乎沒有任何工作。一些觀察:

也許在查詢中使用像CONCAT和IF這樣的東西並不是很pythonic(它甚至可以用ORM嗎?) - 所以我嘗試在SQLAlchemy之外的Python循環中執行這些操作記錄。然而,在這裏我一直在努力訪問各個領域 - 例如,模型訪問器似乎並不深入n層,例如, City.counties.text_strings.language不存在。

我也使用元組實驗 - 我得給它的工作最接近的是通過拆分出來到兩個查詢:

# 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 

我把它分解出到兩個查詢,因爲我不能弄清楚如何在一個查詢中使西裝加入可選。但是這種方法當然是可怕的,而且更糟的是第二個查詢不能100%工作 - 它沒有加入所有不同的city.text_strings以進行後續過濾。

所以我很難倒!你可以給我任何幫助,讓我在SQLAlchemy ORM中執行這些複雜ISH查詢的正確途徑,我將非常感激。

回答

5

Suit的映射不存在,但基於推進查詢,我假設它具有text_strings屬性。

SQLAlchemy的文檔的描述與加入的別名相關的部分是:

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

代的功能是:

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

雖然我認爲它是一個很簡單,只是說:

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

如果你把一個描述符上的城市,西裝:

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

,那麼你可以說city.text_string

+0

巨大的感謝邁克爲這個答案 - 我應該知道使用別名!一旦我將SQLAlchemy升級到更新的版本,您的代碼就可以正常工作。最後,我修改了你的代碼 - 我將在下面粘貼我的代碼作爲單獨的答案,以防有人想看到它。最後一點:你說,「我認爲它只是簡單地說:city.text_strings.text_string ...」我試着做這樣的事情,但是這種語法似乎並不尊重外部聯盟 - 即text_string屬性用於語言=='de'而不是語言=='en'。我不確定我做錯了什麼! – 2011-01-16 13:27:31

0

只是爲了記錄,這裏是我最終使用的代碼。邁克(zzzeek)的答案仍然是正確的和明確的答案,因爲這只是他的改編,這對我來說是一個突破。

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