SQLAlchemy 如何使用声明性映射一对一关系的单列

Posted

技术标签:

【中文标题】SQLAlchemy 如何使用声明性映射一对一关系的单列【英文标题】:SQLAlchemy How to map single column of one-to-one relationship using declarative 【发布时间】:2014-10-28 16:25:58 【问题描述】:

这与这个问题converting to declarative method and column property有关,一直没有回答。

我们正在尝试在现有模式(我们无法更改的模式)上建立一个 Flask-SQLAlchemy 项目,并确定了声明性语法,以便我们可以以一种理智的方式将类组织到多个文件中以进行维护。这适用于我们的大多数关系,除了我们称之为属性表的东西,因为没有更好的术语。这些是一些主要对象的一对一叶表,通常包含某种属性的受控词汇表。 ORM 的目标是映射所有这些(其中有很多)类型的表,就好像它们是主表的属性一样。

这是一个包含两个表的 SQA 示例:

class MarkerType(db.Model):
    __tablename__="mrk_types"
    _marker_type_key = db.Column(db.Integer,primary_key=True)
    name = db.Column(db.String())

class Marker(db.Model):
    __tablename__="mrk_marker"
    _marker_key=db.Column(db.Integer,primary_key=True)
    _marker_type_key=db.Column(db.Integer())

我们想要访问 MarkerType.name,就好像我们在说 Marker.markertype,或者在查询中作为 Marker.markertype=='thing'。我可以管理的唯一方法是在 Marker 类中使用 column_property,如下所示:

markertype = db.column_property(
            db.select([MarkerType.name]).
            where(MarkerType._marker_type_key==_marker_type_key)
    )

但是,我似乎无法找到如何以声明方式执行此操作,并且可能不存在这种方式。有没有一种理智的方法可以实现这一点,而不必担心我的进口,甚至更糟的是我的课程顺序?由于我们有数百个表要映射,如果我们不得不担心类和导入顺序,我可以看到这是一个维护噩梦。

如果这一切完全不可能,一厢情愿,那么映射这些表的更好方法是什么?

【问题讨论】:

你看过Vertical Attribute Mapping的例子吗? 不,但我不确定我的情况是否适用。我没有带有键值对的子表。这是一个只有一个值的子表。我不确定这是否仍被视为垂直表。此外,设置 association_proxy 的示例看起来不是声明性的。 【参考方案1】:

这听起来像是Association Proxy 的绝佳用例。这代理了相关模型的字段。在这种情况下,实现将是:

from sqlalchemy.orm import relationship
from sqlalchemy.ext.associationproxy import association_proxy

class MarkerType(db.Model):
    __tablename__="mrk_types"
    _marker_type_key = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String())

