在 SqlAlchemy 中加载与项目子集的“关系”(类似于 Django 的“Prefetch”对象)
Load `relationship` with subset of items in SqlAlchemy (similar to Django's `Prefetch` object)
有没有办法从 SqlAlchemy 中的关系中检索元素的子集,但保持我在创建 class 时在 backref
(关系)中定义的顺序?
假设我有两个 tables/models:一个 User
和一个 UserLog
,其中包含用户执行的操作(Base
只是一个 declarative_base())
每个 UserLog
都有一个指向执行操作的 User
的 ID 的外键,每个日志的操作类型可以从 Enum
中选择。每个 UserLog 也有一个 created
我订购的时间戳:在常规情况下,我想从最旧到最新(最旧的优先)获取日志。
但是,我还希望能够让特定用户只加载一部分日志(特定操作的日志)。我知道这与连接负载的 "gist" 背道而驰,因为我会得到一个不切实际的数据库状态(有更多链接到用户的日志,我获取的 User
对象会反映出来),但是在某些情况下会有所帮助。
class UserLog(Base):
__tablename__ = 'user_logs'
id = Column(UUID(as_uuid=True), primary_key=True)
timestamp_created = Column(
DateTime, server_default=sqlalchemy.text("now()")
)
user_id = Column(
UUID(as_uuid=True),
ForeignKey("users.id", ondelete="CASCADE")
)
action = Column(SaEnum(UserLogsConstants), nullable=False)
user = relationship(
"User",
backref=backref(
'logs', order_by="asc(UserLog.timestamp_created)"
) # The order_by is important here
)
谢谢 I've been able to load a subset of logs using contains_eager
,但后来我丢失了我在 backref
.
中声明的 timestamp_created
的排序
假设我有两个可用的日志操作:UPLOAD_START
、UPLOAD_END
(对于 action
列)
我设置了一个测试,其中我创建了一个 User
,其中包含 5 个 UPLOAD_START
操作和 5 个 UPLOAD_END
创建的操作 乱序(我人为地先插入"newer"动作)。
所以我想要的是得到一个 User
和它的 .logs
关系包含 只有 事件是 UPLOAD_START
和按backref
对象中指定的时间戳保持顺序。
这是测试查询
u = session.query(User) \
.filter(User.id == test_user_id) \
.join(User.logs) \
.filter(UserLog.action == UserLogsConstants.UPLOAD_START) \
.options(contains_eager(User.logs)) \
.one()
这两个断言工作正常:
assert len(u.logs) == 5
# I inserted 10 logs total, only 5 with action=UPLOAD_START
assert all(ul.action == UserLogsConstants.UPLOAD_START for ul in u.logs)
# Neat, only logs with `UPLOAD_START`
但是这个失败了:不遵守顺序。
assert all(
u.logs[i].timestamp_created < u.logs[i + 1].timestamp_created
for i in range(len(u.logs) - 1)
)
有道理。如果我理解 contains_eager
行为,这有点像 忘记你认为的关系是什么,并使用我在前面的查询中告诉你的内容 (而且我'我没有说任何有关该查询中的顺序的信息)。而且,确实:我正在查看 SqlAlchemy 生成的 SQL 并且没有 ORDER BY
子句。我想我总是可以自己将它添加到查询中,但如果我不需要的话会更干净。
对于那些熟悉 Django 的人,我尝试模拟 Prefech
对象,我可以在其中指定一个子查询以从以下位置获取加载的对象:
User.objects.prefetch_related(
Prefetch(
'logs',
queryset=UserLogs.objects.filter(action=UserLogsConstants.UPLOAD_START)
)
).get(pk='foo-uuid')
当使用 contains_eager
时,您告诉 SQLAlchemy 忽略配置的关系,而是从您手工制作的查询中填充 .logs
容器。这意味着关系上的任何排序设置也将被忽略。
所以如果需要对日志数据进行排序,需要自己动手:
u = (
session.query(User)
.filter(User.id == test_user_id)
.join(User.logs)
.filter(UserLog.action == UserLogsConstants.UPLOAD_START)
.order_by(User.id, asc(UserLog.timestamp_created))
.options(contains_eager(User.logs))
.one()
)
如果您更常过滤用户日志,那么另一种选择是将 logs
设为 dynamic relationship。这会将容器替换为您要过滤的预填充 Query
对象:
user = relationship(
"User",
backref=backref(
'logs', lazy='dynamic', order_by="asc(UserLog.timestamp_created)"
)
)
并使用
u = session.query(User).filter(User.id == test_user_id).one()
upload_start_logs = u.logs.filter(UserLog.action == UserLogsConstants.UPLOAD_START).all()
当然,这确实会发出一个单独的查询。
有没有办法从 SqlAlchemy 中的关系中检索元素的子集,但保持我在创建 class 时在 backref
(关系)中定义的顺序?
假设我有两个 tables/models:一个 User
和一个 UserLog
,其中包含用户执行的操作(Base
只是一个 declarative_base())
每个 UserLog
都有一个指向执行操作的 User
的 ID 的外键,每个日志的操作类型可以从 Enum
中选择。每个 UserLog 也有一个 created
我订购的时间戳:在常规情况下,我想从最旧到最新(最旧的优先)获取日志。
但是,我还希望能够让特定用户只加载一部分日志(特定操作的日志)。我知道这与连接负载的 "gist" 背道而驰,因为我会得到一个不切实际的数据库状态(有更多链接到用户的日志,我获取的 User
对象会反映出来),但是在某些情况下会有所帮助。
class UserLog(Base):
__tablename__ = 'user_logs'
id = Column(UUID(as_uuid=True), primary_key=True)
timestamp_created = Column(
DateTime, server_default=sqlalchemy.text("now()")
)
user_id = Column(
UUID(as_uuid=True),
ForeignKey("users.id", ondelete="CASCADE")
)
action = Column(SaEnum(UserLogsConstants), nullable=False)
user = relationship(
"User",
backref=backref(
'logs', order_by="asc(UserLog.timestamp_created)"
) # The order_by is important here
)
谢谢 contains_eager
,但后来我丢失了我在 backref
.
timestamp_created
的排序
假设我有两个可用的日志操作:UPLOAD_START
、UPLOAD_END
(对于 action
列)
我设置了一个测试,其中我创建了一个 User
,其中包含 5 个 UPLOAD_START
操作和 5 个 UPLOAD_END
创建的操作 乱序(我人为地先插入"newer"动作)。
所以我想要的是得到一个 User
和它的 .logs
关系包含 只有 事件是 UPLOAD_START
和按backref
对象中指定的时间戳保持顺序。
这是测试查询
u = session.query(User) \
.filter(User.id == test_user_id) \
.join(User.logs) \
.filter(UserLog.action == UserLogsConstants.UPLOAD_START) \
.options(contains_eager(User.logs)) \
.one()
这两个断言工作正常:
assert len(u.logs) == 5
# I inserted 10 logs total, only 5 with action=UPLOAD_START
assert all(ul.action == UserLogsConstants.UPLOAD_START for ul in u.logs)
# Neat, only logs with `UPLOAD_START`
但是这个失败了:不遵守顺序。
assert all(
u.logs[i].timestamp_created < u.logs[i + 1].timestamp_created
for i in range(len(u.logs) - 1)
)
有道理。如果我理解 contains_eager
行为,这有点像 忘记你认为的关系是什么,并使用我在前面的查询中告诉你的内容 (而且我'我没有说任何有关该查询中的顺序的信息)。而且,确实:我正在查看 SqlAlchemy 生成的 SQL 并且没有 ORDER BY
子句。我想我总是可以自己将它添加到查询中,但如果我不需要的话会更干净。
对于那些熟悉 Django 的人,我尝试模拟 Prefech
对象,我可以在其中指定一个子查询以从以下位置获取加载的对象:
User.objects.prefetch_related(
Prefetch(
'logs',
queryset=UserLogs.objects.filter(action=UserLogsConstants.UPLOAD_START)
)
).get(pk='foo-uuid')
当使用 contains_eager
时,您告诉 SQLAlchemy 忽略配置的关系,而是从您手工制作的查询中填充 .logs
容器。这意味着关系上的任何排序设置也将被忽略。
所以如果需要对日志数据进行排序,需要自己动手:
u = (
session.query(User)
.filter(User.id == test_user_id)
.join(User.logs)
.filter(UserLog.action == UserLogsConstants.UPLOAD_START)
.order_by(User.id, asc(UserLog.timestamp_created))
.options(contains_eager(User.logs))
.one()
)
如果您更常过滤用户日志,那么另一种选择是将 logs
设为 dynamic relationship。这会将容器替换为您要过滤的预填充 Query
对象:
user = relationship(
"User",
backref=backref(
'logs', lazy='dynamic', order_by="asc(UserLog.timestamp_created)"
)
)
并使用
u = session.query(User).filter(User.id == test_user_id).one()
upload_start_logs = u.logs.filter(UserLog.action == UserLogsConstants.UPLOAD_START).all()
当然,这确实会发出一个单独的查询。