来自join的SQLAlchemy声明属性(单个属性,不是整个对象)

时间:2012-06-05 04:26:42

标签: python orm sqlalchemy declarative

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

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

例如,我希望能够按user_name Address.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示例。

2 个答案:

答案 0 :(得分:6)

您可以在查询对象上使用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>

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

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

答案 1 :(得分:3)

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

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

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

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"