此问题已完全重写为10/17/18
为了拥有一个“ Edit Versioning System”(类似于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
标记了不同的标签(使用我的编辑系统;这样,对于经过编辑的PostVersion
就有多个Post
)。
您可能会注意到,Post和PostVersion之间有一个循环引用。为了进行实验,我使用它来配置以下两个关系:
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>
和
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
。
@IljaEverilä使用echo=True
创建引擎时,发现lazy="dynamic"
更改了SQL。我引用这个问题的评论:
简而言之:使用
lazy="dynamic"
可以得到FROM post, tags, post_version WHERE ...
,但是如果没有FROM post, tags JOIN post_version ON tags.post_version_id = post_version.id WHERE ....
则可以看到,动态设置几乎忽略了复合辅助设备。现在的问题是“为什么?”
似乎lazy="dynamic"
is explicitly discouraged here,但没有其他建议。仍然允许分页并依靠大量馆藏的替代方法是什么?默认值不允许这样做(或者至少以我访问它的方式),并且文档似乎也无法阐明问题!在标题为What kind of loading to use?的小节中,似乎建议为大型馆藏使用的加载策略为lazy="subquery"
,但这不适用于paginate()
和count()
。
答案 0 :(得分:2)
在SQLAlchemy如何处理形成动态加载关系查询的过程中,的确是an issue。虽然查询应该是
SELECT post.id AS post_id, post.head_id AS post_head_id
FROM post, tags JOIN post_version ON tags.post_version_id = post_version.id
WHERE ? = tags.tag_id AND post.head_id = post_version.id
最终以
SELECT post.id AS post_id, post.head_id AS post_head_id
FROM post, tags, post_version
WHERE ? = tags.tag_id AND post.head_id = post_version.id
因此,尽管post
和post_version
之间存在内部联接(采用SQL-92之前的样式),但tags
和post_version
之间的内部联接却丢失了因此在tags
和其余部分之间有一个CROSS JOIN。结果是该查询将加载所有当前帖子版本,而与标签无关。 ,因为每个帖子都与tags
中的每一行连接在一起。这也解释了t.posts.count()
的乘法。
解决方案是等待fix,同时使用其他一些关系加载策略。