【问题标题】:Using PostgreSQL array to store many-to-many relationship使用PostgreSQL数组存储多对多关系
【发布时间】:2012-04-01 12:44:11
【问题描述】:

假设我们有一个 PostgreSQL 数据库,其中包含两个表 A、B。

表 A 列:id、name 表 B 列:id、name、array_a

表 B 中的列 array_a 包含来自表 A 的可变长度 id 数组。在 SQLAlchemy 中,我们有两个类对这些表进行建模,例如类 A 和 B。

以下方法可以很好地获取对象 B 中引用的所有对象 A:

session.query(A).join(B, A.id == func.any(B.array_a)).filter(B.id == <id>).all()

我们如何在 B 中创建一个引用数组对应的对象 A 的关系?使用上面的func.any 尝试了列比较器,但它抱怨ANY(array_a) 不是模型中的列。如上所述指定primaryjoin条件似乎也没有削减它。

【问题讨论】:

    标签: python postgresql sqlalchemy


    【解决方案1】:

    这个反模式叫做"Jaywalking";而 PostgreSQL 强大的类型系统让它非常诱人。你应该使用另一个表:

    CREATE TABLE table_a (
        id SERIAL PRIMARY KEY,
        name VARCHAR
    );
    
    CREATE TABLE table_b (
        id SERIAL PRIMARY KEY,
        name VARCHAR
    );
    
    CREATE TABLE a_b (
        a_id INTEGER PRIMARY KEY REFERENCES table_a(id),
        b_id INTEGER PRIMARY KEY REFERENCES table_b(id)
    )
    

    已映射:

    from sqlalchemy import *
    from sqlalchemy.dialects import postgresql
    from sqlalchemy.ext.declarative import declarative_base
    from sqlalchemy.orm import *
    
    Base = declarative_base()
    
    a_b_table = Table("a_b", Base.metadata,
        Column("a_id", Integer, ForeignKey("table_a.id"), primary_key=True),
        Column("b_id", Integer, ForeignKey("table_b.id"), primary_key=True))
    
    class A(Base):
        __tablename__ = "table_a"
        id = Column(Integer, primary_key=True)
        name = Column(String)
    
    class B(Base):
        __tablename__ = "table_b"
        id = Column(Integer, primary_key=True)
        name = Column(String)
        a_set = relationship(A, secondary=a_b_table, backref="b_set")
    

    示例:

    >>> print Query(A).filter(A.b_set.any(B.name == "foo"))
    SELECT table_a.id AS table_a_id, table_a.name AS table_a_name 
    FROM table_a 
    WHERE EXISTS (SELECT 1 
    FROM a_b, table_b 
    WHERE table_a.id = a_b.a_id AND table_b.id = a_b.b_id AND table_b.name = :name_1)
    

    如果您被ARRAY 列所困扰,最好的办法是使用“看起来”像正确关联表的备用可选选项。

    from sqlalchemy import *
    from sqlalchemy.dialects import postgresql
    from sqlalchemy.ext.declarative import declarative_base
    from sqlalchemy.orm import *
    
    Base = declarative_base()
    
    
    class A(Base):
        __tablename__ = "table_a"
        id = Column(Integer, primary_key=True)
        name = Column(String)
    
    class B(Base):
        __tablename__ = "table_b"
        id = Column(Integer, primary_key=True)
        name = Column(String)
        array_a = Column(postgresql.ARRAY(Integer))
    
    a_b_selectable = select([func.unnest(B.array_a).label("a_id"),
                             B.id.label("b_id")]).alias()
    
    A.b_set = relationship(B, secondary=a_b_selectable,
                              primaryjoin=A.id == a_b_selectable.c.a_id,
                              secondaryjoin=a_b_selectable.c.b_id == B.id,
                              viewonly=True,)
    
    B.a_set = relationship(A, secondary=a_b_selectable,
                              primaryjoin=A.id == a_b_selectable.c.a_id,
                              secondaryjoin=a_b_selectable.c.b_id == B.id,
                              viewonly=True)
    

    给你:

    >>> print Query(A).filter(A.b_set.any(B.name == "foo"))
    SELECT table_a.id AS table_a_id, table_a.name AS table_a_name 
    FROM table_a 
    WHERE EXISTS (SELECT 1 
    FROM (SELECT unnest(table_b.array_a) AS a_id, table_b.id AS b_id 
    FROM table_b) AS anon_1, table_b 
    WHERE table_a.id = anon_1.a_id AND anon_1.b_id = table_b.id AND table_b.name = :name_1)
    

    显然,由于那里没有真正的桌子,viewonly=True 是必要的,如果您避免乱穿马路,您将无法获得良好的、动态的客观善良。

    【讨论】:

    • 我认为 relationrelationships 的不推荐使用的别名,因为有几个版本。
    • selectable 可以满足我们的需要,是的。
    • 当 PostgreSQL 数组可查询并且具有明确定义的类型时,我不确定它是 jaywalking。似乎更多的是 SQLAlchemy 限制它如何处理 PostgreSQL 数组。
    • 感谢 SQL 反模式参考。我今天拿起了这本书,非常棒。
    • @Kiran Jonnalagadda:使用数组类型没有错;它当然非常有用,即使是通过 sqlalchemy 的类型。限制不是因为 Array 类型很差,它不是;但是,通过复合类型强制数据完整性既难以表达,也难以正确处理。通过一个简单的、每个引用的单个单元格排列,一致性约束可以表示为 references footable(foocolumn),这就是乱穿马路的原因。
    【解决方案2】:

    或者你可以像下面这样明确地加入:

    class A(Base):
        __tablename__ = "table_a"
        id = Column(Integer, primary_key=True)
        name = Column(String)
    
    class B(Base):
        __tablename__ = "table_b"
        id = Column(Integer, primary_key=True)
        name = Column(String)
        array_a = Column(postgresql.ARRAY(Integer))
    
        a_ids= relationship('A',primaryjoin='A.id == any_(foreign(B.array_a))',uselist=True)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-08-18
      • 2020-06-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-01-23
      • 1970-01-01
      • 2021-09-25
      相关资源
      最近更新 更多