【发布时间】:2019-09-22 20:27:27
【问题描述】:
这个问题已于 10/17/18 完全重写
为了有一个“编辑版本系统”(类似于 StackOverflow 的功能),我配置了以下类:
tags = db.Table(
"tags",
db.Column("tag_id", db.Integer, db.ForeignKey("tag.id")),
db.Column("post_version_id", db.Integer,
db.ForeignKey("post_version.id"))
)
class Tag(db.Model):
id = db.Column(db.Integer, primary_key=True)
tag = db.Column(db.String(128), index=True, unique=True)
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
head_id = db.Column(db.Integer, db.ForeignKey("post_version.id"))
class PostVersion(db.Model):
id = db.Column(db.Integer, primary_key=True)
previous_id = db.Column(db.Integer, db.ForeignKey("post_version.id"), default=None)
pointer_id = db.Column(db.Integer, db.ForeignKey("annotation.id"))
current = db.Column(db.Boolean, index=True)
tags = db.relationship("Tag", secondary=tags)
排除的是帖子内容等不相关的列。其实真正的数据模型是注解; 为了通用性,我已经简化了这些模型
实际数据由 136 个Post 的可变标记和通过编辑可变版本组成;也就是说:我生成了136个Post的。我有 15 个Tag。最初的 136 个Post 都与 2 个Tag 一致地标记。我随后用不同的标签标记了Post(使用我的编辑系统;因此编辑后的Post有多个PostVersion)。
您可能会注意到,Post 和 PostVersion 之间存在循环引用;我使用它来配置以下两个关系以进行实验:
关系 1 posts
posts = db.relationship("Post",
secondary="join(tags, PostVersion,"
"tags.c.post_version_id==PostVersion.id)",
primaryjoin="Tag.id==tags.c.tag_id",
secondaryjoin="Post.head_id==PostVersion.id",
lazy="dynamic")
基于SQL语句
SELECT
post.id
FROM
tag
JOIN
tags ON tag.id=tags.tag_id
JOIN
post_version ON tags.post_version_id=post_version.id
JOIN
post ON post.head_id=post_version.id
WHERE
tag.id=<tag_id>
和
关系 2 posts2
posts2 = db.relationship("Post",
secondary="join(tags, PostVersion,"
"and_(tags.c.post_version_id==PostVersion.id,"
"AnnotationVersion.current==True))",
primaryjoin="Tag.id==tags.c.tag_id",
secondaryjoin="PostVersion.pointer_id==Post.id",
lazy="dynamic")
基于 SQL 语句
SELECT
annotation.id
FROM
tag
JOIN
tags ON tag.id=tags.tag_id
JOIN
annotation_version ON tags.annotation_version_id=annotation_version.id AND
annotation_version.current=1
JOIN
annotation ON annotation_version.pointer_id = annotation.id
WHERE
tag_id=8;
这会产生以下数据:
Tag Actual len(t.posts.all()) len(t.posts.paginate(1,5,False).items)
t1 0 0 0
t2 1 136 5
t3 1 136 5
t8 136 136 1
t14 136 136 1
t15 24 136 1
Tag Actual t.posts.count() t.posts2.count()
t1 0 0 0
t2 1 136 163
t3 1 136 163
t8 136 22168 26569
t14 136 22168 26569
t15 24 3264 3912
我已经排除了冗余标签(即所有其他 Tag 和 0 个 Post)和相同数据(即来自 posts2 的结果与 posts 相同)。
如您所见,结果存在一些严重问题!特别是对于这两种关系,如果关闭lazy="dynamic",则始终返回正确的Post。
在创建引擎时使用echo=True,@IljaEverilä 发现lazy="dynamic" 更改了SQL。我在这个问题中引用了 cmets:
简而言之:使用
lazy="dynamic"你会得到FROM post, tags, post_version WHERE ...,但没有你会得到FROM post, tags JOIN post_version ON tags.post_version_id = post_version.id WHERE ....正如你所看到的,动态设置几乎忽略了复合二级。现在的问题是“为什么?”
我的问题:
1。这是一个错误吗?
2。我能做些什么来纠正这个困境?
更新:
似乎lazy="dynamic" is explicitly discouraged here 但没有建议替代方案。仍然允许分页并依靠大型集合的替代方法是什么?默认情况下不允许这样做(或者至少在我访问它的方式上)并且文档似乎没有澄清问题!在标题为What kind of loading to use? 的部分中,它似乎为大型集合推荐的加载策略是lazy="subquery",但这不允许paginate() 和count()。
【问题讨论】:
-
lazy="dynamic"似乎有些有趣。没有它,关系会产生正确的查询并且计数匹配。有了它,生成的查询不会使用复合辅助(连接),因此查询最终会执行隐式交叉连接——因此会产生额外的行。t.posts.all()由于 SQLAlchemy 对单个模型实体结果进行重复数据删除的方式而隐藏了这一事实。 -
@IljaEverilä 返回所有注释肯定要快得多。但它会消除
paginate()和count()的使用(不过我很好奇,当我在“产生正确的查询并且计数匹配”时仔细阅读您。 因为我唯一的方法是知道在没有lazy="dynamic"的情况下计算结果将是使用len()。这是您的意思还是您正在使用其他方法?现在阅读lazy参数 -
简而言之:有
lazy="dynamic",你得到FROM post, tags, post_version WHERE ...,但没有你得到FROM post, tags JOIN post_version ON tags.post_version_id = post_version.id WHERE ...。如您所见,动态设置几乎忽略了复合二级。现在的问题是“为什么?” -
lazy="join"默认使用 LEFT JOIN,但您可以使用innerjoin=True覆盖(默认为False)。另一方面,这与您当前的困境有点相切:)。 -
我在创建引擎时使用
echo=True,因此会记录发出的 SQL。
标签: python flask sqlalchemy flask-sqlalchemy