SQLAlchemy 一对(多中的一些)关系
SQLAlchemy One-to-(Some of Many) relationship
我目前有一个 SQLAlchemy 模型 User
,它有两种相关的测量值,体重和身高。这些被添加为外键。
class User(UserMixin, db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(100), unique=True)
password = db.Column(db.String(100))
name = db.Column(db.String(1000))
weight = db.relationship("Weight", cascade="all,delete")
height = db.relationship("Height", cascade="all,delete")
class Weight(db.Model):
__tablename__ = "weight"
id = db.Column(db.Integer, primary_key=True)
date = db.Column(db.DateTime)
weight = db.Column(db.Float)
user_id = db.Column(db.Integer, db.ForeignKey("user.id", ondelete="CASCADE"))
class Height(db.Model):
__tablename__ = "height"
id = db.Column(db.Integer, primary_key=True)
date = db.Column(db.DateTime)
height = db.Column(db.Float)
user_id = db.Column(db.Integer, db.ForeignKey("user.id", ondelete="CASCADE"))
我希望能够添加更多类型的测量,而不必每次都重建数据库结构。因此,我考虑使用通用 Measurement
模型,其中有一列用于测量类型(不是 Enum
,因此我可以轻松添加新类型):
class User(UserMixin, db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(100), unique=True)
password = db.Column(db.String(100))
name = db.Column(db.String(1000))
measurements = db.relationship("Measurement", cascade="all,delete")
@property
def weight(self):
pass # <-- No idea what to do here
@property
def height(self):
pass # <-- No idea what to do here
class Measurement(db.Model):
__tablename__ = "measurement"
id = db.Column(db.Integer, primary_key=True)
date = db.Column(db.DateTime)
value = db.Column(db.Float)
user_id = db.Column(db.Integer, db.ForeignKey("user.id", ondelete="CASCADE"))
measurement_type_id = db.Column(db.Integer, db.ForeignKey('measurement_type.id'))
measurement_type = db.relationship("MeasurementType")
class MeasurementType(db.Model):
__tablename__ = "measurement_type"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100))
unit = db.Column(db.String(100), nullable=True)
使用这个新模型,我如何才能只获得例如权重?对于当前的模型,就这么简单,其中 current_user
是当前登录的用户:
current_user.weight
但我想应该是这样的:
current_user.measurements.filter(Measurement.measurement_type == "Weight")
(这不起作用,因为 current_user.measurements
returns 一个列表。)
同样,我该如何为测量值添加新值?目前我这样做:
current_user.weight.append(Weight(date=date, weight=weight))
db.session.commit()
我是否基本上需要复制 relationship
的低级实现以便按用户 ID 和测量类型这两项进行过滤?
或者我可以使用 association proxy 以某种方式实现此目的吗?或者(正确地)使用 relationship
?
的 primaryjoin
参数
我发现实现此目的的最简单方法是在关系上使用动态延迟加载,this answer to a similar question 也推荐这样做。这样 return 类型就不再是一个简单的列表,而是一个 SQL 对象。这也使用简单的字符串作为测量类型,而不是查找 table.
class User(UserMixin, db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(100), unique=True)
password = db.Column(db.String(100))
name = db.Column(db.String(1000))
measurements = db.relationship("Measurement", cascade="all,delete", lazy="dynamic")
class Measurement(db.Model):
__tablename__ = "measurement"
id = db.Column(db.Integer, primary_key=True)
m_type = db.Column(db.String(20))
date = db.Column(db.DateTime)
value = db.Column(db.Float)
user_id = db.Column(db.Integer, db.ForeignKey("user.id", ondelete="CASCADE"))
之后您可以对测量结果使用普通的过滤方法:
list(current_user.measurements.filter_by(m_type="weight"))
而且添加新行也很简单:
current_user.measurements.append(Measurement(m_type="weight", date=date, value=weight))
我目前有一个 SQLAlchemy 模型 User
,它有两种相关的测量值,体重和身高。这些被添加为外键。
class User(UserMixin, db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(100), unique=True)
password = db.Column(db.String(100))
name = db.Column(db.String(1000))
weight = db.relationship("Weight", cascade="all,delete")
height = db.relationship("Height", cascade="all,delete")
class Weight(db.Model):
__tablename__ = "weight"
id = db.Column(db.Integer, primary_key=True)
date = db.Column(db.DateTime)
weight = db.Column(db.Float)
user_id = db.Column(db.Integer, db.ForeignKey("user.id", ondelete="CASCADE"))
class Height(db.Model):
__tablename__ = "height"
id = db.Column(db.Integer, primary_key=True)
date = db.Column(db.DateTime)
height = db.Column(db.Float)
user_id = db.Column(db.Integer, db.ForeignKey("user.id", ondelete="CASCADE"))
我希望能够添加更多类型的测量,而不必每次都重建数据库结构。因此,我考虑使用通用 Measurement
模型,其中有一列用于测量类型(不是 Enum
,因此我可以轻松添加新类型):
class User(UserMixin, db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(100), unique=True)
password = db.Column(db.String(100))
name = db.Column(db.String(1000))
measurements = db.relationship("Measurement", cascade="all,delete")
@property
def weight(self):
pass # <-- No idea what to do here
@property
def height(self):
pass # <-- No idea what to do here
class Measurement(db.Model):
__tablename__ = "measurement"
id = db.Column(db.Integer, primary_key=True)
date = db.Column(db.DateTime)
value = db.Column(db.Float)
user_id = db.Column(db.Integer, db.ForeignKey("user.id", ondelete="CASCADE"))
measurement_type_id = db.Column(db.Integer, db.ForeignKey('measurement_type.id'))
measurement_type = db.relationship("MeasurementType")
class MeasurementType(db.Model):
__tablename__ = "measurement_type"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100))
unit = db.Column(db.String(100), nullable=True)
使用这个新模型,我如何才能只获得例如权重?对于当前的模型,就这么简单,其中 current_user
是当前登录的用户:
current_user.weight
但我想应该是这样的:
current_user.measurements.filter(Measurement.measurement_type == "Weight")
(这不起作用,因为 current_user.measurements
returns 一个列表。)
同样,我该如何为测量值添加新值?目前我这样做:
current_user.weight.append(Weight(date=date, weight=weight))
db.session.commit()
我是否基本上需要复制 relationship
的低级实现以便按用户 ID 和测量类型这两项进行过滤?
或者我可以使用 association proxy 以某种方式实现此目的吗?或者(正确地)使用 relationship
?
primaryjoin
参数
我发现实现此目的的最简单方法是在关系上使用动态延迟加载,this answer to a similar question 也推荐这样做。这样 return 类型就不再是一个简单的列表,而是一个 SQL 对象。这也使用简单的字符串作为测量类型,而不是查找 table.
class User(UserMixin, db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(100), unique=True)
password = db.Column(db.String(100))
name = db.Column(db.String(1000))
measurements = db.relationship("Measurement", cascade="all,delete", lazy="dynamic")
class Measurement(db.Model):
__tablename__ = "measurement"
id = db.Column(db.Integer, primary_key=True)
m_type = db.Column(db.String(20))
date = db.Column(db.DateTime)
value = db.Column(db.Float)
user_id = db.Column(db.Integer, db.ForeignKey("user.id", ondelete="CASCADE"))
之后您可以对测量结果使用普通的过滤方法:
list(current_user.measurements.filter_by(m_type="weight"))
而且添加新行也很简单:
current_user.measurements.append(Measurement(m_type="weight", date=date, value=weight))