【问题标题】:How to query multiple tables in SQLAlchemy ORM如何在 SQLAlchemy ORM 中查询多个表
【发布时间】:2011-01-16 21:41:38
【问题描述】:

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

我有属于国家的城市数据对象。一些城市也有一个县 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 或

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% 起作用——它没有加入所有不同的 city.text_strings 以进行后续过滤。

所以我被难住了!您能给我的任何帮助,让我在 SQLAlchemy ORM 中执行此类复杂查询的正确路径将不胜感激。

【问题讨论】:

    标签: python doctrine sqlalchemy


    【解决方案1】:

    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
    

    如果你把描述词放在城市,西装:

    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'。我不确定我是否做错了什么!
    【解决方案2】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-05-02
      • 2021-10-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多