2011-01-06 2 views
5

Я новичок в ORM SQLAlchemy, и я изо всех сил пытаюсь выполнить сложные запросы на нескольких таблицах - запросы, которые я считаю относительно простыми в Doctrine DQL.Как запросить несколько таблиц в SQLAlchemy ORM

У меня есть объекты данных городов, которые принадлежат странам. Некоторые города также имеют идентификационный номер округа, но не все. Как и необходимые первичные и внешние ключи, каждая запись также имеет 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 ИЛИ

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 ОРМ, я изо всех сил, чтобы достичь того же. Я считаю, что я установки объектов правильно - в форме, например:

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 inline в запросе, не очень 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 

Я разделил его на два запроса, потому что я не мог выясните, как сделать членство Suit необязательным только в одном запросе. Но этот подход, конечно, страшный и худший, второй запрос не работал на 100% - он не соединял все разные city.text_strings для последующей фильтрации.

Так что я в тупике! Любая помощь, которую вы можете дать мне, чтобы установить меня на правильном пути для выполнения подобных видов сложных запросов в SQLAlchemy ORM, будет очень признательна.

ответ

5

Отображение для Suit нет, но на основе запроса propel я бы предположил, что он имеет атрибут 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 

Если поместить дескриптор на Сити, Suit:

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'. Я не уверен, что я делаю что-то неправильно! –

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