【问题标题】:How to in aggregate distinct values in joined table in SQLAlchemy?如何在 SQLAlchemy 的连接表中聚合不同的值?
【发布时间】:2018-10-28 13:58:04
【问题描述】:

这是架构:

post_tag = Table("post_tag", Base.metadata,
                 Column("post_id", Integer, ForeignKey("post.id")),
                 Column("tag_id ", Integer, ForeignKey("tag.id")))


class Post(Base):
    id = Column(Integer, primary_key=True)
    tags = relationship("Tag", secondary=post_tag, backref="post", cascade="all")
    collection_id = Column(Integer, ForeignKey("collection.id"))


class Tag(Base):
    id = Column(Integer, primary_key=True)
    description = Column("description", UnicodeText, nullable=False, default="")
    post_id = Column(Integer, ForeignKey("post.id"))


class Collection(Base):
    id = Column(Integer, primary_key=True)
    title = Column(Unicode(128), nullable=False) 
    posts = relationship("Post", backref="collection", cascade="all,delete-orphan")

    tags = column_property(select([Tag])
                           .where(and_(Post.collection_id == id, Tag.post_id == Post.id))
                           .correlate_except(Tag))

基本上,PostTag 是多对多,CollectionPost 是一对多。

我想 Collection.tags 返回一组不同的帖子标签集。

但是,当我访问Collection.tags 时出现以下错误:

sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) 对于作为表达式一部分的 SELECT 只允许一个结果

编辑

它生成的 SQL

SELECT (SELECT tag.id, tag.description, tag.post_id 
FROM tag, post 
WHERE post.collection_id = collection.id AND tag.post_id = post.id) AS anon_1, collection.id AS collection_id, collection.title AS collection_title 
FROM collection 
WHERE collection.id = 1

我认为 post_id = Column(Integer, ForeignKey("post.id")) 是错误的,因为 post_id 在 post_tag 中。但是,如果我将其更改为 post_tag.post_id,则会抛出 AttributeError: 'Table' object has no attribute 'post_id'

EDIT2

我改成

tags = column_property(select([Tag])
                       .where(and_(Post.collection_id == id, post_tag.c.post_id == Post.id,
                                   post_tag.c.tag_id == Tag.id)))

虽然这有效

SELECT tag.id, tag.description, tag.category_id, tag.post_id 
FROM tag, post, post_tag 
WHERE post.collection_id = 1 AND post_tag.post_id = post.id AND post_tag.tag_id = tag.id

但 SQLAlchemy 生成的查询没有

SELECT (SELECT tag.id, tag.description, tag.category_id, tag.post_id 
FROM tag, post, post_tag 
WHERE post.collection_id = collection.id AND post_tag.post_id = post.id AND post_tag.tag_id = tag.id) AS anon_1
FROM collection 
WHERE collection.id = 1

【问题讨论】:

  • 我认为您需要向我们展示给出错误的 SQL(或生成它的代码)。这通常意味着查询有where mycol = (select ...),而select 意外返回多行。
  • 可能是表达式上下文中使用的Collection.tags 属性。它应该是一个带有自定义连接的关系属性。
  • @BoarGules 我发现了问题,但我不知道如何在 SQLAlchemy 中解决它。你能看看我的编辑吗?我认为问题在于我试图使用column_property 返回一个列表而不是单个值。

标签: python database sqlite sqlalchemy flask-sqlalchemy


【解决方案1】:

您需要relationship() with a composite "secondary" 而不是column_property()。列属性对于将某些(标量)SQL 表达式映射为与其他属性一起加载的“列”很方便。另一方面,您似乎想要映射一组相关的 Tag 对象:

class Collection(Base):
    id = Column(Integer, primary_key=True)
    title = Column(Unicode(128), nullable=False)
    posts = relationship("Post", backref="collection", cascade="all,delete-orphan")

    tags = relationship(
        "Tag", viewonly=True,
        primaryjoin="Collection.id == Post.collection_id",
        secondary="join(Post, post_tag)",
        secondaryjoin="Tag.id == post_tag.c.tag_id")

如果你想预先加载关系,有点像列属性,你可以默认为lazy="join"。也可以使用Query.options() 为每个查询定义预先加载策略:

session.query(Collection).\
    options(joinedload(Collection.tags)).\
    all()

请注意,您的示例在辅助表post_tags 的定义中有错字(?)。 tag_id 列的名称中有尾随空格。

【讨论】:

  • 非常感谢。我想出的方法只是标记为property的函数并查询结果。使用relationship 似乎要好得多。
猜你喜欢
  • 2022-01-19
  • 2017-05-31
  • 2020-06-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多