Many to many relationship between a table and itself #1456
-
First Check
Commit to Help
Example Codefrom typing import List, Optional
from sqlmodel import Field, Relationship, SQLModel
class Edge(SQLModel, table=True):
from_node_id: Optional[int] = Field(default=None, foreign_key="node.id", primary_key=True)
to_node_id: Optional[int] = Field(default=None, foreign_key="node.id", primary_key=True)
class Node(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
from_nodes: List["Node"] = Relationship(back_populates="to_nodes", link_model=Edge)
to_nodes: List["Node"] = Relationship(back_populates="from_nodes", link_model=Edge) DescriptionI want to be able to create a many to many relationship between a table and itself. In my example, I have a table Please, let me know if it's already possible somehow. Wanted SolutionThe solution I am thinking about is to add a Wanted Codefrom typing import List, Optional
from sqlmodel import Field, Relationship, SQLModel
class Edge(SQLModel, table=True):
from_node_id: Optional[int] = Field(default=None, foreign_key="node.id", primary_key=True)
to_node_id: Optional[int] = Field(default=None, foreign_key="node.id", primary_key=True)
class Node(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
from_nodes: List["Node"] = Relationship(back_populates="to_nodes", link_model=Edge, field="to_node_id")
to_nodes: List["Node"] = Relationship(back_populates="from_nodes", link_model=Edge, field="from_node_id") AlternativesI couldn't think about any decent alternative. Please, let me know if you have another idea. Operating SystemLinux Operating System DetailsNo response SQLModel Version0.0.6 Python VersionPython 3.9.12 Additional ContextNo response |
Beta Was this translation helpful? Give feedback.
Replies: 6 comments
-
What you can already do is set up relationships back-and-forth between the
Here is an example: from typing import Optional
from sqlmodel import Field, Relationship, SQLModel
class Node(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
edges_out: list["Edge"] = Relationship(
back_populates="from_node",
sa_relationship_kwargs={
"foreign_keys": "Edge.from_node_id",
"lazy": "selectin",
},
)
edges_in: list["Edge"] = Relationship(
back_populates="to_node",
sa_relationship_kwargs={
"foreign_keys": "Edge.to_node_id",
"lazy": "selectin",
},
)
class Edge(SQLModel, table=True):
from_node_id: Optional[int] = Field(
default=None,
foreign_key="node.id",
primary_key=True,
)
from_node: Optional[Node] = Relationship(
back_populates="edges_out",
sa_relationship_kwargs={"foreign_keys": "Edge.from_node_id"},
)
to_node_id: Optional[int] = Field(
default=None,
foreign_key="node.id",
primary_key=True,
)
to_node: Optional[Node] = Relationship(
back_populates="edges_in",
sa_relationship_kwargs={"foreign_keys": "Edge.to_node_id"},
)
def __repr__(self) -> str:
return f"Edge({self.from_node_id} -> {self.to_node_id})" The This allows you to quite a few things already. To get adjacency lists however you will still need the additional "hop" via the related Here is a demo: def main() -> None:
from sqlmodel import Session, create_engine, select
engine = create_engine("sqlite:///", echo=True)
SQLModel.metadata.create_all(engine)
session = Session(engine)
n1, n2, n3 = Node(), Node(), Node()
session.add_all([n1, n2, n3])
e1 = Edge(from_node=n1, to_node=n2)
e2 = Edge(from_node=n2, to_node=n3)
e3 = Edge(from_node=n1, to_node=n3)
session.add_all([e1, e2, e3])
session.commit()
nodes = session.execute(select(Node)).scalars().all()
for node in nodes:
print(f"{node.id=}")
print(f" {node.edges_out=}")
print(f" {node.edges_in=}")
print(f" to_nodes={[edge.to_node.id for edge in node.edges_out]}")
print(f" from_nodes={[edge.from_node.id for edge in node.edges_in]}")
if __name__ == "__main__":
main() Output:
It would be really nice, if we could define a Support for a custom association proxy might be a worthwhile feature in the future, but should maybe not be high priority IMHO. |
Beta Was this translation helpful? Give feedback.
-
Looks the same as this #89 |
Beta Was this translation helpful? Give feedback.
-
Thanks @daniil-berg, your answer already helped to improve my understanding about advanced usage of relationships! In your answer above you have solved the case (as you stated) that While this should also be true for my use case I need also that edges can have multiple incoming and outgoing nodes. Just think about a receipe where (simplified) Ideally, the I tried to update your solution by setting in the |
Beta Was this translation helpful? Give feedback.
-
Use "remote_side" in "sa_relationship_kwargs"
|
Beta Was this translation helpful? Give feedback.
-
This way, you can still retrieve the incoming and outgoing edges for each node using the relationships defined in the Node class. The downside is that you have to query both relationships separately when retrieving the adjacency lists.
` |
Beta Was this translation helpful? Give feedback.
-
This seems to work for me for a self-referential many-to-many relationship with relationship attributes. class Node(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
edge_to: list["Edge"] = Relationship(
back_populates="from_node",
sa_relationship_kwargs={
"primaryjoin": "Node.id==Edge.from_node_id",
},
)
edge_from: list["Edge"] = Relationship(
back_populates="to_node",
sa_relationship_kwargs={
"primaryjoin": "Node.id==Edge.to_node_id",
},
)
class Edge(SQLModel, table=True):
from_node_id: int = Field(default=None, foreign_key="node.id", primary_key=True)
from_node: "Node" = Relationship(
back_populates="edge_to",
sa_relationship_kwargs={"primaryjoin": "Edge.from_node_id==Node.id"},
)
to_node_id: int = Field(default=None, foreign_key="node.id", primary_key=True)
to_node: "Node" = Relationship(
back_populates="edge_from",
sa_relationship_kwargs={"primaryjoin": "Edge.to_node_id==Node.id"},
)
link_type: str | None |
Beta Was this translation helpful? Give feedback.
What you can already do is set up relationships back-and-forth between the
Node
andEdge
tables, such thatNode
has a list of all outgoing edges and a list of all incoming edges andEdge
has references to its "to"- and "from"-nodes.Here is an example: