如何在SQLAlchemy ORM中查询多个表格

5

我是SQLAlchemy ORM的新手,我正在努力完成多个表上的复杂查询 - 这些查询在Doctrine DQL中相对简单。

我有城市数据对象,这些对象属于国家。一些城市还设置了县ID,但并非所有城市都设置了。除了必要的主键和外键之外,每个记录还具有text_string_id,该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,
具体取决于此城市是否设置了县属性。在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的操作不太符合Python的风格(使用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%工作-它没有连接所有不同的城市文本字符串以进行后续过滤。

所以我被难住了!如果您可以帮助我找到正确的路径来执行这些复杂的查询,那将不胜感激。

2个回答

5

根据 Propel 查询,我推测 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

如果您在 City 和 Suit 上添加一个描述符:
class City(object):
   # ...
   @property
   def text_string(self):
      return self.text_strings.text_string

那么您可以说:city.text_string


非常感谢Mike提供的答案 - 我应该知道要使用别名!一旦我将SQLAlchemy升级到较新的版本,您的代码就可以正常工作了。最终,我稍微修改了您的代码 - 我将我的代码粘贴在下面作为单独的答案,以防有人想看。最后一个要点:您说,“我认为只需要简单地说:city.text_strings.text_string ...”我尝试过类似的操作,但是这种语法似乎没有考虑到外连接 - 即text_string属性是针对language =='de'而不是language =='en'。我不确定我是否做错了什么! - Alex Dean

0

仅供记录,这是我最终使用的代码。Mike(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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接