我希望创建一个从另一个表填充的对象的映射属性。
使用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示例。
答案 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"