class Marker(db.Model):
    __tablename__="mrk_marker"
    _marker_key=db.Column(db.Integer,primary_key=True)
    _marker_type_key=db.Column(db.Integer, ForeignKey('mrk_types._marker_type_key')

    mt = relationship(MarkerType, uselist=False)
    marker_type = association_proxy('mt', 'name')

这允许像session.query(Marker).filter_by(marker_type='my_marker_type')这样的查询

marker_type 字段是 MarkerType 对象上name 字段的代理。该对象可以被mt 字段(关系字段)引用

注意uselist=False。这表明每个标记有 1 种标记类型。关系会自动检测 ForeignKey 并使用它。

【讨论】:

是的,这看起来正是我所需要的。谢谢。【参考方案2】:

因此,据我所知,您被两张桌子困住了。一个带有整数 col,一个带有字符串 col。

Class Marker
    _marker_key_ primary_key
    # _ = Integer ## This can be ignored as its unnecessary.

另一个有一个

Class MarkerType
    _marker_type_key = primary_key
    name = String

当我读到它时,您希望 Class Marker 有许多可以轻松操作或调用的 Class MarkerType 字符串。不过,我不确定这是否是您想要的。

如果是这样,假设您控制了对数据库的播种,则可以实现此目的。您可以在每个名称的开头构建一个指向 Markers 主键的标志。

示例:MarkerType.name = 10324_Orange

我不熟悉在没有会话的情况下使用 SQLAlchemy,并且真的不想进行研究,所以我只是假设你正在使用 SQLAlchemy 会话来写我的答案,这样你就可以了解这个概念并可以调整为需要。

### !!! ASSUME 'create_session' method exists that 
####    creates a sqlalchemy session instance

Class Marker:
    # ... initialize and such
    # ... then add these helper methods

    ## Get all properties linked to this primary table row
    def marker_types():
        return db.query(MarkerType).
            filter(MarkerType.name.like(str(self._marker_key_)+"_%")).all()
    ## Get specific property linked to this primary table row
    def marker_type(marker_type_name):
        db = create_session()
        marker_type_list = db.query(MarkerType).
            filter(MarkerType.name.like(str(self._marker_key_)+"_%")
            AND marker_type_name == MarkerType.name ).first()
        db.close()
        return marker_type_list

    def update_marker_type(old_val, new_val)
        db = create_session()
        updated_marker_type = marker_type(old_val)
        updated_marker_type.name = str(self._marker_key_)+" "+new_val
        db.close()
        return True

    def create_marker_type(val)
        marker_type = MarkerType(name = str(self._marker_key_)+" "+val)
        db = create_session()
        db.add(marker_type)
        db.commit()
        db.close()
        return marker_type._marker_type_key

您可以在此处向名称字符串添加其他标志。属性类型之类的东西。

Marker.id = 193

MarkerType.id = 1
MarkerType.name = "193_color_Black"
MarkerType.id = 2
MarkerType.name = "193_style_Fine"

这个额外的标志可以让你搜索链接到你的特定行的通用属性特定名称,并且更有用,虽然稍微复杂一些。真的取决于你的用例。

【讨论】:

已经有一段时间了,但我从来没有真正解决过这个问题。对不起,你的答案不是我想要的。我真的想要一种方法来定义 column_property,就像我展示的那样,但是使用字符串语法,所以我不必导入 MarkerType 类。您已经可以通过关系和其他一些事情来做到这一点。 别担心..有人悬赏它,所以我想我试试看。不是很清楚你在找什么,但想通了 id 给它一个非正式的链接表。 顺便说一句,如果它是标记资源,我认为您不需要导入标记类型,并在定义该类的位置导入。特别是如果您仅通过标记类中内置的方法与它进行交互。【参考方案3】:

使用relationship 允许从Marker 表访问marker_type 并指定ForeignKey 约束,以便SQLAlchemy 了解表之间的关系。

这使您可以轻松地从标记记录访问 MarkerType 属性以及查询 MarkerType.name。下面展示了插入两条记录,然后根据属性name进行过滤。

>>> db.session.add(Marker(marker_type=MarkerType(name="blue")))
>>> db.session.add(Marker(marker_type=MarkerType(name="red")))
>>> db.session.commit()


>>> markers = Marker.query.all()
>>> print(m._marker_key: m.marker_type.name for m in markers)

1: 'blue', 2: 'red'

>>> result = Marker.query.filter(Marker._marker_type_key==MarkerType._marker_type_key) \
...                      .filter(MarkerType.name=='blue').all()
>>> print(m._marker_key: m.marker_type.name for m in result)

1: 'blue'

类的声明顺序重要,类需要一起声明。尽管如此,架构必须针对db 的同一实例进行注册,并且当您查询表时,需要导入您引用的表类。

relationshipForeignKey 添加到标记后,声明性架构将变为:

class MarkerType(db.Model):
    __tablename__="mrk_types"
    _marker_type_key = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String())

class Marker(db.Model):
    __tablename__="mrk_marker"
    _marker_key=db.Column(db.Integer, primary_key=True)
    _marker_type_key=db.Column(db.Integer(), db.ForeignKey('mrk_types._marker_type_key'))
    marker_type=db.relationship('MarkerType')

【讨论】:

然而,这不允许直接将标记类型查询为字符串,如 OP 所述 @JesseBakker 实际上我相信它确实如此。我添加了一个示例。

以上是关于SQLAlchemy 如何使用声明性映射一对一关系的单列的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 SQLAlchemy 声明性语法指定关系?

SQLAlchemy 通过关联对象声明性多对多自联接

sqlalchemy一对一关系映射

SQLAlchemy外键的使用

flask 中orm关系映射 sqlalchemy的查询

python orm框架SQLAlchemy简单应用(数据库操作)