-
-
Notifications
You must be signed in to change notification settings - Fork 675
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Many to many relationship between a table and itself #545
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. |
Looks the same as this #89 |
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 |
Use "remote_side" in "sa_relationship_kwargs"
|
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.
` |
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 |
First Check
Commit to Help
Example Code
Description
I want to be able to create a many to many relationship between a table and itself. In my example, I have a table
Node
and a tableEdge
which I'm using to represent a graph. I want the tableEdge
to work as a link table establishing a many to many relationship between the tableNode
and itself. Like that, I would be able to benefit from the SQLModel's lazy loading when querying the nodes and also get the in and out adjacency lists for each node very efficiently.Please, let me know if it's already possible somehow.
Wanted Solution
The solution I am thinking about is to add a
field
parameter to theRelationship
callable. Check out my wanted code.Wanted Code
Alternatives
I couldn't think about any decent alternative. Please, let me know if you have another idea.
Operating System
Linux
Operating System Details
No response
SQLModel Version
0.0.6
Python Version
Python 3.9.12
Additional Context
No response
The text was updated successfully, but these errors were encountered: