Sqlalchemy: Resolve cartesian product warning for many-to-many relationship with custom primaryjoin
Asked Answered
T

0

8

Let's say I have the following database schema:

class A(Base):
    __tablename__ = "a_table"

    id = Column(Integer, primary_key=True, autoincrement=True)
    version = Column(Integer, primary_key=True, default=1)

    # More columns...

    bs = relationship(
        "B", secondary="a2b_table", back_populates="as"
    )

class B(Base):
    __tablename__ = "b_table"

    id = Column(Integer, primary_key=True)

    as = relationship(
        A, secondary="a2b_table", back_populates="bs"
    )

class A2B(Base):
    __tablename__ = "a2b_table"

    a_id = Column(
        Integer(),
        primary_key=True,
    )
    a_version = Column(
        Integer,
        primary_key=True,
    )
    b_id = sa.Column(
        Integer,
        ForeignKey("b.id", name="b_fk"),
        primary_key=True,
    )

    __table_args__ = (
        ForeignKeyConstraint(
            [a_id, a_version],
            [A.id, A.version],
            name="a_fk",
        ),
        {},
    )
    

Each A is identified by an id and can have multiple versions. If something changes in the columns of A (the ones not shown), I produce a new A with the same id and version+1. The relationship bs gives me all instances of B that are associated with a specific version of an A.

The problem is, that the relationship as gives me all versions of each A that is associated with a specific B. Instead, I want the relationship to contain only the latest (highest) version of each A. Following the docs, I tried to solve this with a custom primaryjoin and a window function:

partition = select(
    A,
    row_number()
    .over(order_by=A.version.desc(), partition_by=A.id)
    .label("index"),
).alias()

partitioned_as = aliased(A, partition)

B.latest_as = relationship(
    partitioned_as,
    primaryjoin=and_(
        partition.c.index == 1,
        and_(
            partitioned_as.id == A2B.a_id,
            partitioned_as.version == A2B.a_version,
        ),
    ),
    secondary="a2b_table",
    viewonly=True,
)

Unfortunately, it doesn't work and I get the warning:

SELECT statement has a cartesian product between FROM element(s) "anon_1", "a2b_table" and FROM element "a_table". Apply join condition(s) between each element to resolve.

I checked the SQL statement sqlalchemy generates and it has anon_1, i.e. the query of partition, and a_table in its FROM clause. As far as I understand it, a_table shouldn't be in the FROM clause of this statement because it is already in the FROM clause of partition. I don't know how to get rid of it.

Could anyone point me in the right direction? Thanks in advance.

Trine answered 19/8, 2021 at 7:52 Comment(1)
Apply join condition(s) between each element to resolve. -> In my case i just added a join(Table) in the sqlalchemy statement. You have to Join tablet before to search or compare data across different tables. Im not sure how does this apply to your code.Midsummer

© 2022 - 2024 — McMap. All rights reserved.