I would like to build a many-to-many relationship between instances of the same class. (An object can be composed of 0 to many objects of the same type. The object itself can be contained in 0 or many other objects of the same type.)
I also would like to add extra data to each record in the resulting association table.
With respect to the many-to-many relationship within the same table I found this SO answer (https://stackoverflow.com/a/5652169/3006060) and produced the following test code:
from sqlalchemy import Column
from sqlalchemy import create_engine
from sqlalchemy import ForeignKey
from sqlalchemy import String
from sqlalchemy import Table
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship
from sqlalchemy.orm import sessionmaker
SQLALCHEMY_DB_URL: str = "sqlite:///./loadcases.sqlite"
class Base(DeclarativeBase):
pass
load_case_sub_load_association = Table(
"load_case_sub_load_association",
Base.metadata,
Column("load_case_id", String, ForeignKey("load_cases.id")),
Column("sub_load_case_id", String, ForeignKey("load_cases.id"))
)
class LoadCase(Base):
__tablename__ = "load_cases"
id: Mapped[int] = mapped_column(primary_key=True, index=True)
title: Mapped[str] = mapped_column(index=True)
load_cases = relationship('LoadCase',
secondary="load_case_sub_load_association",
backref='sub_load_cases',
primaryjoin=load_case_sub_load_association.c.load_case_id==id,
secondaryjoin=load_case_sub_load_association.c.sub_load_case_id==id
)
if __name__ == '__main__':
engine = create_engine(
SQLALCHEMY_DB_URL,
connect_args={"check_same_thread": False} # needed for sqlite, remove when using other dbs
)
Base.metadata.create_all(bind=engine)
Session = sessionmaker(engine) # keep Session in same scope as engine!
with Session() as session:
ulc_t = LoadCase(title='ulc_t')
ulc_p = LoadCase(title='ulc_p')
ulc_m = LoadCase(title='ulc_m')
session.add_all([ulc_t, ulc_p, ulc_m])
clc = LoadCase(title='clc', load_cases=[ulc_t, ulc_p, ulc_m])
session.add(clc)
session.commit()
This generates the following tables:
+------------+ +---------------------------------+
| load_cases | | load_case_sub_load_association |
+----+-------+ +--------------+------------------+
| id | title | | load_case_id | sub_load_case_id |
+----+-------+ +--------------+------------------+
| 1 | ulc_t | | 4 | 1 |
| 2 | ulc_p | | 4 | 2 |
| 3 | ulc_m | | 4 | 3 |
| 4 | clc | +--------------+------------------+
+---+--------+
So far so good.
However, I now want to add an extra column to the association table containing a float value, for example:
+------------------------------------------+
| load_case_sub_load_association |
+--------------+------------------+--------+
| load_case_id | sub_load_case_id | factor |
+--------------+------------------+--------+
| 4 | 1 | 1.5 |
| 4 | 2 | 1.0 |
| 4 | 3 | -2.7 |
+--------------+------------------+--------+
I added a column to the association table like this:
load_case_sub_load_association = Table(
"load_case_sub_load_association",
Base.metadata,
Column("load_case_id", String, ForeignKey("load_cases.id")),
Column("sub_load_case_id", String, ForeignKey("load_cases.id")),
Column("factor", Float) # <-- new column
)
This produces the correct table layout but the factor
is empty of cause (NULL
).
I don't know how to amend the relationship('LoadCase', ...)
to be able to add values to that column ...
At some point I read that I need to use an association object and I found an example (https://stackoverflow.com/a/62378982/3006060), but that actually constructed a many-to-many relationship between different object types, and I was not able to bring the two solutions together ...
How do I build a SQLAlchemy many-to-many relationship on a single table with extra data in the related association table? How would I feed in the extra data when the association is built and how would I retrieve the data again from the DB?
PS: I am not baking the factor into the LoadCase object itself, because a loadcase can pop up in different scenarios with different factors!