【问题标题】:SQLAlchemy declarative property from join (single attribute, not whole object)来自连接的 SQLAlchemy 声明性属性(单个属性,而不是整个对象)
【发布时间】:2012-06-09 03:08:43
【问题描述】:

我希望创建从另一个表填充的对象的映射属性。

使用 SQLAlchemy 文档示例,我希望在 Address 类中存在一个 user_name 字段,以便可以轻松查询和访问(无需第二次往返数据库)

例如我希望能够通过user_nameAddress.query.filter(Address.user_name == 'wcdolphin').first()查询和过滤 并且还可以访问所有 Address 对象的 user_name 属性,而不会降低性能,并让它像 __tablename__ 中的属性所期望的那样正确地持久化写入

class User(Base):
    __tablename__ = 'users'

    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    addresses = relation("Address", backref="user")

class Address(Base):
    __tablename__ = 'addresses'

    id = Column(Integer, primary_key=True)
    email = Column(String(50))
    user_name = Column(Integer, ForeignKey('users.name'))#This line is wrong

我该怎么做?

我发现文档相对难以理解,因为它似乎不符合大多数示例,尤其是 Flask-SQLAlchemy 示例。

【问题讨论】:

  • 文档到底有什么问题? sqlalchemy tutorial 涵盖了常见任务的所有内容。在文档的其余部分中可以找到高级概念。

标签: python orm sqlalchemy declarative


【解决方案1】:

您可以在查询对象上使用join 来执行此操作,无需直接指定此属性。所以你的模型看起来像:

from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
from sqlalchemy.orm import sessionmaker, relation
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()
engine = create_engine('sqlite:///')
Session = sessionmaker(bind=engine)

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    addresses = relation("Address", backref="user")

class Address(Base):
    __tablename__ = 'addresses'
    id = Column(Integer, primary_key=True)
    email = Column(String(50))
    user_id = Column(Integer, ForeignKey("users.id"))


Base.metadata.create_all(engine)

过滤用户名后的地址查询如下所示:

>>> session = Session()
>>> session.add(Address(user=User(name='test')))
>>> session.query(Address).join(User).filter(User.name == 'test').first()
<__main__.Address object at 0x02DB3730>

编辑:由于您可以从地址对象直接访问用户,因此无需直接将属性引用到地址类:

>>> a = session.query(Address).join(User).filter(User.name == 'test').first()
>>> a.user.name
'test'

【讨论】:

  • 虽然有些正确,但这不是我的意图,因为我无法访问 Address 的 user_name 属性,正如我在问题中描述的那样。在自定义 PHP/手写 ORM 中写这个的时候,正确生成 SQL 以从 User.__table__ 中选择 User.name 并存储属性是一个简单的情况,SQLAlchemy 必须支持这种情况吗?
  • @wcdolphin 更新了答案。
  • 太棒了,这里的关键是当你创建 backref 时发生的魔法,它实际上“知道”为 Address 提供一个从中查询 id 的用户对象。
【解决方案2】:

如果您确实希望 Address 具有启用 SQL 的“User.name”版本而无需显式连接,则需要使用相关子查询。这在所有情况下都有效,但在数据库端(尤其是 MySQL)往往效率低下,因此与使用常规 JOIN 相比,SQL 端可能存在性能损失。运行一些 EXPLAIN 测试可能有助于分析可能产生的影响。

相关 column_property() 的另一个示例位于 http://docs.sqlalchemy.org/en/latest/orm/mapped_sql_expr.html#using-column-property

对于“设置”事件,相关子查询表示只读属性,但事件可用于拦截更改并将其应用于父用户行。下面介绍了两种方法,一种使用常规身份映射机制,如果用户行不存在,它将导致加载用户行,另一种向行发出直接更新:

from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base

Base= declarative_base()

class User(Base):
    __tablename__ = 'users'

    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    addresses = relation("Address", backref="user")

class Address(Base):
    __tablename__ = 'addresses'

    id = Column(Integer, primary_key=True)
    user_id = Column(Integer, ForeignKey('users.id'))
    email = Column(String(50))

Address.user_name = column_property(select([User.name]).where(User.id==Address.id))

from sqlalchemy import event
@event.listens_for(Address.user_name, "set")
def _set_address_user_name(target, value, oldvalue, initiator):
    # use ORM identity map + flush
    target.user.name = value

    # use direct UPDATE
    #object_session(target).query(User).with_parent(target).update({'name':value})

e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)

s = Session(e)
s.add_all([
    User(name='u1', addresses=[Address(email='e1'), Address(email='e2')])
])
s.commit()

a1 = s.query(Address).filter(Address.user_name=="u1").first()
assert a1.user_name == "u1"

a1.user_name = 'u2'
s.commit()

a1 = s.query(Address).filter(Address.user_name=="u2").first()
assert a1.user_name == "u2"

【讨论】:

  • 使用这种方法有什么好处吗?一个标准的 Python 属性似乎更简单(见我的回答)。或者我错过了什么? (不过,以 address.user.name 的身份访问它是 IMO 最 Python 的版本。)
  • 好吧,他也希望能够在不调用 join() 的情况下使用它进行查询。这种模式是我在 SQLA 早期非常强调的一种模式,如果您需要像“聚合计数”这样的东西,它仍然具有相关性。但是要获得一个相关的属性,是的,不是那么多。 SQLA 真的希望你拼出查询的几何形状。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-07-06
  • 2011-01-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多