Как запросить несколько таблиц в SQLAlchemy ORM - PullRequest
5 голосов
/ 07 января 2011

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

У меня есть объекты данных городов, которые принадлежат странам.В некоторых городах также есть идентификационный номер округа, но не все.Помимо необходимых первичных и внешних ключей, каждая запись также имеет text_string_id, который ссылается на таблицу TextStrings, в которой хранится название города / округа / страны на разных языках.Таблица MySQL TextStrings выглядит следующим образом:

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

Я хочу создать хлебную крошку для каждого города в форме:

имя_ страны_имя_город_ИЛИ ИЛИ

имя_области_имя> county_en_name> city_en_name,

в зависимости от того, установлен или нет атрибут County для этого города.В Doctrine это было бы относительно просто:

    $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 inline, не очень питонично (возможно ли даже с 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, была бы очень признательна.

Ответы [ 2 ]

5 голосов
/ 15 января 2011

Сопоставление для 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

Если вы поместите дескриптор в City, Suit:

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

, тогда вы можете сказать city.text_string.

0 голосов
/ 16 января 2011

Просто для записи, вот код, который я использовал в итоге.Ответ Майка (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
...